Skip to content

Your April RDP Fix Is Already Legacy

Microsoft renamed the .rdp trusted-publisher policy and added SHA-2 support in the July 2026 update and Intune still shows the old SHA-1 label. Here is the complete deployment, signing, and audit playbook.

  • The April 2026 security update (CVE-2026-26151) rewrote the Remote Desktop Connection client’s RDP-file experience. Every .rdp file now throws a security dialog before any connection, and every redirection is off by default until the user explicitly ticks it.
  • The July 2026 security update (KB5101650) renamed the Group Policy setting from *Specify SHA1 thumbprints of certificates representing trusted .rdp publishers* to *Specify thumbprints of certificates representing trusted .rdp publishers*, and added SHA-2 support. SHA-1 is now backward-compatibility only and is scheduled for removal.
  • If you deployed the SHA-1 thumbprint workaround in April, you are now pinned to a deprecated algorithm on a control Microsoft has publicly flagged for removal.
  • If you deployed the RedirectionWarningDialogVersion registry stopgap, you are relying on a value Microsoft says a future update might remove, even on older versions of Windows.
  • Intune has not caught up. The Settings Catalog and the Policy CSP documentation both still carry the old SHA-1 name and SHA-1-only description. This article explains why that is probably cosmetic, how to prove it in your own lab, and how to deploy the control correctly from Intune anyway Settings Catalog, OMA-URI, and PowerShell.

Timeline: what changed, and when

DateWhat happenedWhat it means for you
Pre-April 2026Opening a .rdp file showed the legacy warning with a “don’t ask me again” checkbox. Redirections declared in the file were applied silently.A phishing .rdp file could silently map local drives and clipboard on first connect.
14 April 2026CVE-2026-26151 fix. New first-launch educational dialog, plus a per-launch connection security dialog. All redirections off by default.Helpdesk volume spike. Every unsigned internal .rdp file now shows “Unknown publisher”.
April – June 2026Field workarounds spread: pin a SHA-1 thumbprint via GPO, or set RedirectionWarningDialogVersion = 1 to revert the dialog.Both are now legacy. One is deprecated, the other is explicitly temporary.
14 July 2026KB5101650. Policy renamed; SHA-2 thumbprints supported; SHA-1 retained for backward compatibility only and planned for removal. New Learn article published same day.Re-do your thumbprint deployment on SHA-256 before SHA-1 support is pulled.
TodayIntune Settings Catalog and the ADMX_TerminalServer Policy CSP page still show the old SHA-1 name and description.Portal metadata is stale. Device-side behavior is what actually matters.

What your users actually see now

Two distinct dialogs were introduced in April. Understanding the difference matters, because your remediation target is the second one, not the first.

The first-launch dialog

The first time a user opens any .rdp file after the update, an educational dialog appears explaining what RDP files are and warning about phishing. Once the user allows RDP file connections, it does not appear again for that account. It is per-user, not per-file.

The connection security dialog

This one fires every time an .rdp file is opened, before any connection is made. It shows the remote computer address and a checkbox for each local resource the file requests. Access to all of these is off by default, the user must explicitly enable each one. It has two variants:

Unsigned file – banner reads *”Caution: Unknown remote connection”*, and the Publisher field reads *”Unknown publisher”*.

Signed file – banner reads *”Verify the publisher of this remote connection”*, and the publisher’s name is displayed.

Signing is identity, not safety

A signature confirms who created the file and that it has not been tampered with since. It does not make the file safe. An attacker can obtain a certificate in a name that closely resembles yours, “Contoso Security” instead of “Contoso Ltd.” Train users to read the publisher name, not just look for the absence of a warning.

What connections are NOT affected

Connections started manually from the Remote Desktop Connection UI by typing a computer name are unchanged. This update only affects connections started by opening an .rdp file.

Azure Virtual Desktop and Windows 365 files are typically signed by Microsoft, so users should not see the new dialog for those. If they do, that is a signal to investigate not to click through.

The redirection inventory and why each one matters

The dialog surfaces every redirection the file requests. This table is worth putting in front of your security team, because it reframes “an annoying popup” as “an exfiltration checklist”.

RedirectionWhat it exposesAttack value
DrivesLocal hard drives, USB drives, and network-mapped drives, readable and writable from the remote host.Highest. File theft, credential harvesting from disk, malware written to the Startup folder, and lateral reach into mapped network shares.
ClipboardEverything copied on the local device, both directions.Password capture from password managers; malicious content injected for the user to paste locally.
Smart card / Windows Hello for BusinessLocal smart card and WHfB credentials usable by the remote host.Authenticate as the user on the remote system and anything reachable from it.
WebAuthn (Hello / FIDO2 keys)Local security keys and passkeys used to answer remote web-auth challenges.Authentication prompts relayed from a hostile session to the local device.
MicrophoneLocal audio recording devices.Eavesdropping on the room, meetings, calls.
CameraLocal video capture devices.Visual surveillance surroundings, desk documents, screens.
LocationDevice geographic location.Physical targeting; sensitive for executive, law-enforcement, and field roles.
PrintersLocal printers.Nuisance and social engineering printing convincing fake documents locally.
Ports (COM / LPT)Serial and parallel ports.Access to legacy or specialist attached hardware.
Point-of-service devicesBarcode scanners, receipt printers.Transaction interference, reading financial data from attached devices.
Other PnP devicesA wide range of USB and other peripherals.Data read, device interaction, pivot point.
RemoteFX USBLow-level USB redirection with deeper access than standard redirection.Reaches authentication tokens and storage at a level that bypasses typical application-layer protections.

Note that the available set differs by OS version, older builds support a different redirection list, so your dialog screenshots will not match across a mixed fleet.

WebAuthn has a built-in tell

When a WebAuthn request is redirected through a remote session, Windows shows that fact in the authentication prompt, wording along the lines of “Requested from a remote session by Remote Desktop Connection”. Put that in your user awareness material: if you see it and you did not expect it, do not approve.

The July change, precisely

From the Microsoft Learn article published 14 July 2026:

Verbatim from Microsoft Learn

Before the July 2026 security update, the “Specify thumbprints of certificates representing trusted .rdp publishers” policy was named “Specify SHA1 thumbprints of certificates representing trusted .rdp publishers” and only accepted SHA-1 thumbprints. After the update, it supports SHA-2 thumbprints. SHA-1 thumbprints are supported for backward compatibility only, do not use them for certificate pinning. A future release will remove support for SHA-1 thumbprints. Migrate to SHA-256 thumbprints or stronger to avoid disruption.

The KB5101650 release notes describe the same change from the servicing side: support for SHA-2 certificate thumbprints added for trusted RDP publishers, with SHA-1 retained only for backward compatibility and planned for future removal, alongside new guidance for managing RDP file security through Group Policy.

The undocumented part

Microsoft’s article ends that section with a pointer, not an answer:

The gap

After installing the July 2026 security update, refer to the Help section of the policy in the Local Group Policy Editor to learn more about how to configure SHA-2 certificates and the required prefixes.

“The required prefixes” are not documented anywhere on Microsoft Learn. They exist only in the in-box ADMX/ADML resources on a patched machine.

Proof: the rename in the wild

Two machines, same OU, same everything except the July cumulative update. Local Group Policy Editor, Computer Configuration → Administrative Templates → Windows Components → Remote Desktop Services → Remote Desktop Connection Client:

A screenshot is suggestive. The file system is evidence. Run this on both machines:

PowerShell  —  prove the ADMX changed on disk
$admx = "$env:SystemRoot\PolicyDefinitions\TerminalServer.admx"
$adml = "$env:SystemRoot\PolicyDefinitions\en-US\TerminalServer.adml"
 
