Tampilkan postingan dengan label Code. Tampilkan semua postingan
Tampilkan postingan dengan label Code. Tampilkan semua postingan

Senin, 23 Januari 2017

"Get-Content : Cannot find path 'C:\en-US\Welcome.txt' because it does not exist" error when running GPPowerShellStart.ps1

Lately, I have been dabbing into PowerShell. The truth is, I had been wanting to do this for the past 3 years now, to switch from DOS batch files and VBScript to a more robust, developer-like task automation alternative. So I thought myself PowerShell over the past few days and, in doing so, I decided I'd look into the GP PowerShell feature, so off I went installing it.

Summary

Directly from the PowerShell TechNet page, "Windows PowerShell® is a task-based command-line shell and scripting language designed especially for system administration. Built on the .NET Framework, Windows PowerShell helps IT professionals and power users control and automate the administration of the Windows operating system and applications that run on Windows." More on PowerShell here.

GP PowerShell is available for Microsoft Dynamics GP 2013 and above and can be found on the main application setup page.

Setup page
A couple clicks and you are ready to go.

The Problem

The GP PowerShell components install under the Microsoft Dynamics GP folder which contains a startup script, GPPowerShellStartupScript.ps1. A quick look at the code in Windows PowerShell ISE (or Notepad) quickly points out that this script is supposed to retrieve the path from where the script is being launched, concatenate said path with the locale, obtained from the .NET culture (in my case en-US, English United States) and the name of the file, Welcome.txt, to then show its content.

$ScriptRoot = $MyInvocation.PSScriptRoot


$GPPowerShellConfig = "$ScriptRoot\GPPowerShell.dll.config"
[appdomain]::CurrentDomain.SetData("APP_CONFIG_FILE", $GPPowerShellConfig)
Add-Type -AssemblyName System.Configuration

$Locale = (Get-UICulture).Name
if (Test-Path "$ScriptRoot\$Locale\Welcome.txt")
{
    Get-Content "$ScriptRoot\$Locale\Welcome.txt"
}
else
{
    Get-Content "$ScriptRoot\en-US\Welcome.txt"
}


However, when this script is executed, I immediately get the error:

Get-Content : Cannot find path 'C:\en-US\Welcome.txt' because it does not exist.
At C:\Program Files (x86)\Microsoft Dynamics\GPPowerShell\GP2015\GPPowerShellStartupScript.ps1:14 char:5
+        Get-Content "$ScriptRoot\en-US\Welcome.txt"
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          + CategoryInfo : ObjectNotFound: (C:\en-US\Welcome.txt:String) [Get-Content], ItemNotFoundException
          + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand


The Solution

One thing I like about PowerShell is, the errors are pretty clear and concise as to what the problem is, in this case the path to the welcome.txt file is incorrect, and based on the result, the culprit can only be the $MyInvocation.PSScriptRoot not returning the path corresponding to the script's launch location.

In doing some research, I found that the method of obtaining the path of the current running script depends on the version of PowerShell and .NET Framework you are running. For more information on PowerShell scripting engine requirements click here.

For PowerShell 3+, you can simple use the global variable $PSScriptRoot. For all version of PowerShell, the below method works well:

Split-Path $MyInvocation.MyCommand.Path -Parent

This led me to think that perhaps this problem could have been easily taken care of by providing a universal function that could be called within the script, but we will address the issue with the previous method, as follows:


$ScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
# $ScriptRoot = $MyInvocation.PSScriptRoot

$GPPowerShellConfig = "$ScriptRoot\GPPowerShell.dll.config"
[appdomain]::CurrentDomain.SetData("APP_CONFIG_FILE", $GPPowerShellConfig)
Add-Type -AssemblyName System.Configuration

$Locale = (Get-UICulture).Name
if (Test-Path "$ScriptRoot\$Locale\Welcome.txt")
{
    Get-Content "$ScriptRoot\$Locale\Welcome.txt"
}
else
{
    Get-Content "$ScriptRoot\en-US\Welcome.txt"
}


After making these adjustments, the script produces the expected result:

Welcome to the Microsoft Dynamics GP 2015 PowerShell module.
For a list of GP cmdlets type 'Get-Command -module GP2015'.
Use 'Set-GPSessionCentralAddress' to connect to your GP Session Central Service before executing any other GP cmdlet.

After all, what would be of this world without a warm welcome ;-)

Until next post!

MG.-
Mariano Gomez, MVP

Senin, 01 Desember 2014

Microsoft Dynamics GP 2015 Developer's Preview: Dexterity Service Patterns - Part 5


In my previous article (see Microsoft Dynamics GP 2015 Developer's Preview: Dexterity Service Patterns - Part 4), we discussed the merits of the Wrapped Window pattern and how it can save time by avoiding refactoring of complex business logic embedded on Dexterity forms. I also provided an intro on the Decoupled Logic pattern and why this is the preferred method for exposing Dexterity services as it provides the best performance. However, we also came to the conclusion that refactoring is only feasible in cases where decoupling the business logic from the UI will yield a reasonably increase in performance, without negatively impacting development and product release timelines.

We also looked into the Decoupled Logic pattern considerations and today we will accompany these with sample code to clarify the meaning of each.

Decoupled Logic Considerations
  • In decoupled logic mode, there is no implicit data types validation or conversion. Unlike the Window Wrapped pattern where the map statement manages casing, signed versus unsigned precision, and nulls during the binding of window fields to our instantiated class properties; in the Decoupled Logic pattern we must perform all the conversion and validations.
     
  • Check for valid value for decimals
(Click to enlarge)
     

  • Check for a valid value for a drop-down or radio group
(Click to enlarge)
     
  • Force strings to upper case as needed, for example, items, customers, vendors, etc. (see first example).
  • Set default values as needed as user may pass a null value as a parameter to a service procedure
    

There you have it! Working with services and .NET interop is quite frankly not that complicated so long you understand the patterns and some of the rules governing them. So finally, we will put this all together in a final installment that will complete this series.

Until next post!

MG.-
Mariano Gomez, MVP
Intelligent Partnerships, LLC
http://www.intelligentpartnerships.com/

Senin, 24 November 2014

Microsoft Dynamics GP 2015 Developer's Preview: Dexterity Service Patterns - Part 4

What a week of learning that reIMAGINE 2014 conference was! If you stayed for the post conference training classes, even more power to you.

In my previous article (see Microsoft Dynamics GP 2015 Developer's Preview: .NET Framework Interoperability - Part 3), I talked about the .NET interop capabilities introduced in this iteration of Dexterity and how these new capabilities have become the foundation for unlimited extensibility options for Dexterity-based applications. Today, I wanted to talk about another aspect brought by .NET interop: Dexterity services. However, to understand Dexterity services, we must first take a look at the service implementation patterns.

Wrapped Window Pattern

Under the wrapped window pattern,  a complete Dexterity form or window logic is wrapped into a service. The goal is to instrument the user interface and leverage all the existing validation logic within a RESTful service as to avoid recreating that same logic separately -- similar to the behavior displayed by the Web Client.

Wrapped Window Pattern (click on image to enlarge)

A closer to home example is how Integration Manager's standard adapter leverages the UI to get data into Microsoft Dynamics GP.

Wrapped Window Pattern Considerations

Create .NET object
The basic idea here is to leverage .NET to create a class library with all the properties reflected by the associated Dexterity table, i.e., RM_Customer_MSTR, then expose that class to your Dexterity application (via .NET interop). You can then instantiate that class in Dexterity to map the UI fields to the instantiated class properties. Here are some basic steps to follow:
  • Include all fields from table as part of the .NET class 
  • Remove white spaces from names, i.e., 'Customer Number' should be defined as CustomerNumber within the .NET class 
  • Create enumerations for radio groups and drop down lists

Preparation for window being wrapped
What we are trying to do in this instance is, identify all modal dialog elements in our code that may require branching logic for when our application is running in service mode, finding an effective way to handle these.
  • Handle calls to Warning_Dialogs by evaluating service mode state with IsServiceMode() function.
    Warning_Dialogs handling
  • Ask messages for actions need a predetermined action 
    Ask() function
  • On-the-fly record events cannot be performed . When in service mode, if the record is not found, a trace event is created.
Ask() on-the-fly record add
  • TraceAdd and Trace are used to report service events to calling applications or services.

Decoupled Logic Pattern

The Decoupled Logic pattern exposes Dexterity procedures and functions as a service, this is, the business logic does not reside in a form. Under this approach, the objective is to separate the business logic from the user interface. Since we are dealing with procedures and not the user interface, the decoupled logic pattern can be considerably faster from a service performance perspective.

Decoupled Logic Pattern (click on image to enlarge)

Using our closer to home example, think of Integration Manager's eConnect adapter, which leverages the eConnect assembly and stored procedures to bypass the user interface. At the same time, this is a prime example as to why you want to find a Dexterity pattern approach that's suitable for creating a service -- you may not want to have to refactor the entire UI, depending on the complexity of the UI validations. Key points in determining whether to refactor the UI or not are the frequency of use of the UI in question and performance of the UI code.

Decoupled Logic Considerations

  • In decoupled logic mode, there is no implicit data types validation or conversion. Unlike the Window Wrapped pattern where the map statement manages casing, signed versus unsigned precision, and nulls during the binding of window fields to our instantiated class properties; in the Decoupled Logic pattern we must perform all the conversion and validations.
  • Check for valid value for decimals
  • Check for a valid value for a drop-down or radio group
  • Force strings to upper case as needed, for example, items, customers, vendors, etc.
  • Set default values as needed as user may pass a null value as a parameter to a service procedure

In the interest of clarity (and space), I will tackle some decoupled logic sample code in a follow up article.

Until next post!

MG.-
Mariano Gomez, MVP
Intelligent Partnerships, LLC
http://www.intelligentpartnerships.com/

Rabu, 29 Oktober 2014

Microsoft Dynamics GP 2015 Developer's Preview: .NET Framework Interoperability - Part 3

In part 2 of the series, I delivered a brief primer on the service architecture in Microsoft Dynamics GP 2015 and how you are able to consume services natively created with Dexterity. There are two types of services that can be created: services that wrap existing windows and forms functionality, i.e., creating a customer; and services that can wrap around existing sanScript procedures, i.e, retrieving customer information. The truth is, none of that stuff would be possible without the ability to interoperate with the Microsoft .NET Framework.

Today, I'm going to show you some of the .NET interop capabilities built into Dexterity 14.0 and how these can enhance the user experience and your application integration capabilities. The example used in this post is based on my previous article, Building a COM Interop Assembly to use with Microsoft Dexterity, which showed how we had to leverage .NET's ability to expose an assembly via COM interfaces, so we could reference its methods using Dexterity. In that particular example, I built some standard methods to expose the sine, cosine, and tangent trigonometric functions to a Dexterity application. This time around, I will show how to build the same functionality taking full advantage of Dexterity's new .NET interop capabilities.

The user experience will be slightly different this time. In addition to a window that will allow you to calculate any of the trigonometric functions on a specific angle (entered in degrees), we will display a .NET form showing a visual representation of that angle. The .NET form will be dynamically built and displayed from Dexterity using the methods and properties provided by the Form class (System.Windows.Forms) and the visual drawings will be delivered using the classes exposed in the System.Drawing namespace.

1. The first things we must do is incorporate references to the corresponding assemblies for the Forms class and Drawing namespaces. As developer, the first thing you will notice is the enhancements to Dexterity's Library Definition window, which now allows for the selection of a .NET Assembly library type.

Library Definition
Once the type has been selected, you can browse out to the different .NET assemblies loaded on your operating system.

.NET Assemblies
In particular, what I like is the ability to select the .NET Framework assembly that I want to work with directly, as opposed to having to compile my application for a specific Framework version as you normally would do with Visual Studio.

Resource Explorer will now reflect the different .NET namespaces selected for you application.

Resource Explorer

2. Since this article assumes some familiarity with Dexterity, I'm not going to dive into the process of building the form and window, but rather point out that it follows the same layout and properties I used in my previous article.

Form and Window Definition
The only "rarity" here is I added a local field called '(L) GraphicAngleForm' that will serve as a generic reference to the .NET form we will dynamically create. The '(L) Conversion' and '(L) Angle' fields are currency fields and are formatted with two decimals and unsigned (DLR11_U2).

Generic Reference field

3. The code for our '(L) Sine' button will look something like this.

MGB_Trigonometric_Test l_Sine_CHG
// Created by Mariano Gomez, MVP
// This code is licensed under the Creative Commons
// Attribution-NonCommercial-ShareAlike 3.5 Generic license.
using System;
using System.Windows.Forms;
using System.Drawing;

local currency l_angle;
local Form f;

'(L) Prompt' = "The Sin(%1°) value is ";
substitute '(L) Prompt', str('(L) Angle');

// calculate the sine value of the angle after converting it to radians
'(L) Conversion' = Math.Sin(Math.PI * '(L) Angle' / 180.0);

if empty('(L) GraphicAngleForm') then
f = new Form();
'(L) GraphicAngleForm' = f;

f.Text = "Graphical Representation of the Angle entered";
f.BackColor = Color.White;

// Set the size of the form
f.ClientSize = new Size(640, 480);

// Make the form a fixed size
f.MaximumSize = f.ClientSize;
f.MinimumSize = f.ClientSize;

// event handlers
f.Paint += PaintAngle of form MGB_Trigonometric_Test;
f.FormClosed += CloseDrawingForm of form MGB_Trigonometric_Test;

end if;

// show and activate our .NET form
'(L) GraphicAngleForm'.Show();
'(L) GraphicAngleForm'.Activate();

When you really look at the code, the first thing you will notice is the first 3 lines. sanScript now implements the using statement just like C#. sanScript has also been extended to use the implement statement for dynamically referencing an assembly.

using System;
using System.Windows.Forms;
using System.Drawing;


Next we can then calculate the sine of the angle in radians by simply calling the Math.Sin() method in the System namespace (the System namespace is part of the Microsoft Core Library, mscorlib.dll). We also reference the Math.PI constant to perform the conversion of the angle from degrees to radians.

// calculate the sine value of the angle after converting it to radians
'(L) Conversion' = Math.Sin(Math.PI * '(L) Angle' / 180.0);


Once we have the angle converted and displayed in the '(L) Conversion' field, we can proceed to create the form by instantiating the Form class - in reality, this part is no different than what you would normally do for COM classes - and setting some properties for the .NET form we want to display. Once we have set the size of the window, we need to create some event handlers for when the .NET form open and closes. As you can see in the code, Dexterity now implements event handlers through the use of the += operator.

// event handlers
f.Paint += PaintAngle of form MGB_Trigonometric_Test;
f.FormClosed += CloseDrawingForm of form MGB_Trigonometric_Test;


Our event handlers in this case will be the PaintAngle (on .NET form open) and CloseDrawingForm (on .NET form close). The PaintAngle event handler will display the actual graphical representation of the angle, and the CloseDrawingForm event handler will do some cleanup for us. Now, as you may suspect, these event handlers are implemented as procedure scripts to the MGB_Trigonometric_Test form (form procedures). So let's take a look at the PaintAngle form procedure:

PrintAngle
// Created by Mariano Gomez, MVP
// This code is licensed under the Creative Commons
// Attribution-NonCommercial-ShareAlike 3.5 Generic license.
using System;
using System.Windows.Forms;
using System.Drawing;

inout System.Object sender;
inout PaintEventArgs e;

local Pen bp, rp;
local Color c;

// Get the color
c = Color.Blue;

// Create the blue pen
bp = new Pen(c, 2);

e.Graphics.DrawLine(bp, 320, 240, 520, 240);
e.Graphics.DrawLine(bp, 320, 240, 320 + Math.Cos('(L) Conversion' of window MGB_Trigonometric_Test) * 200, 240
- Math.Sin('(L) Conversion' of window MGB_Trigonometric_Test) * 200);

c = Color.Red;
rp = new Pen(c, 2);

e.Graphics.DrawEllipse(rp, 300, 220, 40, 40);

Again, the mechanics here is not so much the important part, but rather to highlight the clever way in which the event handler parameters are declared for the sender of the event and the event arguments being passed by the sender of the event. For this the Dexterity team chose to implement inout parameters.

Overall the PrintAngle procedure simply draws two lines and a circle. The first line is the base line of the angle and will simply be a horizontal line. The second line actually shows the displacement in reference to the base line to give the actual angle representation. This all looks something like this when the code is executed:

Angle Calculator
Having the ability to leverage .NET capabilities directly from Dexterity has now opened up a new realm of possibilities for Microsoft Dynamics GP business application developers. Applications that were once thought to be out of reach or required complex workarounds are now a thing of the past. Code portability is now much more compact and robust than before. The good part is, Dexterity developers do not need relearn any development patterns and/or be exposed to steep learning curves. All the .NET Framework documentation is available online with tons of sample code to go along.



This article is part of the Microsoft Dynamics GP 2015 Developer's Preview series.

Microsoft Dynamics GP 2015 Developer's Preview: Loading the VHD image - Part 1
Microsoft Dynamics GP 2015 Developer's Preview: Working with Sample URIs - Part 2

Until next post!

MG.-
Mariano Gomez, MVP
Intelligent Partnerships, LLC
http://www.intelligentpartnerships.com/

Senin, 12 Mei 2014

Preventing Visual Studio Tools customization processes from being terminated by Microsoft Dynamics GP

Just recently I ran into a case on the Microsoft Dynamics GP partner forum where the ISV developer was dealing with a potentially long standing process to be executed from within their Visual Studio Tools (VST) add-in customization. As a result of this long standing process, the developer wanted to prevent Microsoft Dynamics GP from closing while his process was executing.

The Theory Part

Typically, while a process is executing in Microsoft Dynamics GP a user attempting to close the application would receive the following message:

Process are being run message

However, Microsoft Dynamics GP can cause a Visual Studio Tools customization to abruptly shut-down if a user closes the application deliberately or accidentally. Microsoft Dexterity developers have never had to worry about this since Dexterity processes are managed by the call stack.

A call stack is an internal Dexterity mechanism that manages the processing of scripts. Dexterity has eight call stacks; seven are used for foreground processing while the other is used for background processing. When the scripts on a particular call stack have finished processing, the call stack is available to be used again. Scripts in the call stack can be viewed in Process Monitor whether they are foreground, background, or remote processes.

Process(es) in call stack while running a Trial Balance report

In some instances and depending on the type of process, a Dexterity developer have the ability to mark such a process as removable from the call stack, in which case the end-user can take advantage of the Process Monitor window to delete the process from the call stack.

The Solution

In order for a Visual Studio Tools add-in to protect itself from termination by the Microsoft Dynamics GP application while processes are being executed, you can add the following lines of code before and after the process to be executed.

C# Implementation
//tell Dexterity's runtime engine that we are running background.

Microsoft.Dexterity.Shell.DexGlobals.BackgroundProcessesPending++;
// Execute your process(es) here
//
//
// tell Dexterity runtime engine we're done
Microsoft.Dexterity.Shell.DexGlobals.BackgroundProcessesPending--;


Hopefully you have found this article useful.

Special thanks to Patrick Roth with the Escalation Engineering team in Fargo for posting the response on the partner forum.

Until next post!

MG.-
Mariano Gomez, MVP
Intelligent Partnerships, LLC
http://www.intelligentpartnerships.com/