Multiple Service Tiers – SP1

Right around the release of Microsoft Dynamics NAV 2009, I wrote a blog entry with some .bat files on how to create multiple Service Tiers when working with NAV 2009.

The blog post is here: http://blogs.msdn.com/freddyk/archive/2008/10/29/multiple-service-tiers.aspx

Now NAV 2009 SP1 is about to be released, it is time for a small update. One of the files of the package is a CustomSettings.template file, which really is just the CustomSettings.config with a few values replaced with variable names, so that we can replace those automagically.

Now in SP1, the CustomSettings.config has changed – new keys have been added and we also support named instances in the database.

SP1 will actually run with the old config file, so we could just ignore the entire thing and continue as if nothing happened – the .bat files will still work in SP1.

However – if we want to take advantage of the named instances in SQL Server or we want to have the additional keys available for modifying we need to change something.

I have created a new CustomSettings.template based on the SP1 config file – copy the config file and change the following keys:

    <add key=”DatabaseServer” value=”#DBSERVER#”></add>
<add key=”DatabaseInstance” value=”#DBINSTANCE#”></add>
<add key=”DatabaseName” value=”#DATABASE#”></add>
<add key=”ServerInstance” value=”#INSTANCE#”></add>

and extended the createservice.bat file to also allow a database instance to be specified, meaning that the usage is now:

CreateService name [dbserver] [“dbinstance”] [“dbname”] [demand|auto|disabled] [both|servicetier|ws]

The new .zip file is available for download here.

Enjoy

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Edit In Excel – Part 4 (out of 4)

If you haven’t read part 3, part 2 (and part 1), you should do so before continuing here.

We have seen how to put code inside Excel, using VSTO and connect to NAV 2009 Web Services. We have seen how to add this to a table inside Excel and how to write data back to NAV through Web Services. We can delete, add and modify records in Excel and we can even do so with both Customers, Vendors and Items. We have added support for NAV filters, error handling and the only thing we are missing to have a very good experience is integrating the whole thing into NAV.

So we better do that now!

Disclaimer

Most of the people reading this are probably superior AL coders compared to me. I really only started coding in AL last year and I am still struggling to understand how things works. So, if I do something stupid in AL – just tell me in a comment how to do it smarter – thanks.

Actions in NAV 2009

What we want to do is

image

Add an action to the menu, which launches Excel, reads the same data as we have in the list place and allows the user to modify that data.

But we need to start in a different area – we need a COM object, which our action can invoke.

Creating a COM object

First of all we will create a COM object, which contains one function.

public void EditInExcel(string page, string view)

I do think there are a number of tutorials that explains how to do this, so I will run over the steps very quickly.

  1. In the same solution as the NAVTemplate, create a new project – type Class Library and name the project NAVEditInExcel
  2. Rename class1.cs to NAVEditInExcel.cs – and say Yes to the question whether you want to rename the class as well.
  3. Select Properties on the project (not the solution)
    1. On the Build tab, set the output path to ..NAVTemplatebinDebug in order to share the output path the the Excel Spreadsheet
    2. On the Build events tab, we need to register the COM object to make it visible to NAV. Add the following Post Build Event: C:\Windows\Microsoft.NET\Framework\v2.0.50727\regasm NAVEditInExcel.dll /codebase /tlb
    3. On the Signing tab, check the Sign the Assembly checkbox and select New key in the combo box, name the key and protect it with a password if you fancy.
  4. Open the AssemblyInfo.cs (under Properties in the Solution Explorer)
    1. Add using system; to the using statements
    2. Add [assembly: CLSCompliant(true)] under the line with [assembly: ComVisible(false)].
  5. Open the source for the NavEditInExcel.cs
    1. Add using System.Runtime.InteropServices; to the using statements
    2. Create an Interface and change the class to be:

[ComVisible(true)]
[Guid(“A2C51FC8-671E-4135-AD27-48EDC491E76E”), InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface INAVEditInExcel
{
void EditInExcel(string page, string view);
}

[ComVisible(true)]
[Guid(“233E0C7F-2276-4142-929C-D6BA8725D7B4”), ClassInterface(ClassInterfaceType.None)]
public class NAVEditInExcel : INAVEditInExcel
{
public void EditInExcel(string page, string view)
{

        // Code goes here…
    }
}

Now you should be able to build the COM object and see it inside NAV when adding a variable of type automation.

Adding the Action in NAV

Open up the Classic Client and design the Customer List Place.

Insert an Action on the Customer List Place called Edit In Excel and edit the code for that (btw. the Image Name for the Excel icon is Excel)

In the code for that Action – create a local variable called NAVEditInExcel of type Automation and select the NAVEditInExcel.NAVEditInExcel COM object to use and add the following code:

CREATE(NAVEditInExcel, TRUE, TRUE);
NAVEditInExcel.EditInExcel(TABLENAME, GETVIEW(TRUE));

That’s it on the NAV side, but of course we didn’t make all the code necessary in the COM object yet.

If you try to launch the Action you will be met by the security dialog

image

Which you of course want to hit always allow to – else you will get this dialog every time you say Edit In Excel.

BTW – If you hit Never allow – you will never be allowed to Edit in Excel – not until you have deleted your PersonalizationStore.xml at least.

Completing the COM object

Having that hooked up we really just need to launch that damn spreadsheet with the right parameters.

We need to add 3 .NET references to the COM object:

  • System.Windows.Forms
  • Microsoft.Office.Interop.Excel
  • Microsoft.VisualStudio.Tools.Applications.ServerDocument.v9.0

and the following 3 using statements:

using Microsoft.VisualStudio.Tools.Applications;
using System.Windows.Forms;
using System.Reflection;

and last but not least, add the following EditInExcel method:

public void EditInExcel(string page, string view)
{
try
{
// Copy the original template to a new template using the page name as name!
string originalTemplate = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), “NAVTemplate.xltx”);
if (!System.IO.File.Exists(originalTemplate))
{
MessageBox.Show(string.Format(“The template: ‘{0}’ cannot be found!”, originalTemplate), “Error”, MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
string template = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), page + “.xltx”);
while (System.IO.File.Exists(template))
{
try
{
System.IO.File.Delete(template);
}
catch (System.IO.IOException)
{
if (MessageBox.Show(string.Format(“The template: ‘{0}’ is locked, cannot open spreadsheet”, template), “Error”, MessageBoxButtons.RetryCancel, MessageBoxIcon.Error) != DialogResult.Retry)
{
return;
}
}
}
System.IO.File.Copy(originalTemplate, template);

        // Open the new template and set parameters
ServerDocument serverDoc = new ServerDocument(template);
CachedDataHostItem host = serverDoc.CachedData.HostItems[0];
host.CachedData[“page”].SerializeDataInstance(page);
host.CachedData[“view”].SerializeDataInstance(view);
serverDoc.Save();
serverDoc.Close();

        // Create a new spreadsheet based on the new template
Microsoft.Office.Interop.Excel.ApplicationClass excelApp = new Microsoft.Office.Interop.Excel.ApplicationClass();
excelApp.Visible = true;
excelApp.Workbooks.Add(template);

        // Erase template
System.IO.File.Delete(template);
}
catch (Exception e)
{
System.Windows.Forms.MessageBox.Show(e.Message, “Critical error”, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}

This method really does 4 things:

  1. Copy the NAVTemplate.xltx to a new template called Customer.xltx (that is if the page name is customer) which is a temporary template
  2. Open the template as a ServerDocument and set the parameters
  3. Ask Excel to create a new spreadsheet based on this template
  4. Erase the template

That was easy!

Oh – there is one things I forgot to say, you need to specify in the Excel Spreadsheet that the page and view variables are cached data (meaning their value are saved with Excel) – this is done by adding an attribute to the variables:

[Cached]
public string page;

[Cached]
public string view;

Having done this, you can open the spreadsheet as a Serverdocument, get and set the value of these parameters and save the document again, pretty sweet way of communicating parameters to Excel or Word – and this will definitely come in handy later.

Adding the action other pages

Having verified that we can edit customers in Excel we can now add the same action as above to the Vendor and the Item List Places.

You can either follow the same steps as above – or you can copy the action and paste it on the other List Places.

Note that you cannot build the Visual Studio solution while you have NAV 2009 open. When NAV loads the COM object, it keeps a reference to it until you close NAV.

Last but not least – this should work from the classic client as well – if you want to add the functionality there – I haven’t tried it.

That’s it folks

That completes the Edit In Excel in Part 1 through 4

As always, there is absolutely no warranty that this code works for the purpose you need it to, but these posts show how to do some things and feel free to use pieces of this or use it as a base to build your own solution using Excel – the code is free – a referral to my blog is always a good way of acknowledgement.

I hope you can make it work, that it is useful and you can download the final solution here: http://www.freddy.dk/NAVTemplate_Final.zip

Enjoy

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Edit In Excel – Part 3 (out of 4)

If you haven’t read part 2 (and part 1), you should do so before continuing here.

In Part 1 and 2, we have seen how easy it is to add a Web Service Reference inside Excel, and use it to get Data. In Part 2 we even had the ability to modify data and send this back to NAV. The original intend was that part 3 would be all about integrating this to NAV on the Client side and part 4 would be to make this loosely coupled – but I have changed my mind on this.

Part 3 will remove the direct dependency on the Customer Web Service from most of the code – and thus allowing us to modify both Customer, Vendor or Item data in Excel with very few tweaks to the code. Also I will add support for parsing a filter string and applying this to the list. I will also add error handling of the save process.

Part 4 will then be to add the Action in NAV and hook that up to set the parameters in Excel.

I will still post the source of the original loosely coupled XMLHTTP based Edit In Excel, but I will not use it for anything.

To prepare ourselves for part 4 we need the following variables:

/// <summary>
/// Page which is going to be used for Edit In Excel
/// Customer, Vendor, Item, etc…
/// The card page for this record needs to be exposed as webservice with that name
/// </summary>
string page;

/// <summary>
/// The filters to apply (format: GETVIEW(TRUE))
/// Sample: “SORTING(No.) WHERE(Balance (LCY)=FILTER(>10,000))”
/// </summary>
string view;

These are the parameters, which we in part 4 will transfer values to Excel in – for now we will build the Spreadsheet to use those.

BTW – I changed the Project name from CustomerTemplate to NAVTemplate (actually I created a new project and copied over some of the files and changed the namespace).

Then I have moved the service connection initialization away from Load – and into Sheet_Startup, the new Sheet1_Startup code looks like this

private void Sheet1_Startup(object sender, System.EventArgs e)
{
switch (this.page)
{
case “Customer”:
this.service = new CustomerRef.Customer_Service();
break;
case “Vendor”:
this.service = new VendorRef.Vendor_Service();
break;
case “Item”:
this.service = new ItemRef.Item_Service();
break;
default:
MessageBox.Show(string.Format(“Page {0} is not setup for Edit In Excel. Please contact your system administrator”, this.page), “Microsoft Dynamics NAV”, MessageBoxButtons.OK, MessageBoxIcon.Error);
break;
}
if (this.service != null)
{
this.service.UseDefaultCredentials = true;
Load();
}
}

and I have added references to all 3 services.

This is the only place I have a switch on the page – the rest of the code is made to work with all – but wait… – how is that possible?

Service Connection classes code generated from Visual Studio doesn’t implement any common interface and we cannot change the code generated proxy classes (or rather – we don’t want to). We can, however, add something to the existing service. Looking at the code generated proxy class we will notice that the Customer_Service class is defined as a partial class – meaning that we can actually write another part of the class just by creating a new class (with the keyword partial)

Looking through my code I really need the Customer_Service to implement an interface like this:

public interface INAVService
{
bool UseDefaultCredentials {get; set; }
System.Net.ICredentials Credentials {get; set; }

    object[] ReadMultiple();
void Update(object obj);
void Create(object obj);
bool Delete(string key);

    Type GetFieldsType();
Type GetObjectType();

    void ClearFilters();
void AddFilter(string field, string criteria);
}

Some of these methods are already implemented by all Service Proxy classes and I use this to allow my code to look at the Service Connection via this interface only and the service variable I have in the sheet is actually type INAVService, flip side of this idea is, that for every new Page I want to add – I need to create a class like this:

public partial class Customer_Service : INAVService
{
List<Customer_Filter> filters;

    #region INAVService Members

    public object[] ReadMultiple()
{
return this.ReadMultiple(this.filters.ToArray(), null, 0);
}

    public void Update(object obj)
{
Customer customer = (Customer)obj;
this.Update(ref customer);
}

    public void Create(object obj)
{
Customer customer = (Customer)obj;
this.Create(ref customer);
}

    public Type GetObjectType()
{
return typeof(Customer);
}

    public Type GetFieldsType()
{
return typeof(Customer_Fields);
}

    public void ClearFilters()
{
this.filters = new List<Customer_Filter>();
}

    public void AddFilter(string field, string criteria)
{
Customer_Filter filter = new Customer_Filter();
filter.Field = (Customer_Fields)Enum.Parse(typeof(Customer_Fields), field, true);
filter.Criteria = criteria;
this.filters.Add(filter);
}

    #endregion
}

Not really nice – but it beats having a series of switch statements scattered around in the source files.

So, whenever we want to add a record object type, which we want to be able to Edit In Excel – we add a source file like this (search and replace Customer with <newtype>), we add an extra outcome in the switch statement above and we expose the page to Web Services in NAV 2009.

BTW – In my solution, I have added the classes to the solution in a folder called Services.

Applying Filters and Load

The Load method now looks like this:

/// <summary>
/// Load Records from NAV via Web Services
/// </summary>
private void Load()
{
PopulateFieldsCollection(this.service.GetObjectType(), this.service.GetFieldsType());
SetFilters(this.view);
this.objects = this.service.ReadMultiple();
PopulateDataTable();
AddDataToExcel();
}

Note that we ask the Service connection class for the Object Type, the Fields Enum Type and we call the ReadMultiple on the Service Connection (all through the interface we just implemented).

After generating fields collection and the DataTable we call SetFilters – which in effect just parses the view variable (sample: “SORTING(No.) WHERE(Balance (LCY)=FILTER(>10,000))” – without the quotes) and calls AddFilter a number of times (in the sample only once) on the Service Connection Interface.

I added a NAVFilterHelper static class with 3 static helper methods – GetBlock, WSName and VSName.

GetBlock parses the string for a block (a keyword followed by a parentheses with stuff in it) – SORTING(No.) is one and the WHERE clause is another. The FILTER  clause is another block inside the WHERE block.

WSName takes a name like “Balance (LCY)” and puts it through name mangling to get the Visual Studio generated identifier name (this is the name used in Enum – Balance_LCY)

VSName takes the enum identifier and removes special characters to get the Property name of the record object (there are no special characters in Balance_LCY)

Confused? – well look at this:

test  &&//(())==??++–**test – is a perfectly good (maybe stupid) field name in NAV

test__x0026__x0026___x003D__x003D__x003F__x003F__x002B__x002B___x002A__x002A_test is the same identifier in the xx_Fields enum (from the WSDL)

test___test is the same identifier as property in Visual Studio (the code generated proxy class)

and yes – you can generate fields, which will cause Web Services to fail. In fact, CTP4 (the US version) has an Option field in Customer and Vendor (Check Seperator), where the options causes Customer and Vendor to fail when exposed to Web Services. This special case is fixed for RTM – and the WSName in my sample contains the same name mangling as NAV 2009 RTM, but you can still create field names, which will end up having identical names in VS – and then your WebService proxy won’t work.

WSName and VSName works for my usage – they might not work for all purposes.

There is really nothing fancy about the SetFilters code, but it works for the purpose:

/// <summary>
/// Parse the view and apply these filters to the Service Connection
/// </summary>
/// <param name=”view”>View to parse (from AL: GETVIEW(TRUE))</param>
private void SetFilters(string view)
{
this.service.ClearFilters();
if (string.IsNullOrEmpty(view))
return;
string sorting = NAVFilterHelper.GetBlock(“SORTING”, ref view);
string where = NAVFilterHelper.GetBlock(“WHERE”, ref view);
do
{
int e = where.IndexOf(“=FILTER”);
if (e < 0)
break;
string field = NAVFilterHelper.WSName(where.Substring(0, e));
string criteria = NAVFilterHelper.GetBlock(“FILTER”, ref where);
this.service.AddFilter(field, criteria);

        if (where.StartsWith(“,”))
where.Remove(0, 1);
}
while (true);
}

Yes, yes – as you of course immediately spotted – this code doesn’t work if you have a field with =FILTER in the field name – so don’t!

The PopulateDataTable and AddDataToExcel methods haven’t changed.

Save

One thing we didn’t get done in part 2 was error handling. If anybody tried to modify a Location Code to an illegal Location Code and save it back to Excel – you will have noticed that Excel just ignored your request.

Reason for this is, that Excel swallows the Exception, and just ignores it.

So – I have changed the Save() method to:

/// <summary>
/// Save Changes to NAV via Web Service
/// </summary>
internal void Save()
{
if (DoSave())
{
Reload();
}
}

and then created the DoSave() – with most of the content from Save() – but refactored inside one loop with error handling (Abort, Retry, Ignore).

/// <summary>
/// Delete, Add and Update Records
/// </summary>
internal bool DoSave()
{
// Run through records marked for delete, create or modify
DataView dv = new DataView(this.dataTable, “”, “”, DataViewRowState.Deleted | DataViewRowState.Added | DataViewRowState.ModifiedCurrent);
foreach (DataRowView drv in dv)
{
bool retry;
do
{
retry = false;
try
{
if (drv.Row.RowState == DataRowState.Deleted)
{
object obj = GetRecordObject((string)drv[0]);
if (obj != null)
{
if (!service.Delete((string)drv[0]))
{
throw new Exception(string.Format(“Unable to delete record”));
}
}
}
else if (drv.Row.RowState == DataRowState.Added)
{
object obj = Activator.CreateInstance(this.service.GetObjectType());
foreach (NAVFieldInfo nfi in this.fields)
{
if (nfi.field != “Key”)
{
nfi.SetValue(obj, drv.Row[nfi.field]);
}
}
this.service.Create(obj);
}
else
{
object obj = GetRecordObject((string)drv[0]);
if (obj != null)
{
foreach (NAVFieldInfo nfi in this.fields)
{
if (nfi.field != “Key”)
{
nfi.SetValue(obj, drv[nfi.field]);
}
}
this.service.Update(obj);
}
}
}
catch (Exception e)
{
DialogResult reply = MessageBox.Show(string.Format(“{0} {1} {2}nn{3}”, this.dataTable.TableName, this.dataTable.Columns[1].Caption, drv[1].ToString(), e.Message), “Microsoft Dynamics NAV”, MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Error);
if (reply == DialogResult.Abort)
return false;
if (reply == DialogResult.Retry)
retry = true;
}
} while (retry);
}
return true;
}

oh yes, and beside that – you can see that I now use the methods on the Service Connection Interface directly and I do not use the type safe references to Customer – but instead just object. A couple of comments:

The DataView is the only way (I know of) that we can see which records have been deleted in the DataTable.

The Line

object obj = Activator.CreateInstance(this.service.GetObjectType());

does the same as using

object obj = new CustomerRef.Customer();

if the object type of the Service Connection is Customer.

So if you change the location code on a customer to something stupid you now get this error:

image

Of course it doesn’t make much sense to Retry this one. Abort aborts the save – and doesn’t reload. Ignore continues the Save and ends up reloading and you loose the changes, we couldn’t save. Note that Abort is a little strange – it cannot abort the changes that has happened up until you abort – and since it doesn’t reload, it leaves the spreadsheet in a bad state.

Maybe we should just remove the abort option – it just seems like a bad thing only to be able to retry or press ignore on every single record. If I come up with a better way of handling Abort, I will post that.

Can’t wait for part 4?

Part 4 is where we integrate this into NAV and create Actions to call out to open this Excel Spreadsheet on the Client and that includes changes in NAV, a Client Side COM object and a mechanism to transfer parameters to an Excel spreadsheet – stay tuned.

In my original Edit In Excel sample, I use XMLHTTP to retrieve page information and look through that – I will still post the original Edit In Excel source code with part 4 – but note, that there are a LOT of pitfalls in that – and, it doesn’t support Adding records or deleting records, and I have stopped working on that code, even though the need for using XMLHTTP might still be relevant.

The safer way is to use the sample solutions posted with this walk through.

BTW – the NAVTemplate solution can be downloaded here: http://www.freddy.dk/NAVTemplate.zip

Enjoy

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

4 BAT files for the Client Tier

If you haven’t done so, please read the post about Multiple Service Tiers before reading this:-)

