Azure DevOps Workspaces Command

Purpose:

The purpose of this blog is to demonstrate the use of Azure DevOps Workspaces command and how it can be executed using the Developer Command Prompt for V2015.

Assumptions:

Visual Studio 2015 version is being used.

Steps:

Sometimes you need to know the existing workspaces created for different users on different machines. The best way to find it is to use the workspaces command.

On Start menu search for Developer Command Prompt for VS2015

This will open the root directory for command line utility.

Use the following command to view all the workspaces

tf workspaces /owner:* /computer:*

For example, to view existing workspaces for a specific computer you can use the following command:

tf workspaces /owner:* /computer:<Machine Name>

This is really helpful if you are investigating The path is already mapped in workpace issue.

References:

Advertisements

D365FO: Design Flow for Business Events in Dynamics 365 for Finance and Operations

Products:

  • Dynamics 365 for Finance and Operations
  • Microsoft Flow
  • Azure messaging service

Purpose:

The purpose of this blog is to demonstrate how you can write Microsoft Flows to consume Business Events which are available in Dynamics 365 for Finance and Operations.

Prerequisites:

  • Basic understanding of Microsoft Flows
  • Business events are activated in Dynamics 365 for Finance and Operations
  • Business events are available from Platform Update 24.

Steps:

To demonstrate business events, we’ll keep this Flow nice and simple. We’ll design a flow to send email notification whenever a purchase order is confirmed. For this purpose, a business event is available out of the box in Dynamics 365 for Finance and Operations.

Login to flow.microsoft.com. Click New > Automated – from blank.

Click Skip on the following dialog.

For Triggers, choose When a Business Event occurs in Dynamics 365 for Finance and Operations.

Configure instance, category, business event and legal entity. For Actions, choose Send an email notification

Configure the send mail action as follows.

Click Save. Let’s go to Dynamics 365 for Finance and Operations and raise and confirm a Purchase order. As soon as you confirm a purchase order. You should receive an email notification originating from your newly created flow.

You can also see history for your flows.

When you click on one of the history run, you can also view actual values past for this run.

This is how you can design flows consuming business events out of Dynamics 365 for Finance and Operations. Given the available options for actions to choose from and power of Microsoft Flows, the possibilities are endless!

 

Integrate Dynamics 365 for Finance and Operations with CDS

Product:

  • Dynamics 365 for Finance and Operations
  • Common Data Service

Purpose:

The purpose of this blog is to demonstrate how we can integrate Dynamics 365 for Finance and Operations with Common Data Service (CDS).

Prerequisites:

  • Basic understanding of Common Data Service and PowerApps Admin Center

Steps:

Create a new custom entity for Vendor and add relevant fields

Create a new connection set in PowerApps Admin Center. Select Finance and Operations as the source environment and CDS as the target environment. Give in relevant organization details.

Create a new Data Integration Project in PowerApps Admin Center. Select Fin and Ops to CDS template.

Select the previously created connection set and then select the relevant organization.


Once the project is created, add a new task to the project, selecting the relevant source and target entities.

Configure field mappings.

Finally run the project.

Bingo! Data flows from Dynamics 365 for Finance and Operations to CDS.

You can schedule the Data Integration Project as well!

D365FO: The path is already mapped in workspace

Product:

Dynamics 365 for Finance and Operations

Purpose:

The purpose of this blog is to explain how can we resolve the version control workspace issue while working with Azure DevOps version control, previously known as VSTS or TFS.

Error:

The path % is already mapped in workspace %

Resolution:

First make sure that no other workspace is mapped to this path (including remote workspaces).

Sometimes the trick could be to clear the TFS cache on client machines

  1. Close all instances of Visual Studio on the client machine
  2. Manually delete the corresponding TFS client cache folder
  3. Start Visual Studio

The path to TFS client cache folder varies depending on the TFS version. Delete contents of the cache folder to resolve the issue:


Tfs 2017: “%localappdata%\Microsoft\Team Foundation\7.0\Cache\”
Tfs 2015: “%localappdata%\Microsoft\Team Foundation\6.0\Cache\”
Tfs 2013: “%localappdata%\Microsoft\Team Foundation\5.0\Cache\”
Tfs 2012: “%localappdata%\Microsoft\Team Foundation\4.0\Cache\”

