Scheduled Task e SChannel 36882: risoluzione dell’errore di handshake TLS con MSAL ed Exchange Online

Un’app .NET che invia email via Microsoft Identity Client e Exchange Online funziona da console ma fallisce quando eseguita da Utilità di pianificazione, lasciando nel registro l’evento SChannel ID 36882. In questa guida trovi cause probabili, diagnosi e correzioni affidabili per eliminare l’errore di handshake TLS.

Indice

Scenario e sintomi

Un eseguibile .NET (framework o .NET modern) invia posta tramite Exchange Online autenticandosi con MSAL e un certificato client. Avviato a mano dall’utente interattivo, tutto ok; quando lo stesso binario è lanciato da Utilità di pianificazione con lo stesso account o un account di servizio, l’invio fallisce. Nel registro eventi compaiono voci SChannel con Event ID 36882 (fallimento del TLS handshake) e, talvolta, ulteriori eventi come 36888 con codici di alert (per esempio handshake_failure). L’app può anche sollevare eccezioni generiche di rete, time‑out o “The request was aborted: Could not create SSL/TLS secure channel”.

Cause tecniche più probabili

ContestoMotivo ricorrente
Store certificatiIl processo pianificato gira in un contesto di servizio (ad esempio NT AUTHORITY\SYSTEM o un service account) che non ha accesso al Personal Store dell’utente interattivo. Manca quindi il certificato client usato da MSAL/SMTP o non è accessibile con la relativa chiave privata.
PrivilegiIl task non è eseguito con token sufficienti ad aprire la chiave privata del certificato o a iniziare la negoziazione TLS. L’opzione predefinita “Esegui solo se l’utente è connesso” può produrre token con capacità ridotte.
Impostazioni TLSIl computer o il processo usano protocolli/cifrari obsoleti. Se TLS 1.2 o cifrari con PFS non sono attivi, SChannel rifiuta la negoziazione e registra 36882.
Proxy e reteUn task non interattivo usa spesso le impostazioni WinHTTP e non il proxy dell’utente (WinINET). Senza proxy corretto il TLS handshake verso login/Graph/Exchange fallisce.
Permessi sulla chiaveIl certificato è nel Computer Store ma l’account del task non ha permessi di lettura sulla private key associata.
Profilo utenteIl logon di tipo batch può non caricare un profilo completo. Componenti che usano DPAPI o archivi utente non trovano i materiali crittografici.
Orario di sistemaUno skew NTP marcato può rompere TLS o OAUTH (token non validi), producendo errori simili durante il handshake.

Diagnosi rapida e verifiche di base

  1. Confronta il contesto: esegui lo stesso binario da console e da task. Nel codice aggiungi una stampa di Environment.UserName e WindowsIdentity.GetCurrent().Name.
  2. Controlla il proxy: in una console elevata esegui netsh winhttp show proxy. Se vuoto ma l’utente usa un proxy, importa o configura quello di sistema.
  3. Ispeziona SChannel: in Event Viewer → Windows Logs → System → SChannel filtra per ID 36882/36888 e annota ora e codice alert.
  4. Verifica il certificato: sotto Certificati (computer)Personale conferma presenza del certificato con chiave privata.
  5. Test TLS: da PowerShell, verifica con Test-NetConnection login.microsoftonline.com -Port 443 e Test-NetConnection outlook.office365.com -Port 443.

Soluzioni operative

Configurazione corretta del task

  1. Apri Utilità di pianificazione, proprietà del task.
  2. Nella scheda Generale seleziona Esegui con i privilegi più elevati.
  3. Seleziona Esegui indipendentemente dalla sessione utente (Run whether user is logged on or not) per ottenere un token completo.
  4. Imposta Configura per la tua versione di Windows Server/Windows.
  5. Compila il campo Avvia in (cartella di lavoro) con la directory dell’eseguibile per evitare problemi di path relativi.
  6. Nella scheda Condizioni disattiva “avvia solo se su alimentazione di rete” o condizioni non pertinenti.
  7. Nella scheda Impostazioni abilita “Consenti esecuzioni forzate” e “Riavvia in caso di errore” con un numero di tentativi ragionevole.

Gestione del certificato e dello store

Per evitare differenze tra contesto interattivo e servizio, mantieni il certificato nel Computer Store (LocalMachine\My) e non nello store utente.

Procedura da MMC

  1. Apri mmc.exeFileAggiungi/Rimuovi snap-inCertificatiAccount computer.
  2. Importa il certificato in Personale. Assicurati che compaia la dicitura Hai la chiave privata che corrisponde a questo certificato.
  3. Click destro sul certificato → Tutte le attivitàGestisci chiavi private e assegna all’account del task il permesso di Lettura sulla chiave.

