Exploration of which request headers you need, should and cannot set when using MsXml2.XmlHttp, Microsoft.XmlHttp and MsXml2.ServerXmlHttp

System Requirements:

  • Windows NT 4.0 Server SP4+
  • Windows Server 2000
  • Windows Server 2003
  • Windows Server 2008, R2
  • Windows Server 2012, R2
  • Windows 2000 Professional
  • Windows XP
  • Windows Vista
  • Windows 7
  • Windows 8, 8.1
  • IIS 4.0, 5.0, 5.1, 6.0, 7.0, 7.5, 8.0
  • ASP 3.0 (Classic)
  • VBScript

The Problem:

I was browsing around the web earlier looking for inspiration on making PowerShell send asynchronous XmlHttp requests when I came across a block of VBScript examples. In them, I observed that the coder was religiously attempting to set the Content-Length request header, something that when using MsXml I don’t usually do – it’s a different story with PHP.

Equally, I observed that the programmer was using Len() to define the length of the content body for text, but ASP usually operates using an internal UTF-16 encoding to represent strings, thus does it follow that Len() will give the correct value?

So I thought that I would explore the issue to see for my own amusement, what was actually going on.

More Info

Let us take a standard code block to use for our tests and use the services of the good people at httpbin.org for a public message echo system.

Option Explicit
Dim x
Dim strRequest : strRequest = "a=1&b=2&c=3" ' <- 11 charactersset x = CreateObject("MsXml2.XmlHttp")
call x.open("POST", "http://httpbin.org/post", false)
call x.setRequestHeader("Content-Length", 11)
call x.setRequestHeader("Accept-Language", "EN-GB")
call x.setRequestHeader("Connection", "keep-alive")x.send(strRequest)

The above gives us a boiler plate for what we believe the correct header should looklike written in VBScript. When executed, HttpBin responds with:

{
"args": {},
"data": "a=1&b=2&c=3",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "EN-GB",
"Cache-Control": "no-cache",
"Connection": "close",
"Content-Length": "11",
"Host": "httpbin.org",
"User-Agent": "<omitted>",
"X-Request-Id": "<omitted>"
},
"json": null,
"origin": "<ommitted>",
"url": "http://httpbin.org/post"
}

HttpBin provides us with an echo of the header and WireShark provides us with a wire capture of the transport packet being sent between the XmlHttpResponse provider and the HttpBin server. With these two tools we can analyse the header states.

Content-Length

If you take out the line call x.setRequestHeader(“Content-Length”, 11) and do not specify a content-length, both HttpBin and Wireshark’s trace of the connection report Content-Length: 11\r\n, which is correct. So logically, this has been inserted by the XmlHttp parser during the request to .send().

So what happens if we attempt to override it. If we inject call x.setRequestHeader(“Content-Length”, 15), which is 4 characters too long, again both WireShark and HttpBin report

Content-Length: 11\r\n

The same occurs if we under-report the content length, i.e. 10 characters. Therefore it is safe to conclude that MsXml2.XmlHttp’s send() operator handles this for you and any processing of the Len() of the message body before sending is just wasting CPU time, memory and lines of code as it is ultimately an action that will be duplicated.

To further test the theory to see if it is required in down-level versions of MsXml, I repeated the experiment using HttpBin and Wireshark with the following COM objects:

Microsoft.XmlHttp
MsXml2.XmlHttp            <-which is logically v3.0
MsXml2.XmlHttp.3.0
MsXml2.XmlHttp.6.0
MsXml2.ServerXmlHttp      <-which is logically v3.0
MsXml2.ServerXmlHttp.3.0
MsXml2.ServerXmlHttp.6.0

None of them could be coerced to include an incorrectly sized Content-Length and all of them appended the correct 11 character content length on their own when no attempt was made to manually provide it.

We can therefore categorically conclude that with MSXML 3.0 and higher, you do not need to waste time calculating the content length.

A an aside, when dealing with character data, the use of Len() appears to be correct for UTF-8 encoding. LenB() should be used when passing in binary data i.e. ADODB.Stream data, FileSystemObject data etc.

Content-Type

If you do not manually specify a Content-Type header, all MSXML providers appear to transmit POST encoded data as

Content-Type: text/xml; charset="utf-8"\r\n

As we were sending POST data (i.e. a HTML form), this is incorrect as the content-type should be “application/x-www-form-urlencoded; Charset=UTF-8”. Thus is is appropriate and necessary to include the Content-Type header manually as shown below.

call x.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; Charset=UTF-8")

Accept-Language

Accept language was decidedly different depending on which provider you are using. You can safely override Accept-language by manually specifying it using setRequestHeader(“Accept-Language”, “EN-GB”).

