# subflow: Test (global)
# Excel to HTML - the quick try, AUTONOMOUS: any workbook range becomes an HTML table that keeps the look of Excel
# (fills, fonts, borders, merged cells, column widths, number formats, conditional formatting, hyperlinks). The table opens in the
# browser; with bool_SendToOutlook = True it also lands in an Outlook draft (classic Outlook only).
# NO input / output variable: the parameters are plain SET lines in the first region, so this subflow can be pasted alone
# into any empty Main. Or run it here with 'Run from here' on its first action.
# The reusable building block is the local function Convert_Excel_To_Html (same script), called by Main.
# PAD has no action that reads the formatting of a cell: ONE 'Run PowerShell script' action asks Excel itself
# (COM, DisplayFormat) and returns a table with inline CSS, the only styling that every Outlook client keeps.
# Everything else is native: guards, HTML file, browser or Outlook, final message. Regions: cyan values, red guards, purple processing, gold output.
@@regionColor: '#57FFE1'
**REGION 1. Variables: parameters, the only lines to change
# The workbook to send: any .xlsx / .xlsm, opened read-only, never modified
SET txt_WorkbookPath TO $'''C:\\Reports\\Sales\\Sales_Report_2026-Q3.xlsx'''
# Worksheet name; empty = the sheet that was active when the workbook was saved
SET txt_SheetName TO $''''''
# Range to export in Excel notation (A1:H18); empty = the used range of the sheet
SET txt_RangeAddress TO $''''''
# Folder that receives the HTML file (it opens in your browser at the end)
SET txt_OutputFolder TO $'''C:\\Reports\\Sales\\Email'''
# False = only the HTML file, opened in the browser (needs Excel only); True = also an email through the classic Outlook
SET bool_SendToOutlook TO False
# Used when bool_SendToOutlook is True: Outlook account that sends (as listed in Outlook), recipients, subject, text above the table (HTML allowed)
SET txt_OutlookAccount TO $'''your.name@example.com'''
SET txt_EmailTo TO $'''sales-managers@example.com'''
SET txt_EmailSubject TO $'''Sales report Q3 2026 - straight from Excel'''
SET txt_EmailIntro TO $'''Hello,
Please find below the sales report of the quarter, exactly as it looks in the workbook.'''
# True = the email is saved in the Drafts folder and opened on screen (demo: nothing leaves the mailbox); False = it is sent
SET bool_DraftOnly TO True
**ENDREGION
@@regionColor: '#F4B6B6'
**REGION 2. Preconditions: a missing workbook is said clearly, a missing output folder is created
IF (File.IfFile.DoesNotExist File: txt_WorkbookPath) THEN
Display.ShowMessageDialog.ShowMessage Title: $'''Excel to Outlook''' Message: $'''Workbook not found: %txt_WorkbookPath%. Fix txt_WorkbookPath in the first region of this subflow, then run again.''' Icon: Display.Icon.ErrorIcon Buttons: Display.Buttons.OK DefaultButton: Display.DefaultButton.Button1 IsTopMost: True ButtonPressed=> _txt_ButtonPressed
EXIT Code: 0
END
IF (Folder.IfFolderExists.DoesNotExist Path: txt_OutputFolder) THEN
# Create folder needs an existing parent: split the path into parent + name first
File.GetPathPart File: txt_OutputFolder Directory=> fold_OutputParent FileName=> txt_OutputFolderName
Folder.Create FolderPath: fold_OutputParent FolderName: txt_OutputFolderName Folder=> _fold_Output
END
**ENDREGION
@@regionColor: '#D9C3E9'
**REGION 3. Convert: Excel renders the range, the script writes it as an HTML table with inline styles
# The same script as in Convert_Excel_To_Html, without the details line; its parameters come from the variables above
Scripting.RunPowershellScript.RunScript Script: $'''# Excel to HTML - turns an Excel range into an HTML table that keeps the look of Excel and survives Outlook.
# How: Excel itself publishes the range as HTML (PublishObjects, its \'Save as web page\' engine: one call, a few
# hundred milliseconds whatever the size), then this script rewrites that HTML for email: the CSS classes of the
# \')
if ($mStyle.Success) {
$styleBlock = $mStyle.Groups[1].Value.Replace(\'\', \' \')
foreach ($m in [regex]::Matches($styleBlock, \'(?s)([^{}]+)\\{([^{}]*)\\}\')) {
$decl = Split-Declarations $m.Groups[2].Value
foreach ($sel in ($m.Groups[1].Value -split \',\')) {
$s = $sel.Trim()
if ($s -eq \'\') { continue }
if (-not $rules.ContainsKey($s)) { $rules[$s] = New-Object System.Collections.Specialized.OrderedDictionary }
Merge-Into $rules[$s] $decl
}
}
}
# 3. The table alone, without the Office conditional comments
$mTable = [regex]::Match($html, \'(?is)
| ]*)>(.*?) | \')) { while ($pos -lt $rowspanLeft.Length -and $rowspanLeft[$pos] -gt 0) { $pos++ } $ca = Parse-Attributes $mc.Groups[1].Value $colspan = 1 if ($ca.ContainsKey(\'colspan\')) { $colspan = [int]$ca[\'colspan\'] } $rowspan = 1 if ($ca.ContainsKey(\'rowspan\')) { $rowspan = [int]$ca[\'rowspan\'] } $visible = 0 $wsum = 0 for ($i = $pos; $i -lt ($pos + $colspan); $i++) { if ($i -lt $colHidden.Count) { if (-not $colHidden[$i]) { $visible++; $wsum += $colWidth[$i] } } else { $visible++ } } if ($rowspan -gt 1) { for ($i = $pos; $i -lt ($pos + $colspan) -and $i -lt $rowspanLeft.Length; $i++) { $rowspanLeft[$i] = $rowspan } } $pos += $colspan if ($visible -eq 0 -or $rowHidden) { continue } $attrText = \'\' if ($visible -gt 1) { $attrText += \' colspan=\"\' + $visible + \'\"\' } if ($rowspan -gt 1) { $attrText += \' rowspan=\"\' + $rowspan + \'\"\' } if ($wsum -gt 0) { $attrText += \' width=\"\' + $wsum + \'\"\' } if ($ca.ContainsKey(\'height\')) { $attrText += \' height=\"\' + $ca[\'height\'] + \'\"\' } if ($ca.ContainsKey(\'align\')) { $attrText += \' align=\"\' + $ca[\'align\'] + \'\"\' } if ($ca.ContainsKey(\'valign\')) { $attrText += \' valign=\"\' + $ca[\'valign\'] + \'\"\' } $style = Inline-Style $rules $ca \'td\' $content = Inline-Content $rules $mc.Groups[2].Value if ($content -eq \'\') { $content = \' \' } [void]$cells.Append(\'\' + $content + \' | \') } for ($i = 0; $i -lt $rowspanLeft.Length; $i++) { if ($rowspanLeft[$i] -gt 0) { $rowspanLeft[$i]-- } } if ($rowHidden) { continue } [void]$out.Append(\'