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.
The tool requires .NET 6+ runtime.
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.
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. |
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.
Please use our YouTrack to report related issues.
- Global state
- Logging
- Command Line API
- Docker API
- .NET build API
- NuGet API
- TeamCity Service Messages API
Args have got from the script arguments.
if (Args.Count > 0)
{
WriteLine(Args[0]);
}
if (Args.Count > 1)
{
WriteLine(Args[1]);
}
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";
Host is actually the provider of all global properties and methods.
var packages = Host.GetService<INuGet>();
Host.WriteLine("Hello");
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.
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"));
}
WriteLine("Hello");
WriteLine();
WriteLine("Hello", Header);
Error("Error info", "Error identifier");
Warning("Warning info");
Info("Some info");
Trace("Some trace info");
// 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 !!!");
// 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);
// 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();
// 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");
// 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();
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();
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();
// 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);
// 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);
// 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();
// 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);
// 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);
// 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);
// 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!"});
// 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);
// 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();
// 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);
// 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);
// 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);
// 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);
// Adds the namespace "HostApi" to use INuGet
using HostApi;
IEnumerable<NuGetPackage> packages = GetService<INuGet>().Restore(new NuGetRestoreSettings("IoC.Container").WithVersionRange(VersionRange.All));
// 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);
// 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);
// 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);
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.