Welcome to MSDN Blogs Sign in | Join | Help
WCF netTcpBinding – What to do if: “..the socket did not complete within the allotted timeout of…”

Sometimes I happened to face a odd error occurring when a client tries to connect to a WCF Service which has a TCP endpoint (netTcpBinding). It looks like this error can raise when one or more clients attempt to establish a good deal of connections in the same time.

I’m talking about the following exception, that you can observe in the WCF service traces (refer to “Configuring tracing” MSDN article to know how to enable WCF tracing):

<ExceptionType>System.TimeoutException, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>

<Message>The socket was aborted because an asynchronous receive from the socket did not complete within the allotted timeout of 00:00:05. The time allotted to this operation may have been a portion of a longer timeout.</Message>

Please note this exception can occur on the service side, while on the client side you should observe some failing connection attempts, since the server is not responding within the expected maximum time.
If you want to perform a WCF service tuning, you may want to consider many aspects: this is not the right place to explore the entire topic, I just want to stay focused on one of them which is often disregarded, but it can give us headaches during our load tests.

Let’s assume you have a burst of incoming connections towards our netTcpBinding WCF service and some of them fail during the establishment phase, if you enable WCF tracing on the service and suddenly face the exception reported above, chances are that you need to properly configure the ChannelInitializationTimeout.
The ChannelInitializationTimeout (default value: 5s) actually is the maximum time the channel can be in the initialization status before being disconnected.

How can you configure it? Let’s say you config file is like this:

<services>
<service name="Microsoft.ServiceModel.Samples.CalculatorService"
behaviorConfiguration="CalculatorServiceBehavior">
...
<endpoint address=""
binding="netTcpBinding"
bindingConfiguration="Binding1"
contract="Microsoft.ServiceModel.Samples.ICalculator" />
...
</service>
</services>

<bindings>
<netTcpBinding>
<binding name="Binding1"
closeTimeout="00:01:00"
openTimeout="00:01:00"
receiveTimeout="00:10:00"
sendTimeout="00:01:00"
transactionFlow="false"
transferMode="Buffered"
transactionProtocol="OleTransactions"
hostNameComparisonMode="StrongWildcard"
listenBacklog="10"
maxBufferPoolSize="524288"
maxBufferSize="65536"
maxConnections="10"
maxReceivedMessageSize="65536">
<security mode="None">
<transport clientCredentialType="None" />
</security>
</binding>
</netTcpBinding>
</bindings>

Unfortunately the ChannelInitializationTimeout property is not directly accessible with the netTcpBinding, therefore we need to define an equivalent customBinding that additionally contains the ChannelInitializationTimeout property explicitly configured with a value greater than 5s (for example: 1 minute).

<services>
<service name="Microsoft.ServiceModel.Samples.CalculatorService"
behaviorConfiguration="CalculatorServiceBehavior">
...
<endpoint address=""
binding="customBinding"
bindingConfiguration="Binding1"
contract="Microsoft.ServiceModel.Samples.ICalculator" />
...
</service>
</services>

<bindings>
<customBinding>
<binding name="Binding1"
closeTimeout="00:01:00"
openTimeout="00:01:00"
receiveTimeout="00:10:00"
sendTimeout="00:01:00">
<binaryMessageEncoding />
<tcpTransport maxBufferPoolSize="524288" maxReceivedMessageSize="65536" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxPendingConnections="10" channelInitializationTimeout="00:01:00"
transferMode="Buffered" listenBacklog="10" portSharingEnabled="false" teredoEnabled="false"
>
</tcpTransport>
</binding>
</customBinding>
</bindings>


It’s important to say that the ChannelInitializationTimeout property has a rather “strict” default value because its purpose is to secure the WCF service in order to prevent any Dos attacks. One possible situation when the ChannelInitializationTimeout prevents a Dos attack could be the following: let’s assume we have authentication enabled between client and service: the former must send a few bytes so that the latter can begin the authentication phase: that number of bytes is much lower than the whole message size. At this point, instead of waiting for the ReceiveTimeout (default value: 1m) expiration, the service can close the connection if the expected bytes needed to begin the authentication are not received within the ChannelInitializationTimeout, preventing a malicious client can keep connections open during the initialization phase more than expected.

The morale of the story: if you need to increase the ChannelInitializationTimeout value because of expected workload peaks, feel free to do it but pay attention: the more a client is allowed to keep a connection in the initialization status, the less your WCF service is secure.
See you smile_regular,

Andrea

Could not find file ‘C:\WINDOWS\TEMP\..dll’

Sometimes customers open technical support cases because of this (apparently simple) issue:

“System.Web.Services. Protocols.SoapException: Server was unable to process request. --> System.Io.FileNotFoudException: Could not find file ‘C:\WINDOWS\TEMP\hgza30lw.dll’.[…]”

clip_image001

When you use an ASP.NET web service/client, a temporary serialization assembly is generated by it several times, in particular a serialization code is first generated and csc.exe compiler is launched in order to create a temp assembly. The aforementioned error describes a failure in the temp assembly generation, which could be a compilation error, but also a trouble in creating the csc.exe process.

If you create a serialization assembly by using sgen.exe, you will avoid running csc.exe because you no longer need to compile temp serialization code: as a matter of fact the generated code is always the same, so it’s much better to create it once and for all from performance point of view. I generally recommend to make a pre-compiled serialization assembly for performance reasons through sgen.exe: http://msdn.microsoft.com/en-us/library/bk3w6240.aspx. But be aware of this: “These generated assemblies cannot be used on the server side of a Web service. This tool is only for Web service clients and manual serialization scenarios.”. You can’t use sgen’d assemblies on the web service side.

The error could also be caused by other software applications (for example, antivirus) which perform some kind of actions in the c:\WINDOWS\TEMP folder. To easily check this, you can configure another folder for temp serialization assemblies compilation from .NET 2.0 Service Pack 1 on. I recommend to have a look at http://blogs.msdn.com/drnick/archive/2008/06/16/serialization-temporary-assemblies.aspx to understand how to do that.

Another important troubleshooting step is to enable xmlserialization tracing in order to detect possible compilation errors of temp assembly: http://support.microsoft.com/kb/823196/en-us. Just add the following entry to your WCF service config file:

<configuration>
    <system.diagnostics>
         <switches>
            <add name="XmlSerialization.Compilation" value="4"/>
         </switches>
    </system.diagnostics>
</configuration>

The NETWORK SERVICE must have access to the c:\windows\temp folder with read/write permission. BTW the compilation traces are generated in the user temp folder and, if we’re running in IIS, it will be c:\temp (%SYSTEMROOT%\Temp). The traces won’t be written in c:\windows\temp, but in c:\temp. From the traces you could get useful info to understand if you either have any compilation error or the csc.exe process can’t be launched.

