Sample/CreateReleaseAnnotation.ps1

# Sample usage .\CreateReleaseAnnotation.ps1 -applicationId "<appId>" -apiKey "<apiKey>" -releaseName "<releaseName>" -releaseProperties @{"ReleaseDescription"="Release with annotation";"TriggerBy"="John Doe"} -eventDateTime "2016-07-07T06:23:44"


#Requires -version 3

#Suppress warning for select
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingCmdletAliases', 'Select')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingCmdletAliases', 'ogv')]

param()

function GetRequestUrlFromFwLink($fwLink) {
    # background info on how fwlink works: After you submit a web request, many sites redirect through a series of intermediate pages before you finally land on the destination page.
    # So when calling Invoke-WebRequest, the result it returns comes from the final page in any redirect sequence. Hence, I set MaximumRedirection to 0, as this prevents the call to
    # be redirected. By doing this, we get a resposne with status code 302, which indicates that there is a redirection link from the response body. We grab this redirection link and
    # construct the url to make a release annotation.
    # Here's how this logic is going to works
    # 1. Client send http request, such as: http://go.microsoft.com/fwlink/?LinkId=625115
    # 2. FWLink get the request and find out the destination URL for it, such as: http://www.bing.com
    # 3. FWLink generate a new http response with status code “302” and with destination URL “http://www.bing.com”. Send it back to Client.
    # 4. Client, such as a powershell script, knows that status code “302” means redirection to new a location, and the target location is “http://www.bing.com”
    
    $request = Invoke-WebRequest -Uri $fwLink -MaximumRedirection 0 -UseBasicParsing -ErrorAction Ignore
    if ($request.StatusCode -eq "302") {
        #Return the(first) redirect URL
        return $request.Headers.Location
    }
    return $null
}

#
function CreateAnnotation {
[CmdletBinding()]
Param (
    #The AI Application ID (not the iKey)
    [string]$applicationId,
    #An API Key to th AI Application
    [string]$APIKey,
    #The body with the AI Annotation detals
    [string]$bodyJson
)    
    #Initialize
    $retries = 1

    #Send the APIKey in the header for the webrequest
    $headers= @{ "X-AIAPIKEY" = $apiKey}

    #Find the AI Service URL URL
    $AIServiceUrl = GetRequestUrlFromFwLink("http://go.microsoft.com/fwlink/?prd=11901&pver=1.0&sbp=Application%20Insights&plcid=0x409&clcid=0x409&ar=Annotations&sar=Create%20Annotation")
    if ($AIServiceUrl -eq $null) {
        Throw "Failed to find the redirect link to create a release annotation"
    } # Or just assume : "https://aigs1.aisvc.visualstudio.com"

    #Process
    while ($retries -lt 6) {

        $location = "$AIServiceUrl/applications/$applicationId/Annotations?api-version=2015-11"
        Write-Verbose "Create Annotation using : $location" 

        $ErrorStatus = $null
        $ErrorStatusDescription = $null
        $result = $null
        #try to create the Annotation
        try {
            $result = Invoke-WebRequest -Uri $location -Method Put -Body $bodyJson -Headers $headers `
                                        -ContentType "application/json; charset=utf-8" -UseBasicParsing
            if ($result.StatusCode -eq 200)  {
                return $result
            }
        } catch {
            #Construct error message
            if ($_.Exception.Response) {
                $ErrorStatus = $_.Exception.Response.StatusCode.value__
                $ErrorStatusDescription = $_.Exception.Response.StatusDescription
            }
            else {
                $ErrorStatus = "Exception"
                $ErrorStatusDescription = $_.Exception.Message
            }            
            $Message = "Failed to create an annotation. Error {0}, Description: {1}." -f  $Result, $ResultDescription
            Throw $Message
        }

        #Check if the error was a permanent error type based on the result code,
        # and break out of the waitloop when : conflict or unauthorized or not found
        if ($result.StatusCode -in 409,404,401) {
            $Message = "Failed to create an annotation. Error {1}, Description: {2}." -f  $Result.StatusCode, $Result.StatusDescription
            Throw $Message
        }   
        $retries = $retries + 1
        Write-Verbose "waiting to retry: $retries"            
        Start-Sleep -Milliseconds 500
    }
    #After 6 tries , stop trying
    $Message = "Timeout while attempting to create an annotation. LastError {1}, Description: {2}." -f  $Result.StatusCode, $Result.StatusDescription
    throw $Message
}

Function CreateReleaseAnnotation { 
    param(
        [parameter(Mandatory = $true)]
        [string]$applicationId,
        [parameter(Mandatory = $true)]
        [string]$apiKey,


        [parameter(Mandatory = $true)]
        [string]$releaseName,
        #A hashtable of additional properties to associate with the Release
        [parameter(Mandatory = $false)]
        [Hashtable]$releaseProperties = @{},

        #Event date/time , defaults to current time
        [parameter(Mandatory = $false)]
        [DateTime]$eventDateTime = (Get-Date ),

        #A GUID to identifiy the annotation, default = new-guid
        [GUID]$AnnotationID = [GUID]::NewGuid(),

        #A GUID to identifiy a related annotation, default = Null;
        [System.Nullable``1[[System.GUID]]] #Nullable GUID
        $RelatedAnnotationID  , 
        #The Annotation's category, default = 'Deployment'
        [string]$Category = 'Deployment',
        #Return the Annotation details that were sent
        [switch]$PassThrough
    )
    #Input validation
    # input must be between NOW and 90 days back maximum
    $Now = Get-Date
    if ($eventDateTime -lt $Now.AddDays(-90) -Or $eventDateTime -gt $Now) {
        throw 'eventDateTime value must be between NOW and 90 days back maximum'
    }

    #Start with an empty hashtable
    $requestBody = @{}
    $requestBody.Id             = $AnnotationID
    $requestBody.AnnotationName = $releaseName
    $requestBody.EventTime      = $eventDateTime.ToString("s")
    $requestBody.Category       = $Category
    $requestBody.RelatedAnnotation=$RelatedAnnotationID
    #Add release name to any passed in properties
    $releaseProperties.Add("ReleaseName", $releaseName)
    $requestBody.Properties     = Convertto-json -InputObject $releaseProperties -Compress 
    #Note : This results in a non standard JSON document , but this is apparently the required structure which the service accepts
    
    #Convert the information to a json document, with one of the attributes containing a seperate JSON document
    $bodyJson = $requestBody | ConvertTo-Json -Compress    
  
    $Result = createAnnotation -applicationId $applicationId -bodyJson $bodyJson -APIKey $apiKey
    if ($Result) {
        #Suppress warning
        [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
      $ReleaseInfo = $result.Content | Convertfrom-json | Select -First 1
        $Message = "Release annotation created. Id: {0}." -f $requestBody.Id
        Write-Verbose $Message
    }
    if ($PassThrough) {
        #And return the constructed
        Return $requestBody
    }
}