Single-file, cross-platform C# applications with nugets

Finally with .NET 10, we can stop using Powershell for single-file applications, and start using the one language to rule them all - C#! It works cross-platform and yes, you can provide compiler directives

#!/usr/bin/env dotnet run

#:property EnableDefaultEmbeddedResourceItems=false
#:property Nullable=enable
#:package Humanizer@*

using Humanizer;
using System;

int number = 42;
Console.WriteLine($"The number {number} in words is: {number.ToWords()}");

To invoke:

dotnet demo.cs

Better still, register .cs files in the registry, to permit double-click to run. Save the following as register-cs.cs and invoke with:

dotnet register-cs.cs
#!/usr/bin/env dotnet run
#:property EnableDefaultEmbeddedResourceItems=false
#:property NoWarn=CA1416

using System;
using System.IO;
using Microsoft.Win32;

// --- Configuration ---
const string CommandName = "CSharpScript.Run";
const string Extension = ".cs";

// --- Registry Setup ---

Console.WriteLine("--- Registering .cs Extension with dotnet.exe ---");

try
{
    // 1. Determine the path to dotnet.exe
    string? dotnetPath = Environment.ExpandEnvironmentVariables(@"%ProgramFiles%\dotnet\dotnet.exe");

    if (!File.Exists(dotnetPath))
    {
        // Fallback check for x86 systems
        dotnetPath = Environment.ExpandEnvironmentVariables(@"%ProgramFiles(x86)%\dotnet\dotnet.exe");
        if (!File.Exists(dotnetPath))
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine($"Error: Cannot find dotnet.exe at expected paths ({Environment.ExpandEnvironmentVariables(@"%ProgramFiles%\dotnet\dotnet.exe")}).");
            Console.WriteLine("Please ensure .NET is installed.");
            Console.ResetColor();
            return;
        }
    }
    
    // The command format: "C:\Path\To\dotnet.exe" "%1"
    string commandValue = $"\"{dotnetPath}\" \"%1\"";
    Console.WriteLine($"Found dotnet.exe: {dotnetPath}");
    Console.WriteLine($"Command: {commandValue}");

    // The root key for user-specific file associations
    RegistryKey? classesRoot = Registry.CurrentUser.OpenSubKey("Software\\Classes", true);

    if (classesRoot == null)
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine("Error: Could not open HKEY_CURRENT_USER\\Software\\Classes. Check permissions.");
        Console.ResetColor();
        return;
    }

    // --- Step 2: Define the Command (CSharpScript.Run) ---

    // Create/open CSharpScript.Run key
    using (RegistryKey? commandKey = classesRoot.CreateSubKey(CommandName))
    {
        if (commandKey == null) throw new Exception($"Failed to create key: {CommandName}");
        commandKey.SetValue(null, "C# Script Runner (dotnet)");

        // Set the icon
        using (RegistryKey? openKey = commandKey.CreateSubKey("shell\\open"))
        {
            openKey?.SetValue("Icon", dotnetPath);
        }

        // Set the actual command executed when opening the file
        using (RegistryKey? commandPath = commandKey.CreateSubKey("shell\\open\\command"))
        {
            if (commandPath == null) throw new Exception("Failed to create command key path.");
            commandPath.SetValue(null, commandValue);
        }
    }
    Console.WriteLine($"Successfully registered the execution command '{CommandName}'.");

    // --- Step 3: Associate the .cs extension ---

    // Create/open the .cs extension key
    using (RegistryKey? extensionKey = classesRoot.CreateSubKey(Extension))
    {
        if (extensionKey == null) throw new Exception($"Failed to create key: {Extension}");
        // Set the default value of the .cs key to the name of our command
        extensionKey.SetValue(null, CommandName);
    }
    Console.WriteLine($"Successfully associated extension '{Extension}' with '{CommandName}'.");

    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine("\nREGISTRATION COMPLETE.");
    Console.WriteLine("-----------------------------------------------------------------");
    Console.WriteLine($"You can now run C# scripts by typing: .\\yourfile{Extension}");
    Console.WriteLine("Note: Running this way will open a separate console window.");
    Console.WriteLine("You may need to restart your terminal or log off/on for changes to fully take effect.");
    Console.ResetColor();

}
catch (System.Security.SecurityException)
{
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine("\nSECURITY ERROR: You might need to run this command with administrative privileges.");
    Console.WriteLine("Try running your terminal as Administrator and executing the script again.");
    Console.ResetColor();
}
catch (Exception ex)
{
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine($"An unexpected error occurred: {ex.Message}");
    Console.ResetColor();
}