Skip to content
This repository was archived by the owner on Jul 18, 2024. It is now read-only.

DevTeam/csharp-interactive-fork

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

C# script tool for

NuGet TeamCity.csi GitHub

This is a repository of TeamCity.csi which is an interactive tool for running C# scripts. It can be used as a TeamCity build runner or installed as a command-line tool on Windows, Linux, or macOS.

Prerequisites

The tool requires .NET 6+ runtime.

Use Inside TeamCity

Currently, the tool can be used as a TeamCity build runner provided in terms of TeamCity 2021.2 Early Access Program. Read the runner's documentation for more details.

Use Outside TeamCity

After installing tool you can use this tool independently of TeamCity, to run C# scripts from the command line. TeamCity.csi is available as a NuGet package. Before installing TeamCity.csi as a local tool dot not forget to create .NET local tool manifest file if it is not exist. Install the tool and add to the local tool manifest:

dotnet new tool-manifest
dotnet tool install TeamCity.csi --version <version>

Or install the tool for the current user:

dotnet tool install TeamCity.csi -g --version <version>

Launch the tool in the interactive mode:

dotnet csi

Run a specified script with a given argument:

dotnet csi script-file.csx

Run a single script located in the MyDirectory directory:

dotnet csi MyDirectory

Usage:

dotnet csi [options] [--] [script file or directory containing single script file] [script-arguments]

Script arguments are accessible in scripts via a global list called Args.

Supported arguments:

Option Description Alternative form
--help Show how to use the command. /?, -h, /h, /help
--version Display the tool version. /version
--source Specify the NuGet package source to use. Supported formats: URL, or a UNC directory path. -s, /s, /source
--property <key=value> Define a key=value pair for the global dictionary called Props, which is accessible in scripts. -p, /property, /p
--property:<key=value> Define a key=value pair in MSBuild style for the global dictionary called Props, which is accessible in scripts. -p:<key=value>, /property:<key=value>, /p:<key=value>
@file Read the response file for more options.
-- Indicates that the remaining arguments should not be treated as options.

Command line sample

echo Creates a new solution "MySolution" in the current directory.
dotnet new sln -n MySolution

echo Creates a sample project "MyLib" and adds it to the solution "MySolution".
dotnet new classlib -n MyLib
dotnet sln add MyLib

echo Installs template "build".
dotnet new install TeamCity.CSharpInteractive.Templates

echo Creates a sample build project "Build" using the template "build" and adds it to the solution "MySolution".
dotnet new build -n Build
dotnet sln add Build

echo Creates a local manifest file for the solution "MySolution" and installs the .NET tool TeamCity.csi locally.
dotnet new tool-manifest
dotnet tool install TeamCity.csi

echo Runs a script from the "Build" project to build solution "MySolution" from the solution directory.
dotnet csi Build

You can modify, debug and run the project "Build" as a ordinary .NET console application and run it as a C# script using dotnet csi Build from the command line.

Report and Track Issues

Please use our YouTrack to report related issues.

Usage Scenarios

Using Args

Args have got from the script arguments.

if (Args.Count > 0)
{
    WriteLine(Args[0]);
}

if (Args.Count > 1)
{
    WriteLine(Args[1]);
}

Using Props dictionary

Properties Props have got from TeamCity system properties automatically.

WriteLine(Props["TEAMCITY_VERSION"]);
WriteLine(Props["TEAMCITY_PROJECT_NAME"]);

// This property will be available at the next TeamCity steps as system parameter _system.Version_
// and some runners, for instance, the .NET runner, pass it as a build property.
Props["Version"] = "1.1.6";

Using the Host property

Host is actually the provider of all global properties and methods.

var packages = Host.GetService<INuGet>();
Host.WriteLine("Hello");

Get services

This method might be used to get access to different APIs like INuGet or ICommandLine.

GetService<INuGet>();

var serviceProvider = GetService<IServiceProvider>();
serviceProvider.GetService(typeof(INuGet));

Besides that, it is possible to get an instance of System.IServiceProvider to access APIs.

Service collection

