Skip to content

priceds/SeaShell

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SeaShell logo

SeaShell

PowerShell is everywhere in Windows automation. Writing it by hand is fine, but generating it programmatically from C# backends has always been string soup. SeaShell fixes that: you write C#, it generates PowerShell at compile time.

Before / After

Before SeaShell:

// Generating PS the old way
var script = new StringBuilder();
script.AppendLine($"$env = \"{environment}\"");
script.AppendLine("if (Test-Path $deployPath) {");
script.AppendLine("    Stop-Service -Name W3SVC");
// ... 40 more lines of this

After SeaShell:

[PowerShellScript]
public static void Deploy(string environment, string deployPath)
{
    if (!Directory.Exists(deployPath))
        return;

    ServiceController.Stop("W3SVC");
    Console.WriteLine($"Deployed to {environment}");
}
// That's it. SeaShell generates the PS at compile time.

Install

dotnet add package SeaShell.Attributes
dotnet add package SeaShell.Runtime

Add the generator as an analyzer:

<PackageReference Include="SeaShell.Generator" Version="0.2.0"
                  OutputItemType="Analyzer"
                  ReferenceOutputAssembly="false" />

Quick Start

using SeaShell;

public partial class HelloWorldSamples
{
    [PowerShellScript]
    public static void SayHello(string name)
    {
        string greeting = $"Hello, {name}!";
        Console.WriteLine(greeting);
    }
}

SeaShell generates:

param(
    [Parameter(Mandatory=$true)]
    [string]$name
)

$greeting = "Hello, $name!"
Write-Host $greeting

And it gives you a C# accessor:

var ps = HelloWorldSamples.SayHelloScript.PowerShell;
await HelloWorldSamples.SayHelloScript.ExecuteAsync("Sarvesh");
HelloWorldSamples.SayHelloScript.SaveTo("say-hello.ps1");

Real World: Deployment

[PowerShellScript]
public static void DeployApplication(
    string environment,
    string buildPath,
    string deployPath,
    string[] services)
{
    string backupDir = $@"C:\Backups\{environment}";

    if (!Directory.Exists(deployPath))
    {
        Console.Error.WriteLine($"Deploy path not found: {deployPath}");
        return;
    }

    Directory.CreateDirectory(backupDir);
    Directory.Copy(deployPath, backupDir);

    try
    {
        foreach (var svc in services)
            ServiceController.Stop(svc);

        Directory.Delete(deployPath);
        Directory.Copy(buildPath, deployPath);

        foreach (var svc in services)
            ServiceController.Start(svc);
    }
    catch (Exception err)
    {
        Console.Error.WriteLine($"Deployment failed: {err.Message}");
    }
}

That kind of script is where SeaShell starts to feel useful. You keep the shape of ordinary C# control flow, and the generated PowerShell is still inspectable.

Execute And React

var result = await MonitoringSamples.CheckDiskSpaceScript.ExecuteAsync(
    driveName: "C",
    thresholdGb: 10);

if (!result.Success)
    await AlertService.SendAsync(result.ErrorOutput);

This is the pattern SeaShell is built around: generate the script at compile time, execute it when you need to, then let your C# app react to output, errors, and exit codes.

Supported C# Constructs

C# PowerShell
string name = "A" $name = "A"
bool ok = true $ok = $true
int count = 42 $count = 42
Console.WriteLine(x) Write-Host $x
Console.Error.WriteLine(x) Write-Error $x
if / else if / else if / elseif / else
foreach foreach ($item in $items)
for, while, do while PowerShell loops
try / catch / finally PowerShell error-handling blocks
return, break, continue Same keywords
switch PowerShell switch
==, !=, >, <, &&, `
String interpolation PowerShell interpolation
Method parameters param() block

BCL Mappings

C# PowerShell
Directory.Exists(path) Test-Path -PathType Container
Directory.CreateDirectory(path) New-Item -ItemType Directory -Force
Directory.Delete(path) Remove-Item -Force
Directory.Copy(src, dst) Copy-Item -Recurse
File.Exists(path) Test-Path -PathType Leaf
File.ReadAllText(path) Get-Content -Raw
File.WriteAllText(path, value) Set-Content
File.AppendAllText(path, value) Add-Content
Path.Combine(a, b) Join-Path
ServiceController.Stop(name) Stop-Service -Force
ServiceController.Start(name) Start-Service
Process.Start(file) Start-Process -Wait
Environment.GetEnvironmentVariable(name) $env:name
Environment.SetEnvironmentVariable(name, value) $env:name = value
DateTime.Now Get-Date
String.IsNullOrEmpty(value) [string]::IsNullOrEmpty(value)
Convert.ToInt32(value) [int]value
Thread.Sleep(ms) Start-Sleep -Milliseconds ms

The [RawPS] Escape Hatch

Some PowerShell is too specific to pretend it is normal C#. That is what [RawPS] is for.

[RawPS("Get-LocalUser -Name $username -ErrorAction SilentlyContinue")]
object existingUser = default!;

SeaShell emits the expression exactly:

$existingUser = Get-LocalUser -Name $username -ErrorAction SilentlyContinue

Use it for Active Directory, WMI, registry, certificates, vendor modules, and one-off commands that are clearer as PowerShell.

Current preview note: C# does not compile attributes directly on local variable declarations, so [RawPS] local-variable examples document the intended generator syntax while the compiler limitation is tracked by tests. Until that syntax is moved to a compiler-valid shape, use the fluent builder or plain PowerShell for those escape-hatch cases.

Diagnostics

Code Meaning Fix
SS001 [PowerShellScript] method is not static Make the method static
SS002 Containing class is not partial Mark the class partial
SS003 C# syntax is not supported Rewrite as supported C# or use [RawPS]
SS004 BCL method has no mapping Use [RawPS] or add a mapping
SS010 LINQ is not supported yet Rewrite as foreach
SS011 async/await is not translated Keep generated scripts synchronous

Cross Platform

Generated scripts run through PowerShell 7 (pwsh) on Linux and macOS. File, path, process, environment, JSON, and many shell operations work well cross-platform. Windows-only PowerShell stays Windows-only: Active Directory, WMI, registry providers, firewall rules, Windows services, and certificate store automation depend on the host.

Write portable scripts by sticking to pwsh cmdlets, Path helpers, environment variables, and [RawPS] blocks that branch on $IsWindows, $IsLinux, or $IsMacOS.

Roadmap

Version Focus
v0.3 LINQ expansion and more collection mappings
v1.0 Full mapping coverage for the core automation surface

Docs

About

Fluent C# PowerShell script generation and runtime execution.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages