17 March, 2013

Will Your Existing GP Customizations Work in GP 2013‌?

With the recent release of Microsoft Dynamics GP 2013 comes an exciting new feature: the Web Client. We've all longed for the Web Client, dreaming of a day when we could access our system from any web browser without being tied to our desktops and office networks, and now it’s finally here! Before we all begin to jump for joy, there is one thing to realize before making the switch to GP 2013—some of the customizations that you've made over the years to your existing Dynamics GP installation may not be fully compatible in the new Web Client.

Before going further, let’s take a look at the different ways that GP can be customized, which of those ways will present an issue moving over to the new GP 2013 Web Client, and the next steps to make the transition.

4 Types of Dynamics GP Customizations

There are four ways that Dynamics GP software can be modified or customized:
  1. Modifier - Allows changes to be made to the user interface (UI). Examples include adding new fields, moving fields around, and changing field labels, etc. Customizations made to GP using Modifier will be compatible with both the GP 2013 desktop version as well as the Web Client.
  2. Dexterity -This is the programming language in which Dynamics GP is written and customizations to the code level require a Dexterity developer. Example of changes to Dexterity code include things like adding new business logic or capturing business domain specific data through alternate forms or window modifications. The possibilities are endless. Customizations made to GP using Dexterity will be compatible with both the GP 2013 desktop version as well as the Web Client.
  3. VBA - Is most often used to add additional functionality to fields such as disabling a field or automatically populate fields from a database, etc. Although customizations made to GP using VBA will be compatible with the desktop version of GP 2013, at this time they are not compatible with the GP 2013 Web Client.
  4. VST - Allows the same type of changes that would be made with Dexterity but with the .NET programming language. Although customizations made to GP using VST will be compatible with the desktop version of GP 2013, at this time they are not compatible with the GP 2013 Web Client without additional modifications.


Type of Customization
Compatible with GP 2013 Desktop Version
Compatible with
GP 2013 Web Client
How to tell if you have this customization
Modifier
Yes
Yes
The page title of the window will have a dot in front of it
Dexterity
Yes
Yes
The dexterity modification will be listed in the Dynamics.set file: C:\Program Files\Microsoft Dynamics\GP (or the application directory where you installed GP)
VBA
(Visual Basic for Applications)
Yes
No
The page title of the window will have a dot at the end of it
VST
(Visual Studios Tools)
Yes
No
All customizations are stored in the AddIn subfolder of the Microsoft Dynamics\GP folder.



How to Tell What Kind of Customizations You Have

There are a few simple ways to tell what type of customizations you have in Dynamics GP.

1.       Modifier - The Customization Maintenance window will list modified forms, modified reports, and custom VBA code and references that are active on the workstation.
Tools -> Customize -> Customization Maintenance


If you have made customizations to GP using Modifier, there will be a dot in front of the window’s page title.





2.       Dexterity - The Customization Status window will list Dexterity customizations, as well as the Dynamics GP modules and Dexterity-based ISV solutions that are installed and active.
Tools -> Customize -> Customization Status



3.       VBA - The Customization Maintenance window will list modified forms, modified reports, and custom VBA code and references that are active on the workstation.
Tools -> Customize -> Customization Maintenance

If you have made customizations to GP using VBA, there will be a dot at the end of the window’s page title.



4.       VST - All customizations made to GP using VST are stored in a folder called AddIns. If you have VST customizations, you will find them inside this folder located AddIn subfolder of the Dynamics GP client installation. This is typically C:\Program Files (x86)\Microsoft Dynamics\GP\AddIns.

Next Steps


If GP customizations exist that may not be compatible with the new GP 2013 Web Client here are a few suggestions to preserve your customization’s functionality.

Business Process Analysis
Is this functionality still needed‌? Can it be improved‌?


Determine if the customization is still needed. New releases are packed with new functionality and the task that the old customization helped accomplish may now be handled out-of-the-box in the new version.  Or, your business may have changed and it is simply not needed any longer! A business process analysis against the new version will help identify which customizations are no longer needed. After reviewing the new release’s capabilities, you may find that there are many other new features offered that you hadn’t even thought of before.