public void Run()
{
    var serviceProvider = 
        GetService<IServiceCollection>()
        .AddTransient<MyTask>()
        .BuildServiceProvider();

    var myTask = serviceProvider.GetRequiredService<MyTask>();
    var exitCode = myTask.Run();
    exitCode.ShouldBe(0);
}

class MyTask
{
    private readonly ICommandLineRunner _runner;

    public MyTask(ICommandLineRunner runner) => 
        _runner = runner;

    public int? Run() => 
        _runner.Run(new CommandLine("whoami"));
}

Write a line to a build log

WriteLine("Hello");

Write an empty line to a build log

WriteLine();

Write a line highlighted with "Header" color to a build log

WriteLine("Hello", Header);

Log an error to a build log

Error("Error info", "Error identifier");

Log a warning to a build log

Warning("Warning info");

Log information to a build log

Info("Some info");

Log trace information to a build log

Trace("Some trace info");

Build command lines

// Adds the namespace "Script.Cmd" to use Command Line API
using HostApi;

// Creates a simple command line from just the name of the executable 
var cmd = new CommandLine("whoami");

// Creates a command line with multiple command line arguments 
cmd = new CommandLine("cmd", "/c", "echo", "Hello");

// Same as previous statement
cmd = new CommandLine("cmd", "/c")
    .AddArgs("echo", "Hello");

// Same as previous statement
cmd = new CommandLine("cmd") + "/c" + "echo" + "Hello";

// Builds a command line with multiple environment variables
cmd = new CommandLine("cmd", "/c", "echo", "Hello")
    .AddVars(("Var1", "val1"), ("var2", "Val2"));

// Same as previous statement
cmd = new CommandLine("cmd") + "/c" + "echo" + "Hello" + ("Var1", "val1") + ("var2", "Val2");

// Builds a command line to run from a specific working directory 
cmd = new CommandLine("cmd", "/c", "echo", "Hello")
    .WithWorkingDirectory("MyDyrectory");

// Builds a command line and replaces all command line arguments
cmd = new CommandLine("cmd", "/c", "echo", "Hello")
    .WithArgs("/c", "echo", "Hello !!!");

Run a command line

// Adds the namespace "HostApi" to use Command Line API
using HostApi;

var exitCode = GetService<ICommandLineRunner>().Run(new CommandLine("cmd", "/c", "DIR"));
exitCode.ShouldBe(0);

// or the same thing using the extension method
exitCode = new CommandLine("cmd", "/c", "DIR").Run();
exitCode.ShouldBe(0);

// using operator '+'
var cmd = new CommandLine("cmd") + "/c" + "DIR";
exitCode = cmd.Run();
exitCode.ShouldBe(0);

// with environment variables
cmd = new CommandLine("cmd") + "/c" + "DIR" + ("MyEnvVar", "Some Value");
exitCode = cmd.Run();
exitCode.ShouldBe(0);

Run a command line asynchronously

// Adds the namespace "HostApi" to use Command Line API
using HostApi;

int? exitCode = await GetService<ICommandLineRunner>().RunAsync(new CommandLine("cmd", "/C", "DIR"));

// or the same thing using the extension method
exitCode = await new CommandLine("cmd", "/c", "DIR").RunAsync();

Run and process output

// Adds the namespace "HostApi" to use Command Line API
using HostApi;

var lines = new List<string>();
int? exitCode = new CommandLine("cmd", "/c", "SET")
    .AddVars(("MyEnv", "MyVal"))
    .Run(output => lines.Add(output.Line));

lines.ShouldContain("MyEnv=MyVal");

Run asynchronously in parallel

// Adds the namespace "HostApi" to use Command Line API
using HostApi;

Task<int?> task = new CommandLine("cmd", "/c", "DIR").RunAsync();
int? exitCode = new CommandLine("cmd", "/c", "SET").Run();
task.Wait();

Cancellation of asynchronous run

The cancellation will kill a related process.

// Adds the namespace "HostApi" to use Command Line API
using HostApi;

