Table of Contents
Silently set “Outlook On The Web” / “Outlook New” signatures. This is a certificate-authenticated silent deployment approach.
Azure Function Apps/Exchange Online content, security, and setup are outside the scope of WPSecure package support and licensing. This integration is provided for convenience and interoperability with other cloud infrastructures. If you have any issues with Azure Function Apps or Exchange Online, please reach out to Microsoft Support.
This article will guide you through setting up an Azure Function with a Powershell Core runtime stack that will process and set the Exchange Online default signature based on the following files created by the WPSecure signature processing engine on the local computer.
wpsecure_web.htm | This file will be set as the default signature for messages in a HTML format. |
wpsecure_web.txt | (Optional) This file will be set as the default signature for messages in a TXT format. |
What administrative permissions are required?
To successfully execute the procedures in this document, you will require the following permissions.
Exchange Online Administrator | Turn off signature roaming and to provide permissions to the Azure Functions Managed Identity. |
Azure Global Administrator | Provide correct Microsoft Graph permissions for the Azure Functions Managed Identity. |
Disable Exchange Online roaming signatures
Exchange Online’s Roaming Signature feature must be turned off for this process to work. But remember that when you turn off the Roaming Signature feature, Exchange Online will not sync or set an email signature from Exchange Online (But this is not a problem).
You will have to be an Exchange Administrator to run the below command.
Set-OrganizationConfig -PostponeRoamingSignaturesUntilLater $true
Ways to secure the Azure Function
The Azure function you create must be secure but UI-free. Therefore, we will have to authenticate using a Certificate. The calls to the Function App cannot trigger any authentication windows or request user interaction.
Function Key | Function keys help ensure that only authorized users or systems can execute your functions, adding an extra layer of security. Using a Function Key alone does not secure your Function App. |
Certificate based authentication | Using certificate-based authentication helps protect your Azure Functions from unauthorized access and ensures secure communication between WPSecure clients and the function. |
VNI and NSGs | Deploy your function app within a virtual network to manage inbound and outbound traffic. Use Network Security Groups (NSGs) to restrict traffic based on port, protocol, source IP address, or destination IP address. Note that this topic is beyond the scope of this article. |
The process flow: What happens in the background?
The process from creating the email signature to setting the signature in Exchange Online will be similar to that shown in the table below.
1 | A WPsecure package that contain the 'wpsecure_web.htm' signature gets executed on the Users device. |
2 | The WPSecure Signature Processing Engine replaces attribute placeholders with user-specific attributes. |
3 | If the local copy of the web signature has been modified or not uploaded at least once in the past 8 hours, WPSecure will trigger the Azure function for the device's primary user. |
4 | The process will authenticate with the Azure function using a certificate stored in the user's certificate store. If the authentication is successful, the email signature will be uploaded to the Azure function. |
5 | The Azure function will then use PowerShell Exchange Online modules to set the default signature for the user with the specified UPN. |
WPSecure will identify the device’s primary user (UPN) using the following command.
whoami /upn
Create a Function App in Azure Portal
Now that the fundamentals are discussed, it is time to create the Azure Function and make the necessary configuration changes.
Create a resource in Azure. Select ‘Function App’ as the Service.

We recommend using the Flex Consumption or a Function Premium plan to prevent a cold start. But you can choose the Consumption plan if you are running with budget constraints.

In the next step, pick a name that describes the Function App’s nature as an auto-sync service. This is necessary because we must create another user-driven signature sync Function App for User-Driven Exchange Online signature update and User attribute placeholder download. Let’s focus on the first Function App in this article.

Choose a storage account. Azure Functions requires an Azure Storage account when you create a function app instance. If you have an existing storage, choose to use it.

Public access? Choose based on your organizational needs. This is entirely up to your Organizational security governance requirements.
But we recommend ‘Public‘ during setup. After a successful setup, you can add more network restrictions.

Azure Monitor application insights is an Application Performance Management (APM) service for developers and DevOps professionals. Enable it below to monitor your application automatically. It will detect performance anomalies and include powerful analytics tools to help you diagnose issues and understand what users do with your app.

Continuous deployment settings and the tags should be based on your organization’s needs. Finally, review and Create.

When the deployment is complete, go to the resource.

Copy the Function App default domain
On the overview page of the Function App. The first parameter required to be copied and saved for future reference is the “Default Domain.” Copy and paste the default domain to a Notepad file.