It’s like trading in your old 1994 Honda Accord for the 2013 model—you were excited about the built in navigation system (no more Garmin attached to your windshield) and heated seats but you had no idea that it had blind spot detectors and could park itself! So when you’re ready to make the switch, make a point to learn about all the new features of the software, not just the ones you already know about and you may be pleasantly surprised. Sometimes you don’t know what you don’t know!

Spend Time on Design

If a customization is going to be re-implemented it makes sense to spend a little extra time on the design of the customization. Ask yourself if there are any features that have been lacking that need to be added or can the functionality be changed to make it more user friendly. By making sure you have got the most value out of your customization it will pay off in the long run when you don’t have to continuously make changes to it again and again in the future.

Use Case Documentation

As with all customizations, it is a best practice to document, document, and document. Before sitting down with a developer take screen shots of the areas you want to change and document the business process flow. This will provide clear communication between you and your developer and help to ensure there aren’t any discrepancies between what you are trying to achieve and what the system ends up doing. It’s great to keep them on record as well!

Hire a Professional Dexterity Developer

Now that you are ready to move forward with your customizations into GP 2013’s Web Client, you’ll need to hire a Dexterity developer. Dexterity developers aren’t as easy to come by as other programming languages, but the good news is that if you've found one, they probably know their stuff (this isn't something your neighbor’s nephew in high school can do for you on the weekends). It’s a sophisticated programming language that takes years of experience to learn.

After having completed all of the leg work; conducting your business process analysis and providing your developer with use case documentation, the dexterity changes should be a breeze!

Here are a few things to look for in a Dexterity developer:
·         Is the programmer certified in Dexterity or Microsoft Dynamics GP‌?
·         How many or how long has the programmer been working with Dexterity‌?
·         Will the programmer provide documentation of their changes‌?
·         Will the programmer, or company they work for be able to support you in the years to come‌?
 

 

02 November, 2012

CSS Consolidator jiu-jitsu


CSS Consolidator

Sometimes you inherit a huge pile of CSS and want to make a few changes. Sometimes there are huge groups of duplicated rules. Sometimes, you just want to see everywhere Comic Sans is used in your stylesheets. (Hopefully, nowhere.)

Paste in your Source CSS below and click Consolidate CSS.  Rules and selectors will be consolidated in the next textbox.  All processing is done client-side in JavaScript.

Below that, you will see a breakdown of your styles, arranged by Selector, Attribute and Values.

Why did I do this?  Why not?

Source CSS (No @media tags please)




Consolidated CSS



Selectors Attributes Values

Capturing (and using) raw SOAP messages in WCF

WCF is great for building web services.  It's also great for interacting with existing web services.  Visual Studio makes it so easy . . . add a service reference, point to the WSDL of the service and just like that, you have a set of classes to handle the service and data contracts.

Unfortunately, sometimes web services don't live up to their contracts.  Recently, I was interacting with a web service and found that sometimes the response would be null.  I fired up Fiddler and looked at the SOAP messages.  Sure enough, an error had occurred on the remote server and the response, while valid XML, looked nothing like the promised Data Contract.  It did however provide a useful description of the error.

There was no SOAP fault . . . no exception thrown (unless I tried to use the null response without checking), just a null response object by the time WCF handed it to me.

I searched the web and found several suggestions.

  • Use Fiddler . . . tried that but it doesn't help when you are trying to get your service into production.
  • Build a SOAP Extension . . . that's great if you are building a legacy asp.net XML Web Service and just want to log or tweak the messages.  I'm doing WCF and needed to make the messages available to my service in real time.
  • Edit the Visual Studio Generated classes to wrap the response in XML and deserialize it yourself . . . that might work but you throw away so much niceness that's built for you already.
I ended up building a custom Endpoint behavior and applying it to the generated SOAP Client object.