[PSCustomObject]@{
    Build   = "{0}.{1}" -f (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").CurrentBuild,
                            (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").UBR
    AdmxSize    = (Get-Item $admx).Length
    AdmxWritten = (Get-Item $admx).LastWriteTime
    AdmlSize    = (Get-Item $adml).Length
    AdmlWritten = (Get-Item $adml).LastWriteTime
    AdmxHash    = (Get-FileHash $admx -Algorithm SHA256).Hash
} | Format-List
 
# The friendly-name string itself
Select-String -Path $adml -Pattern 'thumbprints of certificates representing trusted'

Differing LastWriteTime, differing SHA-256, and a differing .adml display string is a far stronger artifact than a UI capture, and it is what makes the post citable.

Getting the truth out of the ADMX

Before you can push this from Intune via OMA-URI you need three things the portal will not tell you: the element ID of the thumbprint list, the registry subkey the list writes to, and the value prefix, which is almost certainly what Microsoft means by “the required prefixes”.

All three are attributes on the <list> element inside TerminalServer.admx. Extract them:

PowerShell  —  extract element IDs, list keys, and value prefixes
[xml]$admx = Get-Content "$env:SystemRoot\PolicyDefinitions\TerminalServer.admx" -Raw
 
$admx.policyDefinitions.policies.policy |
    Where-Object { $_.name -like 'TS_CLIENT_TRUSTED_CERTIFICATE_THUMBPRINTS*' -or
                   $_.name -like 'TS_CLIENT_ALLOW_*SIGNED_FILES*' } |
    ForEach-Object {
        $p = $_
        $els = @()
        if ($p.elements) {
            foreach ($e in $p.elements.ChildNodes) {
                $els += [PSCustomObject]@{
                    Type        = $e.LocalName          # list / text / boolean / enum
                    Id          = $e.id                 # <-- OMA-URI data id
                    Key         = $e.key                # <-- registry subkey the list writes
                    ValuePrefix = $e.valuePrefix        # <-- the "required prefix"
                    ExplicitVal = $e.explicitValue
                    Additive    = $e.additive
                }
            }
        }
        [PSCustomObject]@{
            Policy   = $p.name
            Class    = $p.class                          # Machine | User | Both
            Key      = $p.key
            ValueName= $p.valueName
            Elements = $els
        }
    } | ForEach-Object { $_; $_.Elements | Format-Table -AutoSize | Out-String | Write-Host }

Working hypothesis worth testing and publishing

ADMX list elements carry an optional valuePrefix attribute that Windows prepends to every stored entry. Microsoft telling admins to read the Help section “to learn how to configure SHA-2 certificates and the required prefixes” strongly suggests SHA-2 entries must be written with an algorithm prefix so the client can distinguish a 64-character SHA-256 hash from a 40-character SHA-1 hash. Do not assume the exact syntax, enable the policy in gpedit with a real SHA-256 thumbprint, then dump the registry and read the literal string Windows wrote. Whatever appears there is the authoritative answer, and publishing it is the single most valuable thing in this article.

Dump exactly what the policy writes

Enable the policy locally with a known SHA-1 value, dump, then repeat with a known SHA-256 value and diff. This is how you discover the real storage format without guessing.

PowerShell  —  discover the real registry shape
$root = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services'
 
function Dump-PolicyTree {
    param([string]$Path)
    if (-not (Test-Path $Path)) { Write-Host "absent: $Path"; return }
    Get-Item $Path | ForEach-Object {
        $k = $_
        Write-Host "`n[$($k.Name)]" -ForegroundColor Cyan
        foreach ($v in $k.GetValueNames()) {
            "{0,-24} {1,-12} {2}" -f $v, $k.GetValueKind($v), $k.GetValue($v)
        }
    }
    Get-ChildItem $Path -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
        Write-Host "`n[$($_.Name)]" -ForegroundColor Cyan
        foreach ($v in $_.GetValueNames()) {
            "{0,-24} {1,-12} {2}" -f $v, $_.GetValueKind($v), $_.GetValue($v)
        }
    }
}
 
Dump-PolicyTree -Path $root

The three policies and their exact CSP mappings

Only three Group Policy settings govern this behaviour. All three live under Windows Components → Remote Desktop Services → Remote Desktop Connection Client and all three are ADMX-backed, meaning they are exposed through the ADMX_TerminalServer area of the Policy CSP.

SettingControlsCSP node
Allow .rdp files from valid publishers and user’s default .rdp settingsWhether users can open signed RDP files or launch connections manually. Disable to block every RDC launch that does not come from a trusted-publisher-signed file.TS_CLIENT_ALLOW_SIGNED_FILES_1 (User)  ·  TS_CLIENT_ALLOW_SIGNED_FILES_2 (Device)  ·  Registry value: AllowSignedFiles
Allow .rdp files from unknown publishersWhether users can open unsigned RDP files. Disable to block all unsigned files.TS_CLIENT_ALLOW_UNSIGNED_FILES_1 (User)  ·  TS_CLIENT_ALLOW_UNSIGNED_FILES_2 (Device)  ·  Registry value: AllowUnsignedFiles
Specify thumbprints of certificates representing trusted .rdp publishersThe trusted publisher thumbprint list. A match means no security warning at all, and the redirections the file requests are enabled automatically.TS_CLIENT_TRUSTED_CERTIFICATE_THUMBPRINTS_1 (Device)  ·  TS_CLIENT_TRUSTED_CERTIFICATE_THUMBPRINTS_2 (User)

Trap #1 – the suffix numbering is inverted on the thumbprint policy

On the two “Allow” policies, _1 is the USER-scoped node and _2 is the DEVICE-scoped node. On the thumbprint policy it is the other way round: _1 is DEVICE and _2 is USER. Anyone who pattern-matches the suffix across the three settings will scope the thumbprint list backwards, and it will fail silently, the profile still reports Success. Verify scope against the CSP reference every time, not against the setting next to it.

Behavioural rules that are easy to miss

  • The thumbprint policy overrides the behaviour of *Allow .rdp files from valid publishers and user’s default .rdp settings*. Pinning a thumbprint is stronger than the general “valid publisher” allowance.
  • Device and user lists combine. If you define the list in both Computer Configuration and User Configuration, the trusted set for that user is the union of both. That is a delegation risk if user-scoped policy is looser than device-scoped policy.
  • Malformed entries are silently ignored. If the list contains a string that is not a certificate thumbprint, Windows discards it. There is no error, no event, and the policy still reports as applied. A typo in a SHA-256 value means you have deployed a policy that trusts nothing while every console you own shows green.
  • Applicability: Windows 10 version 2004 with KB5005101 and later, and Windows 11 21H2 and later. Pro, Enterprise, Education, and IoT Enterprise / IoT Enterprise LTSC.

Trap #2 – this control fails open, loudly reported as success

Combine “malformed entries are ignored” with “ADMX-backed policies report Success once the SyncML is accepted” and you get the worst class of security control: one that reports healthy while enforcing nothing. Section 11 builds the detection that closes this gap. Do not deploy this policy without it.

The Intune documentation gap and why it probably does not matter

Search the Settings Catalog for the setting today and you get the pre-July label, for both scopes:

The ADMX_TerminalServer Policy CSP reference page tells the same stale story. It still carries the old friendly name and still describes the behavior as SHA-1 only, “any certificate with an SHA1 thumbprint that matches a thumbprint on the list is trusted.”

Why the stale label is probably cosmetic

This is the analytical core of the article, and it is worth stating carefully.

ADMX-backed policies are not evaluated in the cloud. Intune transmits a SyncML payload naming the policy and its element data; the MDM client on the device resolves that payload against the ADMX definitions installed locally on that device. The Settings Catalog label is metadata rendered from Microsoft’s service-side copy of the ADMX, presentation only.

If that model holds, then a July-patched client should honour a SHA-256 thumbprint pushed through the Settings Catalog even though the portal calls the setting “SHA1” because the client-side ADMX is the one that changed, and the client is what does the work.

What to do while it is unproven

  • Deploy through the Settings Catalog if the test comes back (a). It is supported, reportable, and survives ADMX changes.
  • Fall back to a custom OMA-URI profile if you hit (b) – you bypass the catalog’s presentation layer and speak directly to the CSP node.

Fall back to Remediations only if both fail, and understand what you are giving up.

Deploying from Intune – Settings Catalog

The supported path, and the one to try first.

  1. Intune admin center → Devices → Manage devices → Configuration → Create → New policy.
  2. Platform: Windows 10 and later. Profile type: Settings catalog.
  3. Name it explicitly, e.g. WIN-SEC-RDP-TrustedPublishers-SHA256-v1. Version the name — you will be rotating certificates against it.
  4. Add settings → browse to Administrative Templates \ Windows Components \ Remote Desktop Services \ Remote Desktop Connection Client.
  5. Select Specify SHA1 thumbprints of certificates representing trusted .rdp publishers — the device-scoped one, *without* the (User) suffix. Despite the label, this maps to TS_CLIENT_TRUSTED_CERTIFICATE_THUMBPRINTS_1.
  6. Set it to Enabled and add your SHA-256 thumbprint entries, one per line, using whatever prefix syntax.
  7. In the same profile, add Allow .rdp files from unknown publishersDisabled, and Allow .rdp files from valid publishers and user’s default .rdp settingsDisabled if you are going to the high-security configuration.
  8. Assign to a pilot group. Do not assign to All Devices on day one.

Assignment scope matters more than usual here

The device-scoped setting applies to every user on the machine. The user-scoped setting applies to the user across every machine. Because the two lists combine, assigning both with different contents produces a trusted set larger than either policy shows on its own. Pick one scope and stay there. Device scope is correct for almost every corporate fleet.

Deploying from Intune – custom OMA-URI

Use this when the Settings Catalog rejects your value, when you need to be certain exactly what payload reaches the device, or when you want the deployment to be self-documenting in source control.

Profile creation

  1. Devices → Configuration → Create → New policy → Platform Windows 10 and later → Profile type TemplatesCustom.
  2. Add a row per OMA-URI setting below.

The OMA-URI settings

PurposeOMA-URITypeValue
Trusted publisher thumbprints (device)./Device/Vendor/MSFT/Policy/Config/ADMX_TerminalServer/TS_CLIENT_TRUSTED_CERTIFICATE_THUMBPRINTS_1String<enabled/> <data id=”TS_CLIENT_TRUSTED_CERTIFICATE_THUMBPRINTS_LIST”
      value=”1&#xF000;THUMBPRINT_ONE&#xF000;2&#xF000;THUMBPRINT_TWO”/>
Block unsigned .rdp files./Device/Vendor/MSFT/Policy/Config/ADMX_TerminalServer/TS_CLIENT_ALLOW_UNSIGNED_FILES_2String<disabled/>
Block valid-publisher + manual launches./Device/Vendor/MSFT/Policy/Config/ADMX_TerminalServer/TS_CLIENT_ALLOW_SIGNED_FILES_2String<disabled/>

The list payload

ADMX-backed list elements take an indexed payload delimited by the Unicode character U+F000. In an Intune custom profile you enter it XML-encoded as &#xF000;. The shape is index, delimiter, value, delimiter, index, delimiter, value:

OMA-URI value  —  template
<enabled/>
<data id="TS_CLIENT_TRUSTED_CERTIFICATE_THUMBPRINTS_LIST"
      value="1&#xF000;THUMBPRINT_ONE&#xF000;2&#xF000;THUMBPRINT_TWO"/>

Generating the payload with PowerShell

Do not hand-assemble this. Generate it, so rotation is a one-line change.

PowerShell  —  build a valid OMA-URI list payload
function New-RdpThumbprintPayload {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string[]] $Thumbprint,
        [Parameter(Mandatory)][string]   $ElementId,   # from the ADMX (see section 5)
        [string] $ValuePrefix = ''                     # from the ADMX valuePrefix, if any
    )
 
    $clean = foreach ($t in $Thumbprint) {
        $n = ($t -replace '[^0-9A-Fa-f]', '').ToUpperInvariant()
        switch ($n.Length) {
            40 { Write-Warning "SHA-1 thumbprint supplied ($n). Deprecated - migrate to SHA-256." }
            64 { }
            default { throw "Not a valid SHA-1 (40) or SHA-256 (64) thumbprint: '$t' (length $($n.Length))" }
        }
        "$ValuePrefix$n"
    }
 
    $sb = [System.Text.StringBuilder]::new()
    for ($i = 0; $i -lt $clean.Count; $i++) {
        if ($i -gt 0) { [void]$sb.Append('&#xF000;') }
        [void]$sb.Append(($i + 1)).Append('&#xF000;').Append($clean[$i])
    }
 
    "<enabled/><data id=""$ElementId"" value=""$($sb.ToString())""/>"
}
 
