Deploying Dynamics GP Addin Using Visual Studio Menu Item

I demonstrated this technique in one of the sessions I presented on GP development at the 2016 Tampa GPUG conference, but never got around to writing about it.

Often when developing Dynamics GP Addins, you may wish to deploy the current build to your local copy of Dynamics GP, perhaps for testing. This can be done manually but gets old real quick after many iterations.

I set my development environment up by creating a Visual Studio Menu item to deploy the current build of my GP Addin. This means I can simply use a hot key to deploy my code to the application and start debugging within GP or testing.

This solution copies the debug files into the application Addins folder if the build is debug, and removes them if it isn’t.

Follow this guide for what to do, the following screen shot shows the “Deploy GP Addin Locally” option we are aiming to build…

Create the Menu Item in Visual Studio

Visual Studio External Tools Menu Item

Visual Studio lets you add custom Tools to the Tools menu, select “Tools>>External Tools…”, to set this up.

Visual Studio External Tools Window

Use the “Add” Button on the form to create a new entry with the following value:

Title: Deploy GP Addin Locally
Command: powershell.exe
Arguments: -ExecutionPolicy RemoteSigned -File "$(SolutionDir)\Deployment Batch Files\DeployGPLocally.ps1" -BuildOutputFolder $(BinDir)
Use Output window: “Ticked”



Click Apply and OK on the window to preserve your changes.

The “Deploy GP Addin Locally menu option should now exist on the Tools Menu of Visual Studio but it wont work yet, not without creating the power shell script it calls.

The Menu calls the power shell interpreter (Powershell.exe),  passing the script to run and the build directory to run the script against as command arguments.

The external tools window provides some super handy environment variables you can insert into the fields. Click the little arrow boxes to the right to see the options available, useful for future reference. However in this case we take the output build location for the binary output $(BinDir) and use that folder as the basis for the script to run against. We also use the ${SolutionDir) variable to locate the powershell script, as different developers may have the source in different root directories.

Create the power shell script

Next create the script that is called from the menu item. I have a folder in the solution for this script (Deployment Batch Files), you can hack the location around to your needs changing the path as required.

Save the following into a text file with the name “DeployGPLocally.ps1" in a “Deployment Batch Files” folder located off the root folder of the solution folder for the AddIn Solution.

The script takes the build folder as a parameter, it then looks up the Dynamics GP Application location from the registry, that is then used for the destination of the copied files.

The script decides if visual studio is in a debug or release build mode, by looking for the folder “debug” in the current build path. It then copies the files appropriate to the build type, to the application Addins folder.


DeployGPLocally.ps1

param ($BuildOutputFolder)
 
### SCript to automate deployment of GP Addins to the installed application Addin folder during development
### Written by T. Wappat 2021-11-25
###
###	Todo - check folders exist before operation
### Todo - provide a list of registry locations for various version of GP  ###    https://timwappat.info/post/2016/12/02/Lookup-Dynamics-GP-install-location-from-registry-key
 
# $AddInsPath = "C:\Program Files (x86)\Microsoft Dynamics\GP2015\AddIns\"
# Fetch the location of GP application from the registry (this requires that GP installer was used to install it)
$AddInsPath = Get-ItemPropertyValue 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Business Solutions\Great Plains\v14.0\1033\DEFAULT\SETUP' 'AppPath'
$AddInsPath = "$($AddinsPath)AddIns\"
 
# Note the following is regular expression match
if($BuildOutputFolder -match '.*\Debug\.*' )
{
	Write-Output "Starting to deploy GP (Debug) to $AddInsPath"
	Write-Output "================================================================================================="
	Copy-Item -Path ($BuildOutputFolder + "\*.dll") -Destination $AddInsPath -Recurse -Force -Verbose
	Copy-Item -Path ($BuildOutputFolder + "\*.pdb") -Destination $AddInsPath -Recurse -Force -Verbose
	Write-Output "================================================================================================="
	Write-Output "Deployed (Debug) GP to $AddInsPath"
}
else
{
	Write-Output "Starting to deploy GP (Release) to $AddInsPath"  Write-Output "================================================================================================="
	Copy-Item -Path ($BuildOutputFolder + "\*.dll") -Destination $AddInsPath -Recurse -Force -Verbose
	Get-ChildItem -Path $AddInsPath *.pdb | foreach { Remove-Item -Path $_.FullName -Verbose }
	Write-Output "================================================================================================="
	Write-Output "Deployed (Release) GP to $AddInsPath"
}
 
 

MyTNT Missing Pending Shipment Menu item fixed

Pending Shipment Menu Item Missing from MyTNT web application

For some users the Pending Shipment Menu item was found to be missing when the logged into the MyTNT website. This was found to be a cross browser issue, also using another login credentials or clearing cookies and cache didn't clear the issue. It was not user rights related in the MyTNT application either. 

This was found to be linked to specific users in a particular Active Directory group policy group applied to the users. This policy was blocking internet access, except for certain domains. This policy was the immediate clue as to what might be happening. 

If you look in the image below, pending shipments does not exist in Domain User A web browser, but does for Domain User B. Also note the hamburger menu is added on B too. 

