Skip to content

Instantly share code, notes, and snippets.

@KyleLeneau
Created August 2, 2026 21:25
Show Gist options
  • Select an option

  • Save KyleLeneau/4baedd56984627bc4540caed9cf0e19b to your computer and use it in GitHub Desktop.

Select an option

Save KyleLeneau/4baedd56984627bc4540caed9cf0e19b to your computer and use it in GitHub Desktop.

Assemble NINA Plugin Zip with MSBuild

Motivation: I really didn't like the xcopy command suggested by the plugin and wanted a better way.

What does this do?

  1. Sets the plugin version dynamically from passed in -p:VersionNumber=1.2.3.4 (default is 9999.0.0.0)
  2. Assembles the Plugin zip folder with your dll's for easy install and sharing during dev (safe to use on non-windows, non-nina machines)
  3. Unzips the resulting zip file to the nina plugin directory if it exists
  4. Generates SHA256 checksums right away
  5. Works with dotnet cli and Visual Studio

How do I use this?

  1. Modify your AssemblyInfo.cs for the dynamic version setting
  2. Modify your csproj to enable Nuget copy and Import new (msbuild) targets file
  3. Add the NINAPlugin.targets to your csproj for the plugin
  4. Modify the NINAPlugin.targets to match your plugin and desired archive location (see NOTE callouts)
