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.

Before we dive into the details of custom calculated fields, let’s start with a quick overview of placeholders. The example below is an Azure Entra ID placeholder that represents the user’s displayName value.

				
					{{az_displayname}}
				
			

 The example below is an Active Directory placeholder that represents the user’s displayName value.

				
					{{ad_displayname}}
				
			

you can use placeholders like {{az_displayname, ad_displayname, custom_displayname}} to get the first available value from left to right. If az_displayname is not available, ad_displayname is used; if ad_displayname is also unavailable, custom_displayname is used.

				
					{{az_displayname, ad_displayname, custom_displayname}}
				
			

For most use cases, the standard placeholders are sufficient. However, every organization has its own preferred format for displaying user details in an email signature. To accommodate this, we use custom calculated fields.

For example, the custom‑calculated value shown 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. Discussed in detail in the documentation page.

				
					"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 custom calculated placeholder 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 into the appropriate format (HTML, RTF fragment, or removed for plain text) when the signature is generated.

The example below demonstrates how to use these controls to apply bold, italic, and underline formatting.

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

When the Outlook email signature is rendered with the custom calculated placeholder value, it will appear similar to the example shown in the image below.

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 02 89364645 | Mobile: +64 56 9353641
Only business phoneBusiness: +64 02 89364645
Only mobile phoneMobile: +64 56 9353641

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.

Note: The below formating tags were implemented in version Version 69.

PlaceholderDescription
{{color-font-#11568F}}Start of font color #11568F
{{color-font-end-#11568F}}End of font color #11568F
{{color-highlight-#3498db}}Start of font highlight color #3498db
{{color-highlight-end-#3498db}}End of font highlight color #3498db

The above example will look even better once the text is displayed in color. Use the modified placeholder below instead of the previous value.

				
					???{{color-font-#1155CC}}{{bold}}Business:{{bold-end}}{{color-font-end-#1155CC}} {{az_businessphones1}} || | {{if_az_mobilephone}} {{if_az_businessphones1}} || {{color-font-#1155CC}}{{bold}}Mobile:{{bold-end}}{{color-font-end-#1155CC}} {{az_mobilephone}}???
				
			

When the Outlook email signature is rendered with the custom calculated placeholder value, it will appear similar to the example shown in the image below.

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. 

				
					# ======================================================================
# WPSecure – Custom Calculated Signature Placeholder Attribute Writer
# Version: 69
#
# Purpose :
#   Applies user‑defined custom signature placeholder values to all
#   discovered WPSecure identity keys under the current user's profile.
#
# Scope :
#   Current User (HKCU). No administrative rights are required.
#
# Usage :
#   • Use this script as part of your signature.ps1 workflow, OR
#   • Deploy it as a compliance baseline using Intune or MECM.
# ======================================================================

[CmdletBinding()]
param()

# -----------------------
# Constants / base paths
# -----------------------
$BaseRegPath = 'HKCU:\Software\Microsoft\Office\Outlook\wpsecure\signature'

# -----------------------
# Discovery / helpers
# -----------------------

function Test-Email {
    [CmdletBinding()]
    param([Parameter(Mandatory=$true)][string]$Value)
    if ($null -eq $Value) { return $false }
    return ($Value -match '^[\w\.\-]+@[\w\-]+\.[\w\.\-]+$')
}

function Get-UserDefineEmailAddresses {
    [CmdletBinding()]
    param()

    try {
        if (-not (Test-Path -LiteralPath $BaseRegPath)) { return @() }

        $emails =
            Get-ChildItem -LiteralPath $BaseRegPath -ErrorAction Stop |
            Select-Object -ExpandProperty PSChildName |
            Where-Object { $_ -match '^[\w\.\-]+@[\w\-]+\.[\w\.\-]+$' } |
            ForEach-Object { $_.ToLower() } |
            Sort-Object -Unique

        return $emails
    } catch {
        return @()
    }
}

function Set-SignatureCustomValue {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)][string]$KeyPath,
        [Parameter(Mandatory=$true)][string]$ValueName,
        [Parameter(Mandatory=$true)][string]$ValueData
    )
    if (-not (Test-Path -LiteralPath $KeyPath)) {
        try { $null = New-Item -Path $KeyPath -Force | Out-Null } catch { return $false }
    }
    try {
        Set-ItemProperty -Path $KeyPath -Name $ValueName -Value $ValueData -Force
        return $true
    } catch { return $false }
}

function Remove-CustomCvValues {
    [CmdletBinding()]
    param([Parameter(Mandatory=$true)][string]$KeyPath)

    if (-not (Test-Path -LiteralPath $KeyPath)) { return 0 }
    try {
        $keyItem = Get-Item -LiteralPath $KeyPath -ErrorAction Stop
        $names = $keyItem.GetValueNames()
        $removed = 0
        foreach ($n in $names) {
            if ($null -ne $n -and $n.ToString().ToLower().StartsWith('custom_cv_')) {
                try {
                    Remove-ItemProperty -Path $KeyPath -Name $n -ErrorAction Stop
                    $removed++
                } catch { }
            }
        }
        return $removed
    } catch {
        return 0
    }
}

# ======================================================================
# =======================  USER SETTINGS START HERE  ====================
# ======================================================================
# Add or modify the placeholder values to be written for each identity.
# Each "Name" = "Value" pair becomes a REG_SZ under:
#   HKCU\Software\Microsoft\Office\Outlook\wpsecure\signature\<identity>\
# Multiline values are allowed (use here-strings @" ... "@).
# make sure the here-string entry @" and exit "@ are on separate lines.
# Note: Always use the prefix "custom_cv_" for new names, but it is not mandatory.

$CustomAttributes = @{

# The custom calculated placeholder below is the best way to express a combination of telephone and mobile numbers.

    "custom_cv_phonedetails" = @"
???{{color-font-#11568F}}{{bold}}Business:{{bold-end}}{{color-font-end-#11568F}} {{az_businessphones1}} || | {{if_az_mobilephone}}{{if_az_businessphones1}} || {{color-font-#11568F}}{{bold}}Mobile:{{bold-end}}{{color-font-end-#11568F}} {{az_mobilephone}}???
"@;

# Below is another example for calculating years of service.

    "custom_cv_yos" = @"
???{{color-font-#11568F}}{{bold}}Years of service:{{bold-end}}{{color-font-end-#11568F}} [[datediff:auto|{{az_employeehiredate}}|utcnow,1]].???
"@

}
# ======================================================================
# ========================   USER SETTINGS END   ========================
# ======================================================================

# ----------------------------
# Main
# ----------------------------
$Identities = Get-UserDefineEmailAddresses

if (-not $Identities -or $Identities.Count -eq 0) {
    Write-Output "No identities discovered. Finished."
    return
}

foreach ($id in $Identities) {
    if ($null -eq $id) { continue }
    $identity = $id.Trim()
    if (-not (Test-Email $identity)) { continue }

    $keyPath = Join-Path $BaseRegPath $identity

    # 1) Clean up any existing values with the 'custom_cv_' prefix
    $removedCount = Remove-CustomCvValues -KeyPath $keyPath
    if ($removedCount -gt 0) {
        Write-Output "Removed $removedCount existing 'custom_cv_' value(s) from '$identity'."
    }

    # 2) Write current set of custom attributes
    $allOk = $true
    foreach ($entry in $CustomAttributes.GetEnumerator()) {
        $name  = [string]$entry.Key
        $value = [string]$entry.Value
        $ok = Set-SignatureCustomValue -KeyPath $keyPath -ValueName $name -ValueData $value
        if (-not $ok) { $allOk = $false }
    }

    if ($allOk) {
        Write-Output "Applied attributes to '$identity'."
    } else {
        Write-Output "One or more attributes failed for '$identity'."
    }
}

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.