Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c8e19e2
Add integration events for file scenario management and enhance UI ac…
StefanSosic Aug 21, 2025
33071a9
Merge branch 'main' of https://github.com/StefanSosic/BCApps into dev…
StefanSosic Aug 21, 2025
b81f6eb
External Storage - Document Attachments
StefanSosic Aug 25, 2025
4adc2b0
Interface + Improvements
StefanSosic Aug 25, 2025
d04ef8b
Fixes
StefanSosic Aug 25, 2025
4978d52
Trailing space
StefanSosic Aug 25, 2025
6357709
Internal
StefanSosic Aug 25, 2025
e5dbc09
Internal
StefanSosic Aug 25, 2025
6ecd65f
app json fixes
StefanSosic Aug 25, 2025
c454a9a
DataClassification
StefanSosic Aug 25, 2025
b485579
Namespaces
StefanSosic Aug 25, 2025
a28fe49
InherentPermissions + InherentEntitlements
StefanSosic Aug 25, 2025
26e1c3a
Update tooltip for sync direction field in DA External Storage Sync r…
StefanSosic Aug 25, 2025
68ef801
Deafult Scenario
StefanSosic Aug 29, 2025
8832186
Empty Line
StefanSosic Aug 29, 2025
ecd581a
Permission Refactor & Message
StefanSosic Aug 29, 2025
4b57268
Add label for date formula in DA External Storage Sync report
StefanSosic Aug 29, 2025
7eb7ebf
FIxes from PR comments
StefanSosic Oct 9, 2025
bf2f5b6
Fixes and Features
StefanSosic Oct 21, 2025
509618d
Add disable check
StefanSosic Oct 21, 2025
244043f
Remove prefix
StefanSosic Oct 21, 2025
0db7610
Adjustments to deletion
StefanSosic Oct 21, 2025
ad8638e
Remove logger
StefanSosic Oct 21, 2025
e87efe6
Merge branch 'main' of https://github.com/StefanSosic/BCApps into dev…
StefanSosic Oct 27, 2025
f358ddb
Update readme.md
StefanSosic Oct 28, 2025
b6c89f6
Improve warning message
JesperSchulz Dec 19, 2025
4f4c207
Fallback for table name if 3rd party App Uninstalled
StefanSosic Dec 19, 2025
372d316
Merge branch 'dev/sso/externalStorageApp' of https://github.com/Stefa…
StefanSosic Dec 19, 2025
f12957b
Add migration report
StefanSosic Dec 19, 2025
14164b2
File Scenario fix breaking change
StefanSosic Jan 19, 2026
3e86854
Merge branch 'main' of https://github.com/StefanSosic/BCApps into dev…
StefanSosic Jan 19, 2026
53648cf
Remove
StefanSosic Jan 19, 2026
852773a
Fix
StefanSosic Jan 19, 2026
3e45ce4
Remove upload
StefanSosic Jan 19, 2026
3e9f8a5
Fix Copy
StefanSosic Jan 19, 2026
fca4ef1
Edge case
StefanSosic Jan 19, 2026
4edda71
One Drive
StefanSosic Jan 19, 2026
90f2a8a
Polishing
StefanSosic Jan 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
159 changes: 159 additions & 0 deletions src/Apps/W1/External Storage - Document Attachments/app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Document Attachments External Storage for Microsoft Dynamics 365 Business Central

## Overview

The External Storage extension provides seamless integration between Microsoft Dynamics 365 Business Central and external storage systems such as Azure Blob Storage, SharePoint, and File Shares. This extension automatically manages document attachments by storing them in external storage systems while maintaining full functionality within Business Central.

## Key Features

### **Multi-Tenant, Multi-Environment, and Multi-Company Support**
- **Environment Hash**: Unique hash based on Tenant ID + Environment Name + Company System ID
- **Organized Folder Structure**: Files are stored in hierarchical folders: `RootFolder/EnvironmentHash/TableName/FileName`
- **Cross-Environment Compatibility**: Files from different tenants, environments, or companies are properly isolated
- **Migration Support**: Built-in migration tool to move files between company folders when needed
- **Environment Hash Display**: View current environment hash for reference and troubleshooting

### **Flexible Deletion Policies**
- **Delete from External Storage**: Optionally delete files from external storage when attachments are removed from BC
- **Automatic Cleanup**: Scheduled job queue can automatically delete expired files based on retention policy

### **Customizable Root Folder**
- Configure a custom root folder path for all attachments
- Interactive folder browser for easy selection
- Automatic folder creation and hierarchy management

