Showing posts with label MSXML2. Show all posts
Showing posts with label MSXML2. Show all posts

Monday, 22 October 2018

OLEDB Simple Provider - Xml Reader

So I have chanced upon something call the OLEDB Simple Provider which allows VBA to convert an Xml document into a recordset. Worth investigating some more in its own right because it is said to support shaped, i.e. hierarchical recordsets. But right now, I want to find how to implement a custom OLEDB Simple Provider for an in memory array, a goal I've had for some time.

It has to be said that a recordset can be saved to Xml and indeed a saved Xml recordset can be synthesised as I did in this blog post.

Option Explicit

'* Tools->References
'MSXML2     Microsoft XML, v6.0                         C:\Windows\SysWOW64\msxml6.dll
'ADODB      Microsoft ActiveX Data Objects 6.1 Library  C:\Program Files (x86)\Common Files\System\ado\msado15.dll

Sub Test()
    Dim xmlDom As MSXML2.DOMDocument60
    Set xmlDom = New MSXML2.DOMDocument60
    
    Dim sColorsXml As String
    sColorsXml = "<colors>" & _
            "<color><colorname>Red</colorname><colorRGB>FF0000</colorRGB></color>" & _
            "<color><colorname>Green</colorname><colorRGB>00FF00</colorRGB></color>" & _
            "<color><colorname>Blue</colorname><colorRGB>0000FF</colorRGB></color></colors>"

    
    xmlDom.LoadXML sColorsXml
    Debug.Assert xmlDom.parseError = 0
    
    Const sFileName As String = "N:Colors.xml"
    xmlDom.Save sFileName

    Dim oConn As ADODB.Connection
    Set oConn = New ADODB.Connection
    'oConn.Open "Provider=MSDAOSP;Data Source=MSXML2.DSOControl.5.0"
    oConn.Open "Provider=MSDAOSP;Data Source=MSXML2.DSOControl"

    Dim rs As ADODB.Recordset
    Set rs = New ADODB.Recordset
    Set rs.ActiveConnection = oConn
    
    rs.Open sFileName
    rs.MoveFirst
    Debug.Assert rs.RecordCount = 3
    Debug.Assert rs.Fields.Count = 3  '* this surprised me
    Debug.Assert rs.Fields.Item(0).Name = "colorname"
    Debug.Assert rs.Fields.Item(1).Name = "colorRGB"
    Debug.Assert rs.Fields.Item(2).Name = "$Text"  '* this is the additional unexpected field, it concatentates the others
        
    '* lets get the recordset to a variant array
    Dim vVarArray
    vVarArray = Application.WorksheetFunction.Transpose(rs.GetRows)
    Debug.Assert vVarArray(1, 1) = "Red"
    Debug.Assert vVarArray(1, 2) = "FF0000"
    Debug.Assert vVarArray(1, 3) = "RedFF0000"
    Debug.Assert vVarArray(2, 1) = "Green"
    Debug.Assert vVarArray(2, 2) = "00FF00"
    Debug.Assert vVarArray(2, 3) = "Green00FF00"
    Debug.Assert vVarArray(3, 1) = "Blue"
    Debug.Assert vVarArray(3, 2) = "0000FF"
    Debug.Assert vVarArray(3, 3) = "Blue0000FF"
    
    Stop
End Sub

Appendix A - Registry Entries

Just doing some digging into the load sequence etc....

So "MSXML2.DSOControl" is a ProgId and we can go find it in the registry ...

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Msxml2.DSOControl.5.0]
@="XML Data Source Object 5.0"

[HKEY_CLASSES_ROOT\Msxml2.DSOControl.5.0\CLSID]
@="{88D969E9-F192-11D4-A65F-0040963251E5}"

So we can lookup the CLSID, {88D969E9-F192-11D4-A65F-0040963251E5} ...

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{88D969E9-F192-11D4-A65F-0040963251E5}]
@="XML Data Source Object 5.0"

[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{88D969E9-F192-11D4-A65F-0040963251E5}\InProcServer32]
@="C:\Program Files (x86)\Common Files\Microsoft Shared\OFFICE11\MSXML5.DLL"
"ThreadingModel"="Apartment"

[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{88D969E9-F192-11D4-A65F-0040963251E5}\ProgID]
@="Msxml2.DSOControl.5.0"

[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{88D969E9-F192-11D4-A65F-0040963251E5}\TypeLib]
@="{F5078F18-C551-11D3-89B9-0000F81FE221}"

[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{88D969E9-F192-11D4-A65F-0040963251E5}\Version]
@="5.0"

We can find the type library with {F5078F18-C551-11D3-89B9-0000F81FE221} There were multiple versions, I'm showing version 5 ...

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Wow6432Node\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}]

