Blog Post

Azure Integration Services Blog
3 MIN READ

Attaching a file to a DevOps Work Item

Pedro_M_Almeida's avatar
Mar 24, 2023

In some automations, you may require to attach a file to a work item in DevOps.

 

For example, you may want to send an email to a Distribution List, and with the email content and attachments, create a work item based on the subject or simply update the work item with more information.

 

The connector actions to update the Work Item are very much straight through and will not be approached in this post.

 

But upload and attaching a file to this work item is not documented to its full extent. Actually, the documentation is somewhat elusive on the actual needs of the Action, but after this, it will make sense.

 

In my example, I read an attachment from an email and then uploaded it to DevOps.

 

 

Adding the attachment to the work item consists of two steps:

  • Uploading the File
  • Linking it to the Work Item.

 

Both these steps will use the "Send an HTTP request to Azure DevOps" action.

 

As per the documentation, you can create file attachments using "Send an HTTP request to Azure DevOps" action. To do this:

 

  • Convert file content to a Base64 string and put it in "Body" parameter.
  • Set "Body is Base64" parameter to Yes.

 

But the action itself does not show the exact needs in the parameters. For example, the "Relative URI" give an example of {project}/{team}/_apis/wit/templates?api-version=5.0-preview.1.

 

Send an HTTP request to Azure DevOps - Azure DevOps - Connectors | Microsoft Learn

 

But if you try with just this part of the URL, it will fail with Unauthorized or NotFound.

 

 

The URI must be:

https://dev.azure.com/{organization}/{project}/_apis/wit/attachments?fileName={fileName}&uploadType={uploadType}&areaPath={areaPath}&api-version=6.0

 

 

I used the API version 7.1-preview3, but you can try this with other versions.

The output bytes from the attachment need to be read as binary, so you need to use the expression to convert the body.  With this, you can just set the content-type to what the Get attachment output sends you, as it already inferred it.

 

The flag Body is Base64 needs to be passed as "Yes".

 

Remember that you must send the Content of the output, not the full body. We just need the Content bytes.

 

 

This POST will upload the file to DevOps.

 

Keep in mind that you can set the content-type to a fixed value, BUT it may not adjust to every situation. You can restrict your integration to a certain type of files, but if you want to integrate everything, you can choose to grab the Content-type directly from the operation that gets the contents of the file you want to upload.

 

 

It will return two properties that we will use, mainly the URL. We need it to PATCH and append it to the Work Item.

As I already knew the output, I used the output directly in the JSON body, and selected the URL property.

I could have parsed the JSON first, but as I already knew it, I skipped this step.

 

 

This is the full input to this action (it's still "Send HTTP Request to DevOps")

{

    "inputs": {

        "host": {

            "connection": {

                "referenceName": "visualstudioteamservices"

            }

        },

        "method": "post",

        "body": {

            "Method": "PATCH",

            "Uri": "https://dev.azure.com/{team}/{project}/_apis/wit/workItems/{Work Item ID}?api-version=6.0",

            "Headers": {

                "Content-Type": "application/json-patch+json"

            },

            "Body": "[

{

"op": "add",

"path": "/relations/-",

"value": {

"rel": "AttachedFile",

"url": "@{body('Upload_File')?['url']}",

"attributes": {

"comment": "Spec for the work"

}

}

}

]",

            "IsBase64": false

        },

        "path": "/httprequest",

        "queries": {

            "account": "pedroalmeidaXXXX"

        }

    }

}

 

You can and should change the comment, but not the other fields. You can see in the body the use of the previous action response and getting the URL property.

Also, the JSON must be properly formatted, so do keep in mind that you may see errors from DevOps if it's not properly formatted.

