Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Microsoft Small business Accounting SDK

Status
Not open for further replies.

MdotButler

Programmer
Jan 18, 2006
104
US
Anyone have experience with integrating with Microsoft Small business Accounting using the SDK?

I am trying to send vendors and invoices but the MS documentation and examples are in C# and VB.Net which is a challenge for me. I have not found anyone using VFP to access/use the SDK as of yet.

Any information would be appreciated...

TIA
Mark
 
Mark - I haven't used this particular SDK, but I've converted dozens (well more than dozens) of VB and C# samples into VFP.

If you'll post one you're trying to use, I'll be happy to see what I can come up with for you.

-Bill

 
Thanx for your response and generous offer. Here is a login script. I can't seem to get to the point of instantiating an object. I do not see the registered component to use in the CREATEOBJECT('XXX')

Code:
//-----------------------------------------------------------------------
// <copyright file="LoggingIn.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <disclaimer>
//  THIS CODE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, WHETHER EXPRESSED
//  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
//  MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK OF
//  USE OR RESULTS IN CONNECTION WITH THE USE OF THIS CODE REMAINS WITH THE USER.  
// </disclaimer>
//-----------------------------------------------------------------------

using System;
using System.Collections;
using System.Data;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using Microsoft.BusinessSolutions.SmallBusinessAccounting;
using Microsoft.Win32;

namespace LoggingIn
{
    /// <summary>
    /// This class contains the body of the sample.
    /// </summary>
    class LoggingIn : IDisposable
    {
        private Utilities utilities = null;

        /// <summary>
        /// Only constructor to use
        /// </summary>
        /// <param name="serverName"></param>
        /// <param name="databaseName"></param>
        public LoggingIn(string serverName, string databaseName )
        {
            try
            {
                string sbaFullBusinessLogicAssemblyPath = Utilities.GetSbaFullBusinessLogicAssemblyPath();
                utilities = new Utilities(sbaFullBusinessLogicAssemblyPath);

                // Logging in or out is an expensive event in terms of resources, so it
                //  is best to design solutions to minimize the number of these events
                Debug.WriteLine(Environment.NewLine + "About to log in..." + Environment.NewLine + Environment.NewLine);
                utilities.LogOn(serverName, databaseName);
            }

            catch (ApplicationException ex)
            {
                if (ex is ISmallBusinessException)
                {
                    utilities.LogOff();
                }

                throw;
            }
        }

        private LoggingIn(){}
    
        /// <summary>
        /// To manage release of resource handles
        /// </summary>
        public void Dispose()
        {
            utilities.LogOff(); // release handles. This utility contains a null check
            GC.SuppressFinalize(this);
        }
    }

    /// <summary>
    /// The main class to start the sample, and handle some exceptions
    /// </summary>
    class CMain
    {
        [STAThread]
        static void Main(string[] args)
        {
            string serverName = null;
            string databaseName = null;

            try
            {
                // Add a listener so that output of sample text will always go to the command-line
                //  environment as well as the Output window in debug sessions through VS.NET (which
                //  is the default listener for that setting)
                TextWriterTraceListener commandLineWriter = new TextWriterTraceListener(System.Console.Out);
                Debug.Listeners.Add(commandLineWriter);

                // Begin by parsing the arguments
                if (Parse(args, ref databaseName, ref serverName) )
                {
                    using (LoggingIn sample = new LoggingIn(serverName, databaseName) )
                    {
                        Debug.WriteLine(Environment.NewLine + "The sample has now instantiated a SmallBusinessInstance, and used it to log in to the specified database." + Environment.NewLine + Environment.NewLine);
                    }            

                    Debug.WriteLine(Environment.NewLine + "The sample has now logged out through disposal of the LoggingIn class." + Environment.NewLine + Environment.NewLine);
                }
                else
                {
                    serverName = null;
                    UsageMessage();
                }
            }
            catch(ApplicationException ex)
            {
                Utilities.OutputToConsoleFromException(ex);
            }
        }

        /// <summary>
        /// Parse the command-line arguments
        /// </summary>
        /// <param name="args">the arguments array</param>
        /// <param name="server">server name</param>
        /// <param name="database">database name</param>
        /// <returns></returns>
        static bool Parse(string[] args, ref string database, ref string server)
        {
            ParseArgs parseArgs = new ParseArgs(args);
            database = parseArgs.UseSwitch('d', "sampleproductcompany");
            server = parseArgs.UseSwitch('s', "(local)\\MICROSOFTSMLBIZ");
            return parseArgs.ParametersCorrect();
        }

