This article contains same code and scripts to facilitate deployment and operational actions against MSMQ.
These examples use the System.Messaging and System.Transactions assemblies.
When using the C# code samples, be sure to add the proper includes for both the
System.Messaging
and System.Transactions
assemblies in the program that's using these functions. When using the PowerShell scripts, include these assemblies by calling Add-Type
in the script.Native send
The native send helper methods
A send involves the following actions:
- Creating and serializing headers.
- Writing a message body directly to MSMQ.
In C#
public static void SendMessage(string queuePath, string messageBody, List<HeaderInfo> headers)
{
using (var scope = new TransactionScope())
{
using (var queue = new MessageQueue(queuePath))
using (var message = new Message())
{
var bytes = Encoding.UTF8.GetBytes(messageBody);
message.BodyStream = new MemoryStream(bytes);
message.Extension = CreateHeaders(headers);
queue.Send(message, MessageQueueTransactionType.Automatic);
}
scope.Complete();
}
}
public static byte[] CreateHeaders(List<HeaderInfo> headerInfos)
{
var serializer = new XmlSerializer(typeof(List<HeaderInfo>));
using (var stream = new MemoryStream())
{
serializer.Serialize(stream, headerInfos);
return stream.ToArray();
}
}
public class HeaderInfo
{
public string Key { get; set; }
public string Value { get; set; }
}
In PowerShell
Set-StrictMode -Version 2.0
Add-Type -AssemblyName System.Messaging
Add-Type -AssemblyName System.Transactions
Add-Type @"
public class HeaderInfo
{
public string Key { get; set; }
public string Value { get; set; }
}
"@
Function CreateHeaders {
param(
[Parameter(Mandatory=$true)]
[ValidateNotNull()]
[Hashtable] $entries
)
$headerinfos = New-Object System.Collections.Generic.List[HeaderInfo]
$entries.Keys | % {
$headerinfo = New-Object HeaderInfo
$headerinfo.Key = $_
$headerinfo.Value = $entries[$_]
$headerinfos.Add($headerinfo)
}
$serializer = New-Object System.Xml.Serialization.XmlSerializer( $headerinfos.GetType() )
$stream = New-Object System.IO.MemoryStream
try
{
$serializer.Serialize($stream, $headerInfos)
return $stream.ToArray()
}
finally
{
$stream.Dispose()
}
}
Function SendMessage {
param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $QueuePath,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $MessageBody,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[Hashtable] $Headers
)
$scope = New-Object System.Transactions.TransactionScope
$queue = New-Object System.Messaging.MessageQueue($QueuePath)
$message = New-Object System.Messaging.Message
try
{
$msgStream = New-Object System.IO.MemoryStream
$msgBytes = [System.Text.Encoding]::UTF8.GetBytes($MessageBody)
$msgStream.Write($msgBytes, 0, $msgBytes.Length)
$message.BodyStream = $msgStream
$message.Extension = CreateHeaders $Headers
$queue.Send($message, [System.Messaging.MessageQueueTransactionType]::Automatic)
$scope.Complete()
}
finally {
$scope.Dispose()
$message.Dispose()
$queue.Dispose()
}
}
Using the native send helper methods
In C#
SendMessage(
queuePath: @"MachineName\private$\QueueName",
messageBody: "{\"Property\":\"PropertyValue\"}",
headers: new List<HeaderInfo>
{
new HeaderInfo
{
Key = "NServiceBus.EnclosedMessageTypes",
Value = "MyNamespace.MyMessage"
}
});
In PowerShell
SendMessage -QueuePath 'MachineName\private$\QueueName' `
-MessageBody: '{\"Property\":\"PropertyValue\"}' `
-Headers @{ 'NServiceBus.EnclosedMessageTypes' = 'MyNamespace.MyMessage'; }
Return message to source queue
The retry helper methods
A retry involves the following actions:
- Reading a message from the error queue.
- Extracting the failed queue from the headers.
- Forwarding that message to the failed queue name so it can be retried.
In C#
public static void ReturnMessageToSourceQueue(string errorQueueMachine, string errorQueueName, string msmqMessageId)
{
var path = $@"{errorQueueMachine}\private$\{errorQueueName}";
var errorQueue = new MessageQueue(path);
{
var messageReadPropertyFilter = new MessagePropertyFilter
{
Body = true,
TimeToBeReceived = true,
Recoverable = true,
Id = true,
ResponseQueue = true,
CorrelationId = true,
Extension = true,
AppSpecific = true,
LookupId = true,
};
errorQueue.MessageReadPropertyFilter = messageReadPropertyFilter;
using (var scope = new TransactionScope())
{
var transactionType = MessageQueueTransactionType.Automatic;
var message = errorQueue.ReceiveById(msmqMessageId, TimeSpan.FromSeconds(5), transactionType);
var fullPath = ReadFailedQueueHeader(message);
using (var failedQueue = new MessageQueue(fullPath))
{
failedQueue.Send(message, transactionType);
}
scope.Complete();
}
}
}
static string ReadFailedQueueHeader(Message message)
{
var headers = ExtractHeaders(message);
var header = headers.Single(x => x.Key == "NServiceBus.FailedQ").Value;
var queueName = header.Split('@')[0];
var machineName = header.Split('@')[1];
return $@"{machineName}\private$\{queueName}";
}
public static List<HeaderInfo> ExtractHeaders(Message message)
{
var serializer = new XmlSerializer(typeof(List<HeaderInfo>));
var extension = Encoding.UTF8.GetString(message.Extension);
using (var stringReader = new StringReader(extension))
{
return (List<HeaderInfo>) serializer.Deserialize(stringReader);
}
}
public class HeaderInfo
{
public string Key { get; set; }
public string Value { get; set; }
}
In PowerShell
Set-StrictMode -Version 2.0
Add-Type -AssemblyName System.Messaging
Add-Type -AssemblyName System.Transactions
Function ReturnMessageToSourceQueue
{
param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $ErrorQueueMachine,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $ErrorQueueName,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $MessageId
)
$queuePath = '{0}\private$\{1}'-f $ErrorQueueMachine, $ErrorQueueName
$propertyFilter = New-Object System.Messaging.MessagePropertyFilter
$propertyFilter.Body = $true
$propertyFilter.TimeToBeReceived =$true
$propertyFilter.Recoverable = $true
$propertyFilter.Id = $true
$propertyFilter.ResponseQueue = $true
$propertyFilter.CorrelationId = $true
$propertyFilter.Extension = $true
$propertyFilter.AppSpecific = $true
$propertyFilter.LookupId = $true
$errorQueue = New-Object System.Messaging.MessageQueue($queuePath)
$errorQueue.MessageReadPropertyFilter = $propertyFilter
$scope = New-Object System.Transactions.TransactionScope
try
{
$transactionType = [System.Messaging.MessageQueueTransactionType]::Automatic
$message = $errorQueue.ReceiveById($MessageId, [System.TimeSpan]::FromSeconds(5), $transactionType)
$failedQueuePath = ReadFailedQueueFromHeaders -Message $message
$failedQueue = New-Object System.Messaging.MessageQueue($failedQueuePath)
$failedQueue.Send($message, $transactionType)
$scope.Complete()
}
finally {
$scope.Dispose()
}
}
Function ReadFailedQueueFromHeaders
{
param(
[Parameter(Mandatory=$true)]
[System.Messaging.Message] $message
)
$rawheaders = [System.Text.Encoding]::UTF8.GetString($message.Extension)
$reader = New-Object System.IO.StringReader($rawheaders)
$xml = [xml] $reader.ReadToEnd()
$header = $xml.ArrayOfHeaderInfo.HeaderInfo | ? Key -eq "NServiceBus.FailedQ" | Select -ExpandProperty Value
return ('{0}\private$\{1}' -f $header.Split('@')[1], $header.Split('@')[0])
}
Using the retry helper methods
ReturnMessageToSourceQueue(
errorQueueMachine: Environment.MachineName,
errorQueueName: "error",
msmqMessageId: @"c390a6fb-4fb5-46da-927d-a156f75739eb\15386");
Create queues
Queue creation can be done for a specific endpoint or queues shared between multiple endpoints.
See also: Queue Permissions
The create queue helper methods
In C#
public static class QueueCreationUtils
{
public static void CreateQueue(string queueName, string account)
{
var path = $@"{Environment.MachineName}\private$\{queueName}";
if (!MessageQueue.Exists(path))
{
using (var messageQueue = MessageQueue.Create(path, true))
{
SetDefaultPermissionsForQueue(messageQueue, account);
}
}
}
static void SetDefaultPermissionsForQueue(MessageQueue queue, string account)
{
var allow = AccessControlEntryType.Allow;
queue.SetPermissions(AdminGroup, MessageQueueAccessRights.FullControl, allow);
queue.SetPermissions(account, MessageQueueAccessRights.WriteMessage, allow);
queue.SetPermissions(account, MessageQueueAccessRights.ReceiveMessage, allow);
queue.SetPermissions(account, MessageQueueAccessRights.PeekMessage, allow);
}
static string AdminGroup = GetGroupName(WellKnownSidType.BuiltinAdministratorsSid);
static string GetGroupName(WellKnownSidType wellKnownSidType)
{
return new SecurityIdentifier(wellKnownSidType, null)
.Translate(typeof(NTAccount))
.ToString();
}
}
In PowerShell
Function CreateQueue
{
param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $QueueName,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[ValidateScript({ValidateAccount -Account $_})]
[string] $Account
)
$queuePath = '{0}\private$\{1}' -f $env:COMPUTERNAME, $QueueName
if (-Not [System.Messaging.MessageQueue]::Exists($queuePath)) {
$messageQueue = [System.Messaging.MessageQueue]::Create($queuePath, $true)
SetDefaultPermissionsForQueue -Queue $messageQueue -Account $Account
}
else {
Write-Warning "$queuepath already exists - no changes were made"
}
}
Function GetAccountFromWellKnownSid
{
param(
[Parameter(Mandatory=$true)]
[System.Security.Principal.WellKnownSidType] $WellKnownSidType
)
$account = New-Object System.Security.Principal.SecurityIdentifier $WellKnownSidType,$null
return $account.Translate([System.Security.Principal.NTAccount]).ToString()
}
Function ValidateAccount {
param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $Account
)
# Test Account is valid
$userAccount = new-object System.Security.Principal.NTAccount($Account)
try {
[void] $userAccount.Translate([System.Security.Principal.SecurityIdentifier])
return $true
}
catch [System.Security.Principal.IdentityNotMappedException] {
Write-Warning "$account does not resolve to a Windows Account"
return $false
}
}
Function SetDefaultPermissionsForQueue
{
param(
[Parameter(Mandatory=$true)]
[System.Messaging.MessageQueue] $Queue,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $Account
)
$adminGroup = GetAccountFromWellKnownSid -wellKnownSidType ([System.Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid)
$Queue.SetPermissions($AdminGroup, "FullControl", "Allow")
$Queue.SetPermissions($Account, "WriteMessage", "Allow")
$Queue.SetPermissions($Account, "ReceiveMessage", "Allow")
$Queue.SetPermissions($Account, "PeekMessage", "Allow")
}
Creating queues for an endpoint
To create all queues for a given endpoint name.
In C#
public static void CreateQueuesForEndpoint(string endpointName, string account)
{
// main queue
QueueCreationUtils.CreateQueue(endpointName, account);
// timeout queue
QueueCreationUtils.CreateQueue($"{endpointName}.timeouts", account);
// timeout dispatcher queue
QueueCreationUtils.CreateQueue($"{endpointName}.timeoutsdispatcher", account);
// retries queue
// TODO: Only required in Versions 5 and below
QueueCreationUtils.CreateQueue($"{endpointName}.retries", account);
}
In PowerShell
Function CreateQueuesForEndpoint
{
param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $EndpointName,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[ValidateScript({ValidateAccount -Account $_})]
[string] $Account,
[Parameter(HelpMessage="Only required for NSB Versions 5 and below")]
[Switch] $IncludeRetries
)
# main queue
CreateQueue -QueueName $EndpointName -Account $Account
# timeout queue
CreateQueue -QueueName "$EndpointName.timeouts" -Account $Account
# timeout dispatcher queue
CreateQueue -QueueName "$EndpointName.timeoutsdispatcher" -Account $Account
# retries queue
if ($IncludeRetries) {
CreateQueue -QueueName "$EndpointName.retries" -Account $Account
}
}
Using the create endpoint queues
In C#
CreateQueuesForEndpoint(
endpointName: "myendpoint",
account: Environment.UserName);
In PowerShell
# For NServiceBus 6 Endpoints
CreateQueuesForEndpoint -EndpointName "myendpoint" -Account $env:USERNAME
# For NServiceBus 5 and below Endpoints
CreateQueuesForEndpoint -EndpointName "myendpoint" -Account $env:USERNAME -IncludeRetries
To create shared queues
In C#
In PowerShell
Delete queues
The delete helper queue methods
In C#
public static class QueueDeletionUtils
{
public static void DeleteAllQueues()
{
var machineQueues = MessageQueue.GetPrivateQueuesByMachine(".");
foreach (var q in machineQueues)
{
MessageQueue.Delete(q.Path);
}
}
public static void DeleteQueue(string queueName)
{
var path = $@"{Environment.MachineName}\private$\{queueName}";
if (MessageQueue.Exists(path))
{
MessageQueue.Delete(path);
}
}
}
In PowerShell
Set-StrictMode -Version 2.0
Add-Type -AssemblyName System.Messaging
Function DeleteQueuesForEndpoint
{
param(
[Parameter(Mandatory=$true)]
[string] $endpointName
)
# main queue
DeleteQueue $endpointName
# timeout queue
DeleteQueue ($endpointName + ".timeouts")
# timeout dispatcher queue
DeleteQueue ($endpointName + ".timeoutsdispatcher")
# retries queue
# TODO: Only required in Versions 5 and below
DeleteQueue ($endpointName + ".retries")
}
Function DeleteQueue
{
param(
[Parameter(Mandatory=$true)]
[string] $queueName
)
$queuePath = '{0}\private$\{1}'-f [System.Environment]::MachineName, $queueName
if ([System.Messaging.MessageQueue]::Exists($queuePath))
{
[System.Messaging.MessageQueue]::Delete($queuePath)
}
}
Function DeleteAllQueues
{
foreach ($queue in [System.Messaging.MessageQueue]::GetPrivateQueuesByMachine("."))
{
[System.Messaging.MessageQueue]::Delete($queue.Path)
}
}
To delete all queues for a given endpoint
public static void DeleteQueuesForEndpoint(string endpointName)
{
// main queue
QueueDeletionUtils.DeleteQueue(endpointName);
// timeout queue
QueueDeletionUtils.DeleteQueue($"{endpointName}.timeouts");
// timeout dispatcher queue
QueueDeletionUtils.DeleteQueue($"{endpointName}.timeoutsdispatcher");
// retries queue
// TODO: Only required in Versions 5 and below
QueueDeletionUtils.DeleteQueue($"{endpointName}.retries");
}
DeleteQueuesForEndpoint("myendpoint");