VSTS.psm1

function New-VstsSession {
    param([Parameter()]$AccountName, 
          [Parameter(Mandatory=$true)]$User, 
          [Parameter(Mandatory=$true)]$Token,
          [Parameter()][string]$Collection = 'DefaultCollection',
          [Parameter()][string]$Server = 'visualstudio.com',
          [Parameter()][ValidateSet('HTTP', 'HTTPS')]$Scheme = 'HTTPS'
          )

    [PSCustomObject]@{
        AccountName = $AccountName
        User = $User
        Token = $Token
        Collection = $Collection
        Server = $Server
        Scheme = $Scheme
    }
}

function Invoke-VstsEndpoint {
    param(
          [Parameter(Mandatory=$true)]$Session, 
          [Hashtable]$QueryStringParameters, 
          [string]$Project,
          [Uri]$Path, 
          [string]$ApiVersion='1.0', 
          [ValidateSet('GET', 'PUT', 'POST', 'DELETE', 'PATCH')]$Method='GET',
          [string]$Body)

    $queryString = [System.Web.HttpUtility]::ParseQueryString([string]::Empty)
   
    if ($QueryStringParameters -ne $null)
    {
        foreach($parameter in $QueryStringParameters.GetEnumerator())
        {
            $queryString[$parameter.Key] = $parameter.Value
        }
    }

    $queryString["api-version"] = $ApiVersion
    $queryString = $queryString.ToString();

    $authorization = Get-VstsAuthorization -User $Session.User -Token $Session.Token
    if ([String]::IsNullOrEmpty($Session.AccountName))
    {
        $UriBuilder = New-Object System.UriBuilder -ArgumentList "$($Session.Scheme)://$($Session.Server)"
    }
    else
    {
        $UriBuilder = New-Object System.UriBuilder -ArgumentList "$($Session.Scheme)://$($Session.AccountName).visualstudio.com"
    }
    $Collection = $Session.Collection
    
    $UriBuilder.Query = $queryString
    if ([String]::IsNullOrEmpty($Project))
    {
        $UriBuilder.Path = "$Collection/_apis/$Path"
    }
    else 
    {
        $UriBuilder.Path = "$Collection/$Project/_apis/$Path"
    }

    $Uri = $UriBuilder.Uri

    Write-Verbose "Invoke URI [$uri]"

    $ContentType = 'application/json'
    if ($Method -eq 'PUT' -or $Method -eq 'POST' -or $Method -eq 'PATCH')
    {
        if ($Method -eq 'PATCH')
        {
            $ContentType = 'application/json-patch+json'
        }

        Invoke-RestMethod $Uri -Method $Method -ContentType $ContentType -Headers @{Authorization=$authorization} -Body $Body
    }
    else
    {
        Invoke-RestMethod $Uri -Method $Method -ContentType $ContentType -Headers @{Authorization=$authorization} 
    }
}

function Get-VstsAuthorization {
<#
    .SYNOPSIS
        Generates a VSTS authorization header value from a username and Personal Access Token.
#>

    param($user, $token)

    $Value = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user, $token)))
    ("Basic {0}" -f $value)
}

function Get-VstsProject {
<#
    .SYNOPSIS
        Get projects in a VSTS account.
#>

    param(
        [Parameter(Mandatory, ParameterSetname='Account')]$AccountName, 
        [Parameter(Mandatory, ParameterSetname='Account')]$User, 
        [Parameter(Mandatory, ParameterSetname='Account')]$Token, 
        [Parameter(Mandatory, ParameterSetname='Session')]$Session, 
        [string]$Name)
    
    if ($PSCmdlet.ParameterSetName -eq 'Account')
    {
        $Session = New-VSTSSession -AccountName $AccountName -User $User -Token $Token
    }

    $Value = Invoke-VstsEndpoint -Session $Session -Path 'projects' 

    if ($PSBoundParameters.ContainsKey("Name"))
    {
        $Value.Value | Where Name -eq $Name
    }
    else
    {
        $Value.Value 
    }
}

function Wait-VSTSProject {
    param([Parameter(Mandatory)]$Session, 
          [Parameter(Mandatory)]$Name, 
          $Attempts = 30, 
          [Switch]$Exists)

    $Retries = 0
    do {
        #Takes a few seconds for the project to be created
        Start-Sleep -Seconds 2

        $TeamProject = Get-VSTSProject -Session $Session -Name $Name

        $Retries++
    } while ((($TeamProject -eq $null -and $Exists) -or ($TeamProject -ne $null -and -not $Exists)) -and $Retries -le $Attempts)

    if (($TeamProject -eq $null -and $Exists) -or ($TeamProject -ne $null -and -not $Exists) ) 
    {
        throw "Failed to create team project!" 
    }
}

function New-VstsProject 
{
    <#
        .SYNOPSIS
            Creates a new project in a VSTS account
    #>

    param(
    [Parameter(Mandatory, ParameterSetname='Account')]$AccountName, 
    [Parameter(Mandatory, ParameterSetname='Account')]$User, 
    [Parameter(Mandatory, ParameterSetname='Account')]$Token, 
    [Parameter(Mandatory, ParameterSetname='Session')]$Session, 
    [Parameter(Mandatory)]$Name, 
    [Parameter()]$Description, 
    [Parameter()][ValidateSet('Git')]$SourceControlType = 'Git',
    [Parameter()]$TemplateTypeId = '6b724908-ef14-45cf-84f8-768b5384da45',
    [Switch]$Wait)

    if ($PSCmdlet.ParameterSetName -eq 'Account')
    {
        $Session = New-VSTSSession -AccountName $AccountName -User $User -Token $Token
    }

    $Body = @{
        name = $Name
        description = $Description
        capabilities = @{
            versioncontrol = @{
                sourceControlType = $SourceControlType
            }
            processTemplate = @{
                templateTypeId = $TemplateTypeId
            }
        }
    } | ConvertTo-Json

    Invoke-VstsEndpoint -Session $Session -Path 'projects' -Method POST -Body $Body

    if ($Wait)
    {
        Wait-VSTSProject -Session $Session -Name $Name -Exists
    }
}

function Remove-VSTSProject {
    <#
        .SYNOPSIS
            Deletes a project from the specified VSTS account.
    #>

    param(
        [Parameter(Mandatory, ParameterSetname='Account')]$AccountName, 
        [Parameter(Mandatory, ParameterSetname='Account')]$User, 
        [Parameter(Mandatory, ParameterSetname='Account')]$Token, 
        [Parameter(Mandatory, ParameterSetname='Session')]$Session,  
        [Parameter(Mandatory)]$Name,
        [Parameter()][Switch]$Wait)

        if ($PSCmdlet.ParameterSetName -eq 'Account')
        {
            $Session = New-VSTSSession -AccountName $AccountName -User $User -Token $Token
        }

        $Id = Get-VstsProject -Session $Session -Name $Name | Select -ExpandProperty Id

        if ($Id -eq $null)
        {
            throw "Project $Name not found in $AccountName."
        }

        Invoke-VstsEndpoint -Session $Session -Path "projects/$Id" -Method DELETE

        if ($Wait)
        {
            Wait-VSTSProject -Session $Session -Name $Name
        }
}

function Get-VstsWorkItem {
<#
    .SYNOPSIS
        Get work items from VSTS
#>

    param(
    [Parameter(Mandatory, ParameterSetname='Account')]$AccountName, 
    [Parameter(Mandatory, ParameterSetname='Account')]$User, 
    [Parameter(Mandatory, ParameterSetname='Account')]$Token, 
    [Parameter(Mandatory, ParameterSetname='Session')]$Session, 
    [Parameter(Mandatory)]$Id)

    if ($PSCmdlet.ParameterSetName -eq 'Account')
    {
        $Session = New-VSTSSession -AccountName $AccountName -User $User -Token $Token
    }

    Invoke-VstsEndpoint -Session $Session -Path 'wit/workitems' -QueryStringParameters @{ids = $id}
}

function New-VstsWorkItem {
<#
    .SYNOPSIS
        Create new work items in VSTS
#>

    param(
    [Parameter(Mandatory, ParameterSetname='Account')]
    $AccountName, 
    [Parameter(Mandatory, ParameterSetname='Account')]
    $User, 
    [Parameter(Mandatory, ParameterSetname='Account')]
    $Token, 
    [Parameter(Mandatory, ParameterSetname='Session')]
    $Session, 
    [Parameter(Mandatory)]
    $Project,
    [Parameter()]
    [Hashtable]
    $PropertyHashtable, 
    [Parameter(Mandatory)]
    [string]
    $WorkItemType
    )

    if ($PSCmdlet.ParameterSetName -eq 'Account')
    {
        $Session = New-VSTSSession -AccountName $AccountName -User $User -Token $Token
    }

    if ($PropertyHashtable -ne $null)
    {
        $Fields = foreach($kvp in $PropertyHashtable.GetEnumerator())
        {
            [PSCustomObject]@{
                op = 'add'
                path = '/fields/' + $kvp.Key
                value = $kvp.value
            }
        }

        $Body = $Fields | ConvertTo-Json
    }
    else
    {
        $Body = [String]::Empty
    }

    Invoke-VstsEndpoint -Session $Session -Path "wit/workitems/`$$($WorkItemType)" -Method PATCH -Project $Project -Body $Body
}

function Get-VstsWorkItemQuery {
    <#
    .SYNOPSIS
        Returns a list of work item queries from the specified folder.
    #>

    param(
    [Parameter(Mandatory, ParameterSetname='Account')]
    $AccountName, 
    [Parameter(Mandatory, ParameterSetname='Account')]
    $User, 
    [Parameter(Mandatory, ParameterSetname='Account')]
    $Token, 
    [Parameter(Mandatory, ParameterSetname='Session')]
    $Session, 
    [Parameter(Mandatory=$true)]$Project, 
    $FolderPath)

    if ($PSCmdlet.ParameterSetName -eq 'Account')
    {
        $Session = New-VSTSSession -AccountName $AccountName -User $User -Token $Token
    }

    $Result = Invoke-VstsEndpoint -Session $Session -Project $Project -Path 'wit/queries' -QueryStringParameters @{depth=1}

    foreach($value in $Result.Value)
    {
        if ($Value.isFolder -and $Value.hasChildren)
        {
            Write-Verbose "$Value.Name"
            foreach($child in $value.Children)
            {
                if (-not $child.isFolder)
                {
                    $child
                }
            }
        }
    } 
}

function New-VstsGitRepository {
    <#
        .SYNOPSIS
            Creates a new Git repository in the specified team project.
    #>

    param(
    [Parameter(Mandatory, ParameterSetname='Account')]
    $AccountName, 
    [Parameter(Mandatory, ParameterSetname='Account')]
    $User, 
    [Parameter(Mandatory, ParameterSetname='Account')]
    $Token, 
    [Parameter(Mandatory, ParameterSetname='Session')]
    $Session, 
    [Parameter(Mandatory=$true)]
    $ProjectId,
    [Parameter(Mandatory=$true)]
    $RepositoryName)  

    if ($PSCmdlet.ParameterSetName -eq 'Account')
    {
        $Session = New-VSTSSession -AccountName $AccountName -User $User -Token $Token
    }

    $Body = @{
        Name = $RepositoryName
        Project = @{
            Id = $ProjectId
        }
    } | ConvertTo-Json

    Invoke-VstsEndpoint -Session $Session -Method POST -Path 'git/repositories' -Body $Body
}

function Get-VstsGitRepository {
    <#
        .SYNOPSIS
            Gets Git repositories in the specified team project.
    #>

        param(
        [Parameter(Mandatory, ParameterSetname='Account')]
        $AccountName, 
        [Parameter(Mandatory, ParameterSetname='Account')]
        $User, 
        [Parameter(Mandatory, ParameterSetname='Account')]
        $Token, 
        [Parameter(Mandatory, ParameterSetname='Session')]
        $Session, 
        [Parameter(Mandatory=$true)]$Project)

    if ($PSCmdlet.ParameterSetName -eq 'Account')
    {
        $Session = New-VSTSSession -AccountName $AccountName -User $User -Token $Token
    }

     $Result = Invoke-VstsEndpoint -Session $Session -Project $Project -Path 'git/repositories' -QueryStringParameters @{depth=1}
     $Result.Value              
}

function Get-VstsCodePolicy {
    <#
        .SYNOPSIS
            Get code policies for the specified project.
    #>


    param(
        [Parameter(Mandatory, ParameterSetname='Account')]
        $AccountName, 
        [Parameter(Mandatory, ParameterSetname='Account')]
        $User, 
        [Parameter(Mandatory, ParameterSetname='Account')]
        $Token, 
        [Parameter(Mandatory, ParameterSetname='Session')]
        $Session, 
        [Parameter(Mandatory=$true)]$Project)

        
    if ($PSCmdlet.ParameterSetName -eq 'Account')
    {
        $Session = New-VSTSSession -AccountName $AccountName -User $User -Token $Token
    }
              
     $Result = Invoke-VstsEndpoint -Session $Session -Project $Project -Path 'policy/configurations' -ApiVersion '2.0-preview.1'
     $Result.Value     
}

function New-VstsCodePolicy {
    <#
        .SYNOPSIS
            Creates a new Code Policy configuration for the specified project.
    #>


    param(
        [Parameter(Mandatory, ParameterSetname='Account')]
        $AccountName, 
        [Parameter(Mandatory, ParameterSetname='Account')]
        $User, 
        [Parameter(Mandatory, ParameterSetname='Account')]
        $Token, 
        [Parameter(Mandatory, ParameterSetname='Session')]
        $Session, 
        [Parameter(Mandatory=$true)]
        $Project,
        [Guid]
        $RepositoryId = [Guid]::Empty,
        [int]
        $MinimumReviewers,
        [string[]]
        $Branches)

    $RepoId = $null
    if ($RepositoryId -ne [Guid]::Empty)
    {
        $RepoId = $RepositoryId.ToString()   
    }

    $scopes = foreach($branch in $Branches)
    {
        @{
            repositoryId = $RepoId
            refName = "refs/heads/$branch"
            matchKind = "exact"
        }
    }

    $Policy = @{
        isEnabled = $true
        isBlocking = $false
        type = @{
            id = 'fa4e907d-c16b-4a4c-9dfa-4906e5d171dd'
        }
        settings = @{
            minimumApproverCount = $MinimumReviewers
            creatorVoteCounts = $false
            scope = @($scopes)
        }
    } | ConvertTo-Json -Depth 10

    if ($PSCmdlet.ParameterSetName -eq 'Account')
    {
        $Session = New-VSTSSession -AccountName $AccountName -User $User -Token $Token
    }

    Invoke-VstsEndpoint -Session $Session -Project $Project -ApiVersion '2.0-preview.1' -Body $Policy -Method POST
}