I need to add a custom activity to the default workflow template to increase assembly versions at the earliest point possible in the build process.
What I would like to achieve is to create and map the exact same workspace (that is be created further down in the workflow) inside my custom activity so that I can check out an xml file, increase the version number held within, write it back to the xml file and check the xml file back in.
I'm aware that this workspace will be created later on in the workflow but that will be too late in the build process for what I'm trying to achieve, so instead of moving any of the activities or duplicating them in a position above my custom activity (this should be ok as this workspace will be deleted and recreated again later)
I think the details I need are the BuildDirectory, WorkspaceName and SourcesDirectory. Can anyone tell me how to achieve the creation of the workspace or how obtain this data in code?
the build will be carried out on a build server, and I am using TFS 2010 and c#.
Thanks in advance
I followed the series of blog articles by Ewald Hofman as a primer and created a custom activity that does the check-out, update and check-in of a GlobalAssemblyInfo file that I parse the current version from. My task is inserted at the top of the "Update Drop Location" which is right after it does the "Get the build" portion of the workflow. I just use require the IBuildDetail and a File Mask as arguments from which you can pull out the VersionControlServer to be able to access TFS. My code is below:
protected override string Execute(CodeActivityContext context)
{
// Obtain the runtime value of the input arguments.
string assemblyInfoFileMask = context.GetValue(AssemblyInfoFileMask);
IBuildDetail buildDetail = context.GetValue(BuildDetail);
var workspace = buildDetail.BuildDefinition.Workspace;
var versionControl = buildDetail.BuildServer.TeamProjectCollection.GetService<VersionControlServer>();
Regex regex = new Regex(AttributeKey + VersionRegex);
// Iterate of the folder mappings in the workspace and find the AssemblyInfo files
// that match the mask.
foreach (var folder in workspace.Mappings)
{
string path = Path.Combine(folder.ServerItem, assemblyInfoFileMask);
context.TrackBuildMessage(string.Format("Checking for file: {0}", path));
ItemSet itemSet = versionControl.GetItems(path, RecursionType.Full);
foreach (Item item in itemSet.Items)
{
context.TrackBuildMessage(string.Format("Download {0}", item.ServerItem));
string localFile = Path.GetTempFileName();
try
{
// Download the file and try to extract the version.
item.DownloadFile(localFile);
string text = File.ReadAllText(localFile);
Match match = regex.Match(text);
if (match.Success)
{
string versionNumber = match.Value.Substring(AttributeKey.Length + 2, match.Value.Length - AttributeKey.Length - 4);
Version version = new Version(versionNumber);
Version newVersion = new Version(version.Major, version.Minor, version.Build + 1, version.Revision);
context.TrackBuildMessage(string.Format("Version found {0}", newVersion));
return newVersion.ToString();
}
}
finally
{
File.Delete(localFile);
}
}
}
return null;
}
Related
Purpose: I have an SSIS Solution with various packages, one set of these packages is currently created with a package per table due to them having different structures. The aim is to create a template package (done), update variables/table names (done), reinitialise and re-map columns, execute package for each table.
Problem: So, as you can tell, I'm up to the point that i need to reinitialise but I am unable to get to the data flow task and keep getting this error:
No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE))
When I run this code:
//// Get Data Flow task
TaskHost tHost = pkgOut.Executables[0] as TaskHost;
MainPipe dataFlowTask = (MainPipe)tHost.InnerObject;
The code that I am using is below:
public void Main()
{
// Set Project Variables
List<string> pkgVars = new List<string>
{
#"pPR_SSIS_Catalog_Server",
#"pPR_SSIS_Catalog_Project",
#"pPR_SSIS_Catalog_Folder"
};
// Get Package
String pkgLocation = #"C:\PATH\TO\TEMPLATE\PACKAGE\a_Load_SAP_Template.dtsx";
Application app = new Application();
Package pkgIn = app.LoadPackage(pkgLocation, null);
String pkgName = "DynamicPackage";
// Add Connections (cos they're project connections and aren't stored in package)
ConnectionEnumerator allConns = Dts.Connections.GetEnumerator();
while ((allConns.MoveNext()) && (allConns.Current != null))
pkgIn.Connections.Join(allConns.Current);
// Convert new package to XML so we can cheat and just do a find and replace
XmlDocument pkgXML = new XmlDocument();
pkgIn.SaveToXML(ref pkgXML, null, null);
// Replace strings
// Set SAP table
String pkgStr = pkgXML.OuterXml.ToString();
pkgStr = pkgStr.Replace("#SAP_SRC_TABLE#", "MyF-ingSAPTables"); // Replace SAP Table Name
// Set Project Variables references == values -- REMEMBER TO CHECK FOR INT PARAMS zzz
foreach (string var in pkgVars)
pkgStr = pkgStr.Replace(#"#[$Project::" + var + #"]", #"""" + Convert.ToString(Dts.Variables[var].Value) + #"""");
// Convert back to XML
XmlDocument newXML = new XmlDocument();
newXML.LoadXml(pkgStr);
Package pkgOut = new Package();
pkgOut.LoadFromXML(newXML, null);
//// Get Data Flow task
TaskHost tHost = pkgOut.Executables[0] as TaskHost;
MainPipe dataFlowTask = (MainPipe)tHost.InnerObject; // THIS IS WHERE THE CODE ERRORS
new Application().SaveToXml(String.Format(#"D:\PATH\TO\SAVE\LOCATION\{0}.dtsx", pkgName), pkgOut, null);
Dts.TaskResult = (int)ScriptResults.Success;
}
I have tried:
Removing everything but the data flow
Recreated a blank data flow
Recreated the package
Made a package in the script and created the data flow (this works but when i open the package, the data flow does not appear...might have added it wrong but it can see it when I run the TaskHost section)
I am not sure what else I can try, I have seen information that I might need to re-register some .dlls but I do not have admin access and I'd prefer not to go through the hassle for something where I do not know whether it will work.
Any help would be appreciated.
Thanks,
Fixed, was due to version difference in the .DLL that was being loaded. Loaded an older version and all went through fine.
I am using the TFS API to get latest code files, directories, .csproj files, etc. under a TFS-bound folder.
For the same, I use something like the following:
var tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new
Uri(ConfigurationManager.AppSettings["TFSUrl"]));
tfs.EnsureAuthenticated();
var vsStore = tfs.GetService<VersionControlServer>();
string workingFolder = #"C:\TFS\SolutionFolder";
Workspace wsp = vsStore.TryGetWorkspace(workingFolder);
if (wsp != null)
{
ItemSet items = vsStore.GetItems(workingFolder, VersionSpec.Latest, RecursionType.Full);
string relativePath = workingFolder + #"/";
foreach (Item item in items.Items)
{
string relativePath1 = item.ServerItem.Replace("$/TFS/SolutionFolder", relativePath);
if (item.ItemType == ItemType.Folder)
{
Directory.CreateDirectory(relativePath1);
}
else
{
item.DownloadFile(relativePath1);
}
}
}
Now, I get the items to download and then download happens. However, I want it to be like how VS handles it - if (and only if) there is a change in a file/folder, then only download the same. With this code, I always get 'n' number of files/folders in that folder and then I overwrite the same. Wrong approach, I know. I can, however, modify this code to check for the folder's or file's last change time and then choose to either overwrite it or ignore it. That's an option, albeit a bad one at that.
Now, what I would ideally like is to get ONLY the list of files/folders that actually need to be changed i.e. the incremental change. After that, I can choose to overwrite/ignore each item in that list. So, in the present case, if a new file/folder is created (or one of the existing ones got changed inside $/TFS/SolutionFolder i.e. in the sever), then only I want to pull that item in the list of files/folders to change(and decide what I want to do with it inside C:\TFS\SolutionFolder).
Also, is using one of the overloads of VersionControlServer.QueryHistory() an option? I had something like this:
(latestVersionIdOf $/TFS/SolutionFolder) - (existingVersionIdOf C:\TFS\SolutionFolder) = (Versions that I'd go out and get back from the server, for that folder)
in mind.
Any pointers will be very helpful. Thanks!
Just use Workspace.Get() or overload method (wsp.Get()), it just update updated files.
I don't think we can achieve that. If the files are downloaded to a folder without in source control, there are no versions compared within the folder, even if the folder is in source control, the behavior is just download also no version compare actions. So, it will download all the files ever time and then overwrite the same ones.
In VS, the files are all in TFS source control system, so when we Get Latest Version the changed/added files will be retrieved from TFS. If you want to get the same behavior as VS handles, you can use the tf get command. See Get Command
You can reference this article to use the tf get command :
get-latest-version-of-specific-files-with-tfs-power-tools
Update :-
var tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(ConfigurationManager.AppSettings["TFSUrl"]));
tfs.EnsureAuthenticated();
var vsStore = tfs.GetService<VersionControlServer>();
string workingFolder = ConfigurationManager.AppSettings["LocalPathToFolder"]; // C:\TFS\SolutionFolder
string tfsPathToFolder = ConfigurationManager.AppSettings["TFSPathToFolder"]; // $/TFS/SolutionFolder
Workspace wsp = vsStore.GetWorkspace(workingFolder);
if (wsp != null)
{
ItemSpec[] specs = { new ItemSpec(tfsPathToFolder, RecursionType.Full) };
ExtendedItem[][] extendedItems = wsp.GetExtendedItems(specs, DeletedState.NonDeleted, ItemType.Any);
ExtendedItem[] extendedItem = extendedItems[0];
var itemsToDownload = extendedItem.Where(itemToDownload => itemToDownload.IsLatest == false);
foreach (var itemToDownload in itemsToDownload)
{
try
{
switch (itemToDownload.ItemType)
{
case ItemType.File:
if (itemToDownload.LocalItem != null)
{
vsStore.DownloadFile(itemToDownload.SourceServerItem, itemToDownload.LocalItem);
}
else
{
string localItemPath = itemToDownload.SourceServerItem.Replace(tfsPathToFolder,
workingFolder);
vsStore.DownloadFile(itemToDownload.SourceServerItem, localItemPath);
}
break;
case ItemType.Folder:
string folderName = itemToDownload.SourceServerItem.Replace(tfsPathToFolder, workingFolder);
if ((!string.IsNullOrEmpty(folderName)) && (!Directory.Exists(folderName)))
{
Directory.CreateDirectory(folderName);
}
break;
}
}
catch (Exception e)
{
File.AppendAllText(#"C:\TempLocation\GetLatestExceptions.txt", e.Message);
}
}
}
This code works well, except:
a. Whenever it downloads the latest copy of, let's say a file, it 'checks it out' in TFS :(
b. For some items, it throws errors like 'Item $/TFS/SolutionFolder/FolderX/abc.cs was not found in source control at version T.' - I have to find out what the exact cause of this issue is, though.
Any ideas on how to get around these two issues or any other problems you see with this code? Thanks!
i´m currently working on a programm which updates templates on our companies Team Foundation Server. I am having those new templates locally on my disk and want to replace the existing ones on the server. I was trying different approaches and this is my newest version. The problem is that either
the new file is "in use" when accessing it through coding in c#(while not in use when i try to replace it in runtime using the normal explorer).
the replacement is not appearing in the pending changes, the pendingChanges array is initial.
using (var tfs = TeamFoundationServerFactory.GetServer("myserver"))
{
var versionControlServer = tfs.GetService(typeof(VersionControlServer)) as VersionControlServer;
// Create a new workspace for the currently authenticated user.
var workspace = versionControlServer.CreateWorkspace("Temporary Workspace", versionControlServer.AuthorizedUser);
try
{
// Check if a mapping already exists.
var workingFolder = new WorkingFolder("$serverpath", #"c:\tempFolder");
// Create the mapping (if it exists already, it just overides it, that is fine).
workspace.CreateMapping(workingFolder);
workspace.Get(VersionSpec.Latest, GetOptions.GetAll);
string[] paths = new string[1];
paths[0] = "test.pdf";
workspace.PendEdit(paths, RecursionType.Full, null, LockLevel.None);
// Go through the folder structure defined and create it locally, then check in the changes.
CreateFolderStructure(workspace, workingFolder.LocalItem);
// Check in the changes made.
int a = workspace.CheckIn(workspace.GetPendingChanges(), "This is my comment");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
// Cleanup the workspace.
workspace.Delete();
// Remove the temp folder used.
Directory.Delete(#"C:\tempFolder", true);
}
}
}
static void CreateFolderStructure(Workspace workspace, string initialPath)
{
workspace.PendDelete("$serverpath/test.pdf", RecursionType.None);
File.Copy(#"C:\test\testnew.pdf", #"C:\tempfolder\test", true);
workspace.PendAdd(#"C:\tempfolder\test.pdf");
}
I found a solution to the problem. The workspace which was used by "authorizedUser" was obviously not enough.. I found out that a need a TeamFoundationIdentity to do it. Here is a guide on how to fix the issue.
http://blogs.msdn.com/b/taylaf/archive/2010/03/29/using-tfs-impersonation-with-the-version-control-client-apis.aspx
I have been playing around with TFS for a while because I need to upload/check in specific files and folders into various locations on the TFServer after they have been created and placed locally. I am mapping the workspace and everything and I get a bunch of changes from PendingChanges but not the ones I want and not where I want. The problem is really annoying because for every check in process I only want to work on a specific folder or file in a specific location that is already mapped. I am gonna paste part of the code here for reference.
using (TfsTeamProjectCollection collection = new TfsTeamProjectCollection(serverUri, _cred))
{
VersionControlServer versionControl = (VersionControlServer)collection.GetService(typeof(VersionControlServer));
string machineName = Environment.MachineName;
string currentUserName = Environment.UserName;
Workspace myWorkspace = versionControl.GetWorkspace(machineName, currentUserName);
// tried this from Stack but didn't work.
//PendingChange[] changes = myWorkspace.GetPendingChanges().Where(x => x.LocalOrServerFolder.Contains(localPath)).ToArray();
PendingChange[] changes = myWorkspace.GetPendingChanges();
if (changes.Length != 0)
{
foreach (PendingChange c in changes)
{
rt.Text += " path: " + c.LocalItem + ", change: " + PendingChange.GetLocalizedStringForChangeType(c.ChangeType) + "\n";
}
}
else
{
rt.Text += "This didn't work.";
}
}
Basically I want to throw a folder or file at my TFS class and let it check if the file or folder is up to date or present on the TFS and act accordingly. I hope I have described the problem thoroughly.
I think that my answer will not response fully you question, but if you want to get pending changes for a specific folder, you can use something like this..
PendingChange[] changes = myWorkspace.GetPendingChanges(localPath, RecursionType.Full, false);
I've been writing a VB Add-in for my company that will go into TFS and automatically mark files that end with ".delete" for deletion. To do this, I'd like to create a workspace "Temp" which maps to my D:\TFSTemp in my local and a folder in TFS. Then, I'd like to download only the .delete files to my local (to avoid having to get latest on all the files in the server), map them from my local to the server, mark them for deletion (workspace.PendDelete()) and then check them all in at once.
My problem is I am not sure I am setting up the correct mapping needed. I am able to download all the .delete files, yet when I invoke Workspace.GetPendingChanges(), the array is not being populated, which is why I suspect I might be not setting it up correctly.
I understand it is a complicated add-in, so please ask me questions if my code does not make sense to you.
//establish connection to tfs
TeamFoundationServer server = new TeamFoundationServer(TFS1);
//test file to output to
StreamWriter xw = new StreamWriter(#"C:\Documents and Settings\A087649\Desktop\FileList.txt");
//get a working object in tfs
VersionControlServer sourceControl = server.GetService(typeof(VersionControlServer)) as VersionControlServer;
int numberOfFiles = 0;
int numToDelete = 0;
try
{
//load config file
//LoadConfig();
//path where we are going to look in tfs
String path = #"$/PAIT_ECOMPARE/Dev/TFSTool/Prod/Offeringdata/AU/CT";
//array of item objects in that path
ItemSet items = sourceControl.GetItems(path, RecursionType.Full);
numberOfFiles = items.Items.Length;
Workspace workspace = sourceControl.CreateWorkspace("Temp");
WorkingFolder workingFolder = new WorkingFolder(path, #"D:\TFSTemp\");
workspace.CreateMapping(workingFolder);
//instance of own created class that represents the progressbar and log output
TFSToolLoad ProgressBar = new TFSToolLoad();
ProgressBar.SetValues(numberOfFiles);
ProgressBar.TopMost = true;
foreach (Item item in items.Items)
{
ProgressBar.Show();
//get only the file path to the file
serverPath = item.ServerItem;
//get changeset Id
changeSetID = item.ChangesetId;
if (serverPath.EndsWith(".delete"))
{
//get file name only and local path
fileName = Path.GetFileName(serverPath);
localPath = #"D:\TFSTemp\"+ fileName;
//get latest on the file
workspace.Get(new GetRequest(serverPath, RecursionType.None, VersionSpec.Latest), GetOptions.None);
workspace.PendDelete(serverPath, RecursionType.None);
numToDelete++;
}
ProgressBar.Step();
}
ProgressBar.SetText
("Number of Files Marked for Delete: " + numToDelete+"\n");
//if there are any pending changes, check them in and merge them into staging
if (numToDelete > 0)
{
//check in all the changes
ProgressBar.SetText("Checking in changes...\n");
PendingChange[] pendingChanges = workspace.GetPendingChanges();
//if there are any pending changes, check them in and merge them into staging
workspace.CheckIn(pendingChanges, "Automated TFS tool cleanup");
ProgressBar.SetText("Done\n Merging changes into Staging...");
//merge
//set up merge by changeset id
ChangesetVersionSpec changeSet = new ChangesetVersionSpec(changeSetID);
//map to target server path before merging, otherwise it won't work
Workspace eCompareAdmin = sourceControl.GetWorkspace(#"D:\PAIT_ECOMPARE");
string mainPath = #"$/PAIT_ECOMPARE/Dev/TFSTool";
string stagingPath = #"$/PAIT_ECOMPARE/Dev/TFSToolStaging";
//Problem:
eCompareAdmin.Merge(mainPath, stagingPath, changeSet, changeSet);
PendingChange[] mergeChanges = eCompareAdmin.GetPendingChanges();
workspace.CheckIn(mergeChanges, "Automated TFS Cleanup");
ProgressBar.SetText("Done\n");
You can't pend a delete until you've done a get of the item into your local workspace.
You're currently doing a DownloadFile, which will simply get the contents of the file - it will not update your workspace to reflect that you have the file locally. You should instead call Workspace.Get for that file before pending your delete.
One other item: you should not be using full recursion for files (without being aware of the consequences), you should probably be using RecursionType.None. Full recursion on a file performs pattern matching and will include all files with that filename beneath the given path. (Ie, full recursion for $/file.txt will match $/file.txt, $/A/file.txt, $/A/B/file.txt, etc.)