Procedura con PowerShell

# Esporta da CurrentUser\My e importa nel LocalMachine\My
$thumb = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"  # thumbprint
$pwd   = ConvertTo-SecureString "P@ssw0rd!" -AsPlainText -Force

Export-PfxCertificate `  -Cert "Cert:\CurrentUser\My\$thumb"`
-FilePath "C:\temp\client.pfx" `
-Password $pwd

Import-PfxCertificate `  -FilePath "C:\temp\client.pfx"`
-CertStoreLocation "Cert:\LocalMachine\My" `
-Password $pwd

Verifica presenza chiave privata

Get-ChildItem Cert:\LocalMachine\My$thumb |
Select-Object Subject, Thumbprint, HasPrivateKey 

Nota: se lo spostamento non è possibile, duplica il certificato (con chiave privata) nel Computer Store. Evita di lasciare PFX su disco; elimina il file dopo l’import e usa password robuste.

Riparazione associazione chiave

Se il certificato appare senza chiave privata (freccia gialla), ripara l’associazione:

certutil -repairstore my <THUMBPRINT>

Proxy e connettività nei processi non interattivi

I processi avviati da Utilità di pianificazione usano spesso le impostazioni WinHTTP. Se a livello utente è configurato un proxy ma WinHTTP è “Direct”, il TLS handshake verso i servizi Microsoft fallirà.

:: Visualizza proxy WinHTTP
netsh winhttp show proxy

:: Importa le impostazioni del proxy dell’utente corrente
netsh winhttp import proxy source=ie

:: Oppure imposta esplicitamente
netsh winhttp set proxy proxy-server="http=myproxy:8080;https=myproxy:8080" bypass-list="*.contoso.local" 

Se esiste un proxy TLS-intercept, assicurati che login.microsoftonline.com, graph.microsoft.com ed endpoint Exchange Online siano esclusi dall’ispezione. L’intercettazione può rompere il TLS handshake e generare 36882.

Impostazioni TLS moderne su Windows

Assicurati che TLS 1.2 (o superiore) sia abilitato su client e che i cipher suite ECDHE siano disponibili.

Abilitazione via registro

[HKEYLOCALMACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client]
"Enabled"=dword:00000001
"DisabledByDefault"=dword:00000000

[HKEYLOCALMACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server]
"Enabled"=dword:00000001
"DisabledByDefault"=dword:00000000 

Uso dei cipher suite corretti

Exchange Online richiede cifrari moderni con Perfect Forward Secrecy (ECDHE). Evita scenari in cui siano rimasti solo “TLSRSA*”. Se stai forzando un ordine di cipher via criteri, verifica che includa TLSECDHERSAWITHAES128GCM_SHA256 (o equivalenti) e che non sia presente un filtro restrittivo lato endpoint di rete.

Impostazioni .NET per TLS e sicurezza

Per .NET Framework (app a 32/64 bit), abilita l’uso dei protocolli di sistema e dei cifrari robusti:

[HKEYLOCALMACHINE\SOFTWARE\Microsoft\.NETFramework\v4.0.30319]
"SchUseStrongCrypto"=dword:00000001
"SystemDefaultTlsVersions"=dword:00000001

[HKEYLOCALMACHINE\SOFTWARE\WOW6432Node\Microsoft.NETFramework\v4.0.30319]
"SchUseStrongCrypto"=dword:00000001
"SystemDefaultTlsVersions"=dword:00000001 

In alternativa, solo per test, puoi forzare TLS 1.2 all’avvio del processo:

System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;

Su .NET 6+ l’handler HTTP usa per default i protocolli abilitati a livello OS; la priorità è quindi configurare correttamente SChannel.

Attivazione del logging diagnostico

  • SChannel: imposta EventLogging=1 in HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL, quindi riavvia il servizio o il sistema per aumentare la verbosità.
  • MSAL: in fase di test abilita PII con Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true; e un callback di logging. Disattiva in produzione.
  • Task Scheduler: abilita il log Microsoft-Windows-TaskScheduler/Operational per vedere il contesto del logon.

Trigger alternativi per isolamento del problema

Se il fallimento avviene solo con trigger basati sul tempo, prova un trigger su evento (per esempio su un ID di un log precedente) per escludere dipendenze da stato di logon o di blocco schermo. Il cambio di trigger aiuta a distinguere problemi di token/privilegi da quelli di rete.

Guida passo‑passo consolidata

  1. Imposta il task con privilegi elevati e “Esegui indipendentemente dalla sessione utente”. Specifica l’account definitivo (service account se possibile).
  2. Sposta o duplica il certificato nel LocalMachine\Personal. Verifica con: certutil -store my Controlla che sia presente “Key Prov Info” o “Private key is present”.
  3. Concedi permessi sulla chiave privata all’account del task tramite MMC → Manage Private Keys.
  4. Allinea proxy WinHTTP a quello utente o configura l’eccezione per gli endpoint Microsoft.
  5. Abilita e verifica TLS 1.2 e la presenza di cipher ECDHE nelle impostazioni SChannel.
  6. Conferma .NET strong crypto e, se legacy, forza TLS 1.2 nel codice per i test.
  7. Verifica l’orologio e la sincronizzazione NTP (w32tm /query /status).
  8. Riesegui e raccogli log da SChannel, Task Scheduler e applicazione.

Esempio di codice MSAL con certificato nel Computer Store

using Microsoft.Identity.Client;
using Microsoft.IdentityModel.Logging;
using System;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;

class Program
{
static async Task Main()
{
#if NETFRAMEWORK
// Per .NET Framework
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
#endif
IdentityModelEventSource.ShowPII = true; // Solo per test
```
    var tenantId = "&lt;TENANT_GUID_O_NOME&gt;";
    var clientId = "&lt;APP_ID_CLIENT&gt;";
    var thumb    = "&lt;THUMBPRINT_CERT&gt;";

    X509Certificate2 cert = null;
    using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
    {
        store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
        cert = store.Certificates
                    .Find(X509FindType.FindByThumbprint, thumb, false)
                    .OfType&lt;X509Certificate2&gt;()
                    .FirstOrDefault(c =&gt; c.HasPrivateKey);
    }
    if (cert == null) throw new Exception("Certificato non trovato o senza chiave privata.");

    var app = ConfidentialClientApplicationBuilder
                .Create(clientId)
                .WithAuthority($"https://login.microsoftonline.com/{tenantId}")
                .WithCertificate(cert)
                .Build();

    string[] scopes = new[] { "https://outlook.office365.com/.default" };

    var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
    Console.WriteLine($"Token acquisito, scadenza: {result.ExpiresOn}");
}
```
} 

Verifica dell’ambiente con PowerShell

# SChannel: ultimi eventi 36882/36888
$filter = @{
    LogName='System'
    ProviderName='Schannel'
    Id=36882,36888
    StartTime=(Get-Date).AddDays(-1)
}
Get-WinEvent -FilterHashtable $filter | 
  Select-Object TimeCreated, Id, LevelDisplayName, Message | 
  Format-List

Proxy WinHTTP

netsh winhttp show proxy

TLS 1.2 su .NET Framework (registro)

reg query "HKLM\SOFTWARE\Microsoft.NETFramework\v4.0.30319" /v SchUseStrongCrypto
reg query "HKLM\SOFTWARE\Microsoft.NETFramework\v4.0.30319" /v SystemDefaultTlsVersions

Verifica porte e reachability

Test-NetConnection login.microsoftonline.com -Port 443
Test-NetConnection outlook.office365.com -Port 443 

Approfondimento su differenze di contesto

Il cuore del problema è la disomogeneità del contesto tra esecuzione interattiva e servizio:

  • Store dei certificati: CurrentUser\My è legato al profilo utente interattivo; un service account o SYSTEM non vede quel certificato. Spostarlo o duplicarlo in LocalMachine\My elimina la dipendenza dal profilo.
  • DPAPI: le chiavi utente sono protette dalle credenziali del profilo. Un logon batch potrebbe non avere accesso al master key.
  • Proxy: WinINET (utente) vs WinHTTP (sistema). Un proxy assente o differente produce time‑out e failure del TLS handshake prima ancora che MSAL possa ottenere un token.
  • Permessi sulla private key: anche se il certificato è nel Computer Store, l’ACL della chiave in %ProgramData%\Microsoft\Crypto\Keys deve includere l’account del task.

Check di sicurezza e buone pratiche