[HKEY_CLASSES_ROOT\Wow6432Node\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}\5.0]
@="Microsoft XML, v5.0"

[HKEY_CLASSES_ROOT\Wow6432Node\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}\5.0\0]
@=""

[HKEY_CLASSES_ROOT\Wow6432Node\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}\5.0\0\win32]
@="C:\\Program Files (x86)\\Common Files\\Microsoft Shared\\OFFICE11\\MSXML5.DLL"

[HKEY_CLASSES_ROOT\Wow6432Node\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}\5.0\FLAGS]
@="0"

[HKEY_CLASSES_ROOT\Wow6432Node\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}\5.0\HELPDIR]
@="C:\\Program Files (x86)\\Common Files\\Microsoft Shared\\OFFICE11\\"

Loading the type library C:\Program Files (x86)\Common Files\microsoft shared\OFFICE11.MSXML5.DLL to see what interfaces DSOControl supports we can see it only has one


[
  uuid(88D969E9-F192-11D4-A65F-0040963251E5),
  helpstring("XML Data Source Object")
]
coclass DSOControl50 {
    [default] interface IDSOControl;
};


[
  odl,
  uuid(310AFA62-0575-11D2-9CA9-0060B0EC3D39),
  helpstring("DSO Control"),
  hidden,
  dual,
  nonextensible,
  oleautomation
]
interface IDSOControl : IDispatch {
    [id(0x00010001), propget]
    HRESULT XMLDocument([out, retval] IXMLDOMDocument** ppDoc);
    [id(0x00010001), propput]
    HRESULT XMLDocument([in] IXMLDOMDocument* ppDoc);
    [id(0x00010002), propget]
    HRESULT JavaDSOCompatible([out, retval] long* fJavaDSOCompatible);
    [id(0x00010002), propput]
    HRESULT JavaDSOCompatible([in] long fJavaDSOCompatible);
    [id(0xfffffdf3), propget]
    HRESULT readyState([out, retval] long* state);
};

And there the trail goes cold, nothing interesting about that interface, certainly nothing that can help our custom implementation of OLEDB Simple Provider.

Monday, 5 February 2018

VBA - ProgID - what is the current version

So a SO question arose about a non-installed version of MSXML2.ServerXMLHTTP. This made me wonder why not poke around in the registry to try and find all instances of MSXML2.ServerXMLHTTP in my registry, the results are given in Appendix A. It showed that version 4 is missing just like for the questioner. The registry sweep shows versions 3.0, 5.0 ,6.0 available.

What is really curious is there is a registry key

Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Msxml2.ServerXMLHTTP\CurVer

whose default value is

Msxml2.ServerXMLHTTP.3.0

This means if one writes the following code using late binding and no version in the prog id to instantiate a Msxml2.ServerXMLHTTP then one gets a 3.0 version and not a 6.0 version. The rationale is given in this MSDN blog.

Sub CreateXHR()
    Dim oXHR As Object
    Set oXHR = VBA.CreateObject("Msxml2.ServerXMLHTTP")
End Sub

We can write some code to query the registry to tell us what the non versioned prog id actually returns ...

Sub TestCurVersion()

    Debug.Print CurVersion("Msxml2.ServerXMLHTTP")
    '* for me returns Msxml2.ServerXMLHTTP.3.0
    
    Debug.Print CurVersion("Excel.Application")
    '* for me returns Excel.Application.15
    
End Sub


Function CurVersion(ByVal sClass As String) As String
    Const HKLM As Long = &H80000002
    Dim oWMIReg As Object
    
    Set oWMIReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _
             ".\root\default:StdRegProv")
    Dim sReturnString As String
    oWMIReg.GetStringValue HKLM, "SOFTWARE\Classes\" & sClass & "\CurVer", "", sReturnString
    CurVersion = sReturnString
End Function

So unless one wants to rewrite the registry keys to change the current version to version 6.0 then I recommend supplying the string "Msxml2.ServerXMLHTTP.6.0" thus

Sub CreateXHR60()
    Dim oXHR As Object
    Set oXHR = VBA.CreateObject("Msxml2.ServerXMLHTTP.6.0")
End Sub

Simulating the ProgID resolution

With COM the resolution of the ProgID take places by calling CLSIDFromString in OLE32.dll and we can write code to simulate this and then go lookup in the registry


Option Explicit

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(7) As Byte
End Type

Private Declare Function OLE32_CLSIDFromString Lib "OLE32" _
    Alias "CLSIDFromString" (ByVal lpszCLSID As String, pclsid As GUID) As Long
    
