TFVC.psm1


using namespace Microsoft.TeamFoundation.Client
using namespace Microsoft.TeamFoundation.VersionControl.Client
using namespace Microsoft.TeamFoundation.VersionControl.Common
using namespace system

$Script:PSModuleRoot = $PSScriptRoot
# ./home/vsts/work/1/s/TFVC/Classes/TFVCSession.ps1

class TFVCSession
{
    [uri]
    $DisplayName

    [uri]
    $Server

    hidden
    [PSCredential]
    $Credential

    hidden
    [TfsTeamProjectCollection]
    $TfsTeamProjectCollection

    hidden
    [VersionControlServer]
    $VersionControlServer


    TFVCSession( [uri]$ProjectCollectionURI )
    {
        $this.Connect( $ProjectCollectionURI )
    }

    TFVCSession( [uri]$ProjectCollectionURI, [PSCredential]$Credential )
    {
        $this.Connect( $ProjectCollectionURI, $Credential )
    }

    TFVCSession( [uri]$Server, [string]$ProjectCollectionName )
    {
        $URI = [uribuilder]::new( $Server)
        $URI.Path = $this.AppendProjectCollectionToPath($URI.Path, $ProjectCollectionName)
        $this.Connect( $URI.uri )
    }

    TFVCSession( [uri]$Server, [string]$ProjectCollectionName, [PSCredential]$Credential )
    {
        $URI = [uribuilder]::new( $Server)
        $URI.Path = $this.AppendProjectCollectionToPath($URI.Path, $ProjectCollectionName)
        $this.Connect( $URI.uri, $Credential )
    }

    [string] AppendProjectCollectionToPath([string]$Path, [string]$ProjectCollectionName)
    {
        if (-not $Path.EndsWith('/'))
        {
            $Path = "$Path/"
        }
        return $Path + $ProjectCollectionName
    }

    [Void] Connect( [uri]$ProjectCollectionURI )
    {
        $this.TfsTeamProjectCollection = [TfsTeamProjectCollection]::new( $ProjectCollectionURI )

        $this.ValidateConnection($ProjectCollectionURI)
        $this.RefreshProperties()
    }

    [Void] Connect( [uri]$ProjectCollectionURI, [PSCredential]$Credential )
    {
        $this.TfsTeamProjectCollection = [TfsTeamProjectCollection]::new( $ProjectCollectionURI, $Credential )
        $this.TfsTeamProjectCollection.Credentials = $Credential

        $this.ValidateConnection($ProjectCollectionURI)
        $this.RefreshProperties()
    }

    [Void] ValidateConnection([uri] $uri)
    {
        if ( $null -eq $this.TfsTeamProjectCollection.ConfigurationServer )
        {
            throw [TFVCSessionException]::New("Was not able to establish a connection to the specified endpoint [$uri]")
        }
    }

    [Void] RefreshProperties()
    {
        if ( $null -ne $this.TfsTeamProjectCollection )
        {
            $this.DisplayName = $this.TfsTeamProjectCollection.DisplayName
            $this.Server = $this.TfsTeamProjectCollection.ConfigurationServer.Uri
            $this.VersionControlServer = $this.GetVersionControlServer()
        }
    }

    [Void] Disconnect()
    {
        if ($null -ne $this.TfsTeamProjectCollection)
        {
            $this.TfsTeamProjectCollection.Disconnect()
            $this.TfsTeamProjectCollection.Dispose()
            $this.TfsTeamProjectCollection = $null
        }
    }

    hidden
    [VersionControlServer] GetVersionControlServer ()
    {
        return $this.TfsTeamProjectCollection.GetService( [VersionControlServer] )
    }

    [Workspace] GetWorkspaceFromPath( $LocalPath )
    {
        return $this.VersionControlServer.GetWorkspace( $LocalPath )
    }

    [Workspace] GetWorkspace( $WorkspaceName, $WorkspaceOwner )
    {
        return $this.VersionControlServer.GetWorkspace( $WorkspaceName, $WorkspaceOwner )
    }

    [Workspace] CreateWorkspace( $Name )
    {
        return $this.VersionControlServer.CreateWorkspace( $Name )
    }

    [Shelveset] CreateShelveset( [string]$Name )
    {
        return [Shelveset]::new( $this.VersionControlServer, $Name, $ENV:USERNAME )
    }

    [Changeset] GetChangeset( [Int32]$ChangesetID, [bool]$IncludeChanges, [bool]$IncludeDownloadInfo )
    {
        return $this.VersionControlServer.GetChangeset(
            $ChangesetID, $IncludeChanges, $IncludeDownloadInfo
        )
    }

    [Void] DownloadFile( [string]$ServerPath, [string]$DestinationPath )
    {
        $folder = Split-Path $DestinationPath
        New-Item -Path $folder -ItemType Directory -Force -ErrorAction Ignore

        $this.VersionControlServer.DownloadFile( $ServerPath, $DestinationPath )
    }

    [Changeset[]] GetHistory (
        [String] $ServerPath,
        [String] $User,
        [Int32] $MaxCount,
        [Boolean] $IncludeChanges,
        [Boolean] $SortAscending
    )
    {
        [Int32] $DeletionId = 0
        [VersionSpec] $Version = [VersionSpec]::Latest
        [RecursionType] $Recursion = [RecursionType]::Full
        [VersionSpec] $VersionFrom = $null
        [VersionSpec] $VersionTo = $null
        [Boolean] $SlotMode = $true
        [Boolean] $IncludeDownloadInfo = $true

        if ( [string]::IsNullOrEmpty( $User ) )
        {
            $User = [NullString]::Value
        }

        return $this.VersionControlServer.QueryHistory(
            $ServerPath,
            $Version,
            $DeletionId,
            $Recursion,
            $User,
            $VersionFrom,
            $VersionTo,
            $MaxCount,
            $IncludeChanges,
            $SlotMode,
            $IncludeDownloadInfo,
            $SortAscending
        )
    }
}

# ./home/vsts/work/1/s/TFVC/Classes/TFVCSessionException.ps1

class TFVCSessionException : Exception
{
    TFVCSessionException() {}

    TFVCSessionException( [string] $Message ) : base( $message ) {}

    TFVCSessionException( [string] $Message, [exception] $Inner ) : base( $Message, $Inner ) {}
}

