Sunday, November 3, 2013

Build Automation: How to Correctly pull labels from TFS using PowerShell

Previously it was demonstrated how to use TF (TFS's command-line utility) in order to pull code via multiple TFS labels ("Build Automation: Getting multiple labels from TFS using TF GET (without deleting the files associated with the previous TF GET)"). The previously mentioned write up showed pulling labels using the command-line or ultimately a batch script. This article takes build automation to the next obvious level, namely how to pull source code from TFS via a label using PowerShell.

Pulling a single label is simple. Pulling multiple means that per-label directories need to be created and used for each invocation of TF GET.

The steps required to pull a label from TFS as part of an automated build are as follows:
1) Specify the following as input parameters:
  Disk location where label is pulled (source code directory)
  TFS work space and folder from which code is pulled
  Label name associated with TS work space and folder

2) Delete existing code from the source code directory

3) Create the source code directory

4) Change the current-working directory to the source code directory
The reason for this is outlined in: "Build Automation: Getting multiple labels from TFS using TF GET (without deleting the files associated with the previous TF GET)"

5) Delete the TFS work space if it exists
a.k.a. tf workspace /delete

6) Create the TFS work space
a.k.a. tf workspace /new 

7) Map the source code folder to the collection folder and associate this mapping with the work space
a.k.a. tf workfold /map

8) Get the label from TFS
a.k.a. tf get /version

1) Input Parameters and Setup

The input parameters will be as follows corresponding to the location on disk, the collection\folder within TFS and the label associated with the aforementioned collection\folder:

$TFSDiskLocation = 'C:\MarkI\Inventory App';
$Collection = 'MarkI\Inventory App';
$Label = 'InventoryApp1.2.3.4';


GetCode $TFSDiskLocation $Collection $Label;


The three parameters are passed to the GetCode method which is as follows:

function GetCode
{
    [CmdletBinding()]
    param            
    (            
        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]             
        [string] $TFSDiskLocation,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]             
        [string] $Collection,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]             
        [string] $Label
    )
        
    process
    {
        $Workspace = $Collection;
        $Workspace = CleanWorkspaceName $Workspace;
        TFSGetLabel $TFSDiskLocation $Workspace $Collection $Label;
    }
}

The previous code simply creates a TFS work space name based on the collection containing the code. The work space name is then passed to the TFSGetLabel function that actually performs all the steps required to get the code.

The function CleanWorkspaceName converts a collection into a legal work space name since it creates a work space name based on the restrictions TFS. The function CleanWorkspaceName uses a global parameter that insures the work space name does not exceed TFS' work space name length limitation as specified in "Naming Restrictions in Team Foundation":
$gTFSWorkspaceNameLength = 64;

The character restrictions are also presented in "Naming Restrictions in Team Foundation" which (to quote) are as follows:
  • Must not include the following printable characters: "/ \ [ ] : | < > + = ; ? *
  • Must not include nonprintable characters in the ASCII value range of 1-31
  • Must not end in a period (.)
  • Must not include commas (,)
The function CleanWorkspaceName is as follow where the code simply adheres to the previously discussed name length and character restrictions:

function CleanWorkspaceName
{
    [CmdletBinding()]
    param            
    (            
        [parameter(Mandatory=$true)]                                                               
        [ValidateNotNullOrEmpty()]             
        [string] $WorkspaceName
    )
        
    process
    {
        # Append computer name to workspace to aid debugging.
        # Remove trailing and leading spaces with Trim.
        $WorkspaceName = $env:computername + $WorkspaceName.Trim();
        # invalid workspace name characters: /:<>\|*?;
        $WorkspaceName = $WorkspaceName.Replace('/', '');
        $WorkspaceName = $WorkspaceName.Replace(':', '');
        $WorkspaceName = $WorkspaceName.Replace('<', '');
        $WorkspaceName = $WorkspaceName.Replace('>', '');
        $WorkspaceName = $WorkspaceName.Replace('\', '');
        $WorkspaceName = $WorkspaceName.Replace('|', '');
        $WorkspaceName = $WorkspaceName.Replace('*', '');
        $WorkspaceName = $WorkspaceName.Replace('?', '');
        $WorkspaceName = $WorkspaceName.Replace(';', '');
        $WorkspaceName = $WorkspaceName.Replace(',', '');
        $WorkspaceName = $WorkspaceName.Replace('.', '');
        # change spaces to underscore to avoid naming issues
        $WorkspaceName = $WorkspaceName.Replace(' ', '_'); 
        if ($WorkspaceName.Length -gt $gTFSWorkspaceNameLength)
        {
            $WorkspaceName = $WorkspaceName.Substring(0, 
                                     $gTFSWorkspaceNameLength);
        }

        return $WorkspaceName;
    }
}

2) Delete existing code from source code directory