If you realize the csc.exe process didn’t get started, based on my experience this kind of problem could also be due to a resource leak: once the ASP.NET worker process is consuming to much resourse, the temp assembly build process could fail.
It might be you have too many application pools/web applications and/or you’re overloading the desktop heap.
You can monitor the desktop heap usage with a tool named DHeapMon.exe. This good post by a colleague of mine explains how to install it: http://blogs.msdn.com/alejacma/archive/2008/07/29/how-to-use-dheapmon-exe-to-troubleshoot-desktop-heap-issues.aspx

Another option could be represented by tuning the desktop heap settings for testing purposes (we can essentially increase the desktop heap space available for the non interactive desktop), but I would recommend this test only on a testing environment, as those settings are machine wide and could affect other application’s behavior.

You could check the registry values for the following keys:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SubSystems\Windows
(refer to http://blogs.msdn.com/ntdebugging/archive/2007/01/04/desktop-heap-overview.aspx  for more details)

Last but not least, make sure the "World Wide Web Publishing Service" the "Allow service to interact with desktop" checkbox on the “Log On” property page is deselected.

image

If you enable “Allow service to interact with desktop”, the worker process will be tied to the windows station “Winsta0” and, to cut a long story short, any user logon/logoff could trigger such a problem, getting w3wp.exe unable to run new processes (the new process we should be able to run is csc.exe). If you want to know more about “windows station”, the article is always the same: http://blogs.msdn.com/ntdebugging/archive/2007/01/04/desktop-heap-overview.aspx.

If you face this problem again, I do hope you will find the root cause listed in this post. smile_regular

Andrea

.Net TCP/IP Port Sharing

As stated in the .Net TCP Port Sharing MSDN article, “Windows Communication Foundation (WCF) provides a new TCP-based network protocol (net.tcp://) for high-performance communication. WCF also introduces a new system component, the Net.TCP Port Sharing Service that enables Net.TCP ports to be shared across multiple user processes.”

I recommend to have a look at the whole article, as it’s quite interesting; however what basically comes out is that we have a Windows service (Net TCP Port Sharing Service) implemented in SmsVcHost.exe which behaves more or less as http.sys in terms of connections and port sharing, but instead of taking care of HTTP connections, SmsVcHost is interested in .Net Tcp ones.

The fundamental result is that you can have several WCF services adopting the .Net TCP binding which share the same port, no matter where those services are hosted in (IIS, self- hosted, etc.) since the SmsVcHost receives the incoming connections and dispatch the messages to the right service (of course the services have to register themselves through the SmsVcHost at the beginning).

Just be aware this is not working at that way by default: you first have to enable and configure the .Net TCP Port Sharing Service, then you have to properly configure your WCF service using a NetTcpBinding through the PortSharingEnabled configuration property.
Here are the steps documented in MSDN:

I’d like to draw your attention on the Net. TCP Port Sharing configuration, especially this sentence in MSDN:

In general, care should be taken when modifying the contents of the SMSvcHost.exe.config file, because any configuration settings specified in this file affect all of the services on a computer that uses the Net.TCP Port Sharing Service. This includes applications on Windows Vista that use the TCP Activation features of the Windows Process Activation Service (WAS).

However, sometimes you may need to change the default configuration for the Net.TCP Port Sharing Service. For example, the default value for maxPendingAccepts is 2, which is a conservative value. Computers that host a large number of services that use port sharing should increase this value to achieve maximum throughput.

Based on the system workload, you could face a strange error like this:

"An error occurred while dispatching a duplicated socket: this handle is now leaked in the process. 
ID: 4972 
Source: System.ServiceModel.Activation.TcpWorkerProcess/29805701 
Exception: System.TimeoutException: This request operation sent to <
http://schemas.microsoft.com/2005/12/ServiceModel/Addressing/Anonymous> did not receive a reply within the configured timeout (00:01:00).  The time allotted to this operation may have been a portion of a longer timeout.  This may be because the service is still processing the operation or because the service was unable to send a reply message.  Please consider increasing the operation timeout (by casting the channel/proxy to IContextChannel and setting the OperationTimeout property) and ensure that the service is able to connect to the client.
Server stack trace: 
   at System.ServiceModel.AsyncResult.End[TAsyncResult](IAsyncResult result) 
   at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.End(SendAsyncResult result) 
   at System.ServiceModel.Channels.ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result) 
   at System.ServiceModel.Channels.ServiceChannelProxy.InvokeEndService(IMethodCallMessage methodCall, ProxyOperationRuntime operation) 
   at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
Exception rethrown at [0]: 
   at System.ServiceModel.AsyncResult.End[TAsyncResult](IAsyncResult result) 
   at System.ServiceModel.Activation.WorkerProcess.EndDispatchSession(IAsyncResult result) 
  Process Name: SMSvcHost 
Process ID: 1728
"

That basically means the SmsVcHost service is not able to handle with all the required .Net TCP connections, so you should properly tune your SmsVcHost.exe.config file settings:

<configuration>
   <system.serviceModel.activation>
       <net.tcp listenBacklog="10"
          maxPendingAccepts="2"
          maxPendingConnections="10"
          receiveTimeout="00:00:10"
          teredoEnabled="false">
          <allowAccounts>
             // LocalSystem account
             <add securityIdentifier="S-1-5-18"/>
             // LocalService account
             <add securityIdentifier="S-1-5-19"/>
             // Administrators account
             <add securityIdentifier="S-1-5-20"/>
             // Network Service account
             <add securityIdentifier="S-1-5-32-544" />
             // IIS_IUSRS account (Vista only)
             <add securityIdentifier="S-1-5-32-568"/>
           </allowAccounts>
       </net.tcp>
</configuration>

You must be aware of how many WCF services have registered themselves against the .NET Tcp Port Sharing Services and how many connections could be established by each one. For instance a setting like this could solve the problem and make the aforementioned exception disappear:

<net.tcp listenBacklog="100" maxPendingConnections="1000" maxPendingAccepts="10" receiveTimeout="00:00:10" teredoEnabled="false">

Maybe the maxPendingConnection could have been set to a lower value (2-300), but changing maxPendingAccepts from 2 to 10 has probably been the solution. Only after testing we could say the last word on the best tuning.

/----------------------/

And now I’d like to share a short Q/A list I wrote for a customer.

Q1: There is no api to configure the tcp-port-sharing service ("App.config File"). Different applications can set the parameters in this file differently.
        (for example

            listenBacklog="1"
            maxPendingConnections="1"
            maxPendingAccepts="1"
            receiveTimeout="00:00:10"
            teredoEnabled="false")

A1: Yes that's correct. This is due to the .Net TCP Port Sharing architecture, which uses the SMSvcHost.exe service (http://msdn.microsoft.com/en-us/library/aa702669.aspx).

Please be aware when using Http Port Sharing, WCF employs http.sys exactly as IIS 6.0 does, even though it's a self hosted web service. From: http://msdn.microsoft.com/en-us/library/ms730158.aspx:

"But because WCF and IIS 6.0 both use the kernel-mode HTTP stack (HTTP.sys), IIS 6.0 can share port 80 with other self-hosted WCF services running on the same machine".

If you change the http.sys configuration (through httpcfg/netsh or the registry: http://support.microsoft.com/Default.aspx?id=820129) you'll affect every application placed on top of it. By the way I understand your concern: editing a file is generally easier.

Q2: Tcp-Port Sharing is not activated by default.

A2: Yes that's correct. You will need to manually enable the service. Refer to http://msdn.microsoft.com/en-us/library/ms733925.aspx for further details. If the service is disabled, an exception will be thrown when the server application is started:

"Unhandled Exception: System.ServiceModel.CommunicationException: The TransportManager failed to listen on the supplied URI using the NetTcpPortSharing service: failed to start the service because it is disabled. An administrator can enable it by running 'sc.exe config NetTcpPortSharing start= demand'.. ---> System.InvalidOperationException: Cannot start service NetTcpPortSharing on computer '.'. ---> System.ComponentModel.Win32Exception: The service cannot be started, either because it is disabled or because it has no enabled devices associated with it"

Port sharing is enabled on the server by setting the PortSharingEnabled property of the NetTcpBinding or the TCP transport binding element. The client does not need to know how port sharing has been configured to use it on the server.

Q3: the default settings are not good for our application because the Service is running under the context of a specially created user. Thus this user's SID has to be included in the App.config.
In addition, the settings for maxPendingConnections  and  maxPendingAccepts must be increased.
What are recommended parameters for large applications?

A3: As far as concerns the maxPendingConnections and maxPendingAccepts settings, our defaults are intentionally conservative so that customers must opt-in to allowing large amounts of work into the system.
It's hard to recommend some values, because it depends on the applications running on the system; you have to keep into account the user could have some other services using TCP Activation with WAS, which your application has to share SMSvcHost.exe resources with. According to the Product Team, you shouldn't increase “maxPendingAccepts” too much. 5-10 would be a good number. It means it spawns 5-10 concurrent threads to pull connections.
Feel free to increase the maxPendingConnections value according to your needs (you can also set 1000, even though I'd wonder why if you needed so many connections. 100-200 can be considered a good choice).

Q4: If we activate the WAS (because we use a foreign Application that uses WAS with tcp activation), does the WAS also manipulate the App.config file?

A4: Installing WAS + non-HTTP activation shouldn't impact on the SMSvcHost.exe.config settings; to make sure I've arranged a test on my Windows 2008 server: after non-HTTP activation feature install, the SMSvcHost.exe.config hasn't been changed at all.

Q5: the WCF WAS examples in MSDN don’t mention the SMSvcHost.exe.config. Therefore I think the WAS manipulates the SMSvcHost.exe.config automatically.
Is that correct?
What happens if the tcp-Service runs with application pools under an own account?
Who add's the SID to the SMSvcHost.exe.config?

A5: As you can read at http://msdn.microsoft.com/en-us/library/aa702669.aspx:

"By default, permission to use the port sharing service is granted to system accounts (LocalService, LocalSystem, and NetworkService) as well as members of the Administrators group. Applications that allow a process running as another identity (for example, a user identity) to connect to the port sharing service must explicitly add the appropriate SID to the SMSvcHost.exe.config (these changes are not applied until the SMSvc.exe process is restarted)."

With the sample you've mentioned, there's no need to change SMSvcHost.exe.config, as the service should be running as "network service". WAS, when using non-HTTP activation, is based on the default SMSvcHost.exe.config settings, so there's no change automatically made in some way.

If your service is configured to run under an account not included in the SMSvcHost.exe.config file, it's up to you to add the SID into the config file.

Q6: in MSDN at "http://msdn.microsoft.com/en-us/library/ms733109.aspx" I dont't understand:
"the binding settings have to match on the following seven properties:
ConnectionBufferSize
ChannelInitializationTimeout
MaxPendingConnections
MaxOutputDelay
MaxPendingAccepts
ConnectionPoolSettings.IdleTimeout
ConnectionPoolSettings.MaxOutboundConnectionsPerEndpoint

Otherwise, the endpoint that is initialized first always determines the values of these properties, and endpoints added later throw a ServiceActivationException if they do not match those settings."  

What happens, if two different Applications, thas uses WAS and tcp-Activation, use different binding settings? Application A don’t know the code of Application B.

A6: That means if your service exposes more than one endpoint and they use different binding configurations, you have to make them in order to have the mentioned 7 properties set to the same value, otherwise you incur in the ServiceActivationException. This is because the SMSHost acts like a proxy for the service. If you have different bindings being used by different services, you shouldn't face any trouble.

/----------------------/

I hope I’ve clarified most aspects of .Net TCP Port Sharing feature, but if this wasn’t enough, I’d like to advise the following article: Extend Your WCF Services Beyond HTTP With WAS. I consider that a “must” if you want to start learning what happens behind the curtains.

If I’ve left any aspect uncovered, please don’t hesitate to ask!

Andrea

WCF End-To-End Tracing

I have to say WCF tracing is quite well documented in MSDN and even though this is certainly an aspect developers could take advantage of, sometimes you can get discouraged if facing tons of documents, technical details, etc. As a consequence you end up to ignore even the fundamental topics. My aim is to provide the main WCF tracing concepts in order to prevent you get disoriented in front of any WCF trace and take advantage of its powerful features.

Let’s begin from the basics. From MSDN, we have:

  • Activities: “are processing units that help the user narrow down the scope of a failure. Errors that occur in the same activity are directly related.”
  • Transfer: “Transfers between activities represent causal relationships between events in the related activities within endpoints. Two activities are related with transfers when control flows between these activities, e.g., a method call crossing activity boundaries.” In plain words, we can see transfers like relationships between activities.
  • Propagation: “Propagation provides the user with direct correlation of error traces for the same unit of processing across application endpoints, e.g., a request. Errors emitted at different endpoints for the same unit of processing are grouped in the same activity, even across application domains.”

You need to get the definitions above quite clear in your mind, so let’s have a look at a sample: I’ve used the WsHttpBinding  CalculatorService sample. Here’s the service interface:

[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
public interface ICalculator
{
    [OperationContract]
    double Add(double n1, double n2);
    [OperationContract]
    double Subtract(double n1, double n2);
    [OperationContract]
    double Multiply(double n1, double n2);
    [OperationContract]
    double Divide(double n1, double n2);
}

And this is the web.config part for the service configuration:

<system.diagnostics>
    <sources>
      <source name="System.ServiceModel"
              switchValue="Information, ActivityTracing"
              propagateActivity="true" >
        <listeners>
          <add name="xml"/>
        </listeners>
      </source>
      <source name="System.ServiceModel.MessageLogging">
        <listeners>
          <add name="xml"/>
        </listeners>
      </source>
    </sources>
    <sharedListeners>
      <add name="xml"
           type="System.Diagnostics.XmlWriterTraceListener"
                 initializeData="C:\\Users\\andreal\\Documents\\Workshop WCF\\WS\\Http\\Client\\bin\\Debug\\SRV_Traces.svclog" />
    </sharedListeners>
  </system.diagnostics>
  
  <system.serviceModel>
    <services>
      <service behaviorConfiguration="Service.ServiceBehavior" name="Microsoft.ServiceModel.Samples.CalculatorService">
          <endpoint address="" binding="wsHttpBinding" contract="Microsoft.ServiceModel.Samples.ICalculator" >
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="Service.ServiceBehavior">
          <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above 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="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>

    <diagnostics wmiProviderEnabled="true">
      <messageLogging
           logEntireMessage="true"
           logMalformedMessages="true"
           logMessagesAtServiceLevel="true"
           logMessagesAtTransportLevel="true"
           maxMessagesToLog="3000"
       />
    </diagnostics>
    
  </system.serviceModel>

The WCF client simply calls the four methods one by one and it’s configured to emit traces exactly like the service does. If you open the client traces with Microsoft Service Trace Viewer a windows like this will appear:

image

As you can see, we have 14 activities: {00000000-0000-0000-0000-000000000000} is the root activity, that is the default activity: all the other 13 activities transfer out of 0. A very powerful feature is the propagateActivity: if propagateActivity is set to true on both the client and service configuration, this activity has the same id as the one defined in the client, and described previously. From this stage we start to benefit from direct correlation across endpoints, because all traces emitted in WCF that are related to the request are in that same activity, including the response message processing. I recommend to read this very good MSDN article: Using Service Trace Viewer for Viewing Correlated Traces and Troubleshooting.

Everything seems well documented so far and you could start wondering why I wrote this post. One day a customer came out with this need:
”We decided to implement our own logging for business purposes, but needed a way to correlate messages through a chain of services. […] What fails is adding another layer of services. The incoming GUID must be passed to the outgoing header of the new request.”

You can easily correlate a C client with a S service, but what if you have C, S1, S2, Sn and need to correlate the message flow through the whole chain? I’m going to explain the solution I thought (that got the customer quite satisfied) with a sample (I think samples always make things easier).

I’ve changed the Extended Tracing sample (from http://msdn.microsoft.com/en-us/library/aa354511.aspx) introducing a “middle-tier” service. The sample produces some custom traces according to http://msdn.microsoft.com/en-us/library/aa738759.aspx. The interface is the well known ICalculator, used in most of our samples.
So I made 3 projects: a client console application, a self-hosted WCF service named Service1 which in turns calls another WCF Service named Service2.

The client generates a global activity id (calculator) and one activity id for each operation. The operation activity identifiers are propagated to Service1, which in turn send them to Service 2.

[...]
TraceSource ts = new TraceSource("ClientCalculatorTraceSource");

 // Start the calculator activity
 Guid newGuid = Guid.NewGuid();
 Trace.CorrelationManager.ActivityId = newGuid;
 ts.TraceEvent(TraceEventType.Start, 0, "Calculator Activity");

 // Create a proxy with given client endpoint configuration
 using (CalculatorProxy proxy = new CalculatorProxy())
 {
     // Save the calculator activity id to transfer back and forth from/to it
     Guid originalGuid = Trace.CorrelationManager.ActivityId;

     // Create and start the Add activity                
     // Generate a new activity id
     newGuid = Guid.NewGuid();
     // Transfer from the calculator activity to the new (Add) activity
     // The value for the "from" in the tranfer is implicit; it is the activity id 
     // previously set in Trace.CorrelationManager.ActivityId
     // The value for the "to" is explicitly passed as the newGuid parameter
     ts.TraceTransfer(0, "Transferring...", newGuid);
     // Set the new activity id in Trace.CorrelationManager.ActivityId; it is now in scope
     // for subsequently emitted traces
     Trace.CorrelationManager.ActivityId = newGuid;
     // Emit the Start trace for the new activity
     ts.TraceEvent(TraceEventType.Start, 0, "Add Activity");

     // Now make the Add request
     double value1 = 100.00D;
     double value2 = 15.99D;
     ts.TraceEvent(TraceEventType.Information, 0,
         "Client sends Add request message.");
     double result = proxy.Add(value1, value2);

     // Trace that you have received the response
     ts.TraceEvent(TraceEventType.Information, 0,
         "Client receives Add response message.");
     Console.WriteLine("Add({0},{1}) = {2}", value1, value2, result);

     // Transfer back to the Calculator activity and stop the current activity
     ts.TraceTransfer(667, "Transferring...", originalGuid);
     ts.TraceEvent(TraceEventType.Stop, 0, "Add Activity");

     // Set the calculator activity back in scope
     Trace.CorrelationManager.ActivityId = originalGuid;

[...]
 }

 ts.TraceEvent(TraceEventType.Stop, 0, "Calculator Activity");
[...]

Client,Service1 and Service2 define their own trace sources, each one writes an XML log on file system.

public double Add(double n1, double n2)
 {
     if (Trace.CorrelationManager.ActivityId == Guid.Empty)
     {
         Guid newGuid = Guid.NewGuid();
         Trace.CorrelationManager.ActivityId = newGuid;
     }

     ts.TraceEvent(TraceEventType.Start, 0, "Add Activity");
     ts.TraceEvent(TraceEventType.Information, 0, "Service1 receives Add request message. Args: {0}, {1}", n1, n2);

     ts.TraceEvent(TraceEventType.Information, 0, "Service1 sends Add request message. Args: {0}, {1}", n1, n2);

     Microsoft.ServiceModel.Samples.Client.CalculatorProxy proxy = new Microsoft.ServiceModel.Samples.Client.CalculatorProxy();
     double result = proxy.Add(n1, n1);
     proxy.Close();

     // Trace that you have received the response
     ts.TraceEvent(TraceEventType.Information, 0, "Service1 receives Add response message. Result: {0}", result);
     Console.WriteLine("Add({0},{1}) = {2}", n1, n1, result);

     //double result = n1 + n2;

     ts.TraceEvent(TraceEventType.Information, 0, "Service1 sends Add response message. Result: {0}", result);
     ts.TraceEvent(TraceEventType.Stop, 0, "Add Activity");

     return result;
 }

If you run a test and then open the trace files with the Service Trace Viewer, you will be able to see the activities correlated through the activity ids being propagated as expected.

Defining your own trace sources allows you to just enable the needed information tracing, discarding other WCF stuff (please notice the config files).

image

Of course this is just a sample, and it’s waiting for being improved by anyone of you! smile_regular Bye for now!

Andrea

WCF service startup too slow? Have you thought to CRL check?

A customer of mine was experiencing an annoying delay when calling a WCF service hosted in IIS6. He provided the following elements with the issue description:

"The delay only occurs when calling the WCF service the first time. The following calls were executed in a reasonable time. It was enough to restart the client application to reproduce the delay on the first call."

"As many assembly references the service includes, the longer the time spent to execute the first call."

To be honest the customer solution was rather complex, including several WCF services calling each other, and Sharepoint as well, but this is not fundamental to our purpose.
When looking at the WCF traces, I couldn't notice any meaningful delay: looked like the service started receiving network data after the delay was spent.
The client appeared stuck during wait on service reply. What was happening?

I thought to ask for network traces on the server, and suddenly noticed something interesting: there were several DNS requests to a host named "crl.microsoft.com". In that case the DNS request kept failing, I guess that had been increasing the delay. I filtered traffic on HTTP and DNS protocols, and found something like that:

   1: 1275   66.940689     {HTTP:35, TCP:34, IPv4:33} 10.15.0.2     10.15.2.64    HTTP   HTTP: Request, POST /NetSourcingServices/UserManagementService.svc 
   2: 1277   67.056225     {HTTP:35, TCP:34, IPv4:33} 10.15.2.64    10.15.0.2     HTTP   HTTP: Response, HTTP/1.1, Status Code = 100
   3: 1278   67.056580     {HTTP:35, TCP:34, IPv4:33} 10.15.0.2     10.15.2.64    HTTP   HTTP: HTTP Payload
   4: 1284   67.294377     {DNS:38, UDP:37, IPv4:36}  10.15.2.64    CSDC001              DNS    DNS: QueryId = 0xB1D2, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
   5: 1295   68.293987     {DNS:40, UDP:39, IPv4:19}  10.15.2.64    CSDC002              DNS    DNS: QueryId = 0xB1D2, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
   6: 1312   69.293905     {DNS:40, UDP:39, IPv4:19}  10.15.2.64    CSDC002              DNS    DNS: QueryId = 0xB1D2, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
   7: 1344   71.293828     {DNS:38, UDP:37, IPv4:36}  10.15.2.64    CSDC001              DNS    DNS: QueryId = 0xB1D2, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
   8: 1345   71.293841     {DNS:40, UDP:39, IPv4:19}  10.15.2.64    CSDC002              DNS    DNS: QueryId = 0xB1D2, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  10: 1425   75.293731     {DNS:40, UDP:39, IPv4:19}  10.15.2.64    CSDC002              DNS    DNS: QueryId = 0xB1D2, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  11: 1522   79.340669     {DNS:41, UDP:39, IPv4:19}  10.15.2.64    CSDC002              DNS    DNS: QueryId = 0x5AD3, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  12: 1537   80.340345     {DNS:42, UDP:37, IPv4:36}  10.15.2.64    CSDC001              DNS    DNS: QueryId = 0x5AD3, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  13: 1554   81.340301     {DNS:41, UDP:39, IPv4:19}  10.15.2.64    CSDC002              DNS    DNS: QueryId = 0x5AD3, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  14: 1570   82.458183     {DNS:38, UDP:37, IPv4:36}  CSDC001              10.15.2.64    DNS    DNS: QueryId = 0xB1D2, QUERY (Standard query), Response - Server failure 
  15: 1586   83.449603     {DNS:42, UDP:37, IPv4:36}  10.15.2.64    CSDC001              DNS    DNS: QueryId = 0x5AD3, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  16: 1587   83.449618     {DNS:41, UDP:39, IPv4:19}  10.15.2.64    CSDC002              DNS    DNS: QueryId = 0x5AD3, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  17: 1598   84.160484     {DNS:40, UDP:39, IPv4:19}  CSDC002              10.15.2.64    DNS    DNS: QueryId = 0xB1D2, QUERY (Standard query), Response - Server failure 
  18: 1651   87.152577     {DNS:42, UDP:37, IPv4:36}  10.15.2.64    CSDC001              DNS    DNS: QueryId = 0x5AD3, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  19: 1652   87.152592     {DNS:41, UDP:39, IPv4:19}  10.15.2.64    CSDC002              DNS    DNS: QueryId = 0x5AD3, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  20: 1728   91.777601     {DNS:46, UDP:39, IPv4:19}  10.15.2.64    CSDC002              DNS    DNS: QueryId = 0xC575, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  21: 1746   92.777358     {DNS:47, UDP:37, IPv4:36}  10.15.2.64    CSDC001              DNS    DNS: QueryId = 0xC575, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  22: 1780   93.777342     {DNS:46, UDP:39, IPv4:19}  10.15.2.64    CSDC002              DNS    DNS: QueryId = 0xC575, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  23: 1815   95.159803     {DNS:41, UDP:39, IPv4:19}  CSDC002              10.15.2.64    DNS    DNS: QueryId = 0x5AD3, QUERY (Standard query), Response - Server failure 
  24: 1818   95.457228     {DNS:42, UDP:37, IPv4:36}  CSDC001              10.15.2.64    DNS    DNS: QueryId = 0x5AD3, QUERY (Standard query), Response - Server failure 
  25: 1832   96.449083     {DNS:47, UDP:37, IPv4:36}  10.15.2.64    CSDC001              DNS    DNS: QueryId = 0xC575, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  26: 1833   96.449096     {DNS:46, UDP:39, IPv4:19}  10.15.2.64    CSDC002              DNS    DNS: QueryId = 0xC575, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  27: 1894   100.448957    {DNS:47, UDP:37, IPv4:36}  10.15.2.64    CSDC001              DNS    DNS: QueryId = 0xC575, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  28: 1895   100.448970    {DNS:46, UDP:39, IPv4:19}  10.15.2.64    CSDC002              DNS    DNS: QueryId = 0xC575, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  29: 1972   104.511458    {DNS:48, UDP:39, IPv4:19}  10.15.2.64    CSDC002              DNS    DNS: QueryId = 0xA60E, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  30: 2003   105.511227    {DNS:49, UDP:37, IPv4:36}  10.15.2.64    CSDC001              DNS    DNS: QueryId = 0xA60E, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  31: 2042   106.511190    {DNS:48, UDP:39, IPv4:19}  10.15.2.64    CSDC002              DNS    DNS: QueryId = 0xA60E, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  32: 2070   107.159070    {DNS:46, UDP:39, IPv4:19}  CSDC002              10.15.2.64    DNS    DNS: QueryId = 0xC575, QUERY (Standard query), Response - Server failure 
  33: 2086   108.456693    {DNS:47, UDP:37, IPv4:36}  CSDC001              10.15.2.64    DNS    DNS: QueryId = 0xC575, QUERY (Standard query), Response - Server failure 
  34: 2105   109.448570    {DNS:49, UDP:37, IPv4:36}  10.15.2.64    CSDC001              DNS    DNS: QueryId = 0xA60E, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  35: 2106   109.448584    {DNS:48, UDP:39, IPv4:19}  10.15.2.64    CSDC002              DNS    DNS: QueryId = 0xA60E, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  36: 2210   113.448417    {DNS:49, UDP:37, IPv4:36}  10.15.2.64    CSDC001              DNS    DNS: QueryId = 0xA60E, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  37: 2211   113.448431    {DNS:48, UDP:39, IPv4:19}  10.15.2.64    CSDC002              DNS    DNS: QueryId = 0xA60E, QUERY (Standard query), Query  for  crl.microsoft.com of type Host Addr on class Internet
  38: 2337   118.079858    {DNS:68, UDP:39, IPv4:19}  10.15.2.64    CSDC002              DNS    DNS: QueryId = 0x4B05, QUERY (Standard query), Query  for  cssqlvs01.csf.lan of type Host Addr on class Internet
  39: 2338   118.080052    {DNS:68, UDP:39, IPv4:19}  CSDC002              10.15.2.64    DNS    DNS: QueryId = 0x4B05, QUERY (Standard query), Response - Success 
  40: 2397   118.268103    {HTTP:35, TCP:34, IPv4:33} 10.15.2.64    10.15.0.2     HTTP   HTTP: Response, HTTP/1.1, Status Code = 200

The customer declared the delay was a bit more than 50 seconds: please have a look at the time between the POST and the response.. are you able to notice anything?
There are about 50 seconds between them, and a lot of DNS queries in the middle.

What is crl.microsoft.com? The CRL acronym stands for "Certificate Revocation List". The Certificate Revocation List (CRL) is a document maintained and published by a certification authority (CA) that lists certificates issued by the CA that are no longer valid. Each signed assembly was checked against the CRL before being loaded and, as you can see in the network traces, because of the network configuration the DNS wasn't able to resolve "crl.microsoft.com".

Please have a look at this article: http://support.microsoft.com/default.aspx/kb/936707 (FIX: A .NET Framework 2.0 managed application that has an Authenticode signature takes longer than usual to start): "When you start a Microsoft .NET Framework 2.0 managed application that has a Microsoft Authenticode signature, the .NET Framework 2.0 managed application takes longer than usual to start."; "This problem occurs because a .NET Framework 2.0 managed assembly that has an Authenticode signature takes longer than usual to load. The signature is always verified when the .NET Framework 2.0 managed assembly that has an Authenticode signature is loaded."

Since you’re using .NET 3.*, you could simply add the entry below to both the application config files:

   2: <configuration>
   3:     <runtime>
   4:          <generatePublisherEvidence enabled="false"/>
   5:     </runtime>
   6: </configuration>
   7:  

So if you observe a strange delay during your application start up, just check if you're trying to load Authenticode signed assembly in your application, and introduce the configuration entry above as a double check.
Cheers,

Andrea

Counters, counters, counters!

Typical (painful) troubles in the troubleshooter life are memory leaks or, generally speaking, issues related to large unexpected resource consumption. Sometimes those issues happen on production environments (we all know testing should have been done before, but this kind of problems keep on occurring on production environments, who knows why I dont know) and sometimes we’re not allowed to work on those machines in person.

Typical situation: an application dies every day at about the same time, access on the machine can’t be granted to us, an operator without any technical skills is the only person allowed to perform actions on that machine. We need to collect Performance Monitor counters and, even though we describe how to configure Perf.Mon.exe in detail, the error chances are very high.

Well, the ideal solution is LogMan.exe: it manages and schedules performance counter and event trace log collections on local and remote systems. Configuring PerfMon.exe in order to exactly retrieve the data we need to troubleshoot the issue is not always easy, besides what if you have to repeat the configuration on many servers? Through LogMan.exe you' are able to create a batch/script file to automatically configure the counters/traces you need, because LogMan.exe is a command line tool.

Let's start by opening a command window and digit:

logman create counter perf_log -c "\Processor(_Total)\% Processor Time"

Let's have a look at permon.exe now:

permon1

We have to start collecting processor time counter, so digit:

logman start perf_log

Now the logging activity appears started:

perfmon2

As you can easily figure out, to stop the logging:

logman stop perf_log

And now a more complex (and useful) sample: how to collect memory counters for a .NET application. Let's assume you need process memory and .NET memory counters every 15 sec, saved in a binary file, here's the batch file you could need:

@echo off
@echo *** Creating counter ***
Logman.exe create counter Perf-Counter-Log -o "C:\PerfLogs\Perftest.blg" -f bin -v mmddhhmm -c "\.NET CLR Memory(ProcessName)\# Bytes in all Heaps" "\.NET CLR Memory(ProcessName)\# GC Handles" "\.NET CLR Memory(ProcessName)\# Gen 0 Collections" "\.NET CLR Memory(ProcessName)\# Gen 1 Collections" "\.NET CLR Memory(ProcessName)\# Gen 2 Collections" "\.NET CLR Memory(ProcessName)\# Induced GC" "\.NET CLR Memory(ProcessName)\# of Pinned Objects" "\.NET CLR Memory(ProcessName)\# of Sink Blocks in use" "\.NET CLR Memory(ProcessName)\# Total committed Bytes" "\.NET CLR Memory(ProcessName)\# Total reserved Bytes" "\.NET CLR Memory(ProcessName)\% Time in GC" "\.NET CLR Memory(ProcessName)\Allocated Bytes/sec" "\.NET CLR Memory(ProcessName)\Finalization Survivors" "\.NET CLR Memory(ProcessName)\Gen 0 heap size" "\.NET CLR Memory(ProcessName)\Gen 0 Promoted Bytes/Sec" "\.NET CLR Memory(ProcessName)\Gen 1 heap size" "\.NET CLR Memory(ProcessName)\Gen 1 Promoted Bytes/Sec" "\.NET CLR Memory(ProcessName)\Gen 2 heap size" "\.NET CLR Memory(ProcessName)\Large Object Heap size" "\.NET CLR Memory(ProcessName)\Promoted Finalization-Memory from Gen 0" "\.NET CLR Memory(ProcessName)\Promoted Finalization-Memory from Gen 1" "\.NET CLR Memory(ProcessName)\Promoted Memory from Gen 0" "\.NET CLR Memory(ProcessName)\Promoted Memory from Gen 1" "\Process(ProcessName)\% Privileged Time" "\Process(ProcessName)\% Processor Time" "\Process(ProcessName)\% User Time" "\Process(ProcessName)\Creating Process ID" "\Process(ProcessName)\Elapsed Time" "\Process(ProcessName)\Handle Count" "\Process(ProcessName)\ID Process" "\Process(ProcessName)\IO Data Bytes/sec" "\Process(ProcessName)\IO Data Operations/sec" "\Process(ProcessName)\IO Other Bytes/sec" "\Process(ProcessName)\IO Other Operations/sec" "\Process(ProcessName)\IO Read Bytes/sec" "\Process(ProcessName)\IO Read Operations/sec" "\Process(ProcessName)\IO Write Bytes/sec" "\Process(ProcessName)\IO Write Operations/sec" "\Process(ProcessName)\Page Faults/sec" "\Process(ProcessName)\Page File Bytes" "\Process(ProcessName)\Page File Bytes Peak" "\Process(ProcessName)\Pool Nonpaged Bytes" "\Process(ProcessName)\Pool Paged Bytes" "\Process(ProcessName)\Priority Base" "\Process(ProcessName)\Private Bytes" "\Process(ProcessName)\Thread Count" "\Process(ProcessName)\Virtual Bytes" "\Process(ProcessName)\Virtual Bytes Peak" "\Process(ProcessName)\Working Set" "\Process(ProcessName)\Working Set Peak" -si 15
@echo *** Counter created ***
@echo *** Starting logging ***
Logman.exe start Perf-Counter-Log

You can also list the counters in a separate text file and use:

@echo off
@echo *** Creating counter ***
Logman.exe create counter Perf-Counter-Log -o "C:\PerfLogs\Perftest.blg" -f bin -v mmddhhmm -cf "DotNetMemory.txt" -si 15
@echo *** Counter created ***
@echo *** Starting logging ***
Logman.exe start Perf-Counter-Log

where the text file looks like this:

"\.NET CLR Memory(ProcessName)\# Bytes in all Heaps"
"\.NET CLR Memory(ProcessName)\# GC Handles"
"\.NET CLR Memory(ProcessName)\# Gen 0 Collections"
"\.NET CLR Memory(ProcessName)\# Gen 1 Collections"
"\.NET CLR Memory(ProcessName)\# Gen 2 Collections"
"\.NET CLR Memory(ProcessName)\# Induced GC"
"\.NET CLR Memory(ProcessName)\# of Pinned Objects"
"\.NET CLR Memory(ProcessName)\# of Sink Blocks in use"
"\.NET CLR Memory(ProcessName)\# Total committed Bytes"
"\.NET CLR Memory(ProcessName)\# Total reserved Bytes"
"\.NET CLR Memory(ProcessName)\% Time in GC"
"\.NET CLR Memory(ProcessName)\Allocated Bytes/sec"
"\.NET CLR Memory(ProcessName)\Finalization Survivors"
"\.NET CLR Memory(ProcessName)\Gen 0 heap size"
"\.NET CLR Memory(ProcessName)\Gen 0 Promoted Bytes/Sec"
"\.NET CLR Memory(ProcessName)\Gen 1 heap size"
"\.NET CLR Memory(ProcessName)\Gen 1 Promoted Bytes/Sec"
"\.NET CLR Memory(ProcessName)\Gen 2 heap size"
"\.NET CLR Memory(ProcessName)\Large Object Heap size"
"\.NET CLR Memory(ProcessName)\Promoted Finalization-Memory from Gen 0"
"\.NET CLR Memory(ProcessName)\Promoted Finalization-Memory from Gen 1"
"\.NET CLR Memory(ProcessName)\Promoted Memory from Gen 0"
"\.NET CLR Memory(ProcessName)\Promoted Memory from Gen 1"
"\Process(ProcessName)\% Privileged Time"
"\Process(ProcessName)\% Processor Time"
"\Process(ProcessName)\% User Time"
"\Process(ProcessName)\Creating Process ID"
"\Process(ProcessName)\Elapsed Time"
"\Process(ProcessName)\Handle Count"
"\Process(ProcessName)\ID Process"
"\Process(ProcessName)\IO Data Bytes/sec"
"\Process(ProcessName)\IO Data Operations/sec"
"\Process(ProcessName)\IO Other Bytes/sec"
"\Process(ProcessName)\IO Other Operations/sec"
"\Process(ProcessName)\IO Read Bytes/sec"
"\Process(ProcessName)\IO Read Operations/sec"
"\Process(ProcessName)\IO Write Bytes/sec"
"\Process(ProcessName)\IO Write Operations/sec"
"\Process(ProcessName)\Page Faults/sec"
"\Process(ProcessName)\Page File Bytes"
"\Process(ProcessName)\Page File Bytes Peak"
"\Process(ProcessName)\Pool Nonpaged Bytes"
"\Process(ProcessName)\Pool Paged Bytes"
"\Process(ProcessName)\Priority Base"
"\Process(ProcessName)\Private Bytes"
"\Process(ProcessName)\Thread Count"
"\Process(ProcessName)\Virtual Bytes"
"\Process(ProcessName)\Virtual Bytes Peak"
"\Process(ProcessName)\Working Set"
"\Process(ProcessName)\Working Set Peak"

Cheers,

Andrea

How can you survive without SOAP Toolkit when using WindowsPE for Vista?

A customer of mine recently came up with a tricky problem regarding SOAP Toolkit and Windows Vista: “We have computer naming system that uses SOAP query. It has worked fine with XP but now we have problems with Vista. We get the following error: ActiveX component can't create object 'MSSOAP.SoapClient'. What could be wrong?”

The first thing raised in my mind was to have a quick look at the supported operating systems in SOAP Toolkit 3.0 download page, and the answer was quickly obtained: Windows 2000; Windows 98; Windows ME; Windows Server 2003; Windows XP.

Basically, all SOAP Toolkits have been replaced by the Microsoft .NET Framework. SOAP Toolkit versions earlier than version 3.0 are no longer supported and standard support for SOAP Toolkit 3.0 expired March 31, 2005. All new development on the SOAP Toolkit has stopped. Consequentially, there will be no future improvements, fixes, or standard compliance updates made to it (there will be no implementation of the SOAP 2.0 specifications). Because of all this, any new development based on the SOAP Toolkit is not advised. Microsoft official recommendation for Web Services now is to use .NET to both access and publish Web Services. When there is no alternative option MS recommends using .NET in combination with COM for non .NET technologies when communicating with web services.

But the customer scenario was a bit more complex: I don’t know if you’ve noticed this ‘detail’ in his question: “We have computer naming system”. In fact the customer uses WindowsPE, a bootable tool from Microsoft that provides operating system features for installation, troubleshooting, and recovery (http://technet.microsoft.com/en-us/windowsvista/aa905120.aspx). It does NOT support .NET framework. You are allowed to run VB scripts with Windows PE.

Let’s wrap up:

1) Soap Toolkit is no longer supported on Windows Vista.
2) The customer uses WindowsPE, a bootable tool from Microsoft that provides operating system features for installation, troubleshooting, and recovery. It does NOT support .NET framework.
3) You can run VBScripts with WindowsPE and the customer used to connect a web service through soap toolkit in a vbscript.
4) The customer uses the web service to retrieve the computer name of the machine to be installed with sysprep.

I started thinking to all of the possibilities we had to easily connect a web service from an environment not including .NET, and several ideas, more or less sensible, rose up: producing a .NET client proxy, wrapping the assembly through COM Interop, invoking COM methods in the customer VB Script.. what a mess!

But why not discovering a way to directly connect the web service from the VB Script, even though I have to build SOAP messages by hand? The first impressions of my colleagues were something like “too much complex”, “what if you have to add methods?” “it seems to me you’re lately getting crazy” and so on.smile_eyeroll

In my opinion, the solution with the proxy client code generated by wsdl.exe is too much complex, and could be acceptable if no code changes were needed: in fact before compiling the .NET class library you have to modify the code generated by wsdl.exe, and it’s advisable to work with Visual Studio. Besides you have to register the .NET assembly with regasm.exe before being able to use the SOAP client. Too many steps to do, too many error chances.

My aim was to allow the customer to re-use his VBScript, and since the ideal solution didn’t exist for this problem, I considered some problem aspects.

1) Let’s try to re-use the work already done, in this case VBScript.

2) Reduce the pre-steps like object installation, registration.

