Monday 21 April 2014

How to use XSD2Code for SYSPRO’s Business Objects

When you call one of SYSPRO’s API’s, called a Business Object, via a web service or COM or DCOM or WCF, you need to pass it some XML. That XML is defined in a couple of XSD files. Generating that XML can be done easily in C# or VB .Net using a tool such as XSD2Codewhich is available from http://xsd2code.codeplex.com/. (Note that for Visual Studio 2012, you need XSD2CODE Beta Version 3.5 or later.)
The advantage of using a C# object rather than coding your XML as one big string is you get the advantages including:
  • Intellisense in Visual Studio – it shows you descriptions about each XML element from the XSD file’s annotations
  • Type safety
  • Easier to code: you don’t have to convert your types to strings, nor worry about maximum lengths etc. – it’s all done for you.
( I also tried using Microsoft’s XSD.exe tool, but found XSD2Code to be far superior – it is more configurable and so can be made to fit SYSPRO.)
These instructions are written for XSD2Code version 3.4 with Visual Studio 2010 and SYSPRO 6.1.
  1. Download and install XSD2Code. It integrates naturally with Visual Studio.
  2. From the <SYSPRO installation directory>/BASE/SCHEMAS directory,  add the XSD files for your desired business object into your project. E.g. to call the SORTOI business object, add C:\Syspro61\Base\Schemas\SORTOI.XSD and SORTOIDOC.XSD into your Visual Studio Project. Actually, I prefer to copy the XSD files into the appropriate sub-directory of my Visual Studio project and then add them from there, so that I can put the XSD files under source code control. (I use Mercurial.)
  3. In the Visual Studio Solution Explorer, right-click on the new XSD file and select Run XSD2Code Generation.
  4. Set the following parameters; the rest can stay as their default values. This is the key bit:
    1. PropertyParams/GeneratePropertyNameSpecified must be None, otherwise you won’t get parameters generated unless you set the correspondingfieldSpecified to true.
    2. Set the Code/NameSpace to <your-namespace> + the XSD name, so that there aren’t clashes with other XSD’s that use the same internal variables. E.g. I set mine to Syspro.Base.Schema.SORTOI and Syspro.Base.Schema.SORTOIDOC.
    3. Set the Code/TargetFramework to Silverlight or Net40 as appropriate.
    4. Leave the Serialization/DefaultEncoder as UTF-8.
    5. Set Serialization / Enabled to True. This saves you having to write your own serialization routine.
    6. Set Serialization / GenerateXMLAttributes to True.
  5. Press the Generate button.

Here is a screen shot of settings to use if you are using XSD2Code version 3.5:

Group your files
In the Visual Studio Solution Explorer, to have the newly generated .cs file (e.g. SORTOI.designer.cs) grouped under the XSD file (e.g. SORTOI.XSD), i.e. as a branch UNDER the XSD file instead of as a sibling file, follow these instructions:
  1. Close Visual Studio.
  2. Edit the .csproj file for that project with a text editor (such as vim or Notepad).
  3. Add DependentUpon tags (and open and close the Compile tag).
    E.g. change this line:
    <Compile Include="Schema\SORTOI.designer.cs" />
    to this:
    <Compile Include="Schema\SORTOI.designer.cs">
    <DependentUpon>SORTOI.XSD</DependentUpon>
    </Compile>

Use Namespaces

Note that when you tell XSD2Code what namespace to use, you need to use a different namespace for each XSD file as many of SYSPRO’s XSD files use properties with the same name, and you need to avoid those names clashing.
For example, for SOIRTOI.XSD, you could use a namespace of Syspro.Base.Schema.SORTOI;
for SORTOIDOC.XSD, you could use a different namespace such as Syspro.Base.Schema.SORTOIDOC.In other words, don’t use the same namespace for all your business objects. 
Then, in your C# code, you can add a reference to your Syspro.Base.Schema project, and you might like to write using statements such as these:
using SORTOI = Syspro.Base.Schema.SORTOI;
using SORTOIDOC = Syspro.Base.Schema.SORTOIDOC;
Then you will be able to access the generated objects with minimal namespace paths in your code.

Generate your XML

Now you can create code your XML settings using plain old C# objects as simply as this example:
var SORTOI_Parameters = new SORTOI.PostOrders()
{
 Parameters = new SORTOI.Parameters()
 {
     ValidateOnly = PORTOI.ValidateOnly.Y,
     StatusInProcess = PORTOI.StatusInProcess.N
 }
};

Edit the code to correct List definitions

Some List definitions in SYSPRO’s XSD files don’t seem to be properly declared, so you may have to manually edit the generated code and find all lines that begin with public List and add a line like this above the declaration (change the parameter on the right to match your type):
[System.XML.Serialization.XmlElementAttribute("Item")]
so that the final code looks like this example:
[System.Xml.Serialization.XmlElementAttribute("Item")]
public List<SetupQotNonStockItem> Item
This problem only occurs for certain XSD files (such as WIPTPBDOC.xsd), where-as others such as PORTOIDOC.xsd have perfectly-generated code. My guess is that some of SYSPRO’s XSD files are not well constructed.
(Note that XSD2Code’s GenerateXMLAttributes option creates these XmlElementAttributes automatically… if the XSD code is correct.)

Sometimes the XSD file is incorrect

Sometimes the XSD file is actually incorrect. I am getting such problems reported and corrected if and when I find them. See this forum post for more information.

If you right-click on the XSD file but can’t see “Run XSD2Code generation”

If you right-click on your XSD file but can’t see Run XSD2Code generation enabled, this may be because the highlighted file has just switched to a different file, due to some behaviour in Visual Studio. To work around this, double-click on your XSD file first so that it comes up for editing; then you will be able to right-click on the XSD file and generate code for it.

Serialize your objects

To convert your object to XML, you can use code such as this:
string XmlParameters = SORTOI_Parameters.Serialize();

Syspro’s VBScript Business Objects don’t like namespace declarations

Note that if you may have XML Namespace declarations in your XML from serialization like this:
<Orders xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema""">
This shouldn’t be a problem BUT if you are passing this XML to Syspro’s VBScript and then calling a Business Object from VBScript, it will not like the xmlns declaration, so you may want to add this code on the VBSCript side (see related article, How to invoke a Syspro VBScript function from a .Net User Control):
Dim nameSpaceDeclaration
nameSpaceDeclaration = "xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"""
XmlParameters = Replace(XmlParameters, nameSpaceDeclaration,"")
This was the case for Syspro 6.1 Port 69.
However, this doesn’t seem to be an issue if calling the Business Object not from VB Script but from a web service or via DCOM.

Adjust the encoding for Silverlight

If you are using this in Silverlight, you may need to adjust the XML encoding; this is an ugly way of doing it:
XmlParameters = XmlParameters.Replace("encoding=\"utf-8\"", "encoding=\"Windows-1252\"");
A better way of doing this may be to pass the Serialize routine a parameter to select which encoding to use; I have yet to try this.

Finally

Then you can pass those XML strings to the web service for the business object, e.g:
setupProxySORTOI.AddAsync(userId, "SORTOI", XMLParameters, XMLIn);

See Also

How to deploy a .Net User Control, that uses multiple assemblies, in a SYSPRO custom pane

If you create a simple .Net User Control and deploy it in a custom pane in SYSPRO, it will work, but if your user control accesses any non-system assemblies, your user control will appear as just a blank grey pane like this:

There are a number of ways you can resolve this:
  • Install your additional assemblies in the GAC.
  • Copy your additional assemblies to your SYSPRO/BASE folder (but leave your initial assembly in the SYSPRO/BASE/ManagedAssemblies folder). See see this post in the SYSPRO Forum for more details.
  • Write your own AssemblyResolve event handler. See see this same post in the Syspro Forum for more details.
  • Combine your assemblies into one assembly using ILMERGE, which you can download from Microsoft here.
    You can edit your .Net project’s properties and create a Post-Build event such as this:
"C:\Program Files (x86)\Microsoft\ILMerge\ILMerge.exe" /v4 $(TargetFileName) YourDLL.dll YourSecondDLL.dll /out:"$(ProjectDir)$(OutDir)..\$(TargetFileName)"
If you need to copy the config file, you can add this to the Post-build event script:
copy $(TargetFileName).config "$(ProjectDir)$(OutDir)..\"
The double quotes are needed around the output directory if your output directory path has spaces in it.

How to access a SYSPRO Executive Dashboard from an iPad or Android

Here’s how to view SYSPRO’s Executive Dashboards on your iPad or Android or similar device.

High-level summary

  1. On your iPad or Android, you need an app that can display Shockwave files.
  2. VBScript calls to SYSPRO BUsiness Objects need to be changed to go via SYSPRO’s e.Net service, either via web services or via COM or DCOM. You’ll need a programmer to make these changes.