Updated Feb 20, 2023
Version 1.0
  • miguelgomes740's avatar
    miguelgomes740
    Copper Contributor

    Hi Pedro,

     

    I've tried a very similar approach to this one (maybe perhaps because I'm on a different version of PowerAutomate), but on the 3rd Send HTTP Request to DEVops (being the 1st one the create work item, and the 2nd one adding the attachments), I have a different result.

    This is the PATCH Action:

     

     

    this is the result:

     

    even though the attachments were successfully uploaded:

     

    Thank you!

    Miguel

  • Hi miguelgomes740 ,

     

    Not found means 2 things: either the Work Item ID is not correct, or the URL provided is not for the recently uploaded item.

     

    Also, make sure that your Input is properly formated JSON, I don't think it would affect the request.

     

    From your image, I think that the issue is the RelativeURL, I don't see the https:// part, and that does affect the request. Are you sure you're passing the full URL for the DevOps WorkItem?

     

     

  • miguelgomes740's avatar
    miguelgomes740
    Copper Contributor

    Thank you for answering!

     

    I think the issue is with the upload of the attachments, even though I see a 201 created result:

    Request:
    {
        "host": {
            "connectionReferenceName""shared_visualstudioteamservices",
            "operationId""HttpRequest"
        },
        "parameters": {
            "account""xxxx",
            "parameters/Method""POST",
            "parameters/Headers": {
                "Content-Type""application/pdf"
            },
            "parameters/Body"null,
            "parameters/IsBase64"true
        }
    }
     
    Response:
    {
        "statusCode"201,
        "headers": {
            "Pragma""no-cache",
    ....
    ....
    ....
            "Expires""-1"
        },
        "body": {
            "id""bf0e589d-8c07-48e4-bc4e-da523ec02ffd",
        }
    }
    (I cant access the PDF returned in the "url" parameter)
     
    I have this in the Send HTTP to DEVOPS Action to add the attachments:
    {
        "inputs": {
            "host": {
                "connectionName""shared_visualstudioteamservices",
                "operationId""HttpRequest",
                "apiId""/providers/Microsoft.PowerApps/apis/shared_visualstudioteamservices"
            },
            "parameters": {
                "account""xxxxxx",
                "parameters/Method""POST",
                "parameters/Headers": {
                    "Content-Type""@{items('Apply_to_each_2')?['contentType']}"
                },
                "parameters/Body""@items('Apply_to_each_2')?['contentBytes']",
                "parameters/IsBase64"true
            },
            "authentication""@parameters('$authentication')"
        },
        "metadata": {
            "operationMetadataId""557ad5ce-9834-4189-b6b3-909f1ffa6d65"
        }
    }
     
  • From what I see there, there's a few issues:

     

    - in the request to upload the file, you're passing an empty body. This should create the file, but as it's empty, the server may delete it, I'm not sure about it.

    - as you can't access it, that's a sign that the upload is incorrect

    - in the last action,   "Content-Type""conte@{items('Apply_to_each_2')?['contentType']}" is incorrect. you should just be sending @items('Apply_to_each_2')?['contentType'], unless this is something specific for PowerAutomate.

    - not knowing how you're getting the file, please ensure that the ContentBytes does contain what you need. It seems that it's not the correct field.

     

    Lastly, this article was designed for Logic Apps, not PowerAutomate. There are some slight differences, although I believe the connector is shared. 

  • miguelgomes740's avatar
    miguelgomes740
    Copper Contributor

    Hi!

     

    Yes you are correct, I'm using PowerAutomate, but it seems that the connector its the same.

    I realized I was not using the "Get Attachment (V2)" action, and now I'm able to successfully upload the attachment to DEVOPs.

    I can access the file uploaded referenced in the url field.

     

    Now I'm struggling with a "404 Page not found" error when PATCHing the workitem, and I'm sure I have the correct workitem id.

    Any thoughts on what it might be?

     

    Raw Inputs:

    {
        "host": {
            "connectionReferenceName""shared_visualstudioteamservices",
            "operationId""HttpRequest"
        },
        "parameters": {
            "account""xxxxx",
            "parameters/Method""PATCH",
            "parameters/Headers": {
                "Content-Type""application/json-patch+json"
            },
            "parameters/Body""[\n    {\n        \"op\": \"add\",\n        \"path\": \"/relations/-\",\n        \"value\": {\n            \"rel\": \"AttachedFile\",\n            \"url\": \"https://dev.azure.com/xxxxx/77dfb1f7-fbc1-48d6-88b4-ae5d7e35b6f9/_apis/wit/attachments/cc154305-07a8-45f7-9e20-7de94585a64b?fileName=Condicoes%20Gerais.pdf\",\n            \"attributes\": {\n                \"comment\": \"Attachments\"\n            }\n        }\n    }\n]",
            "parameters/IsBase64"false
        }
    }

     

    Thanks.

    Miguel