Microsoft Dynamics NAV

Team Blog

  • Microsoft Dynamics NAV Team Blog

    More Charts

    • 9 Comments

    This is a follow up to this post:

    Using Client Extensibility in NAV 2009 SP1 to create Charts

    News in this post are:

    2-series line charts. For example showing number of new Orders and Quotes for the last 10 days:

    2SeriesChart

    Item Sales Chart (basically identical to the Customer Sales Chart in the previous post):

    ItemSales

    Doughnut charts have been shined up a bit to make them look better:

    Don

    Double-click callback to NAV: When you double-click a chart, it will call the ControlAddin trigger. The example below will open up a list of Cust. Ledger Entries when double-clicking on a chart on the Customer Card page:

    Sales (LCY) - OnControlAddIn(Index : Integer;Data : Text[1024])
    CustLedgEntry.SETCURRENTKEY("Customer No.");
    CustLedgEntry.SETRANGE("Customer No.","No.");
    PAGE.RUN(0,CustLedgEntry);

    All you need is attached at the end of this post. It contains .dlls, including their source code, and a NAV codeunit. To implement some examples:

    Implement the charts add-on:

    1)  If you want to modify the attached c# project you must install "Microsoft Chart Controls Add-on for Microsoft Visual Studio 2008".

    2)  On any client machine where you want to run the charts, you must install "Microsoft Chart Controls for Microsoft .NET Framework 3.5".

    3)  Detach and unzip the attached file.

    4)  From the unzipped file, copy the files from \XTCharts\bin\Debug\ into the "Add-ins" sub folder of the RoleTailored Client (RTC).

    5)  And import the codeunit from XTChart.fob in Object Designer.

    6)  In a Classic Client, run table 2000000069 "Client Add-in" and enter one line (if it doesn't already exists from the previous post):

    Control Add-in Name:    XTChart
    Public Key Token:    d1a25808afd603da

    (making sure to have exactly these values, in the right case)

    Adding charts to pages:

    On the page where you want a chart, insert a new line of type Field. In the property "ControlAddIn", select the XTChart (XTChart;PublicKeyToken=d1a25808afd603da). This adds the control itself. To send data to the control you must specify SourceExpression too. Declare a Global variable called XTChartUtil, Type = Codeunit, Sub Type = Codeunit 75550 "XT Chart Util" to use in SourceExpression. If the chart is on a Customer Card page, then specify this SourceExpression:

    For a line-chart, showing "Sales (LCY)" for the last 6 months:  XTChartUtil.CustPointsChart(Rec)

    For a Doughnut chart, showing "Balance (LCY)" percentage of "Credit Limit": XTChartUtil.CustDoughnutChart(Rec)

    On an Item Card page, for a line chart showing Item "Sales (LCY)" for the last 6 months, have SourceExpressions XTChartUtil.ItemPointsChart(Rec)

    On the main Role Centre, to have a 2-series chart showing number of Quotes and Orders created in the past 10 days, I added the chart in Page 9060 with this SourceExpression: ChartUtil.SalesCueChart

    Then, for each example above, if you want to trigger any activity when the user double clicks the chart, just put some C/AL code on the corresponding OnControlAddIn-trigger.

    You can add charts in many other places, except for pages of the following types which cannot handle Extensibility components:

    • Under a Repeater Control (i.e. a list page)
    • Action Pane
    • Command Bar
    • Filter Pane

    Note:

    In the example here, I added a chart to the main Role Centre. In a production environment this is maybe not such a good idea, for these two reasons:

    1)  If a chart goes wrong and crashes, for example if a user has not installed the Chart Controls for .NET (link above), then it may crash the whole RTC. If this happens from some page which shows a chart, then at least the user knows that such and such page has a problem. If it happens on the Role Centre, then RTC will crash as soon as it is opened, and the user cannot use RTC at all, and it may not be obvious why RTC crashes.

    2)  Performance is not great in the SalesCueChart-example here, counting Sales Header records filtering on "Document Date". If a company has 1.000s of orders, then this would have some impact on performance. So, at least only calculate the chart on request, and not on the main page whether the user actually needs it or not.

    Development notes

    If you want to extend this to create new charts, then this is how the attached example works:

    Chart Data is generated in xml format in codeunit 75550 "XT Chart Util". It has a function for each type of chart. The xml data must have this format:

    - <Chart>

    <ChartType>1</ChartType> <-- Chart Type 1 = Lines, 2 = Doughnut and 3 = 3D lines

    - <Data Title="Orders"> <-- Title is optional. If specified, it will show as legend on the chart

    <REC1 Title="16/01/11">0</REC1> <-- Name the elements REC1, REC2, etc.

    <REC2 Title="17/01/11">2</REC2>

    <REC3 Title="18/01/11">0</REC3>

    <REC4 Title="19/01/11">2</REC4>

    </Data>

    - <Data2 Title="Quotes"> <-- Data2 section is optional. Used for making 2-series charts. If there is a Data2 section, then it must have the same number of elements as Data, and with the same names.

    <REC1 Title="16/01/11">0</REC1>

    <REC2 Title="17/01/11">0</REC2>

    <REC3 Title="18/01/11">0</REC3>

    <REC4 Title="19/01/11">1</REC4>

    </Data2>

    </Chart>

    In case of a Doughnut chart, just generate an xml document like this:

    - <Chart>

    <ChartType>2</ChartType>

    - <Data>

    <REC1 Title="Balance">9.20</REC1>

    <REC2 Title="CreditLimit">90.80</REC2>

    </Data>

    </Chart>

    with 2 REC-elements which total up to 100.

    The c#-code is all inside of one big try{} catch{} - structure. If anything goes wrong, then it will just show the data that was received in a MessageBox, but not what actually went wrong. Only if you remark the try{} and catch{} - section, it will throw the actual error message to tell you what went wrong (it should log it in the Application log). But the downside of this is, that RTC is also likely to crash.

    Lars Lohndorf-Larsen

    CSS EMEA

    This posting is provided "AS IS" with no warranties, and confers no rights

  • Microsoft Dynamics NAV Team Blog

    How to get a Dynamics NAV report with a Web Service

    • 9 Comments

    1. Create a new codeunit. In this scenario we will call this codeunit "CUWebReport" with ID 50000

    2. Navigate to "C/AL Globals" and create a function called "GenerateReport"

    image 

    3. Select "Locals"

    4. Select the "Return Value" tab

    5. Set "Return Type"=Text and "Length"=100

    image

    6. With this completed close "C/AL Locals" window.

    7. Now with "C/AL Globals" windows active again. Select "Variables" tab.

    8. Create a variable called "filename" with "Data Type"=Text and "Length"=100

    image

    9. Now let's add the following code to this codeunit:

    filename := 'C:\inetpub\PdfDocuments\';
    filename += FORMAT(CREATEGUID);
    filename := DELCHR(filename, '=', '{-}');
    filename += '.pdf';
    REPORT.SAVEASPDF(111,filename);
    EXIT(filename);

    image

    10. Save and compile the codeunit.

    11. Now it's time to expose this codeunit as Web Service. Navigate to "Administration/IT Administration/General Setup/Web Services"

    12. Select codeunit 50000 and give this a service name, we use "Get_PDF_Report"

    image

    13. Now it is time to verify that we can see this web service. Open this URL  http://localhost:7047/DynamicsNAV/WS/services.

    You should now see this message in your browser, and your Web Service can now be called :

    image

    If you don't see this message, you might want to check that  the service "Microsoft Dynamics NAV Business Web Services" has been started.

    14. Now it is time to call the Web Service, in this example we use Microsoft Visual Web Developer 2005. And we use the following code to call the Web Service:

    Partial Class _Default
        Inherits System.Web.UI.Page

        Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
            Dim service As localhost.Get_PDF_Report = New localhost.Get_PDF_Report()
            service.UseDefaultCredentials = True
            service.Url = "
    http://localhost:7047/DynamicsNAV/WS/CRONUS_International_Ltd/Codeunit/Get_PDF_Report"
            Response.ContentType = "application/pdf"
            Dim pdfFileName As String = service.GenerateReport()
            Response.TransmitFile(pdfFileName)

        End Sub
    End Class

    But how to consume this Web Service is not in scope for this blog, so we suggest you have look our online help how to consume a Web Service from NAV 2009.

    Online help found here: http://msdn.microsoft.com/en-us/library/dd339004.aspx

    15. Now also remember to impersonate your web servers application to an appropriate Dynamics NAV user.

    16. After compiling a running our project we then get this button in our browser.

    image

    17. When activating this button, we get the report specified in Codeunit 50000 displayed as an PDF file in our browser.

    image

    Conclusion, now you can give this URL to people who don't have access to Dynamics NAV, and they can execute the report when they see a need for this.

    Thanks,

    Torben Meyhoff, SDE & Claus Lundstrøm, Program Manager, Microsoft Dynamics NAV

  • Microsoft Dynamics NAV Team Blog

    Updated System Requirements for Microsoft Dynamics NAV 2015 Windows Client

    • 9 Comments

    The system requirements for Microsoft Dynamics NAV 2015 have been updated in the MSDN Library. In the original version, the section for the Windows client contain misleading information about the supported operating systems. This was a bug in the way that system requirements are gathered and tested, and we have changed the process accordingly. We are fully aware of the confusion that the misleading information about only supporting 64-bit editions has caused.

    The Microsoft Dynamics NAV 2015 Windows client supports both 32-bit and 64-bit editions of Windows 8.1, Windows 8, and Windows 7.

    As always, the latest version is in the MSDN Library at the following location: http://msdn.microsoft.com/en-us/library/dd301054(v=nav.80).aspx.

    Best regards,

    The NAV UA  team and the NAV Release Management team

  • Microsoft Dynamics NAV Team Blog

    Introducing the Microsoft Dynamics NAV Application Profiler

    • 9 Comments

    Did you ever wish you could monitor how your application code performs at real time? We have produced a sample and a video that can help you get started with C/AL tracing and application performance profiling.

    Get the Microsoft Dynamics NAV Application Profiler here, http://go.microsoft.com/fwlink/?LinkId=403898, and watch the video on how to install, configure and use this tool to enable C/AL code tracing and determine application performance during code execution here, http://go.microsoft.com/fwlink/?LinkId=403897.

     

    David Worthington and Dmytro Sitnik from the Dynamics NAV team

    Format: ???
    Duration: 7:00

  • Microsoft Dynamics NAV Team Blog

    Cumulative Update 7 for Microsoft Dynamics NAV 2013 R2 has been released

    • 9 Comments

    Cumulative update 7 includes all application and platform hotfixes and regulatory features that have been released for Microsoft Dynamics NAV 2013 R2. 

    The cumulative update includes hotfixes that apply to all countries and hotfixes specific to the following local versions:

    • AU - Australia
    • AT - Austria
    • BE - Belgium
    • CH - Switzerland
    • DE - Germany
    • DK - Denmark
    • ES - Spain
    • FI  - Finland
    • FR - France
    • IS - Iceland
    • IT - Italy
    • NA - North America
    • NL - Netherlands
    • NO - Norway
    • NZ - New Zealand
    • RU – Russia
    • SE - Sweden
    • UK - United Kingdom

    Where to find cumulative update 7

    You can download cumulative update 7 from KB 2964528 – Cumulative Update 7 for Microsoft Dynamics NAV 2013 R2 (Build 36703 - our apologies for originally posting the wrong build number).

    For a full list of all hotfixes included in cumulative updates for Microsoft Dynamics NAV 2013 R2, see the following CustomerSource and PartnerSource pages: 

    CustomerSource:

    PartnerSource

    More Information

    For more information about cumulative updates for Microsoft Dynamics NAV 2013 R2, see Announcement of update rollups for Microsoft Dynamics NAV 2013 R2

  • Microsoft Dynamics NAV Team Blog

    Microsoft Dynamics NAV: Faster than ever.

    • 9 Comments

     

    First, let me start by saying that based on the evidence so far: It is. Significantly.

    A number of the changes to the Dynamics NAV 2013 architecture contribute to the performance boosts that many of the test have shown. To outline some of the changes:

    • Middle tier is 64 bit
    • We are no longer using cursors but MARS
    • Ability to auto-update sift fields when looping, rather than repeating the process for each call. SETAUTOCALCFIELDS reduces the amount of statements issued
    • Pages containing flowfields now issue ‘SmartSQL’ queries, basically one source table query with outer joins for each flowfield query, again reducing the ‘chattiness’
      and calculating sift values in one server roundtrip
    • Global cache
    • REPEATABLEREAD is default isolation level
    • Locking changes in posting routines, locking at later point and reducing overall blocking time

    … and more.

    However(!), a few things have surfaced in the course of time that are not as explicitely documented as the changes above, nor as apparent, and might have unexpected side effects. I have collected some of the side effects that you might or not be aware of and that might leave you panicking if not certain what you’re facing.

     

    • Pages (with FlowFields) will in general run faster on Dynamics NAV 2013 than on NAV 2009 due to SmartSQL queries and reduced chattiness. Flow Fields on a page (all of them) are calculated in one go, which greatly reduces number of statements issued. But SmartSQL queries are not cached. Also, since we changed to MARS, SQL seems to be more sensitive to an index's column cardinality then before. You might experience that (depending on various factors) some queries that have run fine in the past now take much longer, this is due to a poor execution plan on SQL. With Dynamic cursors, query plan optimizer tended to optimize for the ORDER BY, while with MARS, SQL is free to choose.  It does a good job at it too, but in rare occasions might chose poorly. These cases are exceptions and contrary to the popular belief, this is not caused by the SmartSQL queries. These queries will have same poor execution plan and perform poorly even when isolated from the SmartsSQL query.

     

    Consider the following example. This is just an illustration of the problem, constructed on CRONUS extended database:

    A lot of Inventory transactions are posted through the Item Journal, generating a lot of Post cost to the G/L Entry. After this, when browsing an item list and opening an Item card, the page opens very slowly.

    After locating the offending query in the SQL profiler and running it isolated in Microsoft SQL Server Management Studio with ‘Include Actual Execution Plan’ option enabled, the plan looks similar to the one shown below:

     

     

    Each sub-query shows reasonably (small) percentage of cost and no apparent reason for bad execution plan. There are no obvious extreme costs, however there is a Clustered Index Scan here:

     

    Looking at the filter that SQL Server applies, namely it filters on “Post Value Entry to G_L”. “Item_No_” = “Item”.”No_”:


     

     

    Although SQL Server reports Operator Cost as small, it shows CPU Cost in excess of 2.3 in approx. 1.94 executions. So, it is likely scanning the table twice and unfortunately the table has 4,7 million rows.

    Although it is not obvious from the actual SQL Server execution plan that this is a problem, profiling with the SQL Server Profiler reports the query to use more than 5 seconds of CPU, while doing 131.519 reads to fetch 52 rows:

     
     

    The reason the Duration is on par with CPU Seconds is that all reads are logical from SQL Server Buffers. Re-issuing the query after adding the supporting index shows this in SQL Server Profiler:

     


    So Reads were reduced by a factor of 100 and (warm) duration was reduced by a factor of 40.

    As you can see, these poor execution plans are not caused by the SmartSQL. However the fact that the SmartSQL queries don’t cache their results will only amplify the issue. To solve it, we have to tackle the performance of that one isolated query by creating a covering index to improve the execution plan.

    And no, it won’t help to merely customize the page or change the visibility of the field. As long as it is contained in page metadata (so unless removed from page altogether), it will be calculated.

    So in short, if you do run into an issue of rather dramatic slowness of a page containing flowfields in Dynamics NAV 2013 or higher, isolating and testing the Flow Field queries separately (focusing on ones with clustered index scan, regardless of cost) should lead you to the culprit fairly quickly.  A supporting index should resolve the problem.

    • Temp tables: These are now moved to .NET. When you create a Temp record, your private C# TempTableDataProvider object is created. It will store whatever records you insert into a C# Dictionary, with the Primary Key Fields as the key for the Dictionary. The Dictionary is in memory and stores records in the order they
      are inserted.  When you add a SetCurrentKey, an AVLTree is built on the fly at first FindXX() you perform with that (current) key. In terms of performance, this is an expensive operation. This tree is in memory however, so it will be cached and reused later. If you issue a query without calling SetCurrentKey, an AVLTree for the primary key will be used.

    However, maintaining all this can consume quite a lot of memory on your middle tier, so plan for ample memory when scaling. Also, as mentioned above, querying temp tables is cached but sorting them is a fairly expensive operation, just something to keep in mind.

    • The next one is not strictly a performance issue, but can have a fairly drastic performance side-effect if you’re affected by it, so deserves to be mentioned:

    You might (or might not) be aware that the transaction scope and behavior of a page action has changed in Microsoft Dynamics NAV 2013 and higher. This will be especially significant if you are calling functions in codeunits (or objects other than the source table), passing the REC as a parameter and intend to lock the record in that function.

    Consider the following example: You have added an action that invokes a function in a codeunit, that in turn locks/ modifies the record (typically calling custom posting routine)

    So OnAction trigger code is, for example, as follows:

     

      PostingFunction(Rec);

     

    Where PostingFunction is a function (in any object other than the source table).

    Now, the consequence of the previously mentioned transaction scope change and the fact that you’re locking the record in the function, is that the entire source table (Rec) you passed as a parameter in the example above, is locked. In other words, you’re passing the current record as a parameter, but you are locking the whole table. The behavior would cause more damage than good to change at this point due to all functionality it would affect, so it won’t be changed, but fortunately – if
    you’re aware of this issue, the solution is very simple:

    SETSELECTINOFILTER(Rec);  //adding this line

    PostingFunction(Rec);

    Adding the line above should reduce the scope of locking and leave you with locking just the one record (or selection of records if on a list). This applies to card pages as well.

     

    • Last, but not least: you might have noticed increased Async_Network_IO waits. These cause quite a lot of concern out there. However, this is a symptom and not a problem per se.

    When Dynamics NAV issues a query that returns thousands of rows, SQL Server will start a work thread to return the result. If the application does not consume the rows as fast as they are delivered from SQL Server then the SQL Server work thread will have to wait and that shows up as wait type Network_Async_IO. If the application never consumes the entire result set then the worker thread will be hanging until the transaction is ended (when we close the statement), or if it was a read transaction, for a longer period either until the connection is closed (as idle) or if the statement is overwritten by another statement (statement cache is full).

    Example: when we do a FINDSET, a number of records (50, 100...) is retrieved. If it is a larger set it will run until all records are retrieved (or buffer is full), even if only first 10 are actually read by application. Eventually the session goes to sleep and after transaction ends sleeping sessions are removed. So in short, these are merely reflecting NAV data access methods, and are not a problem as such. If you want to reduce these, make sure you’re reading all the rows you’re asking for when using FINDSET, otherwise use FIND(‘-‘) or FIND(‘+’).

     

    With thanks to Jesper Falkebo and Jens Klarskov Jensen

     

    Jasminka Thunes

    Microsoft CSS

     

    These postings are provided "AS IS" with no
    warranties and confer no rights. You assume all risk for your use.

  • Microsoft Dynamics NAV Team Blog

    How to get back the 'hotfix directories' from NAV 2015 Cumulative Update 1

    • 8 Comments

    As you might have read from the comments on the Cumulative Update 1 for Microsoft Dynamics NAV 2015 has been released post, we are no longer providing the 'hotfix directories' in the hotfix download but some people have hinted that they have the need for them. We have created a PowerShell script that should give you back the same folder structure as was present before, and we are providing the source of the script, so that you can modify it to meet your naming needs.

    In order to use it:

    1. Copy the code below to a local file, and save the file as Copy-UpdateFilesToBatchDirectory.ps1.
    2. Unzip the DVD zip file from the Cumulative Update to a local folder.
    3. Run the script.
      1. Open the Microsoft Dynamics NAV 2015 Administration Shell as Administrator
      2. Run this command:
        Import-Module DIRECTORY\Copy-UpdateFilesToBatchDirectory.ps1
        where DIRECTORY is the location where you saved the .ps1 file.
      3. Run this command:
        Copy-UpdateFilesToBatchDirectory -DvdDirectory DVDDIRECTORY -BatchDirectory BATCHDIRECTORY
        where DVDDIRECTORY is the location where you unzipped the DVD from the update, and BATCHDIRECTORY is the location where you want to files to be copied to.
         
    4. It should take a couple of minutes to go through the process, but when done, you should get the same structure as you had before, located in the path you specified for -BatchDirectory

    Here is the script:

    function Copy-UpdateFilesToBatchDirectory
    {
        [CmdletBinding()]
        param (
            [parameter(Mandatory=$true)]
            [string] $DvdDirectory,

            [parameter(Mandatory=$true)]
            [string] $BatchDirectory
        )
        PROCESS
        {
            # Copy all the files from the DVD to a _TEMP folder
            Write-Verbose "Copying files from $DvdDirectory to $BatchDirectory\_Temp\..."
            New-Item -ItemType Directory -Force -Path $BatchDirectory\_Temp\RTC\Add-ins
            New-Item -ItemType Directory -Force -Path $BatchDirectory\_Temp\NST\Add-ins
            New-Item -ItemType Directory -Force -Path $BatchDirectory\_Temp\BPA
            New-Item -ItemType Directory -Force -Path $BatchDirectory\_Temp\"WEB CLIENT"
            New-Item -ItemType Directory -Force -Path $BatchDirectory\_Temp\OUTLOOK
            New-Item -ItemType Directory -Force -Path $BatchDirectory\_Temp\ADCS
            New-Item -ItemType Directory -Force -Path $BatchDirectory\_Temp\HelpServer
            New-Item -ItemType Directory -Force -Path $BatchDirectory\_Temp\UpgradeToolKit
            New-Item -ItemType Directory -Force -Path $BatchDirectory\_Temp\WindowsPowerShellScripts
            Copy-Item $DvdDirectory\"RoleTailoredClient\program files\Microsoft Dynamics NAV\80\RoleTailored Client"\* -destination $BatchDirectory\_Temp\RTC -recurse -Force
            Copy-Item $DvdDirectory\"ServiceTier\program files\Microsoft Dynamics NAV\80\Service"\* -destination $BatchDirectory\_Temp\NST -recurse -Force
            Copy-Item $DvdDirectory\BPA\* -destination $BatchDirectory\_Temp\BPA -recurse -Force
            Copy-Item $DvdDirectory\"WebClient\Microsoft Dynamics NAV\80\Web Client"\* -destination $BatchDirectory\_Temp\"WEB CLIENT" -recurse -Force
            Copy-Item $DvdDirectory\"Outlook\program files\Microsoft Dynamics NAV\80\OutlookAddin"\* -destination $BatchDirectory\_Temp\OUTLOOK -recurse -Force
            Copy-Item $DvdDirectory\"ADCS\program files\Microsoft Dynamics NAV\80\Automated Data Capture System"\* -destination $BatchDirectory\_Temp\ADCS -recurse -Force
            Copy-Item $DvdDirectory\"HelpServer\DynamicsNAV80Help"\* -destination $BatchDirectory\_Temp\HelpServer -recurse -Force
            Copy-Item $DvdDirectory\"UpgradeToolKit"\* -destination $BatchDirectory\_Temp\UpgradeToolKit -recurse -Force
            Copy-Item $DvdDirectory\"WindowsPowerShellScripts"\* -destination $BatchDirectory\_Temp\WindowsPowerShellScripts -recurse -Force
            Write-Verbose "Done copying files RTC files from $DvdDirectory to $BatchDirectory\_Temp."

            # Delete files that are not needed for an installation scenario
            Write-Verbose "Deleting files from $BatchDirectory that are not needed for the batch directory..."

            Get-ChildItem $BatchDirectory\_Temp -include '*.etx' -Recurse | Remove-Item -force -ErrorAction SilentlyContinue
            Get-ChildItem $BatchDirectory\_Temp -include '*.stx' -Recurse | Remove-Item -force -ErrorAction SilentlyContinue
            Get-ChildItem $BatchDirectory\_Temp -include '*.chm' -Recurse | Remove-Item -force -ErrorAction SilentlyContinue
            Get-ChildItem $BatchDirectory\_Temp -include '*.hh'  -Recurse | Remove-Item -force -ErrorAction SilentlyContinue
            Get-ChildItem $BatchDirectory\_Temp -include '*.config'  -Recurse | Remove-Item -force -ErrorAction SilentlyContinue
            Get-ChildItem $BatchDirectory\_Temp -include '*.stx'  -Recurse | Remove-Item -force -ErrorAction SilentlyContinue
            Get-ChildItem $BatchDirectory\_Temp -include '*.etx'  -Recurse | Remove-Item -force -ErrorAction SilentlyContinue
            Get-ChildItem $BatchDirectory\_Temp -include '*.ico'  -Recurse | Remove-Item -force -ErrorAction SilentlyContinue
            Get-ChildItem $BatchDirectory\_Temp -include '*.flf'  -Recurse | Remove-Item -force -ErrorAction SilentlyContinue
            Get-ChildItem $BatchDirectory\_Temp -include '*.sln'  -Recurse | Remove-Item -force -ErrorAction SilentlyContinue
            RemoveUnnecessaryClickOnceDirectory (Join-Path $BatchDirectory\_Temp\RTC\ 'ENU')
            RemoveUnnecessaryClickOnceDirectory (Join-Path $BatchDirectory\_Temp\RTC\ 'en-US')
            RemoveUnnecessaryClickOnceDirectory (Join-Path $BatchDirectory\_Temp\RTC\ 'Images')
            RemoveUnnecessaryClickOnceDirectory (Join-Path $BatchDirectory\_Temp\RTC\ 'SLT')
            RemoveUnnecessaryClickOnceDirectory (Join-Path $BatchDirectory\_Temp\RTC\ 'ReportLayout')
            RemoveUnnecessaryClickOnceDirectory (Join-Path $BatchDirectory\_Temp\BPA\ 'Scripts')
            RemoveUnnecessaryClickOnceDirectory (Join-Path $BatchDirectory\_Temp\"WEB CLIENT"\ 'Resources')
            RemoveUnnecessaryClickOnceDirectory (Join-Path $BatchDirectory\_Temp\HelpServer\ 'css')
            RemoveUnnecessaryClickOnceDirectory (Join-Path $BatchDirectory\_Temp\HelpServer\ 'help')
            RemoveUnnecessaryClickOnceDirectory (Join-Path $BatchDirectory\_Temp\HelpServer\ 'images')
            RemoveUnnecessaryClickOnceDirectory (Join-Path $BatchDirectory\_Temp\WindowsPowerShellScripts\ 'ApplicationMergeUtilities')
            Write-Verbose "Done deleting files from $BatchDirectory that are not needed for for the batch directory."

            # Copy the result to the requested directory and remove the _Temp folder
            Copy-Item $BatchDirectory\_Temp\* -destination $BatchDirectory\ -recurse -Force
            RemoveUnnecessaryClickOnceDirectory (Join-Path $BatchDirectory\ '_Temp')
        }
    }

    function RemoveUnnecessaryClickOnceDirectory
    {
        param ([string]$directory)
        Remove-Item $directory -force -Recurse -ErrorAction SilentlyContinue
    }

     

    Hope this helps,

    Jorge

  • Microsoft Dynamics NAV Team Blog

    Extensibility for the Microsoft Dynamics NAV Tablet Client

    • 8 Comments

    Get in touch

    With Microsoft Dynamics NAV 2015, you will be able to run your Microsoft Dynamics NAV application on tablets. The touch interface on these devices opens for a few new cool scenarios. One of the obvious usage of touch is to allow users to write directly on the tablet, for example to sign documents.

    In this blog post, I will walk you through how to develop a client control add-in with JavaScript that you will be able to add to any Microsoft Dynamics NAV page. This add-in shows a box in which the user can write with a tablet pen or just with his finger. It also demonstrates how to save the image into a Microsoft Dynamics NAV table as a BLOB.

    If you are not familiar with JavaScript client add-ins or if you just need a refresher, take a look at this walkthrough for your classic ‘Hello World’ example.

    I am referring to this add-in as the ‘Signature Add-in’ and to the graphical data as ‘the signature’, but it could really be any type of hand-drawn graphics.

    So, let’s get started.

    Creating the C# class library

    In Visual Studio, create a new C# class library project and add a reference to the Microsoft.Dynamics.Framework.UI.Extensibility.dll assembly. You will find this assembly in a directory similar to C:\Program Files (x86)\Microsoft Dynamics NAV\80\RoleTailored Client.

    If you are already familiar with Microsoft Dynamics NAV HTML/JavaScript add-ins, you know that the purpose of this class library is merely to specify the interface and make the C/AL compiler happy. It does not contain any actual executing code.

    On the server side, besides the usual AddInReady event, we will need two more events; one to write the signature data: the SaveSignature and one to read the signature from the Microsoft Dynamics NAV table to trigger an update on the page; the UpdateSignature.

    On the client side, that is in the JavaScript code, we also need a method to actually draw the graphics and we also want to be able to clear the content.

    To specify this API, create a single public interface looking like this:

     

    namespace SignatureAddIn

    {

        using Microsoft.Dynamics.Framework.UI.Extensibility;

     

        /// <summary>

        /// Interface definition for the signature add-in.

        /// </summary>

        [ControlAddInExport("SignatureControl")]

        public interface ISignatureAddIn

        {

            [ApplicationVisible]

            event ApplicationEventHandler AddInReady;

     

            [ApplicationVisible]

            event ApplicationEventHandler UpdateSignature;

           

            [ApplicationVisible]

            event SaveSignatureEventHandler SaveSignature;

     

            [ApplicationVisible]

            void ClearSignature();

     

            [ApplicationVisible]

            void PutSignature(string signatureData);

        }

     

        public delegate void SaveSignatureEventHandler(string signatureData);

    }

    Notice that the SaveSignatureEventHandler delegate takes a string parameter, which will contain the actual serialized data representing the image.

    Build your assembly to make sure you did not forget a semi-colon somewhere.

    Next, you will need to sign your assembly, obtain its public key token and copy it to the client add-ins folder. To do that, follow the steps as described in the walkthrough.

     

    Creating the manifest file

    In the manifest of an add-in, which is just regular XML file, we specify the resources that the control will use. The client side code consists of one single JavaScript file signature.js and use a single CSS file to style the HTML. We will also add a call to an initialization method in our script. The manifest is a good place to do that as the framework ensures that it gets called only when the browser is ready.

    That makes our manifest look like this:

    <?xml version="1.0" encoding="utf-8" ?>

    <Manifest>

      <Resources>

        <Script>signature.js</Script>

        <StyleSheet>signature.css</StyleSheet>

      </Resources>

      <ScriptUrls>

      </ScriptUrls>

      <Script>

          <![CDATA[

              init();

          ]]>

      </Script>

     

      <RequestedHeight>200</RequestedHeight>

      <RequestedWidth>700</RequestedWidth>

      <VerticalStretch>false</VerticalStretch>

      <HorizontalStretch>false</HorizontalStretch>

    </Manifest>

     

    Creating the CSS file

    No big deal here, just create a file named signature.css (the name needs to match the one in the manifest) with the following content:

     

    .signatureArea {

        width: 300px;

    }

     

    .signatureCanvas {

        border: solid;

        border-width: 1px;

        border-color: #777777;  

        background-color: #fff;

        width: 100%;

    }

     

    .signatureButton {

      width: 100px;

      height: 40px;

      color: white;

      background-color: #666666;

      font-size: 12pt;

      outline: 0;

      border-color: white;

    }

    Feel free to play with the styles, this will only affect your add-in and will not affect the Microsoft Dynamics NAV pages whatsoever.

    The interesting part

    All of what has been described so far is boilerplate stuff, which you will have to do for any Microsoft Dynamics NAV HTML client add-in. We are now getting to the interesting piece, which is the JavaScript code.

    Create a file named signature.js. Again here, the name has to match the one you declared in the manifest.

    Let’s start with the implementation of the interface contract that we previously defined in the C# class library:

    var signature;

     

    function init() {

     

        signature = new ns.SignatureControl();

        signature.init();

        RaiseAddInReady();

    }

     

     

    // Event will be fired when the control add-in is ready for communication through its API.

    function RaiseAddInReady() {

        Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('AddInReady');

    }

     

    // Event raised when the update signature has been called.

    function RaiseUpdateSignature() {

        Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('UpdateSignature');

    }

     

    // Event raised when the save signature has been called.

    function RaiseSaveSignature(signatureData) {

        Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('SaveSignature', [signatureData]);

    }

     

     

    function PutSignature(signatureData) {

        signature.updateSignature(signatureData);

    }

     

    function ClearSignature() {

        signature.clearSignature();

    }

     

    As you can see the SignatureControl object in the ns namespace is doing all the work, so let’s take a closer look at it.

    (function (ns) {

     

        ns.SignatureControl = function () {

            var canvas,

                ctx;

     

            function init() {

                createControlElements();

                wireButtonEvents();

                wireTouchEvents();

                ctx = canvas.getContext("2d");

            }

     

         …

    Here we declare the SignatureControl class in the ns namespace and the init()method. The createControlElements() creates the various HTML elements that the control is made of.

           function createControlElements() {

                var signatureArea = document.createElement("div"),

                    canvasDiv = document.createElement("div"),

                    buttonsContainer = document.createElement("div"),

                    buttonClear = document.createElement("button"),

                    buttonAccept = document.createElement("button"),

                    buttonDraw = document.createElement("button");

     

                canvas = document.createElement("canvas"),

                canvas.id = "signatureCanvas";

                canvas.clientWidth = "100%";

                canvas.clientHeight = "100%";

                canvas.className = "signatureCanvas";

     

                buttonClear.id = "btnClear";

                buttonClear.textContent = "Clear";

                buttonClear.className = "signatureButton";

     

                buttonAccept.id = "btnAccept";

                buttonAccept.textContent = "Accept";

                buttonAccept.className = "signatureButton";

     

                buttonDraw.id = "btnDraw";

                buttonDraw.textContent = "Draw";

                buttonDraw.className = "signatureButton";

     

                canvasDiv.appendChild(canvas);

                buttonsContainer.appendChild(buttonDraw);

                buttonsContainer.appendChild(buttonAccept);

                buttonsContainer.appendChild(buttonClear);

     

                signatureArea.className = "signatureArea";

                signatureArea.appendChild(canvasDiv);

                signatureArea.appendChild(buttonsContainer);

     

                document.getElementById("controlAddIn").appendChild(signatureArea);

            }

    Besides plain old divs and buttons, the canvas is where we will actually be able to draw. Canvas has been supported in most browsers for a while and you can read more about it here.

    The control has three buttons. One to accept the signature, which will save it to the database, one to clear the field and one to redraw the signature from the database, mostly for test purposes, as you would probably not need it in most real-life scenarios. Let’s wire these buttons so do something useful:

    function wireButtonEvents() {

        var btnClear = document.getElementById("btnClear"),

            btnAccept = document.getElementById("btnAccept"),

            btnDraw = document.getElementById("btnDraw");

     

        btnClear.addEventListener("click", function () {

            ctx.clearRect(0, 0, canvas.width, canvas.height);

        }, false);

     

        btnAccept.addEventListener("click", function () {

            var signatureImage = getSignatureImage();

            ctx.clearRect(0, 0, canvas.width, canvas.height);

            RaiseSaveSignature(signatureImage);

        }, false);

     

        btnDraw.addEventListener("click", function () {

            RaiseUpdateSignature();

        }, false);

    }

    Notice that we use the drawing context ctx, that we obtained during initialization to clear the content of the canvas. We will see what the getSignatureImage() exactly does to obtain the data in a sec but before that let’s wire the touch events.

    The touch events

    In order to be able draw, we want to react to touch events. In this example, we also hook up mouse events, which is convenient if you want to test your add-in on a non-touch device with an old-fashioned mouse.

    function wireTouchEvents() {

        canvas.addEventListener("mousedown", pointerDown, false);

        canvas.addEventListener("touchstart", pointerDown, false);

        canvas.addEventListener("mouseup", pointerUp, false);

        canvas.addEventListener("touchend", pointerUp, false);

    }

    As you can see, touchstart is the equivalent of a mousedown, while a touchend is the counterpart of a mouseup.

    Once we have detected a touchstart, the trick is to start listening to touchmove and draw in the canvas to the current position of the ‘touching’. Once we get a touchend, we will then stop the listening and the drawing:

    function pointerDown(evt) {

        ctx.beginPath();

        ctx.moveTo(evt.offsetX, evt.offsetY);

        canvas.addEventListener("mousemove", paint, false);

        canvas.addEventListener("touchmove", paint, false);

    }

     

    function pointerUp(evt) {

        canvas.removeEventListener("mousemove", paint);

        canvas.removeEventListener("touchmove", paint);

        paint(evt);

    }

     

    function paint(evt) {

        ctx.lineTo(evt.offsetX, evt.offsetY);

        ctx.stroke();

    }

    Canvas image data

    We want to be able to serialize and de-serialize the image data from the canvas, so we can send it back and forth to the server in a string. The HTML canvas has built-in functionalities to do that through the context:

    function updateSignature(signatureData) {

        var img = new Image();

        img.src = signatureData;

        ctx.clearRect(0, 0, canvas.width, canvas.height);

        ctx.drawImage(img, 0, 0);

    }

     

    function getSignatureImage() {

        return canvas.toDataURL();

    }

     

    function clearSignature() {

        ctx.clearRect(0, 0, canvas.width, canvas.height);

    }

     

    return {

        init: init,

        updateSignature : updateSignature,

        getSignatureImage: getSignatureImage,

        clearSignature: clearSignature

    };

     

    The toDataURL() method converts the image into a (rather long) URL encoded string containing all the pixels. To convert it back, we only need to create an image and set its src property to this URL encoded string and pass this image to the method drawImage on the canvas context. This is pretty convenient as it allows us to use a simple string rather than more complex data structure such as arrays.

    We are now done with the JavaScript part and the entire file looks like this:

    var signature;

     

    function init() {

        signature = new ns.SignatureControl();

        signature.init();

        RaiseAddInReady();

    }

     

    // Event will be fired when the control add-in is ready for communication through its API.

    function RaiseAddInReady() {

        Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('AddInReady');

    }

     

    // Event raised when the update signature has been called.

    function RaiseUpdateSignature() {

        Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('UpdateSignature');

    }

     

    // Event raised when the save signature has been called.

    function RaiseSaveSignature(signatureData) {

        Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('SaveSignature', [signatureData]);

    }

     

     

    function PutSignature(signatureData) {

        signature.updateSignature(signatureData);

    }

     

    function ClearSignature() {

        signature.clearSignature();

    }

     

    (function (ns) {

     

        ns.SignatureControl = function () {

            var canvas,

                ctx;

     

            function init() {

                createControlElements();

                wireButtonEvents();

                wireTouchEvents();

                ctx = canvas.getContext("2d");

            }

     

            function createControlElements() {

                var signatureArea = document.createElement("div"),

                    canvasDiv = document.createElement("div"),

                    buttonsContainer = document.createElement("div"),

                    buttonClear = document.createElement("button"),

                    buttonAccept = document.createElement("button"),

                    buttonDraw = document.createElement("button");

     

                canvas = document.createElement("canvas"),

                canvas.id = "signatureCanvas";

                canvas.clientWidth = "100%";

                canvas.clientHeight = "100%";

                canvas.className = "signatureCanvas";

     

                buttonClear.id = "btnClear";

                buttonClear.textContent = "Clear";

                buttonClear.className = "signatureButton";

     

                buttonAccept.id = "btnAccept";

                buttonAccept.textContent = "Accept";

                buttonAccept.className = "signatureButton";

     

                buttonDraw.id = "btnDraw";

                buttonDraw.textContent = "Draw";

                buttonDraw.className = "signatureButton";

     

                canvasDiv.appendChild(canvas);

                buttonsContainer.appendChild(buttonDraw);

                buttonsContainer.appendChild(buttonAccept);

                buttonsContainer.appendChild(buttonClear);

     

                signatureArea.className = "signatureArea";

                signatureArea.appendChild(canvasDiv);

                signatureArea.appendChild(buttonsContainer);

     

                document.getElementById("controlAddIn").appendChild(signatureArea);

            }

     

            function wireTouchEvents() {

                canvas.addEventListener("mousedown", pointerDown, false);

                canvas.addEventListener("touchstart", pointerDown, false);

                canvas.addEventListener("mouseup", pointerUp, false);

                canvas.addEventListener("touchend", pointerUp, false);

            }

     

     

            function pointerDown(evt) {

                ctx.beginPath();

                ctx.moveTo(evt.offsetX, evt.offsetY);

                canvas.addEventListener("mousemove", paint, false);

                canvas.addEventListener("touchmove", paint, false);

            }

     

            function pointerUp(evt) {

                canvas.removeEventListener("mousemove", paint);

                canvas.removeEventListener("touchmove", paint);

                paint(evt);

            }

     

            function paint(evt) {

                ctx.lineTo(evt.offsetX, evt.offsetY);

                ctx.stroke();

            }

     

            function wireButtonEvents() {

                var btnClear = document.getElementById("btnClear"),

                    btnAccept = document.getElementById("btnAccept"),

                    btnDraw = document.getElementById("btnDraw");

     

                btnClear.addEventListener("click", function () {

                    ctx.clearRect(0, 0, canvas.width, canvas.height);

                }, false);

     

                btnAccept.addEventListener("click", function () {

                    var signatureImage = getSignatureImage();

                    ctx.clearRect(0, 0, canvas.width, canvas.height);

                    RaiseSaveSignature(signatureImage);

                }, false);

     

                btnDraw.addEventListener("click", function () {

                    RaiseUpdateSignature();

                }, false);

            }

     

            function updateSignature(signatureData) {

                var img = new Image();

                img.src = signatureData;

                ctx.clearRect(0, 0, canvas.width, canvas.height);

                ctx.drawImage(img, 0, 0);

            }

     

            function getSignatureImage() {

                return canvas.toDataURL();

            }

     

            function clearSignature() {

                ctx.clearRect(0, 0, canvas.width, canvas.height);

            }

     

            return {

                init: init,

                updateSignature : updateSignature,

                getSignatureImage: getSignatureImage,

                clearSignature: clearSignature

            };

        };

    })(this.ns = this.ns || {});

    Packaging your add-in

    Now that we have all the parts of the component, we need to zip it together and import it in Microsoft Dynamics NAV. This is again as you would do for any other add-in.

    Create a zip file with the following structure:

     

    Put the manifest at the root, the JavaScript file in the script folder and the CSS file in the Stylesheet folder.

    Open any of the Microsoft Dynamics NAV clients (Windows, Web or Tablet) and go to the Control Add-ins page. Create a new entry named SignatureControl and enter the public key token that you saved earlier. Import the zip file.

     

    The C/SIDE side of things

    Now that our add-in is sitting comfortably within the confines of the Microsoft Dynamics NAV database, we need to add it to page. But before that, we want a place to save the signature image data. In this fabricated example, I will add the signature to the Sales Invoice card page from the Mini app (1304) which is based on the Sales Header table.

    1. In Object Designer, open the Sales Header table and add BLOB field called ‘SignatureImage’.
    2. Add the actual control page by opening page 1304 and add the control into a separate group.

    3.  

    By now you should be able to fire up this page and see how our control looks like. To do that open the client of your choice in the mini app. Navigate to the Sales Invoices and open the Sales Invoice card page.

    You should see the signature control. Try to draw in with the mouse or with your finger if you are on a touch enabled device.

    Even the clear button works already and allows you to delete your doodles.

    The last part that we are missing is to save and retrieve the pixels to the Microsoft Dynamics NAV database. To do that we need to write a bit of C/AL code.

    The C/AL code

    If you recall how we defined the add-in interface, we have three triggers to take care of: AddInReady, UpdateSignature and SignatureSaved.

    Nothing surprising here. The really interesting methods are SaveSignature and GetDataUriFromImage.

    This is where the conversion from between the URL encoded image string and a Microsoft Dynamics NAV BLOB occurs.

    The most convenient way to do this is to use the power of .NET for regular expressions matching and memory streams.

    So, let’s create a SaveSignature method and add the following .NET type variables to the locals:

    The URL encoded representation of the image contains some goo around the actual pixel information. With .NET regular expressions, we strip the header by matching it and preserving the rest.

    What is left is a base 64 encoded string, which we can convert to a byte array using the .net Convert utility class. We then pass it to the memory stream and save it to the Microsoft Dynamics NAV table as a BLOB.

    Obtaining the encoded URI is obviously the reverse operation. This is somewhat simpler; after reading the BLOB, we just need to re-add the header.

    Finally, we want to update the drawing, when we navigate the records:

    That’s it!

    Now you should be able to save the graphics and when you close and re-open the page or navigate through the Sales Invoices, the picture gets updated accordingly.

    Even though the most obvious usage scenarios are on the tablet, this add-in works on all three clients (Windows, Web and Tablet). 

    NOTE: To copy the code samples, see Extensibility for the Microsoft Dynamics NAV Tablet Client on MSDN.

  • Microsoft Dynamics NAV Team Blog

    Announcing Microsoft Dynamics NAV for Tablets

    • 8 Comments

    You can now experience the power and simplicity of Dynamics NAV on your tablet, whether you own an iPad, Android or Windows device!

    For Customers

    Download and install the Dynamics NAV app from the Windows Store, App Store or Google Play. You sign in using your usual credentials and the app connects to your Dynamics NAV 2015 server, on premise or in the cloud.

    Navigate your business data using a fast and fluid interface which leverages design concepts from modern Windows and Office 365, whilst remaining distinctly Dynamics NAV. Do more on the go with capabilities such as send to Excel or Office 365, up-to-date charts and KPIs, emailing of sales quotes and invoices, and shoot and attach pictures with your camera.

    Your executives, sales field, service technicians, warehouse workers and many more can now get access to the data they need from the device they prefer. Microsoft Dynamics NAV 2015 comes ready for small businesses with the Small Business for Tablets role. For midsized businesses seeking custom solutions for various roles, talk to your Partner.

    For Partners

    Almost all content within the app consists of application pages coming from your Dynamics NAV 2015 server, and they are rendered using our new Tablet client, designed exclusively for touch. 

    This makes development for tablets similar to development for any other Microsoft Dynamics NAV client! Create new pages or customize existing pages in the Microsoft Dynamics NAV Development Environment on your workstation, and it is ready for iOS, Android, and Windows. Test your role-tailored solution directly on your workstation using the Tablet client running in the browser. You don’t even need to go out and purchase a bunch of tablets of different sizes, versions and platforms. 

    Since the Tablet client is based on Web client technology, you get to reuse your investments in application objects, business logic, and modern client add-ins, and you administer, license, secure, and maintain it in precisely the same way.

    Learn More

    Get the app today

     

    Download on the App Store
    Download from Windows Store
      Get in on Google Play

    Google Play is a trademark of Google Inc.
    Apple and the Apple logo are trademarks of Apple Inc., registered in the U.S. and other countries. App Store is a service mark of Apple Inc.

    Best regards,
    The Dynamics NAV team

  • Microsoft Dynamics NAV Team Blog

    General Availability of Microsoft Dynamics NAV 2015

    • 8 Comments

    We are happy to announce that Microsoft Dynamics NAV 2015 is now available for download and for requesting licenses for new customers. This release includes improved support for RapidStart code upgrade and data upgrade, ease of use with user experience enhancements and new apps for tablet devices, new document reports, and cash management.

    At Directions US, we presented Microsoft Dynamics NAV 2015 to 600 partner representatives, and we look forward to presenting to another 1100 or so at Directions EMEA next week. Visit the readiness page to access training and resources about Microsoft Dynamics NAV 2015, and download the product here. For more information, see the Microsoft Dynamics NAV Help in the MSDN Library.

    Best regards,

    The Dynamics NAV team

  • Microsoft Dynamics NAV Team Blog

    How to Compile a Database Twice as Fast (or faster)

    • 8 Comments

    Compiling a complete NAV database can take quite a while. Even on powerful development machines with a lot of CPU cores this is still the case - the development environment wasn't designed for the multi-core era. The only way to speed things up is to use separate instances of the development environment and have each compile a (distinct) subset of the objects in the database. With the new Development Environment Commands for PowerShell that were included in the Microsoft Dynamics NAV 2015 Development Shell, this has become a lot easier.

    Before heating up those cores, let's first introduce the command that we need for this: Compile-NAVApplicationObject. In the following example we'll assume that the database and Development Shell reside on the same machine. To compile all non-compiled objects in a database named the command simply takes a parameter that specifies the database (i.e., MyApp) and optionally if and how schema changes should be synchronized:

    Compile-NAVApplicationObject -DatabaseName MyApp -SynchronizeSchemaChanges No

    To compile all objects in a database regardless their current compiled state use the Recompile switch:

    Compile-NAVApplicationObject -DatabaseName MyApp -SynchronizeSchemaChanges No -Recompile

    The command also takes a filter, e.g.:

    Compile-NAVApplicationObject -DatabaseName MyApp -Filter ID=1..100

    compiles all non-compiled objects with an ID in the range 1 to 100.

    Now to parallelize the compilation process we need to partition the set of objects in distinct sets that can be compiled in parallel. The most straightforward way to do this is based on object type. For each object type we can start a compilation job using the AsJob parameter. Using this parameter an instance of the development environment is started in the background and a handle to this background job is returned. PowerShell comes with a set of commands to work with jobs, for instance, to get the output of a job (Receive-Job) or to wait for a job to finish (Wait-Job). Occasionally, race conditions could occur while updating the object table. As a result, some objects may fail to compile. Therefore, after all background jobs have completed we compile all non-compiled objects in a final sweep. This is all we need to understand the following function that compiles all objects in a database in 7 parallel processes:

    function ParallelCompile-NAVApplicationObject
    (
    [Parameter(Mandatory=$true)]
    $DatabaseName
    )
    {
    $objectTypes = 'Table','Page','Report','Codeunit','Query','XMLport','MenuSuite'
    $jobs = @()
    foreach($objectType in $objectTypes)
    {
    $jobs += Compile-NAVApplicationObject $DatabaseName -Filter Type=$objectType -Recompile -SynchronizeSchemaChanges No -AsJob
    }

    Receive-Job -Job $jobs -Wait
    Compile-NAVApplicationObject $DatabaseName -SynchronizeSchemaChanges No
    }

      
    Just for fun, let's measure the performance gain. We can do this using Measure-Command:

    Measure-Command { Compile-NAVApplicationObject MyApp -SynchronizeSchemaChanges No -Recompile }
    Measure-Command { ParallelCompile-NAVApplicationObject MyApp } 

     

    These two screenshots from task manager illustrate the difference in CPU utilization: while running the non-parallel version CPU utilization is hovering around 40%; while running the parallel version CPU utilization is maxed out at 100%.

                 

                        

    On my laptop (with 2 CPU cores) compilation of the 4148 objects in the W1 application takes 8 minutes and 46 seconds using the non-parallel version; using the parallel version it takes only 4 minutes and 32 seconds. Machines with more CPU cores may produce even better results. Note that when parallelizing this way (i.e., based on object type) only four processes are active most of the time - compilation of Queries, XMLports and MenuSuites finishes relatively quick. So if you have a machine with a lot of cores (say 6 or 8) that you want to put to use, you need to find a way to partitioning the set of objects into a larger number of smaller sets.

    Who beats 4 minutes and 32 seconds? And please share how you did it!

  • Microsoft Dynamics NAV Team Blog

    Cumulative Update 3 for Microsoft Dynamics NAV 2015 has been released

    • 8 Comments

    Cumulative Update 3 includes all application and platform hotfixes and regulatory features that have been released for Microsoft Dynamics NAV 2015. 

     The cumulative update includes hotfixes that apply to all countries and hotfixes specific to the following local versions:

    •   AU - Australia
    •   AT - Austria
    •   BE - Belgium
    •   CH – Switzerland
    •   CZ – Czech Republic
    •   DE - Germany
    •   DK - Denmark
    •   ES - Spain
    •   FI  - Finland
    •   FR - France
    •   IS - Iceland
    •   IT - Italy
    •   NA - North America
    •   NL - Netherlands
    •   NO - Norway
    •   NZ - New Zealand
    •   RU – Russia
    •   SE - Sweden
    •   UK - United Kingdom

    Where to find Cumulative Update 3

    You can download the cumulative update from KB 3024901  – Cumulative Update 3 for Microsoft Dynamics NAV 2015 (Build 39368). 

    Additional Information

    For information about how to install the cumulative update, see How to Install a Microsoft Dynamics NAV 2015 Cumulative Update

    For information about how to work around a recent process change, see How to Get Back the 'Hotfix Directories' from NAV 2015 Cumulative Update 1.

    For a list of all cumulative updates for this version, see Released Cumulative Updates for Microsoft Dynamics NAV 2015.  

  • Microsoft Dynamics NAV Team Blog

    Learn NAV PowerShell in your coffee breaks

    • 8 Comments

    For learning PowerShell in the lunch break we have this option.

    We thought that was a good idea. But we also thought we can do even better: Do some NAV PowerShell in your coffee breaks. This post is the first in a series of small script ideas of things you can do with NAV using PowerShell. If you find this useful or have ideas to improvements then please add your comments below and also suggest what you would like to see next.

     

    Coffee Break 1: Add AD users to NAV.

    User story

    Consider the following scenario: With the new security model in Microsoft Dynamics NAV 2013 and later versions, it is no longer possible to add users and permissions by merely adding Windows Groups. Besides, as of NAV 2013, you may not be using Windows authentication. The user wants to automate what is otherwise a trivial task of looking up Windows users, and entering them into NAV.
     
    The script below will give some ideas for adding NAV users in a batch.

    Pre requisites:

    Depending on which OS you run it on, you may need to install Remote Server Administration Tools (RSAT). Make sure to install the one that matches your OS and version. If you run Win 8.1 then install it from here:

    Remote Server Administration Tools for Windows 8.1
    http://www.microsoft.com/en-gb/download/details.aspx?id=39296

    If you run it on a server OS, you may just need to enable it. For more details:
    https://technet.microsoft.com/en-us/library/dd378937(v=ws.10).aspx

    To see if the module is installed, just try to import it and see if that works:

    Import-Module ActiveDirectory

     

     Coffee break 1 - importing NAV users from Active Directory

    #Suggestion: Run each line below one by one and then put them together as needed.

    #Install Remote Server Administration Tools (RSAT) first as described above, then import module

    Import-Module ActiveDirectory

     

    #Import NAV admin module for the version of Dynamics NAV you are using

    Import-Module 'C:\Program Files\Microsoft Dynamics NAV\80\Service\NavAdminTool.ps1'

     

    #Specify your Dynamics NAV Service name

    $NavServerName = "DynamicsNAV80"

     

    #AD filter for use in the next line. If you are not on a large domain, then run the next line (get-aduser) without this filter, or if you use the filter then adjust it to your scenario and domain.

    $Mysearchbase = "DC=<Domain>[,DC=<Corp Domain>,...]"

    #For example:

    $Mysearchbase = "DC=EUROPE,DC=CONTOSO,DC=com"

     

    #Next we will get AD users. If you want to import only users from a Windows group or a subdomain, you can filter the result set on sub-domain/group/...  Furthermore, we have chosen to retrieve only user name and alias in the example below, but choose any properties that fit your purpose. You can see the entire cmdlet output by running get-help <cmdletname>.Furthermore, we want to save this output into a list that we later can retrieve and modify if needed. The list format and default delimiter might vary depending on regional settings, a semicolon is defined here as a delimiter.

    get-aduser -filter 'samaccountname -like "*bill*"' -searchbase $Mysearchbase | Select-Object -Property Name,SAmaccountname | export-csv "c:\temp\userlist.csv" -notypeinformation  -Delimiter ';' -force 

    #Assign the list to a variable

    $myuserlist = Import-csv "c:\temp\userlist.csv"

    #Show the list

    $myuserlist

     

    #Another way of assigning the output to a variable is using outvariable. Next we want to  then pipe everything to New-NAVServerUser cmdlet to create new users in NAV. In the above example we have only read SamAccountName and User Name from  AD, so to add users as Windows users to NAV, following our Contoso scenario, we need to add the domain name too : DOMAIN\samaccountname. 

    import-csv C:\temp\userlist.csv -Delimiter ';' -OutVariable myuserlist | foreach {New-NAVServerUser -serverinstance $NavServerName -WindowsAccount "<DOMAIN>\$($_.samaccountname)"}

    #You can combine the above two actions (Reading AD users and then importing them into NAV) into one cmdlet, without saving the output as in the example above.

    #We're using a loop here and not the pipeline, as New-NAVServerUser doesn't seem to take the pipeline input

    get-aduser -filter 'samaccountname -like "*bill*"' -searchbase $Mysearchbase | foreach { New-NAVServerUser -serverinstance $NavServerName -WindowsAccount "<DOMAIN>$($_.samaccountname)"}

     

    <#Consider now the following scenario. User wants to get AD users using the export script above, but wants to add roles to this user list, before importing them into NAV. So he will break the above process into 2 steps again - in step 1 he will save AD users into a list, then assuming a modified list with added roles - he will import the list of users and their roles into NAV in step 2.

    Step one is then unchanged from the example above (using csv list). Next we will assume that the list is now modified to add roles to users.

    Example below shows step 2, where this list is imported to create users and assign permissions in NAV. Userlist2.csv file refered to in the script below is the name of the csv file containing users and permissions. Example below shows format of this file (csv, semicolon delimited) with Contoso users as examples:

     

    EUROPE\mrhill;BASIC,RAPIDSTART

    EUROPE\mssaddow;BASIC,COST,CASHFLOW

    EUROPE\joeroberts;SUPER

     

    If a user or a role defined in this list already exists in NAV, the cmdlet is expected to continue since the ErrorAction parameter is set to Continue (which is also the default value of this parameter). However it is singled out here to direct the attention to error handling opportunities that best fit the user's scenario. Review the possible values of this parameter and how to use them using get-help cmdlet. The following blog is worth checking:

    http://blogs.msdn.com/b/powershell/archive/2006/11/03/erroraction-and-errorvariable.aspx

    #>

     

    $NavServerName ="DynamicsNAV80"

    $list = Import-csv -Path C:\temp\userlist2.csv -Delimiter ';' -Header username,roleset

    foreach ($user in $list)

    {

      $navuser=Get-NAVServerUser -ServerInstance $NavServerName

       if(!($navuser.UserName -contains $user.username))

         { 

        New-NAVServerUser -ServerInstance $NavServerName -WindowsAccount $user.username -ErrorAction Continue

        }

    #In the csv file used in this example, the list of roles is divided by a comma

      $roleset=$user.roleset.Split(',')

         foreach ($role in $roleset)

        {

        $navrole=Get-NAVServerUserPermissionSet -ServerInstance $NavServerName -WindowsAccount $user.username

        if(!($navrole.PermissionSetID -contains $role))

            {

         New-NAVServerUserPermissionSet -ServerInstance $NavServerName -WindowsAccount $user.username -PermissionSetId $role -ErrorAction Continue

     

           }

        }

    }

     

     

    More coffee breaks to follow soon!

     

    Jasminka Thunes, Escalation Engineer Dynamics NAV EMEA

    Lars Lohndorf-Larsen, Escalation Engineer Dynamics NAV EMEA

     

     

  • Microsoft Dynamics NAV Team Blog

    What to do when the setup.exe doesn’t start in Dynamics NAV 2009

    • 8 Comments

    The reason that setup.exe doesn't start can be many. But I will try to give you ha checklist that you can work through if you end up in this situation.

    1. Ensure that the user account running setup.exe have administrator rights.
    2. Try the DVD/download one another computer. If this work you have eliminated that it's any wrong on the installation media.
    3. Start the task manager and check if any setup.exe is running. Sometimes under very special circumstances the setup.exe crash and will prevent any other attempt to start it again by staying running. If you find any setup.exe process kill them. And try to run the setup.exe again.
    4. Start the task manager and check if any msiexec.exe is running. This may happen when a windows update is running or that another installation process is running or have crashed. The best solution in this case is to restart the machine and check again and try to run the setup.exe again. In most cases the msiexec.exe process will be gone.
    5. If msiexe.exe are still there it probably mean that windows installer are running something. You can on your own risk try to kill all of those processes. The risk you take here is that you may end up with something half installed from windows update or other source. So it's recommended to run windows update after this to ensure its ok.
  • Microsoft Dynamics NAV Team Blog

    Let NAV Speak (with a Simple and Useful Client Add-in)

    • 8 Comments

    In this blog you will find the source code (below) to “let NAV speak.” You would have a wide range of possibilities to use this simple Add-in and enlarge this project.

    If you want to know more about Client Add-ins you can refer to this MSDN link:

    Extending the RoleTailored Client Using Control Add-ins and Microsoft Dynamics NAV 2009 SP1

    This simple Client Add-In is based on System.Speech namespace:

    Microsoft.Speech.Synthesis Namespace

    Step by step creation of the NSpeech Add-In

    (Remember the ‘DodgeBall’ rules: Develop, Sign, Place, Register and Develop)

    1. DEVELOP your add-in (in Visual Studio)
    2. Strong SIGN and build
    3. PLACE DLLs into Add-ins folder
    4. REGISTER the add-in in Table 2000000069, Client Add-in
    5. DEVELOP your C/AL code (in Object Designer)

    A. Create a New Class Project

    1. Open Visual Studio (in this example I am using Visual Studio 2010)
    2. Create a New Project (CTRL+SHIFT+N) with these parameters
      • Visual C# – Windows
      • Class library
      • .NET Framework 3.5
      • Name: NSpeech
      • Location: C:\TMP (or whatever location you like)
      • Solution Name: NSpeech
      • Create directory for solution

    NSpeechVS

    B. Create a Strong Name Key (SNK)

    1. Go to Project > Properties (NSpeech Properties…)
    2. From the Project Properties form go to the Signing tab
    3. Tick the Sign the assembly option
    4. Create a New SNK (e.g. TestSpeechNav.snk)

    NSpeechSign

    C. Add References to the Project

    1. Click on the Class1.cs tab (return to the project)
    2. In the Solution Explorer window select Reference, right Click, Add Reference
    3. Add reference to
      • Microsoft.Dynamics.Framework.UI.Extensibility (Version 1.3.0.0) (By default, the path to the assembly is C:\Program Files\Microsoft Dynamics NAV\60\RoleTailored Client)
      • System.Drawing (Version 2.0.0.0)
      • System.Speech (Version 3.0.0.0)
      • System.Windows.Forms (Version 2.0.0.0)

    NSpeechRef

    D. Develop your NSpeech Project

    (You can simply copy and paste this code into your Class project.)

    using System;

    using System.Collections.Generic;

    using System.Text;

    using System.ComponentModel;

    //Add a reference to the Add-in API (see the solution explorer) and all relevant references

    //Use all relevant references

    using System.Drawing;

    using System.Windows.Forms;

    using Microsoft.Dynamics.Framework.UI.Extensibility;

    using Microsoft.Dynamics.Framework.UI.Extensibility.WinForms;

    //this is to let this add-in speech

    //http://msdn.microsoft.com/en-us/library/dd146744(v=office.13).aspx

    using System.Speech;

    using System.Speech.Synthesis;

    namespace NSpeech

    {

        //Develop the control add-in class.

        //Assign a name to the control add-in (MyCompany.MyProduct.MyAddIn)

        [ControlAddInExport("Cronus.DynamicsNAV.NSpeech")]

        [Description("Let this Add-in Speak")]

      

        //Select a base class as a starting point.

        //Select interfaces to implement features, such as data binding or event handling.

        public class Class1 : StringControlAddInBase

        {

            //Implement control creation

            protected override Control CreateControl()

            {

                //Create a brand new TextBox

                TextBox control = new TextBox();

               

                //Define TextBox size

                control.MinimumSize = new Size(50, 0);

                control.MaximumSize = new Size(500, Int32.MaxValue);

               

                //Add a DoubleClick event for the TextBox

                control.DoubleClick += new EventHandler(control_DoubleClick);

                return control;

            }

            //Define a voice synth

            private SpeechSynthesizer synth;

            private void control_DoubleClick(object sender, EventArgs e)

            {

                //create a new speech synth and set default audio device

                synth = new SpeechSynthesizer();

                synth.SetOutputToDefaultAudioDevice();

               

                //Pass TextBox content in a string variable

                string data = this.Control.Text;

              

                //... and let NAV speak it!

                synth.SpeakAsync(data);

            }

        }

    }

    E. Build the NSpeech.dll

    1. Once you have all setup, you are ready to build your Client Add-In. Go to Build > Build NSpeech

    F. Place DLL into Add-in folder

    1. Locate/copy/paste NSpeech.dll (should be in your C:\TMP\NSpeech\NSpeech\bin\Debug folder) to the Add-ins folder of a machine where the RoleTailored client has been installed

    (typically the Add-ins folder is here: C:\Program Files\Microsoft Dynamics NAV\60\RoleTailored Client\Add-ins)

    G. Determine the PKT (Public Key Token) of NSpeech

    1. Launch the Visual Studio Command Prompt
    2. In the VSCP:

    Sn –T “C:\TMP\NSpeech\NSpeech\bin\Debug\NSpeech.dll”

    In the following example, 250f71f35a467631 is the PKT (Public Key Token) needed to register the Add-in into NAV. (You will have another value.)

    NSpeechSn

    H. Register the NSpeech dll

    1. Open Classic Client
    2. Open Object Designer (SHIFT+F12)
    3. Select Table object (ALT+B)
    4. Run Table 2000000069 Client Add-in
    5. Insert a new line with these values:
    Field Value
    Control Add-in Name Cronus.DynamicsNAV.NSpeech
    Public Key Token 250f71f35a467631 (this is an example)
    Version 1.0.0.0
    Description Let this Add-in Speak

    How to Use This Add-in

    As an example, you can just let NAV speak the content of the field “Name” in Customer Card page (Page 21).

    1. Open Classic Client
    2. Go to Object Designer (SHIFT+F12)
    3. Select Page object (ALT+G)
    4. Design Page 21 Customer Card
    5. Go to Name Field and Edit properties (SHIFT+F4)
    6. Fill the ControlAddIn property with this value Cronus.DynamicsNAV.NSpeech;PublicKeyToken=250f71f35a467631 (change the PublickKeyToken value to the one that you have determined at step G.)
    7. Save and compile the page (CTRL+S)

    Now…you are ready to let NAV speak the Customer Name from the customer card by simply double clicking on on the Name!

    This simple Client Add-in project may be used in, e.g.

    • Speak an alert if the availability of an Item is lower than expected in a document page (e.g. sales quote)
    • Speak an alert if a Customer exceeds assigned Credit Limit
    • Speak internal comments for an item, a vendor, a customer, wherever this is needed
    • … and many more

    This simple Client Add-in project may be enlarged, e.g.

    • It could be possible to set the volume of the voice
    • It could be possible to set the rate of the voice
    • It could be possible to select another voice instead of “Microsoft Anne” default in order to speak words with proper accent language
    • It could be possible to save a .wav file instead of speaking it or even perform both activities
    • … and many more

    These postings are provided "AS IS" with no warranties and confer no rights. You assume all risk for your use.

    Best Regards,

    Duilio Tacconi (dtacconi)

    Microsoft Dynamics Italy

    Microsoft Customer Service and Support (CSS) EMEA

Page 5 of 50 (740 items) «34567»