3) The customer web service seemed to be quite simple, with just one GetName method in order to retrieve the machine name.

I guess the safest and simplest solution, given the aspects above, is to use the ServerXmlHttp object: it provides methods and properties that enable you to establish an HTTP connection between files or objects on different Web servers. It is implemented in msxml*.dll, so you can find it in every Vista installation, and no additional action is required.

The most difficult part is that invoking a web service means to use SOAP, i.e. an XML message formed in order to specify a method invocation together with parameter values. The response coming from the web service will be another XML message which contains the return value and the output parameters, and you’ll have to extract them to be able to use it.

However, working with strings shouldn’t be very hard in VBScript, and you can also use another msxml object, the DomDocument which simplify to handle with XML documents.

The request message was the following:

POST /DataManager.asmx HTTP/1.1
SOAPAction: "http://and.it/wsnaming/GetName"
Content-Type: text/xml..User-Agent: SOAP Sdk
Host: 10.32.56.30
Content-Length: 433
Connection: Keep-Alive
Cache-Control: no-cache

<?xml version="1.0" encoding="UTF-8" standalone="no"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Body><GetName xmlns="http://andrea.org/wsnaming/"><sName xmlns:SOAPSDK1="http://and.it/wsnaming/">&apos;Precision M65                   &apos;,&apos;DH1XP2J&apos;,&apos;LAPTOP&apos;,&apos;00:15:C5:C2:80:BD&apos;,&apos;00:19:D2:02:F6:47&apos;</sName></GetName></SOAP-ENV:Body></SOAP-ENV:Envelope>

While the response was:

HTTP/1.1 200 OK
Date: Tue, 18 Mar 2008 06:19:15 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 1.1.4322
Cache-Control: private, max-age=0
Content-Type: text/xml; charset=utf-8
Content-Length: 352

<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><GetNameResponse xmlns="http://and.it/wsnaming/"><GetNameResult>FIL01709</GetNameResult></GetNameResponse></soap:Body></soap:Envelope>

So we should implement a vbscript which compose the request, and parses the response to get the host name.

I’ve implemented this:

'DECLARE VARIABLES 
Dim objXMLDOC, objXMLDOM, SoapStr 
Dim model, serial, wsType, lanMacc, wLanMac
Dim hostName

model = "&apos;Precision M65                   &apos;"
serial = "&apos;DH1XP2J&apos;"
wsType = "&apos;LAPTOP&apos;"
lanMacc= "&apos;00:15:C5:C2:80:BD&apos;"
wLanMac ="&apos;00:19:D2:02:F6:47&apos;"


'BUILD CALL DATA 
SoapStr = "<?xml version=""1.0"" encoding=""UTF-8"" standalone=""no""?>" 
SoapStr = SoapStr & "<SOAP-ENV:Envelope" 
SoapStr = SoapStr & " xmlns:SOAP-ENV=""http://schemas.xmlsoap.org/soap/envelope/""" 
SoapStr = SoapStr & " <SOAP-ENV:Body>" 
SoapStr = SoapStr & " <GetName xmlns=""http://and.it/wsnaming/"">" 
SoapStr = SoapStr & " <sName xmlns:SOAPSDK1=""http://and.it/wsnaming/"">"
SoapStr = SoapStr & model & "," & serial & "," & wsType & "," & lanMac & "," & wlanMac 
SoapStr = SoapStr & "</sName>" 
SoapStr = SoapStr & " </GetName>" 
SoapStr = SoapStr & " </SOAP-ENV:Body>" 
SoapStr = SoapStr & "</SOAP-ENV:Envelope>" 
 
'CREATE OBJECTS 
Set objXMLDOC = CreateObject("Msxml2.ServerXMLHTTP") 
Set objXMLDOM = CreateObject("Msxml2.DomDocument") 
Set oNode = CreateObject("Microsoft.XMLDOM") 


'MAKE THE CALL 
objXMLDOC.open "POST", "http://10.32.56.30/DataManager.asmx", False 
objXMLDOC.setRequestHeader "Content-Type", "text/xml" 
objXMLDOC.setRequestHeader "SOAPAction", "http://and.it/wsnaming/GetName" 
objXMLDOC.send(SoapStr) 
objXMLDOM.LoadXML objXMLDOC.responseText 

' SOAP RESPONSE FOR TESTING PURPOSE
'SoapStr = "<?xml version=""1.0"" encoding=""utf-8""?>"
'SoapStr = SoapStr & "<soap:Envelope xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"" "
'SoapStr = SoapStr & "xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" "
'SoapStr = SoapStr & "xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">"
'SoapStr = SoapStr & "<soap:Body><GetNameResponse xmlns=""http://and.it/wsnaming/""><GetNameResult>"
'SoapStr = SoapStr & "FIL01709"
'SoapStr = SoapStr & "</GetNameResult></GetNameResponse></soap:Body></soap:Envelope>"
'objXMLDOM.LoadXML SoapStr