First, you need a class to hold the raw Request and Response:
        public class InspectedSOAPMessages
        {
            public string Request { getset; }

            public string Response { getset; }
        }
  

Create an instance of the class and pass it into the new Endpoint Behavior:

        InspectedSOAPMessages soapMessages = new InspectedSOAPMessages();
        batchClient.Endpoint.Behaviors.Add(new CapturingEndpointBehavior(soapMessages));
        BatchService.batchResponseType response = batchClient.BatchOperation(batchRequest);
        batchClient.Close();
           //response will be null if the contract was violated . . . 
        if (response == null)
        {
           results.Message = soapMessages.Response;    } else { //process normal response
  

Now, all we need is the set of classes to handle the Endpoint Behavior:

    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Description;
    using System.ServiceModel.Dispatcher;

    namespace MBSGuru
    {
    /// <summary>
        /// Allows capturing of raw SOAP Messages
        /// </summary>
        public class CapturingEndpointBehavior : IEndpointBehavior
        {
            /// <summary>
            /// Holds the messages
            /// </summary>
            public InspectedSOAPMessages SoapMessages { getset; }

            public CapturingEndpointBehavior(InspectedSOAPMessages soapMessages)
            {
                this.SoapMessages = soapMessages;
            }

            /// <summary>
            /// Required by IEndpointBehavior
            /// </summary>
            /// <param name="endpoint"></param>
            /// <param name="bindingParameters"></param>
            public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { return; }

            /// <summary>
            /// Required by IEndpointBehavior
            /// </summary>
            /// <param name="endpoint"></param>
            /// <param name="clientRuntime"></param>
            public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
            {
                clientRuntime.MessageInspectors.Add(new CapturingMessageInspector(this.SoapMessages));
            }

            /// <summary>
            /// Required by IEndpointBehavior
            /// </summary>
            /// <param name="endpoint"></param>
            /// <param name="endpointDispatcher"></param>
            public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { return; }

            /// <summary>
            /// Required by IEndpointBehavior
            /// </summary>
            /// <param name="endpoint"></param>
            public void Validate(ServiceEndpoint endpoint) { return; }
        }

        /// <summary>
        /// Actual inspector that captures the messages
        /// </summary>
        public class CapturingMessageInspector : IClientMessageInspector
        {
            /// <summary>
            /// Holds the messages
            /// </summary>
            public InspectedSOAPMessages SoapMessages { getset; }

            public CapturingMessageInspector(InspectedSOAPMessages soapMessages)
            {
                this.SoapMessages = soapMessages;
            }

            /// <summary>
            /// Called after the web service call completes.  Allows capturing of raw response.
            /// </summary>
            /// <param name="reply"></param>
            /// <param name="correlationState"></param>
            public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
            {
                this.SoapMessages.Response = reply.ToString();
            }

            /// <summary>
            /// Called before the web service is invoked.  Allows capturing of raw request.
            /// </summary>
            /// <param name="request"></param>
            /// <param name="channel"></param>
            /// <returns></returns>
            public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel)
            {
                this.SoapMessages.Request = request.ToString();
                return null;
            }
        }
    }
  

And just like that, you have a copy of the request and response.  How you handle the violation of the contract is up to you.  At least now you can report something more informative than "Error Occurred"

10 October, 2012

Create a Customer in Dynamics GP 2010 using eConnect

A few days ago I created a simple example to create a vendor in Dynamics GP using eConnect. I received a few requests to show a sample integration to create new customers. Creating new customers in Dynamics GP via eConnect is a common request and usually proceeds the creation of other receivables documents.

The sample below creates a new customer using the eConnect .NET assembly and in-memory serialization.

To run the following code on your machine:
  1. Install the latest version of the eConnect 11 SDK.
  2. Create a new Console Application in Microsoft Visual Studio.
Add references to these dynamic link libraries which are located by default in C:\Program Files (x86)\Microsoft Dynamics\eConnect 11.0\API\. (Ignore the x86 if you are using a 32-bit system.)
  1. Microsoft.Dynamics.GP.eConnect.dll
  2. Microsoft.Dynamics.GP.eConnect.Serialization.dll