Public Function VBA_CLSIDFromString(ByVal sClass As String) As String
    
    Dim rclsid As GUID
    Dim hr As Long
    hr = OLE32_CLSIDFromString(StrConv(sClass, vbUnicode), rclsid)
    If hr <> 0 Then Err.Raise hr

    Dim sHexCLSID As String
    
    sHexCLSID = "{" & PadHex(rclsid.Data1, 8) & "-" & PadHex(rclsid.Data2, 4) & "-" & _
                PadHex(rclsid.Data3, 4) & "-"
    
    Dim lData4Loop As Long
    For lData4Loop = 0 To 7
        If lData4Loop = 2 Then sHexCLSID = sHexCLSID & "-"
        sHexCLSID = sHexCLSID & PadHex(rclsid.Data4(lData4Loop), 2)
    
    Next lData4Loop
    
    VBA_CLSIDFromString = sHexCLSID & "}"
    Debug.Assert Len(VBA_CLSIDFromString) = 38
End Function

Private Function PadHex(ByVal lNum As Long, ByVal lDigits As Long) As String
    PadHex = Right(String(lDigits, "0") & Hex$(lNum), lDigits)
End Function

Public Function WMI_COMClassVersion(ByVal sClsId As String) As String
    Dim oWMIReg As Object
    Set oWMIReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\" & _
             ".rootdefault:StdRegProv")
    Dim sVersionString As String
    oWMIReg.GetStringValue &H80000002, "SOFTWAREClassesCLSID" & sClsId & "Version", "", sVersionString
    WMI_COMClassVersion = sVersionString

End Function


Public Function WhatVersionOfProgID(sClass As String) As String
    
    Dim sClsId As String
    sClsId = VBA_CLSIDFromString(sClass)
    
    WhatVersionOfProgID = WMI_COMClassVersion(sClsId)
    Exit Function
End Function

Private Sub TestWhatVersionOfProgID()
    Debug.Assert WhatVersionOfProgID("MSXML2.ServerXMLHTTP") = "3.0"
End Sub


Appendix A

Sweeping my registry for instances of MSXML2.ServerXMLHTTP turned up the following


Computer\HKEY_CLASSES_ROOT\CLSID\{88d96a0b-f192-11d4-a65f-0040963251e5}\ProgID
Computer\HKEY_CLASSES_ROOT\CLSID\{AFB40FFD-B609-40A3-9828-F88BBE11E4E3}\ProgID
Computer\HKEY_CLASSES_ROOT\CLSID\{AFB40FFD-B609-40A3-9828-F88BBE11E4E3}\VersionIndependentProgID
Computer\HKEY_CLASSES_ROOT\CLSID\{AFBA6B42-5692-48EA-8141-DC517DCF0EF1}\ProgID
Computer\HKEY_CLASSES_ROOT\CLSID\{AFBA6B42-5692-48EA-8141-DC517DCF0EF1}\VersionIndependentProgID
Computer\HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{AFB40FFD-B609-40A3-9828-F88BBE11E4E3}\VersionIndependentProgID
Computer\HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{AFBA6B42-5692-48EA-8141-DC517DCF0EF1}\ProgID
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{88d96a0b-f192-11d4-a65f-0040963251e5}\ProgID
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{AFBA6B42-5692-48EA-8141-DC517DCF0EF1}\ProgID
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{AFBA6B42-5692-48EA-8141-DC517DCF0EF1}\VersionIndependentProgID
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Msxml2.ServerXMLHTTP
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Msxml2.ServerXMLHTTP.3.0
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Msxml2.ServerXMLHTTP.5.0
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Msxml2.ServerXMLHTTP.6.0
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Msxml2.ServerXMLHTTP\CurVer
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\WOW6432Node\CLSID\{88D969EB-F192-11D4-A65F-0040963251E5}\ProgID
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\WOW6432Node\CLSID\{88d96a0b-f192-11d4-a65f-0040963251e5}\ProgID
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\WOW6432Node\CLSID\{AFB40FFD-B609-40A3-9828-F88BBE11E4E3}\ProgID
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\WOW6432Node\CLSID\{AFB40FFD-B609-40A3-9828-F88BBE11E4E3}\VersionIndependentProgID
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\WOW6432Node\CLSID\{AFBA6B42-5692-48EA-8141-DC517DCF0EF1}\ProgID
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\WOW6432Node\CLSID\{AFBA6B42-5692-48EA-8141-DC517DCF0EF1}\VersionIndependentProgID
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Classes\CLSID\{88D969EB-F192-11D4-A65F-0040963251E5}\ProgID
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Classes\CLSID\{88d96a0b-f192-11d4-a65f-0040963251e5}\ProgID
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Classes\CLSID\{AFB40FFD-B609-40A3-9828-F88BBE11E4E3}\ProgID
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Classes\CLSID\{AFB40FFD-B609-40A3-9828-F88BBE11E4E3}\VersionIndependentProgID
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Classes\CLSID\{AFBA6B42-5692-48EA-8141-DC517DCF0EF1}\ProgID
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Classes\CLSID\{AFBA6B42-5692-48EA-8141-DC517DCF0EF1}\VersionIndependentProgID