        /// <summary>
        /// Usage suggestions output to console
        /// </summary>
        static void UsageMessage()
        {
            Debug.WriteLine("Usage:" + Environment.NewLine +
                " LoggingIn [-d DatabaseName] [-s ServerName]" + Environment.NewLine + Environment.NewLine +
                "Where:" + Environment.NewLine +
                " DatabaseName = name of the database the sample uses (default: sampleproductcompany)" + Environment.NewLine +
                " ServerName=machineName\\MSDE_InstanceName (default: (local)\\MICROSOFTSMLBIZ)" + Environment.NewLine + Environment.NewLine +
                "Examples:" + Environment.NewLine +
                " LoggingIn -d sampleproductcompany -s DEVMACHINE\\MSDE_InstanceOnMachine" + Environment.NewLine + Environment.NewLine +
                " LoggingIn ? (to show this arguments help)" + Environment.NewLine + Environment.NewLine +
                "Quotes are required around arguments that contain spaces." + Environment.NewLine +
                "The argument names are case-insensitive." + Environment.NewLine +
                "All arguments must be used." + Environment.NewLine +
                "The order of arguments is not important." + Environment.NewLine +
                "This sample does not read from or write to the database." + Environment.NewLine + Environment.NewLine);
        }
    }

    /// <summary>
    /// Class to parse command-line input arguments to strings
    /// </summary>
    sealed class ParseArgs
    {
        private string[] argsTypedIn = null;
        private bool[] argsMatched = null;
        private bool allRequiredArgsFoundSoFar = true;
        private bool requestForHelp = false;

        private ParseArgs() {}

        /// <summary>
        /// Only available constructor
        /// </summary>
        /// <param name="arguments">arguments from the command line</param>
        public ParseArgs(string[] arguments)
        {
            argsTypedIn = arguments;
            argsMatched = new bool[arguments.Length];

            if ( (arguments != null) && (arguments.Length > 0) )
            {
                string firstArg = arguments[0].ToLower();

                if (    (firstArg == "?")   ||
                    (firstArg == "-?")  ||
                    (firstArg == "/?")  ||
                    (firstArg == "h")  ||
                    (firstArg == "help")  ||
                    (firstArg == "commands")  )
                {
                    allRequiredArgsFoundSoFar = false;
                    requestForHelp = true;
                }
            }
        }

        /// <summary>
        /// Look for the required argument by the switch
        /// </summary>
        /// <param name="letterSwitch"></param>
        /// <returns>The value of the argument from the command line, if available</returns>
        public string UseSwitch(char letterSwitch)
        {
            if (requestForHelp)
            {
                return null;
            }
            else
            {
                return UseSwitch(letterSwitch, true);
            }
        }

        /// <summary>
        /// Look for the non-required argument by the switch, with default value supplied
        /// </summary>
        /// <param name="letterSwitch"></param>
        /// <param name="defaultValue"></param>
        /// <returns>The value of the argument from the command line, or default if not available</returns>
        public string UseSwitch(char letterSwitch, string defaultValue)
        {
            if (requestForHelp)
            {
                return null;
            }
            else
            {
                string argResult = UseSwitch(letterSwitch, false);

                if (argResult == null)
                {
                    argResult = defaultValue;
                }

                return argResult;
            }
        }

        /// <summary>
        /// Last argument check for sample
        /// </summary>
        /// <returns>whether parameters have been correctly matched up with what is required for this sample</returns>
        public bool ParametersCorrect()
        {
            if (requestForHelp)
            {
                return false;
            }
            else
            {
                bool returnBool = allRequiredArgsFoundSoFar;

                if (allRequiredArgsFoundSoFar == false)
                {
                    Debug.WriteLine("Sample is missing one or more arguments.");
                }

                for (int counter = 0; counter < argsMatched.Length; counter++)
                {
                    returnBool = returnBool && argsMatched[counter];

                    if (argsMatched[counter] == false)
                    {
                        Debug.WriteLine(string.Format("Sample doesn't know how to parse argument \"{0}\".", argsTypedIn[counter]) );
                    }
                }

                return returnBool;
            }
        }

        private string UseSwitch(char letterSwitch, bool requiredArgument)
        {
            Debug.Assert(char.IsLetter(letterSwitch), "all hyphenated switches must be letters");
            string hyphenatedSwitch = "-" + letterSwitch;
            string returnString = null;

            for (int argIndex = 0; argIndex < (argsTypedIn.Length - 1 /*The -1 here is because a hyphenated switch as last input argument is not useful*/); argIndex++)
            {
                if ( (string.Compare(hyphenatedSwitch, argsTypedIn[argIndex], true) == 0) && (argsMatched[argIndex] == false) && (argsMatched[argIndex + 1] == false) )
                {
                    returnString = argsTypedIn[argIndex + 1];
                    argsMatched[argIndex] = argsMatched[argIndex + 1] = true;
                    break;
                }
            }

            if (requiredArgument && (returnString == null) )
            {
                allRequiredArgsFoundSoFar = false;
            }

            return returnString;
        }
    }

