Updated Hardware Hub Twain Client

The Hardware Hub Twain Client has been updated.  For more details on that solution read posts tagged with twain on my blog.

The updated version should make is easier to understand what is going on and how the scanning process is going.  By ticking in the Log checkbox all actions will be logged in the Messages text box.

twainclient

Another new feature is designed to solve a problem some of the users have been reporting.  When a users selects to have the scanning settings shown that dialog sometimes is opened in the background.

StoreCard

The solution is to add the process name to the Twain Client.  This is an example settings dialog.

settings

Looking at the Task Manager I can see that the process name is wiawow64.exe.

taskmanager

And by using this small PopUp2Front solution I can see the process name I need to add to my Twain Client.

popup2front

The idea is to have the Twain Client look for this process and bring it to the front of the desktop for the user.  In this case I put (wiawow64) as the PopUp Process in the Twain Client.

twainwithpopup

The Select Process button on the Twain Client will give the same process list, but it is not accessible while scanning.

Please remember to restart the Twain Client after you modify the settings to make sure that they are saved in your registry.

Also please remember that the HardwareHub on hub.dynamics.is is only for developement and testing.  All the clients on this website and all the code supplied can be freely used.  To install your own secure Hardware Hub Service go to Objects4NAV.com to download it.

 

Scanning and attachments for Incoming Documents in Microsoft Dynamics NAV 2013 R2

A new feature in NAV 2013 R2 allows companies to store links to incoming documents and reference them in Purchase Invoices and Journal Lines.  There is a video in the How Do I series on Managing Incoming Documents in Microsoft Dynamics NAV 2013 R2.

I wanted to add a functionality to scan and store the files within NAV.  Using the Hardware Hub and the Hardware Hub Twain Client to easily scan documents and store them in NAV.  The objects needed are attached below.  You will need to put the Hardware Hub Proxy Add-in to the NAV 2013 R2 Server Add-in folder.  There is a possibility of doing this without the add-in by manually creating the soap requests.  I did this in the NAV 2009 R2 version for the Classic Client and if needed I should be able to make available a similar NAV 2013 R2 version.

So, how does it work ?

First step is to download and install the Hardware Hub Twain Client on the computer with a twain compatible scanner and the Hardware Hub Proxy Add-in to the Server Add-in folder..  Double click the icon in the notification area to get the program windows in foreground.  Next step is to install the new and modified objects into your NAV 2013 R2 database.

Then go to Incoming Document in the Windows Client.  After an incoming document entry has been created the New Attachment button will be enabled.NewIncomingDocumentEntry

Click New Attachment button.  The first time you open this page you will need to set up the scanner connection.

ScanningSetup

You need to compare the Hardware Hub Path and the Hardware Hub Scanner GUID to the Twain Client window.  It should be enough to copy the GUID from NAV to the Twain Client.

TwainClient

Minimize the Twain Client and go to the ACTIONS menu to select scanner.  You can test the Hardware Hub connection with the About action.

SelectScanner

These settings will be saved with the page data personalization for the current user.

If you select to “Show Settings” the settings dialog will sometimes appear behind the NAV Windows Client – just to let you know.

You can also browse for an existing file in the File Name drill down arrow and that file will be uploaded into NAV.  When you close this window the URL to the Incoming Document Store will be added to the Incoming Document.  You can add multiple files to a single Incoming Document and the URL will open all of them.

This solution will work on a cloud based NAV and also via remote desktop.  I was able to scan with the Web Client but that functionality is not fully tested.  Your feedback is always appreciated.

Objects: IncomingDocumentStore, 2014-07-29 Update

Hardware Hub IIS Service on Objects4NAV.com

Add-ins Downloaded and Installed with NAV C/AL

I was watching Vjeko’s lecture in NAVTechDays 2013.  He gave me an idea on the add-in installation.  Thank you Vjeko.

It is now finished and time to share this with you all.  The basic function Codeunit is used to handle the downloading and installing of the add-in.  In the Add-on Setup Page I put an action to trigger the Add-ins validation.

CheckAddIns

This function will call the CheckAddins trigger on the Setup Table.  There I have a simple code

[code]
IF NOT CANLOADTYPE(HardwareHubProxy) THEN
AddinMgt.DownloadAnInstallServerAddIn(
‘HardwareHubProxy.dll’,
‘https://dl.dropboxusercontent.com/u/33900811/NAV/Add-Ins/Server/Advania/HardwareHubProxy.dll’,
Text002);[/code]

