Scripts/TFS.ps1
function GetTfPath([string]$version) { <# .SYNOPSIS Gets the path to the [tf.exe] tool which corresponds to the given VisualStudio version. The path depends on the installation location of VisualStudio for example "C:\Program Files (x86)\ Microsoft Visual Studio\2017\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\ Team Explorer\TF.exe". #> if(IsNullOrEmpty($version)) { $version = $global:VsVersion } $vsPath = GetVsPath $version $path = "$vsPath\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\TF.exe" return $path } function CanConnectToTfs([string]$tfsUrl, [string]$version) { <# .SYNOPSIS Checks whether a connection to the given TFS server is possible. .PARAMETER tfsUrl The URL of the TFS server. [Optional] .PARAMETER version The VisualStudio version from which the [tf.exe] path is derived. [Optional] #> if(IsNullOrEmpty($tfsUrl)) { $tfsUrl = $global:TfsUrl } if(IsNullOrEmpty($version)) { $version = $global:VsVersion } $tf = GetTfPath -version "$version" $result = ExecuteProcess -fileName "$tf" -arguments "workspaces /collection:$tfsUrl /noprompt" -maxTime 30000 $success = ($result.ExitCode -eq 0) return $success # Alternative: # & "$TfPath" workspaces /collection:$tfsUrl /noprompt *>$null } function GetChangedProjects() { <# .SYNOPSIS Gets all cs projects impacted by the local pending changes. #> $result = GetPendingChanges $files = $result.Files $projects = New-Object System.Collections.Generic.HashSet[string] foreach($file in $files) { while(!(IsNullOrEmpty $file) -And (ExistsPath $file)) { $file = Split-Path -parent $file $projectFile = Get-ChildItem -Path $file -Filter *.csproj $isProjectDir = !(IsNullOrEmpty $projectFile) if($isProjectDir) { $null = $projects.Add($projectFile.FullName) break } } } Add-Member -InputObject $result -NotePropertyName Projects -NotePropertyValue $projects return $result } function ConvertServerPathToLocalPath([string]$serverPath, [string]$serverWorkspace, [string]$localWorkspace) { <# .SYNOPSIS Converts a server path to a local path. For example the server path "$/TIA Portal/TPE/dev/Step7_Safety_T/src/S7F/FEngine" for server workspace "$/TIA Portal/TPE/dev/Step7_Safety_T" and local workspace "D:\WS\Step7_Safety_T" is converted to "D:\WS\Step7_Safety_T\src\S7F\FEngine". #> if(-not $serverWorkspace) { $serverWorkspace = $global:ServerWorkspace } if(-not $localWorkspace) { $localWorkspace = $global:LocalWorkspace } $result = $null if($serverPath.ToLower().StartsWith($serverWorkspace.ToLower())) { $localPath = $serverPath.SubString($serverWorkspace.Length) $localPath = $localPath.Replace("/","\") $result = $localWorkspace + $localPath } return $result } function ConvertLocalPathToServerPath([string]$localPath, [string]$serverWorkspace, [string]$localWorkspace) { if(-not $serverWorkspace) { $serverWorkspace = $global:ServerWorkspace } if(-not $localWorkspace) { $localWorkspace = $global:LocalWorkspace } $result = $null if($localPath.ToLower().StartsWith($localWorkspace.ToLower())) { $serverPath = $localPath.SubString($localWorkspace.Length) $serverPath = $serverPath.Replace("\","/") $result = $serverWorkspace + $serverPath } return $result } function ConvertGetOutputToChangeList([string]$output) { $lines = $output -split "`r`n" #Write-Output "Line Count:" $lines.Count #$lines = $lines | Where-Object { $_.Length -gt 0 } #Write-Output "Filled Line Count:" $lines.Count $localFolder = $null #$localFolderCount = 0 $changeList = New-Object Collections.Generic.List[PsCustomObject] foreach($line in $lines) { if($line.Length -gt 0) { $foundChange = $false $changeType = $null $localPath = $null if($line.ToLower().StartsWith("getting ")) { $foundChange = $true $changeType = "add" $localPath = $localFolder + "\" + $line.SubString(8) # $item = $line.SubString(8) # $changeList.Add(@{Path = $localFolder + "\" + $item; Adjective = "getting"}) } elseif($line.ToLower().StartsWith("replacing ")) { $foundChange = $true $changeType = "edit" $localPath = $localFolder + "\" + $line.SubString(10) # $item = $line.SubString(10) # $changeList.Add(@{Path = $localFolder + "\" + $item; Adjective = "replacing"}) } elseif($line.ToLower().StartsWith("deleting ")) { $foundChange = $true $changeType = "delete" $localPath = $localFolder + "\" + $line.SubString(9) # $item = $line.SubString(9) # $changeList.Add(@{Path = $localFolder + "\" + $item; Adjective = "deleting"}) } elseif($line.EndsWith(":")) { $localFolder = $line.SubString(0, $line.Length - 1) #$localFolderCount++ } if($foundChange) { $changeList.Add([PsCustomObject]@{LocalPath = $localPath; ChangeType = $changeType}) } } } # Write-Output "ParentCount:" $parentCount # $gettingCount = ($changeList | Where-Object { $_.Adjective -eq "getting" }).Count # $replacingCount = ($changeList | Where-Object { $_.Adjective -eq "replacing" }).Count # $deletingCount = ($changeList | Where-Object { $_.Adjective -eq "deleting" }).Count # Write-Output "GettingCount:" $gettingCount # Write-Output "ReplacingCount:" $replacingCount # Write-Output "DeletingCount:" $deletingCount # $totalCount = $gettingCount + $replacingCount + $deletingCount + $parentCount # Write-Output "TotalCount:" $totalCount return $changeList } function ConvertPendingChangesOutputToChangeList([string]$output) { $lines = $output -split "`r`n" # Variable changes contains the changed items. An items is either a files or a folder. $changes = New-Object Collections.Generic.List[PsCustomObject] # Holds the index where in a line the change type (add, edit, delete) begins. $changeIndex = 0 # Holds the last server folder. In case of a deleted item, the last server folder can be # used to find the local folder from which the item has been deleted. $serverFolder = "" # Holds the count of added items. $addCount = 0 # Holds the count of edited items. $editCount = 0 # Holds the count of deleted items. $deleteCount = 0 foreach($line in $lines) { if($changeIndex -eq 0) { if($line.Length -gt 0) { $index = $line.ToLower().IndexOf("change") if($index -gt 0) { $changeIndex = $index } } } elseif($line.Length -ge $changeIndex) { if($line.StartsWith("$")) { $serverFolder = $line } else { $itemName = $line.SubString(0, $changeIndex).Trim() $line = $line.SubString($changeIndex) $parts = $line -split "\s+" if($parts -ge 1) { $changeType = $parts[0] $foundChange = $false if($changeType -eq "edit") { $foundChange = $true $localPath = $parts[1] $editCount++ } elseif($changeType -eq "add") { $foundChange = $true $localPath = $parts[1] $addCount++ } elseif($changeType -eq "delete") { $foundChange = $true if($localPath.Length -eq 0) { $localFolder = ConvertServerPathToLocalPath -ServerPath $serverFolder $localPath = $localFolder + "\" + $itemName } $deleteCount++ } if($foundChange) { $changes.Add([PsCustomObject]@{LocalPath = $localPath; ChangeType = $changeType}) } } } } } return $changes } function GetPendingChanges() { <# .SYNOPSIS Gets all pending changes. #> $tf = GetTfPath $result = ExecuteProcess -fileName "$tf" -arguments "vc status /collection:$TfsUrl" -maxTime 30000 # Add-Member -InputObject $result -NotePropertyName AddCount -NotePropertyValue $addCount # Add-Member -InputObject $result -NotePropertyName EditCount -NotePropertyValue $editCount # Add-Member -InputObject $result -NotePropertyName DeleteCount -NotePropertyValue $deleteCount # Add-Member -InputObject $result -NotePropertyName Changes -NotePropertyValue $changes return $result } function PreviewGetLatest([string]$rootDir) { <# .SYNOPSIS Checks the given directory for server-side changes. #> $tf = GetTfPath $result = ExecuteProcess -fileName "$tf" -arguments "get `"$rootDir`" /recursive /overwrite /noprompt /preview" return $result } function GetLatest([string]$rootDir) { <# .SYNOPSIS Gets all server-side changes from the given directory. #> $tf = GetTfPath $result = ExecuteProcess -fileName "$tf" -arguments "get `"$rootDir`" /recursive /overwrite /noprompt" return $result } function PreviewGetLatestByVersion([string]$rootDir, [string]$itemVersion) { <# .SYNOPSIS Previews a [tf get] of all server-side changes in the given directory regarding the given item version. The item version can be a changeset, a date or a label. If the item version is not specified, the latest item version is used. #> if(IsNullOrEmpty($itemVersion)) { $itemVersion = "T" } $tf = GetTfPath $command = "get `"$rootDir`" /version:$itemVersion /recursive /overwrite /noprompt /preview" #Write-Host "command: $command" $result = ExecuteProcess -fileName "$tf" -arguments "$command" return $result } function GetLatestByVersion([string]$rootDir, [string]$itemVersion) { <# .SYNOPSIS Performs a [tf get] of all server-side changes in the given directory regarding the given item version. The item version can be a changeset, a date or a label. If the item version is not specified, the latest item version is used. #> if(IsNullOrEmpty($itemVersion)) { $itemVersion = "T" } $tf = GetTfPath $command = "get `"$rootDir`" /version:$itemVersion /recursive /overwrite /noprompt" #Write-Host "command: $command" $result = ExecuteProcess -fileName "$tf" -arguments "$command" return $result } function UndoPendingChanges([string]$localWorkspace, [string]$workspaceName) { <# .SYNOPSIS Undoes the pending changes in the given local workspace and the given workspace name. #> if(IsNullOrEmpty($workspaceName)) { $workspaceName = $global:WorkspaceName } if(IsNullOrEmpty($localWorkspace)) { $localWorkspace = $global:LocalWorkspace } SetCurrentDirectory($localWorkspace) $tf = GetTfPath $result = ExecuteProcess -fileName "$tf" -arguments "undo * /recursive /noprompt /workspace:`"$workspaceName`"" return $result } function ScorchWorkspace([string]$localWorkspace) { <# .SYNOPSIS Scorches the local workspace. #> if(IsNullOrEmpty($localWorkspace)) { $localWorkspace = $global:LocalWorkspace } SetCurrentDirectory($localWorkspace) $tf = GetTfPath $result = ExecuteProcess -fileName "$tf" -arguments "vc scorch /exclude:binaries /noprompt" return $result } function PreviewScorchWorkspace([string]$localWorkspace) { <# .SYNOPSIS Previews a scorch of the local workspace. #> if(IsNullOrEmpty($localWorkspace)) { $localWorkspace = $global:LocalWorkspace } SetCurrentDirectory($localWorkspace) $tf = GetTfPath $result = ExecuteProcess -fileName "$tf" -arguments "vc scorch /exclude:binaries /noprompt /preview" return $result } function CleanWorkspace([string]$localWorkspace) { <# .SYNOPSIS Cleans the local workspace via reconcile command. #> if(IsNullOrEmpty($localWorkspace)) { $localWorkspace = $global:LocalWorkspace } SetCurrentDirectory($localWorkspace) $tf = GetTfPath $result = ExecuteProcess -fileName "$tf" -arguments "reconcile /clean /recursive /ignore /noprompt" return $result } function ExecuteProcess ([string]$fileName, [string]$arguments, [int]$maxTime) { <# .SYNOPSIS Executes the given process. The process is forced to exit if the maximum time has elapsed. .PARAMETER fileName Path or name of the process to execute. .PARAMETER arguments Arguments passed to the process. .PARAMETER maxTime Maximum time in milliseconds to wait for the process to finish. The parameter must be greater than 0, otherwise the process has no time limit. #> $info = New-Object System.Diagnostics.ProcessStartInfo $info.FileName = "$fileName" $info.Arguments = "$arguments" $info.RedirectStandardError = $true $info.RedirectStandardOutput = $true $info.UseShellExecute = $false $p = New-Object System.Diagnostics.Process $p.StartInfo = $info [void]$p.Start() $standardOutput = $p.StandardOutput.ReadToEnd() $standardError = $p.StandardError.ReadToEnd() $exitedInTime = $true if($maxTime -gt 0) { $exitedInTime = $p.WaitForExit($maxTime) } else { $p.WaitForExit() } $exitCode = $p.ExitCode return [PsCustomObject]@{ StandardOutput = $standardOutput StandardError = $standardError ExitCode = $exitCode ExitedInTime = $exitedInTime } } function GetExitCodeString([int]$exitCode) { <# Gets the description of an exit code produced by one of the TFVC methods. #> switch($exitCode) { 0 { "Command succeeded" } 1 { "Command succeeded partially" } 2 { "Command is unrecognized" } 100 { "Command failed" } default { "Exitcdoe is undefined" } } } |