XamlBuild/XamlBuild.psm1

<#
 
.SYNOPSIS
    Gets information about one or more XAML Build agents.
 
.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.
 
#>

Function Get-TfsXamlBuildAgent
{
    [CmdletBinding()]
    [OutputType([Microsoft.TeamFoundation.Build.Client.IBuildAgent])]
    Param
    (
        [Parameter(Position=0)]
        [ValidateScript({$_ -is [string] -or $_ -is [Microsoft.TeamFoundation.Build.Client.IBuildAgent]})]
        [ValidateNotNullOrEmpty()]
        [Alias("Name")]
        [object] 
        $BuildAgent = "*",

        [Parameter(Position=0, ValueFromPipeline=$true)]
        [ValidateScript({$_ -is [string] -or $_ -is [Microsoft.TeamFoundation.Build.Client.IBuildController]})]
        [ValidateNotNullOrEmpty()]
        [Alias("Controller")]
        [object] 
        $BuildController = "*",

        [Parameter()]
        [object]
        $Collection
    )

    Process
    {
        if ($BuildAgent -is [Microsoft.TeamFoundation.Build.Client.IBuildAgent])
        {
            return $BuildAgent
        }

        $controllers = Get-TfsXamlBuildController -BuildController $BuildController -Collection $Collection

        foreach($controller in $controllers)
        {
            $controller.Agents | Where Name -Like $BuildAgent
        }        
    }
}
<#
 
.SYNOPSIS
    Gets information about one or more XAML Build controllers.
 
.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.
 
#>

