55using Microsoft . Windows . ProjFS ;
66using System ;
77using System . ComponentModel ;
8- using System . Diagnostics ;
98using System . IO ;
109using System . Linq ;
1110using System . Runtime . InteropServices ;
@@ -27,7 +26,6 @@ public class ProjFSFilter : IKernelDriver
2726 private const string PrjFltAutoLoggerStartValue = "Start" ;
2827
2928 private const string System32LogFilesRoot = @"%SystemRoot%\System32\LogFiles" ;
30- private const string System32DriversRoot = @"%SystemRoot%\System32\drivers" ;
3129
3230 // From "Autologger" section of prjflt.inf
3331 private const string FilterLoggerGuid = "ee4206ff-4a4d-452f-be56-6bd0ed272b44" ;
@@ -87,6 +85,34 @@ public static bool TryAttach(string enlistmentRoot, out string errorMessage)
8785 return true ;
8886 }
8987
88+ /// <summary>
89+ /// Attempts to attach the ProjFS filter driver to the volume containing the enlistment.
90+ /// If FilterAttach returns ACCESS_DENIED but the ProjFS service is already running,
91+ /// the filter is presumed to already be attached and the call succeeds.
92+ /// </summary>
93+ public static bool TryAttachToVolume ( string enlistmentRoot , ITracer tracer , out string errorMessage )
94+ {
95+ if ( TryAttach ( enlistmentRoot , out errorMessage ) )
96+ {
97+ return true ;
98+ }
99+
100+ // FilterAttach requires SE_LOAD_DRIVER_PRIVILEGE, which is typically only
101+ // granted to administrators. When the caller lacks this privilege but the
102+ // ProjFS service is confirmed running, the filter is already attached to
103+ // the volume — treat ACCESS_DENIED as success in that case.
104+ if ( errorMessage != null
105+ && errorMessage . Contains ( AccessDeniedResult . ToString ( ) )
106+ && IsServiceRunning ( tracer ) )
107+ {
108+ tracer . RelatedInfo ( $ "{ nameof ( TryAttachToVolume ) } : FilterAttach returned ACCESS_DENIED, but ProjFS service is running. Proceeding.") ;
109+ errorMessage = null ;
110+ return true ;
111+ }
112+
113+ return false ;
114+ }
115+
90116 public static bool IsServiceRunning ( ITracer tracer )
91117 {
92118 try
@@ -243,128 +269,45 @@ public static bool TryEnableOrInstallDriver(
243269 out bool isProjFSFeatureAvailable )
244270 {
245271 isProjFSFeatureAvailable = false ;
246- if ( ! TryGetIsInboxProjFSFinalAPI ( tracer , out windowsBuildNumber , out isInboxProjFSFinalAPI ) )
272+
273+ // Log build number for telemetry. ProjFS is inbox on all supported OS versions
274+ // (Windows 10 1809 / build 17763+), so this is informational only.
275+ if ( ! TryGetWindowsBuildNumber ( tracer , out windowsBuildNumber ) )
247276 {
248- return false ;
277+ tracer . RelatedWarning ( $ " { nameof ( TryEnableOrInstallDriver ) } : Could not determine Windows build number" ) ;
249278 }
250279
251- if ( isInboxProjFSFinalAPI )
252- {
253- if ( TryEnableProjFSOptionalFeature ( tracer , fileSystem , out isProjFSFeatureAvailable ) )
254- {
255- return true ;
256- }
280+ isInboxProjFSFinalAPI = true ;
257281
258- return false ;
282+ if ( TryEnableProjFSOptionalFeature ( tracer , fileSystem , out isProjFSFeatureAvailable ) )
283+ {
284+ return true ;
259285 }
260286
261- return TryInstallProjFSViaINF ( tracer , fileSystem ) ;
287+ return false ;
262288 }
263289
264290 public static bool IsNativeLibInstalled ( ITracer tracer , PhysicalFileSystem fileSystem )
265291 {
266292 string system32Path = Path . Combine ( Environment . SystemDirectory , ProjFSNativeLibFileName ) ;
267293 bool existsInSystem32 = fileSystem . FileExists ( system32Path ) ;
268294
269- string gvfsAppDirectory = ProcessHelper . GetCurrentProcessLocation ( ) ;
270- string nonInboxNativeLibInstallPath ;
271- string packagedNativeLibPath ;
272- GetNativeLibPaths ( gvfsAppDirectory , out packagedNativeLibPath , out nonInboxNativeLibInstallPath ) ;
273- bool existsInAppDirectory = fileSystem . FileExists ( nonInboxNativeLibInstallPath ) ;
274-
275295 EventMetadata metadata = CreateEventMetadata ( ) ;
276296 metadata . Add ( nameof ( system32Path ) , system32Path ) ;
277297 metadata . Add ( nameof ( existsInSystem32 ) , existsInSystem32 ) ;
278- metadata . Add ( nameof ( gvfsAppDirectory ) , gvfsAppDirectory ) ;
279- metadata . Add ( nameof ( nonInboxNativeLibInstallPath ) , nonInboxNativeLibInstallPath ) ;
280- metadata . Add ( nameof ( packagedNativeLibPath ) , packagedNativeLibPath ) ;
281- metadata . Add ( nameof ( existsInAppDirectory ) , existsInAppDirectory ) ;
282- tracer . RelatedEvent ( EventLevel . Informational , nameof ( IsNativeLibInstalled ) , metadata ) ;
283- return existsInSystem32 || existsInAppDirectory ;
284- }
285-
286- public static bool TryCopyNativeLibIfDriverVersionsMatch ( ITracer tracer , PhysicalFileSystem fileSystem , out string copyNativeDllError )
287- {
288- string system32NativeLibraryPath = Path . Combine ( Environment . SystemDirectory , ProjFSNativeLibFileName ) ;
289- if ( fileSystem . FileExists ( system32NativeLibraryPath ) )
290- {
291- copyNativeDllError = $ "{ ProjFSNativeLibFileName } already exists at { system32NativeLibraryPath } ";
292- return false ;
293- }
294-
295- string gvfsProcessLocation = ProcessHelper . GetCurrentProcessLocation ( ) ;
296- string nonInboxNativeLibInstallPath ;
297- string packagedNativeLibPath ;
298- GetNativeLibPaths ( gvfsProcessLocation , out packagedNativeLibPath , out nonInboxNativeLibInstallPath ) ;
299- if ( fileSystem . FileExists ( nonInboxNativeLibInstallPath ) )
300- {
301- copyNativeDllError = $ "{ ProjFSNativeLibFileName } already exists at { nonInboxNativeLibInstallPath } ";
302- return false ;
303- }
304-
305- if ( ! fileSystem . FileExists ( packagedNativeLibPath ) )
306- {
307- copyNativeDllError = $ "{ packagedNativeLibPath } not found, no { ProjFSNativeLibFileName } available to copy";
308- return false ;
309- }
310-
311- string packagedPrjfltDriverPath = Path . Combine ( gvfsProcessLocation , "Filter" , DriverFileName ) ;
312- if ( ! fileSystem . FileExists ( packagedPrjfltDriverPath ) )
313- {
314- copyNativeDllError = $ "{ packagedPrjfltDriverPath } not found, unable to validate that packaged driver matches installed driver";
315- return false ;
316- }
317-
318- string system32PrjfltDriverPath = Path . Combine ( Environment . ExpandEnvironmentVariables ( System32DriversRoot ) , DriverFileName ) ;
319- if ( ! fileSystem . FileExists ( system32PrjfltDriverPath ) )
320- {
321- copyNativeDllError = $ "{ system32PrjfltDriverPath } not found, unable to validate that packaged driver matches installed driver";
322- return false ;
323- }
324-
325- FileVersionInfo packagedDriverVersion ;
326- FileVersionInfo system32DriverVersion ;
327- try
328- {
329- packagedDriverVersion = fileSystem . GetVersionInfo ( packagedPrjfltDriverPath ) ;
330- system32DriverVersion = fileSystem . GetVersionInfo ( system32PrjfltDriverPath ) ;
331- if ( ! fileSystem . FileVersionsMatch ( packagedDriverVersion , system32DriverVersion ) )
332- {
333- copyNativeDllError = $ "Packaged sys FileVersion '{ packagedDriverVersion . FileVersion } ' does not match System32 sys FileVersion '{ system32DriverVersion . FileVersion } '";
334- return false ;
335- }
336-
337- if ( ! fileSystem . ProductVersionsMatch ( packagedDriverVersion , system32DriverVersion ) )
338- {
339- copyNativeDllError = $ "Packaged sys ProductVersion '{ packagedDriverVersion . ProductVersion } ' does not match System32 sys ProductVersion '{ system32DriverVersion . ProductVersion } '";
340- return false ;
341- }
342- }
343- catch ( FileNotFoundException e )
344- {
345- EventMetadata metadata = CreateEventMetadata ( e ) ;
346- tracer . RelatedWarning (
347- metadata ,
348- $ "{ nameof ( TryCopyNativeLibIfDriverVersionsMatch ) } : Exception caught while comparing sys versions") ;
349- copyNativeDllError = $ "Exception caught while comparing sys versions: { e . Message } ";
350- return false ;
351- }
352-
353- EventMetadata driverVersionMetadata = CreateEventMetadata ( ) ;
354- driverVersionMetadata . Add ( $ "{ nameof ( packagedDriverVersion ) } .FileVersion", packagedDriverVersion . FileVersion . ToString ( ) ) ;
355- driverVersionMetadata . Add ( $ "{ nameof ( system32DriverVersion ) } .FileVersion", system32DriverVersion . FileVersion . ToString ( ) ) ;
356- driverVersionMetadata . Add ( $ "{ nameof ( packagedDriverVersion ) } .ProductVersion", packagedDriverVersion . ProductVersion . ToString ( ) ) ;
357- driverVersionMetadata . Add ( $ "{ nameof ( system32DriverVersion ) } .ProductVersion", system32DriverVersion . ProductVersion . ToString ( ) ) ;
358- tracer . RelatedInfo ( driverVersionMetadata , $ "{ nameof ( TryCopyNativeLibIfDriverVersionsMatch ) } : Copying native library") ;
359298
360- if ( ! TryCopyNativeLibToNonInboxInstallLocation ( tracer , fileSystem , gvfsProcessLocation ) )
299+ // Check for stale app-local native library from legacy non-inbox installs.
300+ // This file should not exist on current builds; warn if found so admins can clean it up.
301+ string appLocalPath = Path . Combine ( ProcessHelper . GetCurrentProcessLocation ( ) , ProjFSNativeLibFileName ) ;
302+ bool staleAppLocalLibExists = fileSystem . FileExists ( appLocalPath ) ;
303+ if ( staleAppLocalLibExists )
361304 {
362- copyNativeDllError = "Failed to copy native library" ;
363- return false ;
305+ metadata . Add ( nameof ( appLocalPath ) , appLocalPath ) ;
306+ metadata . Add ( TracingConstants . MessageKey . WarningMessage , "Stale app-local ProjectedFSLib.dll found from legacy non-inbox ProjFS install" ) ;
364307 }
365308
366- copyNativeDllError = null ;
367- return true ;
309+ tracer . RelatedEvent ( EventLevel . Informational , nameof ( IsNativeLibInstalled ) , metadata ) ;
310+ return existsInSystem32 ;
368311 }
369312
370313 public bool IsGVFSUpgradeSupported ( )
@@ -469,23 +412,14 @@ public bool IsReady(JsonTracer tracer, string enlistmentRoot, TextWriter output,
469412
470413 if ( ! IsNativeLibInstalled ( tracer , new PhysicalFileSystem ( ) ) )
471414 {
472- error = "ProjFS native library is not installed" ;
415+ error = "ProjFS native library (ProjectedFSLib.dll) is not installed. "
416+ + "Ensure the Windows Projected File System optional feature is enabled: "
417+ + "Enable-WindowsOptionalFeature -Online -FeatureName Client-ProjFS" ;
473418 return false ;
474419 }
475420
476- if ( ! TryAttach ( enlistmentRoot , out error ) )
421+ if ( ! TryAttachToVolume ( enlistmentRoot , tracer , out error ) )
477422 {
478- // FilterAttach requires SE_LOAD_DRIVER_PRIVILEGE (admin). When running
479- // non-elevated on a machine where ProjFS is already set up, the filter
480- // is already attached to the volume and the only failure is ACCESS_DENIED.
481- // Allow the mount to proceed in that specific case.
482- if ( error . Contains ( AccessDeniedResult . ToString ( ) ) )
483- {
484- tracer . RelatedInfo ( $ "IsReady: TryAttach returned ACCESS_DENIED, but ProjFS service is running. Proceeding.") ;
485- error = string . Empty ;
486- return true ;
487- }
488-
489423 return false ;
490424 }
491425
@@ -508,134 +442,20 @@ private static bool IsInboxAndEnabled()
508442 return getOptionalFeatureResult . ExitCode == ( int ) ProjFSInboxStatus . Enabled ;
509443 }
510444
511- private static bool TryGetIsInboxProjFSFinalAPI ( ITracer tracer , out uint windowsBuildNumber , out bool isProjFSInbox )
445+ private static bool TryGetWindowsBuildNumber ( ITracer tracer , out uint windowsBuildNumber )
512446 {
513- isProjFSInbox = false ;
514447 windowsBuildNumber = 0 ;
515448 try
516449 {
517450 windowsBuildNumber = Common . NativeMethods . GetWindowsBuildNumber ( ) ;
518- tracer . RelatedInfo ( $ "{ nameof ( TryGetIsInboxProjFSFinalAPI ) } : Build number = { windowsBuildNumber } ") ;
519- }
520- catch ( Win32Exception e )
521- {
522- tracer . RelatedError ( CreateEventMetadata ( e ) , $ "{ nameof ( TryGetIsInboxProjFSFinalAPI ) } : Exception while trying to get Windows build number") ;
523- return false ;
524- }
525-
526- const uint MinRS4inboxVersion = 17121 ;
527- const uint FirstRS5Version = 17600 ;
528- const uint MinRS5inboxVersion = 17626 ;
529- isProjFSInbox = ! ( windowsBuildNumber < MinRS4inboxVersion || ( windowsBuildNumber >= FirstRS5Version && windowsBuildNumber < MinRS5inboxVersion ) ) ;
530- return true ;
531- }
532-
533- private static bool TryInstallProjFSViaINF ( ITracer tracer , PhysicalFileSystem fileSystem )
534- {
535- string gvfsAppDirectory = ProcessHelper . GetCurrentProcessLocation ( ) ;
536- if ( ! TryCopyNativeLibToNonInboxInstallLocation ( tracer , fileSystem , gvfsAppDirectory ) )
537- {
538- return false ;
539- }
540-
541- ProcessResult result = ProcessHelper . Run ( "RUNDLL32.EXE" , $ "SETUPAPI.DLL,InstallHinfSection DefaultInstall 128 { gvfsAppDirectory } \\ Filter\\ prjflt.inf") ;
542- if ( result . ExitCode == 0 )
543- {
544- tracer . RelatedInfo ( $ "{ nameof ( TryInstallProjFSViaINF ) } : Installed PrjFlt via INF") ;
451+ tracer . RelatedInfo ( $ "{ nameof ( TryGetWindowsBuildNumber ) } : Build number = { windowsBuildNumber } ") ;
545452 return true ;
546453 }
547- else
548- {
549- EventMetadata metadata = CreateEventMetadata ( ) ;
550- metadata . Add ( "resultExitCode" , result . ExitCode ) ;
551- metadata . Add ( "resultOutput" , result . Output ) ;
552- tracer . RelatedError ( metadata , $ "{ nameof ( TryInstallProjFSViaINF ) } : RUNDLL32.EXE failed to install PrjFlt") ;
553- }
554-
555- return false ;
556- }
557-
558- private static bool TryCopyNativeLibToNonInboxInstallLocation ( ITracer tracer , PhysicalFileSystem fileSystem , string gvfsAppDirectory )
559- {
560- string packagedNativeLibPath ;
561- string nonInboxNativeLibInstallPath ;
562- GetNativeLibPaths ( gvfsAppDirectory , out packagedNativeLibPath , out nonInboxNativeLibInstallPath ) ;
563-
564- EventMetadata pathMetadata = CreateEventMetadata ( ) ;
565- pathMetadata . Add ( nameof ( gvfsAppDirectory ) , gvfsAppDirectory ) ;
566- pathMetadata . Add ( nameof ( packagedNativeLibPath ) , packagedNativeLibPath ) ;
567- pathMetadata . Add ( nameof ( nonInboxNativeLibInstallPath ) , nonInboxNativeLibInstallPath ) ;
568-
569- if ( fileSystem . FileExists ( packagedNativeLibPath ) )
570- {
571- tracer . RelatedEvent ( EventLevel . Informational , $ "{ nameof ( TryCopyNativeLibToNonInboxInstallLocation ) } _CopyingNativeLib", pathMetadata ) ;
572-
573- try
574- {
575- fileSystem . CopyFile ( packagedNativeLibPath , nonInboxNativeLibInstallPath , overwrite : true ) ;
576-
577- try
578- {
579- fileSystem . FlushFileBuffers ( nonInboxNativeLibInstallPath ) ;
580- }
581- catch ( Win32Exception e )
582- {
583- EventMetadata metadata = CreateEventMetadata ( e ) ;
584- metadata . Add ( nameof ( nonInboxNativeLibInstallPath ) , nonInboxNativeLibInstallPath ) ;
585- metadata . Add ( nameof ( packagedNativeLibPath ) , packagedNativeLibPath ) ;
586- tracer . RelatedWarning ( metadata , $ "{ nameof ( TryCopyNativeLibToNonInboxInstallLocation ) } : Win32Exception while trying to flush file buffers", Keywords . Telemetry ) ;
587- }
588- }
589- catch ( UnauthorizedAccessException e )
590- {
591- EventMetadata metadata = CreateEventMetadata ( e ) ;
592- tracer . RelatedError ( metadata , $ "{ nameof ( TryCopyNativeLibToNonInboxInstallLocation ) } : UnauthorizedAccessException caught while trying to copy native lib") ;
593- return false ;
594- }
595- catch ( DirectoryNotFoundException e )
596- {
597- EventMetadata metadata = CreateEventMetadata ( e ) ;
598- tracer . RelatedError ( metadata , $ "{ nameof ( TryCopyNativeLibToNonInboxInstallLocation ) } : DirectoryNotFoundException caught while trying to copy native lib") ;
599- return false ;
600- }
601- catch ( FileNotFoundException e )
602- {
603- EventMetadata metadata = CreateEventMetadata ( e ) ;
604- tracer . RelatedError ( metadata , $ "{ nameof ( TryCopyNativeLibToNonInboxInstallLocation ) } : FileNotFoundException caught while trying to copy native lib") ;
605- return false ;
606- }
607- catch ( IOException e )
608- {
609- EventMetadata metadata = CreateEventMetadata ( e ) ;
610- tracer . RelatedWarning ( metadata , $ "{ nameof ( TryCopyNativeLibToNonInboxInstallLocation ) } : IOException caught while trying to copy native lib") ;
611-
612- if ( fileSystem . FileExists ( nonInboxNativeLibInstallPath ) )
613- {
614- tracer . RelatedWarning (
615- CreateEventMetadata ( ) ,
616- "Could not copy native lib to app directory, but file already exists, continuing with install" ,
617- Keywords . Telemetry ) ;
618- }
619- else
620- {
621- tracer . RelatedError ( $ "{ nameof ( TryCopyNativeLibToNonInboxInstallLocation ) } : Failed to copy native lib to app directory") ;
622- return false ;
623- }
624- }
625- }
626- else
454+ catch ( Win32Exception e )
627455 {
628- tracer . RelatedError ( pathMetadata , $ "{ nameof ( TryCopyNativeLibToNonInboxInstallLocation ) } : Native lib does not exist in install directory ") ;
456+ tracer . RelatedWarning ( CreateEventMetadata ( e ) , $ "{ nameof ( TryGetWindowsBuildNumber ) } : Exception while trying to get Windows build number ") ;
629457 return false ;
630458 }
631-
632- return true ;
633- }
634-
635- private static void GetNativeLibPaths ( string gvfsAppDirectory , out string packagedNativeLibPath , out string nonInboxNativeLibInstallPath )
636- {
637- packagedNativeLibPath = Path . Combine ( gvfsAppDirectory , "ProjFS" , ProjFSNativeLibFileName ) ;
638- nonInboxNativeLibInstallPath = Path . Combine ( gvfsAppDirectory , ProjFSNativeLibFileName ) ;
639459 }
640460
641461 private static bool TryEnableProjFSOptionalFeature ( ITracer tracer , PhysicalFileSystem fileSystem , out bool isProjFSFeatureAvailable )
0 commit comments