SendGrid API 正在将表格组合在一起

SendGrid API is grouping tables together

尝试使用 sendgrid 发送电子邮件时,它会将表格分组在一起。这就是我希望电子邮件的外观

但它最终看起来像这样

我试过重新格式化等等。这是我用来创建电子邮件的代码。获取 $bodyjson 的输出时,看起来表已正确拆分。

$StartDates = @(
        [PSCustomObject]@{
            Type      = "CurrentDay"
            startDate = [DateTime]::UtcNow.AddDays(-1).ToString('yyyy-MM-dd')
        }, [PSCustomObject]@{
            Type      = "MonthToDate"
            startDate = [DateTime]::UtcNow.ToString('yyyy-MM-01')
        }
)

$EmailStyle = "<style>BODY{font-family: Arial; font-size: 10pt;}"
$EmailStyle += "TABLE{border: 1px solid black; border-collapse: collapse;}"
$EmailStyle += "TH{border: 1px solid black; background: #D8D8D8; padding: 5px; }"
$EmailStyle += "TD{border: 1px solid black; padding: 5px; }"
$EmailStyle += "</style>"
$Html = ''

$Subscriptions = Get-AzSubscription | Select-Object -First 1

Foreach ($Subscription in $Subscriptions)
{
    Set-AzContext -Subscription $Subscription.SubscriptionId

    Foreach ($Date in $StartDates)
    {    
        $Consumption = Get-AzConsumptionUsageDetail -StartDate $Date.startDate -EndDate ([DateTime]::UtcNow.ToString('yyyy-MM-dd'))
    
        $Costs = $Consumption | Select-Object InstanceId, InstanceName, InstanceLocation, PretaxCost, Product, SubscriptionName
    
        $Costs | % {Add-Member -InputObject $_ -NotePropertyName ResourceGroup -NotePropertyValue ($_.InstanceId.split('/')[4]).ToLower()}
    
        $Groups = $Costs | Group-Object InstanceName
    
        #Write-Output $Groups
    
        $GroupsCost = $Groups | % {[PSCustomObject]@{ResourceGroup=$_.Group[0].ResourceGroup; Item=$_.Name; Description=$_.Group[0].Product; Location=$_.Group[0].InstanceLocation; Sum=($_.Group | Measure -Property 'PretaxCost' -Sum).Sum}}
    
        $TotalCost = ($GroupsCost | Measure -Property 'Sum' -Sum).Sum
        
        $RoundedTotal = [math]::Round($TotalCost, 2)
    
        $TopFive = $GroupsCost | Sort-Object Sum -Descending | Select-Object -First 5
    
        $TopFive | % {Add-Member -InputObject $_ -NotePropertyName NewSum -NotePropertyValue ([Math]::Round($_.Sum,2))}
    
        $TopFive = $TopFive | Select-Object ResourceGroup, Item, Description, Location, NewSum
    
        $TopFive = $TopFive | Add-Member -MemberType AliasProperty -Name Sum -Value NewSum -PassThru | Select-Object ResourceGroup, Item, Description, Location, Sum
    
        Write-Output "$($Subscription.Name) with Total Cost of $($RoundedTotal) for $($Date.Type)"
        Write-Output "Top 5 Spenders:"
        Write-Output $TopFive

        $Html += "<div>$($Subscription.Name) with Total Cost of $($RoundedTotal) for $($Date.Type)</div>"
        $Html += "<br></br>"
        $Html += "Top 5 Spenders:"
        $Html += "<div>$($TopFive | ConvertTo-Html -Head $EmailStyle)</div>"
        $Html += "<br></br>"
    }
}

##Send Email##
$Html += "<br></br>"
$Html += "For troubleshooting this email, the automation for this can be found at this location: "
$Html += "<a href="+ $($AutomationLink) +">Azure Portal: Expiring Security Items</a>"

$SENDGRID_API_KEY = Get-AzKeyVaultSecret -VaultName <vaultname> -Name <keyname> -AsPlainText
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Authorization", "Bearer " + $SENDGRID_API_KEY)
$headers.Add("Content-Type", "application/json")

$body = @{
    personalizations = @(
        @{
            to = @(
                    @{
                        email = $EmailTo
                    }
            )
        }
    )
    from = @{
        email = $EmailFrom
    }
    subject = $Subject
    content = @(
        @{
            type = "text/html"
            value = $Html
        }
    )
}

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

$response = Invoke-RestMethod -Uri https://api.sendgrid.com/v3/mail/send -Method Post -Headers $headers -UseBasicParsing -Body $bodyJson

我不能完全确定 SendGrid 对 HTML 做了什么,因为我没有那个,但我认为问题在于你在哪里使用 ConvertTo-Html -Head 来形成表格.
在那里你想使用开关 -Fragment 而不是 -Head,因为你只需要在 HTML 的 <head>..</head> 标签内定义一次 css 样式。

我最喜欢的构造 HTML 的方法是使用模板 Here-Strings,您可以通过填写占位符来重复使用:

# create a HTML template Here-String which has three placeholders:
# {0}  --> is filled in with the style
# {1}  --> will receive the tables
# {2}  --> the $AutomationLink  (although I couldn't find the definition for that in your code..)
$htmlTemplate = @"
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
{0}
</head>
<body>
{1}
<br></br>
For troubleshooting this email, the automation for this can be found at this location: 
<a href="{2}">Azure Portal: Expiring Security Items</a>
</body>
</html>
"@

# create the CSS style
$styleTemplate = @"
<style>
    BODY  {font-family: Arial; font-size: 10pt;}
    TABLE {border: 1px solid black; border-collapse: collapse;}
    TH    {border: 1px solid black; background: #D8D8D8; padding: 5px; }
    TD    {border: 1px solid black; padding: 5px; }
</style>
"@

# and another template to use for each of the tables (4 placeholders here)
# {0}  --> is filled in with the $Subscription.Name
# {1}  --> will receive the calculated $RoundedTotal
# {2}  --> is replaced by $Date.Type
# {3}  --> is replaced by the $TopFive converted to HTML table
$tableTemplate = @"
    <div>{0} with Total Cost of {1} for {2}</div>
    <br></br>
    Top 5 Spenders:
    <div>{3}</div>
    <br></br>
"@


$StartDates = @(
    [PSCustomObject]@{
        Type      = "CurrentDay"
        startDate = [DateTime]::UtcNow.AddDays(-1).ToString('yyyy-MM-dd')
    }, 
    [PSCustomObject]@{
        Type      = "MonthToDate"
        startDate = [DateTime]::UtcNow.ToString('yyyy-MM-01')
    }
)

# create a StringBuilder object to store the divs and tables in html format in
$sbTables = [System.Text.StringBuilder]::new()

$Subscriptions = Get-AzSubscription | Select-Object -First 1

foreach ($Subscription in $Subscriptions) {
    Set-AzContext -Subscription $Subscription.SubscriptionId

    foreach ($Date in $StartDates) {    
        $Consumption = Get-AzConsumptionUsageDetail -StartDate $Date.startDate -EndDate ([DateTime]::UtcNow.ToString('yyyy-MM-dd'))
        $Costs = $Consumption | Select-Object InstanceId, InstanceName, InstanceLocation, PretaxCost, Product, SubscriptionName
        $Costs | ForEach-Object {Add-Member -InputObject $_ -NotePropertyName ResourceGroup -NotePropertyValue ($_.InstanceId.split('/')[4]).ToLower()}
        $Groups = $Costs | Group-Object InstanceName

        #Write-Output $Groups

        $GroupsCost = $Groups | ForEach-Object {[PSCustomObject]@{ResourceGroup=$_.Group[0].ResourceGroup; Item=$_.Name; Description=$_.Group[0].Product; Location=$_.Group[0].InstanceLocation; Sum=($_.Group | Measure -Property 'PretaxCost' -Sum).Sum}}
        $TotalCost = ($GroupsCost | Measure -Property 'Sum' -Sum).Sum
        $RoundedTotal = [math]::Round($TotalCost, 2)
        $TopFive = $GroupsCost | Sort-Object Sum -Descending | Select-Object -First 5
        $TopFive | ForEach-Object {Add-Member -InputObject $_ -NotePropertyName NewSum -NotePropertyValue ([Math]::Round($_.Sum,2))}
        $TopFive = $TopFive | Select-Object ResourceGroup, Item, Description, Location, NewSum
        $TopFive = $TopFive | Add-Member -MemberType AliasProperty -Name Sum -Value NewSum -PassThru | Select-Object ResourceGroup, Item, Description, Location, Sum

        # Write-Output "$($Subscription.Name) with Total Cost of $($RoundedTotal) for $($Date.Type)"
        # Write-Output "Top 5 Spenders:"
        # Write-Output $TopFive

        # convert the $TopFive data into a HTML table
        $TopFiveTable = ($TopFive | ConvertTo-Html -Fragment) -join [environment]::NewLine
        # use the $tableTemplate string to fill in the placeholders
        $htmlTable = $tableTemplate -f $Subscription.Name, $RoundedTotal, $Date.Type, $TopFiveTable
        # add all this to the StringBuilder
        [void]$sbTables.AppendLine($htmlTable)
    }
}

# fill in the placeholders of the $htmlTemplate to complete it
$htmlBody = $htmlTemplate -f $styleTemplate, $sbTables.ToString(), $AutomationLink

##Send Email##

$SENDGRID_API_KEY = Get-AzKeyVaultSecret -VaultName <vaultname> -Name <keyname> -AsPlainText
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Authorization", "Bearer " + $SENDGRID_API_KEY)
$headers.Add("Content-Type", "application/json")

$body = @{
    personalizations = @(
        @{
            to = @(
                    @{
                        email = $EmailTo
                    }
            )
        }
    )
    from = @{
        email = $EmailFrom
    }
    subject = $Subject
    content = @(
        @{
            type = "text/html"
            value = $htmlBody
        }
    )
}

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

$response = Invoke-RestMethod -Uri https://api.sendgrid.com/v3/mail/send -Method Post -Headers $headers -UseBasicParsing -Body $bodyJson