Skip to content
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
79 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
36318ea
Merge branch 'main' of https://github.com/microsoft/BCApps into dev/s…
JesperSchulz Jan 27, 2026
3c06e87
Fixes & Polishing
StefanSosic Jan 29, 2026
c3fe884
Merge branch 'main' of https://github.com/microsoft/BCApps into dev/s…
JesperSchulz Jan 30, 2026
82a537f
Add setup page invocation automatically on insert scenario
StefanSosic Jan 30, 2026
2687d6c
Improvements
StefanSosic Jan 30, 2026
e625564
Merge branch 'dev/sso/externalStorageApp' of https://github.com/stefa…
JesperSchulz Jan 30, 2026
913abe5
Report optimize
StefanSosic Jan 30, 2026
cdd9fa2
Merge branch 'dev/sso/externalStorageApp' of https://github.com/stefa…
JesperSchulz Jan 30, 2026
fb1d1dd
Drill down
StefanSosic Jan 30, 2026
b34c455
Init true
StefanSosic Jan 30, 2026
c55ef6f
Merge branch 'dev/sso/externalStorageApp' of https://github.com/stefa…
JesperSchulz Jan 30, 2026
94e326b
Fix
StefanSosic Jan 30, 2026
07ed610
Merge branch 'dev/sso/externalStorageApp' of https://github.com/stefa…
JesperSchulz Jan 30, 2026
1fd2540
Merge branch 'main' of https://github.com/StefanSosic/BCApps into dev…
StefanSosic Feb 4, 2026
b7ffcc4
Add tests for External Storage - Document Attachments functionality
StefanSosic Feb 4, 2026
585f7f8
New Guid
StefanSosic Feb 4, 2026
5eeff8d
Merge branch 'dev/sso/externalStorageApp' of https://github.com/stefa…
JesperSchulz Feb 5, 2026
bd39320
Merge branch 'main' of https://github.com/microsoft/BCApps into dev/s…
JesperSchulz Feb 5, 2026
711568d
Merge branch 'main' of https://github.com/microsoft/BCApps into dev/s…
JesperSchulz Feb 9, 2026
8c2d4f0
Updates
StefanSosic Feb 10, 2026
8d65717
Update to 32
StefanSosic Feb 10, 2026
acd40e0
Merge branch 'dev/sso/externalStorageApp' of https://github.com/stefa…
JesperSchulz Feb 10, 2026
d9af01e
Merge branch 'main' of https://github.com/microsoft/BCApps into dev/s…
JesperSchulz Feb 10, 2026
ebffc34
Merge branch 'main' of https://github.com/microsoft/BCApps into dev/s…
JesperSchulz Feb 11, 2026
2168b02
Merge branch 'dev/sso/externalStorageApp' of https://github.com/stefa…
JesperSchulz Feb 11, 2026
a0ce75c
Merge branch 'main' of https://github.com/StefanSosic/BCApps into dev…
StefanSosic Feb 11, 2026
791c1f4
Merge branch 'dev/sso/externalStorageApp' of https://github.com/Stefa…
StefanSosic Feb 11, 2026
ebc2b5d
Merge branch 'dev/sso/externalStorageApp' of https://github.com/stefa…
JesperSchulz Feb 11, 2026
11c33b1
Add telemetry tags.
JesperSchulz Feb 11, 2026
32202ff
Update src/Apps/W1/External Storage - Document Attachments/app/src/Se…
StefanSosic Feb 11, 2026
ae20d03
Update src/System Application/App/External File Storage/src/Scenario/…
StefanSosic Feb 11, 2026
929df45
Update src/System Application/App/External File Storage/src/Scenario/…
StefanSosic Feb 11, 2026
bf03c5c
Fixes to PR Comments
StefanSosic Feb 11, 2026
87a03cc
Merge branch 'dev/sso/externalStorageApp' of https://github.com/Stefa…
StefanSosic Feb 11, 2026
0ca6800
Update src/System Application/App/External File Storage/src/Scenario/…
StefanSosic Feb 11, 2026
af4a2cb
Updates
StefanSosic Feb 11, 2026
717fe2c
All ApplicationArea
StefanSosic Feb 11, 2026
5f375f8
Permissions
StefanSosic Feb 11, 2026
f477e66
Entitlements
StefanSosic Feb 11, 2026
220dc0d
Merge branch 'main' of https://github.com/microsoft/BCApps into dev/s…
JesperSchulz Feb 11, 2026
4f111fe
Merge branch 'dev/sso/externalStorageApp' of https://github.com/stefa…
JesperSchulz Feb 11, 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;
Comment thread
StefanSosic marked this conversation as resolved.
Outdated
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("Stored 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
Loading