In my previous post (Multiple Service Tier) I promised to talk about what to do on the Client Tier if you have installed a number of services on a Service Tier computer that all are set to start manually.

Do you really have to go to the Service Tier machine in order to start the services or is there a simpler way?

As you might have guessed – there is (else you wouldn’t be reading this post)

The SC command can actually start services on a different box with the syntax:

SC \machine start servicename

Assuming that we are using the CreateService.bat to create our Service Tier we know what the service name is based on the instance name and we should be able to create 4 BAT files, which enables us to:

1. Start Service Tiers
2. Stop Service Tiers
3. Restart Service Tiers
4. Start the Role Tailored Client using a specific Service Tier (and start the Service Tier if necessary)

The four scripts really makes things easier when dealing with multiple Service Tiers – and here they go…

StartService.bat

@ECHO OFF
IF “%1” == “” GOTO usage
SETLOCAL
SET SERVICETIER=%2
IF NOT “%SERVICETIER%” == “” SET SERVICETIER=\%SERVICETIER%
SET NAVPATH=%~dp0
SC %SERVICETIER% query MicrosoftDynamicsNavServer$%1 > nul
IF ERRORLEVEL 1 GOTO :eof
SC %SERVICETIER% query MicrosoftDynamicsNavServer$%1 | FINDSTR “RUNNING”
IF NOT ERRORLEVEL 1 GOTO :eof
SC %SERVICETIER% start MicrosoftDynamicsNavServer$%1
CALL “%NAVPATH%SLEEP.BAT” 3
SC %SERVICETIER% start MicrosoftDynamicsNavWS$%1
CALL “%NAVPATH%SLEEP.BAT” 3
GOTO :eof
:usage
ECHO.
ECHO Usage:
ECHO.
ECHO startservice instancename [servicetier]
ECHO.

