Synchronize A/D users to NAV

During my work with demos like Edit In Excel, I wanted to make sure that these things would work in all localized versions of NAV 2009 SP1 – meaning that I needed to install 14 different databases and 14 running Service Tier’s. Having done that, I also wanted to allow my colleagues who needed to check something, access to these service tiers.

For a geek (like me:-)), that problem looks like something you need to write an application for, even though it probably takes more time than it would be to add the users one by one whenever needed, but it certainly is more fun to write the application – and… maybe somebody else can actually use the ideas from this to do something cool.

How to do?

Basically what I want to do is, to enumerate the Remote Desktop Users of the computer and make sure that these users are SUPER in NAV. Now, I can do this in NAV calling out to a COM object enumerating my users – but that really wouldn’t help me, because I would have to start NAV with every service tier or database and launch that action.

So, first I created a codeunit in NAV, exposed this codeunit as Web Services. Then I created a console application running on a schedule on my server, which enumerates the users and invoke the Web Service function to check all users are created in NAV.

Of course the scheduled application has to run with elevated permissions and that user needs to be able to change users in NAV as well.

The NAV code

The codeunit contains two functions:

ImportUserSID(SID : Text[119];Role : Code[20]) : Boolean
IF NOT Winlogin.GET(SID) THEN
EXIT(FALSE);
Winlogin.INIT;
Winlogin.SID := SID;
Winlogin.INSERT();
IF NOT WinAccess.GET(SID) THEN
BEGIN
WinAccess.INIT;
WinAccess.”Login SID” := SID;
WinAccess.”Role ID” := Role;
WinAccess.INSERT;
END;
EXIT(TRUE);

and

SynchronizeUsers()
DATABASE.SYNCHRONIZEALLLOGINS();

BTW. Calling SynchronizeAllLogins running through Web Services didn’t seem to work when called through Web Services and running enhanced database security – whether this is a bug or a problem in my setup – I don’t know, but I am going to file a bug on it.

As you can see, the ImportUserSID does not modify the role if the user is already in the login table – it could of course be modified to do that easily, but it wasn’t necessary for my usage.

The C# code for enumerating an A/D group

Lets just look at the code:

DirectoryEntry dir = new DirectoryEntry(“WinNT://localhost/Administrators”);
Console.WriteLine(“Enumerating users”);
foreach (object obj in (IEnumerable)dir.Invoke(“members”))
{
DirectoryEntry user = new DirectoryEntry(obj);
Console.WriteLine(user.Path);
}
Console.WriteLine(“Done”);
Console.ReadLine();

Running this on my computer outputs the following:

image

The next step is, to get the SID’s for each user – now you would think that there should be an SID function on the user object returning a string, but unfortunately it isn’t that simple. Luckily somebody invented the Internet – and luckily somebody was kind enough to post some information for us to use.

Have a look at http://www.netomatix.com/GetUserSid.aspx where I found the following code, which seems to work for the purpose.

On the user object you have a property collection. One of these properties is called objectSid, which is a byte[] and that can be transformed into a SID string using the following function:

private static string ConvertByteToStringSid(Byte[] sidBytes)
{
StringBuilder strSid = new StringBuilder();
strSid.Append(“S-“);
try
{
// Add SID revision.
strSid.Append(sidBytes[0].ToString());
// Next six bytes are SID authority value.
if (sidBytes[6] != 0 || sidBytes[5] != 0)
{
string strAuth = String.Format
(“0x{0:2x}{1:2x}{2:2x}{3:2x}{4:2x}{5:2x}”,
(Int16)sidBytes[1],
(Int16)sidBytes[2],
(Int16)sidBytes[3],
(Int16)sidBytes[4],
(Int16)sidBytes[5],
(Int16)sidBytes[6]);
strSid.Append(“-“);
strSid.Append(strAuth);
}
else
{
Int64 iVal = (Int32)(sidBytes[1]) +
(Int32)(sidBytes[2] << 8) +
(Int32)(sidBytes[3] << 16) +
(Int32)(sidBytes[4] << 24);
strSid.Append(“-“);
strSid.Append(iVal.ToString());
}

        // Get sub authority count…
int iSubCount = Convert.ToInt32(sidBytes[7]);
int idxAuth = 0;
for (int i = 0; i < iSubCount; i++)
{
idxAuth = 8 + i * 4;
UInt32 iSubAuth = BitConverter.ToUInt32(sidBytes, idxAuth);
strSid.Append(“-“);
strSid.Append(iSubAuth.ToString());
}
}
catch (Exception ex)
{
return “”;
}
return strSid.ToString();
}

Using this function, you can now write out the SID’s for each user:

DirectoryEntry dir = new DirectoryEntry(“WinNT://localhost/Administrators”);
Console.WriteLine(“Enumerating users”);
foreach (object obj in (IEnumerable)dir.Invoke(“members”))
{
DirectoryEntry user = new DirectoryEntry(obj);
Console.WriteLine(user.Path);
System.DirectoryServices.PropertyCollection col = user.Properties;
byte[] sidBytes = col[“objectSid”].Value as byte[];
if (sidBytes != null)
{
string strSid = ConvertByteToStringSid(sidBytes);
if (!string.IsNullOrEmpty(strSid))
{
Console.WriteLine(“SID=” + strSid);
}
}
}
Console.WriteLine(“Done”);
Console.ReadLine();

Of course we are not in the business of writing SID’s for users in a console application, but you should now have the building blocks for creating whatever mechanism to add A/D users to NAV, exposing the codeunit we talked about at first and then calling these web services functions from the console application.

Good luck

Enjoy

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

 

 

 

Running Code on the Client without deploying COM objects

Yes, it can be done!

No, it isn’t .net code – nor AL code.

Why?

It started out as me being a little too fast when stating that you could easily download a file to the Client and attach it to Outlook without any user interaction – and as you might know that is true, but you might also know that if you go the recommended route:

FILE.DOWNLOAD(FileName, ”, ‘<TEMP>’,”, ToFile);
Mail.NewMessage(”,”,Name,”,ToFile,TRUE);
FILE.ERASE(FileName);

Then you will get an e-mail that looks like this:

image

People might not assume that this actually is Invoice no. 10103 in PDF format. What you of course want to have is:

image

So, how do we get there.

I actually did respond to a post on mibuso a while back (http://www.mibuso.com/forum/viewtopic.php?f=32&t=29806) about how this could be done, but that would involve a COM object deployed to all clients and not everybody wants that (although I have posted a method on how to do this automatically).

The problem here is, that file.download always places the file in a temporary directory with a temporary filename – and there is (to my knowledge) no other way to copy a file to the Client.

Assuming that this is correct, how do we then rename a file on the client without having to deploy COM objects?

I said without deploying COM objects, not without USING COM objects

As you know, we can run COM objects on the server or on the Client and one of the COM objects, which ships with Windows can come in handy here. The Windows Script Host – if we instantiate this COM object we can actually give the component a VB Script to execute in the context of the COM component (which would be either on the Server or on the Client).

Windows Script Host

Yes, WSH is legacy – but it is widely used and it is included on all Windows versions from Windows XP and up. I am not going to make this a tutorial on VBScript and WSH – for that you can find a number of good posts on the internet – or start by reading msdn

http://msdn.microsoft.com/en-us/library/ms950396.aspx

Creating a script function / method

The method AddCode on the Windows Script Host COM object is used to add sourcecode to the component.

Note, that you need to add an entire function / method in one call and note, that each line needs to be terminated by a CR.

You also need to specify what language you use, the control supports JScript and VBScript.

A VBScript function which returns Hello <name> could look like this:

function Hello(who)
Hello = “Hello “&who
end function

Creating this function in a Client side COM component could look like:

IF CREATE(objScript,TRUE,TRUE) THEN
BEGIN
CR := ‘ ‘; CR[1] := 13;
objScript.Language := ‘VBScript’;
objScript.AddCode(
‘function Hello(who)’+CR+
‘  Hello = “Hello “&who’+CR+
‘end function’);
END;

The way I write this is, that I try to maintain the structure of the VBScript even though it is inside a string in NAV, maybe I am fooling myself, but I think it is more readable.

Invoking a script function / method

There are two ways of invoking a script method:

Eval – used to invoke a function, and get a return value back.

The above function could be called using

MESSAGE(FORMAT(objScript.Eval(‘Hello(“Freddy”)’)));

Note – when calling functions, VBScript wants your parameters embraced by parentheses.

ExecuteStatement – used to invoke a method which doesn’t return anything

Let’s rewrite the above function to a method and have the method show a MessageBox:

The VBScript could look like:

sub Hello(who)
MsgBox “Hello “&who, 0, “Title”
end sub

and creating this function in a COM object and calling the method could look like:

IF CREATE(objScript,TRUE,TRUE) THEN
BEGIN
CR := ‘ ‘; CR[1] := 13;
objScript.Language := ‘VBScript’;
objScript.AddCode(
‘sub Hello(who)’+CR+
‘  MsgBox “Hello “&who, 0, “Test”‘+CR+
‘end sub’);
objScript.ExecuteStatement(‘Hello “Freddy”‘);
END;

Note – when calling methods (or sub’s) VBScript does NOT want the parameters embraced by parentheses.

Some sample scripts

Rename a temporary file

function RenameTempFile(fromFile, toFile)
set fso = createobject(“Scripting.FileSystemObject”)
set x = createobject(“Scriptlet.TypeLib”)
path = fso.getparentfoldername(fromFile)
toPath = path+””+left(x.GUID,38)
fso.CreateFolder toPath
fso.MoveFile fromFile, toPath+””+toFile
RenameTempFile = toPath
end function

As you can see, I am doing exactly what I responded on the mibuso thread here – just in VBScript instead – which then requires no client side install.

BTW this function is actually used in ClausL’s post about sending e-mail with PDF attachments, which proves that we do talk with our colleagues at Microsoft:-). Note that there is no good way of creating a GUID from VBScript – I (mis)use the fact that every instance of Scriptlet.TypeLib gets assigned a new GUID.

Get Machine name

function GetComputerName()
set net = createobject(“wscript.network”)
GetComputerName = net.ComputerName
end function

I know, that you also can read an environment variable – but this way you can actually get all kind of information on the network though this.

Launch an application

sub Notepad()
set shell = createobject(“WScript.Shell”)
shell.Run “notepad.exe”
end sub

Yes, you can do this by using the Shell object directly in NAV, like:

Shell       Automation       ‘Microsoft Shell Controls And Automation’.Shell

CREATE(objShell,True,true);
objShell.Open(‘c:\windows\system32\notepad.exe’);

I just wanted to show that you that stuff like this can be done in VBScript too, and note, that the Shell object in VBScript and in NAV is not the same.

Asking a simple question

function Input(question, title, default_answer)
Input = InputBox(question, title, default_answer)
end function

A couple of partners have told me, that they are unhappy with the discontinuation of INPUT from NAV and having to create pages for even the simplest questions. Running the following code:

IF CREATE(objScript,TRUE,TRUE) THEN
BEGIN
CR := ‘ ‘; CR[1] := 13;
objScript.Language := ‘VBScript’;

  objScript.AddCode(
‘function Input(question, title, default_answer)’+CR+
‘  Input = InputBox(question, title, default_answer)’+CR+
‘end function’);

  s := objScript.Eval(‘Input(“How old are you?”, “A simple question”, “”)’);
MESSAGE(s);
END;

Brings up this dialog on my machine:

image

Who knows, maybe somebody can use this as an alternative to INPUT.

Read the RoleTailored Client configuration file

function ReadConfigFile()
set shell = CreateObject(“WScript.Shell”)
folder = shell.ExpandEnvironmentStrings(“%LOCALAPPDATA%”)
if folder = “” then folder = shell.ExpandEnvironmentStrings(“%USERPROFILE%”)&”Local SettingsApplication Data”
  filename = folder&”MicrosoftMicrosoft Dynamics NAVClientUserSettings.config”
set fso = createobject(“Scripting.FileSystemObject”)
set file = fso.OpenTextFile(filename, 1)
ReadConfigFile = file.ReadAll()
end function

Note that I have NOT tested this function under Windows XP – I know that LOCALAPPDATA is not defined on Windows XP and I think the line:

  if folder = “” then folder = shell.ExpandEnvironmentStrings(“%USERPROFILE%”)&”Local SettingsApplication Data”

should take care of finding the right folder – if anybody can confirm that, then add that as a comment to this post.

Bringing up a MESSAGE with the outcome of this function on my machine gives me this dialog:

image

I don’t know whether that could come in handy, but maybe it can spawn off some good ideas.

Wrapping up

As you can see, you can do a lot of things in VB Script on the Client (or on the Server). There are a number of scripts you can find on the internet to work with the A/D (create, delete and enumerate users).

Of course there a limitations as to what you can do in VBScript and it isn’t a real alternative to writing a COM component, but for something it is easy and straightforward – and it doesn’t require any client side installation of components and this works in both Classic and RTC.

You can download the rename function from ClausL’s post about sending e-mail with PDF attachments. You will need to do copy, paste and maybe modify the other samples in order to use them.

 

Enjoy

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Installing NAV 2009 without A/D

The majority of installations of NAV 2009 will be in a network environment, you will have a domain server and an Active Directory with your users and your biggest worries will be how to setup a 3T environment on 3 different boxes, getting the delegation setup correctly all of that stuff.

But in a few cases we might run into situations, where there is only a LAN between a number of machines and no server.

In this post I will show what it takes to make one XP computer run the RoleTailored Client (or the Classic) on one XP computer and have another XP computer be the Database Tier, Service Tier – and of course run a client too.

WorkXP1 and WorkXP2

First of all – I have installed two XP computers with XP SP3 and nothing else – no Office, no Visual Studio, no nothing – just plain good old XP.

In both XP’s I have used the Set up a home or small office network wizard to make the computers join a workgroup and be able to see each other

image

The computers are named WorkXP1 and WorkXP2 – and there is one user on each computer XP1 on WorkXP1 and XP2 on WorkXP2.

So what I want to do, is to install the full NAV 2009 on WorkXP1 and only the Clients on WorkXP2 – and make WorkXP2 run with WorkXP1 as the Service Tier and Database tier for the RTC or the Classic. I will also see that I can in fact run Web Services from WorkXP2 as well.

I did not try to install the database tier separate from the Service Tier, as I do not think it is relevant in a scenario like this.

Installing the full NAV2009 on WorkXP1

Note, that I cannot just insert the DVD and press Install Demo, because I don’t have Microsoft Outlook installed, so I select Choose an installation option and then Customize under Server (or Client or Database Components) and select the following options

image

and then just accept the default setup values and install.

Installing the Clients on WorkXP2

on the WorkXP2 we install both clients – Again Choose an Installation option and select customize under Client and Select to run Classic from My Computer.

image

After selecting Next you will need to Click the RoleTailored Client link and specify the Service Tier computer (which is WorkXP1).

image

Installing…

If you wonder how – I am running this experiment as two virtual machines on a Windows 2008 Server running Hyper-V.

image

image

Workgroup networking

As you might have guessed – starting the clients on WorkXP1 just works, and trying the same on WorkXP2 doesn’t.

We need to do 3 things in order to make this work…

1. Create the XP2 user on WorkXP1

The way workgroups works is to authenticate the XP2 user on WorkXP1 using his own Username and Password, so you need to create all the users on the “server”, and note, then need to have the same password.

image

After this you of course need to create the user in NAV (using the Classic Client on WorkXP1) and give him the right permissions (In my world everybody is SUPER).

Having done this, we try to start the Classic Client on the WorkXP2 machine and get the following result:

image

This means that the firewall is blocking access to the SQL Server.

2. Open the firewall for SQLSERVR (if you want to run Classic)

On WorkXP1 open the Control Panel and open Windows Firewall. Click Add Program, browse and locate SQLSERVR.EXE under C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Binn add this to the list of exceptions:

image

At the top of the list of exceptions you will BTW. find DynamicsNAVServer – which means that the RoleTailored actually doesn’t need this setting.

Trying to connect from the Classic Client now will give a different error:

image

The reason for this is that XP by default is running Simple Sharing, meaning that everybody will login as Guest on a different computer and not trying to login with their own Username and Password.

3. Disable Simple Filesharing on WorkXP1

On WorkXP1 in the Control Panel or any explorer window select Tools -> Folder Options, goto the View Tab and remove the checkmark from Use simple file sharing.

image

After having done this – you should be able to connect using the Classic Client and also the RoleTailored Client should now work.

image

 

It is probably not any different than what you would do with NAV 5.0 – but a number of people has asked me whether or not it is possible – and it is.

BTW – Web Services works in exactly the same way. If you start the Web Service listener on WorkXP1 – you can navigate to

http://WorkXP1:7047/DynamicsNAV/WS/CRONUS%20International%20Ltd/Services (this is the SP1 URL)

You might have to open the firewall for port 7047 if it doesn’t pick up that DynamicsNAVServer is listening on both ports.

 

Enjoy

 

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV