private async Task ReportingAsync(AgentCommandPluginExecutionContext context, int totalFiles, CancellationToken token) { int traceInterval = 0; while (!_uploadFinished.Task.IsCompleted && !token.IsCancellationRequested) { bool hasDetailProgress = false; foreach (var file in _fileUploadProgressLog) { string message; while (file.Value.TryDequeue(out message)) { hasDetailProgress = true; context.Output(message); } } // trace total file progress every 25 seconds when there is no file level detail progress if (++traceInterval % 2 == 0 && !hasDetailProgress) { context.Output(StringUtil.Loc("FileUploadProgress", totalFiles, _filesProcessed, (_filesProcessed * 100) / totalFiles)); } await Task.WhenAny(_uploadFinished.Task, Task.Delay(5000, token)); } }
private Boolean IsUncSharePath(AgentCommandPluginExecutionContext context, string path) { if (string.IsNullOrEmpty(path)) { return(false); } Uri uri; // Add try catch to avoid unexpected throw from Uri.Property. try { if (Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out uri)) { if (uri.IsAbsoluteUri && uri.IsUnc) { return(true); } } } catch (Exception ex) { context.Debug($"Can't determine path: {path} is UNC or not."); context.Debug(ex.ToString()); return(false); } return(false); }
public void ProcessCommand(IExecutionContext context, Command command) { // queue async command task to run agent plugin. context.Debug($"Process {command.Area}.{command.Event} command through agent plugin in backend."); if (!_supportedLoggingCommands.ContainsKey(command.Area) || !_supportedLoggingCommands[command.Area].ContainsKey(command.Event)) { throw new NotSupportedException(command.ToString()); } var plugin = _supportedLoggingCommands[command.Area][command.Event]; ArgUtil.NotNull(plugin, nameof(plugin)); ArgUtil.NotNullOrEmpty(plugin.DisplayName, nameof(plugin.DisplayName)); // construct plugin context AgentCommandPluginExecutionContext pluginContext = new AgentCommandPluginExecutionContext { Data = command.Data, Properties = command.Properties, Endpoints = context.Endpoints, }; // variables context.Variables.CopyInto(pluginContext.Variables); var commandContext = HostContext.CreateService <IAsyncCommandContext>(); commandContext.InitializeCommandContext(context, plugin.DisplayName); commandContext.Task = ProcessPluginCommandAsync(commandContext, pluginContext, plugin.CommandPluginTypeName, command, context.CancellationToken); context.AsyncCommands.Add(commandContext); }
private async Task <List <string> > ParallelUploadAsync(AgentCommandPluginExecutionContext context, List <string> files, int concurrentUploads, CancellationToken token) { // return files that fail to upload List <string> failedFiles = new List <string>(); // nothing needs to upload if (files.Count == 0) { return(failedFiles); } // ensure the file upload queue is empty. if (!_fileUploadQueue.IsEmpty) { throw new ArgumentOutOfRangeException(nameof(_fileUploadQueue)); } // enqueue file into upload queue. foreach (var file in files) { _fileUploadQueue.Enqueue(file); } // Start upload monitor task. _filesProcessed = 0; _uploadFinished = new TaskCompletionSource <int>(); _fileUploadTraceLog.Clear(); _fileUploadProgressLog.Clear(); Task uploadMonitor = ReportingAsync(context, files.Count(), _uploadCancellationTokenSource.Token); // Start parallel upload tasks. List <Task <List <string> > > parallelUploadingTasks = new List <Task <List <string> > >(); for (int uploader = 0; uploader < concurrentUploads; uploader++) { parallelUploadingTasks.Add(UploadAsync(context, uploader, _uploadCancellationTokenSource.Token)); } // Wait for parallel upload finish. await Task.WhenAll(parallelUploadingTasks); foreach (var uploadTask in parallelUploadingTasks) { // record all failed files. failedFiles.AddRange(await uploadTask); } // Stop monitor task; _uploadFinished.TrySetResult(0); await uploadMonitor; return(failedFiles); }
public void ProcessCommand(IExecutionContext context, Command command) { // queue async command task to run agent plugin. context.Debug($"Process {command.Area}.{command.Event} command through agent plugin in backend."); if (!_supportedLoggingCommands.ContainsKey(command.Area) || !_supportedLoggingCommands[command.Area].ContainsKey(command.Event)) { throw new NotSupportedException(command.ToString()); } var plugin = _supportedLoggingCommands[command.Area][command.Event]; ArgUtil.NotNull(plugin, nameof(plugin)); ArgUtil.NotNullOrEmpty(plugin.DisplayName, nameof(plugin.DisplayName)); // construct plugin context AgentCommandPluginExecutionContext pluginContext = new AgentCommandPluginExecutionContext { Data = command.Data, Properties = command.Properties, Endpoints = context.Endpoints, }; var target = context.StepTarget(); Variables.TranslationMethod translateToHostPath = Variables.DefaultStringTranslator; ContainerInfo containerInfo = target as ContainerInfo; // Since plugins run on the host, but the inputs and variables have already been translated // to the container path, we need to convert them back to the host path // TODO: look to see if there is a better way to not have translate these back if (containerInfo != null) { translateToHostPath = (string val) => { return(containerInfo.TranslateToHostPath(val)); }; } // variables context.Variables.CopyInto(pluginContext.Variables, translateToHostPath); var commandContext = HostContext.CreateService <IAsyncCommandContext>(); commandContext.InitializeCommandContext(context, plugin.DisplayName); commandContext.Task = ProcessPluginCommandAsync(commandContext, pluginContext, plugin.CommandPluginTypeName, command, context.CancellationToken); context.AsyncCommands.Add(commandContext); }
public async Task ProcessCommandAsync(AgentCommandPluginExecutionContext context, CancellationToken token) { PluginUtil.NotNull(context, nameof(context)); Guid projectId = new Guid(context.Variables.GetValueOrDefault(BuildVariables.TeamProjectId)?.Value ?? Guid.Empty.ToString()); PluginUtil.NotEmpty(projectId, nameof(projectId)); string buildIdStr = context.Variables.GetValueOrDefault(BuildVariables.BuildId)?.Value ?? string.Empty; if (!int.TryParse(buildIdStr, out int buildId)) { throw new ArgumentOutOfRangeException(buildIdStr); } string containerIdStr = context.Variables.GetValueOrDefault(BuildVariables.ContainerId)?.Value ?? string.Empty; if (!long.TryParse(containerIdStr, out long containerId)) { throw new ArgumentOutOfRangeException(buildIdStr); } string artifactName; if (!context.Properties.TryGetValue(ArtifactUploadEventProperties.ArtifactName, out artifactName) || string.IsNullOrEmpty(artifactName)) { throw new Exception(PluginUtil.Loc("ArtifactNameRequired")); } string containerFolder; if (!context.Properties.TryGetValue(ArtifactUploadEventProperties.ContainerFolder, out containerFolder) || string.IsNullOrEmpty(containerFolder)) { containerFolder = artifactName; } var propertyDictionary = ExtractArtifactProperties(context.Properties); string localPath = context.Data; if (context.ContainerPathMappings.Count > 0) { // Translate file path back from container path localPath = context.TranslateContainerPathToHostPath(localPath); } if (string.IsNullOrEmpty(localPath)) { throw new Exception(PluginUtil.Loc("ArtifactLocationRequired")); } string hostType = context.Variables.GetValueOrDefault("system.hosttype")?.Value; if (!IsUncSharePath(context, localPath) && !string.Equals(hostType, "Build", StringComparison.OrdinalIgnoreCase)) { throw new Exception(PluginUtil.Loc("UploadArtifactCommandNotSupported", hostType ?? string.Empty)); } string fullPath = Path.GetFullPath(localPath); if (!File.Exists(fullPath) && !Directory.Exists(fullPath)) { // if localPath is not a file or folder on disk throw new FileNotFoundException(PluginUtil.Loc("PathNotExist", localPath)); } else if (Directory.Exists(fullPath) && Directory.EnumerateFiles(fullPath, "*", SearchOption.AllDirectories).FirstOrDefault() == null) { // if localPath is a folder but the folder contains nothing context.Output(PluginUtil.Loc("DirectoryIsEmptyForArtifact", fullPath, artifactName)); return; } // Upload to file container context.Output(PluginUtil.Loc("UploadArtifact")); FileContainerServer fileContainerHelper = new FileContainerServer(context.VssConnection, projectId, containerId, containerFolder); await fileContainerHelper.CopyToContainerAsync(context, fullPath, token); string fileContainerFullPath = PluginUtil.Format($"#/{containerId}/{containerFolder}"); context.Output(PluginUtil.Loc("UploadToFileContainer", fullPath, fileContainerFullPath)); // Associate build artifact BuildServer buildHelper = new BuildServer(context.VssConnection); var artifact = await buildHelper.AssociateArtifact(projectId, buildId, artifactName, ArtifactResourceTypes.Container, fileContainerFullPath, propertyDictionary, token); context.Output(PluginUtil.Loc("AssociateArtifactWithBuild", artifact.Id, buildId)); }
private async Task ProcessPluginCommandAsync(IAsyncCommandContext context, AgentCommandPluginExecutionContext pluginContext, string plugin, Command command, CancellationToken token) { // Resolve the working directory. string workingDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); ArgUtil.Directory(workingDirectory, nameof(workingDirectory)); // Agent.PluginHost string file = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), $"Agent.PluginHost{Util.IOUtil.ExeExtension}"); // Agent.PluginHost's arguments string arguments = $"command \"{plugin}\""; // Execute the process. Exit code 0 should always be returned. // A non-zero exit code indicates infrastructural failure. // Any content coming from STDERR will indicate the command process failed. // We can't use ## command for plugin to communicate, since we are already processing ## command using (var processInvoker = HostContext.CreateService <IProcessInvoker>()) { object stderrLock = new object(); List <string> stderr = new List <string>(); processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) => { context.Output(e.Data); }; processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs e) => { lock (stderrLock) { stderr.Add(e.Data); }; }; var redirectStandardIn = new InputQueue <string>(); redirectStandardIn.Enqueue(JsonUtility.ToString(pluginContext)); int returnCode = await processInvoker.ExecuteAsync(workingDirectory : workingDirectory, fileName : file, arguments : arguments, environment : null, requireExitCodeZero : false, outputEncoding : null, killProcessOnCancel : false, redirectStandardIn : redirectStandardIn, cancellationToken : token); if (returnCode != 0) { context.Output(string.Join(Environment.NewLine, stderr)); throw new ProcessExitCodeException(returnCode, file, arguments); } else if (stderr.Count > 0) { throw new InvalidOperationException(string.Join(Environment.NewLine, stderr)); } else { // Everything works fine. // Return code is 0. // No STDERR comes out. } } }
public static int Main(string[] args) { if (PlatformUtil.UseLegacyHttpHandler) { AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false); } Console.CancelKeyPress += Console_CancelKeyPress; // Set encoding to UTF8, process invoker will use UTF8 write to STDIN Console.InputEncoding = Encoding.UTF8; Console.OutputEncoding = Encoding.UTF8; try { ArgUtil.NotNull(args, nameof(args)); ArgUtil.Equal(2, args.Length, nameof(args.Length)); string pluginType = args[0]; if (string.Equals("task", pluginType, StringComparison.OrdinalIgnoreCase)) { string assemblyQualifiedName = args[1]; ArgUtil.NotNullOrEmpty(assemblyQualifiedName, nameof(assemblyQualifiedName)); string serializedContext = Console.ReadLine(); ArgUtil.NotNullOrEmpty(serializedContext, nameof(serializedContext)); AgentTaskPluginExecutionContext executionContext = StringUtil.ConvertFromJson <AgentTaskPluginExecutionContext>(serializedContext); ArgUtil.NotNull(executionContext, nameof(executionContext)); VariableValue culture; ArgUtil.NotNull(executionContext.Variables, nameof(executionContext.Variables)); if (executionContext.Variables.TryGetValue("system.culture", out culture) && !string.IsNullOrEmpty(culture?.Value)) { CultureInfo.DefaultThreadCurrentCulture = new CultureInfo(culture.Value); CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo(culture.Value); } AssemblyLoadContext.Default.Resolving += ResolveAssembly; try { Type type = Type.GetType(assemblyQualifiedName, throwOnError: true); var taskPlugin = Activator.CreateInstance(type) as IAgentTaskPlugin; ArgUtil.NotNull(taskPlugin, nameof(taskPlugin)); taskPlugin.RunAsync(executionContext, tokenSource.Token).GetAwaiter().GetResult(); } catch (Exception ex) { // any exception throw from plugin will fail the task. executionContext.Error(ex.Message); executionContext.Debug(ex.StackTrace); } finally { AssemblyLoadContext.Default.Resolving -= ResolveAssembly; } return(0); } else if (string.Equals("command", pluginType, StringComparison.OrdinalIgnoreCase)) { string assemblyQualifiedName = args[1]; ArgUtil.NotNullOrEmpty(assemblyQualifiedName, nameof(assemblyQualifiedName)); string serializedContext = Console.ReadLine(); ArgUtil.NotNullOrEmpty(serializedContext, nameof(serializedContext)); AgentCommandPluginExecutionContext executionContext = StringUtil.ConvertFromJson <AgentCommandPluginExecutionContext>(serializedContext); ArgUtil.NotNull(executionContext, nameof(executionContext)); AssemblyLoadContext.Default.Resolving += ResolveAssembly; try { Type type = Type.GetType(assemblyQualifiedName, throwOnError: true); var commandPlugin = Activator.CreateInstance(type) as IAgentCommandPlugin; ArgUtil.NotNull(commandPlugin, nameof(commandPlugin)); commandPlugin.ProcessCommandAsync(executionContext, tokenSource.Token).GetAwaiter().GetResult(); } catch (Exception ex) { // any exception throw from plugin will fail the command. executionContext.Error(ex.ToString()); } finally { AssemblyLoadContext.Default.Resolving -= ResolveAssembly; } return(0); } else if (string.Equals("log", pluginType, StringComparison.OrdinalIgnoreCase)) { // read commandline arg to get the instance id var instanceId = args[1]; ArgUtil.NotNullOrEmpty(instanceId, nameof(instanceId)); // read STDIN, the first line will be the HostContext for the log plugin host string serializedContext = Console.ReadLine(); ArgUtil.NotNullOrEmpty(serializedContext, nameof(serializedContext)); AgentLogPluginHostContext hostContext = StringUtil.ConvertFromJson <AgentLogPluginHostContext>(serializedContext); ArgUtil.NotNull(hostContext, nameof(hostContext)); // create plugin object base on plugin assembly names from the HostContext List <IAgentLogPlugin> logPlugins = new List <IAgentLogPlugin>(); AssemblyLoadContext.Default.Resolving += ResolveAssembly; try { foreach (var pluginAssembly in hostContext.PluginAssemblies) { try { Type type = Type.GetType(pluginAssembly, throwOnError: true); var logPlugin = Activator.CreateInstance(type) as IAgentLogPlugin; ArgUtil.NotNull(logPlugin, nameof(logPlugin)); logPlugins.Add(logPlugin); } catch (Exception ex) { // any exception throw from plugin will get trace and ignore, error from plugins will not fail the job. Console.WriteLine($"Unable to load plugin '{pluginAssembly}': {ex}"); } } } finally { AssemblyLoadContext.Default.Resolving -= ResolveAssembly; } // start the log plugin host var logPluginHost = new AgentLogPluginHost(hostContext, logPlugins); Task hostTask = logPluginHost.Run(); while (true) { var consoleInput = Console.ReadLine(); if (string.Equals(consoleInput, $"##vso[logplugin.finish]{instanceId}", StringComparison.OrdinalIgnoreCase)) { // singal all plugins, the job has finished. // plugin need to start their finalize process. logPluginHost.Finish(); break; } else { // the format is TimelineRecordId(GUID):Output(String) logPluginHost.EnqueueOutput(consoleInput); } } // wait for the host to finish. hostTask.GetAwaiter().GetResult(); return(0); } else { throw new ArgumentOutOfRangeException(pluginType); } } catch (Exception ex) { // infrastructure failure. Console.Error.WriteLine(ex.ToString()); return(1); } finally { Console.CancelKeyPress -= Console_CancelKeyPress; } }
public async Task CopyToContainerAsync( AgentCommandPluginExecutionContext context, String source, CancellationToken cancellationToken) { int maxConcurrentUploads = Math.Min(Environment.ProcessorCount, 2); List <String> files; if (File.Exists(source)) { files = new List <String>() { source }; _sourceParentDirectory = Path.GetDirectoryName(source); } else { files = Directory.EnumerateFiles(source, "*", SearchOption.AllDirectories).ToList(); _sourceParentDirectory = source.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); } context.Output(StringUtil.Loc("TotalUploadFiles", files.Count())); using (_uploadCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) { // hook up reporting event from file container client. _fileContainerHttpClient.UploadFileReportTrace += UploadFileTraceReportReceived; _fileContainerHttpClient.UploadFileReportProgress += UploadFileProgressReportReceived; try { // try upload all files for the first time. List <string> failedFiles = await ParallelUploadAsync(context, files, maxConcurrentUploads, _uploadCancellationTokenSource.Token); if (failedFiles.Count == 0) { // all files have been upload succeed. context.Output(StringUtil.Loc("FileUploadSucceed")); return; } else { context.Output(StringUtil.Loc("FileUploadFailedRetryLater", failedFiles.Count)); } // Delay 1 min then retry failed files. for (int timer = 60; timer > 0; timer -= 5) { context.Output(StringUtil.Loc("FileUploadRetryInSecond", timer)); await Task.Delay(TimeSpan.FromSeconds(5), _uploadCancellationTokenSource.Token); } // Retry upload all failed files. context.Output(StringUtil.Loc("FileUploadRetry", failedFiles.Count)); failedFiles = await ParallelUploadAsync(context, failedFiles, maxConcurrentUploads, _uploadCancellationTokenSource.Token); if (failedFiles.Count == 0) { // all files have been upload succeed after retry. context.Output(StringUtil.Loc("FileUploadRetrySucceed")); return; } else { throw new Exception(StringUtil.Loc("FileUploadFailedAfterRetry")); } } finally { _fileContainerHttpClient.UploadFileReportTrace -= UploadFileTraceReportReceived; _fileContainerHttpClient.UploadFileReportProgress -= UploadFileProgressReportReceived; } } }
private async Task <List <string> > UploadAsync(AgentCommandPluginExecutionContext context, int uploaderId, CancellationToken token) { List <string> failedFiles = new List <string>(); string fileToUpload; Stopwatch uploadTimer = new Stopwatch(); while (_fileUploadQueue.TryDequeue(out fileToUpload)) { token.ThrowIfCancellationRequested(); try { using (FileStream fs = File.Open(fileToUpload, FileMode.Open, FileAccess.Read, FileShare.Read)) { string itemPath = (_containerPath.TrimEnd('/') + "/" + fileToUpload.Remove(0, _sourceParentDirectory.Length + 1)).Replace('\\', '/'); uploadTimer.Restart(); bool catchExceptionDuringUpload = false; HttpResponseMessage response = null; try { response = await _fileContainerHttpClient.UploadFileAsync(_containerId, itemPath, fs, _projectId, cancellationToken : token, chunkSize : 4 * 1024 * 1024); } catch (OperationCanceledException) when(token.IsCancellationRequested) { context.Output(StringUtil.Loc("FileUploadCancelled", fileToUpload)); if (response != null) { response.Dispose(); response = null; } throw; } catch (Exception ex) { catchExceptionDuringUpload = true; context.Output(StringUtil.Loc("FileUploadFailed", fileToUpload, ex.Message)); context.Output(ex.ToString()); } uploadTimer.Stop(); if (catchExceptionDuringUpload || (response != null && response.StatusCode != HttpStatusCode.Created)) { if (response != null) { context.Output(StringUtil.Loc("FileContainerUploadFailed", response.StatusCode, response.ReasonPhrase, fileToUpload, itemPath)); } // output detail upload trace for the file. ConcurrentQueue <string> logQueue; if (_fileUploadTraceLog.TryGetValue(itemPath, out logQueue)) { context.Output(StringUtil.Loc("FileUploadDetailTrace", itemPath)); string message; while (logQueue.TryDequeue(out message)) { context.Output(message); } } // tracking file that failed to upload. failedFiles.Add(fileToUpload); } else { context.Debug(StringUtil.Loc("FileUploadFinish", fileToUpload, uploadTimer.ElapsedMilliseconds)); // debug detail upload trace for the file. ConcurrentQueue <string> logQueue; if (_fileUploadTraceLog.TryGetValue(itemPath, out logQueue)) { context.Debug($"Detail upload trace for file: {itemPath}"); string message; while (logQueue.TryDequeue(out message)) { context.Debug(message); } } } if (response != null) { response.Dispose(); response = null; } } Interlocked.Increment(ref _filesProcessed); } catch (Exception ex) { context.Output(StringUtil.Loc("FileUploadFileOpenFailed", ex.Message, fileToUpload)); throw ex; } } return(failedFiles); }
public static int Main(string[] args) { Console.CancelKeyPress += Console_CancelKeyPress; try { ArgUtil.NotNull(args, nameof(args)); ArgUtil.Equal(2, args.Length, nameof(args.Length)); string pluginType = args[0]; if (string.Equals("task", pluginType, StringComparison.OrdinalIgnoreCase)) { string assemblyQualifiedName = args[1]; ArgUtil.NotNullOrEmpty(assemblyQualifiedName, nameof(assemblyQualifiedName)); string serializedContext = Console.ReadLine(); ArgUtil.NotNullOrEmpty(serializedContext, nameof(serializedContext)); AgentTaskPluginExecutionContext executionContext = StringUtil.ConvertFromJson <AgentTaskPluginExecutionContext>(serializedContext); ArgUtil.NotNull(executionContext, nameof(executionContext)); AssemblyLoadContext.Default.Resolving += ResolveAssembly; try { Type type = Type.GetType(assemblyQualifiedName, throwOnError: true); var taskPlugin = Activator.CreateInstance(type) as IAgentTaskPlugin; ArgUtil.NotNull(taskPlugin, nameof(taskPlugin)); taskPlugin.RunAsync(executionContext, tokenSource.Token).GetAwaiter().GetResult(); } catch (Exception ex) { // any exception throw from plugin will fail the task. executionContext.Error(ex.ToString()); } finally { AssemblyLoadContext.Default.Resolving -= ResolveAssembly; } return(0); } else if (string.Equals("command", pluginType, StringComparison.OrdinalIgnoreCase)) { string assemblyQualifiedName = args[1]; ArgUtil.NotNullOrEmpty(assemblyQualifiedName, nameof(assemblyQualifiedName)); string serializedContext = Console.ReadLine(); ArgUtil.NotNullOrEmpty(serializedContext, nameof(serializedContext)); AgentCommandPluginExecutionContext executionContext = StringUtil.ConvertFromJson <AgentCommandPluginExecutionContext>(serializedContext); ArgUtil.NotNull(executionContext, nameof(executionContext)); AssemblyLoadContext.Default.Resolving += ResolveAssembly; try { Type type = Type.GetType(assemblyQualifiedName, throwOnError: true); var commandPlugin = Activator.CreateInstance(type) as IAgentCommandPlugin; ArgUtil.NotNull(commandPlugin, nameof(commandPlugin)); commandPlugin.ProcessCommandAsync(executionContext, tokenSource.Token).GetAwaiter().GetResult(); } catch (Exception ex) { // any exception throw from plugin will fail the command. executionContext.Error(ex.ToString()); } finally { AssemblyLoadContext.Default.Resolving -= ResolveAssembly; } return(0); } else { throw new ArgumentOutOfRangeException(pluginType); } } catch (Exception ex) { // infrastructure failure. Console.Error.WriteLine(ex.ToString()); return(1); } finally { Console.CancelKeyPress -= Console_CancelKeyPress; } }
public static int Main(string[] args) { // We can't use the new SocketsHttpHandler for now for both Windows and Linux // On linux, Negotiate auth is not working if the TFS url is behind Https // On windows, Proxy is not working AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false); Console.CancelKeyPress += Console_CancelKeyPress; try { ArgUtil.NotNull(args, nameof(args)); ArgUtil.Equal(2, args.Length, nameof(args.Length)); string pluginType = args[0]; if (string.Equals("task", pluginType, StringComparison.OrdinalIgnoreCase)) { string assemblyQualifiedName = args[1]; ArgUtil.NotNullOrEmpty(assemblyQualifiedName, nameof(assemblyQualifiedName)); // Set encoding to UTF8, process invoker will use UTF8 write to STDIN Console.InputEncoding = Encoding.UTF8; Console.OutputEncoding = Encoding.UTF8; string serializedContext = Console.ReadLine(); ArgUtil.NotNullOrEmpty(serializedContext, nameof(serializedContext)); AgentTaskPluginExecutionContext executionContext = StringUtil.ConvertFromJson <AgentTaskPluginExecutionContext>(serializedContext); ArgUtil.NotNull(executionContext, nameof(executionContext)); VariableValue culture; ArgUtil.NotNull(executionContext.Variables, nameof(executionContext.Variables)); if (executionContext.Variables.TryGetValue("system.culture", out culture) && !string.IsNullOrEmpty(culture?.Value)) { CultureInfo.DefaultThreadCurrentCulture = new CultureInfo(culture.Value); CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo(culture.Value); } AssemblyLoadContext.Default.Resolving += ResolveAssembly; try { Type type = Type.GetType(assemblyQualifiedName, throwOnError: true); var taskPlugin = Activator.CreateInstance(type) as IAgentTaskPlugin; ArgUtil.NotNull(taskPlugin, nameof(taskPlugin)); taskPlugin.RunAsync(executionContext, tokenSource.Token).GetAwaiter().GetResult(); } catch (Exception ex) { // any exception throw from plugin will fail the task. executionContext.Error(ex.Message); executionContext.Debug(ex.StackTrace); } finally { AssemblyLoadContext.Default.Resolving -= ResolveAssembly; } return(0); } else if (string.Equals("command", pluginType, StringComparison.OrdinalIgnoreCase)) { string assemblyQualifiedName = args[1]; ArgUtil.NotNullOrEmpty(assemblyQualifiedName, nameof(assemblyQualifiedName)); string serializedContext = Console.ReadLine(); ArgUtil.NotNullOrEmpty(serializedContext, nameof(serializedContext)); AgentCommandPluginExecutionContext executionContext = StringUtil.ConvertFromJson <AgentCommandPluginExecutionContext>(serializedContext); ArgUtil.NotNull(executionContext, nameof(executionContext)); AssemblyLoadContext.Default.Resolving += ResolveAssembly; try { Type type = Type.GetType(assemblyQualifiedName, throwOnError: true); var commandPlugin = Activator.CreateInstance(type) as IAgentCommandPlugin; ArgUtil.NotNull(commandPlugin, nameof(commandPlugin)); commandPlugin.ProcessCommandAsync(executionContext, tokenSource.Token).GetAwaiter().GetResult(); } catch (Exception ex) { // any exception throw from plugin will fail the command. executionContext.Error(ex.ToString()); } finally { AssemblyLoadContext.Default.Resolving -= ResolveAssembly; } return(0); } else { throw new ArgumentOutOfRangeException(pluginType); } } catch (Exception ex) { // infrastructure failure. Console.Error.WriteLine(ex.ToString()); return(1); } finally { Console.CancelKeyPress -= Console_CancelKeyPress; } }