# Importing from [/home/vsts/work/1/s/TFVC\Public]
# ./TFVC/Public/Add-TFVCItem.ps1
function Add-TFVCItem
{
    <#
        .Synopsis
        Adds items to the workspace that will become pending changes

        .Example
        Add-TFVCItem -Path $Path

        .Notes

    #>

    [Alias('TFAdd')]
    [cmdletbinding()]
    param(
        # Path to the file to add. Supports wildcards
        [Parameter(
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $Path = '.',

        # Recursivly add files
        [Parameter(
            Position = 1,
            ValueFromPipelineByPropertyName
        )]
        [switch]
        $Recurse,

        # The Workspace
        [Parameter(
            Position = 2,
            ValueFromPipeline
        )]
        [Workspace]
        $Workspace = (Get-TFVCActiveWorkspace)
    )

    process
    {
        try
        {
            $allFiles = Resolve-Path -Path $Path

            $count = foreach ( $node in $allFiles.Path )
            {
                $Workspace.PendAdd( $node, $Recurse )
            }

            [PSCustomobject]@{
                LocalItems = $allFiles
                NumberAdded = $count
            }
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./TFVC/Public/Add-TFVCWorkspaceMapping.ps1

function Add-TFVCWorkspaceMapping
{
    <#
        .Synopsis
        Adds a source to folder mapping to the workspace

        .Example
        New-TFVCSession -ServerURI https://tfs -ProjectCollection DevOps
        $workspace = Get-TFVCWorkspace
        $workspace | Add-TFVCWorkspaceMapping -Source '$/DevOpsTFVCTest/master' -Destination 'c:\localworkspace\DevOpsTFVCTest\master'

        .Notes

    #>

    [Alias('TFMap')]
    [cmdletbinding( DefaultParameterSetName = 'Path', SupportsShouldProcess )]
    param(

        # A workspace to add a source mapping to
        [Parameter(
            ValueFromPipeline
        )]
        [ValidateNotNullOrEmpty()]
        [Workspace]
        $Workspace = (Get-TFVCActiveWorkspace),

        # The TFS locaion to map to the local system
        [Alias('SourcePath', 'TFSLocaion', 'ServerItem')]
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'Path'
        )]
        [ValidateNotNullOrEmpty()]
        [String]
        $Source,

        # The location on the local system that gets mapped
        [Alias('DestinationPath', 'Path', 'LocalPath', 'FullName', 'LocalItem')]
        [Parameter(
            Mandatory,
            Position = 1,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'Path'
        )]
        [ValidateNotNullOrEmpty()]
        [String]
        $Destination,

        # The location on the local system that gets mapped
        [Alias('WorkingFolder')]
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipeline,
            ParameterSetName = 'WorkingFolder'
        )]
        [ValidateNotNullOrEmpty()]
        [WorkingFolder]
        $Mapping
    )

    process
    {
        try
        {
            if ( $null -ne $Mapping )
            {
                $Source = $Mapping.ServerItem
                $Destination = $Mapping.LocalItem
            }

            # normalize to full path
            $null = New-Item -Path $Destination -ItemType Directory -Force -ErrorAction Ignore
            $Destination = Resolve-Path -Path $Destination

            Write-Verbose ( "Mapping source [{0}] to local [{1}] in workspace [{2}]" -f $Source, $Destination, $Workspace.DisplayName )

            # Check to see if it is already mapped
            $currentFolder = $Workspace.Folders |
                Where ServerItem -eq $Source
            Where LocalItem -eq $Destination

            if ( $null -eq $currentFolder )
            {
                Write-Verbose ' Adding new mapping'
                if ( $PSCmdlet.ShouldProcess( $Source ) )
                {
                    if ( $null -ne $Mapping )
                    {
                        $Workspace.CreateMapping( $Mapping )
                    }
                    else
                    {
                        $Workspace.Map( $Source, $Destination )
                    }

                    Write-Verbose ' Verify working folder was created'
                    $Workspace.GetWorkingFolderForServerItem( $Source )
                }
            }
            else
            {
                Write-Verbose " Folder [$Destination] is already mapped in this workspace"
                $currentFolder
            }
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./TFVC/Public/Get-TFVCActiveWorkspace.ps1
function Get-TFVCActiveWorkspace
{
    <#
        .Synopsis
        Gets the currently active workspace

        .Example
        Get-TFVCActiveWorkspace

        .Notes
        This is used to provide a default value for cmdlets that need a workspace.
        This is why it throws an error if it is used before a workspace is set.
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param()

    end
    {
        if ( $PSCmdlet.ShouldProcess( $script:ActiveTFVCWorkspace.DisplayName ) )
        {
            if ( $script:ActiveTFVCWorkspace )
            {
                $script:ActiveTFVCWorkspace
            }
            else
            {
                Write-Error -ErrorAction Stop -ErrorId NoActiveTFVCWorkspace -Message 'No active workspace is set. Please provide a workspace or set an active one using Set-TFVCActiveWorkspace.'
            }
        }
    }
}

# ./TFVC/Public/Get-TFVCChangeset.ps1
function Get-TFVCChangeset
{
    <#
        .Synopsis
        Get specified changeset details

        .Example
        Get-TFVCChangeset -ChangesetID $ChangesetID

        .Notes
        GetChangeSet: https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2013/ff737622%28v%3dvs.120%29
        Changeset: https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2013/bb170151%28v%3dvs.120%29
    #>

    [Alias('TFChangeset')]
    [CmdletBinding()]
    [Outputtype('[Microsoft.TeamFoundation.VersionControl.Client.Changeset]')]
    param(

        # The ID of the Changeset.
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [Int32[]]
        $ChangesetID,

        # Active TFVC Session
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [TFVCSession]
        $TFVCSession = (Get-TFVCSession),

        # True to include the changes in the Changeset. False to include only metadata.
        [Parameter()]
        [switch]
        $IncludeChanges,

        # True to get the information needed to download files. Specify false to save bandwidth if not necessary.
        [Parameter()]
        [switch]
        $IncludeDownloadInfo
    )

    process
    {
        try
        {
            foreach ( $id in $ChangesetID )
            {
                $TFVCSession.GetChangeset( $id, $IncludeChanges, $IncludeDownloadInfo )
            }
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./TFVC/Public/Get-TFVCFile.ps1
function Get-TFVCFile
{
    <#
        .Synopsis
        Downloads a file from the TFS server

        .Example
        Get-TFVCFile -ServerPath $ServerPath -Path $Path

        .Notes

    #>

    [Alias('TFDownload')]
    [cmdletbinding(SupportsShouldProcess)]
    param(
        # Server location for the file to download
        [Alias('Source')]
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [String]
        $ServerPath,

        # Location to save the selected file
        [Alias('FullName', 'Path')]
        [Parameter(
            Mandatory,
            Position = 1,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [String]
        $DestinationPath,

        # Active TFVC Session
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [TFVCSession]
        $TFVCSession = (Get-TFVCSession)
    )

    process
    {
        try
        {
            if ( $PSCmdlet.ShouldProcess( $DestinationPath ) )
            {
                $TFVCSession.DownloadFile( $ServerPath, $DestinationPath )
            }
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./TFVC/Public/Get-TFVCHistory.ps1
function Get-TFVCHistory
{
    <#
        .Synopsis
        Get the history for a given server path

        .Example
        Get-TFVCHistory -Path $Path

        .Notes

    #>

    [cmdletbinding()]
    param(

        # The server path to checck the history on. Supports wildcards
        [Alias('Path')]
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $ServerPath,

        # User to search the history for
        [Parameter(
            Position = 1,
            ValueFromPipelineByPropertyName
        )]
        [String]
        $User = [NullString]::Value,

        # max number of history items to return
        [Parameter( ValueFromPipelineByPropertyName )]
        [Int32]
        $MaxCount = [Int32]::MaxValue,

        # include change information
        [Parameter( ValueFromPipelineByPropertyName )]
        [Switch]
        $IncludeChanges,

        # Sort the results Ascending
        [Parameter( ValueFromPipelineByPropertyName )]
        [Switch]
        $SortAscending,

        # Active TFVC Session
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [TFVCSession]
        $TFVCSession = (Get-TFVCSession)
    )

    process
    {
        try
        {
            $TFVCSession.GetHistory(
                $ServerPath,
                $User,
                $MaxCount,
                $IncludeChanges,
                $SortAscending
            )
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }

    end
    {

    }
}

# ./TFVC/Public/Get-TFVCLatest.ps1

function Get-TFVCLatest
{
    <#
        .Synopsis
        Gets the latest changes for all the mappings in the workspace


        .Example
        New-TFVCSession -ServerURI https://tfs -ProjectCollection DevOps
        $workspace = Get-TFVCWorkspace
        $workspace | Get-TFVCLatest

        .Notes

    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        # A workspace to get latest
        [Parameter(
            Position = 0,
            ValueFromPipeline
        )]
        [ValidateNotNullOrEmpty()]
        [Workspace]
        $Workspace = (Get-TFVCActiveWorkspace)
    )

    process
    {
        try
        {
            if ( $null -ne $Workspace -and $PSCmdlet.ShouldProcess( $Workspace.DisplayName ) )
            {
                $Workspace.Get()
            }
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./TFVC/Public/Get-TFVCMergeCandidate.ps1
function Get-TFVCMergeCandidate
{
    <#
        .Synopsis
        Compares 2 branches and get the changeset that are different between them

        .Example
        Get-TFVCMergeCandidate -SourceBranch $SourceBranch -TargetBranch $TargetBbranch -Workspace $Workspace

        .Notes

    #>

    [cmdletbinding()]
    param(

        # Source banch with the changes that need to be merged
        [Parameter(
            Mandatory,
            Position = 1,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [String]
        $SourceBranch,

        # Target branch to merge the changes into
        [Parameter(
            Mandatory,
            Position = 2,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [String]
        $TargetBranch,

        # Active TFVC Session
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [TFVCSession]
        $TFVCSession = (Get-TFVCSession)
    )

    begin
    {
        $recursive = [RecursionType]::Full
    }

    process
    {
        try
        {
            $MergeCandidate = $TFVCSession.VersionControlServer.GetMergeCandidates( $SourceBranch, $TargetBranch, $recursive )
            $MergeCandidate.ChangeSet
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./TFVC/Public/Get-TFVCPendingChange.ps1

function Get-TFVCPendingChange
{
    <#
        .Synopsis
        Gets the pending changes in the workspace

        .Example
        Get-TFVCPendingChange

        .Notes

    #>

    [Alias('Get-TFVCPendingChanges', 'TFPending')]
    [cmdletbinding()]
    param(
        # The Workspace
        [Parameter(
            Position = 0,
            ValueFromPipeline
        )]
        [Workspace]
        $Workspace = (Get-TFVCActiveWorkspace)
    )

    process
    {
        try
        {
            $Workspace.GetPendingChanges()
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./TFVC/Public/Get-TFVCSession.ps1
function Get-TFVCSession
{
    <#
        .Synopsis
        Gets the current active session

        .Example
        Get-TFVCSession

        .Notes

    #>

    [Alias('Get-TFVCConnection', 'GTFVCS')]
    [cmdletbinding()]
    param ()

    process
    {
        if ( $null -eq $script:MasterTFVCSession )
        {
            Write-Warning 'There are no active TFVC sessions, please run [New-TFVCSession] to connect to a server'
        }
        else
        {
            $script:MasterTFVCSession
        }
    }
}

# ./TFVC/Public/Get-TFVCWorkspace.ps1

function Get-TFVCWorkspace
{
    <#
        .Synopsis
        Gets the local workspace

        .Example
        Get-TFVCWorkspace -Path $Path

        .Notes

    #>

    [cmdletbinding( DefaultParameterSetName = 'Default' )]
    [OutputType('Microsoft.TeamFoundation.VersionControl.Client.Workspace')]
    param(
        # Workspace name
        [Alias('WorkspaceName', 'Workspace')]
        [Parameter(
            Position = 0,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'Default'
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(1, 64)]
        [String]
        $Name = "${env:COMPUTERNAME}-Default",

        # Workspace owner
        [Alias('WorkspaceOwner')]
        [Parameter(
            Position = 0,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'Default'
        )]
        [ValidateNotNullOrEmpty()]
        [String]
        $Owner = $env:USERNAME,

        # Local path to a working folder
        [Alias('LocalPath', 'Folder', 'Directory', 'FullName', 'WorkingFolder')]
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'LocalPath'
        )]
        [ValidateNotNullOrEmpty()]
        [String]
        $Path,

        # Active TFVC Session
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [TFVCSession]
        $TFVCSession = (Get-TFVCSession),

        # Sets the resulting workspace as the active workspace
        [switch]
        $SetActiveWorkspace
    )

    process
    {
        try
        {
            $workspace = $null

            if ( $null -eq $TFVCSession )
            {
                Write-Warning 'No TFVCSession available to retreive workspace'
                return
            }

            switch ( $PSCmdlet.ParameterSetName )
            {
                'LocalPath'
                {
                    Write-Debug "Working folder path [$Path]"
                    $workspace = $TFVCSession.GetWorkspaceFromPath( $Path )
                }
                default
                {
                    $workspace = $TFVCSession.GetWorkspace( $PSBoundParameters.Name, $Owner )
                }
            }

            if ( $SetActiveWorkspace )
            {
                $workspace | Set-TFVCActiveWorkspace
            }

            return $workspace
        }
        catch [WorkspaceNotFoundException]
        {
            Write-Verbose 'The workspace could not be found'
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./TFVC/Public/Get-TFVCWorkspaceMapping.ps1

function Get-TFVCWorkspaceMapping
{
    <#
        .Synopsis
        Gets the folder mappings in the workspace

        .Example
        New-TFVCSession -ServerURI https://tfs -ProjectCollection DevOps
        $workspace = Get-TFVCWorkspace
        $Workspace | Get-TFVCWorkspaceMapping

        .Notes

    #>

    [cmdletbinding()]
    param(

        # A workspace to add a source mapping to
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipeline
        )]
        [ValidateNotNullOrEmpty()]
        [Workspace]
        $Workspace,

        # The TFS locaion to map to the local system
        [Alias('SourcePath', 'TFSLocaion', 'ServerItem')]
        [Parameter(
            Position = 1,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [String]
        $Source = '*',

        # The location on the local system that gets mapped
        [Alias('DestinationPath', 'Path', 'LocalPath', 'FullName', 'LocalItem')]
        [Parameter(
            Position = 2,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [String]
        $Destination = '*'
    )

    process
    {
        try
        {
            $Workspace.Folders | Where {$_.ServerItem -like $Source -and $_.LocalItem -like $Destination}
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./TFVC/Public/Merge-TFVCChangeset.ps1

function Merge-TFVCChangeset
{
    <#
        .Synopsis
        Performs a merge in the workspace with the specified changes

        .Example
        Merge-TFVCChangeset -Path $Path

        .Notes
        Workspace.Merge: https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2013/bb139330%28v%3dvs.120%29
        MergeOptionsEx: https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2013/ff736044%28v%3dvs.120%29
    #>

    [cmdletbinding(SupportsShouldProcess)]
    [OutputType('[Microsoft.TeamFoundation.VersionControl.Client.GetStatus]')]
    param(
        # the workspace
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ParameterSetName = 'Workspace'
        )]
        [Workspace]
        $Workspace = (Get-TFVCActiveWorkspace),

        # Source banch with the changes that need to be merged
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [String]
        $SourceBranch,

        # Target branch to merge the changes into
        [Parameter(
            Mandatory,
            Position = 1,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [String]
        $TargetBranch,

        # First or oldest changeset in the list to merge
        [Alias('Changeset', 'Start', 'First')]
        [Parameter(
            Mandatory,
            Position = 2,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [Changeset]
        $FromChangeset,

        # Latest or newest change to be merged
        [Alias('Last', 'End')]
        [Parameter(
            Position = 3,
            ValueFromPipelineByPropertyName
        )]
        [Changeset]
        $ToChangeset,

        # Special options to use for the merge. Default is None
        [Parameter()]
        [MergeOptionsEx]
        $MergeOptions = [MergeOptionsEx]::None,

        # Specified lock level
        [Parameter()]
        [LockLevel]
        $LockLevel = [LockLevel]::None,

        # Recursion type
        [Parameter()]
        [RecursionType]
        $RecursionType = [RecursionType]::Full
    )

    process
    {
        try
        {
            if ( $null -eq $ToChangeset )
            {
                $ToChangeset = $FromChangeset
            }

            $fromVersion = [ChangesetVersionSpec]::new($FromChangeset.ChangesetId)
            $toVersion = [ChangesetVersionSpec]::new($ToChangeset.ChangesetId)

            # Get the results of a merge without doing a merge
            if ( -Not $PSCmdlet.ShouldProcess($TargetBranch) )
            {
                $MergeOptions = $MergeOptions -bor [MergeOptionsEx]::NoMerge
            }

            $status = $Workspace.Merge(
                $SourceBranch,
                $TargetBranch,
                $fromVersion,
                $toVersion,
                $LockLevel,
                $RecursionType,
                $MergeOptions
            )
            $status
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./TFVC/Public/New-TFVCSession.ps1
function New-TFVCSession
{
    <#
        .Synopsis
        Creates a connection to a TFS or VSTS endpoint

        .Example
        New-TFVCSession -Path $Path

        .Notes
    #>


    [Alias('Open-TFVCConnection', 'NTFVCS')]
    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    param(
        # TFS or VSTS endpoint
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [uri]
        $ServerURI,

        # Project Collection Name
        [Parameter(
            Mandatory,
            Position = 1,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $ProjectCollection,

        # Credential
        [Parameter(
            Position = 2,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [PSCredential]
        $Credential,

        # Pass the session object to the pipeline
        [switch]
        $PassThru
    )

    process
    {
        try
        {
            Write-Verbose ('Connectiong to TFVC URI [{0}] and Project Collection [{1}]' -f $ServerURI, $ProjectCollection )

            $script:MasterTFVCSession = $null
            if ( $null -ne $Credential )
            {
                Write-Debug (' With Credential [{0}]' -f $Credential.UserName )
                $script:MasterTFVCSession = [TFVCSession]::New( $ServerURI, $ProjectCollection, $Credential )
            }
            else
            {
                Write-Debug ' With Default Credential'
                $script:MasterTFVCSession = [TFVCSession]::New( $ServerURI, $ProjectCollection )
            }

            if ( $PassThru )
            {
                return $script:MasterTFVCSession
            }
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./TFVC/Public/New-TFVCShelveset.ps1

function New-TFVCShelveset
{
    <#
        .Synopsis
        Creates a Shelveset with the current pending changes

        .Example
        TFShelve

        .Example
        New-TFVCShelveset -Workspace $Workspace

        .Notes
        Workspace.Shelve: https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2013/bb139422(v%3dvs.120)
        Shelveset Class: https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2013/bb171628%28v%3dvs.120%29

    #>

    [Alias('TFShelve')]
    [cmdletbinding(SupportsShouldProcess)]
    param(
        # The Workspace
        [Parameter(
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [String]
        $Name = ('{0}-{1:yyyyMMddHHmmss}' -f $env:USERNAME, (Get-Date) ),

        # The message or comment on the shelveset
        [Parameter(
            Position = 1,
            ValueFromPipelineByPropertyName
        )]
        [String]
        $Comment = $env:USERNAME,

        # The Workspace
        [Parameter(
            Position = 2,
            ValueFromPipeline
        )]
        [Workspace]
        $Workspace = (Get-TFVCActiveWorkspace),

        # Pending changes to shelve
        [Parameter(
            Position = 3,
            ValueFromPipeline
        )]
        [PendingChange[]]
        $PendingChange = (Get-TFVCPendingChange),

        # Active TFVC Session
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [TFVCSession]
        $TFVCSession = (Get-TFVCSession),

        # ShelvingOptions
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ShelvingOptions]
        $ShelvingOptions = [ShelvingOptions]::Replace
    )

    process
    {
        try
        {
            $shelveset = $TFVCSession.CreateShelveset( $Name )
            $shelveset.Comment = $Comment

            if ( $PSCmdlet.ShouldProcess( $Workspace.DisplayName ) )
            {
                $Workspace.Shelve( $shelveset, $PendingChange, $ShelvingOptions )
                return $shelveset
            }
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./TFVC/Public/New-TFVCWorkspace.ps1

function New-TFVCWorkspace
{
    <#
        .Synopsis
        Creates a local workspace

        .Example
        New-TFVCWorkspace -Path $Path

        .Notes
        Name must be unique per owner

    #>

    [cmdletbinding(SupportsShouldProcess)]
    [OutputType('Microsoft.TeamFoundation.VersionControl.Client.Workspace')]
    param(
        # Parameter help description
        [Parameter(
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(1, 64)]
        [String]
        $Name = "${env:COMPUTERNAME}-Default",

        # Active TFVC Session
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [TFVCSession]
        $TFVCSession = (Get-TFVCSession),

        # Sets this workspace as the active workspace
        [switch]
        $SetActiveWorkspace
    )

    process
    {
        try
        {
            if ( $PSCmdlet.ShouldProcess( $Name ) )
            {
                $workspace = $TFVCSession.CreateWorkspace( $Name )
                if ( $SetActiveWorkspace )
                {
                    Set-TFVCActiveWorkspace -Workspace $workspace
                }

                return $workspace
            }
        }
        catch [WorkspaceExistsException]
        {
            Write-Verbose "The workspace [$Name] already exists. Using existing workspace."
            $TFVCWorkspace = @{
                Name               = $Name
                TFVCSession        = $TFVCSession
                SetActiveWorkspace = [bool]$SetActiveWorkspace
            }
            Get-TFVCWorkspace @TFVCWorkspace
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./TFVC/Public/Remove-TFVCActiveWorkspace.ps1
function Remove-TFVCActiveWorkspace
{
    <#
        .Synopsis
        Makes the current active workspace nolonger active.

        .Example
        Remove-TFVCActiveWorkspace

        .Notes

    #>

    [cmdletbinding(SupportsShouldProcess)]
    param()

    end
    {
        if ( $PSCmdlet.ShouldProcess( $script:ActiveTFVCWorkspace.DisplayName ) )
        {
            $script:ActiveTFVCWorkspace = $null
        }
    }
}

# ./TFVC/Public/Remove-TFVCPendingChange.ps1

function Remove-TFVCPendingChange
{
    <#
        .Synopsis
        Removes the specified pending changes

        .Example
        Remove-TFVCPendingChange -Path $Path

        .Example
        Remove-TFVCPendingChange | Remove-TFVCPendingChages

        .Notes

    #>

    [Alias('Remove-TFVCPendingChanges', 'TFUndo')]
    [cmdletbinding(SupportsShouldProcess)]
    [OutputType('[Microsoft.TeamFoundation.VersionControl.Client.PendingChange]')]
    param(

        # The Workspace
        [Parameter(
            Position = 0,
            ValueFromPipeline
        )]
        [Workspace]
        $Workspace = (Get-TFVCActiveWorkspace),

        # Local path to the pending change that should be removed
        [Alias('FullName', 'LocalItem')]
        [Parameter(
            Position = 1,
            ValueFromPipelineByPropertyName
        )]
        $Path,

        # Pending changes to commit
        [Parameter(
            Position = 2,
            ValueFromPipeline
        )]
        [PendingChange[]]
        $PendingChange = (Get-TFVCPendingChange)
    )

    process
    {
        try
        {
            if ( $null -ne $Path )
            {
                $PendingChange | Where LocalItem -in $Path
            }

            if ( $null -ne $PendingChange )
            {
                if ( $PSCmdlet.ShouldProcess( ( $PendingChange.LocalItem -join ',' ) ) )
                {
                    $count = $Workspace.Undo( $PendingChange )

                    [PSCustomOBject]@{
                        LocalItem     = $PendingChange.localItem
                        UndoneChanges = $count
                    }
                }
            }
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./TFVC/Public/Remove-TFVCSession.ps1
function Remove-TFVCSession
{
    <#
        .Synopsis
        Removes the current session

        .Example
        Remove-TFVCSession -TFVCSession $TFVCSession

        .Notes

    #>

    [Alias('Remove-TFVCConnection', 'RTFVCS')]
    [cmdletbinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    param ()

    process
    {
        try
        {
            if ( $null -ne $script:MasterTFVCSession )
            {
                $script:MasterTFVCSession.Disconnect()
                $script:MasterTFVCSession = $null
            }
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./TFVC/Public/Remove-TFVCWorkspace.ps1

function Remove-TFVCWorkspace
{
    <#
        .Synopsis
        This will delete an existing workspace.

        .Example
        Remove-TFVCWorkspace -Path $Path

        .Notes
        This implementation only deletes from the local system, but could be enhanced to delete any workspace from TFS

    #>

    [cmdletbinding(DefaultParameterSetName = 'Named', SupportsShouldProcess)]
    param(
        # Name of the workspace
        [Parameter(
            Position = 0,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'Named'
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(1, 64)]
        [String]
        $Name = "${env:COMPUTERNAME}-Default",

        # the workspace
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipeline,
            ParameterSetName = 'Workspace'
        )]
        [Workspace]
        $Workspace,

        # Active TFVC Session
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [TFVCSession]
        $TFVCSession = (Get-TFVCSession)
    )

    process
    {
        try
        {
            if ( $null -eq $Workspace )
            {
                $Workspace = Get-TFVCWorkspace -Name $Name -TFVCSession $TFVCSession
            }

            if ( $null -ne $Workspace -and $PSCmdlet.ShouldProcess( $Workspace.DisplayName ) )
            {
                if ( $Workspace.Delete() )
                {
                    Write-Verbose "The Workspace [$($Workspace.DisplayName)] was deteled"
                }
                else
                {
                    Write-Warning 'Calling delete on this workspace object returned [$false] unexpectidly. Are the red ones stuff you wanted removed? Ooh, that''s clever, Morty, but I don''t use color to sort things'
                }
            }
        }
        catch [WorkspaceDeletedException]
        {
            Write-Verbose 'This workspace has alraedy been deleted'
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./TFVC/Public/Remove-TFVCWorkspaceMapping.ps1

function Remove-TFVCWorkspaceMapping
{
    <#
        .Synopsis
        Adds a source to folder mapping to the workspace

        .Example
        New-TFVCSession -ServerURI https://tfs -ProjectCollection DevOps
        $workspace = Get-TFVCWorkspace
        $workspace | Remove-TFVCWorkspaceMapping -Source '$/DevOpsTFVCTest/master'

        .Notes

    #>

    [cmdletbinding(DefaultParameterSetName = 'Source', SupportsShouldProcess)]
    param(

        # A workspace to add a source mapping to
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipeline
        )]
        [ValidateNotNullOrEmpty()]
        [Workspace]
        $Workspace,

        # The TFS locaion to map to the local system
        [Alias('SourcePath', 'TFSLocaion', 'ServerItem')]
        [Parameter(
            Mandatory,
            Position = 1,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'Source'
        )]
        [ValidateNotNullOrEmpty()]
        [String]
        $Source,

        # The location on the local system that gets mapped
        [Alias('DestinationPath', 'Path', 'LocalPath', 'FullName', 'LocalItem')]
        [Parameter(
            Mandatory,
            Position = 1,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'Destination'
        )]
        [ValidateNotNullOrEmpty()]
        [String]
        $Destination,

        # The location on the local system that gets mapped
        [Alias('WorkingFolder')]
        [Parameter(
            Mandatory,
            Position = 1,
            ValueFromPipeline,
            ParameterSetName = 'WorkingFolder'
        )]
        [WorkingFolder[]]
        $Mapping
    )

    process
    {
        try
        {
            switch ( $PSCmdlet.ParameterSetName )
            {
                'Source'
                {
                    $Mapping = $Workspace.Folders |
                        Where ServerItem -eq $Source
                }
                'Destination'
                {
                    $Mapping = $Workspace.Folders |
                        Where LocalItem -eq $Destination
                }
            }

            if ( $null -ne $Mapping )
            {
                if ( $PSCmdlet.ShouldProcess( $mapping.ServerItem ) )
                {
                    foreach ($folder in $Mapping)
                    {
                        $Workspace.DeleteMapping( $folder )
                    }
                }
            }
        }
        catch [ItemNotMappedException]
        {
            Write-Verbose 'This mapping does not exist in this workspace'
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./TFVC/Public/Save-TFVCPendingChange.ps1

function Save-TFVCPendingChange
{
    <#
        .Synopsis
        Commit or checking the pending changes in the workspace

        .Example
        Save-TFVCPendingChange

        .Notes

    #>

    [Alias('Save-TFVCPendingChanges', 'TFCommit', 'TFCheckIn')]
    [cmdletbinding(SupportsShouldProcess)]
    param(

        # Commit Message
        [Alias('Message', 'CM')]
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $CommitMessage,

        # The Workspace
        [Parameter(
            Position = 2,
            ValueFromPipeline
        )]
        [Workspace]
        $Workspace = (Get-TFVCActiveWorkspace),

        # Filter local changes to path
        [Alias('FullName', 'LocalItem')]
        [Parameter(
            Position = 1,
            ValueFromPipelineByPropertyName
        )]
        [string[]]
        $Path,

        # Pending changes to commit
        [Parameter(
            Position = 3,
            ValueFromPipeline
        )]
        [PendingChange[]]
        $PendingChange = (Get-TFVCPendingChange)
    )

    process
    {
        try
        {
            if ( $null -ne $Path )
            {
                $PendingChange | Where LocalItem -in $Path
            }

            if ( $null -ne $PendingChange )
            {
                if ( $PSCmdlet.ShouldProcess( ( $PendingChange.LocalItem -join ',' ) ) )
                {
                    $newchangeset = $Workspace.Checkin( $PendingChange, $CommitMessage )
                    [PSCustomObject]@{
                        Changeset = $newchangeset
                    }
                }
            }
            else
            {
                Write-Warning "There were no pending changes to commit in workspace [$($Workspace.Displayname)]"
            }
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

# ./TFVC/Public/Set-TFVCActiveWorkspace.ps1

function Set-TFVCActiveWorkspace
{
    <#
        .Synopsis
        Sets a workspace as the default one for the current powershell session

        .Example
        New-TFVCSession -ServerURI https://tfs -ProjectCollection DevOps
        $workspace = New-TFVCWorkspace
        Set-TFVCActiveWorkspace -Workspace $Workspace

        .Example
        New-TFVCSession -ServerURI https://tfs -ProjectCollection DevOps
        New-TFVCWorkspace -SetActiveWorkspace

        .Notes

    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        # The workspace
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipeline
        )]
        [ValidateNotNullOrEmpty()]
        [Workspace]
        $Workspace
    )

    end
    {
        try
        {
            if ( $PSCmdlet.ShouldProcess( $Workspace.DisplayName ) )
            {
                $script:ActiveTFVCWorkspace = $Workspace
            }
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}