  • Minimizza i privilegi: usa account di servizio dedicati e limita la lettura della chiave privata al solo account necessario.
  • Proteggi i PFX: se devi esportare, usa password robuste e cancella il file al termine (Remove-Item sicuro).
  • Niente hard‑coding di segreti: non inserire thumbprint/tenant in chiaro se non strettamente necessario; valuta variabili d’ambiente o configurazione cifrata.
  • Monitora SChannel: lascia EventLogging a 1 solo durante la diagnosi per evitare rumore nei log.

Domande frequenti

Perché manualmente funziona ma come task no?
Perché cambiano store dei certificati, token e proxy. Il processo ha ambiente, profilo, ACL e impostazioni TLS diverse. Il caso più comune è la mancanza della chiave privata nel contesto del servizio.

Posso risolvere solo forzando ServicePointManager.SecurityProtocol?
È un palliativo per app .NET Framework legacy. La correzione strutturale è abilitare TLS 1.2 a livello OS e allineare proxy/ACL certificato.

Quali cipher suite devo abilitare?
Mantieni una lista moderna con ECDHE e AES‑GCM. Evita di lasciare attivi solo i vecchi TLSRSA*. In presenza di un ordine forzato, inserisci in testa suite ECDHE con AES‑GCM a 128 o 256.

Serve il certificato anche per SMTP?
Il certificato è usato da MSAL per l’autenticazione applicativa e il rilascio del token OAuth2. Lo scambio SMTP/Graph usa poi quel token. Se il certificato non è accessibile nel contesto del task, l’intero flusso fallisce prima del TLS handshake “applicativo”.

Lista di controllo pronta all’uso

  • Task con Run whether user is logged on or not e Run with highest privileges.
  • Certificato duplicato in LocalMachine\My con chiave privata.
  • Permesso di lettura sulla private key per l’account del task.
  • TLS 1.2 abilitato su SChannel e .NET strong crypto attivo.
  • Proxy WinHTTP allineato o esclusioni corrette.
  • Orologio sincronizzato e CRL/OCSP raggiungibili.
  • Logging SChannel e MSAL abilitato per verifica.

Esempio di script per schedulare correttamente

$action = New-ScheduledTaskAction -Execute "C:\Apps\Mailer\Mailer.exe" -WorkingDirectory "C:\Apps\Mailer"
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(5)
$principal = New-ScheduledTaskPrincipal -UserId "DOMAIN\svc-mailer" -LogonType Password -RunLevel Highest

Register-ScheduledTask -TaskName "MailerTask" -Action $action -Trigger $trigger -Principal $principal -Description "Invio email via Exchange Online con MSAL" 

Riepilogo operativo

L’evento SChannel 36882 in uno Scheduled Task è quasi sempre dovuto a differenze di contesto tra esecuzione interattiva e servizio. Sposta o duplica il certificato nel Computer Store, esegui il task con privilegi elevati, abilita TLS 1.2 (o superiore) e allinea il proxy WinHTTP. Queste azioni risolvono la maggior parte dei casi in produzione.

Appendice di comandi utili

:: Verifica certificati nel Computer Store
certutil -store my

:: Verifica orologio
w32tm /query /status

:: Forza aggiornamento criteri
gpupdate /force

:: Avvio manuale del task per test
schtasks /run /tn "MailerTask" 

Note finali

  • Se il problema scompare quando esegui l’app in sessione interattiva ma riappare con il trigger temporizzato, focalizzati su proxy WinHTTP, permessi della chiave privata e impostazioni TLS a livello di sistema.
  • Se vedi eventi 36870 o 36869, il problema è quasi certamente la chiave privata del certificato (mancante o non accessibile).
  • Nel dubbio, crea un task “di prova” che stampa identità, variabili d’ambiente e versione di .NET per confrontare i due contesti.

Soluzioni operative sintetizzate

  1. Configurare il task
    – Spunta “Esegui con i privilegi più elevati”.
    – Imposta “Esegui indipendentemente dalla sessione utente (Run whether user is logged on or not)”.
    – Specifica preferibilmente un account di servizio che possiede il certificato in LocalMachine\Personal.
  2. Spostare o duplicare il certificato
    – Copia il certificato client e la relativa chiave privata in Computer Account → Personal.
    – Verifica con certutil -store my che lo store contenga la chiave (Key Prov Info).
  3. Verificare i protocolli TLS
    – Abilita TLS 1.2/1.3 via registro o criteri: HKLM\System\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server → Enabled=1.
    – Assicurati che non restino solo cipher “TLSRSA*”: Exchange Online richiede cifrari con PFS.
  4. Controllare l’attivazione del trigger
    – Prova un trigger su evento per escludere dipendenze da logon o schermo bloccato.
  5. Logging diagnostico
    – Abilita SChannel event logging: HKLM\...\SCHANNEL → EventLogging = 1.
    – Abilita MSAL logging con IdentityModelEventSource.ShowPII = true in test per identificare la fase esatta del TLS handshake.

Con questi accorgimenti, la catena TLS → MSAL → Exchange Online resta coerente tra esecuzione interattiva e pianificata, eliminando gli errori di handshake SChannel ID 36882.

Indice