var cancellationTokenSource = new CancellationTokenSource();
Task<int?> task = new CommandLine("cmd", "/c", "TIMEOUT", "/T", "120")
    .RunAsync(default, cancellationTokenSource.Token);

cancellationTokenSource.CancelAfter(TimeSpan.FromMilliseconds(100));
task.IsCompleted.ShouldBeFalse();

Run timeout

If timeout expired a process will be killed.

// Adds the namespace "HostApi" to use Command Line API
using HostApi;

int? exitCode = new CommandLine("cmd", "/c", "TIMEOUT", "/T", "120")
    .Run(default, TimeSpan.FromMilliseconds(1));

exitCode.HasValue.ShouldBeFalse();

Build a project

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new library project, running a command like: "dotnet new classlib -n MyLib --force"
var result = new DotNetNew("classlib", "-n", "MyLib", "--force").Build();
result.ExitCode.ShouldBe(0);

// Builds the library project, running a command like: "dotnet build" from the directory "MyLib"
result = new DotNetBuild().WithWorkingDirectory("MyLib").Build();

// The "result" variable provides details about a build
result.Errors.Any(message => message.State == BuildMessageState.StdError).ShouldBeFalse();
result.ExitCode.ShouldBe(0);

Clean a project

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new library project, running a command like: "dotnet new classlib -n MyLib --force"
var result = new DotNetNew("classlib", "-n", "MyLib", "--force").Build();
result.ExitCode.ShouldBe(0);

// Builds the library project, running a command like: "dotnet build" from the directory "MyLib"
result = new DotNetBuild().WithWorkingDirectory("MyLib").Build();
result.ExitCode.ShouldBe(0);

// Clean the project, running a command like: "dotnet clean" from the directory "MyLib"
result = new DotNetClean().WithWorkingDirectory("MyLib").Build();

// The "result" variable provides details about a build
result.ExitCode.ShouldBe(0);

Run a custom .NET command

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Gets the dotnet version, running a command like: "dotnet --version"
NuGetVersion? version = default;
var exitCode = new DotNetCustom("--version")
    .Run(message => NuGetVersion.TryParse(message.Line, out version));

exitCode.ShouldBe(0);
version.ShouldNotBeNull();

Pack a project

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new library project, running a command like: "dotnet new classlib -n MyLib --force"
var result = new DotNetNew("classlib", "-n", "MyLib", "--force").Build();
result.ExitCode.ShouldBe(0);

// Creates a NuGet package of version 1.2.3 for the project, running a command like: "dotnet pack /p:version=1.2.3" from the directory "MyLib"
result = new DotNetPack()
        .WithWorkingDirectory("MyLib")
        .AddProps(("version", "1.2.3"))
        .Build();

result.ExitCode.ShouldBe(0);

Publish a project

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new library project, running a command like: "dotnet new classlib -n MyLib --force"
var result = new DotNetNew("classlib", "-n", "MyLib", "--force", "-f", "net6.0").Build();
result.ExitCode.ShouldBe(0);

// Publish the project, running a command like: "dotnet publish --framework net6.0" from the directory "MyLib"
result = new DotNetPublish().WithWorkingDirectory("MyLib").WithFramework("net6.0").Build();
result.ExitCode.ShouldBe(0);

Restore a project

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new library project, running a command like: "dotnet new classlib -n MyLib --force"
var result = new DotNetNew("classlib", "-n", "MyLib", "--force").Build();
result.ExitCode.ShouldBe(0);

// Restore the project, running a command like: "dotnet restore" from the directory "MyLib"
result = new DotNetRestore().WithWorkingDirectory("MyLib").Build();
result.ExitCode.ShouldBe(0);

Run a project

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new console project, running a command like: "dotnet new console -n MyApp --force"
var result = new DotNetNew("console", "-n", "MyApp", "--force").Build();
result.ExitCode.ShouldBe(0);

// Runs the console project using a command like: "dotnet run" from the directory "MyApp"
var stdOut = new List<string>();
result = new DotNetRun().WithWorkingDirectory("MyApp").Build(message => stdOut.Add(message.Text));
result.ExitCode.ShouldBe(0);