After investigating in the developer tools of the internet browser to see what page objects were failing to load, one set of requests became prime suspect -requests to https://cdn.optimizely.com/js/5435521705.js

Blocking this domain (optimizely) on a machine that was not affected, caused the pending shipments menu option to vanish after clearing caches on the previously working machine -aha! 

Thus group policy was adjusted to allow this content distribution network, and all was well again. 

 

 

 

Dynamics GP 2015 email–”Word experienced an error trying to open the file error sending email” when using Word Templates

When attempting to send a document that utilises word templates, even using PDF format, the following error may occur, due to a tightening in security with in the Microsoft Office product, “Word experienced an error trying to open the file”.

Word experienced an error trying to open the file. Try these suggestions Check the file permissions for the doucment or drve


Information about this issue can be found here: Word Templates will not Email/Print after Office Update

As I understand it the XML generated by Dynamics GP is not compliant and causes office versions with the patch to fail to load it. This is true also if you send the word document as a word document to someone, that was generated in Dynamics GP. If they have patched office, the document will not open.  When Dynamics GP sends a PDF/Word email it saves the document into a temp directory first then sends it, turning it into a PDF as required. This is why even PDF documents are affected too.

Solution for GP versions after 2016

The solution is to update with the latest GP patches if you are on supported version of GP 2016 and above (at time of writing).

Those updates found here:

Microsoft Dynamics GP U.S. Year-End Update 2020 RELEASED!!


Bodge solution for GP version 2015 – maybe previous version too, I’ve not tested

If you are running unsupported GP2015, then you are stuck without a solution, other than the correct one, that you really should be upgrading to a supported version of GP f9or these exact kind of reasons). In the real world there are many reasons why this may not be possible quite yet and you don’t want to postpone rolling out office updates, as for security reasons, you really shouldn’t hold back on updates.

There is a totally unsupported bodge you can use that may give you some time to upgrade whilst still benefiting from the latest office updates.  Please note this action should be considered carefully as it has not been talked about by Microsoft as an option.


Fix Dynamics GP 2015 Word experienced an error issue

To fix this issue for Dynamics 2015, in a totally unsupported way – this is at your own risk!

Download GP2016, more importantly, Download the latest GP 2016 cumulative update and install the client only, obviously you don’t want to upgrade your GP install yet, and have no need for the database components, perhaps do this on another machine for peace of mind. You must have the release that contains the bug fix, follow the link above. It must be the 2016 version of GP, this will not work if you do this with the 2018 version!


Download for GP2016 patch


In the Dynamics GP Program files folder of the 2016 install locate the following two files :

Microsoft.Dynamics.GP.BusinessIntelligence.Office.dll

Microsoft.Dynamics.GP.BusinessIntelligence.Office.dll 
Microsoft.Dynamics.GP.BusinessIntelligence.TemplateProcessing.dll

  • Ensure Dynamics GP is closed on the machine that you are working on
  • Also find the same files in the GP2015 install folder, take a copy of them onto your desktop or somewhere safe, so you have a way to go back if you need to
  • Now replace only these two files in the Dynamics GP2015 folder with those from the GP2016 folder.

You may now launch GP2015. You should find that the ability to send emails of Word Template based reports has now been restored. Roll these two files out to your other workstations that use word template reports.

These were the version numbers on those files from the attempt I made:

version 16.0.865.0 Microsoft.Dynamics.Gp.BusinessIntelligence.TemplateProcessing.dll

version 16.0.865.0 Microsoft.Dynamics.Gp.BusinessIntelligence.TemplateProcessing.dll


Note: This is totally unsupported method and use it at your own risk. However with this just been a reporting and office library dlls, the risk to GP should be low in my opinion. Although this is an unsupported change, if you are such an old version of GP you are running unsupported already to some extent anyway! Also note that this will only work with version 2016->2015. When I tried it with a more modern version of the dll, GP failed to open.

Dynamics GP Web Client is not compatible with Docker due to the Session Central Service not starting

Back in 2017 I attended NAVTechDays and after seeing the benefits the Dynamics NAV community were getting from Docker containers with NAV, I could see similar benefits for Dynamics GP and was also seeing interest in some of the conversations going on out of public within the GP community. I decided to see if I could get a good selection of Dynamics GP versions into Docker. Word of that work spread and brought out more people interested in containers for GP.

I spent a lot of hours attempting to learn Docker and to containerise Dynamics GP. I could build Docker images for the GP server database and add in various of the GP components like eConnect and oData. Sadly I was unable to get the Dynamics GP Web Client working – I really was just a cat's whisker away from success too. I was defeated by the Domain Account the Web Client runs under and its use of System.DirectoryServices.AccountManagement .  The following is a record of what I found when investigating that issue.

Although I can create a Docker container for the database and test company and even things like eConnect, there is a big problem. You see, there is no GUI with Docker containers, you can’t just remote desktop into one, you really need the Web Client running in order to make a self contained system to allow the host to interact with the Dynamics GP application in the container. If you can't access it from a browser, then you end up having to install the GP client on a non-docker machine (host) and connect using it to the running Docker container. Although this works its not really in the spirit of what I was trying to achieve, which was a fully self contained container for GP to allow multiple versions of GP to be easily spun up for testing and demo purposes. 

