Public/Approve-PSUADOPullRequest.ps1

function Approve-PSUADOPullRequest {
    <#
    .SYNOPSIS
        Approves a pull request in Azure DevOps using REST API.
 
    .DESCRIPTION
        This function approves a pull request in Azure DevOps by its ID. It can approve with different vote values
        and optional comments. You can specify the repository details or use auto-detection from git remote.
 
    .PARAMETER Organization
        (Optional) The Azure DevOps organization name under which the project resides.
        Default value is auto-detected from git remote origin URL or $env:ORGANIZATION.
 
    .PARAMETER Project
        (Optional) The Azure DevOps project name containing the repository.
        Default value is auto-detected from git remote origin URL.
 
    .PARAMETER Repository
        (Optional) The repository name containing the pull request.
        Default value is auto-detected from git remote origin URL.
 
    .PARAMETER PullRequestId
        (Mandatory) The ID of the pull request to approve.
 
    .PARAMETER Vote
        (Optional) The approval vote value:
        - 10: Approved
        - 5: Approved with suggestions
        - 0: No vote
        - -5: Waiting for author
        - -10: Rejected
        Default value is 10 (Approved).
 
    .PARAMETER Comment
        (Optional) Comment to add with the approval.
 
    .PARAMETER PAT
        (Optional) Personal Access Token for Azure DevOps authentication.
        Default value is $env:PAT. Set using: Set-PSUUserEnvironmentVariable -Name "PAT" -Value "value_of_PAT"
 
    .EXAMPLE
        Approve-PSUADOPullRequest -PullRequestId 123
 
        Approves pull request with ID 123 using auto-detected organization, project, and repository.
 
    .EXAMPLE
        Approve-PSUADOPullRequest -Organization "myorg" -Project "myproject" -Repository "myrepo" -PullRequestId 123 -Vote 5 -Comment "Looks good with minor suggestions"
 
        Approves pull request with ID 123 with suggestions and a comment.
 
    .EXAMPLE
        Approve-PSUADOPullRequest -PullRequestId 123 -Vote -5 -Comment "Please address the unit test failures"
 
        Sets pull request to "Waiting for author" status with a comment.
 
    .OUTPUTS
        [PSCustomObject]
 
    .NOTES
        Author: Lakshmanachari Panuganti
        Date: 19th August 2025
        Requires: Azure DevOps Personal Access Token with appropriate permissions
 
    .LINK
        https://github.com/lakshmanachari-panuganti/OMG.PSUtilities/tree/main/OMG.PSUtilities.AzureDevOps
        https://www.linkedin.com/in/lakshmanachari-panuganti/
        https://www.powershellgallery.com/packages/OMG.PSUtilities.AzureDevOps
        https://learn.microsoft.com/en-us/rest/api/azure/devops/git/pull-request-reviewers/create-pull-request-reviewer
    #>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSAvoidUsingWriteHost',
        '',
        Justification = 'This is intended for this function to display formatted output to the user on the console'
    )]
    param (
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$Organization = $(if ($env:ORGANIZATION) { $env:ORGANIZATION } else {
            git remote get-url origin 2>$null | ForEach-Object {
                if ($_ -match 'dev\.azure\.com/([^/]+)/') { $matches[1] }
            }
        }),

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$Project = $(git remote get-url origin 2>$null | ForEach-Object {
            if ($_ -match 'dev\.azure\.com/[^/]+/([^/]+)/_git/') { 
                $matches[1]
            }
        }),

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$Repository = $(git remote get-url origin 2>$null | ForEach-Object {
            if ($_ -match '/_git/([^/]+?)(?:\.git)?/?$') { $matches[1] }
        }),

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [int]$PullRequestId,

        [Parameter()]
        [ValidateSet(10, 5, 0, -5, -10)]
        [int]$Vote = 10,

        [Parameter()]
        [string]$Comment,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$PAT = $env:PAT
    )

    process {
        try {
            # Validate required parameters that have auto-detection
            if (-not $Organization) {
                throw "Organization parameter is required. Set it using: Set-PSUUserEnvironmentVariable -Name 'ORGANIZATION' -Value 'your-org' or ensure you're in a git repository with Azure DevOps remote."
            }
            
            if (-not $Project) {
                throw "Project parameter is required. Either specify it explicitly or ensure you're in a git repository with Azure DevOps remote URL."
            }
            
            if (-not $Repository) {
                throw "Repository parameter is required. Either specify it explicitly or ensure you're in a git repository with Azure DevOps remote URL."
            }

            # Get repository ID
            $repos = Get-PSUADORepositories -Project $Project -Organization $Organization -PAT $PAT
            $matchedRepo = $repos | Where-Object { $_.Name -eq $Repository }
            if (-not $matchedRepo) {
                throw "Repository '$Repository' not found in project '$Project'."
            }
            $repositoryId = $matchedRepo.Id

            # Compose authentication header
            $headers = Get-PSUAdoAuthHeader -PAT $PAT

            # Get current user information for the reviewer
            $userUri = "https://dev.azure.com/$Organization/_apis/profile/profiles/me?api-version=7.0"
            $userProfile = Invoke-RestMethod -Method Get -Uri $userUri -Headers $headers -ErrorAction Stop

            # Prepare the reviewer body
            $body = @{
                vote = $Vote
            }

            if ($Comment) {
                $body.comment = $Comment
            }

            $bodyJson = $body | ConvertTo-Json -Depth 10

            $escapedProject = [uri]::EscapeDataString($Project)
            $reviewerId = $userProfile.id
            $uri = "https://dev.azure.com/$Organization/$escapedProject/_apis/git/repositories/$repositoryId/pullrequests/$PullRequestId/reviewers/$reviewerId" + "?api-version=7.0"
            
            # Determine vote text for display
            $voteText = switch ($Vote) {
                10 { "Approved" }
                5 { "Approved with suggestions" }
                0 { "No vote" }
                -5 { "Waiting for author" }
                -10 { "Rejected" }
                default { "Vote: $Vote" }
            }

            Write-Verbose "Setting approval for pull request ID: $PullRequestId in project: $Project"
            Write-Verbose "Repository: $Repository ($repositoryId)"
            Write-Verbose "Vote: $Vote ($voteText)"
            Write-Verbose "API URI: $uri"

            $response = Invoke-RestMethod -Method Put -Uri $uri -Headers $headers -Body $bodyJson -ContentType "application/json" -ErrorAction Stop
            
            $WebUrl = "https://dev.azure.com/$Organization/$escapedProject/_git/$Repository/pullrequest/$PullRequestId"
            Write-Host "Pull Request ID $PullRequestId review submitted: $voteText" -ForegroundColor Green
            Write-Host "PR URL: $WebUrl" -ForegroundColor Cyan

            if ($Comment) {
                Write-Host "Comment: $Comment" -ForegroundColor Yellow
            }

            # Return structured result
            [PSCustomObject]@{
                Id              = $PullRequestId
                Vote            = $response.vote
                VoteText        = $voteText
                Comment         = $Comment
                ReviewerId      = $response.id
                ReviewerName    = $response.displayName
                ReviewerEmail   = $response.uniqueName
                IsRequired      = $response.isRequired
                Organization    = $Organization
                Project         = $Project
                Repository      = $Repository
                WebUrl          = $WebUrl
                PSTypeName      = 'PSU.ADO.PullRequestApproval'
            }
        }
        catch {
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
}