Copy Function App Managed Identity ID
Enable “System Assigned Managed Identity” and save and copy the PrincipalID of the managed identity.

Copy the Azure AD Tenant ID
Open the EntraID portal in a new window or tab and copy the Tenant ID from the ‘basic information‘ section.

Copy the Exchange Online Organizational Unit Root
Open Powershell on your computer and run the following commands. You will need the ExchangeOneline PowerShell module installed to run the below commands. You will have to run the below commands as an Exchange Administrator. Copy and paste the value corresponding to the ‘OrganizationalUnitRoot’ attribute to Notepad for future reference.
Install-Module -Name ExchangeOnlineManagement
Connect-ExchangeOnline
Get-OrganizationalUnit

Grant the required permissions to the Managed Identity.
We need to grant the Function App’s managed identity Exchange Administrator permissions. The permissions can be granted using the script below. Before executing the script, you need to do the following:
- Install the Microsoft Graph SDK if you have not done so before. That happens by executing the following PowerShell command as an administrator:
Install-Module Microsoft.Graph -Scope CurrentUser
- Set your tenant ID and the managed identity (Principal) object ID you copied earlier as the script variable values.
Disconnect-MgGraph //Start by disconnecting old sessions.
$tenantId = "--Your Tenant ID from Notepad--"
$managedIdentityIds = "--Your Managed Identity (Principal) Object ID from Notepad--"
Connect-MgGraph -Scopes AppRoleAssignment.ReadWrite.All, Application.Read.All, RoleManagement.ReadWrite.Directory -TenantId $tenantId
$resourceId = (Get-MgServicePrincipal -Filter "AppId eq '00000002-0000-0ff1-ce00-000000000000'").Id
$appRoleId = "dc50a0fb-09a3-484d-be87-e023b12c6440"
$roleDefinitionId = (Get-MgRoleManagementDirectoryRoleDefinition -Filter "DisplayName eq 'Exchange Administrator'").Id
foreach ($managedIdentityId in $managedIdentityIds) {
New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $managedIdentityId -PrincipalId $managedIdentityId -AppRoleId $appRoleId -ResourceId $resourceId
New-MgRoleManagementDirectoryRoleAssignment -PrincipalId $managedIdentityId -RoleDefinitionId $roleDefinitionId -DirectoryScopeId "/"
}
Create a function within the Azure Function App
Open the ‘Overview’ node of the Function App. Click on the ‘Create Function’ button.

Choose ‘HTTP Trigger’ and click next.

On the next window, type ‘autoSetWebSignature‘ as the function name and choose ‘Function‘ as the Authorization level. Click Create.