Microsoft.XmlHttp         <- NOT SET
MsXml2.XmlHttp.3.0        <- NOT SET
MsXml2.XmlHttp.6.0        <- Derived it from OS i.e. Accept-Language: en-gb\r\n
MsXml2.ServerXmlHttp.3.0  <- Derived it from OS i.e. Accept-Language: en-gb\r\n
MsXml2.ServerXmlHttp.6.0  <- Derived it from OS i.e. Accept-Language: en-gb\r\n

Thus, if you are using the legacy Microsoft.XmlHttp or the client MsXml2.XmlHttp.3.0, it is necessary to manually enter the Accept-Language if of course it is needed.

Connection

All examples set the connection value to Connection: Keep-Alive\r\n when communicating with the HTTP/1.1 protocol. If you need to force the remote server to close the TCP connection immediately after the response has been received, you must manually set Connection: Close\r\n using setRequestHeader(“Connection”, “Close”).

Cache-Control

Cache-control is only set by the client providers and can be added manually to the server providers.

Microsoft.XmlHttp         <- Cache-Control: no-cache\r\n
MsXml2.XmlHttp.3.0        <- Cache-Control: no-cache\r\n
MsXml2.XmlHttp.6.0        <- Cache-Control: no-cache\r\n
MsXml2.ServerXmlHttp.3.0  <- NOT SET
MsXml2.ServerXmlHttp.6.0  <- NOT SET

Accept-Encoding

Accept-encoding is only set by the client providers and can be added manually to the server providers.

Microsoft.XmlHttp         <- Accept-Encoding: gzip, deflate\r\n
MsXml2.XmlHttp.3.0        <- Accept-Encoding: gzip, deflate\r\n
MsXml2.XmlHttp.6.0        <- Accept-Encoding: gzip, deflate\r\n
MsXml2.ServerXmlHttp.3.0  <- NOT SET
MsXml2.ServerXmlHttp.6.0  <- NOT SET

Accept

