faitza.com
faitza.com
> blog_tech

Azure DevOps — Gestion des tickets et équipes en PowerShell

Calcul... powershell azure-devops rest-api automation
Azure DevOps PowerShell REST API

Introduction

Faitza_azdevops.ps1 expose sept fonctions pour piloter Azure DevOps via l'API REST : création et lecture de work items, recherche WIQL, gestion des commentaires, changement de statut et récupération des membres d'équipe. Toutes les fonctions utilisent Get-Faitza_Modul_Headers -resource "azdevops" pour l'authentification Bearer.

Prérequis

Les fonctions utilisent Get-Faitza_Modul_Headers -resource "azdevops" pour obtenir un token valide. Voir : Token Bearer pour les APIs Microsoft en PowerShell.

L'organisation Azure DevOps cible est FAITZA.

New-Faitza_azdevops_ticket

Crée un work item Azure DevOps via un PATCH JSON. Supporte les types Task, Bug, User Story, etc. Retourne l'objet complet du ticket avec une propriété GuiUrl ajoutée.

function New-Faitza_azdevops_ticket {
    [CmdletBinding()]
    param (
        $azdev_projet = "Infrastructure",
        $type = "Task",
        $title,
        $description,
        $assignedto,
        [string[]]$tags
    )

    try {
        Write-Host "$($PSStyle.Background.Red)>>> New-Faitza_azdevops_ticket <<<$($PSStyle.Reset)"

        if ([string]::IsNullOrWhiteSpace($title)) { throw "Il manque le title -title" }
        if ([string]::IsNullOrWhiteSpace($description)) { throw "Il manque la description -description" }

        $headers = Get-Faitza_Modul_Headers -resource "azdevops"
        $headers["Content-Type"] = "application/json-patch+json"

        $patch = @(
            @{ op = "add"; path = "/fields/System.Title"; value = $title },
            @{ op = "add"; path = "/fields/System.Description"; value = $description }
        )

        if ($assignedto) { $patch += @{ op = "add"; path = "/fields/System.AssignedTo"; value = $assignedto } }
        if ($tags) { $patch += @{ op = "add"; path = "/fields/System.Tags"; value = ($tags -join ";") } }

        $uri = "https://dev.azure.com/FAITZA/$azdev_projet/_apis/wit/workitems/`$$($type)?api-version=7.1"
        $response = Invoke-RestMethod -Method Post -Uri $uri -Headers $headers -Body ($patch | ConvertTo-Json -Depth 10) -ErrorAction Stop

        if (-not $response.id) { throw "Reponse API invalide" }

        Write-Host "Ticket cree ID $($response.id)"
        $guiUrl = "https://dev.azure.com/FAITZA/$azdev_projet/_workitems/edit/$($response.id)/"
        $response | Add-Member -MemberType NoteProperty -Name "GuiUrl" -Value $guiUrl -Force
        return $response
    } catch {
        throw "Erreur New-Faitza_azdevops_ticket : $_"
    } finally {
        Write-Host "$($PSStyle.Background.Red)<<< New-Faitza_azdevops_ticket >>>$($PSStyle.Reset)"
    }
}

Exemple d'utilisation

$ticket = New-Faitza_azdevops_ticket `
    -azdev_projet "Infrastructure" `
    -type         "Task" `
    -title        "Audit des comptes AD inactifs" `
    -description  "<p>Lister et désactiver les comptes sans connexion depuis 90 jours.</p>" `
    -assignedto   "[email protected]" `
    -tags         @("AD", "securite", "audit")

Write-Host "Ticket $($ticket.id) : $($ticket.GuiUrl)"

Read-Faitza_azdevops_ticket

Lit les détails d'un work item et ses commentaires. Retourne un objet @{ ticket = ...; commentaires = [...] }.

