From 19da13783a0b629e4dd12d5f06fce3e38e505ab1 Mon Sep 17 00:00:00 2001 From: Jakub Berezanski Date: Thu, 30 Aug 2012 19:49:48 +0200 Subject: [PATCH 01/12] Displaying current machine name in window title. Passing machine name by parameters instead of hardcoded Environment.MachineName. --- MsmqFastView/Infrastructure/MsmqExtensions.cs | 18 +++++++++--------- MsmqFastView/MainWindow.xaml | 2 +- MsmqFastView/MainWindowModel.cs | 17 ++++++++++++++--- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/MsmqFastView/Infrastructure/MsmqExtensions.cs b/MsmqFastView/Infrastructure/MsmqExtensions.cs index 702a6bc..ad451f3 100644 --- a/MsmqFastView/Infrastructure/MsmqExtensions.cs +++ b/MsmqFastView/Infrastructure/MsmqExtensions.cs @@ -9,12 +9,12 @@ public static class MsmqExtensions { public static int GetNumberOfSubqueues(this MessageQueue messageQueue) { - return GetNumberOfSubqueues(messageQueue.FormatName); + return GetNumberOfSubqueues(messageQueue.MachineName, messageQueue.FormatName); } public static string[] GetSubqueueNames(this MessageQueue messageQueue) { - return GetSubqueueNames(messageQueue.FormatName); + return GetSubqueueNames(messageQueue.MachineName, messageQueue.FormatName); } /// @@ -25,10 +25,10 @@ public static string[] GetSubqueueNames(this MessageQueue messageQueue) /// Number of messages. public static int GetNumberOfMessages(this MessageQueue messageQueue) { - return GetNumberOfMessages(messageQueue.FormatName); + return GetNumberOfMessages(messageQueue.MachineName, messageQueue.FormatName); } - public static int GetNumberOfSubqueues(string queueFormatName) + public static int GetNumberOfSubqueues(string machineName, string queueFormatName) { int[] propertyIds = new int[1] { @@ -50,7 +50,7 @@ public static int GetNumberOfSubqueues(string queueFormatName) aStatus = IntPtr.Zero }; - uint returnCode = MsmqNativeMethods.MQMgmtGetInfo(Environment.MachineName, "QUEUE=" + queueFormatName, queueProperties); + uint returnCode = MsmqNativeMethods.MQMgmtGetInfo(machineName, "QUEUE=" + queueFormatName, queueProperties); aPropId.Free(); aPropVar.Free(); @@ -66,7 +66,7 @@ public static int GetNumberOfSubqueues(string queueFormatName) return (int)propertyValues[0].union.ulVal; } - public static string[] GetSubqueueNames(string queueFormatName) + public static string[] GetSubqueueNames(string machineName, string queueFormatName) { int[] propertyIds = new int[1] { @@ -88,7 +88,7 @@ public static string[] GetSubqueueNames(string queueFormatName) aStatus = IntPtr.Zero }; - uint returnCode = MsmqNativeMethods.MQMgmtGetInfo(Environment.MachineName, "QUEUE=" + queueFormatName, queueProperties); + uint returnCode = MsmqNativeMethods.MQMgmtGetInfo(machineName, "QUEUE=" + queueFormatName, queueProperties); aPropId.Free(); aPropVar.Free(); @@ -116,7 +116,7 @@ public static string[] GetSubqueueNames(string queueFormatName) return subQueueNames; } - public static int GetNumberOfMessages(string queueFormatName) + public static int GetNumberOfMessages(string machineName, string queueFormatName) { int[] propertyIds = new int[1] { @@ -138,7 +138,7 @@ public static int GetNumberOfMessages(string queueFormatName) aStatus = IntPtr.Zero }; - uint returnCode = MsmqNativeMethods.MQMgmtGetInfo(Environment.MachineName, "QUEUE=" + queueFormatName, queueProperties); + uint returnCode = MsmqNativeMethods.MQMgmtGetInfo(machineName, "QUEUE=" + queueFormatName, queueProperties); aPropId.Free(); aPropVar.Free(); diff --git a/MsmqFastView/MainWindow.xaml b/MsmqFastView/MainWindow.xaml index 43921c4..6c604b2 100755 --- a/MsmqFastView/MainWindow.xaml +++ b/MsmqFastView/MainWindow.xaml @@ -1,7 +1,7 @@  + Title="{Binding Title}" Height="600" Width="800"> diff --git a/MsmqFastView/MainWindowModel.cs b/MsmqFastView/MainWindowModel.cs index d4dc7b6..8a9214e 100755 --- a/MsmqFastView/MainWindowModel.cs +++ b/MsmqFastView/MainWindowModel.cs @@ -18,6 +18,7 @@ public MainWindowModel() { this.ApplicationVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); this.ShowOnlyNonempty = true; + this.MachineName = Environment.MachineName; this.Refresh = new DelegateCommand(o => { this.queues = null; @@ -25,7 +26,7 @@ public MainWindowModel() }); this.Purge = new DelegateCommand(o => { - foreach (MessageQueue queue in MessageQueue.GetPrivateQueuesByMachine(Environment.MachineName)) + foreach (MessageQueue queue in MessageQueue.GetPrivateQueuesByMachine(this.MachineName)) { queue.Purge(); } @@ -34,7 +35,7 @@ public MainWindowModel() }); this.PurgeAll = new DelegateCommand(o => { - foreach (MessageQueue queue in MessageQueue.GetPrivateQueuesByMachine(Environment.MachineName) + foreach (MessageQueue queue in MessageQueue.GetPrivateQueuesByMachine(this.MachineName) .SelectMany(q => GetQueueWithSubQueues(q))) { queue.Purge(); @@ -56,6 +57,16 @@ public MainWindowModel() public string ApplicationVersion { get; private set; } + public string MachineName { get; private set; } + + public string Title + { + get + { + return "MsmqFastView - " + this.MachineName; + } + } + public IEnumerable Queues { get @@ -81,7 +92,7 @@ private void InitializeQueues() this.queues = new List(); try { - foreach (MessageQueue queue in MessageQueue.GetPrivateQueuesByMachine(Environment.MachineName) + foreach (MessageQueue queue in MessageQueue.GetPrivateQueuesByMachine(this.MachineName) .Where(q => !this.ShowOnlyNonempty || q.GetNumberOfMessages() != 0) .OrderBy(mq => mq.QueueName)) { From 401a32dd021f21f790d88b6bbc0ea5c7120eb452 Mon Sep 17 00:00:00 2001 From: Jakub Berezanski Date: Thu, 30 Aug 2012 20:00:57 +0200 Subject: [PATCH 02/12] Journal queues are shown (as subqueues) for queues which have journaling enabled. As before, Purge All does not touch journal queues, nor are they taken into account by "Show only nonempty queues". --- MsmqFastView/QueueModel.cs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/MsmqFastView/QueueModel.cs b/MsmqFastView/QueueModel.cs index 746acd7..61a6282 100755 --- a/MsmqFastView/QueueModel.cs +++ b/MsmqFastView/QueueModel.cs @@ -11,6 +11,8 @@ namespace MsmqFastView { public class QueueModel : INotifyPropertyChanged { + private const string JournalQueueSuffix = @"\Journal$"; + private string path; private List messages; @@ -19,14 +21,28 @@ public QueueModel(MessageQueue queue) : this() { this.path = queue.Path; - this.Name = GetFriendlyName(queue); List subqueues = new List(); - if (queue.GetNumberOfSubqueues() > 0) + if (!queue.Path.EndsWith(JournalQueueSuffix)) { - foreach (string subQueueName in queue.GetSubqueueNames()) + this.Name = GetFriendlyName(queue); + + // journal queues are only accessible from the local machine + if (queue.UseJournalQueue && queue.MachineName.Equals(Environment.MachineName, StringComparison.OrdinalIgnoreCase)) { - subqueues.Add(new QueueModel(queue, subQueueName)); + subqueues.Add(new QueueModel(new MessageQueue(@".\" + queue.QueueName + JournalQueueSuffix))); } + + if (queue.GetNumberOfSubqueues() > 0) + { + foreach (string subQueueName in queue.GetSubqueueNames()) + { + subqueues.Add(new QueueModel(queue, subQueueName)); + } + } + } + else + { + this.Name = "Journal"; } this.SubQueues = subqueues; From 09d2c945d1eddecdc7675bbba961babd5b6de9ef Mon Sep 17 00:00:00 2001 From: Jakub Berezanski Date: Thu, 30 Aug 2012 20:02:55 +0200 Subject: [PATCH 03/12] Total message count (main queue + subqueues) shown next to main queue name, if greater than zero. --- MsmqFastView/QueueModel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MsmqFastView/QueueModel.cs b/MsmqFastView/QueueModel.cs index 61a6282..531b20c 100755 --- a/MsmqFastView/QueueModel.cs +++ b/MsmqFastView/QueueModel.cs @@ -24,7 +24,8 @@ public QueueModel(MessageQueue queue) List subqueues = new List(); if (!queue.Path.EndsWith(JournalQueueSuffix)) { - this.Name = GetFriendlyName(queue); + var messageCount = queue.GetNumberOfMessages(); + this.Name = GetFriendlyName(queue) + (0 < messageCount ? string.Format(" ({0})", messageCount) : null); // journal queues are only accessible from the local machine if (queue.UseJournalQueue && queue.MachineName.Equals(Environment.MachineName, StringComparison.OrdinalIgnoreCase)) From 34b6f57ebe6687f0e7ac82ace49d7d49167f70ce Mon Sep 17 00:00:00 2001 From: Jakub Berezanski Date: Tue, 4 Sep 2012 15:11:25 +0200 Subject: [PATCH 04/12] Updated ReleaseNotes and TODO --- ReleaseNotes.txt | 4 ++++ TODO.txt | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt index d7cd3b0..80d2c42 100644 --- a/ReleaseNotes.txt +++ b/ReleaseNotes.txt @@ -1,3 +1,7 @@ +HEAD + - journal queue shown (when enabled) + - total message count shown in tree view + MsmqFastView 0.3.0 2011-10-09 "Agile Viewer" - added application icon - added extensibility point to add additional message details diff --git a/TODO.txt b/TODO.txt index c810727..e832e68 100755 --- a/TODO.txt +++ b/TODO.txt @@ -1,4 +1,3 @@ - - journal queue - dead-letter queue - outgoing queue - click once ? From 6973cbfd9f7ac1caf12201d8877c731159c72378 Mon Sep 17 00:00:00 2001 From: Jakub Berezanski Date: Tue, 4 Sep 2012 15:18:57 +0200 Subject: [PATCH 05/12] Support for MSMQ 3.0 (XP/2003) - no subqueues --- MsmqFastView/Infrastructure/MsmqExtensions.cs | 6 ++++-- MsmqFastView/Infrastructure/MsmqNativeMethods.cs | 1 + README | 2 +- ReleaseNotes.txt | 1 + TODO.txt | 1 - 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/MsmqFastView/Infrastructure/MsmqExtensions.cs b/MsmqFastView/Infrastructure/MsmqExtensions.cs index ad451f3..29d17ec 100644 --- a/MsmqFastView/Infrastructure/MsmqExtensions.cs +++ b/MsmqFastView/Infrastructure/MsmqExtensions.cs @@ -55,7 +55,8 @@ public static int GetNumberOfSubqueues(string machineName, string queueFormatNam aPropId.Free(); aPropVar.Free(); - if (returnCode == MsmqNativeMethods.MQ_ERROR.QUEUE_NOT_ACTIVE) + if (returnCode == MsmqNativeMethods.MQ_ERROR.QUEUE_NOT_ACTIVE + || returnCode == MsmqNativeMethods.MQ_ERROR.ILLEGAL_PROPID) { return 0; } @@ -93,7 +94,8 @@ public static string[] GetSubqueueNames(string machineName, string queueFormatNa aPropId.Free(); aPropVar.Free(); - if (returnCode == MsmqNativeMethods.MQ_ERROR.QUEUE_NOT_ACTIVE) + if (returnCode == MsmqNativeMethods.MQ_ERROR.QUEUE_NOT_ACTIVE + || returnCode == MsmqNativeMethods.MQ_ERROR.ILLEGAL_PROPID) { return new string[0]; } diff --git a/MsmqFastView/Infrastructure/MsmqNativeMethods.cs b/MsmqFastView/Infrastructure/MsmqNativeMethods.cs index a3e256d..7ae833e 100644 --- a/MsmqFastView/Infrastructure/MsmqNativeMethods.cs +++ b/MsmqFastView/Infrastructure/MsmqNativeMethods.cs @@ -47,6 +47,7 @@ public static class PROPID_MGMT_QUEUE public static class MQ_ERROR { public const uint QUEUE_NOT_ACTIVE = 0xC00E0004; + public const uint ILLEGAL_PROPID = 0xC00E0039; } [StructLayout(LayoutKind.Sequential)] diff --git a/README b/README index e13e48a..31c2eef 100755 --- a/README +++ b/README @@ -3,7 +3,7 @@ MSMQFastView is very, very simple MSMQ queues viewer. Its UI is optimized for sp It supports subqueues and showing body of arbitrary large messages. Requirements: - - Windows 7 or Windows Server 2008 R2 + - Windows XP / Windows Server 2003 (or higher) - MSMQ - .NET 4.0 diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt index 80d2c42..86cce85 100644 --- a/ReleaseNotes.txt +++ b/ReleaseNotes.txt @@ -1,6 +1,7 @@ HEAD - journal queue shown (when enabled) - total message count shown in tree view + - support for MSMQ 3.0 (without subqueues) - Windows XP/2003 MsmqFastView 0.3.0 2011-10-09 "Agile Viewer" - added application icon diff --git a/TODO.txt b/TODO.txt index e832e68..6f73b5a 100755 --- a/TODO.txt +++ b/TODO.txt @@ -12,6 +12,5 @@ - last refresh date in status bar - build script - disable "Refresh/Purge this queue" by default, to have them disabled before selecting queue - - support for older MSMQ (without subqueues) - detect when MSMQ is not installed - config option to disable RSB integration \ No newline at end of file From 4a080621aef2f10c1ab64176214c0ef27dee23fc Mon Sep 17 00:00:00 2001 From: Jakub Berezanski Date: Tue, 11 Sep 2012 21:04:05 +0200 Subject: [PATCH 06/12] Enabled connecting to remote machines. --- MsmqFastView/MachineModel.cs | 124 +++++++++++++++++++++++++++++++ MsmqFastView/MainWindow.xaml | 19 +++++ MsmqFastView/MainWindow.xaml.cs | 19 +++++ MsmqFastView/MainWindowModel.cs | 21 +++++- MsmqFastView/MsmqFastView.csproj | 1 + MsmqFastView/QueueModel.cs | 4 +- 6 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 MsmqFastView/MachineModel.cs diff --git a/MsmqFastView/MachineModel.cs b/MsmqFastView/MachineModel.cs new file mode 100644 index 0000000..7b871cf --- /dev/null +++ b/MsmqFastView/MachineModel.cs @@ -0,0 +1,124 @@ +using System; +using System.ComponentModel; +using System.Messaging; +using System.Windows; +using System.Windows.Input; +using MsmqFastView.Infrastructure; + +namespace MsmqFastView +{ + public class MachineModel : INotifyPropertyChanged + { + private string machineName; + + private string candidateMachineName; + + private bool editingMachineName; + + public MachineModel() + { + this.MachineName = Environment.MachineName; + + this.ChangeMachine = new DelegateCommand(_ => this.OnChangeMachine()); + this.ConnectToMachine = new DelegateCommand(_ => this.CanConnectToMachine(), _ => this.OnConnectToMachine()); + this.CancelChangeMachine = new DelegateCommand(_ => this.OnCancelChangeMachine()); + } + + public string MachineName + { + get + { + return this.machineName; + } + private set + { + this.machineName = value; + this.PropertyChanged.Raise(this, "MachineName"); + } + } + + public string CandidateMachineName + { + get + { + return this.candidateMachineName; + } + set + { + this.candidateMachineName = value; + this.PropertyChanged.Raise(this, "CandidateMachineName"); + this.ConnectToMachine.RaiseCanExecuteChanged(); + } + } + + public Visibility MachineNameVisibility + { + get + { + return this.editingMachineName ? Visibility.Collapsed : Visibility.Visible; + } + } + + public Visibility EditMachineNameVisibility + { + get + { + return this.editingMachineName ? Visibility.Visible : Visibility.Collapsed; + } + } + + public ICommand ChangeMachine { get; private set; } + + public DelegateCommand ConnectToMachine { get; private set; } + + public ICommand CancelChangeMachine { get; private set; } + + public event PropertyChangedEventHandler PropertyChanged; + + private void ToggleMachineNameEditor(bool editing) + { + this.editingMachineName = editing; + this.PropertyChanged.Raise(this, "MachineNameVisibility"); + this.PropertyChanged.Raise(this, "EditMachineNameVisibility"); + } + + private void OnChangeMachine() + { + this.CandidateMachineName = this.MachineName; + this.ToggleMachineNameEditor(true); + } + + private void OnCancelChangeMachine() + { + this.ToggleMachineNameEditor(false); + } + + private bool CanConnectToMachine() + { + return !string.IsNullOrEmpty(this.CandidateMachineName); + } + + private void OnConnectToMachine() + { + try + { + MessageQueue.GetPrivateQueuesByMachine(this.CandidateMachineName); + } + catch (MessageQueueException ex) + { + MessageBox.Show( + "Unable to obtain queue list from machine " + this.CandidateMachineName + ". Make sure the machine name is correct. To check if the machine is properly configured for remote MSMQ administration, try connecting to it using the MSMQ node in Computer Management/Server Manager.\n" + + "\n" + + "Details:\n" + + ex.ToString(), + "Error during reading queues", + MessageBoxButton.OK, + MessageBoxImage.Error); + return; + } + + this.MachineName = this.CandidateMachineName; + this.ToggleMachineNameEditor(false); + } + } +} diff --git a/MsmqFastView/MainWindow.xaml b/MsmqFastView/MainWindow.xaml index 6c604b2..77c1d51 100755 --- a/MsmqFastView/MainWindow.xaml +++ b/MsmqFastView/MainWindow.xaml @@ -138,6 +138,25 @@ https://github.com/whut/MsmqFastView + + + + Machine: + + + + + + + + + + Connect + Cancel + + + + diff --git a/MsmqFastView/MainWindow.xaml.cs b/MsmqFastView/MainWindow.xaml.cs index 5f72f50..f6fc151 100755 --- a/MsmqFastView/MainWindow.xaml.cs +++ b/MsmqFastView/MainWindow.xaml.cs @@ -1,4 +1,6 @@ using System.ComponentModel; +using System.Threading; +using System.Threading.Tasks; using System.Web.Script.Serialization; using System.Windows; using MsmqFastView.Infrastructure; @@ -30,5 +32,22 @@ protected override void OnClosing(CancelEventArgs e) Settings.Default.MainWindowPlacement = new JavaScriptSerializer().Serialize(this.GetPlacement()); Settings.Default.Save(); } + + private void OnEditMachineNameIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) + { + if ((bool)e.NewValue) + { + // must be deferred, otherwise Focus() will not work + Task.Factory.StartNew( + () => + { + this.tbCandidateMachineName.Focus(); + this.tbCandidateMachineName.SelectAll(); + }, + CancellationToken.None, + TaskCreationOptions.None, + TaskScheduler.FromCurrentSynchronizationContext()); + } + } } } diff --git a/MsmqFastView/MainWindowModel.cs b/MsmqFastView/MainWindowModel.cs index 8a9214e..98c3ceb 100755 --- a/MsmqFastView/MainWindowModel.cs +++ b/MsmqFastView/MainWindowModel.cs @@ -18,7 +18,8 @@ public MainWindowModel() { this.ApplicationVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); this.ShowOnlyNonempty = true; - this.MachineName = Environment.MachineName; + this.Machine = new MachineModel(); + this.Machine.PropertyChanged += (sender, e) => { if (e.PropertyName == "MachineName") { this.OnMachineNameChanged(); } }; this.Refresh = new DelegateCommand(o => { this.queues = null; @@ -57,7 +58,15 @@ public MainWindowModel() public string ApplicationVersion { get; private set; } - public string MachineName { get; private set; } + public string MachineName + { + get + { + return this.Machine.MachineName; + } + } + + public MachineModel Machine { get; private set; } public string Title { @@ -138,5 +147,11 @@ private IEnumerable GetQueueWithSubQueues(MessageQueue queue) } } } - } + + private void OnMachineNameChanged() + { + this.Refresh.Execute(null); + this.PropertyChanged.Raise(this, "Title"); + } + } } diff --git a/MsmqFastView/MsmqFastView.csproj b/MsmqFastView/MsmqFastView.csproj index 4f727fb..1a5474d 100755 --- a/MsmqFastView/MsmqFastView.csproj +++ b/MsmqFastView/MsmqFastView.csproj @@ -69,6 +69,7 @@ + diff --git a/MsmqFastView/QueueModel.cs b/MsmqFastView/QueueModel.cs index 531b20c..656b29a 100755 --- a/MsmqFastView/QueueModel.cs +++ b/MsmqFastView/QueueModel.cs @@ -27,8 +27,8 @@ public QueueModel(MessageQueue queue) var messageCount = queue.GetNumberOfMessages(); this.Name = GetFriendlyName(queue) + (0 < messageCount ? string.Format(" ({0})", messageCount) : null); - // journal queues are only accessible from the local machine - if (queue.UseJournalQueue && queue.MachineName.Equals(Environment.MachineName, StringComparison.OrdinalIgnoreCase)) + // journal queues are only accessible from the local machine (TODO: confirm) + if (queue.MachineName.Equals(Environment.MachineName, StringComparison.OrdinalIgnoreCase) && queue.UseJournalQueue) { subqueues.Add(new QueueModel(new MessageQueue(@".\" + queue.QueueName + JournalQueueSuffix))); } From 1c955217141b753c58e36a5ff1182c58b81b9fce Mon Sep 17 00:00:00 2001 From: Jakub Berezanski Date: Sun, 9 Sep 2012 16:28:01 +0200 Subject: [PATCH 07/12] Enabled viewing of remote journal queues. Showing number of messages in journal queue. --- MsmqFastView/Infrastructure/MsmqExtensions.cs | 63 ++++++++----------- .../Infrastructure/MsmqNativeMethods.cs | 1 + MsmqFastView/QueueModel.cs | 43 +++++++------ 3 files changed, 50 insertions(+), 57 deletions(-) diff --git a/MsmqFastView/Infrastructure/MsmqExtensions.cs b/MsmqFastView/Infrastructure/MsmqExtensions.cs index 29d17ec..a0a0c42 100644 --- a/MsmqFastView/Infrastructure/MsmqExtensions.cs +++ b/MsmqFastView/Infrastructure/MsmqExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Linq; using System.Messaging; using System.Runtime.InteropServices; @@ -28,43 +29,31 @@ public static int GetNumberOfMessages(this MessageQueue messageQueue) return GetNumberOfMessages(messageQueue.MachineName, messageQueue.FormatName); } - public static int GetNumberOfSubqueues(string machineName, string queueFormatName) + public static int GetNumberOfMessages(string machineName, string queueFormatName) { - int[] propertyIds = new int[1] - { - MsmqNativeMethods.PROPID_MGMT_QUEUE.SUBQUEUE_COUNT, - }; - GCHandle aPropId = GCHandle.Alloc(propertyIds, GCHandleType.Pinned); - - MsmqNativeMethods.MQPROPVARIANT[] propertyValues = new MsmqNativeMethods.MQPROPVARIANT[1] - { - new MsmqNativeMethods.MQPROPVARIANT() { vt = (short)VarEnum.VT_NULL } - }; - GCHandle aPropVar = GCHandle.Alloc(propertyValues, GCHandleType.Pinned); - - MsmqNativeMethods.MQQUEUEPROPS queueProperties = new MsmqNativeMethods.MQQUEUEPROPS() - { - cProp = 1, - aPropID = aPropId.AddrOfPinnedObject(), - aPropVar = aPropVar.AddrOfPinnedObject(), - aStatus = IntPtr.Zero - }; - - uint returnCode = MsmqNativeMethods.MQMgmtGetInfo(machineName, "QUEUE=" + queueFormatName, queueProperties); - - aPropId.Free(); - aPropVar.Free(); - - if (returnCode == MsmqNativeMethods.MQ_ERROR.QUEUE_NOT_ACTIVE - || returnCode == MsmqNativeMethods.MQ_ERROR.ILLEGAL_PROPID) - { - return 0; - } + return GetIntProperty( + machineName, + queueFormatName, + MsmqNativeMethods.PROPID_MGMT_QUEUE.MESSAGE_COUNT, + new[] { MsmqNativeMethods.MQ_ERROR.QUEUE_NOT_ACTIVE }); + } - MsmqException.Assert(returnCode == 0, string.Format(CultureInfo.InvariantCulture, "MQMgmtGetInfo returned error: {0:x8}", returnCode)); - MsmqException.Assert(((VarEnum)propertyValues[0].vt) == VarEnum.VT_UI4, "Unexpected type returned, should be " + VarEnum.VT_UI4 + ", but was " + ((VarEnum)propertyValues[0].vt) + "."); + public static int GetNumberOfMessagesInJournal(string machineName, string queueFormatName) + { + return GetIntProperty( + machineName, + queueFormatName, + MsmqNativeMethods.PROPID_MGMT_QUEUE.JOURNAL_MESSAGE_COUNT, + new[] { MsmqNativeMethods.MQ_ERROR.QUEUE_NOT_ACTIVE }); + } - return (int)propertyValues[0].union.ulVal; + public static int GetNumberOfSubqueues(string machineName, string queueFormatName) + { + return GetIntProperty( + machineName, + queueFormatName, + MsmqNativeMethods.PROPID_MGMT_QUEUE.SUBQUEUE_COUNT, + new[] { MsmqNativeMethods.MQ_ERROR.QUEUE_NOT_ACTIVE, MsmqNativeMethods.MQ_ERROR.ILLEGAL_PROPID }); } public static string[] GetSubqueueNames(string machineName, string queueFormatName) @@ -118,11 +107,11 @@ public static string[] GetSubqueueNames(string machineName, string queueFormatNa return subQueueNames; } - public static int GetNumberOfMessages(string machineName, string queueFormatName) + private static int GetIntProperty(string machineName, string queueFormatName, int propId, uint[] allowedReturnCodes) { int[] propertyIds = new int[1] { - MsmqNativeMethods.PROPID_MGMT_QUEUE.MESSAGE_COUNT, + propId, }; GCHandle aPropId = GCHandle.Alloc(propertyIds, GCHandleType.Pinned); @@ -145,7 +134,7 @@ public static int GetNumberOfMessages(string machineName, string queueFormatName aPropId.Free(); aPropVar.Free(); - if (returnCode == MsmqNativeMethods.MQ_ERROR.QUEUE_NOT_ACTIVE) + if (allowedReturnCodes != null && allowedReturnCodes.Contains(returnCode)) { return 0; } diff --git a/MsmqFastView/Infrastructure/MsmqNativeMethods.cs b/MsmqFastView/Infrastructure/MsmqNativeMethods.cs index 7ae833e..8b63d4c 100644 --- a/MsmqFastView/Infrastructure/MsmqNativeMethods.cs +++ b/MsmqFastView/Infrastructure/MsmqNativeMethods.cs @@ -40,6 +40,7 @@ public struct CALPWSTR public static class PROPID_MGMT_QUEUE { public const int MESSAGE_COUNT = 7; + public const int JOURNAL_MESSAGE_COUNT = 9; public const int SUBQUEUE_COUNT = 26; public const int QUEUE_SUBQUEUE_NAMES = 27; } diff --git a/MsmqFastView/QueueModel.cs b/MsmqFastView/QueueModel.cs index 656b29a..e169932 100755 --- a/MsmqFastView/QueueModel.cs +++ b/MsmqFastView/QueueModel.cs @@ -11,7 +11,7 @@ namespace MsmqFastView { public class QueueModel : INotifyPropertyChanged { - private const string JournalQueueSuffix = @"\Journal$"; + private const string JournalQueueName = @"JOURNAL"; private string path; @@ -22,28 +22,22 @@ public QueueModel(MessageQueue queue) { this.path = queue.Path; List subqueues = new List(); - if (!queue.Path.EndsWith(JournalQueueSuffix)) - { - var messageCount = queue.GetNumberOfMessages(); - this.Name = GetFriendlyName(queue) + (0 < messageCount ? string.Format(" ({0})", messageCount) : null); - // journal queues are only accessible from the local machine (TODO: confirm) - if (queue.MachineName.Equals(Environment.MachineName, StringComparison.OrdinalIgnoreCase) && queue.UseJournalQueue) - { - subqueues.Add(new QueueModel(new MessageQueue(@".\" + queue.QueueName + JournalQueueSuffix))); - } + var messageCount = queue.GetNumberOfMessages(); + this.Name = GetFriendlyName(queue) + (0 < messageCount ? string.Format(" ({0})", messageCount) : null); - if (queue.GetNumberOfSubqueues() > 0) - { - foreach (string subQueueName in queue.GetSubqueueNames()) - { - subqueues.Add(new QueueModel(queue, subQueueName)); - } - } + // queue properties (e.g. UseJournalQueue) are only accessible from the local machine + if (!queue.MachineName.Equals(Environment.MachineName, StringComparison.OrdinalIgnoreCase) || queue.UseJournalQueue) + { + subqueues.Add(new QueueModel(queue, JournalQueueName)); } - else + + if (queue.GetNumberOfSubqueues() > 0) { - this.Name = "Journal"; + foreach (string subQueueName in queue.GetSubqueueNames()) + { + subqueues.Add(new QueueModel(queue, subQueueName)); + } } this.SubQueues = subqueues; @@ -53,7 +47,16 @@ public QueueModel(MessageQueue queue, string subQueueName) : this() { this.path = queue.Path + ";" + subQueueName; - this.Name = subQueueName; + + if (subQueueName.Equals(JournalQueueName, StringComparison.OrdinalIgnoreCase)) + { + var messageCount = MsmqExtensions.GetNumberOfMessagesInJournal(queue.MachineName, queue.FormatName); + this.Name = "Journal" + (0 < messageCount ? string.Format(" ({0})", messageCount) : null); + } + else + { + this.Name = subQueueName; + } } private QueueModel() From ca1b57b5a4a3ba90f839eda246711632ee0e1563 Mon Sep 17 00:00:00 2001 From: Jakub Berezanski Date: Tue, 11 Sep 2012 21:04:28 +0200 Subject: [PATCH 08/12] Fixed displaying of remote queue names. Updated ReleaseNotes. --- MsmqFastView/QueueModel.cs | 24 +++++++++++++++++++----- ReleaseNotes.txt | 4 +++- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/MsmqFastView/QueueModel.cs b/MsmqFastView/QueueModel.cs index e169932..bb5e11d 100755 --- a/MsmqFastView/QueueModel.cs +++ b/MsmqFastView/QueueModel.cs @@ -99,13 +99,27 @@ public IEnumerable Messages private static string GetFriendlyName(MessageQueue queue) { - string prefix = "private$\\"; - if (queue.QueueName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + var formatName = queue.FormatName; + + // QueueName is unavailable on remote queue with DIRECT FormatName when the following conditions are met: + // * this machine is not joined to a domain, so MSMQ path translation mechanisms work only on local queues + // * the MessageQueue object does not have its private field queuePath set + // (note: queues obtained from GetPrivateQueuesByMachine DO have this field set, but those returned by e.g. Message.ResponseQueue, or constructed from format name string, DO NOT) + // in case of exception, better to display raw FormatName than fail to display the entire message list + try { - return queue.QueueName.Substring(prefix.Length); - } + string prefix = "private$\\"; + if (queue.QueueName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + return queue.QueueName.Substring(prefix.Length); + } - return queue.QueueName; + return queue.QueueName; + } + catch (MessageQueueException) + { + return queue.FormatName; + } } private void InitMessages() diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt index 86cce85..0d73375 100644 --- a/ReleaseNotes.txt +++ b/ReleaseNotes.txt @@ -1,5 +1,7 @@ HEAD - - journal queue shown (when enabled) + - fixed error while parsing response queue name + - ability to connect to remote machines (domain->domain or workgroup->domain; domain->workgroup will connect but will not display messages) + - journal queues shown (local machine: when enabled, remote: always) with message count - total message count shown in tree view - support for MSMQ 3.0 (without subqueues) - Windows XP/2003 From 3029032fc7b1dbc2af08b98593a45bb47900ebc2 Mon Sep 17 00:00:00 2001 From: Jakub Berezanski Date: Sun, 9 Sep 2012 16:32:23 +0200 Subject: [PATCH 09/12] Displaying of unhandled exceptions for easier diagnostics. --- MsmqFastView/App.xaml.cs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/MsmqFastView/App.xaml.cs b/MsmqFastView/App.xaml.cs index 1ea424d..f6e145c 100755 --- a/MsmqFastView/App.xaml.cs +++ b/MsmqFastView/App.xaml.cs @@ -1,8 +1,27 @@ -using System.Windows; +using System; +using System.Windows; namespace MsmqFastView { public partial class App : Application { + protected override void OnStartup(StartupEventArgs e) + { + base.OnStartup(e); + AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; + } + + private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) + { + MessageBox.Show( + string.Concat( + "An unexpected error has occured. Please notify the developers.", + Environment.NewLine, + Environment.NewLine, + "Details:", + Environment.NewLine, + e.ExceptionObject.ToString()), + "Unhandled exception"); + } } } From 83d06bb2780c0ac9cc1c2805acb814d4fc596fd7 Mon Sep 17 00:00:00 2001 From: Jakub Berezanski Date: Tue, 11 Sep 2012 21:04:36 +0200 Subject: [PATCH 10/12] Showing message type. For acknowledgement messages, showing acknowledgement type. --- MsmqFastView/MainWindow.xaml | 2 ++ MsmqFastView/MessageModel.cs | 8 +++++++- MsmqFastView/QueueModel.cs | 6 +++++- ReleaseNotes.txt | 1 + 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/MsmqFastView/MainWindow.xaml b/MsmqFastView/MainWindow.xaml index 77c1d51..9e134ec 100755 --- a/MsmqFastView/MainWindow.xaml +++ b/MsmqFastView/MainWindow.xaml @@ -82,6 +82,8 @@ + + diff --git a/MsmqFastView/MessageModel.cs b/MsmqFastView/MessageModel.cs index bc6ec95..6171470 100755 --- a/MsmqFastView/MessageModel.cs +++ b/MsmqFastView/MessageModel.cs @@ -8,7 +8,7 @@ public class MessageModel private MessageDetailsModel details; - public MessageModel(string queuePath, string id, string label, DateTime sent, string responseQueue, string correlationId) + public MessageModel(string queuePath, string id, string label, DateTime sent, string responseQueue, string correlationId, string messageType, string acknowledgement) { this.queuePath = queuePath; this.Id = id; @@ -16,6 +16,8 @@ public MessageModel(string queuePath, string id, string label, DateTime sent, st this.Sent = sent; this.ResponseQueue = responseQueue; this.CorrelationId = correlationId; + this.MessageType = messageType; + this.Acknowledgement = acknowledgement; } public string Id { get; private set; } @@ -28,6 +30,10 @@ public MessageModel(string queuePath, string id, string label, DateTime sent, st public string CorrelationId { get; private set; } + public string MessageType { get; private set; } + + public string Acknowledgement { get; private set; } + public MessageDetailsModel Details { get diff --git a/MsmqFastView/QueueModel.cs b/MsmqFastView/QueueModel.cs index bb5e11d..1b41fff 100755 --- a/MsmqFastView/QueueModel.cs +++ b/MsmqFastView/QueueModel.cs @@ -136,6 +136,8 @@ private void InitMessages() messageQueue.MessageReadPropertyFilter.SentTime = true; messageQueue.MessageReadPropertyFilter.ResponseQueue = true; messageQueue.MessageReadPropertyFilter.CorrelationId = true; + messageQueue.MessageReadPropertyFilter.MessageType = true; + messageQueue.MessageReadPropertyFilter.Acknowledgment = true; this.messages = messageQueue .Cast() @@ -146,7 +148,9 @@ private void InitMessages() m.Label, m.SentTime, m.ResponseQueue != null ? GetFriendlyName(m.ResponseQueue) : string.Empty, - m.CorrelationId)) + m.CorrelationId, + m.MessageType.ToString(), + m.Acknowledgment.ToString())) .ToList(); } } diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt index 0d73375..6091e7d 100644 --- a/ReleaseNotes.txt +++ b/ReleaseNotes.txt @@ -1,4 +1,5 @@ HEAD + - showing message type (normal/report/acknowledgement) and acknowledgement type - fixed error while parsing response queue name - ability to connect to remote machines (domain->domain or workgroup->domain; domain->workgroup will connect but will not display messages) - journal queues shown (local machine: when enabled, remote: always) with message count From af0c322e50e1059ffb673254e403b5d6c035ca73 Mon Sep 17 00:00:00 2001 From: Jakub Berezanski Date: Tue, 11 Sep 2012 15:36:32 +0200 Subject: [PATCH 11/12] Work around .NET 4.5 bug when accessing remote queues from a workgroup machine. --- MsmqFastView/MainWindowModel.cs | 80 ++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/MsmqFastView/MainWindowModel.cs b/MsmqFastView/MainWindowModel.cs index 98c3ceb..c986762 100755 --- a/MsmqFastView/MainWindowModel.cs +++ b/MsmqFastView/MainWindowModel.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Linq; using System.Messaging; +using System.Reflection; using System.Windows; using System.Windows.Input; using MsmqFastView.Infrastructure; @@ -16,7 +17,7 @@ public class MainWindowModel : INotifyPropertyChanged public MainWindowModel() { - this.ApplicationVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); + this.ApplicationVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); this.ShowOnlyNonempty = true; this.Machine = new MachineModel(); this.Machine.PropertyChanged += (sender, e) => { if (e.PropertyName == "MachineName") { this.OnMachineNameChanged(); } }; @@ -101,7 +102,11 @@ private void InitializeQueues() this.queues = new List(); try { - foreach (MessageQueue queue in MessageQueue.GetPrivateQueuesByMachine(this.MachineName) + IEnumerable queueSource = MessageQueue.GetPrivateQueuesByMachine(this.MachineName); + + queueSource = ApplyNet45WorkaroundIfNeeded(queueSource); + + foreach (MessageQueue queue in queueSource .Where(q => !this.ShowOnlyNonempty || q.GetNumberOfMessages() != 0) .OrderBy(mq => mq.QueueName)) { @@ -127,6 +132,77 @@ private void InitializeQueues() } } + private static IEnumerable ApplyNet45WorkaroundIfNeeded(IEnumerable queueSource) + { + // 4.0 RTM -> 4.0.30319.1 + // 4.0.1 -> 4.0.30319.232 + // 4.0.2 -> 4.0.30319.245 + // 4.0.3 -> 4.0.30319.276 + // 4.5 Dev -> 4.0.30319.17020 + // 4.5 Beta -> 4.0.30319.17379 + // 4.5 RC -> 4.0.30319.17626 + // 4.5 RTM -> 4.0.30319.17929 + + // we want to do it only on 4.5 (all releases) + if (Environment.Version.Major == 4 + && Environment.Version.Minor == 0 + && Environment.Version.Build == 30319 + && Environment.Version.Revision >= 17000) + { + queueSource = queueSource.Select(mq => WorkAroundNet45FormatNameBug(mq)); + } + return queueSource; + } + + private static MessageQueue WorkAroundNet45FormatNameBug(MessageQueue mq) + { + if (mq == null) + { + return mq; + } + + // In .NET 2.0 and 4.0, if queue is constructed from format name ("FormatName:...."), + // accessing the FormatName property results in a simple substring operation (to trim the "FormatName:" prefix). + + // In .NET 4.5, some overzealous Microsoft programmer thought it was insufficient, + // and if the private queuePath field is set, the FormatName property calls ResolveFormatNameFromQueuePath, + // which calls the native MQPathNameToFormatName API, which does not support remote paths on workgroup (non-domain joined) machines. + // If the private queuePath field is NOT set, the old string manipulation code path is used. + // The computed format name is then cached in a field. + + // (MessageQueue.GetPrivateQueuesByMachine DOES set this field. The MessageQueue constructor does NOT.) + + // The workaround is to clear the queuePath field, then access FormatName to cause the format name to be cached. + // Afterwards, queuePath may be restored. + // This effectively simulates .NET 4.0 behavior. + + var field = mq.GetType().GetMember("queuePath", MemberTypes.Field, BindingFlags.Instance | BindingFlags.NonPublic).SingleOrDefault() as FieldInfo; + + // no field? apparently we are running on a future framework; the internals have changed -> don't touch anything + if (field == null) + { + return mq; + } + + // save existing queuePath value + var savedPath = field.GetValue(mq); + if (savedPath == null) + { + // huh? nothing we can do + return mq; + } + + // clear the field + field.SetValue(mq, null); + + // trigger caching of format name + var formatName = mq.FormatName; + + // restore queuePath value + field.SetValue(mq, savedPath); + return mq; + } + private IEnumerable GetQueueWithSubQueues(MessageQueue queue) { if (this.ShowOnlyNonempty && queue.GetNumberOfMessages() == 0) From 6a82781703a87e3b87dd7cc1a98de9c12a3ae35d Mon Sep 17 00:00:00 2001 From: Jakub Berezanski Date: Tue, 11 Sep 2012 21:50:10 +0200 Subject: [PATCH 12/12] Some notes about connecting to remote machines. --- README | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README b/README index 31c2eef..63c90a1 100755 --- a/README +++ b/README @@ -7,5 +7,17 @@ Requirements: - MSMQ - .NET 4.0 +Remote connection notes: + - with both machines in the same domain, no special configuration is needed + - domain client -> workgroup server: + * only queue names will be shown, viewing messages will fail (Vista+ OS limitation; MSMQ MMC snap-in has the same problem) + * requires accounts with matching names and passwords on both computers, the client may use a domain or local account + - workgroup client -> domain server: + * viewing messages requires: + + turning off RPC security (uncheck "Disable un-authenticated RPC calls" in MSMQ properties on the server) + + for each queue, granting "Receive Message" and/or "Receive Journal Message" to "ANONYMOUS LOGON" pseudo-user (on the other hand, MSMQ MMC snap-in requires only "Peek" permission) + * requires accounts with matching names and passwords on both computers, must be a local account on the server + - works for accounts with administrative privileges on the server; not tested with limited accounts + Homepage: https://github.com/whut/MsmqFastView License: MIT