Accept is set to Accept: */*\r\n by all providers and can be overridden if required.

Memory Leak in SvcHost.exe on Microsoft.XmlHttp (IXMLHTTPRequest) .Send() when called from CScript.exe or WScript.exe

System Requirements:

  • Windows Server 2008 R2

The Problem:

Svchost.exe, that black box amongst many other black boxes. If you ever happened to be in the business of watching what your scripts are getting up to on a Sunday morning and you are using Microsoft.XmlHttp, then you might be in for a surprise.

Every 2 hours a batch process on a group of servers fires off a script that in turn iteratively runs a second VBS script some 200-300 times. The script, calls a Web Service and performs a push/pull of instructions. Within a few days of the patch Tuesday reboot, you start noticing that memory use is going up, and up, and up.

You’ve done all of your deallocations, right? “set xmlHttp = nothing”? Yep, but despite that, memory use continues to grow. The culprit, svchost.exe. It grows until it’s into the page file and then grows a little bit more. Every run of the script puts between 4 and 100KB onto the memory footprint. At the end of the month, the servers are groaning because of memory starvation and your SAN array’s are not happy because of all of the paging.

True story.

More Info

I have been able to reproduce this on 3 separate and wholly independent Server 2008 systems (read different clients, enterprise/retail licensing, server hardware and install images) as well as on related servers (read from the same image on same or similar hardware). I have attempted to reproduce it on Windows Server 2012 R2 and I was not successful. Server 2012 R2 does not appear to be impacted by the issue. Running the iterator loop below for 10 minutes yields no increase in the memory use curve on the operating system, just a constant cycle of assign, release, assign, release that you would expect to see.

After a lot of diagnostics and a lot of me initially assuming that the problem was the web service (many, many wasted hours… although I did find a few bugs in the service code itself…) I managed to narrow it down to Microsoft.XmlHttp. More specifically, it’s in the way that CScript or WScript interfaces with Microsoft.XmlHttp at initialisation.

As you probably know, svchost itself is just a service wrapper. Inspection of the wrapper reveals a number of services running inside the wrapper. In this case the specific services are:

  • COM+ Event System
  • Windows Font Cache Service
  • Network List Service
  • Network Store Interface Service
  • Secure Socket Tunneling Protocol Service
  • WinHTTP Web Proxy Auto-Discovery

There are two things here that could be interesting, COM+ Event System and WinHTTP Web Proxy. Microsoft.XMLHTTP itself relies upon the WinHTTP stack for operation, but we are also using a COM interface to call it from VBScript.

While we cannot shutdown the COM+ Event Service and expect the operating system to survive for long, we can the WinHTTP Web Proxy Auto-Discovery Service. Did it release the memory consumed in the leak? No. So in the balance of probabilities, it’s coming from COM+.

The problem with that is in the need to reboot the server to safely clear the memory leak, hence why Patch Tuesday has been the true savior in keeping a gradual performance bottle neck from becoming a full scale meltdown. So what is going on?

I stripped off all of the web service and customisation parts and went back to vanilla Microsoft implementation examples. We cannot get much simpler than this.

Option Explicit
Dim xmlset xml = CreateObject("Microsoft.XmlHttp")
xml.open "POST", "http://127.0.0.1", false
xml.send "he=llo"
set xml = nothing

Save it to a VBS and run it via CScript, run it a lot. Run it in a BAT file loop

:start
ccscript.exe testfile.vbs
goto start

Watch the svchost.exe processes until you spot the instance with the rising service working set (or private set). Now you know which one to focus on.

It’s memory leaked. Hold on, we’ve created the instance of Microsoft.XmlHttp (which is actually an instance of IXMLHTTPRequest), done something and told CScript to deallocate it (set xml = nothing). Why is it leaking memory?

The third parameter on .Open() is bAsync – is it an asynchronous request? It’s false above, meaning that the request is synchronous. It continues to leak. It would be more likely to leak asynchronously than synchronously, however changing that to true makes no difference.

So where is the leak being triggered? By process of line elimination we can reveal that the memory is committed into the svchost wrapper during xml.send(). Run it without .Send() as below and there is no growth in the scvhost process memory footprint no matter how many times you run it..

Option Explicit
Dim xmlset xml = CreateObject("Microsoft.XmlHttp")
xml.open "POST", "http://127.0.0.1", false
' COMMENTED OUT      xml.send "he=llo"
set xml = nothing

In the MSDN documentation for the .Send() method, it states

“If the input type is a BSTR, the response is always encoded as UTF-8. The caller must set a Content-Type header with the appropriate content type and include a charset parameter.”

So far we haven’t done that and we are sending a VBString – which is ultimately a BSTR in C++, so add in the necessary setRequestHeader beneath the .Open() method call in case it is a case of not following the documentation:

Option Explicit
Dim xmlset xml = CreateObject("Microsoft.XmlHttp")
xml.open "POST", "http://127.0.0.1", false
xml.setRequestHeader "Content-Type", "application/x-www-form-urlencoded; Charset=UTF-8"
xml.send "he=llo"
set xml = nothing

It isn’t. There is no change, it still results in an increase in process memory after cscript.exe has shutdown.

We have confirmed that there is a memory leak, where it is and what is triggering it. We can also be confident that given the extremely simple nature of the sample code printed above – and its match to the samples documentation – that it is being implemented correctly.

So the next step is to try and prove that there is an issue in the COM implementation between CreateObject and set nothing. This is achieved by running the allocate/deallocate (set/set nothing) in a loop as shown below

Option Explicit
Dim i
Dim xmlwscript.echo TypeName(xml)              ' This returns "empty" on this testfor i = 0 to 999

set xml = CreateObject("Microsoft.XmlHttp")
xml.open "POST", "http://127.0.0.1", false
xml.setRequestHeader "Content-Type", "application/x-www-form-urlencoded; Charset=UTF-8"
xml.send "he=llo"
wscript.echo xml.responsexml.xml    ' This returns nothing on this test
wscript.echo xml.statusText         ' This returns "OK" on this test
set xml = nothing

next

wscript.echo TypeName(xml)              ' This returns "nothing" on this test

At this point you would expect to see a large increase in the svchost.exe memory footprint.

It does not happen.

1000 iterations and instantiation of the IXMLHTTPRequest later and there is no obvious exponential increase in the memory footprint of svchost.exe. It simply increments once i.e. the additional memory consumption is no worse than running the script with only 1 call to CreateObject/set nothing despite the fact that .send() has been called 1000 times.

What does that mean? Well, it would seem to suggest that the fault isn’t actually in IXMLHTTPRequest (Microsoft.XMLHTTP), but actually in VBScript itself. As a speculative suggestion, I would suggest that VBScript is registering event callbacks with COM+’s Event Management System on the first call to .Send() which are not being cleaned up by the garbage collector when “set nothing” is called in the code. So either there is a bug in VBScript or there is a bug in the event handling interface for COM+ event registration through which IXMLHTTPRequest is registering its own actions.

Most people aren’t going to notice this problem, they are morelikely to iterate instance of Microsoft.XmlHttp inside VBScript than they are to repeatedly externally iterate accross it. It just so happens that I need to fire it externally to the script processor via the command shell. The chances are that if you are reading this, so do you.

The Fix

As of writing, I have not found a direct way to force VBScript to release the memory from scvhost, short of rebooting (or migrating to Windows Server 2012). Calling Microsoft.XmlHttp from WScript or CScript seems to be the problem and the fact that the web service scripts are using an external iterator to repeatedly call n new instances of CScript are exacerbating the situation. Simply put, the transaction load is the catalyst for spotting the leak. In most cases growth would be very subtle as would growth were the iteration internal to the CScript.exe script instance.

While not necessarily ideal, if you are in the position of being able to change provider, you can substitute Microsoft.XMLHTTP for MSXML2.ServerXmlHttp, which provides most of the functionality without making use if WinHTTP. This provider does not exhibit the memory growth issue as in its client counterpart, however its use requires MSXML 3 or 6 and you lose some functionality.

The fact that I could not reproduce the issue under Windows Server 2012 R2 suggests that the culprit has been fixed – either intentionally or inadvertantly. By default, Microsoft.XMLHTTP is a COM Class ID reference to msxml3.dll. Under Windows Server 2008 R2 the file version is SP11 at 8.110.7601.18334, under 2012 R2 the file version is simply 8.110.9600.16483. Yet oddly, with all systems fully patched, vbscript.dll under Windows Server 2008 R2 is version 5.8.9600.17041 (KB2929437) while its counterpart under Server 2012 R2 is 5.8.9600.17031.

What I can tell you is that these systems have been running this recursion script every 2 hours since the beginning of 2012 and the issue has only been observed in more recent months, therefore I suspect that Microsoft have a regression bug on their hands. Until it is fixed however, I have a load of (thankfully firewalled, private network) web service that have a DOS vulnerability. So do you.

Error: “Setup failed while installing sub-component Exchange ActiveSync with error code 0xC0070643 (please consult the installation logs for a detailed description). You may cancel the installation or try the failed setup again.” while installing Exchange 2003 SP2

System Requirements:

  • Exchange Server 2003, SP2
  • Windows 2000, 2003 Server

The Problem:

When attempting to install Exchange Server 2003 Service Pack 2 (SP2) you receive the following error message:

Setup failed while installing sub-component Exchange ActiveSync with error code 0xC0070643
(please consult the installation logs for a detailed description).
You may cancel the installation or try the failed setup again.

The installation halts at a retry prompt or aborts and continues the installation normally.

More Information:

Microsoft basically screwed up in their version number catching during scripting and testing of the SP. If you have followed my Windows patching guides on HPC:Factor, your Exchange 2003 SP2 install will by default experience this issue; not because I screwed up, but because Microsoft clearly don’t think any of you ever update anything other than the half-hearted list generated by Windows Update.

Credit for the time on this one goes without me stealing the lime-light to the ‘SBS Diva‘, but her explanation for the issue is incorrect. The issue isn’t Shavlik’s, it’s Microsoft’s.

After a little digging, I discovered that the Exchange 2003 RTM shipped with MSXML Core Service 3.0 SP2. This is understandable, as Windows 2000 SP3/SP4 with Internet Explorer 5.01 SP3/SP4 lacks this module (Internet Explorer 6.0 SP1 ships with SP3). However, Service Pack 2 for Exchange Server ships with MSXML Core Service SP5, Service Pack 7 (SP7) was released a month later.

The Exchange SP Installer is simply expecting to find a lower version of MSXML for the Exchange ActiveSync system (SP2/3/4). If it encounters the same or higher version, the installer errors out believing the installation to have erroneously failed, when in reality it was just badly written.

The Fix:

Just as stated by SBS Diva, if you purge the version check (which is done against the Windows Installer versioning, not the file version) backup and then delete the key for MSXML 3.

MSXML 3.0 SP5 – [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Installer\Products\45D60EC31B272B44BA064E72E78CE04F]
MSXML 3.0 SP7 – [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Installer\Products\E83E246D42D0C684A9D23E61DD96F6B4]

There is no MSXML 3.0 SP6.

 

Vignette 1: If you do perform a visual search of HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Installer\Products, ensure that you are not deleting MSXML 4.0, 5.0 or 6.0 related entries. It is also possible for MSXML 2.x to be installed on the system (though unlikely unless your install is as old as Windows 2000 itself)

Vignette 2: The Exchange SP2 installer is going to leave the system *thinking* that it has SP5 installed on it. If you started on SP5, fine, but if you were on SP7, you should put the hive back again or reinstall SP7. For SP5 users, I recommend installing SP7 after Exchange is up and running. The Exchange SP installation process does not devolve the MXSML level back to SP5.