Scriptor is a powerful command-line tool that allows you to compile and execute C# scripts dynamically without the need for a full project setup. It automatically handles NuGet package dependencies, provides flexible entry point detection, and supports modern .NET frameworks.
- Dynamic C# Script Execution: Run C# scripts without creating a full project
- Automatic NuGet Package Management: Reference NuGet packages directly in your scripts using comments
- Flexible Entry Point Detection: Automatically detects
Main
,Run
, orExecute
methods - Framework Compatibility: Supports multiple .NET frameworks with intelligent compatibility resolution
- Dependency Resolution: Automatically resolves and downloads package dependencies
- Assembly Isolation: Uses collectible assembly load contexts for clean resource management
- Comprehensive Logging: Configurable logging levels for debugging and monitoring
# Install from NuGet (example)
dotnet tool install -g Scriptor.Console
# Or build from source
git clone <repository-url>
cd Scriptor
dotnet build
dotnet pack -o ./nupkg
dotnet tool install -g --add-source ./nupkg Scriptor.Console
Create a simple C# script file (hello.cs
):
using System;
public class HelloWorld
{
public static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
Console.WriteLine($"Received {args.Length} arguments");
foreach (var arg in args)
{
Console.WriteLine($" - {arg}");
}
}
}
Run the script:
scriptor hello.cs
Create a script with NuGet package dependencies (json-example.cs
):
// #nuget: Newtonsoft.Json
// #nuget: Serilog@2.12.0
using System;
using Newtonsoft.Json;
using Serilog;
public class JsonExample
{
public static void Main()
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
var data = new { Name = "John", Age = 30 };
var json = JsonConvert.SerializeObject(data, Formatting.Indented);
Log.Information("Generated JSON: {Json}", json);
Console.WriteLine(json);
}
}
Run the script (packages will be automatically downloaded):
scriptor json-example.cs --verbose
scriptor <script-file> [options]
script-file
: Path to the C# script file to compile and run (required)
--method <method-name>
: Specify the entry point method name (default: auto-detect)--class <class-name>
: Specify the class containing the entry point (default: auto-detect)--framework <framework>
: Target framework version (default: net8.0)--verbose
: Enable verbose logging for debugging--args <arguments>
: Arguments to pass to the script (space-separated)
# Basic execution
scriptor myscript.cs
# Specify entry point method
scriptor myscript.cs --method Run
# Specify class and method
scriptor myscript.cs --class MyApp --method Execute
# Pass arguments to script
scriptor myscript.cs --args arg1 arg2 "argument with spaces"
# Use different framework
scriptor myscript.cs --framework net6.0
# Enable verbose logging
scriptor myscript.cs --verbose
Reference NuGet packages in your scripts using comment directives:
// #nuget: PackageName
// #nuget: PackageName@Version
// #package: PackageName@Version
Examples:
// #nuget: Newtonsoft.Json
// #nuget: Serilog@2.12.0
// #package: Microsoft.Extensions.Http@7.0.0
- Automatic Version Resolution: If no version is specified, the latest version is used
- Dependency Resolution: All package dependencies are automatically resolved and downloaded
- Framework Compatibility: Packages are selected based on target framework compatibility
- Assembly Loading: Package assemblies are loaded in an isolated context
- Caching: Downloaded packages are cached locally for faster subsequent runs
Scriptor supports intelligent framework compatibility resolution:
- .NET 5+: net5.0, net6.0, net7.0, net8.0, net9.0
- .NET Core: netcoreapp2.0, netcoreapp2.1, netcoreapp3.0, netcoreapp3.1
- .NET Standard: netstandard1.0 through netstandard2.1
- .NET Framework: net35, net40, net45, net451, net452, net46, net461, net462, net47, net471, net472, net48
Scriptor automatically detects entry points using the following priority order:
- Specified Method: If
--method
is provided, looks for that method - Common Methods: Searches for methods named
Main
,Run
, orExecute
- Static Methods: Prefers static methods over instance methods
- Method Signature: Supports various parameter combinations:
void Method()
void Method(string[] args)
Task Method()
Task Method(string[] args)
int Method(string[] args)
// Static void Main
public static void Main(string[] args) { }
// Async Main
public static async Task Main(string[] args) { }
// Return code Main
public static int Main(string[] args) { return 0; }
// Custom method names
public static void Run() { }
public static async Task Execute(string[] args) { }
// Instance methods (requires parameterless constructor)
public void Main() { }
public async Task Run(string[] args) { }
using System;
public class SimpleApp
{
public static void Main(string[] args)
{
Console.WriteLine("Simple console application");
if (args.Length > 0)
{
Console.WriteLine($"First argument: {args[0]}");
}
}
}
// #nuget: Microsoft.Extensions.Http
using System;
using System.Net.Http;
using System.Threading.Tasks;
public class HttpExample
{
public static async Task Main()
{
using var client = new HttpClient();
var response = await client.GetStringAsync("https://api.github.com/users/octocat");
Console.WriteLine(response);
}
}
// #nuget: Newtonsoft.Json
// #nuget: Serilog
// #nuget: Serilog.Sinks.Console
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Serilog;
public class JsonProcessor
{
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
var data = new List<object>
{
new { Id = 1, Name = "Alice", Age = 30 },
new { Id = 2, Name = "Bob", Age = 25 },
new { Id = 3, Name = "Charlie", Age = 35 }
};
var json = JsonConvert.SerializeObject(data, Formatting.Indented);
Log.Information("Processing {Count} records", data.Count);
Console.WriteLine(json);
Log.Information("JSON processing completed");
}
}
// #nuget: System.IO.Abstractions
using System;
using System.IO;
using System.Threading.Tasks;
public class FileProcessor
{
public static async Task Main(string[] args)
{
if (args.Length == 0)
{
Console.WriteLine("Usage: scriptor file-processor.cs --args <file-path>");
return;
}
var filePath = args[0];
if (!File.Exists(filePath))
{
Console.WriteLine($"File not found: {filePath}");
return;
}
var content = await File.ReadAllTextAsync(filePath);
var lines = content.Split('\n');
Console.WriteLine($"File: {filePath}");
Console.WriteLine($"Lines: {lines.Length}");
Console.WriteLine($"Characters: {content.Length}");
Console.WriteLine($"Words: {content.Split(' ', '\n', '\r', '\t').Length}");
}
}
- Program: Main entry point and command-line interface
- DynamicCompiler: Handles C# source code compilation using Roslyn
- NuGetPackageManager: Manages NuGet package resolution and downloading
- Assembly Execution: Dynamic method invocation with parameter mapping
- Parse Package References: Extract
#nuget
comments from source code - Resolve Versions: Get latest versions for unspecified package versions
- Dependency Resolution: Recursively resolve all package dependencies
- Download Packages: Download and extract NuGet packages to local cache
- Assembly Selection: Choose compatible assemblies based on target framework
- Load Assemblies: Load assemblies into isolated load context
- Source Parsing: Parse C# source code into syntax trees
- Reference Resolution: Collect metadata references from system and NuGet assemblies
- Compilation: Use Roslyn to compile source code to in-memory assembly
- Assembly Loading: Load compiled assembly into execution context
- Entry Point Discovery: Find and validate entry point method
- Execution: Invoke entry point with appropriate parameters
Specify different target frameworks:
# Target .NET 6.0
scriptor script.cs --framework net6.0
# Target .NET Core 3.1
scriptor script.cs --framework netcoreapp3.1
# Target .NET Framework 4.8
scriptor script.cs --framework net48
Control logging verbosity:
# Minimal logging (default)
scriptor script.cs
# Verbose logging for debugging
scriptor script.cs --verbose
Packages are cached in the application directory under packages/
:
packages/
├── newtonsoft.json/
│ └── 13.0.3/
├── serilog/
│ └── 2.12.0/
└── microsoft.extensions.http/
└── 7.0.0/
Scriptor provides comprehensive error handling for common scenarios:
- File Not Found: Clear error when script file doesn't exist
- Compilation Errors: Detailed compilation error messages with line numbers
- Package Resolution Failures: Specific errors for failed package downloads
- Entry Point Issues: Helpful messages when entry points can't be found
- Runtime Exceptions: Clean error reporting with optional stack traces
- Package Caching: Downloaded packages are cached to avoid re-downloading
- Concurrent Downloads: Multiple packages are downloaded concurrently
- Assembly Isolation: Uses collectible assembly load contexts to prevent memory leaks
- Optimized Compilation: Uses release-mode compilation for better performance
- Security: Scripts run with full trust - use caution with untrusted scripts
- Platform Dependencies: Some NuGet packages may have platform-specific requirements
- Framework Compatibility: Not all packages are compatible with all frameworks
- Resource Cleanup: Long-running scripts should properly dispose of resources
-
Package Not Found
Error: Could not resolve latest version for package PackageName
- Check package name spelling
- Verify package exists on NuGet.org
- Try specifying a specific version
-
Compilation Errors
Compilation failed: (10,15): error CS0246: The type or namespace name 'JsonConvert' could not be found
- Ensure all required
using
statements are included - Verify NuGet package references are correct
- Check for typos in class/method names
- Ensure all required
-
Entry Point Not Found
No suitable entry point method found in class 'MyClass'
- Ensure your class has a
Main
,Run
, orExecute
method - Use
--method
option to specify custom entry point - Check method visibility (should be public)
- Ensure your class has a
-
Framework Compatibility
No compatible assemblies found for target framework
- Try a different target framework
- Check package documentation for supported frameworks
- Use
--verbose
to see detailed compatibility information
Use verbose logging to diagnose issues:
scriptor script.cs --verbose
This provides detailed information about:
- Package resolution process
- Assembly loading
- Compilation diagnostics
- Entry point detection
- Execution flow
Contributions are welcome! Please see the contributing guidelines for details on:
- Code style and formatting
- Testing requirements
- Pull request process
- Issue reporting
This project is licensed under the MIT License - see the LICENSE file for details.
- Initial release
- Dynamic C# script compilation and execution
- NuGet package management with dependency resolution
- Automatic entry point detection
- Multiple framework support
- Comprehensive logging and error handling