### **Bulk Operations**
- Synchronize multiple files between internal and external storage
- Bulk upload to external storage
- Bulk download from external storage
- Progress tracking with detailed reporting

## Installation & Setup

### Prerequisites
- Microsoft Dynamics 365 Business Central version 28.0 or later
- File Account module configured with external storage connector
- Appropriate permissions for file operations

### Installation Steps

1. **Configure File Account**
- Open **File Accounts** page
- Create a new File Account with your preferred connector:
- Azure Blob Storage
- SharePoint
- File Share
- Assign the account to **External Storage** scenario

2. **Configure External Storage**
- Open **File Accounts** page
- Select assigned **External Storage** scenario
- Open **Additional Scenario Setup**
- Configure settings:
- **Enabled**: Enable the External Storage feature
- **Root Folder**: Select the root folder path for attachments (use AssistEdit to browse)
- **Delete from External Storage**: Enable to delete external files when attachments are removed from BC

### Configuration Options

#### General Settings
- **Enabled**: Master switch to activate/deactivate the External Storage feature
- **Root Folder**: Base folder path in external storage where all attachments will be organized
- Files are stored in: `RootFolder/EnvironmentHash/TableName/FileName`
- Use AssistEdit button to browse and select folder interactively

#### Upload and Delete Policy
- **Delete from External Storage**: When enabled, files are deleted from external storage when the attachment is removed from Business Central


## Usage

### Multi-Company and Multi-Environment Support

#### Environment Hash
Every file uploaded to external storage includes an environment hash that uniquely identifies:
- **Tenant ID**: Your Business Central tenant
- **Environment Name**: Current environment (e.g., Production, Sandbox)
- **Company System ID**: Unique identifier for the company

This ensures files from different tenants, environments, or companies are properly isolated in external storage.

#### Folder Structure
Files are organized hierarchically:
```
RootFolder/
├── [EnvironmentHash-1]/
│ ├── Sales_Header/
│ │ └── invoice-{guid}.pdf
│ └── Purchase_Header/
│ └── order-{guid}.pdf
└── [EnvironmentHash-2]/
└── Sales_Header/
└── quote-{guid}.pdf
```

#### File Migration
When moving data between environments or companies:
1. Open **External Storage Setup** page
2. Click **Migrate Files** action
3. System automatically:
- Identifies files from previous environment/company
- Copies files to current environment/company folder structure
- Updates file paths and environment hash
- Maintains all file metadata and associations

#### Environment Hash Display
- Click **Show Current Environment Hash** to view your current hash
- Use this hash to identify your files in external storage
- Helpful for troubleshooting and cross-environment scenarios

### Manual Operations

#### Setup Page Actions
From **External Storage Setup** page:
- **Storage Sync**: Run synchronization manually to upload/download files
- **Migrate Files**: Migrate all files from previous environment/company to current folder structure
- **Show Current Environment Hash**: Display the current environment hash for reference
- **Document Attachments**: Open the list of all document attachments with external storage information

#### Individual File Operations
From **Document Attachment - External** page:
- **Upload to External Storage**: Upload selected file manually
- **Download from External Storage**: Download file for viewing
- **Download to Internal Storage**: Restore file to internal storage
- **Delete from External Storage**: Remove file from external storage
- **Delete from Internal Storage**: Remove file from internal storage only

#### Bulk Operations
From **External Storage Synchronize** report:
- **To External Storage**: Upload multiple files to external storage
- **From External Storage**: Download multiple files from external storage
- **Delete Expired Files**: Clean up files based on retention policy

### File Access and Compatibility
- Files uploaded to external storage remain fully accessible through standard Business Central functionality
- Document preview, download, and management work seamlessly
- Files deleted internally are automatically retrieved from external storage when accessed
- No change to end-user experience
- Cross-environment and cross-company access is handled automatically

## Important Notes

### Data Safety
- **This extension is provided as-is**
- Always maintain proper backups of your external storage
- Test thoroughly in a sandbox environment before production use
- Verify file accessibility after migration

### Environment Changes
- When moving between environments, use the **Migrate Files** action
- Environment hash changes with tenant, environment, or company changes
- Files from previous environments are not automatically deleted
- Manual cleanup of old environment folders may be required

### Feature Disable Protection
- Cannot disable External Storage setup if files are uploaded
- Must delete all uploaded files before disabling the feature
- Cannot unassign External Storage scenario if files exist in external storage