In order to clean up the build location from a previous build, the source code location is deleted. This step is handled by the DeleteDirectory function which behaves as follows:

1) List all files in directory
1.1) If the file is read-only, clear the read-only flag
1.2) Delete the file
2) List all sub-directories
2.1) Invoke the DeleteDirectory on the sub-directory
2.2) Delete the sub-directory

function DeleteDirectory
{
    [CmdletBinding()]
    param            
    (   
        [parameter(Mandatory=$true)]                                                               
        [ValidateNotNullOrEmpty()]             
        [String] $directory
    )
   
    process            
    {   
        if (![System.IO.Directory]::Exists($directory))
        {
            return ;
        }

        $files = @();
        $files += [System.IO.Directory]::GetFiles($directory);
        foreach ($file in $files)
        {
            $attributes = [System.IO.File]::GetAttributes($file);
            if ($attributes -band 
                  [System.IO.FileAttributes]::ReadOnly)
            {
                $attributes = [int]$attributes - 
                   [int][System.IO.FileAttributes]::ReadOnly;
                [System.IO.File]::SetAttributes($file, 
                                     $attributes);
            }
            
            # Write-Host "[System.IO.File]::Delete($file)";
            [System.IO.File]::Delete($file);        
        }
        
        $directories = @();
        $directories += [System.IO.Directory]::GetDirectories(
                                                    $directory);
        foreach ($subDirectory in $directories)
        {
            DeleteDirectory $subDirectory;
        }
        
        # Write-Host "[System.IO.Directory]::Delete($directory)";
        [System.IO.Directory]::Delete($directory);
    }
}

The previous code is self-explanatory (basic file I/O).

The DeleteDirectory function is invoked from TFSGetLabel as follows which is the first action taken as part of the source code retrieval process:

function TFSGetLabel
{
    [CmdletBinding()]
    param            
    (   
        [parameter(Mandatory=$true)]                                                               
        [ValidateNotNullOrEmpty()]             
        [String] $SourceFolder,

        [parameter(Mandatory=$true)]                                                               
        [ValidateNotNullOrEmpty()]             
        [String] $WorkSpace,

        [parameter(Mandatory=$true)]                                                               
        [ValidateNotNullOrEmpty()]             
        [String] $Collection,

        [parameter(Mandatory=$true)]                                                               
        [ValidateNotNullOrEmpty()]             
        [String] $Label
)

    process            
    {   
        try            
        {
            Write-Host 'TFSGetLabel:SourceFolder $SourceFolder';
            Write-Host 'TFSGetLabel:Label $Label';            
            DeleteDirectory $SourceFolder;
            $DirectoryInfo = [System.IO.Directory]::CreateDirectory(
                                 $SourceFolder);
            [System.IO.Directory]::SetCurrentDirectory(
                                 $SourceFolder);
            $cwd = [System.IO.Directory]::GetCurrentDirectory();

            Write-Host "CWD: $cwd";            

The invocation of DeleteDirectory is accented in boldface above.

3) Create the source code directory

The creation of the source code directory in TFSGetLabel as follows where the action is indicated by the line in boldface:

Write-Host 'TFSGetLabel:SourceFolder $SourceFolder';
Write-Host 'TFSGetLabel:Label $Label';            
DeleteDirectory $SourceFolder;
$DirectoryInfo = [System.IO.Directory]::CreateDirectory(
$SourceFolder);
[System.IO.Directory]::SetCurrentDirectory(
$SourceFolder);
$cwd = [System.IO.Directory]::GetCurrentDirectory();
Write-Host "CWD: $cwd";        
    

4) Change the current-working directory to the source code directory

Changing the current working directory to be the same as the source code directory in TFSGetLabel as follows where the action is indicated by the line in boldface:

Write-Host 'TFSGetLabel:SourceFolder $SourceFolder';
Write-Host 'TFSGetLabel:Label $Label';            
DeleteDirectory $SourceFolder;
$DirectoryInfo = [System.IO.Directory]::CreateDirectory(
 $SourceFolder);
[System.IO.Directory]::SetCurrentDirectory(
 $SourceFolder);
$cwd = [System.IO.Directory]::GetCurrentDirectory();
Write-Host "CWD: $cwd";            

5) Delete the TFS work space if it exists

