GlobalList/GlobalList.ps1

<#
.SYNOPSIS
Exports the contents of one or more Global Lists to XML.
 
.DESCRIPTION
This cmdlets generates an XML containing one or more global lists and their respective items, in the same format used by witadmin. It is functionally equivalent to 'witadmin exportgloballist'
 
.PARAMETER Name
Specifies the name of the global list to be exported. Wildcards are supported; when used, they result in a single XML containing all the matching global lists.
 
.PARAMETER Collection
Specifies either a URL/name of the Team Project Collection to connect to, or a previously initialized TfsTeamProjectCollection object.
 
When using a URL, it must be fully qualified. The format of this string is as follows:
 
http[s]://<ComputerName>:<Port>/[<TFS-vDir>/]<CollectionName>
 
Valid values for the Transport segment of the URI are HTTP and HTTPS. If you specify a connection URI with a Transport segment, but do not specify a port, the session is created with standards ports: 80 for HTTP and 443 for HTTPS.
 
To connect to a Team Project Collection by using its name, a TfsConfigurationServer object must be supplied either via -Server argument or via a previous call to the Connect-TfsConfigurationServer cmdlet.
 
For more details, see the Get-TfsTeamProjectCollection cmdlet.
 
.INPUTS
Microsoft.TeamFoundation.Client.TfsTeamProjectCollection
System.String
System.Uri
 
.EXAMPLE
Export-TfsGlobalList | Out-File 'gl.xml'
Exports all global lists in the current project collection to a file called gl.xml.
 
.EXAMPLE
Export-TfsGlobalList -Name 'Builds - *'
Exports all build-related global lists (with names starting with 'Build - ') and return the resulting XML document
 
.NOTES
To export or list global lists, you must be a member of the Project Collection Valid Users group or have your View collection-level information permission set to Allow.
#>

Function Export-TfsGlobalList
{
    [CmdletBinding()]
    [OutputType('string')]
    Param
    (
        [Parameter(Position=0)]
        [Alias('Name')]
        [SupportsWildcards()]
        [string] 
        $GlobalList = "*",

        [Parameter(ValueFromPipeline=$true)]
        [object]
        $Collection
    )

    Begin
    {
        #_ImportRequiredAssembly -AssemblyName 'Microsoft.TeamFoundation.WorkItemTracking.Client'
    }
    
    Process
    {
        $tpc = Get-TfsTeamProjectCollection $Collection
        $store = $tpc.GetService([type]'Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore')

        $xml = [xml] $store.ExportGlobalLists()

        $procInstr = $xml.CreateProcessingInstruction("xml", 'version="1.0"')

        [void] $xml.InsertBefore($procInstr, $xml.DocumentElement)

        $nodesToRemove = $xml.SelectNodes("//GLOBALLIST")

        foreach($node in $nodesToRemove)
        {
            if (([System.Xml.XmlElement]$node).GetAttribute("name") -notlike $GlobalList)
            {
                [void]$xml.DocumentElement.RemoveChild($node)
            }
        }

        return $xml.OuterXml
    }
}
<#
 
.SYNOPSIS
    Gets the contents of one or more Global Lists.
 
.PARAMETER Collection
    Specifies either a URL/name of the Team Project Collection to connect to, or a previously initialized TfsTeamProjectCollection object.
 
When using a URL, it must be fully qualified. The format of this string is as follows:
 
http[s]://<ComputerName>:<Port>/[<TFS-vDir>/]<CollectionName>
 
Valid values for the Transport segment of the URI are HTTP and HTTPS. If you specify a connection URI with a Transport segment, but do not specify a port, the session is created with standards ports: 80 for HTTP and 443 for HTTPS.
 
To connect to a Team Project Collection by using its name, a TfsConfigurationServer object must be supplied either via -Server argument or via a previous call to the Connect-TfsConfigurationServer cmdlet.
 
For more details, see the Get-TfsTeamProjectCollection cmdlet.
 
.INPUTS
    Microsoft.TeamFoundation.Client.TfsTeamProjectCollection
    System.String
    System.Uri
#>

Function Get-TfsGlobalList
{
    [CmdletBinding()]
    [OutputType('PSCustomObject')]
    Param
    (
        [Parameter()]
        [Alias('Name')]
        [SupportsWildcards()]
        [string] 
        $GlobalList = "*",
    
        [Parameter(ValueFromPipeline=$true)]
        [object]
        $Collection
    )

    Begin
    {
        #_ImportRequiredAssembly -AssemblyName 'Microsoft.TeamFoundation.WorkItemTracking.Client'
    }
    
    Process
    {
        $xml = [xml](Export-TfsGlobalList @PSBoundParameters)

        foreach($listNode in $xml.SelectNodes("//GLOBALLIST"))
        {
            $list = [PSCustomObject] [ordered] @{
                Name = $listNode.GetAttribute("name")
                Items = @()
            }

            foreach($itemNode in $listNode.SelectNodes("LISTITEM"))
            {
                $list.Items += $itemNode.GetAttribute("value")
            }

            $list
        }
    }
}
<#
.SYNOPSIS
Imports one or more Global Lists from an XML document
 
.DESCRIPTION
This cmdletsimports an XML containing one or more global lists and their respective items, in the same format used by witadmin. It is functionally equivalent to 'witadmin importgloballist'
 
.PARAMETER InputObject
XML document object containing one or more global list definitions
 
.PARAMETER Collection
Specifies either a URL/name of the Team Project Collection to connect to, or a previously initialized TfsTeamProjectCollection object.
 
When using a URL, it must be fully qualified. The format of this string is as follows:
 
http[s]://<ComputerName>:<Port>/[<TFS-vDir>/]<CollectionName>
 
Valid values for the Transport segment of the URI are HTTP and HTTPS. If you specify a connection URI with a Transport segment, but do not specify a port, the session is created with standards ports: 80 for HTTP and 443 for HTTPS.
 
To connect to a Team Project Collection by using its name, a TfsConfigurationServer object must be supplied either via -Server argument or via a previous call to the Connect-TfsConfigurationServer cmdlet.
 
For more details, see the Get-TfsTeamProjectCollection cmdlet.
 
.INPUTS
System.Xml.XmlDocument
 
.EXAMPLE
Get-Content gl.xml | Import-GlobalList
Imports the contents of an XML document called gl.xml to the current project collection
 
.NOTES
To import global lists, you must be a member of the Project Collection Administrators security group
#>

Function Import-TfsGlobalList
{
    [CmdletBinding(ConfirmImpact='Medium')]
    Param
    (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [Alias("Xml")]
        [object] 
        $InputObject,

        [Parameter()]
        [switch]
        $Force,
    
        [Parameter()]
        [object]
        $Collection
    )

    Begin
    {
        #_ImportRequiredAssembly -AssemblyName 'Microsoft.TeamFoundation.WorkItemTracking.Client'
    }
    
    Process
    {
        $tpc = Get-TfsTeamProjectCollection $Collection
        $store = $tpc.GetService([type]'Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore')

        if ($InputObject -is [xml])
        {
            $doc = $InputObject.OuterXml
        }
        else
        {
            $doc = $InputObject
        }

        if (-not $Force)
        {
            $existingLists = Get-TfsGlobalList -Collection $tpc
            $listsInXml = ([xml]($InputObject)).SelectNodes('//*/@name')."#text"

            foreach($list in $existingLists)
            {
                if ($list.Name -in $listsInXml)
                {
                    Throw "Global List '$($list.Name)' already exists. To overwrite an existing list, use the -Force switch."
                }
            }
        }

        [void] $store.ImportGlobalLists($doc)
    }
}
<#
 
.SYNOPSIS
    Creates a new Global List.
 
.PARAMETER Collection
    Specifies either a URL/name of the Team Project Collection to connect to, or a previously initialized TfsTeamProjectCollection object.
 
When using a URL, it must be fully qualified. The format of this string is as follows:
 
http[s]://<ComputerName>:<Port>/[<TFS-vDir>/]<CollectionName>
 
Valid values for the Transport segment of the URI are HTTP and HTTPS. If you specify a connection URI with a Transport segment, but do not specify a port, the session is created with standards ports: 80 for HTTP and 443 for HTTPS.
 
To connect to a Team Project Collection by using its name, a TfsConfigurationServer object must be supplied either via -Server argument or via a previous call to the Connect-TfsConfigurationServer cmdlet.
 
For more details, see the Get-TfsTeamProjectCollection cmdlet.
 
.INPUTS
    System.String / System.String[]
#>

Function New-TfsGlobalList
{
    [CmdletBinding(ConfirmImpact='Medium', SupportsShouldProcess=$true)]
    [OutputType('PSCustomObject')]
    Param
    (
        [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName='Name')]
        [Alias('Name')]
        [string] 
        $GlobalList,
    
        [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName='Items')] 
        [string[]] 
        $Items,

        [Parameter()]
        [switch]
        $Force,

        [Parameter()]
        [object]
        $Collection,

        [Parameter()]
        [switch]
        $Passthru
    )

    Begin
    {
        #_ImportRequiredAssembly -AssemblyName 'Microsoft.TeamFoundation.WorkItemTracking.Client'
    }
    
    Process
    {
        [xml] $xml = Export-TfsGlobalList -Collection $Collection

        # Checks whether the global list already exists
        $list = $xml.SelectSingleNode("//GLOBALLIST[@name='$GlobalList']")

        if ($null -ne $list)
        {
            if ($Force.IsPresent)
            {
                if ($PSCmdlet.ShouldProcess($GlobalList, 'Overwrite existing global list'))
                {
                    [void] $list.ParentNode.RemoveChild($list)
                }
            }
            else
            {
                Throw "Global List $GlobalList already exists. To overwrite an existing list, use the -Force switch."
            }
        }

        if($PSCmdlet.ShouldProcess($GlobalList, 'Create global list'))
        {
            # Creates the new list XML element
            $list = $xml.CreateElement("GLOBALLIST")
            $list.SetAttribute("name", $GlobalList)

            # Adds the item elements to the list
            foreach($item in $Items)
            {
                $itemElement = $xml.CreateElement("LISTITEM")
                [void] $itemElement.SetAttribute("value", $item)
                [void]$list.AppendChild($itemElement)
            }

            # Appends the new list to the XML obj
            [void] $xml.DocumentElement.AppendChild($list)

            Import-TfsGlobalList -Xml $xml -Collection $Collection
            $list =  Get-TfsGlobalList -Name $GlobalList -Collection $Collection

            if ($Passthru)
            {
                return $list
            }
        }        
    }
}
<#
 
.SYNOPSIS
    Deletes one or more Global Lists.
 
.PARAMETER Collection
    Specifies either a URL/name of the Team Project Collection to connect to, or a previously initialized TfsTeamProjectCollection object.
 
When using a URL, it must be fully qualified. The format of this string is as follows:
 
http[s]://<ComputerName>:<Port>/[<TFS-vDir>/]<CollectionName>
 
Valid values for the Transport segment of the URI are HTTP and HTTPS. If you specify a connection URI with a Transport segment, but do not specify a port, the session is created with standards ports: 80 for HTTP and 443 for HTTPS.
 
To connect to a Team Project Collection by using its name, a TfsConfigurationServer object must be supplied either via -Server argument or via a previous call to the Connect-TfsConfigurationServer cmdlet.
 
For more details, see the Get-TfsTeamProjectCollection cmdlet.
 
.INPUTS
    System.String
#>

Function Remove-TfsGlobalList
{
    [CmdletBinding(ConfirmImpact="High", SupportsShouldProcess=$true)]
    Param
    (
        [Parameter(ValueFromPipelineByPropertyName='Name')]
        [Alias('Name')]
        [SupportsWildcards()]
        [string] 
        $GlobalList = "*",
    
        [Parameter()]
        [object]
        $Collection
    )

    Begin
    {
        #_ImportRequiredAssembly -AssemblyName 'Microsoft.TeamFoundation.WorkItemTracking.Client'
    }
    
    Process
    {
        $tpc = Get-TfsTeamProjectCollection $Collection
        $store = $tpc.GetService([type]'Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore')

        $lists = Get-TfsGlobalList -Name $GlobalList -Collection $Collection
        $listsToRemove = @()

        foreach($list in $lists)
        {
            if ($PSCmdlet.ShouldProcess($list.Name, "Remove global list"))
            {
                $listsToRemove += $list
            }
        }

        if ($listsToRemove.Length -eq 0)
        {
            return
        }

        $xml = [xml] "<Package />"

        foreach($list in $listsToRemove)
        {
            $elem = $xml.CreateElement("DestroyGlobalList");
            $elem.SetAttribute("ListName", "*" + $list.Name);
            $elem.SetAttribute("ForceDelete", "true");
            [void]$xml.DocumentElement.AppendChild($elem);
        }

        $returnElem = $null

        $store.SendUpdatePackage($xml.DocumentElement, [ref] $returnElem, $false)
    }
}
<#
.SYNOPSIS
Changes the name or the contents of a Global List.
 
.PARAMETER Collection
Specifies either a URL/name of the Team Project Collection to connect to, or a previously initialized TfsTeamProjectCollection object.
 
When using a URL, it must be fully qualified. The format of this string is as follows:
 
http[s]://<ComputerName>:<Port>/[<TFS-vDir>/]<CollectionName>
 
Valid values for the Transport segment of the URI are HTTP and HTTPS. If you specify a connection URI with a Transport segment, but do not specify a port, the session is created with standards ports: 80 for HTTP and 443 for HTTPS.
 
To connect to a Team Project Collection by using its name, a TfsConfigurationServer object must be supplied either via -Server argument or via a previous call to the Connect-TfsConfigurationServer cmdlet.
 
For more details, see the Get-TfsTeamProjectCollection cmdlet.
 
.INPUTS
System.String
#>

Function Set-TfsGlobalList
{
    [CmdletBinding(ConfirmImpact='Medium', SupportsShouldProcess=$true)]
    Param
    (
        [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName='Name')]
        [Alias('Name')]
        [string] 
        $GlobalList,
    
        [Parameter(ParameterSetName="Edit list items")]
        [string[]] 
        $Add,

        [Parameter(ParameterSetName="Edit list items")]
        [string[]] 
        $Remove,

        [Parameter(ParameterSetName="Rename list", Mandatory=$true)]
        [string] 
        $NewName,

        [Parameter(ParameterSetName="Edit list items")]
        [switch] 
        $Force,

        [object]
        $Collection
    )

    Begin
    {
        #_ImportRequiredAssembly -AssemblyName 'Microsoft.TeamFoundation.WorkItemTracking.Client'
    }
    
    Process
    {
        $xml = [xml] (Export-TfsGlobalList -Name $GlobalList -Collection $Collection)

        # Retrieves the list
        $list = $xml.SelectSingleNode("//GLOBALLIST")
        $newList = $false

        if ($null -eq $list)
        {
            if (-not $Force.IsPresent)
            { 
                throw "Global list name $GlobalList is invalid or non-existent. Either check the name or use -Force to create a new list."
            }
            
            # Creates the new list XML element
            $list = $xml.CreateElement("GLOBALLIST")
            [void] $list.SetAttribute("name", $GlobalList)
            [void] $xml.DocumentElement.AppendChild($list)
            $newList = $true
        }

        if ($PSCmdlet.ParameterSetName -eq "Rename list")
        {
            if($PSCmdlet.ShouldProcess($GlobalList, "Rename global list to $NewName"))
            {
                $list.SetAttribute("name", $NewName)
                Import-TfsGlobalList -Xml $xml -Collection $Collection
                Remove-TfsGlobalList -Name $GlobalList -Collection $Collection -Confirm:$false
            }
            return Get-TfsGlobalList -Name $NewName -Collection $Collection
        }

        foreach($item in $Add)
        {
            if (-not $newList)
            {
                # Checks if the element exists (prevents duplicates)
                $existingItem = $list.SelectSingleNode("LISTITEM[@value='$item']")
                if ($null -ne $existingItem) { continue }
            }

            if($PSCmdlet.ShouldProcess($GlobalList, "Add item '$item' to global list"))
            {
                $isDirty = $true
                $itemElement = $xml.CreateElement("LISTITEM")
                [void] $itemElement.SetAttribute("value", $item)
                [void]$list.AppendChild($itemElement)
            }
        }
        
        if (-not $newList)
        {
            foreach($item in $Remove)
            {
                $existingItem = $list.SelectSingleNode("LISTITEM[@value='$item']")
                
                if ($existingItem -and $PSCmdlet.ShouldProcess($GlobalList, "Remove item '$item' from global list"))
                {
                    $isDirty = $true
                    [void]$list.RemoveChild($existingItem)
                }
            }
        }
                
        # Saves the list back to TFS
        if($isDirty)
        {
            Import-TfsGlobalList -Xml $xml -Collection $Collection -Force
        }

        return Get-TfsGlobalList -Name $GlobalList -Collection $Collection
    }
}