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
尝试使用 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