Tuesday, 24 July 2018

Getting started with Transactions in WCF

Transactions is a powerful concept in many parts of the software industry, they ensure that two or more procedures are either all carried out and persisted, or not any of them. This ensures that data is consistent, that the operation that performs the procedures are atomic, it is isolated and durable (ACID-principle). But what about WCF? Is it possible to easily support transactions in WCF in a full-stack scenario? Can you try to carry out two or more WCF service calls and either have all the calls changes on data in for example a SQL Server database persisted or not any of them? Is it possible to make a transaction that spans two or more WCF service calls? Yes it is! I have created a sample solution here that you can clone with Git:

git clone https://toreaurstad@bitbucket.org/toreaurstad/wcfdemotransactions.git

The repository with the code sample is available as a public repository on Bitbucket where you can view the code here: https://bitbucket.org/toreaurstad/wcfdemotransactions/src/master/ The code sample is a full-stack WPF application with a backend implemented in WCF serviced under WAS / IIS and the data layer uses Entity Framework and Model first (EDMX). This scenario will display that we can implement transactions that span multiple WCF calls and be able to either commit the update of data in the database that all these WCF calls inflict, or abort them all, i.e. a transaction. The GUI looks like this:


Enabling transactions for the WCF service

First off, enable transactionflow on the binding of the service (web.config)

  <system.serviceModel>

    <bindings>
      <wsHttpBinding>
        <binding name="wsHttpBindingWithTransactionFlow"  transactionFlow="true" >
          <security>
            <transport clientCredentialType="None"></transport>
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>

    <services>
      <service name="WcfTransactionsDemo.ServiceImplementation.SampleServiceImplementation" behaviorConfiguration="SampleServiceBehavior">
        <endpoint bindingConfiguration="wsHttpBindingWithTransactionFlow" binding="wsHttpBinding" address="http://localhost/WcfTransactionsDemo.Host/sampleservice.svc" contract="WcfTransactionsDemo.Common.ServiceContract.ISampleServiceContract"></endpoint>
      </service>
    </services>

 
Next, define that the transactionflow from the client is mandatory in the WCF methods that will support this in the Service Contract of the WCF service: (this is done setting the TransactionFlow attribute to Mandatory on the WCF service methods (operations) that will join a transaction flowed downstream from the client.

 [ServiceContract(Namespace = Constants.ServiceContractsNamespace)]
    public interface ISampleServiceContract
    {

        [OperationContract]
        [FaultContract(typeof(FaultDataContract))]
        [TransactionFlow(TransactionFlowOption.NotAllowed)]
        List GetAllCustomers();

        [OperationContract]
        [FaultContract(typeof(FaultDataContract))]
        [TransactionFlow(TransactionFlowOption.NotAllowed)]
        List GetAllProducts();

        [OperationContract]
        [FaultContract(typeof(FaultDataContract))]
        [TransactionFlow(TransactionFlowOption.Mandatory)]
        string PlaceOrder(OrderDataContract order);

        [OperationContract]
        [FaultContract(typeof(FaultDataContract))]
        [TransactionFlow(TransactionFlowOption.Mandatory)]
        string AdjustInventory(int productId, int quantity);

        [OperationContract]
        [FaultContract(typeof(FaultDataContract))]
        [TransactionFlow(TransactionFlowOption.Mandatory)]
        string AdjustBalance(int customerId, decimal amount);

    }
Next, specify the transaction isolation level of the WCF service implementation, using a ServiceBehavior attribute.

    [ServiceBehavior(TransactionIsolationLevel = IsolationLevel.Serializable, TransactionTimeout = "00:00:30")]
    public class SampleServiceImplementation : ISampleServiceContract
    {
Serializable is default in .NET and provides the consistency, it is though not recommended in high traffic scenarios as it causes too much database locking. (ReadCommitted can then be used for instance instead) Each WCF method in the service implementation that will join a transaction flowing from the client now specifies this with an OperationBehavior attribute, for example as the sample solution's AdjustBalance method:
         [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)] 
        public string AdjustBalance(int customerId, decimal amount)
        {

Setting up a transaction at the client side

The WCF service is now configured to support transactions in our sample demo. Moving on next to the app.config file, updating service reference should set the transaction flow attribute correct. Note that the servicePrincipalName in this demo must be adjusted to match your computer's name (or use localhost).

<system.serviceModel>
        <bindings>
            <wsHttpBinding>
                <binding name="WSHttpBinding_ISampleServiceContract" transactionFlow="true"  />
            </wsHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost/WcfTransactionsDemo.Host/sampleservice.svc"
                binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_ISampleServiceContract"
                contract="SampleService.ISampleServiceContract" name="WSHttpBinding_ISampleServiceContract">
                <identity>
                    <servicePrincipalName value="host/AlienHivemind" />
                </identity>
            </endpoint>
        </client>
    </system.serviceModel>

The demo of this article works in the following manner :
  • Client selects a row from the list of customers in the GUI
  • Client selects a row from the list of products in the GUI
  • Client enters a quantity of the selected product to order
  • Client clicks Place Order to place the order
If the client does this, three WCF service calls are invoked. If no transactions were done, the client could cause inconsistent data in the database. There are three WCF calls (this scenario is perhaps not so realistic, but it gives you the idea of WCF calls that are related and should all succeed or neither succeed. Ignore for now the semantics and just accept the fact that there are multiple WCF calls that must succeed or not any of them).

Now try to do the following: Do not select a customer but select a product. Also enter a quantity such as 10. Then click the button to place an order. What happens is that the WCF service expects the client to have selected a customer and a product. Since the client has not selected a customer, the AdjustBalance() methods throws a FaultException at the WCF service. The method AdjustInventory() however succeeds. If there was no transaction scope at the client side, you would see that the InStock / OnHand value is reduced, but there are no Balance reduction on either Customer, since the client forgot to select a Customer. Actually, using WCF transaction, it is possible to roll back data and get consistent data still - since the client defines a transaction. The client does it in the following manner:


    private void PlaceOrderCommandExecute(object obj)
        {
            using (var client = new SampleServiceContractClient())
            {

                using (var transactionScope = new TransactionScope())
                {
                    try
                    {


                        string orderPlacement = client.PlaceOrder(new OrderDataContract
                        {
                            CustomerId = SelectedCustomer != null ? SelectedCustomer.CustomerId : 0,
                            ProductId = SelectedProduct != null ? SelectedProduct.ProductId : 0,
                            Quantity = Quantity
                        });
                        MessageBox.Show("Placing order: " + orderPlacement);



                        string adjustedInventory = client.AdjustInventory(SelectedProduct != null ?
                            SelectedProduct.ProductId : 0, -1 * Quantity);

                        MessageBox.Show("Adjusting inventory: " + adjustedInventory);


                        string adjustedBalance = client.AdjustBalance(SelectedCustomer != null ?
                            SelectedCustomer.CustomerId : 0, -1 * (SelectedProduct != null && SelectedProduct.Price > 0 ? SelectedProduct.Price.Value : 0) * Quantity);

                        MessageBox.Show("Adjusting balance: " + adjustedBalance);

                        transactionScope.Complete(); 

                    }
                    catch (FaultException err)
                    {
                        MessageBox.Show("FaultException: " + err.Message);
                    }
                    catch (ProtocolException perr)
                    {
                        MessageBox.Show("ProtocolException: " + perr.Message);
                    }
                }

                try
                {
                    LoadCustomers();
                    LoadProducts();
                }
                catch (Exception err)
                {
                    Console.WriteLine(err.Message);
                }
            }
        }

As the client code shows, there are multiple WCF calls (three WCF calls) and the second call gave a FaultException when the client did not enter a Customer. The change inflicted was not persisted to the database and we managed to keep a consistent content of our two tables and the transaction rolled back the persistence of data data Entity Framework was about to inflict - accross multiple WCF calls. Transaction support is rather easy to add to your WCF services and at the client side there is little code that must be writted to ensure that multiple WCF calls inflict consistent change in data. Adding WCF transactions are a much more elegant way to add transaction support to your API accross multiple WCF methods than manually trying to undo WCF operations or refactor / rewrite much code to achieve what you always want with your API - to persist data supporting all four ACID principles. Note that you must use SQL Server database (I have used SqlExpress) and give access to the database I have added a SQL script for (Transactionsdemo.sql) to the app pool user so that the database TransactionsDemo can be accessed and updated (grant db_datareader and db_datawriter access in SQL Management Studio). Hope you found this sample interesting.

Monday, 23 July 2018

Using MSMQ with WCF and message security

This article will present how you can get started using MSMQ as the communication protocol with WCF and apply message level security. MSMQ has got some strengths compared to HTTP requests and other communication protocols:
  • Requests are default durable, that is if the server is down, the client can send the messages to the server later when it is back again
  • There are no responses as the protocol is one-way and sometimes avoiding a response is a gain
  • All requests are queued and can be made ordered. Using transactional MSMQ queues allows requests only be sent once
  • It can utilize features such as Journal, system queues such as poison messages and dead letters and integrates well with Biztalk
  • It is the most resilient and sturdy communication protocol around and works well also in server-server scenarios and for clients
It also has some weaknesses:
  • There are no responses, so it requires to inspect the queue(s) if something went wrong
  • If there are a lot of messages, chances are that MSMQ queues will go full - it is not the quickest protocol
  • Clients must also have MSMQ installed, not default set up in Windows
  • There is a learning curve for developers and users as MSMQ is less known protocol compared to HTTP, HTTPS and TCP
I have created a sample to get you started with WCF and MSMQ. It will support Message-level security with self signed certificates for client and server. First clone this repo with Git:
git clone https://toreaurstad@bitbucket.org/toreaurstad/demonetmsmqwcfgit.git

Open up the solution in Visual Studio (2017 or 2015). First off, the solution needs to set up self signed certificates for client and server on your developer PC. Run the Script in the Scripts folder in the Host project, the Powershell script is: CreateCertificatesMsmqDemo.ps1 Run it as admin, as it will use Powershell to generate a new certificate, copy over Openssl.exe to c:\openssl folder (if you already have c:\openssl populated, you might want to change this) and convert the certificate to have a RSA format instead of CNG. There are more setup to do, such as selecting your web site in Internet Information Server admin (inetmgr) and setting up enabled protocols to http, net.msmq for the web site for this solution. You need to install MSMQ as a feature in Windows with required subfeatures. Also after the certificates are installed, right click them (in MMC you select Local Computer and Personal certificate store) and select Manage private keys. Now adjust security settings here so that your App Pool user can access the certificates for MSMQ.

Here is where you in Inetmgr (IIS Admin) set up enabled protocols to MSMQ for the web site of this article:
Moving on to the solution itself, I will describe the implementation details. Note that the creation of the MSMQ queue is helped programatically, as the queue cannot be created automatically by WCF. I created a ServiceHostFactory class to create the MSMQ queue that is used in the communication between the client and server.
using System;
using System.Configuration;
using System.Messaging;
using System.ServiceModel;
using System.ServiceModel.Activation;

namespace WcfDemoNetMsmqBinding.Host
{
    public class MessageQueueFactory : ServiceHostFactory
    {

        public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
        {
            CreateMsmqIfMissing();
            return base.CreateServiceHost(constructorString, baseAddresses);
        }

        public static void CreateMsmqIfMissing()
        {
            string queueName = string.Format(@".\private$\{0}", MessageQueueName);
            if (!MessageQueue.Exists(queueName))
            {
                MessageQueue createdMessageQueue = MessageQueue.Create(queueName, true);
                string usernName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
                createdMessageQueue.SetPermissions(usernName, MessageQueueAccessRights.FullControl);

            }
        }

        public static string MessageQueueName
        {
            get
            {
                return ConfigurationManager.AppSettings["QueueName"];

            }
        }

    }
}
This class will create the MessageQueue if it is missing. The QueueName is an appsetting in web config. The .svc file for the Msmq service contains a reference to the factory.

<%@ ServiceHost Language="C#" Debug="true" Service="WcfDemoNetMsmqBinding.Host.MessageQueueService" Factory="WcfDemoNetMsmqBinding.Host.MessageQueueFactory" %>

Now, the setup of the wcf service is done declaratively in web.config (it could be done in code, but I chose to use web.config for most of this sample for defining the MSMQ service). This is the web.config that define the MSMQ service, as you can see it is not very extensive to get started with MSMQ and WCF:

<?xml version="1.0"?>
<configuration>

  <appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
    <add key="QueueName" value="DemoQueue3" />
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.6.1" />
    <httpRuntime targetFramework="4.6.1"/>
  </system.web>
  
  <system.serviceModel>

    <services>
      <service name="WcfDemoNetMsmqBinding.Host.MessageQueueService" behaviorConfiguration="NetMsmqBehavior">
        <endpoint contract="WcfDemoNetMsmqBinding.Host.IMessageQueueService" name="NetMsmqEndpoint" 
                  binding="netMsmqBinding" bindingConfiguration="NetMsmq" address="net.msmq://localhost/private/DemoQueue3" />
      </service>    
    </services>

    <bindings>
      <netMsmqBinding>
        <binding name="NetMsmq" durable="true" exactlyOnce="true" receiveErrorHandling="Move" useActiveDirectory="False" queueTransferProtocol="Native">
          <security mode="Message">
            <message clientCredentialType="Certificate"/>
          </security>
        </binding>
      </netMsmqBinding>
    </bindings>
    
      
    <behaviors>
      <serviceBehaviors>
        <behavior name="NetMsmqBehavior">
          <!-- To avoid disclosing metadata information, set the values below to false before deployment -->
          <serviceMetadata httpGetEnabled="true" />
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="true"/>
        <serviceCredentials>
          <serviceCertificate findValue="MSMQWcfDemoserver" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />
            <clientCertificate>
              <certificate findValue="MSMQWcfDemoClient" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />
              <authentication certificateValidationMode="PeerTrust" />
            </clientCertificate>
        </serviceCredentials>
        
        </behavior>
      </serviceBehaviors>
    </behaviors>

  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <!--
        To browse web app root directory during debugging, set the value below to true.
        Set to false before deployment to avoid disclosing web app folder information.
      -->
    <directoryBrowse enabled="true"/>
  </system.webServer>

</configuration>

Note that the two certificates that were generated in this sample is set up in serviceCredentials element. We define the serviceCertificae and clientCertificate here. The client takes note to point to the same queue, net.msmq://localhost/private/DemoQueue3 Note that this example is tested with the client and server on same machine. The client could have a local queue in case the server was on a different machine to support durability in the scenario of many computers The client sets up the corresponding certificates in the app.config:
 
 <?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
    <system.serviceModel>
        <bindings>
            <netMsmqBinding>
                <binding name="NetMsmqEndpoint">
                    <security mode="Message">
                        <message clientCredentialType="Certificate" algorithmSuite="Default" />
                    </security>
                </binding>
            </netMsmqBinding>
        </bindings>
      <behaviors>
        <endpointBehaviors>
          <behavior name="MsmqEndpointBehavior">
            <clientCredentials>
              <clientCertificate storeName="My" storeLocation="LocalMachine" x509FindType="FindBySubjectName"
                                  findValue="MSMQWcfDemoClient" />
              <serviceCertificate>
                <defaultCertificate storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" findValue="MSMQWcfDemoserver"/>
              </serviceCertificate>
            
            </clientCredentials>
            
          </behavior>
        </endpointBehaviors>
      </behaviors>
        <client>
            <endpoint address="net.msmq://localhost/private/DemoQueue3" binding="netMsmqBinding"
                bindingConfiguration="NetMsmqEndpoint" behaviorConfiguration="MsmqEndpointBehavior" contract="MessageQueueServiceProxy.IMessageQueueService"
                name="NetMsmqEndpoint">
              <identity>
                <dns value="MSMQWcfDemoServer"/>
              </identity>

            </endpoint>
        </client>
    </system.serviceModel>
  <system.web>
    <compilation debug="true" />
  </system.web>
</configuration>

All in all, we end up with a sample where you can communicate between a client and a service using WCF with durable and ordered requests using NetMsmqBinding. As I have presented in earlier articles, the communication is actually in the form of WCF message inside every MSMQ message. The WCF message itself is protected, as this picture shows.

(it is protected using the self signed certificate we generated)
The Powershell script InspectMessageQueueWcfContent.ps1 is included, if you want to inspect the WCF messages themselves of the queue themselves. Note that if you enable Journal on the MSMQ queue this solution creates, you can see the MSMQ messages after they have been received and consumed, using compmgmt.msc Or an alternative is to use QueueExplorer instead, available as a trial here: Queue Explorer
This tool can view the WCF message inside the MSMQ message as in my Powershell script, but also display syntax coloring and other functionality for quickly navigation of your MSMQ queues. Are MSMQ in WCF an alternative for ordinary scenarios using HTTP, TCP or Federated bindings? This article was meant at least to give a demo of the capability WCF gives developers to utilize MSMQ as the communication protocol between client and server. Hope you found this article interesting.

Sunday, 22 July 2018

Self-signed certificates in .NET with RsaCryptoServiceProvider

This article is written after experiencing how difficult it is today to create self-signed certificates that works with .NET and specificially WCF. If you use the default method in Windows, using the cmdlet New-SelfSignedCertificate in Powershell, chances are high that you will stumble upon this error: System.Security.Cryptography.CryptographicException: Invalid provider type specified. A few years back, creating certificates was way easier using makecert.exe Then Windows required the certificates to have length of minimum 1024. After some years passed, makecert.exe was deprecated. Microsoft now offers .NET developers to create a self signed certificate the cmdlet New-SelfSignedCertificate to use in development and test environments. Actually it turns out that you still must adjust the private part of certificate to use the RSACryptoServiceProvider by using OpenSSL to achieve this and then import to the certificate, sadly Microsoft gives developers tools for self-signed certificate generation that needs to use third-part libraries such as SSLR to work properly with .NET, at least this is the case with WCF.. Here is the Powershell script I ended up with:
 Write-Host "Generating a self signed certificate for client in MSMQ WCF demo"

$clientCertFile = "C:\temp\MSMQWcfDemoClient.pfx" 

$clientCertFileRsaFormatted = "C:\temp\MSMQWcfDemoClient.RSAConverted.pfx"

$cert = New-SelfSignedCertificate -Subject "CN=MSMQWcfDemoClient" -certstorelocation cert:\localmachine\my `
-NotAfter (Get-Date).AddYears(3) -KeyLength 2048 -KeySpec KeyExchange
$pwd = ConvertTo-SecureString -String ‘bongo’ -Force -AsPlainText 
$path = 'cert:\localMachine\my\' + $cert.thumbprint 
Export-PfxCertificate -cert $path -FilePath $clientCertFile -Password $pwd

$clientCertPasswordSec = ConvertTo-SecureString "bongo" -AsPlainText -Force


Write-Host "Generating a self signed certificate for server in MSMQ WCF demo"

$serverCertFile = "C:\temp\MSMQWcfDemoServer.pfx" 

$serverCertFileRsaFormatted =  "C:\temp\MSMQWcfDemoServer.RSAConverted.pfx"

$certServer = New-SelfSignedCertificate -Subject "CN=MSMQWcfDemoserver" -certstorelocation cert:\localmachine\my `
  -NotAfter (Get-Date).AddYears(3) -KeyExportPolicy Exportable -KeyLength 2048  -KeySpec KeyExchange
$pwdServer = ConvertTo-SecureString -String ‘kongo’ -Force -AsPlainText
$pathServer = 'cert:\localMachine\my\' + $certServer.thumbprint 
Export-PfxCertificate -cert $pathServer -FilePath $serverCertFile -Password $pwdServer

$serverCertPasswordSec = ConvertTo-SecureString "kongo" -AsPlainText -Force

$command = @'
cmd.exe /c c:\temp\rsaconvert.bat
'@

Write-Host "Starting bat file to convert from CNG to RSA format.." 

Invoke-Expression -Command:$command 

Write-Host "Importing RSA formatted certificates.."

Import-PfxCertificate -FilePath $clientCertFileRsaFormatted -CertStoreLocation Cert:\LocalMachine\My -Password $clientCertPasswordSec
Import-PfxCertificate -FilePath $clientCertFileRsaFormatted -CertStoreLocation Cert:\LocalMachine\root -Password $clientCertPasswordSec



Import-PfxCertificate -FilePath $serverCertFileRsaFormatted -CertStoreLocation Cert:\LocalMachine\My -Password $serverCertPasswordSec
Import-PfxCertificate -FilePath $serverCertFileRsaFormatted -CertStoreLocation Cert:\LocalMachine\root -Password $serverCertPasswordSec

The powershell script uses a bat file that calls Openssl to convert the pfx certificate to use the RSACryptoServiceProvider. This is how the bat file looks like:
echo Generating RSA format certificates for server using OpenSSL..

c:\openssl\openssl.exe pkcs12 -in "C:\temp\MSMQWcfDemoserver.pfx" -nokeys -out "C:\temp\MSMQWcfDemoserver.cer" -passin "pass:kongo"
c:\openssl\OpenSSL.exe pkcs12 -in "C:\temp\MSMQWcfDemoserver.pfx" -nocerts -out "C:\temp\MSMQWcfDemoserver.pem" -passin "pass:kongo" -passout "pass:kongo"
c:\openssl\OpenSSL.exe rsa -inform PEM -in "C:\temp\MSMQWcfDemoserver.pem" -out "C:\temp\MSMQWcfDemoserver.rsa" -passin "pass:kongo" -passout "pass:kongo"
c:\openssl\openssl.exe pkcs12 -export -in  "C:\temp\MSMQWcfDemoserver.cer" -inkey "C:\temp\MSMQWcfDemoserver.rsa" -out "C:\temp\MSMQWcfDemoserver.RSAConverted.pfx" -passin "pass:kongo" -passout "pass:kongo"


echo Generating RSA format certificates for client using OpenSSL..

c:\openssl\openssl.exe pkcs12 -in "C:\temp\MSMQWcfDemoclient.pfx" -nokeys -out "C:\temp\MSMQWcfDemoclient.cer" -passin "pass:bongo"
c:\openssl\OpenSSL.exe pkcs12 -in "C:\temp\MSMQWcfDemoclient.pfx" -nocerts -out "C:\temp\MSMQWcfDemoclient.pem" -passin "pass:bongo" -passout "pass:bongo"
c:\openssl\OpenSSL.exe rsa -inform PEM -in "C:\temp\MSMQWcfDemoclient.pem" -out "C:\temp\MSMQWcfDemoclient.rsa" -passin "pass:bongo" -passout "pass:bongo"
c:\openssl\openssl.exe pkcs12 -export -in  "C:\temp\MSMQWcfDemoclient.cer" -inkey "C:\temp\MSMQWcfDemoclient.rsa" -out "C:\temp\MSMQWcfDemoclient.RSAConverted.pfx" -passin "pass:bongo" -passout "pass:bongo"


The bat file uses OpenSSL to extract (dismantle) the public part of the certificate into a .cer file (this part could have been done with MMC). The next step is to generate a from our existing pfx file to export to a pem file and then a rsa file with OpenSSL. We then meld the .cer file and .rsa file into a converted .pfx file. This file is with the Powershell script automatically imported into the certificate store Personal (My) of certificate location Local Computer. The Powershell script also Import-PfxCertificate to Trusted Root Certification Authorities. Anyways, my goal was to give you a demo about .NET, WCF and NetMsmqBinding using message security, but I first had to get over this hurdle to be able to have some protection in WCF with certificate and had no idea that Microsoft had given developers so cumbersome tools to generate a self signed certificate to actually work with WCF. Now my MSMQ message queue is filled with encrypted and protection MSMQ messages (containing WCF message to be consumed)! :)
Note: the MSMQ queue was inspected using this Powershell script:

#
# InspectMessageQueueWcfContent.ps1
#


#
# InspectMessageQueue.ps1
#

[System.Reflection.Assembly]::LoadWithPartialName("System.Messaging") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("System.Xml") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("System.ServiceModel") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("System.IO") | Out-Null


$queuePath = ".\private$\demoqueue3"

Write-Host "Powershell MSMQ queue WCF inspector v0.1. Inspecting queue contents of the queue: $queuePath"
Write-Host ""


Run-MainDemoIterateMsmq $queuePath



Function Get-XmlFromWcfMessage([System.Messaging.Message] $msg) {
   
    $doc = New-Object System.Xml.XmlDocument;
    $messageLength = [int] $msg.BodyStream.Length


    $buffer = New-Object byte[] $messageLength

    
    $msg.BodyStream.Read($buffer, 0, $messageLength)

    $envelopeStart = Find-SoapEnvelopeStart($buffer)

    $envelopeStart = $envelopeStart - 0

    $envelopeLength = $($buffer.Length - $envelopeStart)
    #Write-Host $envelopeStart


    $stream = New-Object System.IO.MemoryStream($buffer, $envelopeStart, $envelopeLength)

    $elm = New-Object System.ServiceModel.Channels.BinaryMessageEncodingBindingElement
    $elm.ReaderQuotas.MaxStringContentLength = 10000000
    $elm.ReaderQuotas.MaxBytesPerRead = 10000000


    $msg1 = $elm.CreateMessageEncoderFactory().Encoder.ReadMessage($stream, 10000000);

    $doc.Load($msg1.GetReaderAtBodyContents());

    $msg.BodyStream.Position = 0;



    return $doc;
}

Function Find-SoapEnvelopeStart([byte[]] $stream)
{
    $i = 0;
    $j = 0;
    $prevByte = $stream[$i];
    $curByte = [byte]$j;
    for ($i = 0; $i -lt $stream.Length; $i++)
    {
        $curByte = $stream[$i];
        if ($curByte -eq [byte] 0x02 -and $prevByte -eq [byte] 0x56) {
            break;
        }
        $prevByte = $curByte;
    }
    return $i - 1;
}


Function Run-MainDemoIterateMsmq([string] $queuePath) {

$queue = New-Object System.Messaging.MessageQueue $queuePath

foreach ($message in $queue.GetAllMessages()){
  $xmlDoc = Get-XmlFromWcfMessage $message 
  Write-Host $xmlDoc.OuterXml
 } 


}