SETLOCAL means that the changes we do to environment variables here are not reflected outside this .BAT file.
The .BAT files checks whether the service exists and whether it already has been started – if that is the case, there is no real reason for starting it.
Note BTW that you will need the Sleep.bat file described in the Multiple Service Tiers post here as well.

StopService.bat

@ECHO OFF
IF “%1” == “” GOTO usage
SETLOCAL
SET SERVICETIER=%2
IF NOT “%SERVICETIER%” == “” SET SERVICETIER=\%SERVICETIER%
SET NAVPATH=%~dp0
SC %SERVICETIER% query MicrosoftDynamicsNavServer$%1 > nul
IF ERRORLEVEL 1 GOTO :eof
SC %SERVICETIER% query MicrosoftDynamicsNavServer$%1 | FINDSTR “STOPPED”
IF NOT ERRORLEVEL 1 GOTO :eof
SC %SERVICETIER% stop MicrosoftDynamicsNavWS$%1
CALL “%NAVPATH%SLEEP.BAT” 3
SC %SERVICETIER% stop MicrosoftDynamicsNavServer$%1
CALL “%NAVPATH%SLEEP.BAT” 3
GOTO :eof
:usage
ECHO.
ECHO Usage:
ECHO.
ECHO stopservice instancename [servicetier]
ECHO.