# Example
New-RdpThumbprintPayload -ElementId 'TS_CLIENT_TRUSTED_CERTIFICATE_THUMBPRINTS_LIST' `
                         -Thumbprint 'A1B2...64hex', 'C3D4...64hex'

The validation in that function is the point. It refuses anything that is not 40 or 64 hex characters, which is precisely the failure mode Windows silently swallows.

Deploying from Intune – the PowerShell path

When to use it

  • The Settings Catalog rejects SHA-2 and you cannot use OMA-URI for a policy reason.
  • You need conditional logic for example, apply a different thumbprint set to a specific business unit based on installed software.

You are remediating the stopgap registry value rather than deploying the thumbprint list itself. This is the strongest use case.

The script

PowerShell  —  Set-RdpTrustedPublishers.ps1
<#
    Set-RdpTrustedPublishers.ps1
    Writes the trusted .rdp publisher thumbprint list and the two Allow policies.
    Run in SYSTEM context (Intune Remediations / Win32 app).
#>
[CmdletBinding()]
param(
    [Parameter(Mandatory)][string[]] $Thumbprint,
    [string]  $ListSubKey    = 'TrustedCertThumbprints',  # CONFIRM against section 5 output
    [string]  $ValuePrefix   = '',                          # CONFIRM against section 5 output
    [switch]  $BlockUnsigned,
    [switch]  $BlockUnpinned
)
 
$ErrorActionPreference = 'Stop'
$tsRoot   = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services'
$listPath = Join-Path $tsRoot $ListSubKey
 
# --- normalise + validate ------------------------------------------------
$entries = foreach ($t in $Thumbprint) {
    $n = ($t -replace '[^0-9A-Fa-f]', '').ToUpperInvariant()
    if ($n.Length -notin 40, 64) { throw "Invalid thumbprint length ($($n.Length)): $t" }
    if ($n.Length -eq 40) { Write-Warning "SHA-1 entry $n is deprecated." }
    "$ValuePrefix$n"
}
if (-not $entries) { throw 'No valid thumbprints supplied.' }
 
# --- write the list ------------------------------------------------------
if (Test-Path $listPath) { Remove-Item $listPath -Recurse -Force }
New-Item -Path $listPath -Force | Out-Null
for ($i = 0; $i -lt $entries.Count; $i++) {
    New-ItemProperty -Path $listPath -Name ($i + 1) -Value $entries[$i] `
                     -PropertyType String -Force | Out-Null
}
 
# --- the two Allow policies ---------------------------------------------
if (-not (Test-Path $tsRoot)) { New-Item -Path $tsRoot -Force | Out-Null }
if ($BlockUnsigned) {
    New-ItemProperty -Path $tsRoot -Name AllowUnsignedFiles -Value 0 -PropertyType DWord -Force | Out-Null
}
if ($BlockUnpinned) {
    New-ItemProperty -Path $tsRoot -Name AllowSignedFiles   -Value 0 -PropertyType DWord -Force | Out-Null
}
 
Write-Output "Wrote $($entries.Count) thumbprint(s) to $listPath"

What you give up with the script path

CapabilitySettings Catalog / OMA-URIPowerShell script
Per-device apply/error reporting in IntuneYesDetection output only
Automatic removal when assignment is removedYesNo – values persist
Conflict detection against other profilesYesNo
Survives ADMX schema changesYesNo – you own the registry contract
Conditional / computed valuesNoYes
Works when the catalog rejects the valueNoYes