Replace the Program.cs class in the project for the new class below.


using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.Dynamics.GP.eConnect;
using Microsoft.Dynamics.GP.eConnect.Serialization;
 
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Beginning integration test.");
 
        using (eConnectMethods eConnectMethods = new eConnectMethods())
        {
            try
            {
                Console.WriteLine("Creating a new customer.");
 
                // Modify the connection string for your environment.
                string connectionString = @"data source=localhost; initial catalog=TWO; integrated security=SSPI";
 
                // Create the customer.
                taUpdateCreateCustomerRcd customer = new taUpdateCreateCustomerRcd();
                customer.CUSTNMBR = "TEST001";
                customer.CUSTNAME = "Test Customer";
 
                // Assign the customer to a new master customer type.
                RMCustomerMasterType customerMasterType = new RMCustomerMasterType();
                customerMasterType.taUpdateCreateCustomerRcd = customer;
 
                // Assign the master customer type to a new collection of master customer types.
                RMCustomerMasterType[] customerMasterTypes = { customerMasterType };
 
                // Serialize the master vendor type in memory.
                eConnectType eConnectType = new eConnectType();
                MemoryStream memoryStream = new MemoryStream();
                XmlSerializer xmlSerializer = new XmlSerializer(eConnectType.GetType());
 
                // Assign the master customer types to the eConnectType.
                eConnectType.RMCustomerMasterType = customerMasterTypes;
 
                // Serialize the eConnectType.
                xmlSerializer.Serialize(memoryStream, eConnectType);
 
                // Reset the position of the memory stream to the start.              
                memoryStream.Position = 0;
 
                // Create an XmlDocument from the serialized eConnectType in memory.
                XmlDocument xmlDocument = new XmlDocument();
                xmlDocument.Load(memoryStream);
                memoryStream.Close();
 
                // Call eConnect to process the XmlDocument.
                eConnectMethods.CreateEntity(connectionString, xmlDocument.OuterXml);
 
                Console.WriteLine("Successfully created customer {0}.", customer.CUSTNMBR);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception occured: " + ex.Message);
            }
            finally
            {
                eConnectMethods.Dispose();
            }
        }
 
        Console.WriteLine("Integration test complete." +
                           Environment.NewLine +
                           Environment.NewLine);
        Console.WriteLine("Press <Enter> to continue...");
        Console.ReadLine();
    }
}

Execute the project and you should see the following output:


And you should see the vendor created in Dynamics GP:



05 October, 2012

Create a Vendor in Dynamics GP 2010 with eConnect using In Memory Serialization

Developing several integrations between Microsoft Dynamics GP 2010 and various third-party systems the last few weeks reminded me to update my previous article on In Memory Serialization for eConnect 10.

Microsoft Dynamics GP 2010 uses eConnect version 11 which includes significant updates. Notably, the COM+ component has been changed to a WCF service.

In this example, I am creating a new vendor record in Dynamics GP using the same in memory serialization technique. Why write a file to disk unnecessarily?

To run the following code on your machine:
  1. Install the latest version of the eConnect 11 SDK.
  2. Create a new Console Application in Microsoft Visual Studio.
Add references to these dynamic link libraries which are located by default in C:\Program Files (x86)\Microsoft Dynamics\eConnect 11.0\API\. (Ignore the x86 if you are using a 32-bit system.)
  1. Microsoft.Dynamics.GP.eConnect.dll
  2. Microsoft.Dynamics.GP.eConnect.Serialization.dll
Replace the Program.cs class in the project for the new class below.