// Just comment out the AssemblyVersion and AssemblyFileVersion here, it will dynamically generate
// To set just pass: `-p:VersionNumber=1.2.3.4` to your build command
// [MANDATORY] The assembly versioning
// Should be incremented for each new release build of a plugin
// These get added in a BeforeBuild step from NINAPlugin.targets
// [assembly: AssemblyVersion("1.0.0.1")]
// [assembly: AssemblyFileVersion("1.0.0.1")]
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<AppDataLocalDir>$([System.Environment]::GetFolderPath(SpecialFolder.LocalApplicationData))</AppDataLocalDir>
<!-- NOTE: Modify this if you have a different version -->
<NinaPluginsDirectory>$(AppDataLocalDir)\NINA\Plugins\3.0.0\</NinaPluginsDirectory>
</PropertyGroup>
<PropertyGroup>
<VersionNumber Condition="'$(VersionNumber)' == ''">9999.0.0.0</VersionNumber>
</PropertyGroup>
<!-- Support setting the build number at build and release time.
Named uniquely and hooked in via BeforeTargets rather than named "BeforeBuild":
the SDK's Microsoft.Common.CurrentVersion.targets (imported after this file via
the implicit Sdk.targets) defines its own empty "BeforeBuild" stub, and since the
last-parsed definition of a target wins in MSBuild, naming this target "BeforeBuild"
would silently get overridden and never run. -->
<Target Name="SetPluginVersion" BeforeTargets="BeforeBuild">
<Message Text="Version $(VersionNumber)" Importance="High" />
<ItemGroup>
<AssemblyAttributes Include="AssemblyVersion">
<_Parameter1>$(VersionNumber)</_Parameter1>
</AssemblyAttributes>
<AssemblyAttributes Include="AssemblyFileVersion">
<_Parameter1>$(VersionNumber)</_Parameter1>
</AssemblyAttributes>
</ItemGroup>
<MakeDir Directories="$(IntermediateOutputPath)" />
<WriteCodeFragment
Language="C#"
OutputFile="$(IntermediateOutputPath)Version.cs"
AssemblyAttributes="@(AssemblyAttributes)" />
<ItemGroup>
<Compile Include="$(IntermediateOutputPath)Version.cs" />
</ItemGroup>
</Target>
<!-- Assemble the artifact into a NINA Plugin and optionally install it -->
<Target Name="Assemble" AfterTargets="Build">
<!-- TargetDir/TargetName are defined by the SDK's implicit Sdk.targets import,
which is loaded after this file, so ArtifactFolder must be computed here
(evaluated at execution time) rather than in a top-level PropertyGroup. -->
<PropertyGroup>
<ArtifactFolder>$(TargetDir)$(TargetName)</ArtifactFolder>
</PropertyGroup>
<ItemGroup>
<!-- NOTE - Modify this list to match your needed dependencies -->
<!-- Because this ItemGroup is inside the target, this will enumerate
all files just before calling Copy. If the ItemGroup were outside
the target , it would enumerate the files during evaluation, before
the build starts, which may miss files created during the build. -->
<Dependencies Include="$(TargetDir)CloudNative.*.dll" />
<Dependencies Include="$(TargetDir)NATS.*.dll" />
<Dependencies Include="$(TargetDir)Microsoft.Extensions.*.dll" />
<Dependencies Include="$(TargetDir)System.IO.Pipelines.dll" />
<Dependencies Include="$(TargetDir)System.Text.Json.dll" />
<Dependencies Include="$(TargetDir)System.Text.Encodings.Web.dll" />
</ItemGroup>
<!-- Build up the artifact stage directory -->
<Message Text="Packaging Build Artifacts $(ArtifactFolder)" />
<RemoveDir Directories="$(ArtifactFolder)" />
<MakeDir Directories="$(ArtifactFolder)\dll"/>
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(ArtifactFolder)" />
<Copy SourceFiles="@(Dependencies)" DestinationFolder="$(ArtifactFolder)\dll" />
<!-- Get the version from the built assembly -->
<GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
<Output TaskParameter="Assemblies" ItemName="PluginAssembly"/>
</GetAssemblyIdentity>
<PropertyGroup>
<AssemblyPluginVersion>%(PluginAssembly.Version)</AssemblyPluginVersion>
</PropertyGroup>
<!-- Format a Artifact zip file name, placed in a /build folder at the repository root -->
<PropertyGroup>
<!-- NOTE: change this directory of your zip how you see file ex: `$(MSBuildThisFileDirectory)..\build` -->
<RepositoryBuildFolder>$(MSBuildThisFileDirectory)\</RepositoryBuildFolder>
</PropertyGroup>
<MakeDir Directories="$(RepositoryBuildFolder)" />
<CreateProperty Value="$(RepositoryBuildFolder)$(AssemblyPluginVersion)-$(TargetName)-$(Configuration).zip">
<Output TaskParameter="Value" PropertyName="ArtifactZip" />
</CreateProperty>
<!-- Create the zip from the stage -->
<ZipDirectory
SourceDirectory="$(ArtifactFolder)"
DestinationFile="$(ArtifactZip)"
Overwrite="true" />
<!-- Create an SHA256 Checksum -->
<GetFileHash Files="$(ArtifactZip)" Algorithm="SHA256">
<Output TaskParameter="Hash" PropertyName="ArtifactZipHash" />
</GetFileHash>
<WriteLinesToFile
File="$(ArtifactZip).sha256"
Lines="$(ArtifactZipHash) $([System.IO.Path]::GetFileName('$(ArtifactZip)'))"
Overwrite="true" />
<!-- Install this to NINA if installed -->
<Message
Text="Installing to Nina install $(NinaPluginsDirectory)"
Condition="Exists('$(NinaPluginsDirectory)')" />
<Unzip
SourceFiles="$(ArtifactZip)"
DestinationFolder="$(NinaPluginsDirectory)$(TargetName)"
OverwriteReadOnlyFiles="true"
Condition="Exists('$(NinaPluginsDirectory)')" />
</Target>
<!-- Clean up any previous stages -->
<Target Name="PostCleanScript" AfterTargets="Clean">
<PropertyGroup>
<ArtifactFolder>$(TargetDir)$(TargetName)</ArtifactFolder>
</PropertyGroup>
<RemoveDir Directories="$(ArtifactFolder)" Condition="Exists('$(ArtifactFolder)')" />
</Target>
</Project>
<!-- Edit your csproj like #1 & 2 below -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- 1. Make sure this is set to true for Nuget packages to be found -->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<!-- 2. Add this to the bottom -->
<Import Project="NINAPlugin.targets" />
</Project>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment