Outlook Signature Placeholders with Calculated / Dynamic Fields

Custom placeholders provide greater flexibility in email signature templates by allowing the definition and use of non-standard fields beyond the built-in placeholders derived from Azure AD/Active Directory attributes (commonly prefixed with az_ or ad_).

All custom placeholders follow a consistent naming convention, beginning with the keyword custom (for example: customphonedetails, customJobTitle, etc.), which distinguishes them from standard directory-sourced variables.

For example, the custom-calculated value below can be assigned to the placeholder ‘customyos‘.

				
					???{{bold}}Years of service: {{bold-end}}[[datediff:auto|{{az_employeehiredate}}|now,1]].???
				
			

Side note: The expression above uses the placeholder {{az_employeehiredate}}, which is not a standard Entra ID attribute. Use the command below to request this additional attribute.
If you are using Active Directory, store the employee hire date in one of the available extension attributes.

				
					"c:\Program Files (x86)\wpsecure\wpsecure-set.exe" -extraattributescsv "employeeHireDate,extensionattribute1,extensionattribute2,extensionattribute3" -version 1.3.2.5
				
			

The custom-calculated value below can combine the user’s job title and department into a single line. This value can be assigned to the placeholder ‘customjobdeptcombo‘.

				
					???{{az_jobtitle}} - {{az_department}}???
				
			

But the above custom-calculated value for the placeholder ‘customjobdeptcombo‘ is better written as follows. The reasons will become clear as you read further.

				
					???{{az_jobtitle}} || - {{if_az_jobtitle}} {{if_az_department}} || {{az_department}}???
				
			

Note: Most features on this page were introduced in v68. Please update your tool to the latest version.

Custom mathematical expression

Mathematical expressions in email signature templates enable dynamic calculations based on values from other placeholder attributes.

These expressions allow you to perform mathematical operations on supported placeholder values and incorporate the computed result directly into the signature.

				
					[[your*expression+goes-here,precision]]
				
			

The precision is the number of decimal places that can be displayed after the expression is evaluated.

				
					[[(3 * (2 + 5)^2 - (4.8 * {{customattribute1}}),3]]
				
			

The following is a deliberately exaggerated and impractical example designed purely to demonstrate the flexibility and power of mathematical expressions within email signature templates. While such a complex formula would rarely (if ever) appear in a real-world signature, it effectively illustrates how multiple placeholder attributes can be combined, manipulated, and calculated dynamically.

				
					[[({{az_extensionattribute1}} * (2 + 5)^2 - (4.8 * 7.2) + ({{customattribute1}} ^ 0.5 * 3.14159)) / (({{az_extensionattribute2}} + 9.5^2) * (0.25 + 1.618) - 50 / (8 - 3)),5]]
				
			

All custom mathematical expressions are enclosed by double square brackets. The expression must begin with [[ and end with ]].

				
					[[some+expression-goes*here]]
				
			

A custom mathematical expression cannot contain another custom mathematical expression nested inside it. Nested expressions of the form [[ … [[ … ]] … ]] are not supported and will result in incorrect parsing or evaluation.

				
					[[some+expression-goes*here+[[what+am+i-doing/here]]]]
				
			

The delimiters [[ and ]] are reserved exclusively for enclosing custom mathematical expressions and must not be used anywhere else within the expression itself (for example, as part of arithmetic operators).

These markers serve only as the start and end delimiters of a complete mathematical expression. Any other appearance of [[ or ]] inside the mathematical expression is prohibited and will result in parsing errors or incorrect rendering.

The below expression is incorrect.

				
					[[(3 * (2 + 5)^2 - (4.8 * [[ 1 + 1 ]])]]
				
			

The intended calculation can be achieved by using a properly structured mathematical expression, as shown below.

				
					[[(3 * (2 + 5)^2 - (4.8 * ( 1 + 1 ))]]
				
			

The following arithmetic operators can be used. 

OperatorDescriptionExampleResult
+Addition5 + 38
Subtraction10 − 46
*Multiplication6 * 742
/Division15 / 35
^Exponentiation2 ^ 416

You can extend mathematical expressions by integrating them into wider placeholder networks. This powerful combination will be explained in detail later in the guide.

Date difference expression

The signature engine supports dynamic date difference calculations using a special expression syntax.

This allows you to display time spans (e.g. years of service, time until an event, age in months, etc.) directly in the email signature — updated automatically whenever the signature is rendered.

All date difference expressions must be wrapped in double square brackets:

				
					[[ datediff:<unit> | <start-date> | <end-date> [,precision] ]]
				
			
				
					[[ datediff:auto | <date1> | <date2> [,precision] ]]
				
			

The unit can be ‘seconds‘, ‘minutes‘, ‘hours‘, ‘days‘,  ‘months‘, or ‘years‘. If omitted or set to ‘auto‘, the most natural unit is chosen automatically.

The start date is an earlier point in time, and the end date / later point in time.

Precision is optional. The precision is the number of decimal places that can be displayed after the expression is evaluated.

Important Rules & Behaviors

  • Auto unit logic chooses the largest sensible unit where the value ≥ 1:

≥ 1 year → years
≥ 1 month → months
≥ 1 day → days
≥ 1 hour → hours
≥ 1 minute → minutes
otherwise → seconds

  • Precision rounds the final number to N decimal places. If N = 0 → integer output (no decimal point shown)
  • Invalid/unparseable dates → the whole expression fails, and the placeholder is set to NULL.
  • No nesting — you cannot put one `[[ … ]]` expression inside another. No `[[` or `]]` inside the expression, these characters are strictly reserved as delimiters

The parser expects an ISO 8601 format. A date/time format that ends with Z, where the Z stands for “Zulu time,” meaning UTC (Coordinated Universal Time).

PurposeExpressionPossible Output (example)
Hours between two times[[datediff:hours|2026-03-10T09:30:00Z|2026-03-10T10:45:00Z]]1.25 hours
Auto unit (smart choice)[[datediff:auto|2025-01-15T00:00:00Z|2026-04-10T00:00:00Z]]1.24 years or 15 months
Years of service with 2 decimals[[datediff:years|{{hiredate}}|now,2]]4.82 years
Days until event[[datediff:days|utcnow|2026-12-25T00:00:00Z]]324 days
Minutes precision[[datediff:minutes|2026-03-10T09:30:00Z|2026-03-10T10:45:00Z]]75 minutes
Conditional & Error-Safe Blocks

The `??? … ???` syntax creates a conditional display block (also called a “maybe” or “error-filtering” block).

It is recommended to group related signature elements — especially when they contain placeholders, calculations, or date differences — so that a single failure does not break the entire line.

It is strongly recommended to enclose every placeholder, math expression `[[ … ]]`, or `datediff` expression inside a `??? … ???` block — even if it contains only one item.

  • Prevents broken signatures when a single attribute is missing or invalid
  • Makes the template far more tolerant of incomplete user profiles
  • Keeps layout clean (no stray labels like “Business:” when there is no phone number)

Basic Structure

				
					??? element1 || element2 || element3 ???
				
			

The content inside `??? … ???` is split by `||` (double pipe) into separate segments (also called tokens or clauses).

Each segment is evaluated independently. If any segment evaluates to an error (e.g., unresolved placeholder, invalid math expression, failed `datediff`, or produces an error), that entire segment is removed — it is not displayed. All remaining (valid) segments are joined together.

Key Behaviors

  • If one part fails (missing data, bad calculation, etc.), only that part disappears — the rest of the line still renders cleanly.
  • The separator `||` is purely structural. In the rendered signature, valid segments are joined with **one space** (not with `||` or any other delimiter).
  • After trimming, empty tokens are skipped.

Good practice examples

				
					???{{az_givenname}} {{az_surname}}???
???{{az_title}} @ {{az_company}}???
???{{bold}}Business: {{bold-end}}{{az_businessphones1}}???
???[[datediff:years|{{hiredate}}|utcnow,1]] years of service???
				
			

Poor practice (avoid)

				
					{{bold}}Business:{{bold-end}} {{az_businessphones1}}     ← label appears even if phone is missing
[[datediff:days|utcnow|{{eventdate}}]] days left     ← may show ERROR if eventdate is invalid
				
			
Formatting Markup Inside Blocks

You can safely use the following inline formatting placeholders inside ??? … ??? blocks:

PlaceholderDescription
{{bold}}Start bold
{{bold-end}}End bold
{{italic}}Start italic
{{italic-end}}End italic
{{underline}}Start underline
{{underline-end}}End underline

These tags are automatically converted to the correct format (HTML, RTF fragment, or stripped for plain text) when the signature is generated.

Real-World Example (Phone Numbers)

This is a very common pattern used to show business and mobile numbers only when they exist, with clean formatting:

				
					???{{bold}}Business: {{bold-end}}{{az_businessphones1}} || | {{if_az_mobilephone}} {{if_az_businessphones1}} || {{bold}}Mobile: {{bold-end}}{{az_mobilephone}}???
				
			

The calculated value above can be assigned to a custom placeholder named customphonedetails.
Next, let’s test how this renders in scenarios where mobile and business phone numbers may be present or absent.

ScenarioRendered output
Both phones presentBusiness: +64 9 123 4567 | Mobile: +64 21 555 123
Only business phoneBusiness: +64 9 123 4567
Only mobile phoneMobile: +64 21 555 123

Render the | separator only when both {{az_mobilephone}} and {{az_businessphones1}} are present.{{if_az_mobilephone}} exists only if {{az_mobilephone}} exists.

{{if_az_businessphones1}} exists only if {{az_businessphones1}} exists. 

When the parent placeholder is not present, its corresponding if placeholder ({{if_az_mobilephone}} or {{if_az_businessphones1}}) returns an error. So, the middle element is not displayed if either of the elements returns an error.

Deploying custom placeholders

Custom placeholder names should start with ‘custom’. The expression below is a calculated expression that is set as a custom placeholder with the name “customphonedetails“.

				
					???{{bold}}Business: {{bold-end}}{{az_businessphones1}} || | {{if_az_mobilephone}} {{if_az_businessphones1}} || {{bold}}Mobile: {{bold-end}}{{az_mobilephone}}???
				
			

Custom placeholders can be set in the registry location below using your own PowerShell scripts. But this document will walk you through using a built-in PowerShell method that might be more effective.

Key pathHKEY_CURRENT_USER\Software\Microsoft\Office\Outlook\wpsecure\signature
Sub keyBilly.George@mycompany.email
Namecustomphonedetails
Value???{{bold}}Business:{{bold-end}} {{az_businessphones1}} || | {{if_az_mobilephone}} {{if_az_businessphones1}} || {{bold}}Mobile:{{bold-end}} {{az_mobilephone}}???
Type REG_SZ

The PowerShell script below will help you set custom placeholders in the right location. 

				
					###############################################################################
#                     DO NOT EDIT ANYTHING IN THIS BLOCK
#         ───────────────────────────────────────────────────────────────
#         Structural code, logic, functions, registry handling, etc.
#         Only edit below the "USER SETTINGS START HERE" line
###############################################################################

$upn = whoami /upn

# Basic UPN validation
if ($upn -notmatch '^[\w\.\-]+@[\w\-]+\.[\w\-\.]+$') {
    Write-Warning "UPN does not appear to be a valid email address: $upn"
    exit 0
}

Write-Output "Valid UPN detected: $upn"

$baseRegPath = "HKCU:\Software\Microsoft\Office\Outlook\wpsecure\signature"

# ────────────────────────────────────────────────────────────────
#  Helper function — creates key if missing and sets a property
# ────────────────────────────────────────────────────────────────
function Set-SignatureCustomValue {
    param(
        [string]$KeyPath,
        [string]$ValueName,
        [string]$ValueData
    )

    if (-not (Test-Path $KeyPath)) {
        try {
            $null = New-Item -Path $KeyPath -Force | Out-Null
            Write-Output "Created registry key: $KeyPath"
        }
        catch {
            Write-Warning "Failed to create key: $KeyPath ── $($_.Exception.Message)"
            return $false
        }
    }

    try {
        Set-ItemProperty -Path $KeyPath -Name $ValueName -Value $ValueData -Force
        Write-Output "Set '$ValueName' in $KeyPath"
        return $true
    }
    catch {
        Write-Warning "Failed to set '$ValueName' in $KeyPath ── $($_.Exception.Message)"
        return $false
    }
}

# Get email from UPN key (if it exists)
$upnKey = Join-Path $baseRegPath $upn
$emailProp = Get-ItemProperty -Path $upnKey -Name "mail" -ErrorAction SilentlyContinue
$email = $emailProp.mail

if (-not $email) {
    Write-Warning "Could not read 'mail' value from registry for $upn"
    # You can choose to continue using only $upn or exit here
    # exit 0
}

$keysToUpdate = @($upnKey)
if ($email -and $upn.ToLower() -ne $email.ToLower()) {
    $emailKey = Join-Path $baseRegPath $email
    $keysToUpdate += $emailKey
}

###############################################################################
#                  USER SETTINGS START HERE
# ──────────────────────────────────────────────────────────────────────────────
#     Add / change / remove entries in this hashtable only
#     You can add hundreds of entries — one per line
###############################################################################

$customAttributes = @{
    "customphonedetails" = @"
???{{bold}}Business:{{bold-end}} {{az_businessphones1}} || | {{if_az_mobilephone}} {{if_az_businessphones1}} || {{bold}}Mobile:{{bold-end}} {{az_mobilephone}}???
"@

    # "customyearsofservice" = "???{{bold}}Years of service: {{bold-end}}[[datediff:auto|{{az_employeehiredate}}|now,1]].???"
    # "customjobtitle"       = "{{az_jobtitle}} - {{az_department}}"
    # "customlocation"       = "{{az_city}}, {{az_country}}"
    # "customextension"      = "???{{bold}}Ext: {{bold-end}}{{az_extension}}???"
    # "customdisclaimer"           = "Confidential — do not forward without permission""
    # ... add as many as you want
}

###############################################################################
#               END OF USER-EDITABLE SECTION
#         ───────────────────────────────────────────────────────────────
#               Nothing below should normally be changed
###############################################################################

$overallSuccess = $true

foreach ($keyPath in $keysToUpdate) {
    foreach ($entry in $customAttributes.GetEnumerator()) {
        $name  = $entry.Key
        $value = $entry.Value

        $success = Set-SignatureCustomValue -KeyPath $keyPath `
                                            -ValueName $name `
                                            -ValueData $value

        if (-not $success) {
            $overallSuccess = $false
        }
    }
}

if ($overallSuccess) {
    Write-Output "`nAll custom signature attributes applied successfully to $($keysToUpdate.Count) key(s)."
} else {
    Write-Warning "`nOne or more registry operations failed — check messages above."
}

Write-Output "Finished."
				
			
Deploying scripts natively

WPSecure allows the deployment of the following PowerShell scripts. The run time of each of these scripts cannot exceed 60 seconds (1 minute). 

  • device.ps1 runs when the machine starts up (boot time).
  • user.ps1 runs during user logon.
  • ‘signature.ps1’: runs when the user unlocks their session.

You can add the modified PowerShell script (shown above) to set custom placeholder values. Save the file as ‘signature.ps1’. On a device where one or more WPSecure Packages are installed, run the following command from the directory where one or more of the above scripts are located.

				
					"c:\Program Files (x86)\wpsecure\wpsecure-set.exe" -scriptsversion 1.2.3.5
				
			

Please refer to the main documentation and read the section on script deployment to the entire organization at the very end of the page.