The code to delete the existing TFS work space relies on the function DoesTFSWorkspaceExist to determine if the work space already exists. The work space is only deleted if it already exists.

The DoesTFSWorkspaceExist function using the following global parameter corresponding to TFS's TF function:

$gTFUtility = 'tf';
    
The DoesTFSWorkspaceExist function:
1) Invokes the tf workspaces using .NET's System.Diagnostics.Process class as invoked via PowerShell.
1.1) The tf workspaces command lists all existing work spaces.
2) Read the output from the tf workspaces command to determine if the work space already exists.
2.1) The output is accessed via the System.Diagnostics.Process class' StandardOutput property.

The DoesTFSWorkspaceExist function is implemented as follows:

function DoesTFSWorkspaceExist
{
    [CmdletBinding()]
    param            
    (   
        [parameter(Mandatory=$true)]                                                               
        [ValidateNotNullOrEmpty()]             
        [String] $Workspace
    )

    process            
    {
        Write-Host 
          "DoesTFSWorkspaceExist -- workspace: $Workspace";
        $ProcessStartInfo = New-Object 
             System.Diagnostics.ProcessStartInfo;
        $ProcessStartInfo.FileName = $gtfUtility;
        $ProcessStartInfo.RedirectStandardError = $True;
        $ProcessStartInfo.RedirectStandardOutput = $True;
        $ProcessStartInfo.UseShellExecute = $False;
        # we are invoking: tf workspaces
        $ProcessStartInfo.Arguments = "workspaces";
        $process = New-Object System.Diagnostics.Process;
        $process.StartInfo = $ProcessStartInfo;
        # ignore return value with Out-Null
        $process.Start() | Out-Null; 
        $process.WaitForExit();
        $ExitCode = $process.ExitCode;
        if ($ExitCode -ne 0)
        {
            $ErrorText = $process.StandardError.ReadToEnd();
            Write-Host "Error: $ErrorText.";

            throw $ErrorText;
        }

        $stdout = $process.StandardOutput;
        while ($True)
        {
          $line = $stdout.ReadLine();
          # check for null
          if (!$line)
          {
              break;
          }

          if ($line.ToUpper().StartsWith($Workspace.ToUpper()))
          {
                Write-Host "Workspace exists: $Workspace";
                return $True;
          }
        }

        return $False;
    }
}

The DoesTFSWorkspaceExist function is invoked by TFSGetLabel as follows where the action is indicated by the line in boldface:

Write-Host 'TFSGetLabel:SourceFolder $SourceFolder';
Write-Host 'TFSGetLabel:Label $Label';            
DeleteDirectory $SourceFolder;
$DirectoryInfo = [System.IO.Directory]::CreateDirectory(
                                             $SourceFolder);
[System.IO.Directory]::SetCurrentDirectory($SourceFolder);
$cwd = [System.IO.Directory]::GetCurrentDirectory();
Write-Host "CWD: $cwd";            
# protect against space in name
$SourceFolder = '"' + $SourceFolder + '"'; 
# protect against space in name
$Collection = '"' + $Collection + '"'; 
$Label = '"' + $Label + '"';
if (DoesTFSWorkspaceExist($WorkSpace))
{
    # e.g. tf workspace /delete WorkSpaceATest /noprompt
    $commandLine = 'workspace /delete ' + $WorkSpace + 
                                               ' /noprompt';
    InvokeTF $commandLine $SourceFolder;
}

The code following DoesTFSWorkspaceExist deletes the work space (if it exists) using tf workspace /delete. which is invoked by the InvokeTF function.


function InvokeTF
{
    [CmdletBinding()]
    param            
    (   
        [parameter(Mandatory=$true)]                              
        [ValidateNotNullOrEmpty()]             
        [String] $CommandLine,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]             
        [String] $WorkingDirectory
    )

    process            
    {
        Write-Host "Working Directory: $WorkingDirectory";
        Write-Host "Invoking: $gtfUtility $commandLine";
        $ProcessStartInfo = New-Object 
                         System.Diagnostics.ProcessStartInfo;
        $ProcessStartInfo.FileName = $gtfUtility;
        $ProcessStartInfo.RedirectStandardError = $True;
        $ProcessStartInfo.RedirectStandardOutput = $False;
        $ProcessStartInfo.UseShellExecute = $False;
        $ProcessStartInfo.Arguments = $CommandLine;
        $process = New-Object System.Diagnostics.Process;
        $process.StartInfo = $ProcessStartInfo;
        # ignore return value with Out-Null
        $process.Start() | Out-Null; 
        $process.WaitForExit();
        $ExitCode = $process.ExitCode;
        if ($ExitCode -ne 0)
        {
            $ErrorText = $process.StandardError.ReadToEnd();
            Write-Host "Error: $ErrorText.";

            throw $ErrorText;
        }
    }
}

