Scripting

Component: MSMQ Transport | Nuget: NServiceBus (Version: 5.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.

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