Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get latest from Visual Studio Team Services using Command Line passing /login credentials with TF.exe

Has anyone had success getting latest source code from the Visual Studio Team Services (formerly Visual Studio Online, Team Foundation Service) Version Control Server using the command line and passing in credentials programmatically?

-I have discovered that you can't use the Windows ID credentials that you use to login to Team Explorer or the VSO website in the command line. You need to create Alternate Credentials for the user profile in Team Services.

-I have found out that if you omit the /login in tf.exe, the Team Services login dialog appears and asks you to type in your Windows ID credentials (unless they are already cached in your Team Explorer or Visual Studio (or even possibly Browser and Windows Credential Caches)

-I have found out that the alternate credential work Using the Java version of tf.exe - Team Explorer Everywhere Command Line Client (TEE CLC). TEE CLC actually uses the /login credentials that you pass in and lets you connect. The same thing does NOT seem to be possible with the TF.EXE in C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\ BUT INSTALLING JAVA ON THIS BUILD ENVIRONMENT IS AGAINST POLICY. So the TEE CLC is NOT a viable option.

tf get $/MyProj /collection:https://myaccount.visualstudio.com/DefaultCollection /login:user:pass 

the above command simply ignores the /login credentials if you have the Windows ID credentials cached or it returns the error message TF30063: You are not authorized to access myaccount.visualstudio.com (which is not true, because the credentials DO work with the Java client)

Are there any other alternatives that do not require installing Java?

like image 397
Tion Avatar asked Feb 14 '23 04:02

Tion


2 Answers

I got an answer from Microsoft Support: AA Creds for VSO do not work with TF.EXE at this time. TEE CLC or using object model code are the only alternatives currently. We are looking at doing this in the future.

Object Model Code refers to the Microsoft.TeamFoundation.VersionControl.Client Namespace in the dll by the same name. I ended up writing a quick C# console app to download the latest code without installing Java. An added benefit of this approach is that it does not require creating a throwaway Workspace.

if you use the code below to create an executable called tfsget.exe it can be called from the command line like this:

tfsget https://myvso.visualstudio.com/DefaultCollection $/MyProj/Folder c:\Projects login password

and I added a silent switch to suppress listing each file that can be used like:

tfsget https://myvso.visualstudio.com/DefaultCollection $/MyProj/Folder c:\Projects login password silent

here's the code, hope this helps until MS updates TF.exe

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;

namespace TfsGet
{
    class Program
    {
        static void Main(string[] args)
        {
            var tfsParams = TfsDownloadParams.Create(args);

            var tpc = new TfsTeamProjectCollection(new Uri(tfsParams.ServerUrl), tfsParams.Credentials);

            CheckAccess(tpc, tfsParams);

            Download(tpc, tfsParams);

        }

        private static void CheckAccess(TfsTeamProjectCollection tpc, TfsDownloadParams tfsParams)
        {
            try
            {
                tpc.Authenticate();
            }
            catch
            {
                Console.WriteLine("TFS Authentication Failed");
                Console.WriteLine("Server Url:{0}", tfsParams.ServerUrl);
                Console.WriteLine("Project Path:{0}", tfsParams.ServerProjectPath);
                Console.WriteLine("Target Path:{0}", tfsParams.TargetPath);
                Environment.Exit(1);
            }
        }

        static void Download(TfsTeamProjectCollection tpc, TfsDownloadParams tfsParams)
        {   
            var versionControl = tpc.GetService<VersionControlServer>();
            // Listen for the Source Control events.
            versionControl.NonFatalError += Program.OnNonFatalError;

            var files = versionControl.GetItems(tfsParams.ServerProjectPath, VersionSpec.Latest, RecursionType.Full);
            foreach (Item item in files.Items)
            {
                var localFilePath = GetLocalFilePath(tfsParams, item);

                switch (item.ItemType)
                {
                    case ItemType.Any:
                        throw new ArgumentOutOfRangeException("ItemType.Any - not sure what to do with this");
                    case ItemType.File:
                        if (!tfsParams.Silent) Console.WriteLine("Getting: '{0}'", localFilePath);
                        item.DownloadFile(localFilePath);
                        break;
                    case ItemType.Folder:
                        if (!tfsParams.Silent) Console.WriteLine("Creating Directory: {0}", localFilePath);
                        Directory.CreateDirectory(localFilePath);
                        break;
                }
            }
        }

        private static string GetLocalFilePath(TfsDownloadParams tfsParams, Item item)
        {
            var projectPath = tfsParams.ServerProjectPath;
            var pathExcludingLastFolder = projectPath.Substring(0, projectPath.LastIndexOf('/')+1);
            string relativePath = item.ServerItem.Replace(pathExcludingLastFolder, "");
            var localFilePath = Path.Combine(tfsParams.TargetPath, relativePath);
            return localFilePath;
        }

        internal static void OnNonFatalError(Object sender, ExceptionEventArgs e)
        {
            var message = e.Exception != null ? e.Exception.Message : e.Failure.Message;
            Console.Error.WriteLine("Exception: " + message);
        }
    }

    public class TfsDownloadParams
    {
        public string ServerUrl { get; set; }
        public string ServerProjectPath { get; set; }
        public string TargetPath { get; set; }
        public TfsClientCredentials Credentials { get; set; }
        public bool Silent { get; set; }

        public static TfsDownloadParams Create(IList<string> args)
        {
            if (args.Count < 5)
            {
                Console.WriteLine("Please supply 5 or 6 parameters: tfsServerUrl serverProjectPath targetPath userName password [silent]");
                Console.WriteLine("The optional 6th 'silent' parameter will suppress listing each file downloaded");
                Console.WriteLine(@"Ex: tfsget ""https://myvso.visualstudio.com/DefaultCollection"" ""$/MyProject/ProjectSubfolder"" ""c:\Projects Folder"", user, password ");

                Environment.Exit(1);
            }

            var tfsServerUrl = args[0]; //"https://myvso.visualstudio.com/DefaultCollection";
            var serverProjectPath = args[1]; // "$/MyProject/Folder Path";
            var targetPath = args[2]; // @"c:\Projects\";
            var userName = args[3]; //"login";
            var password = args[4]; //"passsword";
            var silentFlag = args.Count >= 6 && (args[5].ToLower() == "silent"); //"silent";
            var tfsCredentials = GetTfsCredentials(userName, password);

            var tfsParams = new TfsDownloadParams
            {
                ServerUrl = tfsServerUrl,
                ServerProjectPath = serverProjectPath,
                TargetPath = targetPath,
                Credentials = tfsCredentials,
                Silent = silentFlag,
            };
            return tfsParams;
        }

        private static TfsClientCredentials GetTfsCredentials(string userName, string password)
        {
            var networkCreds= new NetworkCredential(userName, password);
            var basicCreds = new BasicAuthCredential(networkCreds);
            var tfsCreds = new TfsClientCredentials(basicCreds)
            {
                AllowInteractive = false
            };
            return tfsCreds;
        }
    }
}
like image 107
Tion Avatar answered May 16 '23 06:05

Tion


Here is a snippet using .NET TFS libraries & Powershell integration that worked for us - tf.exe does not play well with VSO authorization. Curious if anyone else has success using this route. We are using ADFS, so the powershell process is run as the user we want to authenticate with.

TF Get Latest Workspace.ps1

$path = "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\ReferenceAssemblies\v2.0" 
Add-Type -Path "$path\Microsoft.TeamFoundation.Client.dll"
Add-Type -Path "$path\Microsoft.TeamFoundation.VersionControl.Client.dll"
Add-Type -Path "$path\Microsoft.TeamFoundation.VersionControl.Common.dll"
 
$collection = "https://mycorp.visualstudio.com/defaultcollection"
$tpc = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($collection)
$vc = $tpc.GetService([Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer])

# retrieve workspace by path 
$projectPath = "$/MyApp/MyBranch"
$workspacePath = "C:\Projects\MyApp\MyBranch"
$workspace = $vc.GetWorkspace($workspacePath)


# get full download every time (tf get /force /recursive)
function DownloadAllFiles([Microsoft.TeamFoundation.VersionControl.Client.Workspace] $workspace, [string] $projectPath) {     
    $recursionType = [Microsoft.TeamFoundation.VersionControl.Client.RecursionType]::Full
    $versionSpec = [Microsoft.TeamFoundation.VersionControl.Client.LatestVersionSpec]::Instance
    $itemSpec = new-object Microsoft.TeamFoundation.VersionControl.Client.ItemSpec($projectPath,$recursionType)
    $getRequest = New-Object Microsoft.TeamFoundation.VersionControl.Client.GetRequest($projectPath,$recursionType,$versionSpec)
    $getOptions = [Microsoft.TeamFoundation.VersionControl.Client.GetOptions]::GetAll -bor [Microsoft.TeamFoundation.VersionControl.Client.GetOptions]::Overwrite 
    $workpaceStatus = $workspace.Get($getRequest, $getOptions)
    write-output $workpaceStatus 
}

# get delta download - changes only (retrieves all mapped items)
function DownloadWorkspaceUpdates([Microsoft.TeamFoundation.VersionControl.Client.Workspace] $workspace) {
    $workpaceStatus = $workspace.Get()
    write-output $workpaceStatus 
}

# get delta download - changes only (retrieves specific mapped items)
function DownloadWorkspaceUpdates([Microsoft.TeamFoundation.VersionControl.Client.Workspace] $workspace, [string] $projectPath) {
    $recursionType = [Microsoft.TeamFoundation.VersionControl.Client.RecursionType]::Full
    $versionSpec = [Microsoft.TeamFoundation.VersionControl.Client.LatestVersionSpec]::Instance
    $itemSpec = new-object Microsoft.TeamFoundation.VersionControl.Client.ItemSpec($projectPath,$recursionType)
    $getRequest = New-Object Microsoft.TeamFoundation.VersionControl.Client.GetRequest($projectPath,$recursionType,$versionSpec)
    $getOptions = [Microsoft.TeamFoundation.VersionControl.Client.GetOptions]::Overwrite 
    $workpaceStatus = $workspace.Get($getRequest, $getOptions)
    write-output $workpaceStatus 
}

# force latest download (slower)
DownloadAllFiles -workspace $workspace -projectPath $projectPath

# download deltas (fast), all mappings
DownloadWorkspaceUpdates -workspace $workspace

# download deltas (fast), specific mapping
DownloadWorkspaceUpdates -workspace $workspace -projectPath $projectPath

This could easily be extended to support TfsClientCredentials (i.e. Alternate Access credentials). I prefer the powershell approach as it doesn't require compilation or an extra EXE to copy around.

like image 38
SliverNinja - MSFT Avatar answered May 16 '23 04:05

SliverNinja - MSFT