Retiring the April stopgap

Many organisations reverted the new dialog in April with a registry value. Microsoft is explicit that this is temporary and may be removed by a future update, even on older versions of Windows. Find it and remove it before it is removed for you, unannounced, mid-quarter.

PowerShell  —  Intune Remediation pair
# Detection — is the legacy dialog stopgap in place?
$k = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services\Client'
$v = (Get-ItemProperty -Path $k -Name RedirectionWarningDialogVersion -ErrorAction SilentlyContinue).RedirectionWarningDialogVersion
if ($null -ne $v) { Write-Output "Stopgap present: RedirectionWarningDialogVersion=$v"; exit 1 }
Write-Output 'Stopgap absent'; exit 0
 
# Remediation — remove it
Remove-ItemProperty -Path $k -Name RedirectionWarningDialogVersion -Force -ErrorAction SilentlyContinue
Write-Output 'Removed RedirectionWarningDialogVersion'

Developers embedding the Remote Desktop ActiveX control (mstscax.dll) have an equivalent lever through the RedirectionWarningDialogVersion property on IMsRdpExtendedSettings. If you have in-house applications wrapping RDC, that is where to look and the same deprecation warning applies.

Certificate strategy

The thumbprint list is only as good as the key behind it. Get this wrong and you have built a phishing primitive with your own logo on it.

Choosing the certificate

OptionWhen it fitsWatch out for
Internal PKI code-signing certificateAny organisation with ADCS or Cloud PKI. The right default.Template must include the Code Signing EKU. Plan the renewal, thumbprint changes on renewal.
Public code-signing certificateYou distribute RDP files to parties outside your tenant.Cost, and hardware-token requirements on modern issuance rules make automation awkward.
Self-signedLab and proof-of-concept only.Thumbprint pinning makes it *work*, which is exactly the trap, no revocation, no chain, no governance.
RDS RDPublishing role certificateYou publish RemoteApp or desktop collections.Server Manager generates self-signed by default; use Set-RDCertificate to bind a proper chained certificate instead.

Creating and protecting the key

PowerShell  —  obtain the signing certificate and both thumbprints
# Internal PKI (preferred) - request against a Code Signing template
# Get-Certificate -Template "CodeSigningRDP" -CertStoreLocation Cert:\LocalMachine\My
 