Kind of the same story as StartService.bat – if the Service is stopped, there is no real reason to try and stop it.

RestartService.bat

You probably guessed by now what this .BAT file is doing – so no reason in explaining.

@ECHO OFF
IF “%1” == “” GOTO usage
SETLOCAL
SET SERVICETIER=%2
IF NOT “%SERVICETIER%” == “” SET SERVICETIER=\%SERVICETIER%
SET NAVPATH=%~dp0
SC %SERVICETIER% query MicrosoftDynamicsNavServer$%1 > nul
IF ERRORLEVEL 1 GOTO :eof
SC %SERVICETIER% query MicrosoftDynamicsNavServer$%1 | FINDSTR “STOPPED”
IF NOT ERRORLEVEL 1 GOTO dontstop
SC %SERVICETIER% stop MicrosoftDynamicsNavWS$%1
CALL “%NAVPATH%SLEEP.BAT” 3
SC %SERVICETIER% stop MicrosoftDynamicsNavServer$%1
CALL “%NAVPATH%SLEEP.BAT” 3
:dontstop
SC %SERVICETIER% start MicrosoftDynamicsNavServer$%1
CALL “%NAVPATH%SLEEP.BAT” 3
SC %SERVICETIER% start MicrosoftDynamicsNavWS$%1
CALL “%NAVPATH%SLEEP.BAT” 3
GOTO :eof
:usage
ECHO.
ECHO Usage:
ECHO.
ECHO restartservice instancename [servicetier]
ECHO.