AX 2012: Existing DIXF service for new AOS instance

Product:

Dynamics AX 2012

Purpose:

The purpose of this document is to explain what we can do to reuse a DIXF service of an existing AOS instance for a new AOS instance

Steps:

Copy paste the following files from the bin folder of an existing AOS instance to the bin folder of the new AOS instance:

  • DMFClientConfig.xml
  • DMFConfig.xml
  • Microsoft.Dynamics.AX.Framework.Tools.DMF.ServiceProxy.dll.config

D365: Send email in X++ using email templates

Product:

Dynamics 365 for Finance and Operations

Purpose:

The purpose of this document is to demonstrate how we can send emails in X++ using built-in email templates. For demonstration purposes we’ll be using standard email template CnfmOrder. This email template is used to send email to customer when a sales order is confirmed.

Business requirement:

Automate sending email to customer’s contact when a sales order is confirmed.

Prerequisites:

Email parameters must be configured.

Development:

You can find built-in email templates in the system under:

Organization administration > Setup > Email templates

Note that we have keyed in the sender email field as it will be used in the code as the “Sender”.

image

You can use the following code to automatically send email using X++. The given code also handles inserting placeholder values in the email template. It uses salesId variable which is a global class member and is provided by the caller class.

public void generateEmail()
{
   // Tokens for email template replacement
   #define.SalesIdToken('salesid')
   #define.CustNameToken('customername')
   #define.DeliveryAddress('deliveryaddress')
   #define.CustomerAddress('customeraddress')
   #define.ShipDate('shipdate')
   #define.ModeOfDelivery('modeofdelivery')
   #define.Charges('charges')
   #define.Discount('discount')
   #define.Tax('tax')
   #define.Total('total')
   #define.LineProductName('lineproductname')
   #define.LineProductDesc('lineproductdescription')
   #define.LinePrice('lineprice')
   #define.LineQuantity('linequantity')
   #define.LineNetAmount('linenetamount')

   SalesTable salesTable;
   SalesLine salesLine;
   CustTable custTable;
   ContactPerson contactPerson;
   SalesTotals salesTotals;
   SysEmailId emailId;
   Map templateTokens;

   str emailSenderName;
   str emailSenderAddr;
   str emailSubject;
   str emailBody;
   str emailToAddress;

   salesTable = SalesTable::find(salesId);
   salesLine = SalesLine::find(salesTable.SalesId);
   custTable = CustTable::find(salesTable.CustAccount);
   contactPerson = ContactPerson::find(salesTable.SixCustContactPersonId);
   salesTotals = SalesTotals::construct(salesTable);
   emailToAddress = contactPerson.email();
   emailId = salesTable.SixSysEmailId;

   templateTokens = new Map(Types::String, Types::String);
   templateTokens.insert(#SalesIdToken, salesTable.SalesId);
   templateTokens.insert(#CustNameToken, custTable.name());
   templateTokens.insert(#DeliveryAddress, salesTable.deliveryAddress().Address);
   templateTokens.insert(#CustomerAddress, custTable.address());
   templateTokens.insert(#ShipDate, strFmt("%1", salesTable.ShippingDateRequested));
   templateTokens.insert(#ModeOfDelivery, strFmt("%1", salesTable.DlvMode));
   templateTokens.insert(#Charges, strFmt("", salesTotals.totalMarkup()));
   templateTokens.insert(#Discount, strFmt("", salesTotals.totalEndDisc()));
   templateTokens.insert(#Tax, strFmt("", salesTotals.totalTaxAmount()));
   templateTokens.insert(#Total, strFmt("", salesTotals.totalAmount()));
   templateTokens.insert(#LineProductName, salesLine.itemName());
   templateTokens.insert(#LineProductDesc, salesLine.itemLineDisc());
   templateTokens.insert(#LinePrice, strFmt("%1", salesLine.SalesPrice));
   templateTokens.insert(#LineQuantity, strFmt("%1", salesLine.SalesQty));
   templateTokens.insert(#LineNetAmount, strFmt("%1", salesLine.LineAmount));
      
   if (emailId)
   {
    [emailSubject, emailBody, emailSenderAddr, emailSenderName] =
      SixCustVendEmailController::getEmailTemplate(emailId, custTable.languageId());
   }
      
   var messageBuilder = new SysMailerMessageBuilder();
   messageBuilder.addTo(emailToAddress)
    .setSubject(emailSubject)
    .setBody(SysEmailMessage::stringExpand(
      emailBody, SysEmailTable::htmlEncodeParameters(templateTokens)));

   if (emailSenderAddr)
   {
    messageBuilder.setFrom(emailSenderAddr, emailSenderName);                
    SysMailerFactory::getNonInteractiveMailer().sendNonInteractive(messageBuilder.getMessage());
   }
}

The generateEmail() method calls the following method to get the email template:

protected static container getEmailTemplate(SysEmailId _emailId, LanguageId _languageId)
{
   var messageTable = SysEmailMessageTable::find(_emailId, _languageId);
   var emailTable = SysEmailTable::find(_emailId);

   if (!messageTable && emailTable)
   {
      // Try to find the email message using the default language from the email parameters
      messageTable = SysEmailMessageTable::find(_emailId, emailTable.DefaultLanguage);
   }

   if (messageTable)
   {
      return [messageTable.Subject, messageTable.Mail, emailTable.SenderAddr, emailTable.SenderName];
   }
   else
   {
      warning("@SYS135886"); // Let the user know we didn't find a template
      return ['', '', emailTable.SenderAddr, emailTable.SenderName];
   }
}

Once triggered, the code sends out the email through the configured SMTP server and can be found in the inbox:

D365: Create lookup in X++

Product:

Dynamics 365 for Finance and Operations

Purpose:

The purpose of this document is to demonstrate how we can create a lookup in X++ and attach it to an extension field added to the form extension of standard Sales order form. This is a good example to see how we can use event handling to achieve our goal without modifying the standard code.

Business requirement:

Display contacts of the customer in a lookup for which the sales order has been raised.

Development:

You can find the event handler below which handles the lookup event of a form control. This form control is added to the extension of SalesTable standard AX form.

[FormControlEventHandler(formControlStr(SalesTable, SixSalesOrderConfirmation_SixCustContactPersonId),
FormControlEventType::Lookup)]
public static void SixSalesOrderConfirmation_SixCustContactPersonId_OnLookup(FormControl sender, FormControlEventArgs e)
{
Query query;
QueryBuildDataSource qbdsContactPerson;
QueryBuildDataSource qbdsCustTable;
QueryBuildDataSource qbdsSalesTable;
SysTableLookup sysTableLookup;
SalesId salesId;
FormControlCancelableSuperEventArgs event;

event = e as FormControlCancelableSuperEventArgs;
salesId = sender.formRun().design().controlName(formControlStr(SalesTable, SalesTable_SalesId)).valueStr();

query = new Query();
qbdsContactPerson = query.addDataSource(tableNum(ContactPerson));

qbdsCustTable = qbdsContactPerson.addDataSource(tableNum(CustTable));
qbdsCustTable.joinMode(JoinMode::InnerJoin);
qbdsCustTable.relations(false);
qbdsCustTable.addLink(
fieldNum(ContactPerson, ContactForParty),
fieldNum(CustTable, Party));

qbdsSalesTable = qbdsCustTable.addDataSource(tableNum(SalesTable));
qbdsSalesTable.joinMode(JoinMode::InnerJoin);
qbdsSalesTable.relations(false);
qbdsSalesTable.addLink(
fieldNum(CustTable, AccountNum),
fieldNum(SalesTable, CustAccount));

qbdsSalesTable.addRange(fieldNum(SalesTable, SalesId)).value(SysQuery::value(salesId));

sysTableLookup = SysTableLookup::newParameters(tableNum(ContactPerson), sender);
sysTableLookup.addLookupfield(fieldNum(ContactPerson, ContactPersonId));
sysTableLookup.addLookupfield(fieldNum(ContactPerson, Party));
sysTableLookup.parmQuery(query);
sysTableLookup.performFormLookup();

event.CancelSuperCall();
}