Scripting

Component: MSMQ Transport | Nuget: NServiceBus (Version: 6.x)

Example code and scripts to facilitate deployment and operational actions against MSMQ.

These examples use the System.Messaging.dll and System.Transactions.dll assemblies.

Native Send

The native send helper methods

A send involves the following actions:

  • Create and serialize headers.
  • Write a message body directly to MSMQ.

In C#

Edit
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

Edit
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#

Edit
SendMessage(
    queuePath: @"MachineName\private$\QueueName",
    messageBody: "{\"Property\":\"PropertyValue\"}",
    headers: new List<HeaderInfo>
    {
        new HeaderInfo
        {
            Key = "NServiceBus.EnclosedMessageTypes",
            Value = "MyNamespace.MyMessage"
        }
    });

In PowerShell

Edit
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:

  • Read a message from the error queue.
  • Extract the failed queue from the headers.
  • Forward that message to the failed queue name so it can be retried.

In C#

Edit
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

Edit
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

Edit
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.

It may be necessary to script the creation of extra instance specific queues. For example when using Callbacks or scale out based on Sender Side Distribution.

See also: Queue Permissions

The create queue helper methods

In C#

Edit
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

Edit
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#

Edit
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

Edit
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 create endpoint queues

In C#

Edit
CreateQueuesForEndpoint(
    endpointName: "myendpoint",
    account: Environment.UserName);

In PowerShell

Edit
# For NServiceBus 6 Enpoints 
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#

Edit
QueueCreationUtils.CreateQueue(
    queueName: "error",
    account: Environment.UserName);

QueueCreationUtils.CreateQueue(
    queueName: "audit",
    account: Environment.UserName);

In PowerShell

Edit
CreateQueue -QueueName "error" -Account $env:USERNAME
CreateQueue -QueueName "audit" -Account $env:USERNAME

Delete queues

The delete helper queue methods

In C#

Edit
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

Edit
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

Edit
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");
}
Edit
DeleteQueuesForEndpoint("myendpoint");

To delete shared queues

Edit
QueueDeletionUtils.DeleteQueue(queueName: "error");
QueueDeletionUtils.DeleteQueue(queueName: "audit");

Last modified