When you click ‘Create,’ you will be presented with a ‘run.ps1‘ PowerShell file in edit mode. Review the file content and understand the layout. Replace the file content with the following code. Please do not forget to ‘Save.‘
using namespace System.Net
using namespace System.Web
# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)
# Write to the Azure Functions log stream.
Write-Host "A new process has started to set Exchange email signature for a User."
try {
$EXCHANGE_ORGANIZATIONAL_UNIT_ROOT = $env:EXCHANGE_ORGANIZATIONAL_UNIT_ROOT
if (-not [string]::IsNullOrEmpty($EXCHANGE_ORGANIZATIONAL_UNIT_ROOT) -and $EXCHANGE_ORGANIZATIONAL_UNIT_ROOT -ne "") {
# Get signature related environment variables
$AUTO_ADD_SIGNATURE_ON_NEW_MESSAGE = $true
$AUTO_ADD_SIGNATURE_ON_REPLY_MESSAGE = $true
try {$AUTO_ADD_SIGNATURE_ON_NEW_MESSAGE = [bool]::Parse($env:AUTO_ADD_SIGNATURE_ON_NEW_MESSAGE) } catch {$AUTO_ADD_SIGNATURE_ON_NEW_MESSAGE = $true} # Set as detault signature for New messages?
try {$AUTO_ADD_SIGNATURE_ON_REPLY_MESSAGE = [bool]::Parse($env:AUTO_ADD_SIGNATURE_ON_REPLY_MESSAGE) } catch {$AUTO_ADD_SIGNATURE_ON_REPLY_MESSAGE = $true} # Set as detault signature for Reply messages?
Import-Module ExchangeOnlineManagement -Force
Connect-ExchangeOnline -ManagedIdentity -Organization $EXCHANGE_ORGANIZATIONAL_UNIT_ROOT
$upn = $Request.Body.upn;
if (-not [string]::IsNullOrEmpty($upn) -and $upn -ne "") {
$signatureHtml = [System.Web.HttpUtility]::HtmlDecode($Request.Body.signatureHtml)
$signatureText = [System.Web.HttpUtility]::HtmlDecode($Request.Body.signatureText);
Write-Host "Starting the process to set Exchange email signature for mailbox with UPN => $upn."
if ((-not [string]::IsNullOrEmpty($signatureHtml)) -and ($signatureHtml -ne "") -and (-not [string]::IsNullOrEmpty($signatureText)) -and ($signatureText -ne "")) {
Write-Host "Setting HTML and TXT signatures for UPN => $upn."
Set-MailboxMessageConfiguration -Identity $upn -SignatureHTML $signatureHtml -SignatureText $signatureText
Write-Host "Finished setting HTML and TXT signature for UPN => $upn."
} elseif ((-not [string]::IsNullOrEmpty($signatureHtml)) -and ($signatureHtml -ne "")) {
Write-Host "Setting HTML signature for UPN => $upn."
Set-MailboxMessageConfiguration -Identity $upn -SignatureHTML $signatureHtml
Write-Host "Finished setting HTML signature for UPN => $upn."
} elseif ((-not [string]::IsNullOrEmpty($signatureText)) -and ($signatureText -ne "")) {
Write-Host "Setting TXT signature for UPN => $upn."
Set-MailboxMessageConfiguration -Identity $upn -SignatureText $signatureText
Write-Host "Finished setting TXT signature for UPN => $upn."
} else {
Write-Host "Signature files empty or NULL. Did not set HTML and TXT signatures for UPN => $upn."
}
Write-Host "Setting AUTO signature application settings for UPN => $upn."
Set-MailboxMessageConfiguration -Identity $upn -AutoAddSignature $AUTO_ADD_SIGNATURE_ON_NEW_MESSAGE -AutoAddSignatureOnReply $AUTO_ADD_SIGNATURE_ON_REPLY_MESSAGE
Write-Host "Finished setting AUTO signature application settings for UPN => $upn."
}
else {
Write-Host "Error: UPN not identified."
}
} else {
Write-Host "Error: No EXCHANGE_ORGANIZATIONAL_UNIT_ROOT value supplied for this transaction."
}
}
catch {
$ex = $_.Exception
Write-Host "Exception: The process failed. $($ex.Message)"
}
finally {
Disconnect-ExchangeOnline -Confirm:$false
Get-PSSession | Remove-PSSession
}

Copy the function key
Click on the “Function Keys” tab. Copy the function key of the new function and paste it into Notepad.

Set Azure Function App Environment variables
Move to the main page of the Function App. Go to ‘Settings/Environmental variables‘
We will have to input 3 environmental variables into the Azure Function App.
AUTO_ADD_SIGNATURE_ON_NEW_MESSAGE | Set this value to TRUE. |
AUTO_ADD_SIGNATURE_ON_REPLY_MESSAGE | Set this value to TRUE. |
EXCHANGE_ORGANIZATIONAL_UNIT_ROOT | Copy and paste this value from Notepad. We got this value in one of the previous steps. yourorgvalue.onmicrosoft.com |


Add an authentication identity provider
Go to ‘Settings/Authentication‘ and add ‘Microsoft‘ as the identity provider.

Accept the default settings for most of the values. Choose ‘Secret‘ expiration duration and remind the secret to be renewed.

In ‘Permissions‘, accept default values and click ‘Add‘.

The process will take you to a screen that resembles the image below. Click to open the newly created Identity Provider, which is a standard Entra ID App Registration object.

Click to open the newly created Identity Provider, a standard Entra ID App Registration object.
Copy the “Application (Client) ID” and the “Directory (tenant) ID” to Notepad. You would have already copied the Tenant ID in the previous steps.

Authentication Provider Platform configuration
The authentication provider ‘Microsoft’ is just an ‘app registration’ object in Entra ID. The automatic ‘app registration’ defaults are mostly correct but require a few modifications. Let’s remove the ‘Web‘ platform because the authentication happens using a Certificate and is non-interactive.

Remove Microsoft Graph API Permission
Remove any Admin Consents for ‘Microsoft Graph/User.Read‘ and Click on ‘Remove permission.‘

Authentication Provider Certificate based authentication setup
For production environments deploy a Certificate to the ‘CurrentUser‘ ‘Personal‘ certificate store using Microsoft Intune or Active Directory Group Policy. Identify the Certificate Thumbprint.

For this article, we will use a self-signed certificate. However, we recommend using a certificate from your internal CA or a reputable certificate authority.
!! Caution
Self-signed certificates are digital certificates that a trusted third-party CA doesn’t sign. They are created, issued, and signed by the company or developer responsible for the website or software being signed. This is why self-signed certificates are considered unsafe for public-facing websites and applications.
Use the following Powershell script to generate a self-signed certificate. The script will create two file.
wpsecure-auto-sync-signature.cer | Import into Azure Function authentication provider. |
wpsecure-auto-sync-signature.pfx | Secure it and use it in other automations or import certificate into other devices. |
# Define variables
$SubjectName = "wpsecure-auto-sync-signature"
$CertificateStore = "CurrentUser"
$ValidityPeriodMonths = 24
$CertificateFolderPath = "C:\temp\certs"
# Create a new self-signed certificate
$NewCertificateParams = @{
Subject = "CN=$($SubjectName)"
CertStoreLocation = "Cert:\$($CertificateStore)\My"
KeyExportPolicy = "Exportable"
KeySpec = "Signature"
NotAfter = (Get-Date).AddMonths($($ValidityPeriodMonths))
}
$Certificate = New-SelfSignedCertificate @NewCertificateParams
# Export the public key only
$ExportPublicKeyParams = @{
Cert = $Certificate
FilePath = "$($CertificateFolderPath)\$($SubjectName).cer"
}
Export-Certificate @ExportPublicKeyParams
# Export the certificate with the private key
$CertificateThumbprint = $Certificate.Thumbprint
$CertificatePassword = Read-Host -Prompt "Enter password for your certificate: " -AsSecureString
$ExportPfxParams = @{
Cert = "Cert:\$($CertificateStore)\My\$($CertificateThumbprint)"
FilePath = "$($CertificateFolderPath)\$($SubjectName).pfx"
ChainOption = "EndEntityCertOnly"
NoProperties= $null
Password = $CertificatePassword
}
Export-PfxCertificate @ExportPfxParams
# *** DELETE THE CERTIFICATE ***
try {
# Get the certificate again, as it might have a different path after export.
$CertificateToDelete = Get-ChildItem "Cert:\$($CertificateStore)\My\$($CertificateThumbprint)"
if ($CertificateToDelete) {
Remove-Item -Path $CertificateToDelete.PSPath -Force -Confirm:$false # -Confirm:$false suppresses confirmation prompts
Write-Host "Certificate '$SubjectName' deleted successfully."
} else {
Write-Warning "Certificate '$SubjectName' not found for deletion."
}
}
catch {
Write-Error "Error deleting certificate: $_"
}
# Best Practice: Clear the secure string password from memory
$CertificatePassword.Dispose()
# Best Practice: Check if the files were created
if (Test-Path "$($CertificateFolderPath)\$($SubjectName).cer") {
Write-Host "Public key certificate exported successfully."
} else {
Write-Warning "Public key certificate export failed."
}
if (Test-Path "$($CertificateFolderPath)\$($SubjectName).pfx") {
Write-Host "PFX certificate exported successfully."
} else {
Write-Warning "PFX certificate export failed."
}
Write-Host "Certificate Thumbprint ==> $CertificateThumbprint"
When you run the PowerShell script, you will get two certificates, and the script’s output will be the certificate thumbprint. Copy the thumbprint to Notepad.


Install (Rightclick Install PFX) the PFX certificate into the Current user’s “Personal” certificate store and make it ‘NonExportable.’ If you use a self-signed certificate, copy the certificate to the “Trusted Root Certification Authorities” as well.

Upload the Certificate (.cer) file to the Azure Functions authentication provider object.
From within the Authentication Provider object (App registration), click on the ‘Manage/Certificate and secrets‘ node and then on the ‘Certificates‘ tab. Click on the ‘Upload certificate‘ button.


Verify if the imported thumbprint matches the thumbprint that you copied to Notepad.