Why I failed?

After the Docker image has installed GP and the the web client product features are installed, one of these, the Session Central Service fails to start within the container. The Session Central Service is the component of the Dynamics GP web client installation that creates and tracks individual web client sessions as users connect and disconnect via web browsers. It is essential to getting web client to work.

At this point let me jump tight to the core issue, essentially the problem is that the “Lanman” windows service can’t be started in a Windows container. The Lanman service provides things like SMB file share services and is also referred to as the “Sever Service”, as that is how it appears, named in the services control panel list of services.

Containers are supposed to be stateless isolated single-app process groups, not mini virtual machines, so running Lanman would break that philosophy, hence it is not appropriate for it to be supported in Windows Docker containers as it doesn't make sense. Containers are supposed to be created and torn down at the drop of a hat, participating in active directory goes against the grain. This is why, along with a few other Windows features that are inappropriate for containers, we find the Lanman service just isn’t supported in a Windows Container.

So why is this a problem?

You find that the session central service, checks for the user running, see (2) in the following screenshot, and it does this using a call to the following method:

Dynamics.GP.Web.Foundation.DirectoryServices.PrincipalManager.GetPrincipal

This method in turn calls .NET method:

System.DirectoryServices.AccountManagement.Principal.FindByIdentityWithType(PrincipalContext  context, Type principalType, IdentityType identityType, String identityValue)

It is the above call that fails, and looking in the event log we see it reporting as indicated by (1) in the screenshot below that:

Exception message:
                      The Server service is not started.

Possible Root cause sml

Hey? - "The Server service is not started" – a confusing message as it means that the Lanman Service (aka server service) is not started. Yes this was super confusing error at the start of this investigation, not realising there was a "server service", so I assumed it was just a poor error message that wasn't telling me the service that had failed. So once I realised what it meant, I found out that, indeed if you go into the Docker container with Power Shell you can check that the Server Service is not started and even try to start it, however it will not start,  and this is due to the lack of support in windows containers, as previously stated. Lanman service can't be run on a windows container, thus preventing the use of .NETs System.DirectoryServices.AccountManagement. Microsoft in the past also confirm there are no plans for this to ever change, as it is fundamentally at odds with container philosophy to have this service running.

Without a small change to the Session Central Service code I was not going to get anywhere with this until…

Docker and Group Managed Service Account (gMSAs)

Hope then followed when I then discovered about the existence of Group Managed Service Account (gMSA), that I thought may provide a route to get around the lack of Lanman support. Using it we can authenticate to Active Directory resources from Windows container which is not part of your domain (remember the container is not part of domain), using gMSA. gMSA is a special account that can be used to (over simplifying it) allow passing over a domain account for the purposes of running a service. This could be perfect for replacing the user running the session central service, instead it can ran using a gMSA account for authentication. That in turn would bypass Lanman. When you run a container with a gMSA, the container host retrieves the gMSA password from an Active Directory domain controller and gives it to the container instance. The container will use the gMSA credentials whenever its computer account (SYSTEM) needs to access network resources. In our case, when building the Docker container we can use Power Shell to change the Session Central service to use the SYSTEM account having prepared the plumbing required for gMSA to authenticate against AD. SYSTEM become a kind of proxy for the AD account behind the gMSA account. For this to work certain prerequisites needs to be met, there is quite a bit of learning that is more than I want to get into here. I built this up in Docker to test.

Sadly on testing this thoroughly for a few days, it turns out there is another a problem. A docker container can access AD using the gMSAsaccount, allowing FindByIdentity to work WHEN the constructor for PrincipalContext is created with “ContextType.Domain” and specifying an accessible AD. The group managed service account technique does not work when the context type of “ContextType.Machine” is used to create the PrincipalContext.  

If we look at what is going on in the class in the screen shot– we find that the class is created with a constructor specifying machine as the context, stumping our attempts to use the gMSA to get around the lack of Lanman. This is due to no user name getting passed to the isMachineContext function (see last screen shot). I'm guessing that using the built in NETWORK user means no user name is passed into this method thus resulting in the highlighted code (1) being ran in the following screen shot.  This was upsetting to find as I thought I had it cracked at this point.

Not all that learning is a loss as it was interesting to learn about gMSA as it is an important container and enterprise computing concept to understand.

The following screenshot shows the code causing our issue (1) in the Microsoft.Dynamics.GP.Web.Foundation.DirectoryServices.PrincipalManager.

 

GetPrincipal2

 

User name being blank when calling the following IsMachineContext method then means the split in the logic in the above screen shot causes it to create the PrincipalContext using the wrong context type to use gMSA.

ismachinecontext

Finally


We can create containers with the database and test company but sadly we end up having to install the GP client on the host to access the container, that was not really my objective.

 Would be amazing if the code could be updated by Microsoft but I don’t see this happening. I need to refresh myself on the exact details of this issue and check again that nothing has changed in the years since I was trying as Docker is getting constantly improved with new features.