The InvokeTF function calls the TF utility using the command-line supplied by the aptly named $CommandLine parameter. The TF utility is run in the same working directory as the source code folder. The source code folder is passed in as the parameter $WorkingDirectory.

The code that called InvokeTF passed as a parameter with the command-line required to delete a work space (workspace /delete <workspacename> /noprompt) which is demonstrated below:

if (DoesTFSWorkspaceExist($WorkSpace))
{
    # e.g. tf workspace /delete WorkSpaceATest /noprompt
    $commandLine = 'workspace /delete ' + $WorkSpace + 
                                               ' /noprompt';
    InvokeTF $commandLine $SourceFolder;

}

6) Create the TFS work space 

The TFSGetLabel function contains the code to handle the work space delete, work space creation, folder to work space mapping and getting the label. The work space creation is highlighted below in boldface:

# e.g. tf workspace /new WorkSpaceATest /noprompt
$commandLine = 'workspace /new ' + $WorkSpace + ' /noprompt';
InvokeTF $commandLine $SourceFolder;
# e.g. tf workfold /map $/ATest D:\ATest /WorkSpace:WorkSpaceATest
$commandLine = 'workfold /map $/' + $Collection + ' ' + $SourceFolder + ' /WorkSpace:' + $WorkSpace;
InvokeTF $commandLine $SourceFolder;
# e.g. tf get /version:LBTest01
# note the L in /Version:L means a label is specified
$commandLine = 'get /Version:L' + $Label + ' /noprompt';
InvokeTF $commandLine $SourceFolder;

The previously highlight code invokes  tf workspace /new <workspacename>.


7) Map the source code folder to the collection folder and associate this mapping with the work space

The TFSGetLabel function handling the folder to work space mapping is highlighted below in boldface:

# e.g. tf workspace /new WorkSpaceATest /noprompt
$commandLine = 'workspace /new ' + $WorkSpace + ' /noprompt';
InvokeTF $commandLine $SourceFolder;
# e.g. tf workfold /map $/ATest D:\ATest /WorkSpace:WorkSpaceATest
$commandLine = 'workfold /map $/' + $Collection + ' ' + $SourceFolder + ' /WorkSpace:' + $WorkSpace;
InvokeTF $commandLine $SourceFolder;
# e.g. tf get /version:LBTest01
# note the L in /Version:L means a label is specified
$commandLine = 'get /Version:L' + $Label + ' /noprompt';
InvokeTF $commandLine $SourceFolder;

The previously highlight code invokes:
tf workfold /map <collection> <folder> /Workspace:<workspacename>


8) Get label from TFS

The TFSGetLabel function handling retrieving the label is highlighted below in boldface:

# e.g. tf workspace /new WorkSpaceATest /noprompt
$commandLine = 'workspace /new ' + $WorkSpace + ' /noprompt';
InvokeTF $commandLine $SourceFolder;
# e.g. tf workfold /map $/ATest D:\ATest /WorkSpace:WorkSpaceATest
$commandLine = 'workfold /map $/' + $Collection + ' ' + $SourceFolder + ' /WorkSpace:' + $WorkSpace;
InvokeTF $commandLine $SourceFolder;
# e.g. tf get /version:LBTest01
# note the L in /Version:L means a label is specified
$commandLine = 'get /Version:L' + $Label + ' /noprompt';
InvokeTF $commandLine $SourceFolder;

The previously highlight code invokes:
tf get /map /Version:L<label name> /noprompt

The capital L after the /Version: option is crucial so that the label is interpreted as a label name and not a version number. 

Entire Script

The script in its entirety is as follows:

$gTFUtility = 'tf';
$gTFSWorkspaceNameLength = 64;
    
