Register cmdlets via source generated extension

This commit is contained in:
Pat Hartl 2026-05-26 20:36:25 -05:00
parent 68b112d6ce
commit a67b1953f0
6 changed files with 127 additions and 42 deletions

View file

@ -0,0 +1,90 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace LANCommander.SDK.SourceGenerators;
[Generator]
public class CmdletRegistrationGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var cmdletClasses = context.SyntaxProvider
.ForAttributeWithMetadataName(
"System.Management.Automation.CmdletAttribute",
predicate: static (node, _) => node is ClassDeclarationSyntax,
transform: static (ctx, _) => GetCmdletInfo(ctx))
.Where(static info => info is not null)
.Select(static (info, _) => info!.Value);
context.RegisterSourceOutput(
cmdletClasses.Collect(),
static (spc, cmdlets) => Execute(spc, cmdlets));
}
private static CmdletInfo? GetCmdletInfo(GeneratorAttributeSyntaxContext context)
{
var typeSymbol = (INamedTypeSymbol)context.TargetSymbol;
foreach (var attr in typeSymbol.GetAttributes())
{
if (attr.AttributeClass?.ToDisplayString() != "System.Management.Automation.CmdletAttribute")
continue;
if (attr.ConstructorArguments.Length < 2)
continue;
var verb = attr.ConstructorArguments[0].Value?.ToString();
var noun = attr.ConstructorArguments[1].Value?.ToString();
if (verb is null || noun is null)
continue;
return new CmdletInfo
{
CmdletName = $"{verb}-{noun}",
FullTypeName = typeSymbol.ToDisplayString()
};
}
return null;
}
private static void Execute(SourceProductionContext context, ImmutableArray<CmdletInfo> cmdlets)
{
if (cmdlets.IsDefaultOrEmpty)
return;
var sorted = cmdlets.OrderBy(c => c.CmdletName).ToList();
var sb = new StringBuilder();
sb.AppendLine("// <auto-generated/>");
sb.AppendLine("using System.Management.Automation.Runspaces;");
sb.AppendLine();
sb.AppendLine("namespace LANCommander.SDK.PowerShell.Extensions;");
sb.AppendLine();
sb.AppendLine("public static class InitialSessionStateExtensions");
sb.AppendLine("{");
sb.AppendLine(" public static void AddCustomCmdlets(this InitialSessionState initialSessionState)");
sb.AppendLine(" {");
foreach (var cmdlet in sorted)
{
sb.AppendLine($" initialSessionState.Commands.Add(new SessionStateCmdletEntry(\"{cmdlet.CmdletName}\", typeof({cmdlet.FullTypeName}), null));");
}
sb.AppendLine(" }");
sb.AppendLine("}");
context.AddSource("InitialSessionStateExtensions.g.cs", sb.ToString());
}
private struct CmdletInfo
{
public string CmdletName;
public string FullTypeName;
}
}

View file

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<IsRoslynComponent>true</IsRoslynComponent>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
</ItemGroup>
</Project>

View file

@ -55,6 +55,9 @@
<ItemGroup>
<ProjectReference Include="..\LANCommander.Steam\LANCommander.Steam.csproj" />
<ProjectReference Include="..\LANCommander.SDK.SourceGenerators\LANCommander.SDK.SourceGenerators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,19 @@
using LANCommander.SDK.Extensions;
using System.IO;
using System.Management.Automation;
namespace LANCommander.SDK.PowerShell.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "SanitizedPath")]
[OutputType(typeof(string))]
public class GetSanitizedPathCmdlet : Cmdlet
{
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The path to sanitize.")]
public string Path { get; set; }
protected override void ProcessRecord()
{
WriteObject(Path.SanitizeFilename());
}
}
}

View file

@ -1,42 +0,0 @@
using System.Management.Automation.Runspaces;
using LANCommander.SDK.PowerShell.Cmdlets;
namespace LANCommander.SDK.PowerShell.Extensions;
public static class InitialSessionStateExtensions
{
public static void AddCustomCmdlets(this InitialSessionState initialSessionState)
{
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Convert-AspectRatio", typeof(ConvertAspectRatioCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("ConvertFrom-SerializedBase64", typeof(ConvertFromSerializedBase64Cmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("ConvertTo-SerializedBase64", typeof(ConvertToSerializedBase64Cmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("ConvertTo-StringBytes", typeof(ConvertToStringBytesCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Edit-PatchBinary", typeof(EditPatchBinaryCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Edit-PatchGameSpy", typeof(EditPatchGameSpy), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-GameManifest", typeof(GetGameManifestCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-HorizontalFov", typeof(GetHorizontalFovCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-PrimaryDisplay", typeof(GetPrimaryDisplayCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-UserCustomField", typeof(GetUserCustomFieldCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-VerticalFov", typeof(GetVerticalFovCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Out-PlayerAvatar", typeof(OutPlayerAvatarCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Update-IniValue", typeof(UpdateIniValueCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Update-UserCustomField", typeof(UpdateUserCustomFieldCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Write-GameManifest", typeof(WriteGameManifestCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("New-Package", typeof(NewPackageCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Write-ReplaceContentInFile", typeof(ReplaceContentInFileCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Connect-SteamCmd", typeof(ConnectSteamCmdCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Disconnect-SteamCmd", typeof(DisconnectSteamCmdCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-SteamCmdConnectionStatus", typeof(GetSteamCmdConnectionStatusCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-SteamCmdPath", typeof(GetSteamCmdPathCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-SteamCmdProfile", typeof(GetSteamCmdProfileCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-SteamCmdProfiles", typeof(GetSteamCmdProfilesCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Install-SteamContent", typeof(InstallSteamContentCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Remove-SteamContent", typeof(RemoveSteamContentCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Remove-SteamCmdProfile", typeof(RemoveSteamCmdProfileCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Set-SteamCmdProfile", typeof(SetSteamCmdProfileCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-SteamAppInfo", typeof(GetSteamAppInfoCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-SteamWebAssetUri", typeof(GetSteamWebAssetUriCmdlet), null));
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Search-SteamGames", typeof(SearchSteamGamesCmdlet), null));
}
}

View file

@ -43,4 +43,5 @@
<Project Path="LANCommander.Steam\LANCommander.Steam.csproj" />
<Project Path="LANCommander.UI\LANCommander.UI.csproj" />
<Project Path="LANCommander.CompletionGenerator\LANCommander.CompletionGenerator.csproj" />
<Project Path="LANCommander.SDK.SourceGenerators\LANCommander.SDK.SourceGenerators.csproj" />
</Solution>