Function Get-TfsXamlBuildController
{
    [CmdletBinding()]
    [OutputType([Microsoft.TeamFoundation.Build.Client.IBuildController])]
    Param
    (
        [Parameter(Position=0)]
        [ValidateScript({$_ -is [string] -or $_ -is [Microsoft.TeamFoundation.Build.Client.IBuildController]})]
        [ValidateNotNullOrEmpty()]
        [Alias("Name")]
        [object] 
        $BuildController = "*",

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

    Process
    {
        if ($BuildController -is [Microsoft.TeamFoundation.Build.Client.IBuildController])
        {
            return $BuildController

        }

        $tpc = Get-TfsTeamProjectCollection $Collection

        $buildServer = $tpc.GetService([type]'Microsoft.TeamFoundation.Build.Client.IBuildServer')
        $buildControllers = $buildServer.QueryBuildControllers()
        
        return $buildControllers | Where Name -Like $BuildController
    }
}
<#
 
.SYNOPSIS
    Gets one or more XAML Build definitions.
 
.PARAMETER BuildDefinition
    Uses this parameter to filter for an specific Build Defintion.
    If suppress, cmdlet will show all queue builds.
 
.PARAMETER Project
    Specifies either the name of the Team Project or a previously initialized Microsoft.TeamFoundation.WorkItemTracking.Client.Project object to connect to. If omitted, it defaults to the connection opened by Connect-TfsTeamProject (if any).

For more details, see the Get-TfsTeamProject cmdlet.
 
.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.
 
.EXAMPLE
    Get-TfsBuildQueue -BuildDefinition "My Build Definition" -Project "My Team Project"
    Get all queued builds given a definition name and a team project name
 
.EXAMPLE
    Get-TfsBuildQueue
    Get all queued builds, regardless of definition name or team project name
 
#>

Function Get-TfsXamlBuildDefinition
{
    [CmdletBinding()]
    [OutputType([Microsoft.TeamFoundation.Build.Client.IBuildDefinition])]
    Param
    (
        [Parameter(Position=0)]
        [ValidateScript({$_ -is [string] -or $_ -is [Microsoft.TeamFoundation.Build.Client.IBuildDefinition]})]
        [ValidateNotNullOrEmpty()]
        [Alias("Name")]
        [object] 
        $BuildDefinition = "*",

        [Parameter(ValueFromPipeline=$true)]
        [object]
        $Project,

        [Parameter()]
        [object]
        $Collection
    )

    Process
    {
        if ($BuildDefinition -is [Microsoft.TeamFoundation.Build.Client.IBuildDefinition])
        {
            return $BuildDefinition
        }

        $tp = Get-TfsTeamProject $Project $Collection
        $tpName = $tp.Name
        $tpc = $tp.Store.TeamProjectCollection

        $buildServer = $tpc.GetService([type]'Microsoft.TeamFoundation.Build.Client.IBuildServer')
        $buildDefs = $buildServer.QueryBuildDefinitions($tpName)
        
        return $buildDefs | Where Name -Like $BuildDefinition
    }
}
<#
 
.SYNOPSIS
    Gets information about queued XAML Builds.
 
.PARAMETER BuildDefinition
    Uses this parameter to filter for an specific Build Defintion.
    If suppress, cmdlet will show all queue builds.
 
.PARAMETER Project
    Specifies either the name of the Team Project or a previously initialized Microsoft.TeamFoundation.WorkItemTracking.Client.Project object to connect to. If omitted, it defaults to the connection opened by Connect-TfsTeamProject (if any).

For more details, see the Get-TfsTeamProject cmdlet.
 
.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.
 
.EXAMPLE
    Get-TfsBuildQueue -BuildDefinition "My Build Definition" -Project "My Team Project"
    Get all queued builds given a definition name and a team project name
 
.EXAMPLE
    Get-TfsBuildQueue
    Get all queued builds, regardless of definition name or team project name
 
#>

Function Get-TfsXamlBuildQueue
{
    [CmdletBinding()]
    [OutputType([Microsoft.TeamFoundation.Build.Client.IQueuedBuild])]
    Param
    (
        [Parameter(Position=0, ValueFromPipeline=$true)]
        [ValidateScript({$_ -is [string] -or $_ -is [Microsoft.TeamFoundation.Build.Client.IBuildDefinition]})]
        [ValidateNotNullOrEmpty()]
        [object] 
        $BuildDefinition = "*",

        [Parameter()]
        [object]
        $Project,

        [Parameter()]
        [object]
        $Collection
    )

    Process
    {
        if ($BuildDefinition -is [Microsoft.TeamFoundation.Build.Client.IBuildDefinition])
        {
            $buildDefName = $BuildDefinition.Name
        }
        else
        {
            $buildDefName = $BuildDefinition
        }

        if ($Project)
        {
            $tp = Get-TfsTeamProject $Project $Collection
            $tpName = $tp.Name
            $tpc = $tp.Store.TeamProjectCollection
        }
        else
        {
            $tpName = "*"
            $tpc = Get-TfsTeamProjectCollection $Collection
        }

        $buildServer = $tpc.GetService([type]'Microsoft.TeamFoundation.Build.Client.IBuildServer')
        $query = $buildServer.CreateBuildQueueSpec($tpName, $buildDefName)
        
        $buildServer.QueryQueuedBuilds($query).QueuedBuilds
    }
}
<#
.SYNOPSIS
   Sets up an additional build service in the current computer
 
.DESCRIPTION
 
   In Team Foundation Server, the Build Service is a Windows Service that is associated with a particular TFS Team Project Collection (since 2010).
    
   Each Build Service support zero to one (0..1) Build Controllers and zero to n (0..n) Build Agents.
    
   Each Build Agent is associated to a specific Build Controller but an Agent and its corresponding Controller don�t need to be on the same machine.
    
   This topology lets you configure a continuous integration build that queues its builds on the Build Controller you�ve specified which then farms out the heavy lifting to any of the n agents it manages. This gives you an easy way to load balance your builds across a set of machines.
    
   There is a potential down-side, however, in that each build service can only service one particular project collection and you cannot install more than one build service in any given computer.
 
   This script allows you work around that limitation, setting up more than one build service in the same machine.
 
.NOTES
     
    This is not supported and should not be used in production environments.
#>

Function New-TfsXamlBuildService
{
    Param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $CollectionName,

        [Parameter()]
        [string]
        $BuildServiceName = "TFSBuildServiceHost.2013",

        [Parameter()]
        [string]
        $ComputerName = $env:COMPUTERNAME,

        [Parameter()]
        [ValidateRange(1,65535)]
        [int]
        $Port = 9191,

        [Parameter(Mandatory=$true)]
        [System.Management.Automation.Credential()]
        [System.Management.Automation.PSCredential]
        $ServiceCredential = (Get-Credential),

        [Parameter()]
        [switch]
        $Force
    )

    Begin
    {
        if ($ComputerName -eq "localhost")
        {
            $ComputerName = $env:COMPUTERNAME
        }
        $tpc = Get-TfsTeamProjectCollection -Current
        $buildServer = $tpc.GetService([type]"Microsoft.TeamFoundation.Build.Client.IBuildServer")
    }

    Process
    {
        $ToolsVersion = 12
        $EnvVarName = "TFSBUILDSERVICEHOST.2013"
        $BuildEndpointTemplate = "Build/v5.0/Services"

        if (-not $TfsInstallationPath) 
        {
            $TfsInstallationPath = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\TeamFoundationServer\12.0").InstallPath
        }

        if (-not $BuildServiceName)
        {
            $BuildServiceName = "TfsBuildService-$collectionName"
        }

        $ServiceBinPath = "$TfsInstallationPath\Tools\TfsBuildServiceHost.exe /NamedInstance:$BuildServiceName"
        $ServiceDisplayName = "Visual Studio Team Foundation Build Service Host ($collectionName)"


        _CreateService $BuildServiceName $ServiceDisplayName $ServiceBinPath $Force.IsPresent
        _RegisterBuildServiceHost $TfsInstallationPath $Force.IsPresent
        _CreateBuildController
        _CreateBuildAgent
    }
}