The .BAT file more or less just does a StopService followed by a StartService.

RTC.bat

Now this is the fun stuff – this .BAT file starts the Role Tailored Client connecting to a specific Service Tier – but first it starts the Service Tier in question if it wasn’t already started.

@ECHO OFF
IF “%1” == “” GOTO usage
SETLOCAL
SET COMPANY=%3
REM IF ‘%COMPANY%’ == ” SET COMPANY=”CRONUS International Ltd.”
IF ‘%COMPANY%’ == ” SET COMPANY=”CRONUS USA, Inc.”
ECHO.%COMPANY%
SET COMPANY=%COMPANY:”=%
SET COMPANY=%COMPANY:,=#%
:again
SET BEFORE=%COMPANY%
FOR /F “tokens=1* delims= ” %%A IN (‘ECHO.%COMPANY%’) DO (
IF NOT “%%B” == “” SET COMPANY=%%A%%20%%B
)
IF NOT “%BEFORE%” == “%COMPANY%” GOTO again
SET COMPANY=%COMPANY:#=,%
SET MACHINE=%2
SET SERVICETIER=%MACHINE%
IF “%SERVICETIER%” == “” SET SERVICETIER=localhost
CALL STARTSERVICE.BAT %1 %SERVICETIER%
START dynamicsnav://%SERVICETIER%/%1/%COMPANY%/
GOTO :eof
:usage
ECHO.
ECHO Usage:
ECHO.
ECHO RTC instancename [servicetier] [“Company”]
ECHO.

As you can see in the usage section, RTC can be started specifying the Service Tier instance name, the Service Tier machine and the Company name.

Launching a Role Tailored Client pointing to the default installed Service Tier would be:

C:\Prog…60>RTC DynamicsNAV localhost “CRONUS International Ltd.”

or just

C:\Prog…60>RTC DynamicsNAV

because localhost and CRONUS are the default values for the 2nd and 3rd parameters. No real reason for that except for the fact that this has been working for me.

Note, that the FOR loop in the .BAT file replaces spaces in the COMPANY variable with %20. Another thing to notice is, that I replace commas with # before the loop (since commas will change the loop) and replace them back afterwards, meaning that you cannot have # in your Company names. You also cannot have ” in your company name.

If you have other special characters or if you do have # in your company names you would have to change the name mangling on the COMPANY name.

I use this .BAT file for a number of shortcuts on my desktop to launch Role Tailored Clients to specific Service Tiers and on my Service Tier box I run a command every night shutting down all my service Tiers:

for /f %D in (‘dir /ad/b’) do ( CALL STOPSERVICE.BAT %D )

The .BAT files can of course also be used on the Service Tier (except for the RTC one) for stopping and starting services – but they are a big help on the Client Tier – the way I am running things at least.

Once again – the people who has read all the way to the end will be able to download a ZIP file containing the .BAT files from http://www.freddy.dk/4ForTheClientTier.zip this ZIP file also contains a .BAT file called StopServices.bat (which actually only runs on the Service Tier = Stops all Running NAV Service Tiers).

I promise this is my last post with .BAT files:-)

Enjoy

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV