Coffee Break: Use the PowerShell Runner Add-In


In this post we will look how to run your Windows PowerShell scripts using your favorite application: Dynamics NAV 🙂 . This can be done using .NET Framework libraries, but Microsoft Dynamics NAV 2016 also released an add-in called PowerShellRunner (found in service add-in folder). Libraries found here can be used to run PowerShell scripts from the Dynamics NAV client, for any scenario you might see useful. For example: avoiding using separate  msc service management consoles : manage your services from your Dynamics NAV client instead 🙂 ;  manage backup/restore or provisioning test or production environments; export, merge, compile objects, and so on.

So as an illustration, let us consider the following user scenario. Our customer is a small distribution company, somewhere in the snowy town of Drøbak (Norway). Their chief of operations (we’ll refer to him as S.Claus) is out on the road distributing goods for most of the December, and needs a possibility to provision companies, add requests, updates and do some general management ad-hoc in a simple manner, using his smartphone (for best network coverage in all sorts of remote places).

For this, his assistants have used the PowerShellRunner add-in and created a management interface in Dynamics NAV that S.Claus will use from his phone app.

They found the PowerShellRunner add-in in the service folder on the product media with all other add-ins, and they added it to the add-in table.

In its simplest, most basic form, running a PowerShell command would look something like this:

  1. Declare PowerShellRunner as a variable of type DotNet.
  2.  Create an instance of  PowerShellRunner.
  3. Tell PowershellRunner to write to the event log.
  4. Import the modules from PowerShellRunner.
  5. Set the various parameters for connection to a Dynamics NAV instance.
  6. Run the relevant cmdlets.

The following code snippet illustrates the kind of codeunit that S.Claus’ assistants might have written:

OBJECT Codeunit 50020 PS RUNNER
{
 OBJECT-PROPERTIES
 {
 Date=12/18/15;
 Time=[ 4:32:03 PM];
 Modified=Yes;
 Version List=;
 }
 PROPERTIES
 {
 OnRun=VAR
 PowerShellLogEntry@1000 : Record 8060;
 BEGIN
 AddService;
 EnableDebugging;
 StartService;
 MountTenant;
 END;

 }
 CODE
 {
 VAR
 PowerShellRunner@1000 : DotNet "'Microsoft.Dynamics.Nav.PowerShellRunner, Version=9.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.Microsoft.Dynamics.Nav.PowerShellRunner";
 PSObject@1001 : DotNet "'System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.System.Management.Automation.PSObject";
 ResultEnumerator@1004 : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Collections.Generic.IEnumerator`1";
 ErrorEnumerator@1003 : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Collections.Generic.IEnumerator`1";
 Created@1002 : Boolean;
 LogOut@1005 : OutStream;
 Result@1006 : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.String";

 LOCAL PROCEDURE AddService@1();
 BEGIN
 PowerShellRunner := PowerShellRunner.CreateInSandbox;
 PowerShellRunner.WriteEventOnError := TRUE;
 //to enable logging

 //To import modules
 PowerShellRunner.ImportModule('C:\Program Files\Microsoft Dynamics NAV\90\Service\Microsoft.Dynamics.Nav.Management.dll');

 PowerShellRunner.AddCommand('New-NAVServerInstance');
 PowerShellRunner.AddParameter('ServerInstance','ProdNAV');
 PowerShellRunner.AddParameter('ManagementServicesPort','7055');
 PowerShellRunner.AddParameter('ClientServicesPort','7056');
 PowerShellRunner.AddParameter('SOAPServicesPort','7057');
 PowerShellRunner.AddParameter('ODataServicesPort','7058');
 PowerShellRunner.AddParameter('DatabaseServer','MyServerName');
 PowerShellRunner.AddParameter('DatabaseInstance','NAVDEMO');
 PowerShellRunner.AddParameter('DatabaseName','MyProd');
 PowerShellRunner.AddParameter('ServiceAccount','NetworkService');




 //Result := PowerShellRunner.GetLogMessageList;
 PowerShellRunner.WriteEventOnError := TRUE;
 //Created := TRUE

 PowerShellRunner.BeginInvoke;
 END;

 LOCAL PROCEDURE EnableDebugging@2();
 BEGIN
 PowerShellRunner := PowerShellRunner.CreateInSandbox;
 //PowerShellRunner.WriteEventOnError := TRUE;

 //To import modules
 PowerShellRunner.ImportModule('C:\Program Files\Microsoft Dynamics NAV\90\Service\Microsoft.Dynamics.Nav.Management.dll');

 PowerShellRunner.AddCommand('Set-NAVServerConfiguration');
 PowerShellRunner.AddParameter('ServerInstance','ProdNAV');
 PowerShellRunner.AddParameter('KeyName','EnableDebugging');
 PowerShellRunner.AddParameter('KeyValue','True');
 PowerShellRunner.BeginInvoke;
 END;

 LOCAL PROCEDURE StartService@3();
 BEGIN
 PowerShellRunner := PowerShellRunner.CreateInSandbox;

 PowerShellRunner.ImportModule('C:\Program Files\Microsoft Dynamics NAV\90\Service\Microsoft.Dynamics.Nav.Management.dll');

 PowerShellRunner.AddCommand('Set-NavServerInstance');
 PowerShellRunner.AddParameter('ServerInstance','ProdNAV');
 PowerShellRunner.AddParameter('Start');
 PowerShellRunner.BeginInvoke;
 END;

 LOCAL PROCEDURE MountTenant@4();
 BEGIN
 //new service is created as multitenant. To run a single tenant database, simply mount it.

 PowerShellRunner := PowerShellRunner.CreateInSandbox;

 PowerShellRunner.ImportModule('C:\Program Files\Microsoft Dynamics NAV\90\Service\Microsoft.Dynamics.Nav.Management.dll');

 PowerShellRunner.AddCommand('Mount-NAVTenant');
 PowerShellRunner.AddParameter('ServerInstance','ProdNAV');
 PowerShellRunner.AddParameter('Id','default');
 PowerShellRunner.AddParameter('DatabaseName','MyProd');
 PowerShellRunner.BeginInvoke;
 END;

 BEGIN
 END.
 }
}

In this example, ProdNAV is the name of the server instance, and MyProd is the name of the production database, in this case a restore of the demonstration database.

All of this is running server-side, so to be able to execute this, your service account (on the service that you are running this from), has to be a member of the administrators’ group. Then you can manage all other services, databases, and so on from one management service.
Add to this a bit of database structure and simple GUI, and S.Claus can roam the world with all his operations available at the flick of his thumb.

The coffee break team and the rest of the Dynamics NAV team wishes all of you:

Happy Holidays!

