Execute tf.exe (TFS) from C# halts - c#

I need to get the revision number from TFS, if i run tf.exe from a Process the process halts. If i run the same command from command promts it works?
int revision;
var repo = "path to repo"
var psi = new ProcessStartInfo("cmd", #"/c ""C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\tf.exe"" properties $/MYProject -recursive /version:W")
{
UseShellExecute = false,
ErrorDialog = false,
CreateNoWindow = false,
WorkingDirectory = repo,
RedirectStandardOutput = true,
RedirectStandardError = true
};
using (var p = Process.Start(psi))
{
p.WaitForExit();
if (p.ExitCode != 0)
{
using (var standardError = p.StandardError)
{
Console.WriteLine(standardError.ReadToEnd());
}
}
else
{
using (var standardOutput = p.StandardOutput)
{
revision = int.Parse(standardOutput.ReadToEnd());
}
}
}
edit:
I did this, works, should I go with it?
public int GetLatestChangeSet(string url, string project)
{
var server = new TeamFoundationServer(new Uri(url));
var version = server.GetService(typeof(VersionControlServer)) as VersionControlServer;
var items = version.GetItems(string.Format(#"$\{0}", project), RecursionType.Full);
return items.Items.Max(i => i.ChangesetId);
}

you better use the below namespace which contains all you need to achieve that
Microsoft.TeamFoundation.VersionControl.Client
//this is just an example
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
TfsTeamProjectCollection tpc = new TfsTeamProjectCollection(new Uri("http://myserver:8080/"));
VersionControlServer sourceControl = tpc.GetService<VersionControlServer>();
return sourceControl.GetLatestChangesetId();
http://msdn.microsoft.com/en-us/library/ms228232(v=vs.80)

The error occurs because your StandardOutput stream buffer is full and thus blocks. To read standard input/output it's recommended to subscribe to the OutputDataReceived event. Alternatively, spin up another thread to constantly read the data from the StandardOutput stream.
See the example on the OutputDataReceived event docs for a complete code sample.
A better solution would be to use the TFS API as suggested by Massimiliano Peluso. But this is the reason for your approach failing.

I ended up with this solution which uses the local workspace revision
public class ReadTfsRevisionTask : Task
{
public override bool Execute()
{
try
{
ChangesetId = GetLatestChangeSet(Server, Project);
return true;
}
catch
{
return false;
}
}
private int GetLatestChangeSet(string url, string project)
{
project = string.Format(#"$/{0}", project);
var server = new TeamFoundationServer(new Uri(url));
var version = server.GetService<VersionControlServer>();
var workspace = version.QueryWorkspaces(null, WindowsIdentity.GetCurrent().Name, System.Environment.MachineName).First();
var folder = workspace.Folders.First(f => f.ServerItem == project);
return workspace.GetLocalVersions(new[] { new ItemSpec(folder.LocalItem, RecursionType.Full) }, false)
.SelectMany(lv => lv.Select(l => l.Version)).Max();
}
[Required]
public string Server { get; set; }
[Required]
public string Project { get; set; }
[Output]
public int ChangesetId { get; set; }
}

Related

How can i run .cmd file from http trigger azure function

I want to run .cmd file using azure function. I want to run this in background process instead of main process of azure function.
I have already saved the cmd file on azure function platform using Kudu Editor. I can run this locally but after deploying its not working at all (I am also not getting any error).
string cmdFileLocation = #"D:\home\jobs.cmd";
Process proc = new Process();
ProcessStartInfo info = new ProcessStartInfo();
try
{
info.FileName = cmdFileLocation;
info.Arguments = name;
info.WindowStyle = ProcessWindowStyle.Minimized;
info.UseShellExecute = false;
proc.StartInfo = info;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
proc.Start();
proc.WaitForExit();
}
catch (Exception ex)
{
log.LogInformation("Exception Occurred :{0},{1}", ex.Message, ex.StackTrace.ToString());
}
For testing there is curl command in cmd file. The curl will trigger on local machine using azure function as I can see the request coming (https://webhook.site) but after deplying nothing happens.
Here is an easy way of getting any .exe/.cmd running as a web service. You just specify the input parameters to your exe/cmd in a configuration file. You can use binary files as inputs to the exe by specifying a URL to download it from.
Here's what the configuration file looks like
{
"name": "consoleAppToFunctions",
"input": {
"command": "ffmpeg.exe -i {inputFile} {output1}",
"arguments": {
"inputFile": {
"url": "https://1drv.ms/v/<link-to-file>"
//binary file input
},
"output1": {
"localfile": "out.mp3"
//stored in a temp folder
}
}
},
"output": {
"folder": "outputFolder",
"binaryFile": {
"returnFile": "*.mp3",
"returnFileName": "yourFile.mp3"
}
}
}
Here is the AZURE FUNCTION CODE for the same:
#r "Newtonsoft.Json"
using System.Net;
using Newtonsoft.Json;
using System.IO;
using System.Diagnostics;
//Code from https://github.com/Azure-Samples/functions-dotnet-migrating-console-apps/edit/master/code/run.csx
//Written by Ambrose http://github.com/efficientHacks and Murali http://github.com/muralivp
public class ExeArg
{
public string Name { get; set; }
public string Type { get; set; }
public string Value { get; set; }
}
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
string localPath = req.RequestUri.LocalPath;
string functionName = localPath.Substring(localPath.LastIndexOf('/')+1);
var json = File.ReadAllText(string.Format(#"D:\home\site\wwwroot\{0}\FunctionConfig.json",functionName));
var config = JsonConvert.DeserializeObject<dynamic>(json);
var functionArguments = config.input.arguments;
var localOutputFolder = Path.Combine(#"d:\home\data", config.output.folder.Value, Path.GetFileNameWithoutExtension(Path.GetTempFileName()));
Directory.CreateDirectory(localOutputFolder);
var workingDirectory = Path.Combine(#"d:\home\site\wwwroot", functionName + "\\bin");
Directory.SetCurrentDirectory(workingDirectory);//fun fact - the default working directory is d:\windows\system32
var command = config.input.command.Value;
var argList = new List<ExeArg>();
//Parse the config file's arguments
//handle file system, local file etc. and construct the input params for the actual calling of the .exe
foreach (var arg in functionArguments)
{
var exeArg = new ExeArg();
exeArg.Name = arg.Name;
var value = (Newtonsoft.Json.Linq.JObject)arg.Value;
var property = (Newtonsoft.Json.Linq.JProperty)value.First;
exeArg.Type = property.Name;
exeArg.Value = property.Value.ToString();
var valueFromQueryString = await getValueFromQuery(req, exeArg.Name);
log.Info("valueFromQueryString name=" + exeArg.Name);
log.Info("valueFromQueryString val=" + valueFromQueryString);
if(!string.IsNullOrEmpty(valueFromQueryString))
{
exeArg.Value = valueFromQueryString;
log.Info(exeArg.Name + " " + valueFromQueryString);
}
if(exeArg.Type.ToLower() == "localfile" || exeArg.Type.ToLower() == "localfolder")
{
exeArg.Value = Path.Combine(localOutputFolder, exeArg.Value);
exeArg.Type = "string";
}
if(string.IsNullOrEmpty(exeArg.Value))
{
//throw exception here
}
argList.Add(exeArg);
}
//call the exe
Dictionary<string, string> paramList = ProcessParameters(argList, localOutputFolder);
foreach (string parameter in paramList.Keys)
{
command = command.Replace(parameter, paramList[parameter]);
}
string commandName = command.Split(' ')[0];
string arguments = command.Split(new char[] { ' ' }, 2)[1];
log.Info("the command is " + command);
log.Info("the working dir is " + workingDirectory);
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = commandName;
p.StartInfo.Arguments = arguments;
p.Start();
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
File.WriteAllText(localOutputFolder+"\\out.txt",output);
//handle return file
log.Info("handling return file localOutputFolder=" + localOutputFolder);
string outputFile = config.output.binaryFile.returnFile.Value;
string outputFileName = config.output.binaryFile.returnFileName.Value;
var path = Directory.GetFiles(localOutputFolder, outputFile)[0];
log.Info("returning this file " + path);
var result = new FileHttpResponseMessage(localOutputFolder);
var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = outputFileName
};
return result;
}
private static Dictionary<string, string> ProcessParameters(List<ExeArg> arguments, string outputFolder)
{
Dictionary<string, string> paramList = new Dictionary<string, string>();
foreach (var arg in arguments)
{
switch (arg.Type)
{
case "url":
string downloadedFileName = ProcessUrlType((string)arg.Value, outputFolder);
paramList.Add("{" + arg.Name + "}", downloadedFileName);
break;
case "string":
paramList.Add("{" + arg.Name + "}", arg.Value.ToString());
break;
default:
break;
}
}
return paramList;
}
//you can modify this method to handle different URLs if necessary
private static string ProcessUrlType(string url, string outputFolder)
{
Directory.CreateDirectory(outputFolder);
string downloadedFile = Path.Combine(outputFolder, Path.GetFileName(Path.GetTempFileName()));
//for oneDrive links
HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(url);
webRequest.AllowAutoRedirect = false;
WebResponse webResp = webRequest.GetResponse();
webRequest = (HttpWebRequest)HttpWebRequest.Create(webResp.Headers["Location"].Replace("redir", "download"));
webResp = webRequest.GetResponse();
string fileUrl = webResp.Headers["Content-Location"];
WebClient webClient = new WebClient();
webClient.DownloadFile(fileUrl, downloadedFile);
return downloadedFile;
}
private async static Task<string> getValueFromQuery(HttpRequestMessage req, string name)
{
// parse query parameter
string value = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, name, true) == 0)
.Value;
//if not found in query string, look for it in the body (json)
// Get request body
dynamic data = await req.Content.ReadAsAsync<object>();
// Set name to query string or body data
value = value ?? data?[name];
return value;
}
//this is to delete the folder after the response
//thanks to: https://stackoverflow.com/a/30522890/2205372
public class FileHttpResponseMessage : HttpResponseMessage
{
private string filePath;
public FileHttpResponseMessage(string filePath)
{
this.filePath = filePath;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
Content.Dispose();
Directory.Delete(filePath,true);
}
}
Here you can find more on this. Hope it helps.

TFS SDK launch threeway merge

There is a class for ThreeWayMerge in TFS SDK. There is a function to Run it. There is a property to run merge in external tool. But when I run code like this to resolve conflict, nothing is happening. No error. No result. Output temporary file gets deleted. When I would expect tool to be launched like it does with Difference.VisualDiffItems. Am I wrong in my expectations or doing something wrong?
public void Merge(string sourceFile)
{
string tempBase = Path.GetTempFileName();
string tempLatest = Path.GetTempFileName();
string tempModified = Path.GetTempFileName();
string tempOutput = Path.GetTempFileName();
Conflict[] conflicts = workSpace.QueryConflicts(new string[] { sourceFile }, false);
Conflict cnf = conflicts[0];
cnf.DownloadBaseFile(tempBase);
//cnf.DownloadYourFile(tempModified);
File.Copy(cnf.LocalPath, tempModified,true);
cnf.DownloadTheirFile(tempLatest);
int baseCodePage = FileType.Detect(tempBase, null);
int latestCodePage = FileType.Detect(tempLatest, null);
int modifiedCodePage = FileType.Detect(tempModified, null);
ThreeWayMerge threeWayMerge = new ThreeWayMerge();
threeWayMerge.UseExternalMergeTool = true;
threeWayMerge.UseExternalAutomergeTool = true;
threeWayMerge.VersionControlServerGuid = VCS.ServerGuid;
threeWayMerge.LatestFileName = tempLatest;
threeWayMerge.LatestFileEncoding = Encoding.GetEncoding(latestCodePage);
threeWayMerge.ModifiedFileName = tempModified;
Encoding modifiedEncoding = Encoding.GetEncoding(modifiedCodePage);
threeWayMerge.ModifiedFileEncoding = modifiedEncoding;
threeWayMerge.OriginalFileName = tempBase;
threeWayMerge.OriginalFileEncoding = Encoding.GetEncoding(baseCodePage);
threeWayMerge.MergedFileName = tempOutput;
threeWayMerge.MergedFileEncoding = modifiedEncoding;
if (threeWayMerge.Run())
{
Console.WriteLine("Something");
}
}

Running a PythonScript as a process and reading Standard Output issue

I am trying to run a python script as a process to which I pass a couple of parameters and then read standard output. I have a little console app and a dummy script that works fine, but when I do the same thing in my WebApi project, Standard Output is always blank, and I cannot figure out why. My code follows:
Console App
class Program
{
private static string Foo(string inputString)
{
string result = String.Empty;
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = "python";
start.Arguments = string.Format(" {0} {1} {2}", #"*path*\runner.py", #"*path*\test2.py", inputString);
start.UseShellExecute = false;
start.RedirectStandardOutput = true;
using (Process process = Process.Start(start))
{
using (StreamReader reader = process.StandardOutput)
{
result = reader.ReadToEnd();
}
}
return result;
}
static void Main(string[] args)
{
var result = Foo("flibble");
Console.Write(result);
Console.ReadKey();
}
}
runner.py (for the console app)
import sys, imp
test = imp.load_source('test',sys.argv[1])
result = test.hello(sys.argv[2])
test2.py (from the console app)
import sys
def hello(inputString):
sys.stdout.write(inputString)
return
That is the end of what I have that works, now onto the code where the issue is:
ApiEndpoint
[HttpPost]
public IHttpActionResult TestEndpoint()
{
string postedJson = ReadRawBuffer().Result;
if (postedJson == null) throw new ArgumentException("Request is null");
var result = _pythonOperations.Foo(postedJson);
// Deal with result
return Ok();
}
_pythonOperations.Foo()
public string Foo(string inputString)
{
string result;
var start = new ProcessStartInfo
{
FileName = _pathToPythonExecutable,
Arguments = string.Format(" {0} {1} {2}", _pathToPythonRunnerScript, _pathToPythonFooScript, inputString),
UseShellExecute = false,
RedirectStandardOutput = true
};
using (Process process = Process.Start(start))
{
using (StreamReader reader = process.StandardOutput)
{
result = reader.ReadToEnd();
}
}
return result;
}
pythonRunnerScript
import sys, imp
module = imp.load_source('foo', sys.argv[1])
module.Foo(sys.argv[2])
Foo script
import sys
def Foo(inputString)
outputString = "output"
sys.stdout.write(outputString)
return
This is quite possibly one of the longest posts I have made, so thanks for taking the time to read it, and hopefully I can get a solution to this.
Cheers
Turns out the format I was passing in was wrong. I was using Postman REST Api client, and pasting the huge amounts of data into their request content window truncated it, leaving me with half a line. Once this was sorted, everything ran through ok.

I am having trouble comparing two lists. The purpose of the program is to perform offline patch installation

The problem comes in the looping. It seems to perform the comparison between the wmi qfe list and the KB list. The issue I run into is taking and searching the contents of the .msu file name list for the KB and then installing it. I have made a similar program in python and was mimicking a similar construct in the C# code. Any guidance on how to make this function properly would be greatly appreciated. The code is as follows:
static void Server08r2Patches()
{
var IE9 = from a in IAVA.Worksheet<IE9>("IE9") select a;
var Server08r2 = from a in IAVA.Worksheet<Server08r2>("Server08r2") select a;
var KBlist = Server08r2.Select(s=>new {KB = s.KB}).ToList();
var ExeList = Server08r2.Select(s=>new {Executable = s.Executable}).ToList();
string path = (Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)+#"\x64\");
foreach (var item in KBlist)
{
if (OsPatches.Contains(item.KB))
{
Console.WriteLine(item.KB+" is already installed.");
}
else
{
Console.WriteLine(item.KB+" will now be installed.");
foreach (var inst in ExeList)
{
do
{
var kb_inst = new ProcessStartInfo()
{
CreateNoWindow = true,
UseShellExecute = true,
FileName =path+inst.Executable,
WindowStyle = ProcessWindowStyle.Hidden,
Arguments = (#" /quiet /norestart"),
};
try
{
Process update = new Process();
update.StartInfo = kb_inst;
update.Start();
update.WaitForExit();
Console.WriteLine(inst.Executable+" was successfully installed");
}
catch (Exception Ex)
{
Console.WriteLine(Ex.Message+" "+inst.Executable);
}
}
while (inst.Executable.Contains(item.KB) == true);
}
}
}
}

How can I programmatically check-out an item for edit in TFS?

I'm working on a utility processing files being under source control using TFS 2010.
If an item is not yet checked-out for edit, I'm getting an exception, what is definitely predictable because file is in read-only mode.
What ways exist to check-out a file?
P.S. I want something for programmatic rather then Process.Start("tf.exe", "..."); if that's applicable.
Some of the other approaches mentioned here only work for certain versions of TFS or make use of obsolete methods. If you are receiving a 404, the approach you are using is probably not compatible with your server version.
This approach works on 2005, 2008, and 2010. I don't use TFS any longer, so I haven't tested 2013.
var workspaceInfo = Workstation.Current.GetLocalWorkspaceInfo(fileName);
using (var server = new TfsTeamProjectCollection(workspaceInfo.ServerUri))
{
var workspace = workspaceInfo.GetWorkspace(server);
workspace.PendEdit(fileName);
}
private const string tfsServer = #"http://tfsserver.org:8080/tfs";
public void CheckOutFromTFS(string fileName)
{
using (TfsTeamProjectCollection pc = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(tfsServer)))
{
if (pc != null)
{
WorkspaceInfo workspaceInfo = Workstation.Current.GetLocalWorkspaceInfo(fileName);
if (null != workspaceInfo)
{
Workspace workspace = workspaceInfo.GetWorkspace(pc);
workspace.PendEdit(fileName);
}
}
}
FileInfo fi = new FileInfo(fileName);
}
Note that Microsoft.TeamFoundation.Client.TeamFoundationServerFactory is obsolete: The TeamFoundationServer class is obsolete. Use the TeamFoundationProjectCollection or TfsConfigurationServer classes to talk to a 2010 Team Foundation Server.
In order to talk to a 2005 or 2008 Team Foundation Server use the TeamFoundationProjectCollection class. The corresponding factory class for that is the TfsTeamProjectCollectionFactory.
You can use Team Foundation Version Control client API.
The method is PendEdit()
workspace.PendEdit(fileName);
Checkout detailed example on MSDN
http://blogs.msdn.com/b/buckh/archive/2006/03/15/552288.aspx
First get the workspace
var tfs = new TeamFoundationServer("http://server:8080/tfs/collection");
var version = (VersionControlServer)tfs.GetService(typeof(VersionControlServer));
var workspace = version.GetWorkspace("WORKSPACE-NAME", version.AuthorizedUser);
With the workspace you can checkout the file
workspace.PendEdit(fileName);
var registerdCollection = RegisteredTfsConnections.GetProjectCollections().First();
var projectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(registerdCollection);
var versionControl = projectCollection.GetService<VersionControlServer>();
var workspaceInfo = Workstation.Current.GetLocalWorkspaceInfo(_fileName);
var server = new TeamFoundationServer(workspaceInfo.ServerUri.ToString());
var workspace = workspaceInfo.GetWorkspace(server);
workspace.PendEdit(fileName);
I have two approaches how to do that: simple and advanced.
1). Simple:
#region Check Out
public bool CheckOut(string path)
{
using (TfsTeamProjectCollection pc = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(ConstTfsServerUri)))
{
if (pc == null) return false;
WorkspaceInfo workspaceInfo = Workstation.Current.GetLocalWorkspaceInfo(path);
Workspace workspace = workspaceInfo?.GetWorkspace(pc);
return workspace?.PendEdit(path, RecursionType.Full) == 1;
}
}
public async Task<bool> CheckoutAsync(string path)
{
return await Task.Run(() => CheckOut(path));
}
#endregion
2). Advanced (with receiving status):
private static string GetOwnerDisplayName(PendingSet[] pending)
{
var result = pending.FirstOrDefault(pendingSet => pendingSet.Computer != Environment.MachineName) ?? pending[0];
return result.OwnerDisplayName;
}
private string CheckoutFileInternal(string[] wsFiles, string folder = null)
{
try
{
var workspaceInfo = Workstation.Current.GetLocalWorkspaceInfo(folder);
var server = new TfsTeamProjectCollection(workspaceInfo.ServerUri);
var workspace = workspaceInfo.GetWorkspace(server);
var request = new GetRequest(folder, RecursionType.Full, VersionSpec.Latest);
GetStatus status = workspace.Get(request, GetOptions.None);
int result = workspace.PendEdit(wsFiles, RecursionType.Full, null, LockLevel.None);
if (result == wsFiles.Length)
{
//TODO: write info (succeed) to log here - messageText
return null;
}
var pending = server.GetService<VersionControlServer>().QueryPendingSets(wsFiles, RecursionType.None, null, null);
var messageText = "Failed to checkout !.";
if (pending.Any())
{
messageText = string.Format("{0}\nFile is locked by {1}", messageText, GetOwnerDisplayName(pending));
}
//TODO: write error to log here - messageText
return messageText;
}
catch (Exception ex)
{
UIHelper.Instance.RunOnUiThread(() =>
{
MessageBox.Show(Application.Current.MainWindow, string.Format("Failed checking out TFS files : {0}", ex.Message), "Check-out from TFS",
MessageBoxButton.OK, MessageBoxImage.Error);
});
return null;
}
}
public async Task<string> CheckoutFileInternalAsync(string[] wsFiles, string folder)
{
return await Task.Run(() => CheckoutFileInternal(wsFiles, folder));
}

Categories