Un programma Windows può funzionare perfettamente quando lo si avvia con un doppio clic da Esplora file e fallire, magari con un messaggio apparentemente inspiegabile, quando parte da un file batch. Non è un’anomalia di Windows 11 né un problema specifico della finestra del terminale: spesso cambia semplicemente la directory di lavoro corrente assegnata al processo. È un dettaglio che deriva dall’architettura Win32 che risale a decenni fa e che continua ad avere conseguenze concrete ancora oggi, soprattutto con applicazioni legacy, utilità portabili e programmi che cercano file di configurazione usando percorsi relativi.
Raymond Chen, storico sviluppatore Microsoft e autore del blog The Old New Thing, ha raccontato il caso di un cliente alle prese proprio con questo comportamento. Il programma funzionava lanciandolo graficamente, ma produceva un errore se richiamato da un file batch usando il percorso completo dell’eseguibile.
Chen ha individuato subito un possibile colpevole: lo script avviava correttamente il file EXE, ma lasciava invariata la cartella corrente del processo. È una differenza piccola sulla carta, enorme quando il software presume di trovare “accanto a sé” file aggiuntivi.
Il percorso dell’EXE e la directory corrente non sono la stessa cosa
Quando Windows crea un processo esistono almeno due percorsi da tenere in debita considerazione: il primo identifica il file eseguibile caricato, ad esempio C:\Program Files\MiaApp\MiaApp.exe. Il secondo rappresenta la directory corrente dalla quale il programma risolve molti percorsi relativi.
Un programma può quindi trovarsi fisicamente in C:\Program Files\MiaApp ma avviarsi con C:\Users\Mario\Desktop come directory corrente.
Se il codice tenta di aprire config.ini, Windows non deduce automaticamente che lo sviluppatore intendesse C:\Program Files\MiaApp\config.ini: il risultato dipende da come l’applicazione costruisce il percorso e dalle API utilizzate.
Scrivere in un file batch il percorso completo dell’eseguibile garantisce che Windows trovi il programma giusto, ma non imposta automaticamente la cartella contenente l’EXE come directory di lavoro.
Perché il doppio clic da Esplora file può cambiare il risultato
Quando si avvia un programma facendo doppio clic sul suo file EXE in Esplora file, Windows imposta come directory corrente la cartella che contiene quell’eseguibile. Chen sottolinea proprio questo comportamento: serve anche a garantire compatibilità con programmi che cercano file aggiuntivi usando percorsi relativi anziché percorsi completi.
Supponiamo, per esempio, che MiaApp.exe si trovi in C:\Program Files\MiaApp e che il programma tenti di aprire semplicemente settings.dat. Se l’EXE parte con il doppio clic da quella cartella, la directory corrente coincide con C:\Program Files\MiaApp e il file può essere trovato senza problemi.
La situazione cambia se un file batch esegue direttamente "C:\Program Files\MiaApp\MiaApp.exe" mentre il prompt nella finestra del terminale si trova, per esempio, in C:\Scripts. Indicare il percorso completo dell’EXE dice a Windows quale programma avviare, ma non cambia automaticamente la directory corrente. MiaApp.exe potrebbe quindi cercare settings.dat dentro C:\Scripts anziché nella propria cartella e restituire un errore, anche se l’eseguibile è esattamente lo stesso.
La documentazione Win32 Microsoft conferma il meccanismo generale: un processo figlio eredita la directory corrente del processo padre, a meno che chi lo crea non ne specifichi esplicitamente una differente. L’API CreateProcess, per esempio, espone proprio un parametro dedicato alla directory iniziale del nuovo processo.
La soluzione pratica in un file batch: pushd e popd
Nel caso descritto da Microsoft, il cliente ha risolto cambiando directory prima dell’avvio del programma: nello specifico, ha i comandi pushd e popd, soluzione più pulita di un semplice cd perché consente di memorizzare la posizione precedente e ripristinarla al termine.
Con pushd è possibile salvare la directory attuale in uno stack e passare alla cartella indicata; dopo l’esecuzione dell’applicazione, popd recupera la directory precedente. In pratica, lo script può comportarsi in modo simile all’avvio effettuato dalla cartella dell’applicazione senza lasciare il prompt del terminale in una posizione diversa da quella iniziale.
Per esempio, uno script può eseguire pushd "C:\Program Files\MiaApp", avviare MiaApp.exe e poi richiamare popd.
C’è anche un vantaggio poco noto: con le estensioni dei comandi abilitate, pushd gestisce percorsi UNC come \\server\share\cartella creando temporaneamente una lettera di unità disponibile, che popd poi rimuove in un secondo tempo.
Per script destinati a essere spostati insieme all’applicazione, torna utile anche la variabile %~dp0, che in un batch rappresenta unità e percorso del file .bat stesso. Un costrutto come pushd "%~dp0" permette quindi di lavorare rispetto alla posizione dello script, non rispetto alla cartella dalla quale l’utente lo ha richiamato.
Per uno sviluppatore affidarsi alla directory corrente è fragile
Dal punto di vista di chi scrive software, però, correggere il batch risolve il sintomo più che la causa. Un’applicazione moderna non dovrebbe presumere che la cartella corrente coincida sempre con quella dell’eseguibile.
Windows offre API specifiche per ricavare il percorso del modulo: GetModuleFileName, ad esempio, consente a un’applicazione desktop Win32 di determinare il percorso dell’EXE caricato; da lì può ricavare la directory dell’applicazione e cercare risorse che devono trovarsi “accanto al programma”.
I dati modificabili dall’utente meritano un trattamento ancora diverso: configurazioni, database locali, cache e file generati dal programma non dovrebbero in genere finire nella directory di installazione sotto Program Files. API come SHGetKnownFolderPath permettono di individuare cartelle appropriate quali LocalAppData o RoamingAppData.
C’è un’altra ragione per non costruire un’applicazione attorno alla directory corrente: le API GetCurrentDirectory e SetCurrentDirectory lavorano su uno stato condiviso dall’intero processo, non da un singolo thread.
Se un thread modifica la directory mentre un altro sta risolvendo un percorso relativo, il secondo può finire per lavorare su un file diverso da quello previsto. La stessa documentazione Microsoft sconsiglia infatti l’uso della directory corrente e dei percorsi relativi nel codice multithread e nelle librerie condivise.
C’è anche un risvolto di sicurezza: ricerca delle DLL
La directory corrente può influire anche sul caricamento delle DLL, cioè le librerie che un programma utilizza durante l’esecuzione. Il problema nasce quando l’applicazione chiede a Windows di caricare una libreria indicando soltanto il nome, per esempio example.dll, senza specificare il percorso completo del file.
A quel punto Windows deve decidere dove cercarla: non controlla una sola cartella, ma segue un preciso ordine di ricerca che comprende, tra le altre, la directory dell’applicazione, alcune cartelle di sistema e, in determinate condizioni, anche la directory corrente del processo.
Se il programma si aspetta di caricare example.dll da una posizione affidabile ma Windows trova prima una DLL con lo stesso nome in una cartella controllata da un aggressore, potrebbe caricare il file sbagliato. È il principio alla base degli attacchi noti come DLL hijacking: non si modifica l’eseguibile originale, ma si tenta di convincerlo a caricare una libreria malevola al posto di quella prevista.
Windows riduce questo rischio attraverso la tecnica Safe DLL Search Mode, che assegna alla directory corrente una priorità inferiore rispetto ad altre posizioni più affidabili.
Non elimina però il problema alla radice. Per questo Microsoft raccomanda agli sviluppatori di evitare ricerche troppo generiche e, quando possibile, di indicare il percorso completo della DLL oppure limitare esplicitamente le directory nelle quali Windows può cercarla.
API come SetDefaultDllDirectories, AddDllDirectory e LoadLibraryEx permettono proprio di restringere la ricerca delle librerie DLL così da non fare affidamento a percorsi che possono cambiare in base a come l’applicazione è stata avviata.
App Paths: un altro motivo per cui Explorer e CMD possono comportarsi diversamente
Chen indica anche una seconda pista che avrebbe verificato se il cambio di directory non avesse risolto il problema: le chiavi App Paths del Registro di sistema.
Windows usa questa infrastruttura almeno dai tempi di Windows 95 per consentire alla shell di individuare applicazioni senza costringere gli installer ad aggiungere continuamente directory alla variabile PATH globale. Le registrazioni si trovano sotto HKEY_CURRENT_USER oppure HKEY_LOCAL_MACHINE, nel ramo Software\Microsoft\Windows\CurrentVersion\App Paths.
Oltre al percorso completo dell’eseguibile, una voce App Paths può contenere un valore Path con directory supplementari che la shell aggiunge all’ambiente del processo quando avvia l’applicazione attraverso meccanismi quali ShellExecuteEx. Un programma lanciato dalla shell potrebbe quindi ricevere indicazioni diverse rispetto allo stesso EXE eseguito direttamente dalla finestra del terminale.
È un dettaglio che spiega molti problemi apparentemente assurdi: “lo stesso programma” non parte sempre nelle stesse condizioni (o almeno in condizioni che sembrano identiche).