hostName = ""

'PROCESS THE CALL 
If objXMLDOM.parseError.errorCode <> 0 Then 'parser error found 
'handle the error 
Else 'Call was successful, process 

Set oNode = objXMLDOM.getElementsByTagName("GetNameResult")
hostName =  oNode.Item(i).text
Wscript.Echo hostName

End If 


'CLEAN UP EVERYTHING 
Set objXMLDOC = Nothing 
Set objXMLDOM = Nothing 
Set oNode = Nothing 

Please consider this as just a track to follow: I suggest you to think on how to inject this code in your existent vbscript after having verified it’s working as is. Please also consider I'm not an expert VB Script programmer, so I know this sample doesn't contain any proper error handling, and maybe some unorthodox constructs Smile My purpose was mainly to show how some unusual problems can lead to the approach you would never have thought before.

Cheers,

Andrea

Hi there!

Hi guys, my name's Andrea and I'm a member of Microsoft EMEA Distributed Services Team. We provide support for many MS technologies (COM+, WCF, WF, MSDTC, etc.), thus each one of us is specialized on some of them. My area mainly consists of web services: Windows Communication Foundation, .NET Remoting, ASP .NET web services.

In my daily job the typical issues I have to struggle with are quite varied, but I can tell they usually have something to do with interoperability with either third-party or older generation technologies, security, data serialization, performance, crash/hang/leak.

I worked for the MS EMEA Visual Studio Team till last year, and I had been supporting .NET Framework, both managed languages and not managed ones (C#, VB .NET, C++/CLI, C++), solving crash/hang/leak in customer applications, and obviously supporting the tool which gave the name to the team, 2003 and 2005 versions. Last but not least.. I’m very fond of C++ Smile

I'd like to share some interesting issues I face every day, and I do hope to be able to provide the related solution as well! Hot

Enjoy your staying here and.. do not spend too much time in front of your PC Tongue out

Page view tracker