Comments (12)

  1. waldo says:

    OK .. now my “Running PowerShell from NAV” is completely redundant (http://www.waldo.be/2014/12/17/running-powershell-from-nav/) 🙂

    Apparently there is a use afterall 😉

  2. Haven’t seen your post before, great post and great tool :)!
    The post above is to introduce ps Library released With Dynamics NAV, psrunner, and show a basic example of it’s usage in it ‘s simplest form. An outline of one of the possibilites. But as always, the more – the merrier 🙂
    Thanks for sharing!

  3. Thanks for the hint. On my wishing list for Santa Claus: Source code samples like this one also as a download link, as these snippets are not indented properly, so you can’t import them without running through all the lines since there no code beautifiers for C/AL available.

  4. Capone says:

    Great post!
    I agree with Kai that you should have attached a downloadable file.
    Unfortunately I recieved an error when I tried to use this for the included NAV functions.

    I’m having the following code:
    PowerShellRunner.ImportModule(‘C:\Program Files (x86)\Microsoft Dynamics NAV\90\RoleTailored Client\Microsoft.Dynamics.Nav.Model.Tools.psd1’);
    PowerShellRunner.AddCommand(‘Export-NAVApplicationObject’);
    PowerShellRunner.AddParameter(‘DatabaseName’,’Demo Database NAV (9-0)’);
    PowerShellRunner.AddParameter(‘Path’,’c:\temp\testexportNAV.txt’);
    PowerShellRunner.WriteEventOnError := TRUE;
    PowerShellRunner.BeginInvoke;

    But I only recieve the following in the eventvwr:
    Command: Export-NAVApplicationObject

    Timestamp: 2016-01-07 19:06:35
    Error: Cannot convert ‘System.String’ to the type ‘System.Management.Automation.SwitchParameter’ required by parameter ‘Verbose’.
    Command name: Export-NAVApplicationObject
    Stack trace: at Export-NAVApplicationObject, C:\Program Files (x86)\Microsoft Dynamics NAV\90\RoleTailored Client\Microsoft.Dynamics.Nav.Ide.psm1: line 346

    1. Point taken 🙂 We’ll post a list of all of these posts so far, with downloadable files for each. As for your issue, it wants -Verbose switch for logging. I’ve modified your code to add filter (for test execution) , and verbose (for logging). I also added force, to overwrite existing file.

      PowerShellRunner.ImportModule(‘C:\Program Files (x86)\Microsoft Dynamics NAV\90\RoleTailored Client\Microsoft.Dynamics.Nav.Model.Tools.psd1’);
      PowerShellRunner.AddCommand(‘Export-NAVApplicationObject’);
      PowerShellRunner.AddParameter(‘DatabaseName’,’Demo Database NAV (9-0)’);
      PowerShellRunner.AddParameter(‘Path’,’c:\temp\testexportNAV.txt’);
      PowerShellRunner.AddParameter(‘DatabaseServer’,’);
      PowerShellRunner.AddParameter(‘Filter’,’Type=Codeunit;Id=1′);
      PowerShellRunner.AddParameter(‘Verbose’);
      PowerShellRunner.AddParameter(‘Force’);

      1. Capone says:

        Thanks! Works like a charm 🙂

      2. Matthias König says:

        Hello,

        I tried the Export-NAVApplicationObject command on my own system (client and servicetier on the same PC) but it did not work.
        There is no info in the event log and nothing happen 🙁
        This is the result of the PowershellRunner.log:
        Microsoft Dynamics NAV
        —————————

        09:19:01:Importing module C:\Program Files (x86)\Microsoft Dynamics NAV\90\RoleTailored Client\Microsoft.Dynamics.Nav.Model.Tools.psd1…
        ———————————————
        Command: Export-NAVApplicationObject
        ———————————————
        Parameters:
        DatabaseServer =
        DatabaseName = Demo Database NAV (9-0) Version Check
        Path = C:\TEMP\PSRUNNER\NAVObj.txt
        Filter = Type=5;ID=50020
        Force
        Verbose
        ———————————————
        ————–Execution Started————–

        —————————
        OK
        —————————

        Did anybody had an Idea why this not work or where I get an error message? :/

  5. Viktoras says:

    I was trying to use this feature to extract captions from object text file, but it seems that it works only if I add confirm or something else to wait until process is finished. If function is finished right after BeginInvoke, then result file is not created. Is there a possibility to run PowerShell and wait until command is finished?
    My code is following:
    PowerShellRunner := PowerShellRunner.CreateInSandbox;
    PowerShellRunner.WriteEventOnError := TRUE;
    PowerShellRunner.ImportModule(‘C:\Program Files\Microsoft Dynamics NAV\90\Service\Microsoft.Dynamics.Nav.Model.Tools.dll’);

    PowerShellRunner.AddCommand(‘Export-NAVApplicationObjectLanguage’);
    PowerShellRunner.AddParameter(‘-Source’,’E:\_Exchange\Page50000.txt’);
    PowerShellRunner.AddParameter(‘-Destination’,’E:\_Exchange\Page50000Captions.txt’);
    PowerShellRunner.AddParameter(‘Verbose’);
    PowerShellRunner.AddParameter(‘Force’);
    PowerShellRunner.BeginInvoke;

    I also don’t get any errors on Power Shell log or when I try MESSAGE(PowerShellRunner.ExceptionMessage) – even when source file doesn’t exist, I don’t see any information about that anywhere. Where errors are logged?

    Is it possible to find a documentation for this Add-In?

    1. Robert Tuck says:

      Quote: “If function is finished right after BeginInvoke, then result file is not created. Is there a possibility to run PowerShell and wait until command is finished?”
      add the following or something like it after the BeginInvoke and all will be good.

      ProgressWindow.OPEN(‘Processing Request #1##’);
      Countdown := 5;
      REPEAT
      SLEEP(1000);
      ProgressWindow.UPDATE(1,Countdown);
      Countdown -= 1;
      UNTIL Countdown = 0;
      ProgressWindow.CLOSE;

  6. Theunis Modderman says:

    I have recently found this post and it’s comments, and I am curious whether some more documentation is yet available (I haven’t found any).

    I have created the following code (see below) to start/stop/restart a service. It works fine but only if you run this code on the PC where the service is actually running. I am trying to find a solution to get this to work on any PC (within the same network). I want to use this to restart a NAS after having added a new company. By the way, I found that it only works if you indeed give it some time to execute (like Rober wrote), a SLEEP(1000) will also do, or any IF NOT CONFIRM-construction.

    Here’s my code:

    Function SetServiceStatus(What : ‘Start,Stop,Restart’ ; InstanceName : Text))
    PowerShellRunner := PowerShellRunner.CreateInSandbox;
    PowerShellRunner.WriteEventOnError := TRUE;
    PowerShellRunner.ImportModule(‘C:\Program Files\Microsoft Dynamics NAV\9.00\Service\Microsoft.Dynamics.Nav.Management.dll’);
    PowerShellRunner.AddCommand(‘Set-NAVServerInstance’);
    PowerShellRunner.AddParameter(‘ServerInstance’,InstanceName);
    PowerShellRunner.AddParameter(FORMAT(What));
    PowerShellRunner.BeginInvoke;

    ProgressWindow.OPEN(‘Processing Request);
    SLEEP(1000);
    ProgressWindow.CLOSE;

    1. Theunis Modderman says:

      By the way: the following code at the end is nicer…

      CountDown := 1;
      WHILE NOT PowerShellRunner.IsCompleted and (CountDown < 5) do begin
      CountDown := CountDown + 1;
      sleep(1000);
      end;
      IF PowerShellRunner.IsCompleted THEN
      MESSAGE('The %1-process has executed succesfully.',FORMAT(What));

  7. olivier says:

    Can someone explain how to get property of resulted PSObject (eg. State of ServerInstance) ?

    PowerShellRunner := PowerShellRunner.CreateInSandbox;
    PowerShellRunner.ImportModule(‘C:\Program Files\Microsoft Dynamics NAV\90\Service\Microsoft.Dynamics.Nav.Management.dll’);
    PowerShellRunner.AddCommand(‘Get-NavServerInstance’);
    PowerShellRunner.AddParameter(‘ServerInstance’,ServerInstance);
    PowerShellRunner.BeginInvoke;
    REPEAT UNTIL PowerShellRunner.IsCompleted=TRUE;
    PSDataCollectionResults := PowerShellRunner.Results;
    Enumerator := PSDataCollectionResults.GetEnumerator;
    WHILE Enumerator.MoveNext DO BEGIN
    PSObjectAdapter := Enumerator.Current;
    Object := PSObjectAdapter.GetProperty(‘State’); //errors
    END;

Skip to main content