placing get-content into array for html email
Asked Answered
S

3

2

Html portion


$html = @($htm)

$html = @"

<!doctype html>

<html lang="en">
<head>
<body>
text
$computername
$username
text
</body>
</html>
"@

 $html | out-file c:\scripts\temp\Report.html

I have html language in the $html variable. When I decide to change the HTML code I need to go back into the PowerShell script. I'd rather just have the html in a separate file. How can I use get-content to place the contents of separate file into the $html = @"..."@ variable.
I am using powershell $variables in the html portion to make dynamic emails!

Schindler answered 3/10, 2023 at 11:47 Comment(0)
E
2

You're looking for string templating, i.e. the ability to expand a string with placeholders on demand, based on the then-current values that the placeholders (variables) represent.

PowerShell offers such a feature via the (little-known) .InvokeCommand.ExpandString() method of the automatic $ExecutionContext variable, which treats a string value as if it were an expandable (double-quoted) string ("..."), i.e. performs string interpolation on it:

  • Save your HTML template string via a verbatim string ('...'), i.e., a single-quoted one, so as to prevent instant expansion of embedded variable references such as $computer:
# Note the use of *single* quotes, to ensure
# that $computername and $username *aren't* expanded.
@'
<!doctype html>
<html lang="en">
<head>
<body>
text
$computername
$username
text
</body>
</html>
'@ | Out-File c:\scripts\temp\Report.html
  • Then use the template as follows:
# Read the template in full (-Raw)
$htmlTemplate = Get-Content -Raw c:\scripts\temp\Report.html

# Perform sample instantiations of the template.
1..2 | ForEach-Object {
  # Set the variable values to use in the template.
  $computername = "computer$_"
  $username = "user$_"
  # Now instantiate the template
  $ExecutionContext.InvokeCommand.ExpandString($htmlTemplate)
  "---`n" 
}

This outputs the following - note how $computer and $username were expanded to the then-current values of these variables:

<!doctype html>
<html lang="en">
<head>
<body>
text
computer1
user1
text
</body>
</html>

---

<!doctype html>
<html lang="en">
<head>
<body>
text
computer2
user2
text
</body>
</html>

---

Caveat:

  • The above assumes that you either fully control or implicitly trust the content of your HTML template file, given that it is possible to inject arbitrary commands into the template, using $(...), the subexpression operator

  • See below for a way to prevent expansion of subexpressions.


Preventing expansion of subexpressions ($(...)), to prevent code injection:
  • The following variation ensures that any (unescaped) $(...) sequences are exempt from expansion, i.e. they are retained verbatim.

  • This is achieved by `-escaping the $ in every $( sequence, but only if that $ isn't already escaped. It is the latter requirement that requires the nontrivial regex below. In a nutshell, the $ in $( must only be escaped if it is either preceded by no or an even number of ` chars. (the latter form a sequence of escaped escape characters, which implies that the following $ is unescaped). For an explanation of the regex and the option to experiment with it, see this regex101.com page.

# Read the template in full (-Raw) and escape the $ of unescaped
# $( sequences with ` (backtick), which will prevent expansion of any
# $(...) subexpressions.
$htmlTemplate = 
  (Get-Content -Raw t.html) -replace '(?<!`)((?:``)*)\$\(', '$1`$$('

# Perform sample instantiations of the template.
1..2 | ForEach-Object {
  # Set the variable values to use in the template.
  $computername = "computer$_"
  $username = "user2"
  # Now instantiate the template.
  $ExecutionContext.InvokeCommand.ExpandString($htmlTemplate)
  "---`n" 
}
Echovirus answered 3/10, 2023 at 13:5 Comment(2)
this is definitively the better way. About code injection, you can add something like this if ($htmlTemplate -match "\$\(.+\)") {throw "This template contains possible arbitrary code"} right after reading the template. Obsiously, this cannot be done if your template needs such code...Calvaria
Thanks, @CFou, but instead of a warning it may be simpler to simply suppress expansion of what would otherwise be subexpressions. Please see my update.Echovirus
I
0

Dependent on your use case, a simple string replacement might work:

# read the content of the file
[string]$html = Get-Content `
    -Path 'c:\path\to\template.html' `
    -Encoding 'UTF8' `
    -Raw;

# replace the strings
$html = $html.Replace('$computername', $computername);
$html = $html.Replace('$username', $username);

# write the result to the output file
$html | Out-File 'c:\path\to\report.html';
Indeterminism answered 3/10, 2023 at 12:0 Comment(3)
Your answer is perfectly right and easy to remember matches between the html code and the variables. However, I find this way [String]::Format((Get-Content 'c:\path\to\template.html' -Raw), $computername, $username) | Out-File 'c:\path\to\report.html' with replacing $computername by {0} and $username by {1} into the html code, more elegant - but this is only a matter of taste :)Calvaria
Any ideas to make dynamic? I have 40 variables to fill.Schindler
@Schindler you might want to store the variables in a dictionary and then iterate over the entries in the dictionaryIndeterminism
D
0

If you need to have a HTML template where several variables need to be inserted, I would rather use a structured CSV file than a simple text file to read the variable values needed.

Lets say your csv file looks like this:

"ComputerName","UserName"
"PC0123","Roger Rabbit"
"PC8769","Daffy Duck"
"PC5544","Yosemite Sam"

Option 1: Use a HTML template with numeric placeholders to use with the -f Format operator

$html = @"
<!doctype html>

<html lang="en">
<head>
<body>
text
{0}
{1}
text
</body>
</html>
"@

Then use like

Import-Csv -Path 'X:\Somewhere\UsersAndComputers.csv' | ForEach-Object {
    # replace the placeholder strings {0} and {1} etc. with values from the csv
    $newHtml = $html -f $_.ComputerName, $_.UserName
    # create a path and filename for the filled-in html
    $newFile = 'C:\scripts\temp\Report_{0}.html' -f $_.UserName
    # write the file
    $newHtml | Set-Content -Path $newFile -Encoding UTF8
}

Option 2: Use a HTML template with string placeholders to use with .Replace() or -replace

$html = @"
<!doctype html>

<html lang="en">
<head>
<body>
text
@@COMPUTERNAME@@
@@USERNAME@@
text
</body>
</html>
"@


Import-Csv -Path 'X:\Somewhere\UsersAndComputers.csv' | ForEach-Object {
    # replace the placeholder strings with values from the csv
    $newHtml = $html.Replace('@@COMPUTERNAME@@', $_.ComputerName).Replace('@@USERNAME@@', $_.UserName)
    # create a path and filename for the filled-in html
    $newFile = 'C:\scripts\temp\Report_{0}.html' -f $_.UserName
    # write the file
    $newHtml | Set-Content -Path $newFile -Encoding UTF8
}

Note: Instead of the string .Replace() method you could also use -replace or -creplace regex operators like

    $newHtml = $html -replace '@@COMPUTERNAME@@', $_.ComputerName -replace '@@USERNAME@@', $_.UserName

where -replace is a case-insensitive operator. If you need it to work case-sensitively, then use -creplace

When you need to insert many variables, option 1 would be my preferred way..

Diastyle answered 3/10, 2023 at 12:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.