**© 2025 Microsoft Corporation. All rights reserved.**
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"id": "5f2e93a0-6083-4718-b05a-7ac89be5644d",
"name": "External Storage - Document Attachments",
"publisher": "Microsoft",
"version": "28.0.0.0",
"brief": "External Storage processor for Business Central document attachments",
"description": "Processes document attachments from Business Central and stores them in configured External Storage connectors (Azure Blob, File Share, SharePoint)",
"privacyStatement": "https://go.microsoft.com/fwlink/?linkid=724009",
"EULA": "https://go.microsoft.com/fwlink/?linkid=2009120",
"help": "https://go.microsoft.com/fwlink/?linkid=2134520",
"url": "https://go.microsoft.com/fwlink/?linkid=724011",
"logo": "ExtensionLogo.png",
"application": "28.0.0.0",
"platform": "28.0.0.0",
"internalsVisibleTo": [
],
"dependencies": [],
"screenshots": [],
"idRanges": [
{
"from": 8750,
"to": 8770
}
],
"resourceExposurePolicy": {
"allowDebugging": true,
"allowDownloadingSource": true,
"includeSourceInSymbolFile": true
},
"features": [
"TranslationFile"
],
"contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2134520",
"target": "Cloud"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------

namespace Microsoft.ExternalStorage.DocumentAttachments;

using Microsoft.Foundation.Attachment;

/// <summary>
/// Report for migrating document attachments from previous environment/company folder to current environment/company folder.
/// Can be scheduled via job queue for background processing.
/// </summary>
report 8753 "DA External Storage Migration"
{
Caption = 'External Storage Migration';
ProcessingOnly = true;
UseRequestPage = true;
Extensible = false;
ApplicationArea = Basic, Suite;
UsageCategory = None;
Permissions = tabledata "DA External Storage Setup" = r,
tabledata "Document Attachment" = rimd;

dataset
{
dataitem(DocumentAttachment; "Document Attachment")
{
trigger OnPreDataItem()
begin
// Filter for files that are uploaded externally and need migration
SetRange("Uploaded Externally", true);
SetFilter("External File Path", '<>%1', '');

TotalCount := Count();

if TotalCount = 0 then begin
if GuiAllowed() then
Message(NoFilesToMigrateLbl);
CurrReport.Break();
end;

ProcessedCount := 0;
MigratedCount := 0;
FailedCount := 0;

if GuiAllowed() then
Dialog.Open(ProcessingMsg, TotalCount);
end;

trigger OnAfterGetRecord()
begin
ProcessedCount += 1;

if GuiAllowed() then
Dialog.Update(1, ProcessedCount);

// Check if file needs migration
if ExternalStorageImpl.IsFileFromAnotherEnvironmentOrCompany(DocumentAttachment) then
if ExternalStorageImpl.MigrateFileToCurrentEnvironment(DocumentAttachment) then
MigratedCount += 1
else
FailedCount += 1;

Commit(); // Commit after each record to avoid lost in communication error with external storage service
end;

trigger OnPostDataItem()
begin
if MigratedCount > 0 then
LogMigrationTelemetry();

if GuiAllowed() then begin
if TotalCount <> 0 then
Dialog.Close();

if MigratedCount > 0 then
Message(MigrationCompletedMsg, MigratedCount, FailedCount)
else
Message(NoFilesToMigrateLbl);
end;
end;
}
}

requestpage
{
SaveValues = true;

layout
{
area(Content)
{
group(General)
{
Caption = 'General';
label(InfoLabel)
{
ApplicationArea = Basic, Suite;
Caption = 'This report will migrate all document attachments from a previous environment or company folder to the current environment/company folder in external storage.';
}
}
}
}
}

var
ExternalStorageImpl: Codeunit "DA External Storage Impl.";
Dialog: Dialog;
FailedCount: Integer;
MigratedCount: Integer;
ProcessedCount: Integer;
TotalCount: Integer;
MigrationCompletedMsg: Label '%1 file(s) have been successfully migrated to the current company folder. %2 failed.', Comment = '%1 = Number of migrated files, %2 = Number of failed migrations';
NoFilesToMigrateLbl: Label 'No files need to be migrated.';
ProcessingMsg: Label 'Processing #1###### attachments...', Comment = '%1 - Total Number of Attachments';

local procedure LogMigrationTelemetry()
var
DAFeatureTelemetry: Codeunit "DA Feature Telemetry";
begin
DAFeatureTelemetry.LogCompanyMigration();
end;
}
Loading