Technical summary

  1. On your iPad, install an app that can display Shockwave (.swf) files.
    Photon is one such app; see below for a list of others.
  2. Copy the dashboard files into IIS.
  3. If you are going to access Syspro’s Business Objects via Interop.Encore.dll (i.e. via COM), then you will need to set the IIS application’s Application Pool to be 32-bit enabled.
  4. Create a .aspx file from the dashboard’s .txt file.
  5. Edit the VB code in the .aspx file so that:
    1. It is a valid .aspx file
    2. It creates the XML file with the dashboard’s data in the SAME directory as the .aspx file.
    3. It calls any SYSPRO Business Objects via web services instead of directly via CallBO.
  6. Remove the final call to COMGET. You don’t need this because you have already manually copied the file into IIS.

Detail

1. Set up IIS: Add an Application in IIS; give it an Alias and a Physical Path of your choosing, similar to the screen shot below.
Your existing Executive Dashboard will need to be modified, so you will need to make a copy of it; place those copied files (.xlf, .swf, .txt and .xls) in a directory accessible from IIS.

Select an appropriate Application Pool, as shown in the previous screen shot. For the Application Pool, you must select one that is 32-bit enabled so that your dashboard will be able to run 32-bit applications such as Encore.Interop, which it will need to use to access Syspro’s Business Objects. To find an Application Pool that is 32-bit enabled, or to enable one, in IIS go to Connections / Application Pools; select the desired Application Pool, click Advanced Settings, and set Enable 32-bit Applications to True, as shown in the two screen-shots below.


If you do not do this, then when you try to view your dashboard, you will get an error such as Cannot create ActiveX component.
Rename the copied the Executive Dashboard’s .txt file to .aspx, e.g.
rename exec_gl_turnover_actual_year_rate.txt exec_gl_turnover_actual_year_rate.aspx
Edit the .aspx file:
  1. Convert the RTF code to VB comments.
  2. Add these two lines at the top of the file:
    <%@ Page Language="VB" aspcompat=true Debug="true" validateRequest="false" %>
    <%
  3. Add this line at the bottom of the file:
    %>
  4. Add initial code near the top, such as:
    ' This is the directory where the dashboard files will go in IIS.
    Private Const BASE_DIR "C:\inetpub\wwwroot\xcelsius"
    
    ' This is the base filename for the dashboard files.
    ' This filename should NOT have an extension:
    Private Const BASE_FILENAME "exec_gl_turnover_actual_year_rate"
    
    ' SYSPRO Login details:
    Private Const SysproOperator = "ADMIN"
    Private Const operatorPassword = ""
    Private Const companyId = "0"
    Private Const companyPassword = ""
    
    Private Const languageCode = "05"
    Private Const logLevel = "0"
    Private Const encoreInstance = "0"
    
    Dim utilities as Object
    utilities = CreateObject("Encore.Utilities")
    
    Dim GUID
    GUID = utilities.Logon(SysproOperator, operatorPassword, companyId, companyPassword, languageCode, logLevel, encoreInstance, XmlIn)
    
    Dim query
    query = CreateObject("Encore.query")
  5. Remove the function declaration for CustomisedPane_OnRefresh, i.e. remove this line of code, but LEAVE THE CONTENTS of the function:
    Function CustomisedPane_OnRefresh()
  6. Remove the corresponding “End Function” line.
  7. Avoid Cross-Domain Policy errors: Note that the BASE_DIR will be the directory where all of the the dashboard’s need to be placed: the .swf file, the .aspx file, and the .xml file that is updated each time the dashboard is served up by IIS. In particular, note that your .aspx file will create an XML file that is read by the Shockwave file; this XML file MUST live in the same directory as the Shockwave file; otherwise you will get a cross-domain error when shockwave tries to access the XML file, as shown in the screen shot below:
  8. Change this line, near the end of the CustomizedPane_OnRefresh function:
    dashfile = SystemVariables.CodeObject.baseSettingsFolder & "Dashboards\exec_gl_turnover_actual_year_rate_output.xml"
    Replace it with this line:
    dashfile = BASE_DIR & "\" & BASE_FILENAME & "_output.xml"
    (This output filename, the dashfile, should match the output file that your Dashboard looks for in its Data Connection as set in Excel or Xcelsius under Data / Connections.)
  9. Delete any “Set” commands; just leave them as basic assign statements.
    E.g. Change lines like these:
    Set xmlDoc = createobject("Msxml2.FreeThreadedDOMDocument.3.0")
    ...
    Set Dom = createobject("MSXML.DOMDocument")
    to these:
    xmlDoc = createobject("Msxml2.FreeThreadedDOMDocument.3.0")
    ...
    Dom = createobject("MSXML.DOMDocument")
  10. Delete the entire function, CustomizedPane_OnLoad().
  11. Add code to serve the Shockwafe file:
    ' Output the SWF file.
    ' Reference: https://help.maximumasp.com/KB/a330/binarywrite-to-response-object-does-not-work-when-binary.aspx
    Dim chunkSize, stream
    
    chunkSize = 262144 ' Read the stream in 256K chunks
    stream = Server.CreateObject("ADODB.Stream")
    stream.Open
    stream.Type = 1
    stream.LoadFromFile (BASE_DIR & "\" & BASE_FILENAME & ".swf")
    Response.Expires = 0
    
    'Turn off buffering
    Response.Buffer = False
    Response.ContentType = "application/x-shockwave-flash"
    
    ' Enclose the binarywrite statement in a loop
    ' that will read your stream in a specified chunk size.
    While Response.IsClientConnected And (stream.Position < stream.Size)
     Response.BinaryWrite(stream.Read(chunkSize))
    End While
    
    stream.Close
    stream = Nothing
  12. Update all calls to Business Objects:
    The VBScript, when it was running inside SYSPRO, had direct access to all the Business Objects via the function, CallBO. As the VBScript will now be running in IIS, it will not have direct internal access to the Business Objects. To resolve this, we can call Syspro’s .Net Interop object (Encore.dll). To do this, replace lines such as the following:
    on error resume next
    XMLOut = CallBO("COMGRW",XMLIn,"auto")
    if err then
     msgbox err.Description, vBCritical, "Calling Business Object COMGRW"
     exit function
    end if
    Replace it with code such as this:
    XmlOut = query.Query(GUID, "COMGRW", XmlIn)
    Do this for all calls to CallBO.
You should now be able to open the Dashboard in any web browser that has the Addon for Shockwave installed; you will need the URL that takes you to the .swf file.

iPad setup

Install some software that can display Shockwave files, i.e. stream Flash, such as:
  • Appsverse Photon - costs around $5.
  • Inovista – this is possibly the best one; the reader is free but you have to get the appropriate plugin which costs around $20.
Here are some more possibilities but I do not know if these work:
Enter the URL for the .swf file into your app and view the dashboard.

How to invoke a SYSPRO VBScript function from a .Net User Control

If you are invoking a SYSPRO VBScript function from a .Net User Control on a Custom pane, there are a number of traps to avoid that aren’t explicitly stated in the otherwise very helpful document, Using .Net User Controls in 6.1 on the SYSPRO Support Zone.

Your delegate must be in the same namespace

Your delegate that calls doRefresh must be in the same namespace as the control linked to the customized pane. (This isn’t explicitly stated in the version of the documentation published 08 June 2010, although the converse situation is mentioned in the section, “Invoking methods on the User Control from a SYSPRO form”.)

Parameter values must be able to become valid VBScript

The value being passed as a parameter to doRefresh must compile as standard VBScript, i.e.:
  • if it contains any double quotes, they must be escaped as two double quotes. Even better, on the .Net side, just encode any double quotes as a non-printable character such as CTRL+C, then on the VBScript side, do the reverse.
  • if it contains any new lines, they must be removed.
The parameter which becomes the RefreshValue in VBScript CAN be of unlimited length (contrary to what I first wrote here).
Here is some example code for the DotNet side to safely encode double quotes and new lines:
private static string MakeSafeForVBScript(string sourceString)
{
 // The VB Script will essentially do a statement like this:
 // Dim RefreshValue : Refreshvalue = "<?xml version="1.0" encoding="Windows-1252"?>
 // However, it will give a syntax error on any embedded double quotes or new lines,
 // so convert new lines into CTRL+B,
 // and convert double-quotes into CTRL+D.

 string newLineReplacement = ((char)0x2).ToString(); // CTRL+B
 const char doubleQuoteReplacement = (char)0x4; // CTRL+D
 const char doubleQuote = '"';

sourceString = sourceString.Replace(doubleQuote, doubleQuoteReplacement).Replace("\r\n", newLineReplacement);

 return dotNetVariable;

}
Here is an example VBScript code snippet; it puts the double-quotes and new lines back:
Function Unencode (encodedString)
 Dim control_B
 Dim control_D
 Dim doubleQuote
 Dim newLine
 Dim result

 control_B = Chr(2)
 control_D = Chr(4)
 doubleQuote = Chr(34)
 newLine = chr(13) & chr(10) ' \r\n, i.e. CTRL+M CTRL+J.
 encodedString = Replace(encodedString, control_D, doubleQuote)

 Unencode = Replace(encodedString, control_B, newLine)

End Function

GetApplicationInfo and GetSysproInfo don’t always return pure XML

Note that returned XML from GetApplicationInfo or GetSysproInfo does NOT always contain valid XML, because it sometimes contains embedded double quotes, so you have to  escape those quotes before passing the returned value into an XML parser.
E.g. the returned XML may contain a fragment like this:
<DiagnosticAppPath Value="""C:\SYSPRO61\BASE\IMPACT.EXE"""/>
This needs to become (note the backslashed quotes):
<DiagnosticAppPath Value="\"C:\SYSPRO61\BASE\IMPACT.EXE\""/>
You can use the following C# code to sort out the embedded quotes:
String tripleQuotes = @"""""""";
String singleQuoteBackslashedQuote = @"""";
var fixedQuotes = systemVariables.Replace(tripleQuotes, singleQuoteBackslashedQuote);

Remove XML NameSpace declarations

Also note that Syspro’s Business Objects called from SYSPRO’s VBScript doesn’t like XML that has namespace declarations; see How to use XSD2Code for Syspro Business Objects for more details. (Calls to SYSPRO’s Business Objects via Web Services or DCOM don’t seem to have this problem.)

Conclusion

After sorting these things out, you should be ready to run.

How to install Xcelsius 2008 with Office 2007 or Office 2010

If you try to install Xcelsius 2008 on a Windows computer that has Office 2007 or Office 2010 but NOT Office 2003 installed, then you will get the following error:
“Microsoft Office XP and later applications: Excel, Word, Outlook or PowerPoint must be pre-installed.”
To solve this, download and install Microsoft’s Orca tool, which you can get from here:http://www.softpedia.com/get/Authoring-tools/Setup-creators/Orca.shtml
and possible from here:
http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=6510
Create a backup copy of the Xcelsius installation file, Xcelsius.msi.
Open Xcelsius.msi with Orca.
Find the Launch Condition that looks for Office 2003, as highlighted in the screen shot below.

You are looking for the Condition that says:
(EXCEL10 OR OUTLOOK10 OR WORD10) OR (EXCEL11 OR OUTLOOK11 OR WORD11) OR (EXCEL12 OR OUTLOOK12 OR WORD12) OR Installed
Change the condition; replace it with the exact number of spaces as there are letters in the condition.
Save and exit.
You should now be able to install Xcelsius 2008.

How to access SOAP services from VBA in Excel or similar

To access a SOAP web service (such as a Syspro web service) from Visual Basic for Applications (such as an Excel 2003 macro), you can download Microsoft’s SOAP toolkit and access that from Visual Basic.
If you tailor the example code from Microsoft’s SOAP toolkit to make it easy to call Syspro functions; here’s a code snippet example:
Dim GUID as String
Dim XmlOut as String
Dim WebServicesBaseURL = "http://example.com/sysprowebservices"

GUID = LogonToSysproViaWebServices()
XmlOut = CreateSalesOrderWebServices(GUID, "SORTOI", XmlParameters, XmlIn)

Private Function LogonToSysproViaWebServices() As String

    Dim XmlIn As String
    Dim XmlOut As String
    Dim GUID As String

    XmlIn = ""

    Dim objSysproWS As New Syspro_Utilities_Web_Service
    objSysproWS.Setup WebServicesBaseURL

    XmlOut = objSysproWS.Logon(Operator, OperatorPassword, CompanyId, CompanyPassword, LanguageCode, LogLevel, EncoreInstance, XmlIn)

    GUID = Trim(XmlOut)

    Set objSysproWS = Nothing

    LogonToSysproViaWebServices = GUID

End Function

Private Function CreateSalesOrderWebServices(ByRef GUID As String, ByRef BusinessObject As String, ByRef XmlParameters As String, ByRef XmlIn As String) As String
    Dim XmlOut As String
    Dim objSysproWS As New Syspro_Transaction_Web_Service
    objSysproWS.Setup WebServicesBaseURL

    XmlOut = objSysproWS.Post(GUID, BusinessObject, XmlParameters, XmlIn)

    Set objSysproWS = Nothing

    CreateSalesOrderWebServices = XmlOut

End Function ' CreateSalesOrderWebServices

Place the following code in a Class Module in Excel; call the moduleSyspro_Utilities_Web_Service, or change the above code to match whatever you call it.
'*****************************************************************
' This is based on Microsoft-generated code from:
' http://msdn.microsoft.com/en-us/magazine/cc163837.aspx
' ... but it has been modified to make the URL configurable.
'
'*****************************************************************
'This class was created by the Microsoft Office 2003 Web Services Toolkit.
'
'Description:
'This class is a Visual Basic for Applications class representation of the 'Web service as defined by http://localhost/sysprowebservice/transaction.asmx?wsdl.
'
'To Use:
'Dimension a variable as new clsws_Service, and then write code to
'use the methods provided by the class.
'Example:
' Dim ExampleVar as New clsws_Service
' debug.print ExampleVar.wsm_Post("Sample Input")
'
'For more information, see Complex Types in Microsoft Office 2003
'Web Services Toolkit Help.
'
'Changes to the code in this class may result in incorrect behavior.
'
'*****************************************************************

Option Explicit

'Dimensioning private class variables.
Private sc_Service As SoapClient30

' e.g. Private Const c_WSDL_URL As String = "http://localhost/sysprowebservices/utilities.asmx?WSDL"
' e.g. Private Const c_WSDL_URL As String = "http://www.example.com/sysprowebservices2/utilities.asmx?WSDL"
' The last part of the line MUST be "/utilities.asmx?WSDL".
'Private Const c_WSDL_URL As String = "http://localhost/sysprowebservices/utilities.asmx?WSDL"

Private c_WSDL_URL As String
Private Const c_WSDL_URL_Extension As String = "/utilities.asmx?WSDL"

Private Const c_SERVICE As String = "Service"
Private Const c_PORT As String = "ServiceSoap"
Private Const c_SERVICE_NAMESPACE As String = "http://www.syspro.com/ns/utilities/"


Private Sub Class_Initialize()

End Sub


Public Sub Setup(ByVal c_WSDL_URL_Base As String)
    '*****************************************************************
    'This subroutine will be called each time the class is instantiated.
    'Creates sc_ComplexTypes as new SoapClient30, and then
    'initializes sc_ComplexTypes.mssoapinit2 with WSDL file found in
    'http://localhost/sysprowebservices/transaction.asmx?wsdl.
    '*****************************************************************
    
    Dim str_WSML As String
    ' WSML: Default value is "".
    ' This string is in Web Services Meta Language (WSML).
    ' This is a required parameter only when using custom type mappers.
    str_WSML = ""
    Set sc_Service = New SoapClient30
    ' Set sc_Service = Server.CreateObject("MSSOAP.SoapClient30")
    
    ' Not needed:
    'sc_Service.ClientProperty("ServerHTTPRequest") = True

    c_WSDL_URL = c_WSDL_URL_Base & c_WSDL_URL_Extension

    sc_Service.MSSoapInit (c_WSDL_URL)
    
    ' Doesn't work; reason unknown:
    'sc_Service.MSSoapInit2 c_WSDL_URL, str_WSML, c_SERVICE, c_PORT, c_SERVICE_NAMESPACE
    
    'Use the proxy server defined in Internet Explorer's LAN settings by
    'setting ProxyServer to 
    sc_Service.ConnectorProperty("ProxyServer") = ""

    'Autodetect proxy settings if Internet Explorer is set to autodetect
    'by setting EnableAutoProxy to True
    sc_Service.ConnectorProperty("EnableAutoProxy") = True

End Sub


Private Sub Class_Terminate()
    '*****************************************************************
    'This subroutine will be called each time the class is destructed.
    'Sets sc_ComplexTypes to Nothing.
    '*****************************************************************
    'Error Trap
    On Error GoTo Class_TerminateTrap
    Set sc_Service = Nothing
    Exit Sub

Class_TerminateTrap:
    ServiceErrorHandler ("Class_Terminate")

End Sub


Private Sub ServiceErrorHandler(str_Function As String)
    '*****************************************************************
    'This subroutine is the class error handler. It can be called from any
    'class subroutine or function when that subroutine or function
    'encounters an error. Then, it will raise the error along with the
    'name of the calling subroutine or function.
    '*****************************************************************

    'SOAP Error
    If sc_Service.FaultCode  "" Then
        Err.Raise vbObjectError, str_Function, sc_Service.FaultString

    'Non SOAP Error
    Else
        Err.Raise Err.Number, str_Function, Err.Description
    End If
End Sub


Public Function Logon(ByVal Operator As String, ByVal OperatorPassword As String, ByVal CompanyId As String, ByVal CompanyPassword As String, ByVal LanguageCode As String, ByVal LogLevel As String, ByVal EncoreInstance As String, ByVal XmlIn As String) As String

    '*****************************************************************
    'Proxy function created from
    'http://localhost/sysprowebservice/utilities.asmx?wsdl.
    '
    '"Logon" is defined as XML. See Complex Types: XML Variables
    'in Microsoft Office 2003 Web Services Toolkit Help for details on
    'implementing XML variables.
    '*****************************************************************
    'Error Trap
    On Error GoTo Logon_ErrorHandler
    Logon = sc_Service.Logon(Operator, OperatorPassword, CompanyId, CompanyPassword, LanguageCode, LogLevel, EncoreInstance, XmlIn)
    Exit Function

Logon_ErrorHandler:
    ServiceErrorHandler "Logon"

End Function


Public Function Logoff(ByVal Operator As String) As String

    '*****************************************************************
    'Proxy function created from
    'http://localhost/sysprowebservice/utilities.asmx?wsdl.
    '
    '"Logoff" is defined as XML. See Complex Types: XML Variables
    'in Microsoft Office 2003 Web Services Toolkit Help for details on
    'implementing XML variables.
    '*****************************************************************
    'Error Trap
    On Error GoTo Logoff_ErrorHandler
    Logoff = sc_Service.Logoff(Operator)
    Exit Function

Logoff_ErrorHandler:
    ServiceErrorHandler "Logoff"

End Function

' TODO: Create other routines: GetLogonProfile, Run.
Place the following code in a Class Module in Excel; call the moduleSyspro_Transaction_Web_Service, or change the above code to match whatever you call it.
'*****************************************************************
' Proxy to post to the a web service.
'
' This is based on Microsoft-generated code from:
' http://msdn.microsoft.com/en-us/magazine/cc163837.aspx
' ... but it has been modified to make the URL configurable.
'
'*****************************************************************
'This class was created by the Microsoft Office 2003 Web Services Toolkit.
'
'Description:
'This class is a Visual Basic for Applications class representation of the 'Web service as defined by http://localhost/sysprowebservice/transaction.asmx?wsdl.
'
'To Use:
'Dimension a variable as new clsws_Service, and then write code to
'use the methods provided by the class.
'Example:
' Dim ExampleVar as New clsws_Service
' debug.print ExampleVar.wsm_Post("Sample Input")
'
'For more information, see Complex Types in Microsoft Office 2003
'Web Services Toolkit Help.
'
'Changes to the code in this class may result in incorrect behavior.
'
'*****************************************************************

Option Explicit

'Dimensioning private class variables.
Private sc_Service As SoapClient30

Dim c_WSDL_URL As String
Private Const c_WSDL_URL_Extension As String = "/transaction.asmx?WSDL"

Private Const c_SERVICE As String = "Service"
Private Const c_PORT As String = "ServiceSoap"
Private Const c_SERVICE_NAMESPACE As String = "http://www.syspro.com/ns/transaction/"


Private Sub Class_Initialize()

End Sub


Public Sub Setup(ByVal c_WSDL_URL_Base As String)
    '*****************************************************************
    'This subroutine will be called each time the class is instantiated.
    'Creates sc_ComplexTypes as new SoapClient30, and then
    'initializes sc_ComplexTypes.mssoapinit2 with WSDL file found in
    'http://localhost/sysprowebservices/transaction.asmx?wsdl.
    '*****************************************************************

    Dim str_WSML As String
    str_WSML = ""
    Set sc_Service = New SoapClient30
    ' Set sc_Service = Server.CreateObject("MSSOAP.SoapClient30")
    
    ' Not needed:
    'sc_Service.ClientProperty("ServerHTTPRequest") = True

 c_WSDL_URL = c_WSDL_URL_Base & c_WSDL_URL_Extension

    sc_Service.MSSoapInit (c_WSDL_URL)
    
    ' Doesn't work; reason unknown:
    'sc_Service.MSSoapInit2 c_WSDL_URL, str_WSML, c_SERVICE, c_PORT, c_SERVICE_NAMESPACE

    'Use the proxy server defined in Internet Explorer's LAN settings by
    'setting ProxyServer to 
    sc_Service.ConnectorProperty("ProxyServer") = ""

    'Autodetect proxy settings if Internet Explorer is set to autodetect
    'by setting EnableAutoProxy to True
    sc_Service.ConnectorProperty("EnableAutoProxy") = True

End Sub


Private Sub Class_Terminate()
    '*****************************************************************
    'This subroutine will be called each time the class is destructed.
    'Sets sc_ComplexTypes to Nothing.
    '*****************************************************************
    'Error Trap
    On Error GoTo Class_TerminateTrap
    Set sc_Service = Nothing
    Exit Sub

Class_TerminateTrap:
    ServiceErrorHandler ("Class_Terminate")

End Sub


Private Sub ServiceErrorHandler(str_Function As String)
    '*****************************************************************
    'This subroutine is the class error handler. It can be called from any
    'class subroutine or function when that subroutine or function
    'encounters an error. Then, it will raise the error along with the
    'name of the calling subroutine or function.
    '*****************************************************************

    'SOAP Error
    If sc_Service.FaultCode  "" Then
        Err.Raise vbObjectError, str_Function, sc_Service.FaultString

    'Non SOAP Error
    Else
        Err.Raise Err.Number, str_Function, Err.Description
    End If
End Sub


Public Function Post(ByVal UserId As String, ByVal BusinessObject As String, ByVal XmlParameters As String, ByVal XmlIn As String) As String

    '*****************************************************************
    'Proxy function created from
    'http://localhost/sysprowebservice/transaction.asmx?wsdl.
    '
    '"Post" is defined as XML. See Complex Types: XML Variables
    'in Microsoft Office 2003 Web Services Toolkit Help for details on
    'implementing XML variables.
    '*****************************************************************
    'Error Trap
    On Error GoTo Post_ErrorHandler
    Post = sc_Service.Post(UserId, BusinessObject, XmlParameters, XmlIn)
    Exit Function

Post_ErrorHandler:
    ServiceErrorHandler "Post"

End Function

In Excel, in the Microsoft Visual Basic for Applications window, you will need to go to Tools / References and add these references, if you not using late binding to the Microsoft SOAP library:
  • Microsoft Soap Type Library v3.0
  • Microsoft Soap WinHttp Connector Type Library (v3.0)
  • Microsoft Soap WinInet Connector Type Library (v3.0)

Microsoft’s SOAP libraries have been deprecated

Microsoft recommends you use VSTO (VIsual Studio Tools for Office) going forward.

The SOAP toolkit doesn’t work on Windows 2008 R2 Server

Microsoft’s SOAP libraries have been deprecated, but if you have applications that still want to use SOAP, there are still ways to do that.
Firstly, I found that the Microsoft SOAP libraries still work on Windows XP, Windows 7 (both 32 and 64-bit), but NOT on Windows Server 2008 R2.
They don’t seem to work on Windows Server 2008 R2 because the system can’t find the SOAP DLL’s in the registry, because the system seems to use new, different, or wrong registry keys to locate the SOAP DLL’s:
On Windows Server 2008 R2, the system looked, unsuccessfully, for this registry key:
HKCR\Wow6432Node\CLSID\{the-GUID}\InprocHandler
but on Windows 7 64-bit, where it was successful, it looks for this registry key:
HKCR\Wow6432Node\CLSID\{the-GUID}\InprocServer32.
(This was discovered using Process Monitor to watch registry activity.

How to access SOAP web services from Windows Server 2008 R2

One solution to accessing SOAP is to create a .Net SOAP client (create a project, add a web service to your SOAP endpoint; add a subroutine to call that SOAP endpoint). Then expose that as a COM object so that you can consume your newly created DLL in VBA or where-ever you want.

NOTE for developers

Visual Studio 2010 must be run As Administrator so that you can test the DLL when you run it from Visual Studio as the SoapClient is exposed as a COM Object and needs to be registered when it is built so that it can be found when it is run.
(You can also manually register it using regasm; again you must run as Administrator.)
On the client’s machine, it doesn’t matter where you put the DLL on the client’s machine but the DLL must be registered using regasm.

See Also

  • The Code Snippet in Syspro’s VBScript editor – there’s a code snippet there for calling SOAP.