Links

Wednesday, 27 December 2017

Fake namespace! VBA MSXML2 XPath namespace looks like we need to fake a prefix for the default namespace

This is more a TODO post to myself to find a better solution to a VBA Xml programming program. Visitors are welcome to comment at the bottom if they know a better answer.

So a while ago, I had a problem with using XPath to get some elements out of an Xml Dom using VBA Xml library, MSXML2. You can see my SO question about it here. I expressed unhappiness about how we have to fake a prefix when setting the dom's SelectionNamespace.

Today a similar question arose but the twist here is that multiple namespaces have to be set. Here is my answer.

To set multiple namespaces simply space separate thus...


    dom.setProperty "SelectionNamespaces", "xmlns:sf='urn:sobject.enterprise.soap.sforce.com' xmlns:sf2='urn:enterprise.soap.sforce.com'"


(I omitted a couple of namespaces for formatting issues but you get the idea)

So I got some code working for the OP but I'd really like to find a way to not have to fake the prefix. In the source Xml note xmlns="urn:enterprise.soap.sforce.com" has no prefix but in our VBA we have to fake it with "sf2".

As a working resource I'm copying the Xml file here and the code below


<?xml version="1.0" encoding="UTF-8" ?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:enterprise.soap.sforce.com" xmlns:sf="urn:sobject.enterprise.soap.sforce.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
  <soapenv:Header>
    <LimitInfoHeader>
      <limitInfo>
        <current>50</current>
        <limit>5000000</limit>
        <type>API REQUESTS</type>
      </limitInfo>
    </LimitInfoHeader>
  </soapenv:Header>
  <soapenv:Body>
    <queryResponse>
      <result>
        <done>true</done>
        <queryLocator xsi:nil="true"/>
        <records xsi:type="sf:Nodav">
          <sf:Id>a0S0E000000DMUg320</sf:Id>
          <sf:Name>Netta test 11</sf:Name>
        </records>
        <records xsi:type="sf:Nodav">
          <sf:Id>a0S0E000000DMUg321</sf:Id>
          <sf:Name>Netta test 32</sf:Name>
        </records>
        <size>2</size>
      </result>
    </queryResponse>
  </soapenv:Body>
</soapenv:Envelope>


Option Explicit


Sub TestCoreLogic()
    Dim dom As MSXML2.DOMDocument60
    Set dom = New MSXML2.DOMDocument60
    
    dom.Load "N:\xmlfile1.xml"
    Debug.Assert dom.parseError.ErrorCode = 0

    dom.setProperty "SelectionLanguage", "XPath"
    
    dom.setProperty "SelectionNamespaces", "xmlns:sf='urn:sobject.enterprise.soap.sforce.com' xmlns:sf2='urn:enterprise.soap.sforce.com' " & _
            "xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' "
    
    CoreLogic dom
End Sub
'
Function CoreLogic(ByVal xmlDoc As MSXML2.DOMDocument60)

    Dim records As MSXML2.IXMLDOMElement
    Set records = xmlDoc.DocumentElement
    
    Dim lists As MSXML2.IXMLDOMNodeList
    
    'Debug.Assert records.SelectNodes("//soapenv:Envelope").Length = 1
    'Debug.Assert records.SelectNodes("//soapenv:Envelope/soapenv:Body").Length = 1
    'Debug.Assert records.SelectNodes("//soapenv:Envelope/soapenv:Body/sf2:queryResponse").Length = 1
    'Debug.Assert records.SelectNodes("//soapenv:Envelope/soapenv:Body/sf2:queryResponse/sf2:result").Length = 1
    '
    'Debug.Assert records.SelectNodes("//soapenv:Envelope/soapenv:Body/sf2:queryResponse/sf2:result/sf2:records").Length = 2
    'Set lists = records.SelectNodes("//soapenv:Envelope/soapenv:Body/sf2:queryResponse/sf2:result/sf2:records")
    
    Debug.Assert records.SelectNodes("//sf2:records").Length = 2
    Set lists = records.SelectNodes("//sf2:records")

    Dim listNode As MSXML2.IXMLDOMNode
    
    For Each listNode In lists
        Debug.Print "---Email---"
        
        Dim fieldNode As MSXML2.IXMLDOMNode
        For Each fieldNode In listNode.ChildNodes
            Debug.Print "[" & fieldNode.BaseName & "] = [" & fieldNode.Text & "]"
        Next fieldNode
    Next listNode

    Set records = Nothing
    Set lists = Nothing
    Set listNode = Nothing
    Set fieldNode = Nothing


End Function