Search code examples
azure-devopsazure-pipelinesworkflowwebhooksoffice365connectors

Sending Notification in Microsoft Teams using Webhook Request Workflow


I had built a pipeline which scans the Keyvaults and sends a Microsoft Teams notification using Webhook(Eonos Office Webhook) incase any Key/Certificate has expired/about to Expire. And the pipeline worked perfectly, however since O365 connectors are deing depreciated, I am forced to use Microsoft Workflows to post a message from a Webhook Request.

For that I created a Workflow which 'Post to a channel when a webhook request is received' but when I replaced my current Eonos WebhookURL with the new Workflow URL, my notification layout is completely changed and I am getting more of a text than a proper notification.

This is how my notifications used to look like: enter image description here

and I just replaced the URL with the new Workflow Webhook URl, I am getting the following output:

enter image description here

So, It is just printing the value that it is being provided.

This is my Workflow Configuration:

enter image description here

and this is how I am sending the Webhook request from my Powershell skript: enter image description here

and this is my Send-Notification function: enter image description here

I believe that I need to change the configuration of my Flow (maybe) but I am not sure. Please help me with this :')

update from 18.09.2024:

Now I am get the links (of the secrets) as a Text instead of clickable links eventhough I enabled the Code view in the message: enter image description here

and this is how I am getting the notification now:

enter image description here

Do you know how to solve this?

when I save it in code view and refresh it and check it again, it shows me in normal mode: enter image description here

this is my code, where I check secret and create the URL for it:

function Check-Secrets {
param(
    [string]$keyVault,
    [string]$globalSubscription,
    [string]$resourceGroup
)

$expiringSecrets = @()
$noExpirySecrets = @()

$secrets = az keyvault secret list --vault-name $keyVault --query "[].id" -o tsv 2>$null
if ($LASTEXITCODE -ne 0) {
    Write-Output "Unable to access Key Vault: $keyVault"
    $global:inaccessibleKeyVaults += $keyVault
    continue
}

foreach ($secret in $secrets) {
    $secretAttributesJson = az keyvault secret show --id $secret --query "attributes" -o json
    $secretAttributes = $secretAttributesJson | ConvertFrom-Json
    $secretName = az keyvault secret show --id $secret --query "name" -o tsv
    $secretUrl = "https://portal.azure.com/#view/Microsoft_Azure_KeyVault/ListObjectVersionsRBACBlade/~/overview/objectType/secrets/objectId/https%3A%2F%2F$keyVault.vault.azure.net%2Fsecrets%2F$secretName/vaultResourceUri/%2Fsubscriptions%2F$globalSubscription%2FresourceGroups%2F$resourceGroup%2Fproviders%2FMicrosoft.KeyVault%2Fvaults%2F$keyVault/vaultId/%2Fsubscriptions%2F$globalSubscription%2FresourceGroups%2F$resourceGroup%2Fproviders%2FMicrosoft.KeyVault%2Fvaults%2F$keyVault"

    if ($null -ne $secretAttributes.expires -and $secretAttributes.expires -ne '') {
        $expiryDate = [DateTime]::Parse($secretAttributes.expires)
        $oneMonthFromNow = (Get-Date).AddMonths(1)

        if ($expiryDate -lt $oneMonthFromNow) {
            $expiringSecrets += "Secret ID: [$secret]($secretUrl), Expiry Date: $expiryDate"
        }
    }
    else {
        $noExpirySecrets += "Secret ID: [$secret]($secretUrl)"
    }
}


return @{
    "ExpiringSecrets" = $expiringSecrets
    "NoExpirySecrets" = $noExpirySecrets
}

}

and this is how I called the function:

$expiringSecretsMessage = if ($expiringSecrets.Count -gt 0) { "The following secrets are expiring:<br />" + ($expiringSecrets -join "<br />") }
    $noExpirySecretsMessage = if ($noExpirySecrets.Count -gt 0) { "The following secrets have no expiry date:<br />" + ($noExpirySecrets -join "<br />") }
    $expiringCertificatesMessage = if ($expiringCertificates.Count -gt 0) { "The following certificates are expiring:<br />" + ($expiringCertificates -join "<br />") }
    $noExpiryCertificatesMessage = if ($noExpiryCertificates.Count -gt 0) { "The following certificates have no expiry date:<br />" + ($noExpiryCertificates -join "<br />") }

    Send-Notification -keyVault $keyVault -WebhookURL $WebhookURL -messageParts @($expiringSecretsMessage, $noExpirySecretsMessage, $expiringCertificatesMessage, $noExpiryCertificatesMessage)

