CSharp extension: ZIP archive support for InstallExternalLibrary/UninstallExternalLibrary#85
Open
stuartpa wants to merge 6 commits intomicrosoft:dev/sicongliu/dotnet-install-external-libraryfrom
Conversation
e81fdfb to
5377b74
Compare
5377b74 to
82f9cbc
Compare
…fest-based cleanup, conflict detection, ALTER support - Fix build script to use VS 2022 instead of VS 2019 - Add global.json to pin .NET SDK 8.0 - Fix: use library name as-is (no unconditional .dll append) - InstallExternalLibrary: ZIP extraction with file conflict detection, manifest tracking - UninstallExternalLibrary: manifest-based targeted file deletion, bottom-up empty dir cleanup - ALTER EXTERNAL LIBRARY: clean up previous install before re-installing - CHANGES.md documenting all changes
82f9cbc to
10ce19a
Compare
added 4 commits
April 17, 2026 05:18
…LTER New tests: - ManifestWrittenTest, ManifestListsNestedFilesTest - InstallLibNameAliasNoExtensionTest - DirectoryOverlapAllowedTest, FileConflictFailsTest - UninstallPreservesOtherLibrariesTest - UninstallRemovesEmptyNestedDirsTest - AlterExternalLibraryTest Update 3 existing tests to reflect new libName-without-extension naming: - InstallInvalidZipTest, InstallRawDllNotZipTest, InstallZipWithManyFilesTest
- ErrorMessagePopulatedOnFailureTest: verify libError is surfaced to SQL Server for non-existent file, zip-slip, and file-conflict failure modes - UninstallNonZipLibraryTest: raw-DLL install/uninstall (no-manifest path) - InnerZipFileConflictFailsTest: conflict detection in inner-zip code path - TempFolderCleanedUpAfterConflictTest: no GUID temp-dir leaks after failures - AlterFromNonZipToZipTest: ALTER from raw DLL to ZIP (missing-manifest case) - AliasFileRemovedOnUninstallTest: libName-alias file recorded in manifest and removed on uninstall
…validation, Linux case-sensitivity, cleanup
- Alias file now written as {libName}.dll so DllUtils.CreateDllList ({libName}.*) finds it (fixes feature regression on Linux)
- Raw-DLL install writes {libName}.dll; uninstall fallback updated to match
- Defense-in-depth zip-slip check on inner-ZIP entries before adding to manifest and again in CleanupManifest before deleting
- ALTER is now transactional: stage + validate new ZIP before removing old version, with manifest-aware conflict suppression
- Reject library names containing path separators, '..', null, or absolute paths
- Use OS-appropriate path comparer (Ordinal on Linux, OrdinalIgnoreCase on Windows)
- Sort dirs for bottom-up cleanup by separator count, not string length
- Extract conflict detection into single helper (removes duplicate check)
- Trim error messages and comments; update 3 tests to match new alias naming
This PR branch is based on sicongliu's branch which predates PR microsoft#83 (DECIMAL Type Support on main). PR microsoft#83 added Microsoft.Data.SqlClient 5.2.2 as a required runtime dependency for SqlDecimal handling. Without it, sp_execute_external_script fails with HRESULT 0x80004004 when any script touches DECIMAL types.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Extends
InstallExternalLibrary/UninstallExternalLibraryin the .NET Core CSharp Language Extension to support ZIP archives with arbitrary file trees (e.g. skill packages with nested folders), not just flat DLL files. Also makesInstallExternalLibraryidempotent soALTER EXTERNAL LIBRARYworks correctly.+938 / -81 across two files:
language-extensions/dotnet-core-CSharp/src/managed/CSharpExtension.cs— implementationlanguage-extensions/dotnet-core-CSharp/test/src/native/CSharpLibraryTests.cpp— tests (28 total, 14 new)What changed vs. sicongliu's base branch
1. Manifest-based uninstall
UninstallExternalLibraryonly receivesLibraryName+LibraryInstallDirectory— noLibraryFile, so we cannot re-read ZIP entries fromsys.external_librariesat drop time. To work around this,InstallExternalLibrarynow writes a<libName>.manifestfile listing the relative paths of every extracted file.UninstallExternalLibraryreads that manifest and deletes exactly those files — no more, no less.The previous uninstall implementation deleted everything in the shared install directory, which would wipe out unrelated libraries' files.
2. File-level conflict detection on install
Before extracting a ZIP, every entry is checked against the install directory. If a file of the same name already exists (from another library), install fails with a clear error:
Directory (folder) overlaps are allowed — multiple libraries can share a parent folder like
skills/, they just can't overwrite each other's files.3. Empty-directory cleanup on uninstall
After a manifest-driven delete, parent directories of removed files are walked deepest-first (sorted by separator count) and removed only if empty. Shared parent folders survive as long as any other library still has content in them.
4.
ALTER EXTERNAL LIBRARYsupport (transactional re-install)SQL Server may call
InstallExternalLibraryagain for the same library name duringALTER EXTERNAL LIBRARYwithout first callingUninstallExternalLibrary. The install now:If the new ZIP is corrupt or conflicts with another library, the old version is left intact — the install is atomic from the caller's perspective.
5. Defense in depth
ZipFile.ExtractToDirectoryalready guards against zip-slip on extract; this closes the secondary vector where a malicious entry path could be persisted and later deleted on uninstall..., path separators, null characters, and absolute paths inlibNamebefore using it to build on-disk paths.Ordinalon Linux andOrdinalIgnoreCaseon Windows, so/install/Liband/install/libare correctly treated as distinct paths on Linux.Multi-library lifecycle example
After both installs:
skills/what-is-sql/SKILL.mdand its manifest are deleted. Theskills/folder andskills-explain-query's content are preserved because the folder is still non-empty.Last library using
skills/is dropped; the now-emptyskills/folder is removed.Scriptoria.dlland other unrelated libraries are untouched.Error-handling matrix
<libName>.dll; no manifest written<libName>.dllis deleted directlyALTER EXTERNAL LIBRARYwith valid new contentALTER EXTERNAL LIBRARYwith corrupt new content..or path separatorTests
14 new
TEST_Fcases inCSharpLibraryTests.cppcovering:<libName>.dllalias namingALTER-style re-installlibraryErrorThree existing tests updated to assert the corrected
<libName>.dllnaming.