PowerShell and Native Libraries
Posted: 2024-07-03 Filed under: Uncategorized | Tags: Native libraries, Post-build, PowerShell, Visual Studio Leave a commentI had been dabbling with PowerShell script modules to make Http Requests to web-service endpoints and decided I should try and directly host my code from a command-line interface. I first awkwardly stumbled around with making my own command-line processor and got slightly mesmerized by ANSI control sequences to do neat things with colors and progress bars, but eventually went back to PowerShell and Binary modules.
I had been (slowly) revamping the Ikosa Framework for the past year and a half and had finally switched my serialization from OPC through Zip-files before landing on SQLite.
Discordance of Native Platform Libraries
Unfortunately, SQLite uses native libraries (wrapped in managed calling interfaces in .NET) and while this is not a problem for .NET, it can be a problem for PowerShell.
Visual Studio and .NET Runtimes agree that native libraries (DLLs on Windows, and various other file extensions on other platforms) should be in a specialized sub-folder of the assembly’s base directory.
Since there are many “runtime-IDs” there is one folder to contain them all called “runtimes”, with each “runtime-ID” (RID) as a separate sub-folder, with itself having a couple of subfolders.
Visual Studio and Post-Build .NET Native Library Layout
So for example, on win-x64 the folder should be something like:
\bin\debug\net8.0\MyManagedLibrary.dll
\bin\debug\net8.0\runtimes\win-x64\native\native-x64-library.dll
and so on for each defined RID (e.g., win-x86 => \bin\debug\net8.0\runtimes\win-x86\native).
When packaging, publishing and running everything works out well.
However, with PowerShell Binary Modules (and this is the unfortunate part), consideration for multiple native runtime libraries wasn’t built into the original design. Earlier versions couldn’t even deal with multiple binary versions directly…you would have to make a unique binary module for each platform, and ensure the DLLs were in the correct folder.
PowerShell Native Library Layout
When PowerShell did finally add support for single output .NET assemblies with variant RIDs, it used a similar, but not identical pattern to what Visual Studio and .NET runtime hosts use. There is no “runtimes” subfolder, so each “RID” folder is directly in the managed library’s (i.e., binary module’s) folder, and there is no “native” subfolder under that.
\bin\debug\net8.0\MyManagedLibrary.dll
\bin\debug\net8.0\win-x64\native-x64-library.dll
Different aims between registering an assembly in PowerShell and build/debug/pack/publish/host from Visual Studio or .NET.
But how to bridge the gap?
PowerShell Script in Project Folder
I created a “postbuild.ps1” PowerShell script in my project with the following contents:
$runtimes = get-childItem ".\runtimes"
foreach ($r in $runtimes){
remove-item "$($r.Name)" -recurse -erroraction 'silentlycontinue'
remove-item ".\_native" -recurse -erroraction 'silentlycontinue'
copy-item ".\runtimes\$($r.Name)\native" ".\_native" -recurse -erroraction 'silentlycontinue'
rename-item ".\_native" "$($r.Name)" -erroraction 'silentlycontinue'
}
Post-Build Step to Run the Script
Then I added the following to my .csproj file:
<ItemGroup>
<None Update="postbuild.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="powershell $(TargetDir)postbuild.ps1" WorkingDirectory="$(TargetDir)" />
</Target>
This allowed me to hoist the native libraries up to where PowerShell expects to see them so I could import-module and test directly from build output without breaking for not finding e_sqlite3.dll
