Super Tips for Testing Dynamics AX File Permission Access
Batch processes which use file input are very common designs in Dynamics AX, but there are often assumptions made by the AX developer as to their ability to read/write/execute files at the time they will be processed. Reading a file for input to AX is generally pretty simple, but a different permission set may be needed when AX needs to write results out to a file in a batch process. Further complicating the issue is that often these batch processes are tested with a manual run (probably processing code on the AX client) then executed in production using the automated batch process (which runs on the AOS server). If care isn't taken to ensure that your export code can execute on the server (which is very common), you may encounter a situation where the test export runs fine, but the production batch fails writing the file.
We have a periodic routine which reads table data in AX and exports a flat text file to a file share for use by an Electronic Data Interchange (EDI) partner. Our custom class which collects the data and exports to the file funs on the client by default. In the custom class we can develop around the usual file handling problems using InteropPermissions, but when we run the export on the batch in production AX, the job failed writing the output file. The problem isn't so much that we don't know how to handle this scenario in code, but that our users may want a tool in AX to show them whether or not a batchable job will have access to write and output file prior to tossing it onto the batch and hoping for the best.
Our solution here will show how to borrow some code from the Application Integration Framework to test file IO permissions from both the client and server contexts given an output file path. With this tool we should be able to test file permissions before scheduling a batch job, giving us a high degree of certainty that our batch process will finish successfully.
We found some very relevant code in the AifFileSystemAdapter class, validateTransportAddress method which provides a sort of template for how we can test file path permissions.
The resulting code looks like this:
client server static void validateExportfilePermissions(FilePath _filePath,
NoYesId _checkRead,
NoYesId _checkWrite,
NoYesId _checkDelete)
{
int requiredPermissions;
int directoryPermissions;
str errorMessage;
Microsoft.Dynamics.IntegrationFramework.Adapter.FileSystem fileSystem;
#AIF
;
if (_checkRead)
{
requiredPermissions = AifDirectoryPermission::ReadFile;
}
if (_checkWrite)
{
requiredPermissions = requiredPermissions | AifDirectoryPermission::WriteFile;
}
if (_checkDelete)
{
requiredPermissions = requiredPermissions | AifDirectoryPermission::DeleteFile;
}
errorMessage = "@SYS95130";
new InteropPermission(InteropKind::ClrInterop).assert();
fileSystem = AifUtil::getClrObject(#FileSystemProgId);
CodeAccessPermission::revertAssert();
// Check that path exists
new InteropPermission(InteropKind::ClrInterop).assert();
if (!fileSystem.DoesDirectoryExist(_filePath))
throw error(strfmt("@SYS95128", _filePath));
CodeAccessPermission::revertAssert();
// Check required directory permissions
new InteropPermission(InteropKind::ClrInterop).assert();
directoryPermissions = fileSystem.VerifyDirectoryPermissions(_filePath, requiredPermissions);
CodeAccessPermission::revertAssert();
if (requiredPermissions != directoryPermissions)
{
throw error(strfmt(errorMessage, _filePath));
}
}
Effectively all we're really doing is using a .Net file system object to test directory permissions. If the directory doesn't exist, or the required permissions aren't found, errors will be thrown. For that reason our main method uses try..catch statements when checking from the client and server contexts. Our server test method is a simple wrapper for validateExportfilepermissions ensuring the code runs on the server:
server static void serverTest(FilePath _filePath,
NoYesId _checkRead,
NoYesId _checkWrite,
NoYesId _checkDelete)
{
;
info("Server test start...");
info(strFmt(" File path: %1", _filePath));
info(strFmt(" User: %1", WinAPIServer::getUserName()));
FileIOPermsTest::validateExportfilePermissions(_filePath, _checkRead, _checkWrite, _checkDelete);
info("Server test passed");
}
Similarly, our client code test simply ensures the validation method runs on the AX client:
client static void clientTest(FilePath _filePath,
NoYesId _checkRead,
noYesId _checkWrite,
NoYesId _checkDelete)
{
;
info("Client test start...");
info(strFmt(" File path: %1", _filePath));
info(strFmt(" User: %1", WINAPI::getUserName()));
FileIOPermsTest::validateExportfilePermissions(_filePath, _checkRead, _checkWrite, _checkDelete);
info("Client test passed");
}
You'll notice when running this code that the server test will run as the user login for the windows service administrating the Dynamics AOS. When running the client test, we'll see that the file path is validated against the current user's permissions. One caveat we haven't mentioned is that AX tries at every turn to guide you toward using UNC file paths whenever possible. This is due to the impossibility of server-side code to correctly choose drive letters at run-time (so always use a UNC path and never drive letters in file path dialogs).
Using this method we have been able to provide our users with a predictable tool for testing file output permissions on batchable jobs. We hope this approach is helpful in troubleshooting and proactively determining a successful batch run.