    /// <summary>
    /// This class is used as a simple wrapper for a set of utilities to be used for the samples.
    /// </summary>
    sealed class Utilities
    {
        private ISmallBusinessInstance smallBusinessInstance = null;

        private Utilities() {}

        /// <summary>
        /// The only public constructor
        /// </summary>
        /// <param name="pathToImplementationAssembly"></param>
        public Utilities(string pathToImplementationAssembly)
        {
            // Since this sample does not reference SBAAPI.DLL directly, it has
            //  no compile-time knowledge of that assembly. Therefore the sample
            //  has to instantiate the SBA instance through reflection
            //
            // It would be more performant to just reference SBAAPI.DLL directly
            //  and instantiate the SBA instance that way, but the technique
            //  used here is neater and more configurable because we're only
            //  referencing the interface at compile time.

            Assembly businessLogicAssembly = Assembly.LoadFrom(pathToImplementationAssembly);
            smallBusinessInstance = (ISmallBusinessInstance) businessLogicAssembly.CreateInstance("Microsoft.BusinessSolutions.SmallBusinessAccounting.SmallBusinessInstance");
        }

        /// <summary>
        /// Central accessor for the SmallBusinessInstance created with LogOn method
        ///  in this class object
        /// </summary>
        public ISmallBusinessInstance SbaInstance
        {
            get
            {
                return smallBusinessInstance;
            }
        }

        /// <summary>
        /// Central utility to log on
        /// </summary>
        /// <param name="serverName"></param>
        /// <param name="database"></param>
        public void LogOn(string serverName, string database)
        {
            if ( (serverName != null) && (database != null) )
            {
                if (!smallBusinessInstance.IsConnected)
                {
                    Debug.WriteLine(string.Format("Logging on with databaseName \"{0}\", serverName \"{1}\"" + Environment.NewLine + Environment.NewLine, database, serverName) );
                    smallBusinessInstance.LogOn(database, serverName);
                }
            }
        }

        /// <summary>
        /// Checks for null, and logs off the SBA instance.
        /// </summary>
        public void LogOff()
        {
            if (smallBusinessInstance != null)
            {
                if (smallBusinessInstance.IsConnected)
                {
                    smallBusinessInstance.LogOff();
                }

                smallBusinessInstance = null;
            }
        }

        /// <summary>
        /// Outputs message and InnerException message if available
        /// </summary>
        /// <param name="ex">exception for which info will be output to console</param>
        public static void OutputToConsoleFromException(ApplicationException ex)
        {
            if (ex != null)
            {
                Debug.WriteLine(string.Format("ApplicationException was thrown with Message: {0}", ex.Message) );

                if ( (ex.InnerException != null) && (ex.InnerException.Message != null) && (ex.InnerException.Message.Length > 0) )
                {
                    Debug.WriteLine(string.Format(" InnerException Message: {0}", ex.InnerException.Message) );
                }
            }
        }

		public static string GetSbaFullBusinessLogicAssemblyPath()
		{
			// If setup was run, then the install path will be in the registry  
			const string MagellanInstallPathRegKey = @"SOFTWARE\Microsoft\Business Solutions eCRM\2.0\";
			const string InstallPathRegValue = "AccountingAPI";
			const string BusinessLogicAssemblyName = "SBAAPI.DLL";
			const string DefaultPath = @"C:\Program Files\Microsoft Small Business\Small Business Accounting\";

			StringBuilder installPath = new StringBuilder();

			// Check for a installation 
			RegistryKey regkey = Registry.LocalMachine.OpenSubKey(MagellanInstallPathRegKey);

			if (regkey != null)
			{
				Object temp = regkey.GetValue(InstallPathRegValue);

				if (temp != null)
				{
					installPath.Append(temp.ToString());
				}
			}

			if (regkey != null)
			{
				regkey.Close();
			}

			if (installPath.Length == 0)
			{
				// Default value
				installPath.Append(DefaultPath);
				installPath.Append(BusinessLogicAssemblyName);
			}
			return installPath.ToString();
		}
    }
}
 
Mark -

You sure didn't pick anything simple to begin with :)

I don't have SmallBusiness server installed anywhere so I'm a bit at a loss here on what Object you need to create.

Let me ask you this... are you attempting to 'login' to the server to access the SQL database? If so, there are a couple of alternatives as in DSN od DNS-less connections to do that.

If on the other hand you're attempting to instantiate an instance of the Small business Accounting application, we'll need to look at that products object model...

Sorry I can't be more help on this at the moment... feel free to contact me off list (go to my website and send me a feedback form, reference that you're from here) if you want to discuss this in more detail.

Once we figure it out we can post a solution here.

-Bill

Bill Coupe
CP.Solutions
 
Here is another example (much smaller).
Code:
' Retrieve SBI reference
Dim sb As ISmallBusinessInstance = SBALib.GetSBIReference()
Dim companyName As String = "sampleproductcompany"
Dim serverName As String = "(local)\MICROSOFTSMLBIZ"
' Use ISmallBusinessInstance to call Logon method
Try
   sb.LogOn(companyName, serverName)
   ' Handle exception
Catch ex As Exception
   MsgBox("Error: " + ex.Message, MsgBoxStyle.Critical Or MsgBoxStyle.OKOnly, _
          "Logon Failure")
End Try
If I try the following:
Code:
loSBA=CREATEOBJECT('SBALib.GetSBIReference')
I get: 'Class definition ... not found.'
 
MdotButler,

The problem you're running into here is that SBAIAPI.DLL is a .NET assembly (as opposed to a COM server). SBAIAPI.DLL is the assembly that the ISmallBusinessInstance interface exists in. So, no amount of conversion is really gonna help you here. I mean you can use regasm.exe from the .NET 2.0 framework to register and generate a typelib for this assembly (and others within the Small Business Accounting install directory) and then use VFP's object browser to view the tlb file. This will show you what the classes are, what the interfaces are, and all the constants which might prove useful, but you're still not going to be able to do this completely in VFP. Even though the assembly has been registered, most if not all of the classes will not be able to be instantiated or used directly from Visual FoxPro. This type of "Fusion" with .NET assemblies routinely exposes relatively little of the functionality available in .NET to VFP or VB 6.

If you want VFP to be able to work with this then you're going to need to write some wrappers in .NET that use COM interop to expose the functionality you want to VFP. Although my answer is probably not what you were hoping to hear, it usually isn't too difficult to create these wrappers for the functionality you need access to. You can refer to examples on my blog and also on Rick Strahl's website for some insights on how to do this.

boyd.gif

SweetPotato Software Website
My Blog
 
I should perhaps explain a little further what I meant by "fusion" in my previous post. If you use regasm.exe to register the System.dll that is in the .NET Framework you can access some of it's functionality from Visual FoxPro. So, after running the following command from a DOS prompt in the .NET framework folder on your computer...

regasm /tlb system.dll

...certain stuff will work from within Visual FoxPro and other COM aware development tools such as Visual Basic 6.0. The problem is that only a few things are actually compatible with COM when this approach is used. The bulk of the classes and functionality can only be accessed from the environment it was intended for... namely a .NET application built with a .NET language. That having been said, the following stuff works in Visual FoxPro after System.dll has be registered...
Code:
**********************************************
*!* Sample 1 of VFP Fusion
**********************************************
LOCAL lostack as System.Collection.Stack
stack = CREATEOBJECT("System.Collections.Stack")
stack.Push(".NET")
stack.Push("Hello World ")
MESSAGEBOX(stack.Pop + stack.Pop)
RELEASE loStack

**********************************************
*!* Sample 2 of VFP Fusion
*!* [URL unfurl="true"]http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvs05/html/VBBestPrac.asp[/URL]
**********************************************
PUBLIC loForm as form
LOCAL lowebDownload, lcDownloadedImageFile
lcDownloadedImageFile = ADDBS(SYS(2023)) + "latest_westir.jpg"
lowebDownload = CREATEOBJECT("System.Net.WebClient") && creating a .NET object in VFP
lowebDownload.downloadFile("[URL unfurl="true"]http://www.ssec.wisc.edu/data/west/latest_westir.jpg",;[/URL]
    lcDownloadedImageFile)
RELEASE lowebDownload
loForm = CREATEOBJECT("form")
loForm.addobject("Image1", "Image")
loForm.Image1.Move(0, 0, loform.Image1.width, loform.Image1.height)
loForm.Image1.picture = lcDownloadedImageFile
loform.Image1.visible = .T.
loForm.autocenter = .T.
loform.show(1)
ERASE (lcDownloadedImageFile)


boyd.gif

SweetPotato Software Website
My Blog
 
Craig - You're right on the money with this and that's the direction Mark and I had been talking about (off list). Thanks for the link to Rick's info and your blog, between those two we may be able to get something useable from the assemblies.

Thanks

-Bill

Bill Coupe
CP.Solutions
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top