using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.Dynamics.GP.eConnect;
using Microsoft.Dynamics.GP.eConnect.Serialization;
 
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Beginning integration test.");
 
        using (eConnectMethods eConnectMethods = new eConnectMethods())
        {
            try
            {
                Console.WriteLine("Creating a new customer.");
 
                // Modify the connection string for your environment.
                string connectionString = @"data source=localhost; initial catalog=TWO; integrated security=SSPI";
 
                // Create the customer.
                taUpdateCreateCustomerRcd customer = new taUpdateCreateCustomerRcd();
 
                // Assign the vendor to a new master vendor type.
                PMVendorMasterType vendorType = new PMVendorMasterType();
                vendorType.taUpdateCreateVendorRcd = vendor;
 
                // Assign the master vendor type to a new 
                // collection of master vendor types.
                PMVendorMasterType[] masterVendorTypes = { vendorType };
 
                // Serialize the master vendor type in memory.
                eConnectType eConnectType = new eConnectType();
                MemoryStream memoryStream = new MemoryStream();
                XmlSerializer xmlSerializer = new XmlSerializer(eConnectType.GetType());
 
                // Assign the master vendor types to the eConnectType.
                eConnectType.PMVendorMasterType = masterVendorTypes;
 
                // Serialize the eConnectType.
                xmlSerializer.Serialize(memoryStream, eConnectType);
 
                // Reset the position of the memory stream to the start.              
                memoryStream.Position = 0;
 
                // Create an XmlDocument from the serialized eConnectType in memory.
                XmlDocument xmlDocument = new XmlDocument();
                xmlDocument.Load(memoryStream);
                memoryStream.Close();
 
                // Call eConnect to process the XmlDocument.
                eConnectMethods.CreateEntity(connectionString, xmlDocument.OuterXml);
 
                Console.WriteLine("Successfully created vendor {0}.", vendor.VENDORID);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception occured: " + ex.Message);
            }
            finally
            {
                eConnectMethods.Dispose();
            }
        }
 
        Console.WriteLine("Integration test complete." +
                           Environment.NewLine +
                           Environment.NewLine);
        Console.WriteLine("Press <Enter> to continue...");
        Console.ReadLine();
    }
}

Execute the project and you should see the following output:

And you should see the vendor created in Dynamics GP:

28 December, 2011

Dynamics GP ActiveX component can't create object run-time error 429

Recently I was asked to look into an error a client was receiving while standing up a new Citrix server installation for their Dynamics GP 9 clients:


NOTE: If you are recieving this error while attempting to upgrade from Dynamics GP 9 to Dynamics GP 10, see this article.

When we hit the debug button to look at the underlying VBA code, we saw this line:

Based on the line of VBA code above, I knew that a connection to a database was needed, and the RetrieveGlobals9.dll was not installed on the new Citrix server. You might see slightly different code in your VBA, but the important part was that it was trying to call CreateObject on RetreiveGlobals9.

The first step to solve this problem was to download a copy of RetrieveGlobals9.dll from Partner Source at this URL.

Contact your partner to download this for you if you don't have access to Partner Source. Place the file into the Microsoft Dynamics GP folder in the Program Files directory. Since this was a 64-bit machine, the Microsoft Dynamics GP folder was in Program Files (x86).

Next, open a command prompt in elevated mode.

Finally, to register the .dll, type:
Regsvr32 "C:\Program Files (x86)\Microsoft Dynamics\GP\RetrieveGlobals9.dll"

You will need to include the quotes because the directories have spaces in them.

If everything worked properly, you'll receive a confirmation that the .dll was registered successfully.



After these steps were complete, we were able to open and use Microsoft Dynamics GP without receiving the initial VBA error.

Hope it helps!
Bryan Prince







28 August, 2011

Record Lock Trace v 2.0

I've updated the Lock Trace Utility for Dynamics GP with 2 new features.

The first enhancement will replace the standard "This batch is being edited by another user" prompt when attempting to post or delete a SOP Batch with the name of the user that has the batch locked with an activity record in SY00800.


The second enhancement will clear all records that are locked by users that are not actively logged in to GP.  This will be executed when attempting to access a transaction locked by a user that is not actively logged in.  After, the record will be available and the following prompt will be presented.


The basic delete statements executed by this feature are listed below:

delete tempdb.dbo.dex_lock where session_id not in (select SQLSESID from dynamics.dbo.activity)
delete tempdb.dbo.dex_session where session_id not in (select SQLSESID from dynamics.dbo.activity)
delete DYNAMICS.dbo.SY00800 where userid not in (select USERID from DYNAMICS.dbo.activity)

If you are not sure what version of the utility you have installed, since there isn't a GUI, I've updated the about message to include the version information.


Download Record Lock Trace for Dynamics GP here or view the product page for more information.  Please leave a comment if there are other features or transaction types that you would like added to this utility.

09 August, 2011

Reconciling SOP Batches from WITHIN Dynamics GP

An article I posted sometime ago with a SQL Script that would reconcile SOP Batch Totals has really developed legs recently.  It's been downloaded several times a week for several months.  As a result, I felt some enhancement was in order.

To that end, I've compiled the same logic, with few exceptions, into an assembly you can download here.  Copy this to your Addins folder and then you can perform this same function from the Sales Batch Entry Additional Menu without having to run a SQL script or access the Reconcile Receivable Amounts Window in GP.


One exception to the logic in the script is that this will not delete empty batches.  If you have an opinion as to how valuable that feature might be please leave a comment and I will evolve this accordingly.  This utility will simply do the following:
  1. Update batch totals for those that are NOT locked by a user with a Batch Activity Record (SY00800).  This was not designed to reconcile a specific batch but rather all batches at once.  So, don't select a batch in Sales Batch Entry when executing this routine unless you want the batch selected to be excluded from the logic.
  2. Create missing Batch Header records for batches that do exist in SOP WORK (SOP10100) but not SY00500.
This will be especially useful if you want to enable GP users to resolve SOP Batch Total discrepancies on their own without having to grant them access to the Reconcile Receivables Amounts window.

This was developed on GP 2010 and tested on both GP 2010 and GP 10.

07 June, 2011

Resolving "Error occurred in deployment step 'Activate Features': Invalid file name"

The other day I was working on a SharePoint project that required the deployment of a Content Type, a List Template, a couple of List Instances and a couple of Feature Receivers. Things were coming along well until I started to reorganize the project. I dragged the List Instances into the List Template folder and renamed several folders to better represent their purposes. When I went to deploy, I got the error: "Error occurred in deployment step 'Activate Features': Invalid file name".

I looked around the .spdata files, checked the Feature file and double-checked them all again. Everything looked right. The ULS Logger wasn't much help but it did give me the actual exception: "Exception: Microsoft.SharePoint.SPException: Invalid file name. The file name you specified could not be used. It may be the name of an existing file or directory, or you may not have permission to access the file"

Still not much help. I removed all the items from the feature and was able to deploy successfully. Unfortunately, a feature that doesn't do anything isn't much good so I started adding items and deploying one at a time. Feature Receivers: Check. Content Type: Check. List Template: Check. List Instance: Failed.

After looking at the List Instance for a while and not seeing the problem, I deleted the Instance from the project and recreated it from scratch. I added it to the feature, deployed and it failed again.

I went through the List Template again and saw the familiar warning:
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<!-- Do not change the value of the Name attribute below. 
If it does not match the folder name of the List Definition project item, 
an error will occur when the project is run. -->
<ListTemplate
        Name="OldFolderName"
        Type="10001"
I had renamed the List Template folder from within Visual Studio but it did not update the ListTemplate Name element.

After correcting the Name to match the new folder name everything worked and the world was right again.

Add Solution:
Found 1 deployment conflict(s). Resolving conflicts ...
Deleted list instance 'Lists/SomeAwesomeList' from server.
Adding solution 'SomeAwesomeSolution.wsp'...
Deploying solution 'SomeAwesomeSolution.wsp'...
Activate Features:
Activating feature 'SomeAwesomeFeature' ...
Run Post-Deployment Command:
Skipping deployment step because a post-deployment command is not specified.
========== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ==========
========== Deploy: 1 succeeded, 0 failed, 0 skipped ==========

25 April, 2011