Prepare Powershell modules for upload to Azure Function App
Let’s upload the following PowerShell modules to the Function App from your computer. The reason for doing so is simple: The Function App will time-out if the modules were to be downloaded and installed at Function run time.
Install the following modules on your computer. Aim to download and install the latest but stable version.
Az.Accounts | 4.0.2 |
Az.ManagedServiceIdentity | 1.3.0 |
ExchangeOnlineManagement | 3.7.1 |
PowerShellGet | 2.2.5 |
PackageManagement | 1.4.8.1 |
Microsoft.Graph.Users | 2.19.0 |
Microsoft.Graph.Authentication | 2.24.0 |
Some modules might not be required, but we recommend uploading them. These modules will allow you to expand the Azure Function to accommodate your organization-specific needs.
Install the latest or the most appropriate version of each of the above-listed modules on your computer. The newly installed modules will be found at the following location, Assuming the ‘C:’ drive contains your Program Files folder.
C:\Program Files\WindowsPowerShell\Modules
Let’s copy the required modules from the above location to a temp location of your choice.

Look for the required PowerShell modules and copy the module folder to your temporary location. Each module could have one or more versions within subfolders. For the sake of space management, we recommend deleting unnecessary versions.

Compress all of the above folders into a ZIP file and call the ZIP file “PSModules.zip“.

Add references to Powershell modules in the Azure Function App
Open the Azure Function App in the portal. Go to ‘Function/App files‘.
Paste the following content into the ‘requirements.psd1′ file. Make sure you use the correct version for each of these modules.
'Az.Accounts' = '4.0.2'
'Az.ManagedServiceIdentity' = '1.3.0'
'ExchangeOnlineManagement' = '3.7.1'
'PowerShellGet' = '2.2.5'
'PackageManagement' = '1.4.8.1'
'Microsoft.Graph.Users' = '2.19.0'
'Microsoft.Graph.Authentication' = '2.24.0'
Paste the above content under the commented module #’AZ’ = ’13.*’.


Please Save your Function App before you proceed with the setup.
Deploy Powershell modules to the Azure Function App
Upload the ZIP file containing the Powershell modules to the Function App. Open the Function App in the portal and go to ‘Development Tools/Advanced Tools.‘


Clicking ‘Go‘ will open Microsoft KUDU+ Environment.

Click on the ‘Debug console‘ menu item and then ‘PowerShell.‘

Click on the directory structure at the top. Change directory to ‘site/wwwroot/‘. Create a directory called “Modules“.


The individual module folders will be placed inside the “Modules” directory. Click to get into the Modules directory.

After uploading the Powershell modules, the ‘Modules‘ directory will look similar to the image below.

Restart the Azure Function App from the overview page.
The Azure function is now ready to receive a request from WPSecure. The rest of the article will explain how to set up a WPSecure package to call the Azure function.
Ensure you save the Notepad file with the necessary inputs for future use. The WPSecure Signature Processing Engine will use the highlighted items to communicate with this Azure Function App.

Enabling or disabling email signature sync with Exchange Online
By default, automatic signature sync to Exchange Online is disabled. To enable it, run the following command as an administrator. The executable is in the WPSecure application directory within the ProgramFiles (x86) directory.

wpsecure-set.exe -defaultdomain wpsecure-auto-sync-signature.azurewebsites.net -functionkey TTCVeEoUyQ2dEiDFG8acXDER9HgaEqNoX0oNak0DFWE-aiAzFu7rXAWg== -tenantid 9fa4d4d6-7541-490f-a49a-0012345392731f -clientid 8d349d-57ec-4073-b1118-bfc5a07edad8 -thumbprint 0DE027D649973659FDR1DBC906ACD5FW8AA9
To disable it, run the following command as an administrator.
wpsecure-set.exe -dawsu
Troubleshooting
After a successful setup, the process runs on autopilot. But getting there is never a straight line. That is when ‘Log stream‘ plays a vital role.
- Devices have to authenticate to access the function.
- Devices must use the correct “function code” to access the function.
- If no logs appear in the “Log steam,” the device has not correctly authenticated to the Function App.
- From within the function, all the PowerShell modules should have been loaded.
- The System assigned managed identity should have access to Exchange Online.
Client-side logs will be in ‘wpsecure-set.log‘ in the ‘%TEMP%’ directory
