Skip to main content

Comparing AX and Active Directory User Accounts

metod 1

I was recently working with an AX 2009 customer who wanted to compare the user accounts configured in AX with the user accounts in Active Directory. The basic goals were:
1.Find all AX user accounts that no longer exist in Active Directory.
2.Find all accounts that are disabled in Active Directory but not in AX.

It would be great if AX would flag these scenarios for you, but unfortunately it doesn't. If you’re interested in knowing if you have any orphaned accounts or accounts that should probably be disabled in AX, here’s a quick way to do just that.
1.Export AD users to a CSV file. I used a PowerShell command for this step. The command I used requires the Active Directory Module for Windows PowerShell. This is installed by default on domain controllers, but it is also available via the Remote Server Administration Tools for Windows 7 if you want to run it from a workstation instead. http://www.microsoft.com/download/en/details.aspx?id=7887
2.Create a table for the AD user details. I created a new table in the AX database to store the AD user account details so I could easily join this information to the AX user details already stored in the database.
3.Load the contents of the CSV file into the table. I used a bulk insert statement to load the data from the CSV file created in step 1 into the table created in step 2.
4.Query the table for your results. I used 2 simple queries that joined the AD user account table with the AX userinfo table to get the information I needed.

NOTE: See the attached text file for the exact PowerShell commands and SQL statements I used.

In the one real world scenario (AX 2009) that we looked at, AX had 112 orphaned accounts and there were another 75 accounts that were disabled in AD but not in AX.

This procedure should work for both AX 4.0 and 2009. The userinfo table still exists in AX 2012, so the comparison should work with this version too, but there might be some scenarios such as flexible authentication that throw the results off. That's something I haven't really looked into yet.

===========
metod 2

Here's a sample job that does the same:
#define.UserAccountControl ('userFlags')
#define.UF_ACCOUNTDISABLE (0x0002)
Counter numTotal;
Counter numNotFound;
Counter numDisabled;
UserInfo userInfo;
int userAccControl;
COM dirObject;
str dirPath;
;

while select userInfo
where userInfo.networkAlias
&& userInfo.networkDomain
{
numTotal++;
dirPath = strfmt(@"WinNT://%1/%2,User", userInfo.networkDomain, userInfo.networkAlias);
dirObject = COM::getObjectEx(dirPath);
if (dirObject)
{
if (userInfo.enable)
{
userAccControl = dirObject.get(#UserAccountControl);
if (bitTest(userAccControl, #UF_ACCOUNTDISABLE))
{
numDisabled++;
info(strfmt("%1@%2 disabled in AD, but not in AX", userInfo.networkAlias, userInfo.networkDomain));

}
}

dirObject.finalize();
dirObject = null;
}
else
{

numNotFound++;
warning(strfmt(@"%1@%2 - not found", userInfo.networkAlias, userInfo.networkDomain));
}
}

info(strfmt(@"Total: %1, not found: %2, disabled in AD, but not in AX: %3", numTotal, numNotFound, numDisabled));

========

Wrote once a Job to update the SID after a Domain change, but you could also use it to Sync the active accounts between AD and AX

static void ChangeDomain(Args _args)
{
UserInfo userInfo;
xAxaptaUserManager axUsrMgr;
xAxaptaUserDetails axUsrDet;
Boolean doUpdate;
Username user;
SID oldSID;

#define.NewDomain("New.DOMAIN.NET")
;

doUpdate = Box::yesNo("User aktualisieren?", DialogButton::No) == DialogButton::Yes;

axUsrMgr = new xAxaptaUserManager();
ttsbegin;
setPrefix('SID-Aktualisierung');

while select forupdate userInfo
{
axUsrDet = axUsrMgr.getDomainUser(#NewDomain,userInfo.networkAlias);
if(userInfo && axUsrDet)
{
oldSID = userInfo.sid;
userInfo.networkDomain = #NewDomain;
userInfo.sid = axUsrMgr.getUserSid(userInfo.networkAlias, #NewDomain);

if(doUpdate)
{
info(strfmt("Aktualisiert", userInfo.networkAlias));
userInfo.update();
}
}
else
{
error(strfmt("Nicht gefunden", userInfo.networkAlias));
}
}
ttscommit;
}

Popular posts from this blog

Dynamics Axapta: Sales Orders & Business Connector

Well, again folllowing my same idea of writting close to nothing and pasting code, I'll paste in some code to create a sales order from some basic data and the invoice it. I'll try to explain more in the future. AxaptaObject axSalesTable = ax.CreateAxaptaObject("AxSalesTable"); AxaptaRecord rcInventDim = ax.CreateAxaptaRecord("InventDim"); AxaptaRecord rcCustTable = ax.CreateAxaptaRecord("CustTable"); rcCustTable.ExecuteStmt("select * from %1 where %1.AccountNum == '" + MySalesOrderObject.CustAccount + "'"); if (MySalesOrderObject.CurrencyCode.Trim().Length == 0) MySalesOrderObject.CurrencyCode = rcCustTable.get_Field("Currency").ToString().Trim(); string sTaxGroup = rcCustTable.get_Field("taxgroup").ToString().Trim(); //set header level fields axSalesTable.Call("parmSalesName", MySalesOrderObject.SalesName.Trim()); axSalesTable.Call("parmCustAccount", M

Passing values between form and class

Class name is EmplDuplication and Form is EmplTable . void clicked() {    MenuFunction mf;    args args = new Args();    ;     args.record(EmplTable);     mf = new menufunction(identifierstr(EmplDuplication), MenuItemType::Action); mf.run(args); } Meanwhile, in the main() method of the EmplDuplication class, we need to put this Axapta x++ code to get the datasource: static void main(Args args) {     EmplDuplication EmplDuplication; EmplTable localEmplTable; ;     if(args.record().TableId == tablenum(EmplTable)) localEmplTable = args.record();     ... }