// Checks StdOut
stdOut.ShouldBe(new[] {"Hello, World!"});

Test a project

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new test project, running a command like: "dotnet new mstest -n MyTests --force"
var result = new DotNetNew("mstest", "-n", "MyTests", "--force").Build();
result.ExitCode.ShouldBe(0);

// Runs tests via a command like: "dotnet test" from the directory "MyTests"
result = new DotNetTest().WithWorkingDirectory("MyTests").Build();

// The "result" variable provides details about a build
result.ExitCode.ShouldBe(0);
result.Tests.Count(test => test.State == TestState.Passed).ShouldBe(1);

Run tests under dotCover

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new test project, running a command like: "dotnet new mstest -n MyTests --force"
var exitCode = new DotNetNew("mstest", "-n", "MyTests", "--force").Run();
exitCode.ShouldBe(0);

exitCode = new DotNetNew("tool-manifest")
    .WithWorkingDirectory("MyTests")
    .Run();
exitCode.ShouldBe(0);

exitCode = new DotNetCustom("tool",  "install", "--local", "JetBrains.dotCover.GlobalTool")
    .WithWorkingDirectory("MyTests")
    .Run();
exitCode.ShouldBe(0);

var result =
    // Creates a test command
    new DotNetTest().WithWorkingDirectory("MyTests")
    // Modifies the test command by putting "dotCover" in front of all arguments to have something like "dotnet dotcover test ..."
    .Customize(cmd => cmd.WithArgs("dotcover").AddArgs(cmd.Args))
    // Runs tests via a command like: "dotnet test" from the directory "MyTests"
    .Build();

// The "result" variable provides details about a build
result.ExitCode.ShouldBe(0);
result.Tests.Count(test => test.State == TestState.Passed).ShouldBe(1);

// Generates a JSON code coverage report.
exitCode = new DotNetCustom("dotCover", "report", "--source=dotCover.Output.dcvr", "--reportType=JSON")
    .WithWorkingDirectory("MyTests")
    .Run();
exitCode.ShouldBe(0);

// Check for a dotCover report
File.Exists(Path.Combine("MyTests", "dotCover.Output.json")).ShouldBeTrue();

Restore local tools

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

var projectDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()[..4]);
Directory.CreateDirectory(projectDir);
    
// Creates a local tool manifest 
var exitCode = new DotNetNew("tool-manifest").WithWorkingDirectory(projectDir).Run();
exitCode.ShouldBe(0);

// Restore local tools
exitCode = new DotNetToolRestore().WithWorkingDirectory(projectDir).Run();
exitCode.ShouldBe(0);

Test an assembly

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new test project, running a command like: "dotnet new mstest -n MyTests --force"
var result = new DotNetNew("mstest", "-n", "MyTests", "--force").Build();
result.ExitCode.ShouldBe(0);

// Builds the test project, running a command like: "dotnet build -c Release" from the directory "MyTests"
result = new DotNetBuild().WithWorkingDirectory("MyTests").WithConfiguration("Release").WithOutput("MyOutput").Build();
result.ExitCode.ShouldBe(0);

// Runs tests via a command like: "dotnet vstest" from the directory "MyTests"
result = new VSTest()
    .AddTestFileNames(Path.Combine("MyOutput", "MyTests.dll"))
    .WithWorkingDirectory("MyTests")
    .Build();

// The "result" variable provides details about a build
result.Tests.Count(test => test.State == TestState.Passed).ShouldBe(1);
result.ExitCode.ShouldBe(0);

Build a project using MSBuild

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new library project, running a command like: "dotnet new classlib -n MyLib --force"
var result = new DotNetNew("classlib", "-n", "MyLib", "--force").Build();
result.ExitCode.ShouldBe(0);

// Builds the library project, running a command like: "dotnet msbuild /t:Build -restore /p:configuration=Release -verbosity=detailed" from the directory "MyLib"
result = new MSBuild()
    .WithWorkingDirectory("MyLib")
    .WithTarget("Build")
    .WithRestore(true)
    .AddProps(("configuration", "Release"))
    .WithVerbosity(DotNetVerbosity.Detailed)
    .Build();

// The "result" variable provides details about a build
result.Errors.Any(message => message.State == BuildMessageState.StdError).ShouldBeFalse();
result.ExitCode.ShouldBe(0);

Shuts down build servers

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Shuts down all build servers that are started from dotnet.
var exitCode = new DotNetBuildServerShutdown().Run();

exitCode.ShouldBe(0);

Restore NuGet a package of newest version

// Adds the namespace "HostApi" to use INuGet
using HostApi;

IEnumerable<NuGetPackage> packages = GetService<INuGet>().Restore(new NuGetRestoreSettings("IoC.Container").WithVersionRange(VersionRange.All));

Restore a NuGet package by a version range for the specified .NET and path

// Adds the namespace "HostApi" to use INuGet
using HostApi;

var packagesPath = Path.Combine(
    Path.GetTempPath(),
    Guid.NewGuid().ToString()[..4]);

var settings = new NuGetRestoreSettings("IoC.Container")
    .WithVersionRange(VersionRange.Parse("[1.3, 1.3.8)"))
    .WithTargetFrameworkMoniker("net5.0")
    .WithPackagesPath(packagesPath);

IEnumerable<NuGetPackage> packages = GetService<INuGet>().Restore(settings);

Build a project in a docker container

// Adds the namespace "HostApi" to use .NET build API and Docker API
using HostApi;

// Creates a base docker command line
var dockerRun = new DockerRun()
    .WithAutoRemove(true)
    .WithImage("mcr.microsoft.com/dotnet/sdk")
    .WithPlatform("linux")
    .WithContainerWorkingDirectory("/MyProjects")
    .AddVolumes((Environment.CurrentDirectory, "/MyProjects"));

// Creates a new library project in a docker container
var exitCode = dockerRun
    .WithCommandLine(new DotNetCustom("new", "classlib", "-n", "MyLib", "--force"))
    .Run();

exitCode.ShouldBe(0);

// Builds the library project in a docker container
var result = dockerRun
    .WithCommandLine(new DotNetBuild().WithProject("MyLib/MyLib.csproj"))
    .Build();

// The "result" variable provides details about a build
result.Errors.Any(message => message.State == BuildMessageState.StdError).ShouldBeFalse();
result.ExitCode.ShouldBe(0);

Running in docker

// Adds the namespace "HostApi" to use Command Line API and Docker API
using HostApi;

// Creates some command line to run in a docker container
var cmd = new CommandLine("whoami");

// Runs the command line in a docker container
var result = new DockerRun(cmd, "mcr.microsoft.com/dotnet/sdk")
    .WithAutoRemove(true)
    .Run();

result.ShouldBe(0);

TeamCity integration via service messages

For more details how to use TeamCity service message API please see this page. Instead of creating a root message writer like in the following example:

using JetBrains.TeamCity.ServiceMessages.Write.Special;
using var writer = new TeamCityServiceMessages().CreateWriter(Console.WriteLine);

use this statement:

using JetBrains.TeamCity.ServiceMessages.Write.Special;
using var writer = GetService<ITeamCityWriter>();

This sample opens a block My Tests and reports about two tests:

// Adds a namespace to use ITeamCityWriter
using JetBrains.TeamCity.ServiceMessages.Write.Special;

using var writer = GetService<ITeamCityWriter>();
using (var tests = writer.OpenBlock("My Tests"))
{
    using (var test = tests.OpenTest("Test1"))
    {
        test.WriteStdOutput("Hello");
        test.WriteImage("TestsResults/Test1Screenshot.jpg", "Screenshot");
        test.WriteDuration(TimeSpan.FromMilliseconds(10));
    }

    using (var test = tests.OpenTest("Test2"))
    {
        test.WriteIgnored("Some reason");
    }
}

For more information on TeamCity Service Messages, see this page.

About

Build automation tool

Resources

License

Stars

Watchers

Forks

Languages

  • C# 99.9%
  • Other 0.1%