function DeleteDirectory
{
    [CmdletBinding()]
    param            
    (   
        [parameter(Mandatory=$true)]                                                               
        [ValidateNotNullOrEmpty()]             
        [String] $directory
  )
   
    process            
    {   
        if (![System.IO.Directory]::Exists($directory))
        {
            return ;
        }

        $files = @();
        $files += [System.IO.Directory]::GetFiles($directory);
        foreach ($file in $files)
        {
            $attributes = [System.IO.File]::GetAttributes($file);
            if ($attributes -band 
                    [System.IO.FileAttributes]::ReadOnly)
            {
                $attributes = [int]$attributes - 
                   [int][System.IO.FileAttributes]::ReadOnly;
                [System.IO.File]::SetAttributes($file, 
                                                $attributes);
            }
            
            # Write-Host "[System.IO.File]::Delete($file)";
            [System.IO.File]::Delete($file);        
        }
        
        $directories = @();
        $directories += 
           [System.IO.Directory]::GetDirectories($directory);
        foreach ($subDirectory in $directories)
        {
            DeleteDirectory $subDirectory;
        }
        
        # Write-Host "[System.IO.Directory]::Delete($directory)";
        [System.IO.Directory]::Delete($directory);
    }
}

function DoesTFSWorkspaceExist
{
    [CmdletBinding()]
    param            
    (   
        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]             
        [String] $Workspace
    )

    process            
    {
        Write-Host 
            "DoesTFSWorkspaceExist -- workspace: $Workspace";
        $ProcessStartInfo = New-Object 
                    System.Diagnostics.ProcessStartInfo;
        $ProcessStartInfo.FileName = $gtfUtility;
        $ProcessStartInfo.RedirectStandardError = $True;
        $ProcessStartInfo.RedirectStandardOutput = $True;
        $ProcessStartInfo.UseShellExecute = $False;
        # we are invoking: tf workspaces
        $ProcessStartInfo.Arguments = "workspaces";
        $process = New-Object System.Diagnostics.Process;
        $process.StartInfo = $ProcessStartInfo;
        # ignore return value with Out-Null
        $process.Start() | Out-Null; 
        $process.WaitForExit();
        $ExitCode = $process.ExitCode;
        if ($ExitCode -ne 0)
        {
            $ErrorText = $process.StandardError.ReadToEnd();
            Write-Host "Error: $ErrorText.";

            throw $ErrorText;
        }

        $stdout = $process.StandardOutput;
        while ($True)
        {
          $line = $stdout.ReadLine();
          # check for null
          if (!$line)
          {
              break;
          }

          if ($line.ToUpper().StartsWith($Workspace.ToUpper()))
          {
                Write-Host "Workspace exists: $Workspace";
                return $True;
          }
        }

        return $False;
    }
}

function InvokeTF
{
    [CmdletBinding()]
    param            
    (   
        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]             
        [String] $CommandLine,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]             
        [String] $WorkingDirectory
    )

    process            
    {
        Write-Host "Working Directory: $WorkingDirectory";
        Write-Host "Invoking: $gtfUtility $commandLine";
        $ProcessStartInfo = New-Object 
                   System.Diagnostics.ProcessStartInfo;
        $ProcessStartInfo.FileName = $gtfUtility;
        $ProcessStartInfo.RedirectStandardError = $True;
        $ProcessStartInfo.RedirectStandardOutput = $False;
        $ProcessStartInfo.UseShellExecute = $False;
        $ProcessStartInfo.Arguments = $CommandLine;
        $process = New-Object System.Diagnostics.Process;
        $process.StartInfo = $ProcessStartInfo;
        # ignore return value with Out-Null
        $process.Start() | Out-Null; 
        $process.WaitForExit();
        $ExitCode = $process.ExitCode;
        if ($ExitCode -ne 0)
        {
            $ErrorText = $process.StandardError.ReadToEnd();
            Write-Host "Error: $ErrorText.";

            throw $ErrorText;
        }
    }
}

function TFSGetLabel
{
    [CmdletBinding()]
    param            
    (   
        [parameter(Mandatory=$true)]                                                               
        [ValidateNotNullOrEmpty()]             
        [String] $SourceFolder,

        [parameter(Mandatory=$true)]                                                               
        [ValidateNotNullOrEmpty()]             
        [String] $WorkSpace,

        [parameter(Mandatory=$true)]                                                               
        [ValidateNotNullOrEmpty()]             
        [String] $Collection,

        [parameter(Mandatory=$true)]                                                               
        [ValidateNotNullOrEmpty()]             
        [String] $Label
)
    process            
    {   
        try            
        {
            Write-Host 'TFSGetLabel:SourceFolder $SourceFolder';
            Write-Host 'TFSGetLabel:Label $Label';            
            DeleteDirectory $SourceFolder;
            $DirectoryInfo = 
                 [System.IO.Directory]::CreateDirectory(
                                      $SourceFolder);
            [System.IO.Directory]::SetCurrentDirectory(
                       $SourceFolder);
            $cwd = [System.IO.Directory]::GetCurrentDirectory();
            Write-Host "CWD: $cwd";            
            # protect against space in name
            $SourceFolder = '"' + $SourceFolder + '"'; 
            # protect against space in name
            $Collection = '"' + $Collection + '"'; 
            $Label = '"' + $Label + '"';
            if (DoesTFSWorkspaceExist($WorkSpace))
            {
                # e.g. tf workspace /delete WorkSpaceATest /noprompt
                $commandLine = 'workspace /delete ' + 
                     $WorkSpace + ' /noprompt';
                InvokeTF $commandLine $SourceFolder;
            }

            # e.g. tf workspace /new WorkSpaceATest /noprompt
            $commandLine = 'workspace /new ' + $WorkSpace + 
                                 ' /noprompt';
            InvokeTF $commandLine $SourceFolder;
            # e.g. tf workfold /map $/ATest D:\ATest 
            # /WorkSpace:WorkSpaceATest
            $commandLine = 'workfold /map $/' + $Collection + 
               ' ' + $SourceFolder + ' /WorkSpace:' + $WorkSpace;
            InvokeTF $commandLine $SourceFolder;
            # e.g. tf get /version:LBTest01
            # note the L in /Version:L means a label is specified
            $commandLine = 'get /Version:L' + $Label + 
                            ' /noprompt';
            InvokeTF $commandLine $SourceFolder;
        }            
        catch
        {
            Write-Error "Build Error $SolutionFilePath, $_";
            Exit 1;     
        }
    }
}

function CleanWorkspaceName
{
    [CmdletBinding()]
    param            
    (            
        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]             
        [string] $WorkspaceName
    )
        
    process
    {
        # Add computer name to work space to aid in debugging.
        # Remove trailing and leading spaces with Trim
        $WorkspaceName = $env:computername + 
                                 $WorkspaceName.Trim(); 
        # invalid workspace name characters: /:<>\|*?;
        $WorkspaceName = $WorkspaceName.Replace('/', '');
        $WorkspaceName = $WorkspaceName.Replace(':', '');
        $WorkspaceName = $WorkspaceName.Replace('<', '');
        $WorkspaceName = $WorkspaceName.Replace('>', '');
        $WorkspaceName = $WorkspaceName.Replace('\', '');
        $WorkspaceName = $WorkspaceName.Replace('|', '');
        $WorkspaceName = $WorkspaceName.Replace('*', '');
        $WorkspaceName = $WorkspaceName.Replace('?', '');
        $WorkspaceName = $WorkspaceName.Replace(';', '');
        $WorkspaceName = $WorkspaceName.Replace('.', '');
        $WorkspaceName = $WorkspaceName.Replace(',', '');
        # change spaces to underscore
        $WorkspaceName = $WorkspaceName.Replace(' ', '_'); 
        if ($WorkspaceName.Length -gt $gTFSWorkspaceNameLength)
        {
            $WorkspaceName = $WorkspaceName.Substring(0, 
                               $gTFSWorkspaceNameLength);
        }

        return $WorkspaceName;
}
}

function GetCode
{
    [CmdletBinding()]
    param            
    (            
        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]             
        [string] $TFSDiskLocation,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]             
        [string] $Collection,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]             
        [string] $Label
    )
        
    process
    {
        $Workspace = $Collection;
        $Workspace = CleanWorkspaceName $Workspace;
        TFSGetLabel $TFSDiskLocation $Workspace 
                           $Collection $Label;
}
}

$TFSDiskLocation = 'C:\MarkI\Inventory App';
$Collection = 'MarkI\Inventory App';
$Label = 'InventoryApp1.2.3.4';

GetCode $TFSDiskLocation $Collection $Label;



3 comments :

  1. Great code examples. Just what I was looking for. But eg the Delete directory why are you not using more powershell cmd. I think you could do something like: get-childitem $directory -include * -recurse | Remove-Item -recurse –force that would be less code imho you are using tf.exe could it work the PSSnapin Microsoft.TeamFoundation.PowerShell ?

    ReplyDelete
    Replies
    1. Thanks for the feedback.

      In a few of my posts I mention that my team and I are C# developers which is why I favor file I/O .NET System.IO. On projects where my team maintains with me (build task) I code more like C#. For other tasks, I code pure PowerShell.

      The same bias is why I use TF over PSSnapin. Every developer on the team has TF installed with Visual Studio.

      Delete