Migrate document attachments to AX7

The Archive directory, Database and the Original location options for storing file attachments in the "New Dynamics AX" aka "Dynamics AX Online" aka AX7 are depreciated in favor of the Azure Storage or SharePoint. The option Archive directory (see Organization administration > Document management > Document types) now designates the Azure Blob Storage, the Database option is present but dysfunctional, and the Original location disappeared altogether.

What if you have to migrate existing attachments from an AX 2009 or AX 2012 system and leave them operational i. e. viewable from the original record?

In the cases Archive directory or Original location you are in deep trouble. You may try to copy the files to the Azure Storage by means of the Azure Storage Explorer, but the name of the file must be converted into a deterministic GUID, and a link to this GUID has to be re-established in the DocuValue table. It also seems that the DLL managing files in Azure for AX7 relies on some metadata in the blob, and fails to produce a download URL from a faked GUID. I suggest writing a script in the legacy AX 2012 to absorb the external files to the database first.

For the “Database”-stored attachments there is [yet] no official upgrade path, but the below runnable class can quickly dump the files preserved in the DocuValue table to the Azure Storage through a memory stream, and rejuvenate the DocuValue table with the GUID and a link to the blob. The script also updates the Location in all file document types from Database to Archive directory (meaining the Azure Storage).

 class DocuFileMigrateDBtoAzure
{
    public static void main(Args _args)
    {
        DocuValue           docuValue;
        DocuRef             docuRef;
        DocuType            docType;
        System.IO.Stream    fileStream;
        Microsoft.Dynamics.AX.Framework.FileManagement.DocumentLocation         location;
        Microsoft.Dynamics.AX.Framework.FileManagement.IDocumentStorageProvider storageProvider;
        DataArea            dataArea;
        ;
        while select dataArea
        {
            changecompany(dataArea.id)
            {
                docType = null;
                docType.skipTTSCheck(true);
                while select forupdate docType
                    where docType.TypeGroup == DocuTypeGroup::File
                {
                    if (docType.FilePlace == DocuFilePlace::Database)
                    {
                        docType.FilePlace = DocuFilePlace::Archive;
                        docType.update();
                    }
                    storageProvider = Docu::GetStorageProvider(docType, false, curUserId());
                    if (storageProvider == null)
                        continue;
                    while select forupdate docuValue
                        where docuValue.StorageProviderId == 0
                           && docuValue.Type == DocuValueType::Others
                    join docuRef
                        where docuRef.ValueRecId      == docuValue.RecId
                           && docuRef.ActualCompanyId == docType.DataAreaId
                           && docuRef.TypeId          == docType.TypeId
                    {
                        fileStream = Binary::constructFromContainer(docuValue.File).getMemoryStream();
                        if (fileStream.Length == 0)
                            continue;
                        docuValue.FileId = newGuid();
                        location = storageProvider.SaveFile(
                                docuValue.FileId,
                                storageProvider.GenerateUniqueName(docuValue.OriginalFileName),
                                System.Web.MimeMapping::GetMimeMapping(docuValue.OriginalFileName),
                                fileStream);
                        fileStream.Close();
                        if (location == null)
                            continue;
                        if (location.NavigationUri)
                        {
                            docuValue.Path = location.get_NavigationUri().ToString();
                        }
                        if (location.AccessUri)
                        {
                            docuValue.AccessInformation = location.get_AccessUri().ToString();
                        }
                        docuValue.StorageProviderId = storageProvider.ProviderId;
                        docuValue.update();
                    }
                }
            }
        }
    }
}