function Read-Faitza_azdevops_ticket {
    [CmdletBinding()]
    param ($project = "Infrastructure", $id)

    try {
        Write-Host "$($PSStyle.Background.Red)>>> Read-Faitza_azdevops_ticket <<<$($PSStyle.Reset)"
        if ([string]::IsNullOrWhiteSpace($id)) { throw "Il manque l'id du ticket (-id)" }

        $headers = Get-Faitza_Modul_Headers -resource "azdevops" -contentType "jsonpatch"
        $resp = Invoke-RestMethod -Method Get -Uri "https://dev.azure.com/FAITZA/$project/_apis/wit/workitems/$($id)?api-version=7.1" -Headers $headers -ErrorAction Stop
        $resp_comments = Invoke-RestMethod -Method Get -Uri "https://dev.azure.com/FAITZA/$project/_apis/wit/workitems/$($id)/comments?api-version=7.1-preview.3" -Headers $headers -ErrorAction Stop

        if (-not $resp -or -not $resp.id) { throw "Aucun ticket trouve pour: $id" }

        $formattedComments = $resp_comments.comments | ForEach-Object {
            [pscustomobject]@{ Author = $_.createdBy.displayName; Date = $_.createdDate; Comment = $_.text }
        }

        return [pscustomobject]@{
            ticket = [pscustomobject]@{
                Id           = $resp.id
                WorkItemType = $resp.fields."System.WorkItemType"
                Title        = $resp.fields."System.Title"
                State        = $resp.fields."System.State"
                AssignedTo   = $resp.fields."System.AssignedTo".displayName
                Tags         = $resp.fields."System.Tags"
                Description  = $resp.fields."System.Description"
                Comments     = $resp_comments.comments
            }
            commentaires = $formattedComments
        }
    } catch {
        throw "Erreur Read-Faitza_azdevops_ticket : $_"
    } finally {
        Write-Host "$($PSStyle.Background.Red)<<< Read-Faitza_azdevops_ticket >>>$($PSStyle.Reset)"
    }
}

Exemple d'utilisation

$result = Read-Faitza_azdevops_ticket -project "Infrastructure" -id 42

Write-Host "Titre  : $($result.ticket.Title)"
Write-Host "Statut : $($result.ticket.State)"
$result.commentaires | Format-Table Author, Date, Comment

Find-Faitza_azdevops_ticket

Recherche des tickets via une requête WIQL. Filtre par titre (-title), statut (-state) ou les deux. Exclut automatiquement les tickets Closed si aucun statut n'est précisé. Retourne la liste des IDs.

function Find-Faitza_azdevops_ticket {
    [CmdletBinding()]
    param ($project = "Infrastructure", $operator = "CONTAINS", $title, $state)

    try {
        Write-Host "$($PSStyle.Background.Red)>>> Find-Faitza_azdevops_ticket <<<$($PSStyle.Reset)"
        if (-not $title -and -not $state) { throw "Il manque des parametres : vous devez fournir un -title, un -state, ou les deux." }

        $headers = Get-Faitza_Modul_Headers -resource "azdevops"
        $queryConditions = @("[System.TeamProject] = '$project'")
        if ($title) { $queryConditions += "[System.Title] $operator '$($title.Replace("'", "''"))'" }
        if ($state) { $queryConditions += "[System.State] = '$($state.Replace("'", "''"))'" } else { $queryConditions += "[System.State] <> 'Closed'" }

        $whereClause = $queryConditions -join "`n                AND "
        $wiql = @{ query = "SELECT [System.Id] FROM WorkItems WHERE $whereClause" }
        $resp = Invoke-RestMethod -Method Post -Uri "https://dev.azure.com/FAITZA/$project/_apis/wit/wiql?api-version=7.1" -Headers $headers -Body ($wiql | ConvertTo-Json -Depth 5) -ErrorAction Stop

        if (-not $resp.workItems -or $resp.workItems.Count -eq 0) { Write-Host "Aucun ticket trouve avec ces criteres."; return $null }
        $results = @($resp.workItems | ForEach-Object { $_.id })
        Write-Host "$($results.Count) ticket(s) trouve(s) : $results"
        return $results
    } catch {
        throw "Erreur Find-Faitza_azdevops_ticket : $_"
    } finally {
        Write-Host "$($PSStyle.Background.Red)<<< Find-Faitza_azdevops_ticket >>>$($PSStyle.Reset)"
    }
}

Exemple d'utilisation

# Chercher par titre
$ids = Find-Faitza_azdevops_ticket -title "audit"

# Chercher par statut
$ids = Find-Faitza_azdevops_ticket -state "Active"

# Combiner les deux
$ids = Find-Faitza_azdevops_ticket -title "AD" -state "Active"

# Lire chaque ticket trouve
foreach ($id in $ids) {
    $ticket = Read-Faitza_azdevops_ticket -id $id
    Write-Host "$($ticket.ticket.Id) — $($ticket.ticket.Title)"
}

Find-Faitza_azdevops_user

Recherche un utilisateur dans l'organisation Azure DevOps via l'API VSSPS identities. Retourne l'ID, le display name, l'UPN et le HTML de mention (@mention).

function Find-Faitza_azdevops_user {
    [CmdletBinding()]
    param ($identity)

    try {
        Write-Host "$($PSStyle.Background.Red)>>> Find-Faitza_azdevops_user <<<$($PSStyle.Reset)"
        if ([string]::IsNullOrWhiteSpace($identity)) { throw "Il manque l'identite a chercher (-identity)" }

        $headers = Get-Faitza_Modul_Headers -resource "azdevops"
        $identity_encoded = [uri]::EscapeDataString($identity)
        $uri = "https://vssps.dev.azure.com/FAITZA/_apis/identities?searchFilter=General&filterValue=$identity_encoded&queryMembership=None&api-version=7.1"

        $resp = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -ErrorAction Stop
        if (-not $resp.value -or $resp.count -eq 0) { throw "Aucun utilisateur trouve pour: $identity" }

        $results = foreach ($user in $resp.value) {
            [pscustomobject]@{
                Id          = $user.id
                Provider    = $user.providerDisplayName
                DisplayName = $user.customDisplayName
                UniqueName  = $user.uniqueName
                IsActive    = $user.isActive
                IsContainer = $user.isContainer
                Url         = $user.url
                MentionHtml = "<a href='#' data-vss-mention='version:2.0,$($user.id)'>@$($user.customDisplayName)</a>"
            }
        }

        $results | Select-Object Id, DisplayName, UniqueName, IsActive | Format-Table -AutoSize
        return $results
    } catch {
        throw "Erreur Find-Faitza_azdevops_user : $_"
    } finally {
        Write-Host "$($PSStyle.Background.Red)<<< Find-Faitza_azdevops_user >>>$($PSStyle.Reset)"
    }
}

Exemple d'utilisation

$users = Find-Faitza_azdevops_user -identity "[email protected]"
$user = $users[0]
Write-Host "ID : $($user.Id) — Mention : $($user.MentionHtml)"

Get-Faitza_azdevops_TeamMembers

Retourne la liste des membres d'une équipe dans un projet Azure DevOps.

function Get-Faitza_azdevops_TeamMembers {
    [CmdletBinding()]
    param ($project, $team)

    try {
        Write-Host "$($PSStyle.Background.Red)>>> Get-Faitza_azdevops_TeamMembers <<<$($PSStyle.Reset)"
        if ([string]::IsNullOrWhiteSpace($project)) { throw "Il manque le projet (-project)" }
        if ([string]::IsNullOrWhiteSpace($team)) { throw "Il manque l'equipe (-team)" }

        $headers = Get-Faitza_Modul_Headers -resource "azdevops"
        $project_encoded = [uri]::EscapeDataString($project)
        $team_encoded = [uri]::EscapeDataString($team)
        $uri = "https://dev.azure.com/FAITZA/_apis/projects/$project_encoded/teams/$team_encoded/members?api-version=7.1"

        $resp = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -ErrorAction Stop
        if (-not $resp.value -or $resp.count -eq 0) { Write-Host "Aucun membre trouve pour l'equipe '$team' dans le projet '$project'."; return @() }

        $results = foreach ($member in $resp.value) {
            [pscustomobject]@{ DisplayName = $member.identity.displayName; UniqueName = $member.identity.uniqueName; Id = $member.identity.id }
        }
        Write-Host "Nombre de membres trouves : $($results.Count)"
        return $results
    } catch {
        throw "Erreur Get-Faitza_azdevops_TeamMembers : $_"
    } finally {
        Write-Host "$($PSStyle.Background.Red)<<< Get-Faitza_azdevops_TeamMembers >>>$($PSStyle.Reset)"
    }
}

Exemple d'utilisation

$membres = Get-Faitza_azdevops_TeamMembers -project "Infrastructure" -team "Infrastructure Team"
$membres | Format-Table DisplayName, UniqueName

Add-Faitza_azdevops_TicketComment

Ajoute un commentaire à un work item via le champ System.History.

function Add-Faitza_azdevops_TicketComment {
    [CmdletBinding()]
    param ($id, $comment, $azdev_projet = "Infrastructure")

    try {
        Write-Host "$($PSStyle.Background.Red)>>> Add-Faitza_azdevops_TicketComment <<<$($PSStyle.Reset)"
        if ([string]::IsNullOrWhiteSpace($id)) { throw "Il manque l'id du ticket (-id)" }
        if ([string]::IsNullOrWhiteSpace($comment)) { throw "Il manque le commentaire (-comment)" }

        $headers = Get-Faitza_Modul_Headers -resource "azdevops"
        $headers["Content-Type"] = "application/json-patch+json"
        $patch = @(@{ op = "add"; path = "/fields/System.History"; value = $comment })
        $uri = "https://dev.azure.com/FAITZA/$azdev_projet/_apis/wit/workitems/$id`?api-version=7.1"
        $response = Invoke-RestMethod -Method Patch -Uri $uri -Headers $headers -Body (ConvertTo-Json -InputObject $patch -Depth 10) -ErrorAction Stop

        if (-not $response.id -or $response.id -ne $id) { throw "Reponse API invalide" }
        Write-Host "Commentaire ajoute au ticket $id"
        return $true
    } catch {
        throw "Erreur Add-Faitza_azdevops_TicketComment : $_"
    } finally {
        Write-Host "$($PSStyle.Background.Red)<<< Add-Faitza_azdevops_TicketComment >>>$($PSStyle.Reset)"
    }
}

Exemple d'utilisation

Add-Faitza_azdevops_TicketComment `
    -id          42 `
    -comment     "Audit termine : 12 comptes desactives." `
    -azdev_projet "Infrastructure"

Set-Faitza_azdevops_Ticket

Modifie le statut d'un work item (Active, Resolved, Closed, etc.).

function Set-Faitza_azdevops_Ticket {
    [CmdletBinding()]
    param ($id, $state, $azdev_projet = "Infrastructure")

    try {
        Write-Host "$($PSStyle.Background.Red)>>> Set-Faitza_azdevops_Ticket <<<$($PSStyle.Reset)"
        if ([string]::IsNullOrWhiteSpace($id)) { throw "Il manque l'id du ticket (-id)" }
        if ([string]::IsNullOrWhiteSpace($state)) { throw "Il manque le statut (-state)" }

        $headers = Get-Faitza_Modul_Headers -resource "azdevops"
        $headers["Content-Type"] = "application/json-patch+json"
        $patch = @(@{ op = "add"; path = "/fields/System.State"; value = $state })
        $uri = "https://dev.azure.com/FAITZA/$azdev_projet/_apis/wit/workitems/$id`?api-version=7.1"
        $response = Invoke-RestMethod -Method Patch -Uri $uri -Headers $headers -Body (ConvertTo-Json -InputObject $patch -Depth 10) -ErrorAction Stop

        if (-not $response.id -or $response.id -ne $id) { throw "Reponse API invalide" }
        Write-Host "Statut du ticket $id modifie vers $state"
        return $true
    } catch {
        throw "Erreur Set-Faitza_azdevops_Ticket : $_"
    } finally {
        Write-Host "$($PSStyle.Background.Red)<<< Set-Faitza_azdevops_Ticket >>>$($PSStyle.Reset)"
    }
}

Exemple d'utilisation

Set-Faitza_azdevops_Ticket -id 42 -state "Resolved"
Set-Faitza_azdevops_Ticket -id 42 -state "Closed"

// Commentaires

Aucun commentaire pour l'instant.