Thursday 4 September 2014

How to call a Syspro Business Object from a SQL Trigger

Sometimes one of the worst options for customising Syspro, using a SQL Trigger, is the best option you've got. In that circumstance, how do you call a Syspro Business Object from within the SQL Trigger? Here are some ways:

Create a CLR Stored Procedure

Create a CLR Stored Procedure and call that from your SQL Trigger. In your CLR Stored Procedure, you can call the Business Object via one of Syspro's COM Objects, or via Syspro's web service or Syspro's WCF service. However, you must watch out for the 32/64-bit issue:

Watch out for the 32/64-bit issue

If you are using one of Syspro's COM objects, you may run into the problem of trying to run a 32-bit application in a 64-bit process, which can't be done (unless you want to hack around with the registry as described here - good luck). This problem arises because if your version of SQL Server is 64-bit, then you won't be able to access any 32-bit COM objects from it (such as Encore.dll or Syspro32.dll); you could use Syspro.dll (which is 64-bit), but at the time of writing, September 2014, it had't been released.

You could overcome the 32-64 bit disconnect by using Inter-Process Communication or some other communication method, but you may just find it easier to use another method.

Call a COM Object

You CAN call a COM object directly from a SQL Trigger using the extended stored procedures, sp_OACreate and sp_OAMethod (details here).
Then you could either call a web service, or call Encore.dll or Syspro.dll (being mindful of the 32/64-bit issue as mentioned above). Here's one example.
However, this is not a good solution due to the level of security you have to give to run those extended stored procedures.

Use Document Flow Manager

You could dump out files and feed them into Syspro's Document Flow Manager, a server process which monitors a directory for files and feeds them into Syspro via one of Syspro's e.Net interfaces.

Call a separate process via xp_cmdshell

The quickest and simplest method is: from your SQL Trigger, use the xp_cmdshell function, e.g.

EXEC master..xp_cmdshell @yourParameters
This has the same security issues as calling a COM Object, but it isolates your process from the SQL Server, and so this is probably the best method.

Friday 29 August 2014

Best Practices for SQL Server for Syspro

These are recommended best practices for configuring SQL Server for use with Syspro.

Basic configuration

  • The collation order must be Latin1_General_Bin for Syspro.
  • Use Mixed Mode Authentication.
  • Store your database on a DIFFERENT disk to your database's log file.
  • Store your backups on a third disk.
  • Use RAID 5 (recommended by Syspro) or RAID 1+0 for data protection.
  • Give your database server plenty of memory. As a point of reference, 10 Gb is very nice for a 20 user site.
  • Create at least one user for Syspro to access the database; don't use the system administrator user (sa) for Syspro!

Backup basics

  • Copy your backups off your server, ideally daily
  • Take a copy of your backup OFF SITE regularly
  • Keep backups at several levels: yearly, monthly, weekly and daily,

Really bad backup practices to avoid at all costs


  • Backing up to the same disk as the database - if the disk dies, you’ll lose all your data for ever!
  • Not backing up at all!

SQL Log file settings

If using the FULL recovery model, the SQL log file should be set so that if it fills up, it will leave a few Gb unused on the file-system so that you don’t crash Windows.

If using the SIMPLE recovery model, the SQL Log file should have its maximum size set to a safe limit so that if it fills up to that limit, it WON’T fill up the hard disk. As a rough guide, that limit should be about 2 to 4 times the normal database size; anything beyond that would need special circumstances.

Other recommended log file settings:

  • AUTOGROWTH - Yes
  • AUTOSHRINK - NO; definitely not. Setting it to Yes is bad practice; the log file should ideally be a static size, large enough for each day’s transactions.
  • Recovery Model - Full Recovery Model is recommended; SIMPLE Recovery Model is OK.

Anti-virus software on your SQL Server?