#======================
# Helper methods
#======================

Function _CreateService
{
    Param
    (
        $BuildServiceName,
        $ServiceDisplayName,
        $ServiceBinPath,
        $Force
    )

    if (Get-Service $BuildServiceName -ErrorAction SilentlyContinue)
    {
        if (-not $Force)
        {
            throw "Build Service $BuildServiceName is already registered. To re-register a build service, use the -Force switch"
        }

        sc.exe delete $BuildServiceName | Out-Null
    }

    $svc = New-Service -Name $BuildServiceName -DisplayName $ServiceDisplayName -BinaryPathName $ServiceBinPath
    
    sc.exe failure $BuildServiceName reset= 86400 actions= restart/60000 | Out-Null
}

Function _RegisterBuildServiceHost
{
    Param
    (
        $TfsInstallationPath,
        $Force
    )

    _LoadPrivateAssemblies $TfsInstallationPath

    $flags = [System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::SetProperty -bor [System.Reflection.BindingFlags]::Static
    [Microsoft.TeamFoundation.Build.Config.BuildServiceHostProcess].InvokeMember("NamedInstance", $flags, $null, $null, [object[]] $BuildServiceName)

    if ($buildServer.QueryBuildServiceHosts($BuildServiceName).Length -ne 0)
    {
        if (-not $Force)
        {
            throw "Build Service $BuildServiceName is already registered. To re-register a build service, use the -Force switch"
        }
        
        [Microsoft.TeamFoundation.Build.Config.BuildServiceHostUtilities]::Unregister($true, $true)
    }

    $port = _GetNextAvailablePort
    $endpoint = "http://$env:COMPUTERNAME:$port/$BuildEndpointTemplate"
    $serviceHost = $buildServer.CreateBuildServiceHost($BuildServiceName, $endpoint)
    $serviceHost.Save()

    $userName = $ServiceCredential.UserName
    $password = $ServiceCredential.GetNetworkCredential().Password

    [Microsoft.TeamFoundation.Build.Config.BuildServiceHostUtilities]::Register($serviceHost, $userName, $password)
}

Function _GetNextAvailablePort
{
    $RegistryPath = "HKLM:\SOFTWARE\Microsoft\VisualStudio\$ToolsVersion.0\TeamFoundation\Build"
    $PortsTaken = Get-ChildItem -Path $RegistryPath | Get-ItemProperty -Name Endpoint | ForEach { [uri] $_.Endpoint } | Select -ExpandProperty Port

    $StartPort = 9191
    $MaxPorts = $StartPort + $PortsTaken.Length

    for ($i = $StartPort; $i -le $MaxPorts; $i++)
    { 
        if ($PortsTaken -notcontains $i)
        {
            $Port = $i
            break
        }
    }

    return $Port
}

Function _LoadPrivateAssemblies
{
    Param
    (
        $TfsInstallationPath
    )

    $AssemblyPath = "$TfsInstallationPath\Tools\Microsoft.TeamFoundation.Build.Config.dll"
    Add-Type -Path $AssemblyPath
}

Function _GetRegValue
{
    Param
    (
        $KeyPath, $ValueName, $ComputerName
    )

    Process
    {
        if ((-not $ComputerName) -or ($ComputerName -eq $env:COMPUTERNAME))
        {
            return Get-ChildItem -Path $KeyPath | Get-ItemProperty -Name $ValueName
        }

        $KeyMapping = @{
            HKCU = "CurrentUser";
            HKLM = "LocalMachine"
        }

        $RootKey = ($KeyPath -split ":\")[0]
        $Path = ($KeyPath -split ":\")[1]
        
        $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($KeyMapping[$RootKey], $ComputerName)
        $RegKey= $Reg.OpenSubKey($Path)
        $Value = $RegKey.GetValue($ValueName)

        $RegKey.Close()
        $Reg.Close()

        return $Value
    }
}
<#
 
.SYNOPSIS
    Queues a XAML Build.
 
.PARAMETER BuildDefinition
    Build Definition Name that you want to queue.
 
.PARAMETER Project
    Specifies either the name of the Team Project or a previously initialized Microsoft.TeamFoundation.WorkItemTracking.Client.Project object to connect to. If omitted, it defaults to the connection opened by Connect-TfsTeamProject (if any).

For more details, see the Get-TfsTeamProject cmdlet.
 
.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.
 
.EXAMPLE
    Start-TfsBuild -BuildDefinition "My Build Definition" -Project "MyTeamProject"
    This example queue a Build Definition "My Build Definition" of Team Project "MyTeamProject".
 
#>

Function Start-TfsXamlBuild
{
    Param
    (
        [Parameter(Mandatory=$true, Position=0)]
        [object] 
        $BuildDefinition,

        [Parameter(ValueFromPipeline=$true, Mandatory=$true)]
        [object]
        [ValidateNotNull()]
        [ValidateScript({($_ -is [string]) -or ($_ -is [Microsoft.TeamFoundation.WorkItemTracking.Client.Project])})] 
        $Project,

        [Parameter()]
        [object]
        $Collection,

        [Parameter()]
        [string]
        [ValidateSet("LatestOnQueue", "LatestOnBuild", "Custom")]
        $GetOption = "LatestOnBuild",

        [Parameter()]
        [string]
        $GetVersion,

        [Parameter()]
        [string]
        $DropLocation,

        [Parameter()]
        [hashtable]
        $Parameters
    )

    Process
    {

        $tp = Get-TfsTeamProject $Project $Collection
        $tpc = $tp.Store.TeamProjectCollection

        $buildServer = $tpc.GetService([type]"Microsoft.TeamFoundation.Build.Client.IBuildServer")

        if ($BuildDefinition -is [Microsoft.TeamFoundation.Build.Client.IBuildDefinition])
        {
            $buildDef = $BuildDefinition
        }
        else
        {
            $buildDef = $buildServer.GetBuildDefinition($tp.Name, $BuildDefinition);
        }

        $req = $buildDef.CreateBuildRequest()
        $req.GetOption = [Microsoft.TeamFoundation.Build.Client.GetOption] $GetOption;

        if ($GetOption -eq "Custom")
        {
            $req.CustomGetVersion = $GetVersion
        }

        if ($DropLocation)
        {
            $req.DropLocation = $DropLocation
        }

        $buildServer.QueueBuild($req)
    }
}