That will check if the add-in is installed and if not call a trigger in the add-ins function Codeunit to install the add-in.  This works both for the client and server.

In this example I am installing the HardwareHubProxy.dll that is required on the server to use the Hardware Hub.  Text002 is ‘ENU=Connection to the Advania Hardware Hub;ISL=Tenging við vélbúnaðargátt Advania’.

When installing the add-in I begin by downloading it into a Base64 string.  This is always done on the server.  The add-in is then saved to a temporary file, on the server for a server add-in and on the client for a client add-in.

Next I create a command file that is used to copy the temporary file to the add-ins folder.  This command file is then executed.  Before execution there is a check to see if the current user is able to write to the add-in directly.  If so, then the add-in is copied to the add-ins folder and the process finishes.

If the service user is not able to write to the server add-ins folder the user will get a save-as dialog and is asked to copy the file to the server add-ins folder.  In all my tests the server has been able to save the add-in without problems.

By default the running client will not be able to write the add-in to the client add-ins folder.  If this is the case then a confirmation dialog will be prompted as the copy function is running in an elevated access mode.

AddInFunctions

Gathering permission for users and groups

I have a solution that stores scanned and linked documents in an external database.  A URL to the document is stored in the Record Link table in NAV and attached to the relevant record.  The URL points to a web page that delivers the attached document given that the user asking for the document has permission to read the table the document belongs to.

In my latest installation I faced a new challenge; we are now using Active Directory Groups for access control.  Still I need to store the permissions in my external database for each user.  Of course it would be possible to change the permission handling in the external database and modify the ASPX web page that delivers the document, but I am a NAV programmer and if the solution is possible in NAV it will be written in NAV.

The task was to find all users that belong to a group.  The method I used is through dotnet and through the service tier.  If the service tier is not running on a domain account the dotnet execution needs to be on the client side.

The first step is to create the connection string to Active Directory.  I use “System.Net.NetworkInformation.IPGlobalProperties” dotnet object to the the current dns domain name and change the domain name from example.com to the connection string “DC=example,DC=com”.

Next step is to load all the group information from Active Directory into a temporary table.  I use table 367 as ADBuffer and load all combinations of group and user into that table.

For every group used in the access control I filter the buffer table and read all the users within that group.

This is a part of the codeunit I use in my solution and should give you a hint on how to do this in your own solution.

[code] PROCEDURE CreatePermissionBuf@1200050012(VAR PermissionBuf@1200050001 : Record 2000000005;VAR TempObject@1100408001 : Record 2000000001) : Boolean;
VAR
PermissonSet@1100408000 : Record 2000000004;
User@1005 : Record 2000000120;
AccessControl@1006 : Record 2000000053;
Permission@1008 : Record 2000000005;
TempUser@50000 : TEMPORARY Record 91;
HasPermission@1003 : Boolean;
BEGIN
PermissionBuf.DELETEALL;

WITH User DO
IF FINDSET THEN REPEAT
IF CreateTempUserIDs(User,TempUser) THEN BEGIN
PermissonSet.SETFILTER("Role ID",’SUPER’ + ‘*’);
PermissonSet.FINDSET;
REPEAT
HasPermission := AccessControl.GET("User Security ID",PermissonSet."Role ID",COMPANYNAME);
IF NOT HasPermission THEN
HasPermission := AccessControl.GET("User Security ID",PermissonSet."Role ID",”);
UNTIL (PermissonSet.NEXT = 0) OR HasPermission;

IF HasPermission THEN BEGIN
IF TempUser.FIND(‘-‘) THEN
REPEAT
TempObject.FIND(‘-‘);
REPEAT
PermissionBuf."Role ID" := TempUser."User ID";
PermissionBuf."Object Type" := PermissionBuf."Object Type"::"Table Data";
PermissionBuf."Object ID" := TempObject.ID;
IF PermissionBuf.INSERT THEN;
UNTIL TempObject.NEXT = 0;
UNTIL TempUser.NEXT = 0;
END ELSE BEGIN
TempObject.FIND(‘-‘);
REPEAT
Permission.SETRANGE("Object Type",Permission."Object Type"::"Table Data");
Permission.SETRANGE("Object ID",TempObject.ID);
Permission.SETRANGE("Read Permission",Permission."Read Permission"::Yes);
IF Permission.FINDSET THEN
REPEAT
HasPermission := AccessControl.GET("User Security ID",Permission."Role ID",COMPANYNAME);
IF NOT HasPermission THEN
HasPermission := AccessControl.GET("User Security ID",Permission."Role ID",”);
UNTIL HasPermission OR (Permission.NEXT = 0);
IF HasPermission THEN BEGIN
IF TempUser.FIND(‘-‘) THEN
REPEAT
PermissionBuf."Role ID" := TempUser."User ID";
PermissionBuf."Object Type" := PermissionBuf."Object Type"::"Table Data";
PermissionBuf."Object ID" := TempObject.ID;
IF PermissionBuf.INSERT THEN;
UNTIL TempUser.NEXT = 0;
END;
UNTIL TempObject.NEXT = 0;
END;
END;
UNTIL NEXT = 0;

TempObject.FIND(‘-‘);
REPEAT
PermissionBuf."Role ID" := SigningSetup."Database Login Name";
PermissionBuf."Object Type" := PermissionBuf."Object Type"::"Table Data";
PermissionBuf."Object ID" := TempObject.ID;
IF PermissionBuf.INSERT THEN;
UNTIL TempObject.NEXT = 0;
END;

LOCAL PROCEDURE CreateTempUserIDs@50000(FromUser@50001 : Record 2000000120;VAR TempUser@50000 : Record 91) : Boolean;
VAR
DomainName@50003 : Text;
GroupName@50004 : Text;
BEGIN
TempUser.DELETEALL;

WITH FromUser DO BEGIN
IF "Windows Security ID" = ” THEN EXIT(FALSE);
CASE "License Type" OF
"License Type"::"Windows Group":
BEGIN
DomainName := SigningTools.GetDomainName("User Name");
GroupName := SigningTools.RemoveDomainName("User Name");
GetListOfAdUsersByGroup(DomainName,GroupName,TempUser);
END;
ELSE BEGIN
TempUser.INIT;
TempUser."User ID" := SigningTools.RemoveDomainName(UPPERCASE("User Name"));
TempUser.INSERT;
END;
END;
EXIT(TRUE);
END;
END;

LOCAL PROCEDURE GetListOfAdUsersByGroup@50033(DomainName@50001 : Text;GroupName@50002 : Text;VAR TempUser@50000 : Record 91);
VAR
IPProperties@50003 : DotNet "’System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Net.NetworkInformation.IPGlobalProperties";
TextMgt@50005 : Codeunit 10000204;
ConnectionString@50014 : Text;
IPDomainName@50013 : Text;
BEGIN
ADBuffer.RESET;
IF ADBuffer.ISEMPTY THEN BEGIN
IPDomainName := IPProperties.GetIPGlobalProperties.DomainName;
WHILE STRPOS(IPDomainName,’.’) > 0 DO BEGIN
IF ConnectionString = ” THEN
ConnectionString := ‘DC=’ + TextMgt.ExtractFirstPartFromString(IPDomainName,’.’,”)
ELSE
ConnectionString += ‘,DC=’ + TextMgt.ExtractFirstPartFromString(IPDomainName,’.’,”);
END;

IF ConnectionString = ” THEN
ConnectionString := ‘DC=’ + IPDomainName
ELSE
ConnectionString += ‘,DC=’ + IPDomainName;
LoadADBuffer(ConnectionString);
END;

ADBuffer.SETRANGE(Totaling,GroupName);
IF ADBuffer.FIND(‘-‘) THEN REPEAT
TempUser.INIT;
TempUser."User ID" := ADBuffer.Name;
TempUser.INSERT;
UNTIL ADBuffer.NEXT = 0;
END;

LOCAL PROCEDURE LoadADBuffer@50003(ConnectionString@50015 : Text);
VAR
DirectoryEntry@50004 : DotNet "’System.DirectoryServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’.System.DirectoryServices.DirectoryEntry";
DirectorySearcher@50003 : DotNet "’System.DirectoryServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’.System.DirectoryServices.DirectorySearcher";
SearchResultCollection@50006 : DotNet "’System.DirectoryServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’.System.DirectoryServices.SearchResultCollection";
SearchResult@50009 : DotNet "’System.DirectoryServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’.System.DirectoryServices.SearchResult";
ResultPropertyCollection@50010 : DotNet "’System.DirectoryServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’.System.DirectoryServices.ResultPropertyCollection";
ResultPropertyValueCollection@50007 : DotNet "’System.DirectoryServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’.System.DirectoryServices.ResultPropertyValueCollection";
Query@50005 : Text;
KeyValue@50000 : Code[10];
SearchLoop@50008 : Integer;
PropertyLoop@50001 : Integer;
BEGIN
KeyValue := ‘00000001’;
DirectoryEntry := DirectoryEntry.DirectoryEntry(‘LDAP://’ + ConnectionString);
DirectorySearcher := DirectorySearcher.DirectorySearcher(DirectoryEntry);
Query := ‘(&(objectCategory=person)(objectClass=user)(memberOf=*))’;
DirectorySearcher.Filter := Query;
DirectorySearcher.PropertiesToLoad.Add(‘sAMAccountName’);
DirectorySearcher.PropertiesToLoad.Add(‘memberOf’);
SearchResultCollection := DirectorySearcher.FindAll;
FOR SearchLoop := 1 TO SearchResultCollection.Count DO BEGIN
SearchResult := SearchResultCollection.Item(SearchLoop – 1);
ResultPropertyCollection := SearchResult.Properties;
ADBuffer.INIT;
ADBuffer.Name := UPPERCASE(ResultPropertyCollection.Item(‘sAMAccountName’).Item(0));
FOR PropertyLoop := 1 TO ResultPropertyCollection.Item(‘memberOf’).Count DO BEGIN
ADBuffer.Code := KeyValue;
ADBuffer.Totaling := UPPERCASE(COPYSTR(SELECTSTR(1,ResultPropertyCollection.Item(‘memberOf’).Item(PropertyLoop – 1)),4));
ADBuffer.INSERT;
KeyValue := INCSTR(KeyValue);
END;
END;
END;
[/code]

Hardware Hub Update

I just updated the hardware hub service on Dynamics.  I added functions to send and receive status.  I had problems with the Topaz signature pad as the status updates from the pad where interfering with the commands sent between NAV and the Topaz client.

I now send the updates via the status service and things are working better for me.

A little recap on the Hardware Hub.  This is a web service used as a bridge between NAV and a hardware client running on the host computer.  This moves all communication between the software and the hardware to standard web requests.  That means that as long as the NAV service and the hardware client can both access the Hub service they will always be able to communicate.

To follow the update on the service I also updated the proxy add-in for Dynamcis NAV.

HardwareHubProxy

Hardware Hub IIS Service on Objects4NAV.com

Twain Scanner for Dynamics NAV that works in remote desktop

A few days ago I published objects for NAV 2013 and details on how to use a twain scanner with the hardware hub.

I know that many happy Dynamics NAV users are still using older versions so I wanted to make this solution available for NAV 2009, both in the Role Tailored Client and in the Classic Client.

ClassicHubScanning

If you are running NAV 2009 you should be able to use the attached object to test your scanner.  It should be easy to change the code to work with older versions of Dynamics NAV.

Hub Scanning for NAV 2009

Hardware Hub IIS Service on Objects4NAV.com

Bar code reader and the Hardware Hub

We have some cases where the Dynamics NAV client is running in a remote desktop and the local machine has a bar code scanner connected.  One way is to have the bar code scanner configured as a keyboard.  This requires the focus on the correct input field in NAV to work.  The other way is to have the scanner configured to use a Serial Communication Port.

DevMgt

This allows me to listen to the Serial Port and handle the input with C/AL code.  To make sure that this will work where ever the client is running I choose to use the Hardware Hub.  Attached is the NAV objects required to test this functionality.  Start by installing the Serial Port Client.

HubSerialPortClient

Import the NAV object into your NAV 2013 and make sure the the HardwareHubProxy.dll is in the add-ins folder for the middle tier service.  The start Page 50095.  Press the assist button to get a new Serial Port GUID.

HubBarcodeDemo

Copy the Serial Port GUID into the Serial Port Client, choose the correct port settings and check the box to open the port.  You can test the scanning to make sure.  The last scanned bar code will appear in the Last Data text box.

HubSerialPortClientActive

In Dynamics NAV start the listener and test the scanning.

NAV objects are attached, Hub Barcode Demo.

Hardware Hub IIS Service on Objects4NAV.com