# Lab only - self-signed code signing certificate
$cert = New-SelfSignedCertificate `
    -Type CodeSigningCert `
    -Subject 'CN=Contoso RDP Publishing' `
    -KeyUsage DigitalSignature `
    -KeyAlgorithm RSA -KeyLength 3072 `
    -HashAlgorithm SHA256 `
    -NotAfter (Get-Date).AddYears(2) `
    -CertStoreLocation 'Cert:\LocalMachine\My'
 
# The SHA-256 thumbprint you will deploy
$sha256 = (Get-FileHash -InputStream ([IO.MemoryStream]::new($cert.RawData)) -Algorithm SHA256).Hash
 
[PSCustomObject]@{
    Subject   = $cert.Subject
    NotAfter  = $cert.NotAfter
    Sha1      = $cert.Thumbprint          # legacy - what the April workaround used
    Sha256    = $sha256                   # what you deploy now
} | Format-List

Signing your RDP files

Manual signing with rdpsign.exe

rdpsign.exe ships with Windows and signs an individual .rdp file. It suits files distributed by email, file share, or intranet portal.

PowerShell  —  sign and verify .rdp files
<#
    Invoke-RdpFileSigning.ps1
    Signs one or many .rdp files with SHA-256 and verifies the result.
#>
[CmdletBinding()]
param(
    [Parameter(Mandatory)][string] $Thumbprint,   # SHA-1 thumbprint of the SIGNING CERT (cert store id)
    [Parameter(Mandatory)][string[]] $Path
)
 
$ErrorActionPreference = 'Stop'
$cert = Get-ChildItem Cert:\LocalMachine\My, Cert:\CurrentUser\My |
        Where-Object { $_.Thumbprint -eq ($Thumbprint -replace '\s', '') } |
        Select-Object -First 1
if (-not $cert)             { throw "Signing certificate $Thumbprint not found." }
if (-not $cert.HasPrivateKey) { throw 'Certificate has no private key on this machine.' }
 
foreach ($file in (Get-Item $Path)) {
    if ($file.Extension -ne '.rdp') { Write-Warning "Skipping $($file.Name)"; continue }
 
    # /sha256 takes the certificate hash with spaces removed
    $args = @('/sha256', ($cert.Thumbprint -replace '\s', ''), "`"$($file.FullName)`"")
    $p = Start-Process -FilePath "$env:SystemRoot\System32\rdpsign.exe" `
                       -ArgumentList $args -NoNewWindow -Wait -PassThru
 
    $signed = Select-String -Path $file.FullName -Pattern '^signature:s:' -Quiet
    [PSCustomObject]@{
        File     = $file.Name
        ExitCode = $p.ExitCode
        Signed   = [bool]$signed
        SignedAt = (Get-Date).ToString('s')
    }
}

The signature:s: line check matters. rdpsign can exit zero in situations where the file was not actually signed; reading the file back is the only honest verification.

Automated signing in RDS collections

If you publish through RemoteApp or virtual desktop collections, the certificate bound to the RDPublishing role signs every published RemoteApp and desktop connection automatically, no per-file step. The Server Manager RDS certificate UI generates self-signed certificates; for a properly chained certificate, bind it with Set-RDCertificate against the RDPublishing role instead.

Distributing the trust

Signing alone changes the dialog from “Unknown publisher” to a named publisher. It does not remove the dialog. Managed devices skip the warning only for a signing certificate they trust, which is what the thumbprint policy delivers. Two artifacts, two pipelines:

  • The signing certificate’s thumbprint → the trusted publisher policy.
  • The certificate chain → an Intune Trusted certificate profile, if you also want the file to validate as a “valid publisher” for the second policy. Optional when you pin, mandatory when you rely on chain validation.

Rotation: the thing that will bite you in 24 months

Certificates expire. Thumbprints change. If you swap the certificate and the policy in the same change window, every signed .rdp file in your estate throws warnings until policy converges across the fleet, which on a laptop population means days, not minutes.

Run an overlap window instead:

  1. T-60 days. Issue the new signing certificate. Do not sign anything with it yet.
  2. T-45. Add the new thumbprint to the policy alongside the old one. The list accepts multiple entries; both are now trusted.
  3. T-30. Confirm ≥98% policy convergence using the audit script. Do not proceed until you can prove it, this is the gate.
  4. T-14. Re-sign all distributed .rdp files with the new certificate. Devices trust both, so nothing breaks.
  5. T-0. Old certificate expires. Nothing happens, because nothing depends on it.
  6. T+30. Remove the old thumbprint from the policy. Archive the old private key; do not delete it until you have confirmed no signed artifact still references it.

The audit script

There is no native report for any of this. Build one. This runs as an Intune Remediation detection script, emits a single-line JSON object for the remediation output column, and exits non-zero when the device needs attention.

PowerShell  —  Get-RdpTrustState.ps1
<#
    Get-RdpTrustState.ps1  |  Intune Remediation - DETECTION
    Reports RDP file trust posture. Exit 1 = attention required.
    Run as SYSTEM, 64-bit.
#>
 
$ErrorActionPreference = 'Stop'
$r = [ordered]@{}
 
function Classify-Thumbprint {
    param([string] $Raw)
    $prefix = ''
    $val    = $Raw
    if ($Raw -match '^(?<p>[^0-9A-Fa-f]*[A-Za-z0-9]+[:=])(?<v>.+)$') {
        $prefix = $Matches.p; $val = $Matches.v
    }
    $hex = ($val -replace '[^0-9A-Fa-f]', '')
    $alg = switch ($hex.Length) { 40 { 'SHA1' } 64 { 'SHA256' } default { 'INVALID' } }
    [PSCustomObject]@{ Prefix = $prefix; Hex = $hex.ToUpperInvariant(); Length = $hex.Length; Algorithm = $alg }
}
 
try {
    # --- build ----------------------------------------------------------
    $cv = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
    $r.Build   = "{0}.{1}" -f $cv.CurrentBuild, $cv.UBR
    $r.Edition = $cv.EditionID
    # July 2026 cumulative: 26100.8875 (24H2) / 26200.8875 (25H2)
    $r.JulyPatched = ([int]$cv.CurrentBuild -ge 26100 -and [int]$cv.UBR -ge 8875) -or [int]$cv.CurrentBuild -gt 26200
 
    # --- ADMX on disk ---------------------------------------------------
    $adml = "$env:SystemRoot\PolicyDefinitions\en-US\TerminalServer.adml"
    if (Test-Path $adml) {
        $txt = Get-Content $adml -Raw
        $r.AdmlRenamed = ($txt -match '>Specify thumbprints of certificates representing trusted')
        $r.AdmlLegacy  = ($txt -match '>Specify SHA1 thumbprints of certificates representing trusted')
        $r.AdmlWritten = (Get-Item $adml).LastWriteTime.ToString('s')
    } else { $r.AdmlRenamed = $false; $r.AdmlLegacy = $false }
 
    # --- policy state ---------------------------------------------------
    $ts = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services'
    $p  = Get-ItemProperty -Path $ts -ErrorAction SilentlyContinue
    $r.AllowSignedFiles   = if ($null -ne $p.AllowSignedFiles)   { [int]$p.AllowSignedFiles }   else { 'unset' }
    $r.AllowUnsignedFiles = if ($null -ne $p.AllowUnsignedFiles) { [int]$p.AllowUnsignedFiles } else { 'unset' }
 
    # --- thumbprint list (discovered, not assumed) ----------------------
    $found = @()
    if (Test-Path $ts) {
        foreach ($k in (Get-ChildItem $ts -Recurse -ErrorAction SilentlyContinue)) {
            foreach ($vn in $k.GetValueNames()) {
                $raw = [string]$k.GetValue($vn)
                if ($raw -match '[0-9A-Fa-f]{40,}') {
                    $c = Classify-Thumbprint -Raw $raw
                    $found += [PSCustomObject]@{
                        Key = $k.Name.Split('\\')[-1]; Name = $vn
                        Alg = $c.Algorithm; Len = $c.Length; Prefix = $c.Prefix
                        Hash = $c.Hex.Substring(0, [Math]::Min(12, $c.Hex.Length))
                    }
                }
            }
        }
    }
    $r.ThumbprintCount = $found.Count
    $r.Sha1Count       = @($found | Where-Object Alg -eq 'SHA1').Count
    $r.Sha256Count     = @($found | Where-Object Alg -eq 'SHA256').Count
    $r.InvalidCount    = @($found | Where-Object Alg -eq 'INVALID').Count
    $r.ObservedPrefix  = (($found | Select-Object -ExpandProperty Prefix -Unique) -join ',')
 
    # --- April stopgap --------------------------------------------------
    $cli = Join-Path $ts 'Client'
    $rw  = (Get-ItemProperty -Path $cli -Name RedirectionWarningDialogVersion -ErrorAction SilentlyContinue).RedirectionWarningDialogVersion
    $r.StopgapPresent = ($null -ne $rw)
 
    # --- verdict --------------------------------------------------------
    $issues = @()
    if (-not $r.JulyPatched)                        { $issues += 'NotJulyPatched' }
    if ($r.StopgapPresent)                          { $issues += 'LegacyDialogStopgap' }
    if ($r.Sha1Count   -gt 0)                       { $issues += 'DeprecatedSha1Pin' }
    if ($r.InvalidCount -gt 0)                      { $issues += 'MalformedThumbprint' }
    if ($r.ThumbprintCount -eq 0)                   { $issues += 'NoTrustedPublisher' }
    if ($r.AllowUnsignedFiles -eq 1 -or $r.AllowUnsignedFiles -eq 'unset') { $issues += 'UnsignedRdpAllowed' }
    $r.Issues  = ($issues -join ',')
    $r.Verdict = if ($issues.Count) { 'AttentionRequired' } else { 'Compliant' }
}
catch { $r.Error = $_.Exception.Message; $r.Verdict = 'Error' }
 
$json = $r | ConvertTo-Json -Compress
if ($json.Length -gt 2000) { $json = $json.Substring(0, 2000) }
Write-Output $json
if ($r.Verdict -eq 'Compliant') { exit 0 } else { exit 1 }

Custom compliance policy

Remediation output tells you the state. Compliance turns it into a Conditional Access decision. Discovery script:

PowerShell  —  custom compliance discovery script
$cv  = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
$ts  = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services'
$p   = Get-ItemProperty -Path $ts -ErrorAction SilentlyContinue
$cli = Join-Path $ts 'Client'
 
$sha1 = 0; $sha256 = 0
if (Test-Path $ts) {
    foreach ($k in (Get-ChildItem $ts -Recurse -ErrorAction SilentlyContinue)) {
        foreach ($vn in $k.GetValueNames()) {
            $hex = ([string]$k.GetValue($vn)) -replace '[^0-9A-Fa-f]', ''
            if ($hex.Length -eq 40) { $sha1++ } elseif ($hex.Length -eq 64) { $sha256++ }
        }
    }
}
 
$hash = @{
    RdpBuildUbr        = [int]$cv.UBR
    RdpSha256Pins      = $sha256
    RdpSha1Pins        = $sha1
    RdpUnsignedBlocked = ($p.AllowUnsignedFiles -eq 0)
    RdpStopgapPresent  = ($null -ne (Get-ItemProperty -Path $cli -Name RedirectionWarningDialogVersion -ErrorAction SilentlyContinue))
}
return $hash | ConvertTo-Json -Compress

Matching rules JSON:

JSON  —  custom compliance rules
{
  "Rules": [
    {
      "SettingName": "RdpSha256Pins",
      "Operator": "GreaterEquals",
      "DataType": "Int64",
      "Operand": 1,
      "MoreInfoUrl": "https://scudra.ca/rdp-trusted-publishers",
      "RemediationStrings": [
        { "Language": "en_US",
          "Title": "RDP trusted publisher not configured",
          "Description": "This device has no SHA-256 trusted .rdp publisher pinned. Contact IT." }
      ]
    },
    {
      "SettingName": "RdpUnsignedBlocked",
      "Operator": "IsEquals",
      "DataType": "Boolean",
      "Operand": true,
      "MoreInfoUrl": "https://scudra.ca/rdp-trusted-publishers",
      "RemediationStrings": [
        { "Language": "en_US",
          "Title": "Unsigned RDP files are permitted",
          "Description": "This device allows unsigned .rdp files to run. Contact IT." }
      ]
    },
    {
      "SettingName": "RdpStopgapPresent",
      "Operator": "IsEquals",
      "DataType": "Boolean",
      "Operand": false,
      "MoreInfoUrl": "https://scudra.ca/rdp-trusted-publishers",
      "RemediationStrings": [
        { "Language": "en_US",
          "Title": "Legacy RDP dialog override present",
          "Description": "A temporary registry override is suppressing RDP security warnings. Contact IT." }
      ]
    }
  ]
}

Hunting: find the exposure before you close it

Before you enforce, find out what you are about to break and whether anyone is already being targeted.

Which RDP files are actually in use

KQL  —  RDP file inventory (Defender advanced hunting)
DeviceProcessEvents
| where Timestamp > ago(30d)
| where FileName =~ "mstsc.exe"
| where ProcessCommandLine has ".rdp"
| extend RdpFile = tostring(extract(@'([^\s"]+\.rdp)', 1, ProcessCommandLine))
| where isnotempty(RdpFile)
| summarize Launches = count(),
            Devices  = dcount(DeviceId),
            Users    = dcount(AccountUpn),
            LastSeen = max(Timestamp)
          by RdpFile
| order by Launches desc

This is your migration backlog. Every distinct file in that list needs an owner and a signing decision before you disable unsigned files.

RDP files arriving from hostile channels

KQL  —  .rdp files written by mail and browser processes
let deliveryApps = dynamic(["outlook.exe","olk.exe","msedge.exe","chrome.exe",
                            "firefox.exe","teams.exe","ms-teams.exe","explorer.exe"]);
DeviceFileEvents
| where Timestamp > ago(30d)
| where FileName endswith ".rdp"
| where ActionType in ("FileCreated", "FileModified")
| where InitiatingProcessFileName in~ (deliveryApps)
| project Timestamp, DeviceName, FileName, FolderPath, FileOriginUrl,
          InitiatingProcessFileName, InitiatingProcessAccountUpn
| order by Timestamp desc

Devices still carrying the April stopgap

KQL  —  stopgap registry value
DeviceRegistryEvents
| where Timestamp > ago(30d)
| where RegistryKey has @"Terminal Services\Client"
| where RegistryValueName =~ "RedirectionWarningDialogVersion"
| summarize arg_max(Timestamp, RegistryValueData) by DeviceId, DeviceName
| order by Timestamp desc

The Central Store trap (hybrid and AD-managed estates)

This is the trap that will outlive the article, and it is worth understanding even in a cloud-first shop, because most of us still touch a domain somewhere.

  • gpedit.msc reads the local %SystemRoot%\PolicyDefinitions folder. The July cumulative update refreshed it. That is why the rename shows up locally.
  • GPMC reads the SYSVOL Central Store if one exists. No Windows update ever touches SYSVOL.

The consequence: a domain-managed organisation with a Central Store will keep seeing the old “SHA1” name and the old Help text indefinitely, while their clients have already moved to SHA-2 support. The admin console and the endpoint disagree, and the console is the one that is wrong.

PowerShell  —  Central Store drift check
# Compare Central Store against a patched reference machine
$central = "\\$env:USERDNSDOMAIN\SYSVOL\$env:USERDNSDOMAIN\Policies\PolicyDefinitions"
$local   = "$env:SystemRoot\PolicyDefinitions"
 
foreach ($f in 'TerminalServer.admx', 'en-US\TerminalServer.adml') {
    $c = Join-Path $central $f; $l = Join-Path $local $f
    [PSCustomObject]@{
        File          = $f
        CentralExists = Test-Path $c
        CentralHash   = if (Test-Path $c) { (Get-FileHash $c -Algorithm SHA256).Hash.Substring(0,16) }
        LocalHash     = (Get-FileHash $l -Algorithm SHA256).Hash.Substring(0,16)
        InSync        = (Test-Path $c) -and
                        ((Get-FileHash $c).Hash -eq (Get-FileHash $l).Hash)
    }
}
 
# Refresh (run from a fully patched reference machine, out of hours)
# Copy-Item "$local\TerminalServer.admx"        "$central\"        -Force
# Copy-Item "$local\en-US\TerminalServer.adml"  "$central\en-US\"  -Force

Recommended configurations

Microsoft’s high-security configuration

Microsoft recommends this for all organisations, regardless of security posture, whenever the environment allows it — and especially for those handling sensitive data or frequently targeted by phishing.

PolicySetting
Allow .rdp files from valid publishers and user’s default .rdp settingsDisabled
Allow .rdp files from unknown publishersDisabled
Specify thumbprints of certificates representing trusted .rdp publishersEnabled – only your own signing thumbprints, preferably SHA-2

Effect: users can only open .rdp files signed by an explicitly trusted publisher. Everything else is blocked, including files signed by valid but untrusted publishers, and connections launched from the Remote Desktop Connection UI itself. That last clause is the one that generates tickets.

Microsoft’s permissive configuration

For organisations that cannot yet stop using unsigned .rdp files

PolicySetting
Allow .rdp files from valid publishers and user’s default .rdp settingsEnabled (default)
Allow .rdp files from unknown publishersEnabled (default)
Specify thumbprints of certificates representing trusted .rdp publishersEnabled with your thumbprints, or Disabled (default)

Effect: everything still opens, but the security warnings remain and redirections stay off until the user opts in. Protection rests entirely on end-user judgement. Microsoft is blunt that this should be considered temporary.

A practical baseline by device class

Microsoft’s two configurations are the poles. Most fleets need something in between during migration.

Device classUnsignedValid publishers / manual launchThumbprintsRationale
Privileged access workstations, Tier-0 adminDisabledDisabledEnabled, SHA-256, minimal listHighest value target. Manual RDC launch should come from a jump host, not a laptop.
Standard knowledge workerDisabledEnabledEnabled, SHA-256Kills the phishing vector without breaking ad-hoc support connections.
IT / service deskDisabledEnabledEnabled, SHA-256They launch RDC manually all day. Disabling that is how you get an exception request that never closes.
Developer / engineeringDisabledEnabledEnabled, SHA-256Watch for tooling that generates unsigned .rdp files on the fly- inventory first.
Frontline / shared / kioskDisabledDisabledEnabled, SHA-256, single entryFixed, known connections only. Nothing ad-hoc is legitimate here.
Contractor / unmanaged-adjacentDisabledEnabledEnabled, SHA-256You cannot control what lands in their inbox; block the unsigned path at minimum.

Rollout plan

RingPopulationConfigurationExit gate
Ring 0 – Lab5–10 VMs, mixed 24H2/25H2, mixed Pro/Enterprise, at least one pre-July buildFull high-security config; SHA-256 pinsPrefix syntax confirmed.
Ring 1 – ITEndpoint and security teams, 20–50 devicesThumbprints + block unsigned. Leave valid-publisher enabled.Zero unexplained RDP failures for 7 days. Audit script reports 100% pinned.
Ring 2 – Pilot BUOne business unit with known RDP usage, 100–300 devicesSame as Ring 1Helpdesk ticket rate below agreed threshold for 10 days.
Ring 3 – BroadRemainder, staged by device classPer device class≥98% compliant on the custom compliance policy before enabling Conditional Access enforcement.
Ring 4 HardeningPAW and frontline classes onlyAdd valid-publisher = DisabledException register closed; jump-host path validated.

Lab test matrix

These are the tests that turn this from a summary of documentation into original research. Record every result, including the boring ones.

#TestWhy it matters
1Confirm the gpedit rename pre- and post-July on identical hardware; hash TerminalServer.admx / .adml on both.Establishes the change is real and locally delivered.
2Extract element ID, list key, and valuePrefix from the ADMX.Produces the undocumented prefix syntax. The headline finding.
3Enable the policy in gpedit with SHA-1, dump the registry. Repeat with SHA-256. Diff.Reveals the authoritative storage format, prefix included.
4Push SHA-256 via Settings Catalog. Check portal status, registry, and actual dialog suppression.Answers whether the stale SHA-1 label is cosmetic. Three possible outcomes, all publishable.
5Push SHA-256 via OMA-URI. Same three checks.Confirms the fallback path works if the catalog validates against a stale schema.
6Push a deliberately malformed 64-char value.Confirms the silent fail-open. Screenshot the green portal status against a still-warning client.
7Push a SHA-256 pin to a pre-July device.Establishes blast radius during a staggered patch rollout.
8Pin device-scope and user-scope with different lists on one machine.Confirms the union behaviour and quantifies the delegation risk.
9Sign an .rdp with rdpsign /sha256, then edit it, then reopen.Demonstrates signature invalidation on edit, the most common field mistake.
10Pin a self-signed cert whose chain is untrusted.Proves thumbprint pinning bypasses chain validation. Good, slightly alarming demo.
11Repeat 4–6 on Windows 11 Pro as well as Enterprise.Catches any edition-scoped behaviour before it catches you.
12Set RedirectionWarningDialogVersion = 1 on a July build.Confirms whether the April stopgap still functions post-July, or has already been quietly retired.

Leave a Reply

Your email address will not be published. Required fields are marked *