It sounds like a good thing, but it's the wrong tool for the job. You should be using a host intrusion detection system (H-IDS) such as Tripwire. See this post for details.
However, if you do have anti-virus software on your SQL and/or Syspro server, you should exempt these files from being scanned: your database files including log and backup files (*.mdf, *.ldf, *.bak), and all your Syspro settings, work and data files (C:\Syspro\Work\*, C:\Syspro\Data\*, C:\Syspro\Base\Settings\*).

Tuesday 10 June 2014

How to refresh the Syspro screen from VBScript

This handy line of VBScript in Syspro will refresh the screen (such as the Sales Order Lines' grid):

SystemVariables.CodeObject.ActionToInvoke = "DoRefreshLines,40004"


Tuesday 20 May 2014

How to run multiple versions of Syspro

If you are a Syspro developer, you may want to run multiple versions of Syspro on the same machine. This article describes how to install and switch between version 6.1 and 7.0 of Syspro.

Setup

You probably already have Syspro 6.1 (server) installed, plus Syspro 6.1's versions of Syspro WCF and Syspro Web Services, so we'll take that as the starting point.

Install Syspro 7.0 (server), but pay attention to the points below.

Installing Syspro 7.0 Web Services

When installing Syspro Web Services version 7, it will remove and replace version 6.1’s web services; that is OK; you will not need to re-install Syspro 6.1 web services to access Syspro 6.1 web services. You will be able to access both versions of web services with Syspro 7's web services... so long as you follow the steps below under "Switching between versions".

Installing Syspro 7.0 WCF

Installing Syspro 7 WCF will wipe out your Syspro 6 WCF installation, so the recommend procedure is:
  1. Backup your Syspro 6 WCF config file.
  2. Install Syspro 7 WCF.
  3. Re-install Syspro 6 WCF, but to a DIFFERENT directory.
  4. Re-configure Syspro 6 WCF using the config file you saved in step 1.
  5. Configure Syspro 7 WCF to use DIFFERENT ports to Syspro 6.1 WCF.

Switching between versions

From an elevated command prompt, unregister the Syspro 6.1 version of Encore.dll and register the Syspro 7 version, e.g.

cd /syspro61/base
regsvr32 /u encore.dll
cd /syspro7/base
regsvr32 encore.dll

Then you should be able to use Syspro 7’s web services – you will need to use the correct company code and the instance number for your Syspro 7 installation, of course.

You will not be able to access Syspro 6 while Syspro 7’s Encore.dll is registered; you must use the procedure in reverse to unregister Syspro 7's Encore.dll and register Syspro 6.1's Encore.dll.

Thursday 15 May 2014

How to test your Syspro WCF Service

These are some basic tests you can use to check if Syspro's WCF Service is up and running.
  1. Open a web browser and go to a URL such as:

    http://example.com:97/SYSPROWCFService/Rest

    You should see something like this:

  2. Open a web browser and go to a URL such as:

    http://example.com:97/SYSPROWCFService/Rest/GetVersion

    You should receive a text file called GetVersion containing the version number of the WCF Service, e.g. 7.0.0.3.
  3. Can you ping the Syspro server (provided the Syspro server responds to ICMP ping requests)?
     
  4. Telnet to the server and port, then hit ENTER a few times, e.g.

    telnet example.com 99

    If you get this:

    Connecting To example.com...Could not open connection to the host, on port 99: Connection failed

    then you didn't get a connection at all. This could happen if you had the wrong port or IP address.

    However, if you got a clear screen, then when you hit ENTER the telnet session terminated, then you got a connection OK.

    "Actively refused" means that you got at least as far as a TCP Connect packet arriving at the named server.
     
  5. From an elevated command prompt (i.e. run CMD as Administrator), run this command:

    netstat -ano | findstr 9999

    where 9999 is the process number or port number you are interested in.
    You should see your process and port listed.
     
  6. Check the Event Log for any error messages: in the Event Viewer, look under Application and Services Logs / SYSPROWCF.

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.