A command-line utility for extracting and replacing embedded assembly resources in ExcelDNA XLL add-in files. This tool enables code signing of assemblies that are packed inside XLL files, which is essential for enterprise deployment scenarios where all executables must be digitally signed.
The starting XLL file that ExcelDNA uses contains pre-built .NET assemblies embedded as resources that are not signed. At runtime, ExcelDNA will extract and use these unsigned assemblies, often triggering tools like Microsoft Defender or InTune to block loading of unsigned assemblies and cause the addin to fail to load.
ResourceExtractor allows you to:
- Extract embedded assemblies from an XLL file to disk
- Sign them using your preferred signing tool (signtool, AzureSignTool, etc.)
- Replace the resources in the XLL with the signed versions
The tool handles both ASSEMBLY (uncompressed) and ASSEMBLY_LZMA (LZMA-compressed) resource types, automatically decompressing and recompressing as needed.
- Extracts
ASSEMBLYandASSEMBLY_LZMAembedded resources from XLL files - Automatically decompresses LZMA resources for signing, recompresses on replacement
- Skips already-signed assemblies to avoid redundant work (with
-forceoverride) - Adds
.dllextension to extracted files for compatibility with signing tools - Cleans up extraction folders on each run to ensure fresh state
- Non-zero exit codes on failure for CI/CD integration
Requires .NET 10 SDK.
dotnet publish -c Release -r win-x64The output will be in bin/Release/net10.0/win-x64/publish/ResourceExtractor.exe.
# Extract unsigned assemblies from an XLL file
ResourceExtractor.exe extract MyAddin.xll
# Replace assemblies with signed versions
ResourceExtractor.exe replace MyAddin.xll
# Extract all assemblies (even if already signed) for verification
ResourceExtractor.exe extract MyAddin.xll -forceExtracts embedded assembly resources to folders alongside the XLL file.
ResourceExtractor.exe extract <file.xll> [-force]- Creates
ASSEMBLY/andASSEMBLY_LZMA/folders in the same directory as the XLL - Resources are saved with
.dllextension (e.g.,MYASSEMBLY→MYASSEMBLY.dll) - LZMA-compressed resources are automatically decompressed
- Already-signed assemblies are skipped unless
-forceis specified - Existing extraction folders are deleted before extraction
Options:
-force,-f,--force- Extract all resources regardless of signature status
Replaces embedded resources with signed versions from the extraction folders.
ResourceExtractor.exe replace <file.xll>- Reads
.dllfiles fromASSEMBLY/andASSEMBLY_LZMA/folders - Strips
.dllextension to match original resource names - LZMA resources are automatically recompressed before embedding
- Only replaces resources that exist in the original XLL
# 1. Extract assemblies
ResourceExtractor.exe extract MyAddin-packed.xll
# 2. Sign the extracted DLLs
signtool sign /sha1 THUMBPRINT /tr http://timestamp.digicert.com /td sha256 ^
ASSEMBLY\*.dll ASSEMBLY_LZMA\*.dll
# 3. Replace with signed versions
ResourceExtractor.exe replace MyAddin-packed.xll
# 4. Sign the XLL itself
signtool sign /sha1 THUMBPRINT /tr http://timestamp.digicert.com /td sha256 ^
MyAddin-packed.xll- task: PowerShell@2
displayName: 'Sign XLL embedded assemblies'
inputs:
targetType: 'inline'
script: |
$xllFile = "$(Build.ArtifactStagingDirectory)/MyAddin-packed.xll"
$resourceExtractor = "$(Build.SourcesDirectory)/Tools/ResourceExtractor.exe"
# Extract unsigned assemblies
& $resourceExtractor extract $xllFile
# Collect files to sign
$filesToSign = @()
$filesToSign += Get-ChildItem -Path "ASSEMBLY" -Filter "*.dll" -ErrorAction SilentlyContinue
$filesToSign += Get-ChildItem -Path "ASSEMBLY_LZMA" -Filter "*.dll" -ErrorAction SilentlyContinue
if ($filesToSign.Count -gt 0) {
# Sign with AzureSignTool
azuresigntool sign -kvu "$(KeyVaultUrl)" -kvi "$(ClientId)" `
-kvt "$(TenantId)" -kvs "$(ClientSecret)" -kvc "$(CertName)" `
-tr http://timestamp.digicert.com $filesToSign.FullName
# Replace with signed versions
& $resourceExtractor replace $xllFile
}
# Clean up
Remove-Item -Path "ASSEMBLY" -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -Path "ASSEMBLY_LZMA" -Recurse -Force -ErrorAction SilentlyContinueExcelDNA embeds assemblies as Windows PE resources with custom types:
- ASSEMBLY - Uncompressed .NET assemblies
- ASSEMBLY_LZMA - LZMA-compressed .NET assemblies (smaller XLL size)
- Loads the XLL using Windows resource APIs (
LoadLibraryEx) - Enumerates resources of type
ASSEMBLYandASSEMBLY_LZMA - For each resource:
- Extracts the raw bytes
- Decompresses if LZMA (5-byte header + 8-byte size + compressed data)
- Checks for existing Authenticode signature
- Writes to disk with
.dllextension if unsigned (or if-force)
- Reads signed DLLs from extraction folders
- For
ASSEMBLY_LZMAresources, recompresses using LZMA - Updates the XLL resources using Windows resource update APIs (
BeginUpdateResource,UpdateResource,EndUpdateResource)
The tool uses WinVerifyTrust to detect signatures. A resource is considered "signed" if it has any Authenticode signature present, regardless of trust chain validity. This means self-signed certificates and certificates not in the trust store are still detected as signed.
- Windows (uses Windows resource APIs)
- .NET 10 Runtime (or self-contained build includes runtime)
# Clone the repository
git clone https://github.com/yourusername/ResourceExtractor.git
cd ResourceExtractor
# Build framework-dependent (smaller, requires .NET 10 installed)
dotnet publish -c Release
# Build self-contained (larger, no runtime required)
dotnet publish -c Release -r win-x64 --self-contained trueMIT License - See LICENSE file for details.