From 74aa25783986b92b0782cf693765517ab0afc321 Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Tue, 8 Nov 2022 17:37:29 -0500 Subject: [PATCH] Skip cloud files marked as "not on disk" during command discovery (#18152) --- experimental-feature-linux.json | 1 + experimental-feature-windows.json | 1 + .../ExperimentalFeature.cs | 4 ++ .../engine/Modules/ModuleUtils.cs | 57 ++++++++++++++++++- 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/experimental-feature-linux.json b/experimental-feature-linux.json index 08f398171d..0e3bfc3d52 100644 --- a/experimental-feature-linux.json +++ b/experimental-feature-linux.json @@ -4,5 +4,6 @@ "PSLoadAssemblyFromNativeCode", "PSNativeCommandErrorActionPreference", "PSSubsystemPluginModel", + "PSModuleAutoLoadSkipOfflineFiles", "PSFeedbackProvider" ] diff --git a/experimental-feature-windows.json b/experimental-feature-windows.json index 08f398171d..0e3bfc3d52 100644 --- a/experimental-feature-windows.json +++ b/experimental-feature-windows.json @@ -4,5 +4,6 @@ "PSLoadAssemblyFromNativeCode", "PSNativeCommandErrorActionPreference", "PSSubsystemPluginModel", + "PSModuleAutoLoadSkipOfflineFiles", "PSFeedbackProvider" ] diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index 5fe456f48d..55036d87fc 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -22,6 +22,7 @@ namespace System.Management.Automation internal const string EngineSource = "PSEngine"; internal const string PSNativeCommandErrorActionPreferenceFeatureName = "PSNativeCommandErrorActionPreference"; + internal const string PSModuleAutoLoadSkipOfflineFilesFeatureName = "PSModuleAutoLoadSkipOfflineFiles"; internal const string PSCustomTableHeaderLabelDecoration = "PSCustomTableHeaderLabelDecoration"; internal const string PSFeedbackProvider = "PSFeedbackProvider"; @@ -118,6 +119,9 @@ namespace System.Management.Automation new ExperimentalFeature( name: PSNativeCommandErrorActionPreferenceFeatureName, description: "Native commands with non-zero exit codes issue errors according to $ErrorActionPreference when $PSNativeCommandUseErrorActionPreference is $true"), + new ExperimentalFeature( + name: PSModuleAutoLoadSkipOfflineFilesFeatureName, + description: "Module discovery will skip over files that are marked by cloud providers as not fully on disk."), new ExperimentalFeature( name: PSCustomTableHeaderLabelDecoration, description: "Formatting differentiation for table header labels that aren't property members"), diff --git a/src/System.Management.Automation/engine/Modules/ModuleUtils.cs b/src/System.Management.Automation/engine/Modules/ModuleUtils.cs index a69b89744a..d8e2b24e01 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleUtils.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleUtils.cs @@ -13,22 +13,46 @@ namespace System.Management.Automation.Internal { internal static class ModuleUtils { + // These are documented members FILE_ATTRIBUTE, they just have not yet been + // added to System.IO.FileAttributes yet. + private const int FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS = 0x400000; + + private const int FILE_ATTRIBUTE_RECALL_ON_OPEN = 0x40000; + // Default option for local file system enumeration: // - Ignore files/directories when access is denied; // - Search top directory only. private static readonly System.IO.EnumerationOptions s_defaultEnumerationOptions = - new System.IO.EnumerationOptions() { AttributesToSkip = FileAttributes.Hidden }; + new System.IO.EnumerationOptions() { AttributesToSkip = FileAttributesToSkip }; + + private static readonly FileAttributes FileAttributesToSkip; // Default option for UNC path enumeration. Same as above plus a large buffer size. // For network shares, a large buffer may result in better performance as more results can be batched over the wire. // The buffer size 16K is recommended in the comment of the 'BufferSize' property: // "A "large" buffer, for example, would be 16K. Typical is 4K." private static readonly System.IO.EnumerationOptions s_uncPathEnumerationOptions = - new System.IO.EnumerationOptions() { AttributesToSkip = FileAttributes.Hidden, BufferSize = 16384 }; + new System.IO.EnumerationOptions() { AttributesToSkip = FileAttributesToSkip, BufferSize = 16384 }; private static readonly string EnCulturePath = Path.DirectorySeparatorChar + "en"; private static readonly string EnUsCulturePath = Path.DirectorySeparatorChar + "en-us"; + static ModuleUtils() + { + if (ExperimentalFeature.IsEnabled(ExperimentalFeature.PSModuleAutoLoadSkipOfflineFilesFeatureName)) + { + FileAttributesToSkip = FileAttributes.Hidden + // Skip OneDrive files/directories that are not fully on disk. + | FileAttributes.Offline + | (FileAttributes)FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS + | (FileAttributes)FILE_ATTRIBUTE_RECALL_ON_OPEN; + + return; + } + + FileAttributesToSkip = FileAttributes.Hidden; + } + /// /// Check if a directory is likely a localized resources folder. /// @@ -276,6 +300,11 @@ namespace System.Management.Automation.Internal manifestPath += StringLiterals.PowerShellDataFileExtension; if (File.Exists(manifestPath)) { + if (HasSkippedFileAttribute(manifestPath)) + { + continue; + } + isModuleDirectory = true; yield return manifestPath; } @@ -288,6 +317,11 @@ namespace System.Management.Automation.Internal string moduleFile = Path.Combine(directoryToCheck, proposedModuleName) + ext; if (File.Exists(moduleFile)) { + if (HasSkippedFileAttribute(moduleFile)) + { + continue; + } + isModuleDirectory = true; yield return moduleFile; @@ -337,6 +371,25 @@ namespace System.Management.Automation.Internal return versionFolders; } + private static bool HasSkippedFileAttribute(string path) + { + try + { + FileAttributes attributes = File.GetAttributes(path); + if ((attributes & FileAttributesToSkip) is not 0) + { + return true; + } + } + catch + { + // Ignore failures so that we keep the current behavior of failing + // later in the search. + } + + return false; + } + private static void ProcessPossibleVersionSubdirectories(IEnumerable subdirectories, List versionFolders) { foreach (string subdir in subdirectories)