Solution

  • As far as I have tested, to send clickable hyperlink as parts of your Teams message requires your context to use the valid HTML hyperlink syntax like Secret ID: <a href="URL">ALIAS</a>. Here is a sample YAML pipeline that is to generate the webhook payload with hyperlink syntax.

    pool:
      vmImage: ubuntu-latest
    
    variables:
      myTestKV: xxxmytestkvxxx
      TeamsWebhookURL: https://xxxxxx
    
    steps:
    - task: AzureCLI@2
      inputs:
        azureSubscription: 'ARMSvcCnnSub0'
        scriptType: 'pscore'
        scriptLocation: 'inlineScript'
        inlineScript: |
          $myTestKV = "$(myTestKV)" 
    
          # Fetch secrets using Azure CLI command
          $secrets = az keyvault secret list --id https://$myTestKV.vault.azure.net/ | ConvertFrom-Json
    
          $currentDate = (Get-Date).ToUniversalTime()
          $expiringSecrets = @()
          $noExpirySecrets = @()
    
          foreach ($secret in $secrets) {
              $expires = $secret.attributes.expires
              if ($expires) {
                  $expiryDate = [datetime]::Parse($expires)
                  $daysRemaining = ($expiryDate - $currentDate).Days
                  if ($daysRemaining -le 30) {
                      $expiringSecrets += $secret
                  }
              } else {
                $noExpirySecrets += $secret
              }
          }
    
          Write-Output "expiringSecrets - $($expiringSecrets | ConvertTo-Json -Depth 100)"
          Write-Output "noExpirySecrets - $($noExpirySecrets | ConvertTo-Json -Depth 100)"
    
          $expiringSecretsText = ""
          foreach ($secret in $expiringSecrets) {
              $expiryDate = [datetime]::Parse($secret.attributes.expires).ToUniversalTime().ToString("MM/dd/yyyy HH:mm:ss")
              $expiringSecretsText += "Secret ID: <a href=`\`"$($secret.id)`\`">$($secret.name)</a>Expiry Date (UTC): <span style=`\`'color: red;`\`'>$expiryDate</span><br />"
          }
    
          $noExpirySecretsText = ""
          foreach ($secret in $noExpirySecrets) {
              $noExpirySecretsText += "Secret ID: <a href=`\`"$($secret.id)`\`">$($secret.name)</a><br />"
          }
    
          $testMessageJSON = @"
          {
            "text": "<b>The following secrets are expiring:</b><br />$expiringSecretsText<br /><b>The following secrets have no expiry date:</b><br />$noExpirySecretsText<br />",
            "title": "Key Vault: $myTestKV"
          }
          "@
    
          Write-Output $testMessageJSON
          
          $WebhookURL = "$(TeamsWebhookURL)"
          Write-Host "WebhookURL - $WebhookURL"
          Invoke-RestMethod -Method Post -Uri $WebhookURL -Body $testMessageJSON -ContentType 'application/json'
    

    Sample webhook payload

    {
      "text": "<b>The following secrets are expiring:</b><br />Secret ID: <a href=\"https://xxxmytestkvxxx.vault.azure.net/secrets/secret1\">secret1</a>Expiry Date (UTC): <span style=\'color: red;\'>09/22/2024 16:00:00</span><br />Secret ID: <a href=\"https://xxxmytestkvxxx.vault.azure.net/secrets/secret3\">secret3</a>Expiry Date (UTC): <span style=\'color: red;\'>09/30/2024 15:59:59</span><br /><br /><b>The following secrets have no expiry date:</b><br />Secret ID: <a href=\"https://xxxmytestkvxxx.vault.azure.net/secrets/secret2\">secret2</a><br />Secret ID: <a href=\"https://xxxmytestkvxxx.vault.azure.net/secrets/secret4\">secret4</a><br /><br />",
      "title": "Key Vault: xxxmytestkvxxx"
    }
    

    enter image description here

    enter image description here