├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── ProjectedFSLib.Managed.API ├── ApiHelper.cpp ├── ApiHelper.h ├── AssemblyInfo.cpp ├── CallbackDelegates.h ├── DirectoryEnumerationResults.h ├── HResult.h ├── IDirectoryEnumerationResults.h ├── IRequiredCallbacks.h ├── IVirtualizationInstance.h ├── IWriteBuffer.h ├── NetCore │ └── ProjectedFSLib.Managed.Netcore.vcxproj ├── NetFramework │ ├── ProjectedFSLib.Managed.vcxproj │ └── ProjectedFSLib.Managed.vcxproj.filters ├── NotificationMapping.h ├── NotificationType.h ├── OnDiskFileState.h ├── ProjectedFSLib.Managed.props ├── Resource.h ├── UpdateFailureCause.h ├── UpdateType.h ├── Utils.cpp ├── Utils.h ├── VirtualizationInstance.cpp ├── VirtualizationInstance.h ├── WriteBuffer.cpp ├── WriteBuffer.h ├── app.ico ├── app.rc ├── prjlib_deprecated.h ├── scripts │ ├── CreateCliAssemblyVersion.bat │ └── CreateVersionHeader.bat ├── signing │ ├── 35MSSharedLib1024.snk │ ├── CodeSignConfig.xml │ └── NuPkgSignConfig.xml ├── stdafx.cpp └── stdafx.h ├── ProjectedFSLib.Managed.Test ├── BasicTests.cs ├── Helpers.cs ├── NUnitRunner.cs ├── Program.cs ├── ProjectedFSLib.Managed.Test.csproj └── README.md ├── ProjectedFSLib.Managed.cpp.props ├── ProjectedFSLib.Managed.cs.props ├── ProjectedFSLib.Managed.props ├── ProjectedFSLib.Managed.sln ├── README.md ├── SECURITY.md ├── doc └── ProjectedFSLib.Managed.xml ├── global.json ├── nuget.config ├── scripts ├── BuildProjFS-Managed.bat ├── InitializeEnvironment.bat ├── NukeBuildOutputs.bat └── RunTests.bat └── simpleProviderManaged ├── ActiveEnumeration.cs ├── EnvironmentHelper.cs ├── FileSystemApi.cs ├── NotificationCallbacks.cs ├── Program.cs ├── ProjFSSorter.cs ├── ProjectedFileInfo.cs ├── ProviderOptions.cs ├── README.md ├── SimpleProvider.cs ├── SimpleProviderManaged.csproj └── app.config /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 5 | the rights to use your contribution. For details, visit https://cla.microsoft.com. 6 | 7 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide 8 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions 9 | provided by the bot. You will only need to do this once across all repos using our CLA. 10 | 11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ProjFS Managed API 2 | ------------------ 3 | 4 | MIT License 5 | 6 | Copyright (c) Microsoft Corporation. All rights reserved. 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE 25 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/ApiHelper.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #include "stdafx.h" 5 | #include "ApiHelper.h" 6 | #include "Utils.h" 7 | 8 | using namespace System; 9 | using namespace System::Globalization; 10 | using namespace System::IO; 11 | using namespace Microsoft::Windows::ProjFS; 12 | 13 | ApiHelper::ApiHelper() : 14 | supportedApi(ApiLevel::v1803) 15 | { 16 | auto projFsLib = ::LoadLibraryW(L"ProjectedFSLib.dll"); 17 | if (!projFsLib) 18 | { 19 | throw gcnew FileLoadException(String::Format(CultureInfo::InvariantCulture, "Could not load ProjectedFSLib.dll to set up entry points.")); 20 | } 21 | 22 | if (::GetProcAddress(projFsLib, "PrjStartVirtualizing") != nullptr) 23 | { 24 | // We have the API introduced in Windows 10 version 1809. 25 | this->supportedApi = ApiLevel::v1809; 26 | 27 | this->_PrjStartVirtualizing = reinterpret_cast(::GetProcAddress(projFsLib, 28 | "PrjStartVirtualizing")); 29 | 30 | this->_PrjStopVirtualizing = reinterpret_cast(::GetProcAddress(projFsLib, 31 | "PrjStopVirtualizing")); 32 | 33 | this->_PrjWriteFileData = reinterpret_cast(::GetProcAddress(projFsLib, 34 | "PrjWriteFileData")); 35 | 36 | this->_PrjWritePlaceholderInfo = reinterpret_cast(::GetProcAddress(projFsLib, 37 | "PrjWritePlaceholderInfo")); 38 | 39 | this->_PrjAllocateAlignedBuffer = reinterpret_cast(::GetProcAddress(projFsLib, 40 | "PrjAllocateAlignedBuffer")); 41 | 42 | this->_PrjFreeAlignedBuffer = reinterpret_cast(::GetProcAddress(projFsLib, 43 | "PrjFreeAlignedBuffer")); 44 | 45 | this->_PrjGetVirtualizationInstanceInfo = reinterpret_cast(::GetProcAddress(projFsLib, 46 | "PrjGetVirtualizationInstanceInfo")); 47 | 48 | this->_PrjUpdateFileIfNeeded = reinterpret_cast(::GetProcAddress(projFsLib, 49 | "PrjUpdateFileIfNeeded")); 50 | 51 | this->_PrjMarkDirectoryAsPlaceholder = reinterpret_cast(::GetProcAddress(projFsLib, 52 | "PrjMarkDirectoryAsPlaceholder")); 53 | if (::GetProcAddress(projFsLib, "PrjWritePlaceholderInfo2") != nullptr) 54 | { 55 | // We have the API introduced in Windows 10 version 2004. 56 | this->supportedApi = ApiLevel::v2004; 57 | 58 | this->_PrjWritePlaceholderInfo2 = reinterpret_cast(::GetProcAddress(projFsLib, 59 | "PrjWritePlaceholderInfo2")); 60 | 61 | this->_PrjFillDirEntryBuffer2 = reinterpret_cast(::GetProcAddress(projFsLib, "PrjFillDirEntryBuffer2")); 62 | } 63 | 64 | ::FreeLibrary(projFsLib); 65 | 66 | if (!this->_PrjStartVirtualizing || 67 | !this->_PrjStopVirtualizing || 68 | !this->_PrjWriteFileData || 69 | !this->_PrjWritePlaceholderInfo || 70 | !this->_PrjAllocateAlignedBuffer || 71 | !this->_PrjFreeAlignedBuffer || 72 | !this->_PrjGetVirtualizationInstanceInfo || 73 | !this->_PrjUpdateFileIfNeeded || 74 | !this->_PrjMarkDirectoryAsPlaceholder) 75 | { 76 | throw gcnew EntryPointNotFoundException(String::Format(CultureInfo::InvariantCulture, 77 | "Could not get a required entry point.")); 78 | } 79 | 80 | if (this->supportedApi >= ApiLevel::v2004) 81 | { 82 | if (!this->_PrjWritePlaceholderInfo2 || 83 | !this->_PrjFillDirEntryBuffer2) 84 | { 85 | throw gcnew EntryPointNotFoundException(String::Format(CultureInfo::InvariantCulture, 86 | "Could not get a required entry point.")); 87 | } 88 | } 89 | } 90 | else if (::GetProcAddress(projFsLib, "PrjStartVirtualizationInstance") == nullptr) 91 | { 92 | // Something is wrong; we didn't find the 1809 API nor can we find the 1803 API even though 93 | // we loaded ProjectedFSLib.dll. 94 | 95 | ::FreeLibrary(projFsLib); 96 | 97 | throw gcnew EntryPointNotFoundException(String::Format((CultureInfo::InvariantCulture, 98 | "Cannot find ProjFS API."))); 99 | } 100 | else 101 | { 102 | // We have the beta API introduced in Windows 10 version 1803. 103 | 104 | this->_PrjStartVirtualizationInstance = reinterpret_cast(::GetProcAddress(projFsLib, 105 | "PrjStartVirtualizationInstance")); 106 | 107 | this->_PrjStartVirtualizationInstanceEx = reinterpret_cast(::GetProcAddress(projFsLib, 108 | "PrjStartVirtualizationInstanceEx")); 109 | 110 | this->_PrjStopVirtualizationInstance = reinterpret_cast(::GetProcAddress(projFsLib, 111 | "PrjStopVirtualizationInstance")); 112 | 113 | this->_PrjGetVirtualizationInstanceIdFromHandle = reinterpret_cast(::GetProcAddress(projFsLib, 114 | "PrjGetVirtualizationInstanceIdFromHandle")); 115 | 116 | this->_PrjConvertDirectoryToPlaceholder = reinterpret_cast(::GetProcAddress(projFsLib, 117 | "PrjConvertDirectoryToPlaceholder")); 118 | 119 | this->_PrjWritePlaceholderInformation = reinterpret_cast(::GetProcAddress(projFsLib, 120 | "PrjWritePlaceholderInformation")); 121 | 122 | this->_PrjUpdatePlaceholderIfNeeded = reinterpret_cast(::GetProcAddress(projFsLib, 123 | "PrjUpdatePlaceholderIfNeeded")); 124 | 125 | this->_PrjWriteFile = reinterpret_cast(::GetProcAddress(projFsLib, 126 | "PrjWriteFile")); 127 | 128 | this->_PrjCommandCallbacksInit = reinterpret_cast(::GetProcAddress(projFsLib, 129 | "PrjCommandCallbacksInit")); 130 | 131 | ::FreeLibrary(projFsLib); 132 | 133 | if (!this->_PrjStartVirtualizationInstance || 134 | !this->_PrjStartVirtualizationInstanceEx || 135 | !this->_PrjStopVirtualizationInstance || 136 | !this->_PrjGetVirtualizationInstanceIdFromHandle || 137 | !this->_PrjConvertDirectoryToPlaceholder || 138 | !this->_PrjWritePlaceholderInformation || 139 | !this->_PrjUpdatePlaceholderIfNeeded || 140 | !this->_PrjWriteFile || 141 | !this->_PrjCommandCallbacksInit) 142 | { 143 | throw gcnew EntryPointNotFoundException(String::Format(CultureInfo::InvariantCulture, 144 | "Could not get a required entry point.")); 145 | } 146 | } 147 | } 148 | 149 | bool ApiHelper::UseBetaApi::get(void) 150 | { 151 | return (this->supportedApi == ApiLevel::v1803); 152 | } 153 | 154 | ApiLevel ApiHelper::SupportedApi::get(void) 155 | { 156 | return this->supportedApi; 157 | } 158 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/ApiHelper.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #pragma once 5 | 6 | namespace Microsoft { 7 | namespace Windows { 8 | namespace ProjFS { 9 | /// 10 | /// Defines values describing the APIs available from this wrapper. 11 | /// 12 | enum class ApiLevel : short 13 | { 14 | /// Using Windows 10 version 1803 beta API. 15 | v1803 = 1803, 16 | 17 | /// Using Windows 10 version 1809 API. 18 | v1809 = 1809, 19 | 20 | /// Adding APIs introduced in Windows 10 version 2004. 21 | v2004 = 2004, 22 | }; 23 | /// Helper class for using the correct native APIs in the managed layer. 24 | /// 25 | /// 26 | /// The final ProjFS native APIs released in Windows 10 version 1809 differ from the now-deprecated 27 | /// beta APIs released in Windows 10 version 1803. In 1809 the beta APIs are still exported from 28 | /// ProjectedFSLib.dll, in case an experimental provider written against the native 1803 APIs is run 29 | /// on 1809. 30 | /// 31 | /// 32 | /// This managed API wrapper is meant to be usable on 1803 and later, so it is able to use the 33 | /// beta 1803 native APIs and the final 1809 native APIs. Since the 1809 APIs are not present on 34 | /// 1803, and because we intend to remove the beta 1803 APIs from a later version of Windows, we 35 | /// dynamically load the native APIs here. If we didn't do that then trying to use this managed 36 | /// wrapper on a version of Windows missing one or the other native API would result in the program 37 | /// dying on startup with an unhandled System::IO::FileLoadException: "A procedure 38 | /// imported by 'ProjectedFSLib.Managed.dll' could not be loaded." 39 | /// 40 | /// 41 | /// It is likely that at some point after removing the beta 1803 native APIs from Windows we will 42 | /// also remove support for them from this managed wrapper. 43 | /// 44 | /// 45 | ref class ApiHelper { 46 | internal: 47 | ApiHelper(); 48 | 49 | property bool UseBetaApi 50 | { 51 | bool get(void); 52 | } 53 | 54 | property ApiLevel SupportedApi 55 | { 56 | ApiLevel get(void); 57 | } 58 | 59 | private: 60 | 61 | #pragma region Signatures for Windows 10 version 2004 APIs 62 | 63 | typedef HRESULT(__stdcall* t_PrjWritePlaceholderInfo2)( 64 | _In_ PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceVirtualizationContext, 65 | _In_ PCWSTR destinationFileName, 66 | _In_reads_bytes_(placeholderInfoSize) const PRJ_PLACEHOLDER_INFO* placeholderInfo, 67 | _In_ UINT32 placeholderInfoSize, 68 | _In_opt_ const PRJ_EXTENDED_INFO* ExtendedInfo 69 | ); 70 | 71 | typedef HRESULT(__stdcall* t_PrjFillDirEntryBuffer2) ( 72 | _In_ PRJ_DIR_ENTRY_BUFFER_HANDLE dirEntryBufferHandle, 73 | _In_ PCWSTR fileName, 74 | _In_opt_ PRJ_FILE_BASIC_INFO* fileBasicInfo, 75 | _In_opt_ PRJ_EXTENDED_INFO* extendedInfo 76 | ); 77 | 78 | #pragma endregion 79 | 80 | #pragma region Signatures for Windows 10 version 1809 APIs 81 | 82 | typedef HRESULT (__stdcall* t_PrjStartVirtualizing)( 83 | _In_ PCWSTR virtualizationRootPath, 84 | _In_ const PRJ_CALLBACKS* callbacks, 85 | _In_opt_ const void* instanceContext, 86 | _In_opt_ const PRJ_STARTVIRTUALIZING_OPTIONS* options, 87 | _Outptr_ PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT* namespaceVirtualizationContext 88 | ); 89 | 90 | typedef void (__stdcall* t_PrjStopVirtualizing)( 91 | _In_ PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceVirtualizationContext 92 | ); 93 | 94 | typedef HRESULT (__stdcall* t_PrjWriteFileData)( 95 | _In_ PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceVirtualizationContext, 96 | _In_ const GUID* dataStreamId, 97 | _In_reads_bytes_(length) void* buffer, 98 | _In_ UINT64 byteOffset, 99 | _In_ UINT32 length 100 | ); 101 | 102 | typedef HRESULT (__stdcall* t_PrjWritePlaceholderInfo)( 103 | _In_ PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceVirtualizationContext, 104 | _In_ PCWSTR destinationFileName, 105 | _In_reads_bytes_(placeholderInfoSize) const PRJ_PLACEHOLDER_INFO* placeholderInfo, 106 | _In_ UINT32 placeholderInfoSize 107 | ); 108 | 109 | typedef void* (__stdcall* t_PrjAllocateAlignedBuffer)( 110 | _In_ PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceVirtualizationContext, 111 | _In_ size_t size 112 | ); 113 | 114 | typedef void (__stdcall* t_PrjFreeAlignedBuffer)( 115 | _In_ void* buffer 116 | ); 117 | 118 | typedef HRESULT (__stdcall* t_PrjGetVirtualizationInstanceInfo)( 119 | _In_ PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceVirtualizationContext, 120 | _Out_ PRJ_VIRTUALIZATION_INSTANCE_INFO* virtualizationInstanceInfo 121 | ); 122 | 123 | typedef HRESULT (__stdcall* t_PrjUpdateFileIfNeeded)( 124 | _In_ PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceVirtualizationContext, 125 | _In_ PCWSTR destinationFileName, 126 | _In_reads_bytes_(placeholderInfoSize) const PRJ_PLACEHOLDER_INFO* placeholderInfo, 127 | _In_ UINT32 placeholderInfoSize, 128 | _In_opt_ PRJ_UPDATE_TYPES updateFlags, 129 | _Out_opt_ PRJ_UPDATE_FAILURE_CAUSES* failureReason 130 | ); 131 | 132 | typedef HRESULT (__stdcall* t_PrjMarkDirectoryAsPlaceholder)( 133 | _In_ PCWSTR rootPathName, 134 | _In_opt_ PCWSTR targetPathName, 135 | _In_opt_ const PRJ_PLACEHOLDER_VERSION_INFO* versionInfo, 136 | _In_ const GUID* virtualizationInstanceID 137 | ); 138 | 139 | #pragma endregion 140 | 141 | #pragma region Signatures for deprecated Windows 10 version 1803 142 | 143 | typedef HRESULT (__stdcall* t_PrjStartVirtualizationInstance)( 144 | _In_ LPCWSTR VirtualizationRootPath, 145 | _In_ PPRJ_COMMAND_CALLBACKS Callbacks, 146 | _In_opt_ DWORD Flags, 147 | _In_opt_ DWORD GlobalNotificationMask, 148 | _In_opt_ DWORD PoolThreadCount, 149 | _In_opt_ DWORD ConcurrentThreadCount, 150 | _In_opt_ PVOID InstanceContext, 151 | _Outptr_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE* VirtualizationInstanceHandle 152 | ); 153 | 154 | typedef HRESULT (__stdcall* t_PrjStartVirtualizationInstanceEx)( 155 | _In_ LPCWSTR VirtualizationRootPath, 156 | _In_ PPRJ_COMMAND_CALLBACKS Callbacks, 157 | _In_opt_ PVOID InstanceContext, 158 | _In_opt_ PVIRTUALIZATION_INST_EXTENDED_PARAMETERS ExtendedParameters, 159 | _Outptr_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE* VirtualizationInstanceHandle 160 | ); 161 | 162 | typedef HRESULT (__stdcall* t_PrjStopVirtualizationInstance)( 163 | _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle 164 | ); 165 | 166 | typedef HRESULT (__stdcall* t_PrjGetVirtualizationInstanceIdFromHandle)( 167 | _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle, 168 | _Out_ LPGUID VirtualizationInstanceID 169 | ); 170 | 171 | typedef HRESULT (__stdcall* t_PrjConvertDirectoryToPlaceholder)( 172 | _In_ LPCWSTR RootPathName, 173 | _In_ LPCWSTR TargetPathName, 174 | _In_opt_ PRJ_PLACEHOLDER_VERSION_INFO* VersionInfo, 175 | _In_opt_ DWORD Flags, 176 | _In_ LPCGUID VirtualizationInstanceID 177 | ); 178 | 179 | typedef HRESULT (__stdcall* t_PrjWritePlaceholderInformation)( 180 | _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle, 181 | _In_ LPCWSTR DestinationFileName, 182 | _In_ PPRJ_PLACEHOLDER_INFORMATION PlaceholderInformation, 183 | _In_ DWORD Length 184 | ); 185 | 186 | typedef HRESULT (__stdcall* t_PrjUpdatePlaceholderIfNeeded)( 187 | _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle, 188 | _In_ LPCWSTR DestinationFileName, 189 | _In_ PPRJ_PLACEHOLDER_INFORMATION PlaceholderInformation, 190 | _In_ DWORD Length, 191 | _In_opt_ DWORD UpdateFlags, 192 | _Out_opt_ PDWORD FailureReason 193 | ); 194 | 195 | typedef HRESULT (__stdcall* t_PrjWriteFile)( 196 | _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle, 197 | _In_ const GUID* StreamId, 198 | _In_reads_bytes_(Length) void* Buffer, 199 | _In_ UINT64 ByteOffset, 200 | _In_ UINT32 Length 201 | ); 202 | 203 | typedef HRESULT (__stdcall* t_PrjCommandCallbacksInit)( 204 | _In_ DWORD CallbacksSize, 205 | _Out_writes_bytes_(CallbacksSize) PPRJ_COMMAND_CALLBACKS Callbacks 206 | ); 207 | 208 | #pragma endregion 209 | 210 | ApiLevel supportedApi{ ApiLevel::v1803 }; 211 | 212 | internal: 213 | 214 | // 2004 API 215 | t_PrjWritePlaceholderInfo2 _PrjWritePlaceholderInfo2 = nullptr; 216 | t_PrjFillDirEntryBuffer2 _PrjFillDirEntryBuffer2 = nullptr; 217 | 218 | // 1809 API 219 | t_PrjStartVirtualizing _PrjStartVirtualizing = nullptr; 220 | t_PrjStopVirtualizing _PrjStopVirtualizing = nullptr; 221 | t_PrjWriteFileData _PrjWriteFileData = nullptr; 222 | t_PrjWritePlaceholderInfo _PrjWritePlaceholderInfo = nullptr; 223 | t_PrjAllocateAlignedBuffer _PrjAllocateAlignedBuffer = nullptr; 224 | t_PrjFreeAlignedBuffer _PrjFreeAlignedBuffer = nullptr; 225 | t_PrjGetVirtualizationInstanceInfo _PrjGetVirtualizationInstanceInfo = nullptr; 226 | t_PrjUpdateFileIfNeeded _PrjUpdateFileIfNeeded = nullptr; 227 | t_PrjMarkDirectoryAsPlaceholder _PrjMarkDirectoryAsPlaceholder = nullptr; 228 | 229 | // 1803 API 230 | t_PrjStartVirtualizationInstance _PrjStartVirtualizationInstance = nullptr; 231 | t_PrjStartVirtualizationInstanceEx _PrjStartVirtualizationInstanceEx = nullptr; 232 | t_PrjStopVirtualizationInstance _PrjStopVirtualizationInstance = nullptr; 233 | t_PrjGetVirtualizationInstanceIdFromHandle _PrjGetVirtualizationInstanceIdFromHandle = nullptr; 234 | t_PrjConvertDirectoryToPlaceholder _PrjConvertDirectoryToPlaceholder = nullptr; 235 | t_PrjWritePlaceholderInformation _PrjWritePlaceholderInformation = nullptr; 236 | t_PrjUpdatePlaceholderIfNeeded _PrjUpdatePlaceholderIfNeeded = nullptr; 237 | t_PrjWriteFile _PrjWriteFile = nullptr; 238 | t_PrjCommandCallbacksInit _PrjCommandCallbacksInit = nullptr; 239 | }; 240 | 241 | }}} // namespace Microsoft.Windows.ProjFS -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/AssemblyInfo.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ProjFS-Managed-API/3a59d9cb8534a45a1bb0bf3eb9ad1ffd7980dc03/ProjectedFSLib.Managed.API/AssemblyInfo.cpp -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/CallbackDelegates.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ProjFS-Managed-API/3a59d9cb8534a45a1bb0bf3eb9ad1ffd7980dc03/ProjectedFSLib.Managed.API/CallbackDelegates.h -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/DirectoryEnumerationResults.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #pragma once 5 | 6 | #include "IDirectoryEnumerationResults.h" 7 | #include "ApiHelper.h" 8 | 9 | using namespace System; 10 | using namespace System::Globalization; 11 | using namespace System::IO; 12 | 13 | namespace Microsoft { 14 | namespace Windows { 15 | namespace ProjFS { 16 | ///Helper class for providing the results of a directory enumeration. 17 | /// 18 | /// ProjFS passes an instance of this class to the provider in the 19 | /// parameter of its implementation of a GetDirectoryEnumerationCallback delegate. The provider 20 | /// calls one of its Add methods for each item in the enumeration 21 | /// to add it to the result set. 22 | /// 23 | public ref class DirectoryEnumerationResults : public IDirectoryEnumerationResults { 24 | internal: 25 | 26 | DirectoryEnumerationResults(PRJ_DIR_ENTRY_BUFFER_HANDLE bufferHandle, ApiHelper^ apiHelper) : 27 | m_dirEntryBufferHandle(bufferHandle), m_apiHelper(apiHelper) 28 | { } 29 | 30 | // Provides access to the native handle to the directory entry buffer. 31 | // Used internally by VirtualizationInstance::CompleteCommand(int, IDirectoryEnumerationResults^). 32 | property PRJ_DIR_ENTRY_BUFFER_HANDLE DirEntryBufferHandle 33 | { 34 | PRJ_DIR_ENTRY_BUFFER_HANDLE get(void) 35 | { 36 | return m_dirEntryBufferHandle; 37 | } 38 | }; 39 | 40 | public: 41 | 42 | /// Adds one entry to a directory enumeration result. 43 | /// 44 | /// 45 | /// In its implementation of a GetDirectoryEnumerationCallback delegate the provider 46 | /// calls this method for each matching file or directory in the enumeration. 47 | /// 48 | /// 49 | /// If the provider calls this Add overload, then the timestamps reported to the caller 50 | /// of the enumeration are the current system time. If the provider wants the caller to see other 51 | /// timestamps, it must use the other Add overload. 52 | /// 53 | /// 54 | /// If this method returns false, the provider returns and waits for 55 | /// the next GetDirectoryEnumerationCallback. Then it resumes filling the enumeration with 56 | /// the entry it was trying to add when it got false. 57 | /// 58 | /// 59 | /// If the method returns false for the first file or directory in the enumeration, the 60 | /// provider returns from the GetDirectoryEnumerationCallback 61 | /// method. 62 | /// 63 | /// 64 | /// The name of the file or directory. 65 | /// The size of the file. 66 | /// true if this item is a directory, false if it is a file. 67 | /// 68 | /// 69 | /// true if the entry was successfully added to the enumeration buffer, false otherwise. 70 | /// 71 | /// 72 | /// 73 | /// is null or empty. 74 | /// 75 | virtual bool Add( 76 | System::String^ fileName, 77 | long long fileSize, 78 | bool isDirectory) sealed 79 | { 80 | if (System::String::IsNullOrEmpty(fileName)) 81 | { 82 | throw gcnew System::ArgumentException(System::String::Format(System::Globalization::CultureInfo::InvariantCulture, 83 | "fileName cannot be empty.")); 84 | } 85 | 86 | pin_ptr pFileName = PtrToStringChars(fileName); 87 | 88 | PRJ_FILE_BASIC_INFO basicInfo = { 0 }; 89 | basicInfo.IsDirectory = isDirectory; 90 | basicInfo.FileSize = fileSize; 91 | 92 | auto hr = ::PrjFillDirEntryBuffer(pFileName, 93 | &basicInfo, 94 | m_dirEntryBufferHandle); 95 | 96 | if (FAILED(hr)) 97 | { 98 | return false; 99 | } 100 | 101 | return true; 102 | } 103 | 104 | /// Adds one entry to a directory enumeration result. 105 | /// 106 | /// 107 | /// In its implementation of a GetDirectoryEnumerationCallback delegate the provider 108 | /// calls this method for each matching file or directory in the enumeration. 109 | /// 110 | /// 111 | /// If this method returns false, the provider returns and waits for 112 | /// the next GetDirectoryEnumerationCallback. Then it resumes filling the enumeration with 113 | /// the entry it was trying to add when it got false. 114 | /// 115 | /// 116 | /// If the method returns false for the first file or directory in the enumeration, the 117 | /// provider returns from the GetDirectoryEnumerationCallback 118 | /// method. 119 | /// 120 | /// 121 | /// The name of the file or directory. 122 | /// The size of the file. 123 | /// true if this item is a directory, false if it is a file. 124 | /// The file attributes. 125 | /// The time the file was created. 126 | /// The time the file was last accessed. 127 | /// The time the file was last written to. 128 | /// The time the file was last changed. 129 | /// 130 | /// 131 | /// true if the entry was successfully added to the enumeration buffer, false otherwise. 132 | /// 133 | /// 134 | /// 135 | /// is null or empty. 136 | /// 137 | virtual bool Add( 138 | System::String^ fileName, 139 | long long fileSize, 140 | bool isDirectory, 141 | System::IO::FileAttributes fileAttributes, 142 | System::DateTime creationTime, 143 | System::DateTime lastAccessTime, 144 | System::DateTime lastWriteTime, 145 | System::DateTime changeTime) sealed 146 | { 147 | ValidateFileName(fileName); 148 | 149 | pin_ptr pFileName = PtrToStringChars(fileName); 150 | PRJ_FILE_BASIC_INFO basicInfo = BuildFileBasicInfo(fileSize, 151 | isDirectory, 152 | fileAttributes, 153 | creationTime, 154 | lastAccessTime, 155 | lastWriteTime, 156 | changeTime); 157 | 158 | auto hr = ::PrjFillDirEntryBuffer(pFileName, 159 | &basicInfo, 160 | m_dirEntryBufferHandle); 161 | 162 | if FAILED(hr) 163 | { 164 | return false; 165 | } 166 | 167 | return true; 168 | } 169 | 170 | /// Adds one entry to a directory enumeration result. 171 | /// 172 | /// 173 | /// In its implementation of a GetDirectoryEnumerationCallback delegate the provider 174 | /// calls this method for each matching file or directory in the enumeration. 175 | /// 176 | /// 177 | /// If this method returns false, the provider returns and waits for 178 | /// the next GetDirectoryEnumerationCallback. Then it resumes filling the enumeration with 179 | /// the entry it was trying to add when it got false. 180 | /// 181 | /// 182 | /// If the method returns false for the first file or directory in the enumeration, the 183 | /// provider returns from the GetDirectoryEnumerationCallback 184 | /// method. 185 | /// 186 | /// 187 | /// The name of the file or directory. 188 | /// The size of the file. 189 | /// true if this item is a directory, false if it is a file. 190 | /// The file attributes. 191 | /// The time the file was created. 192 | /// The time the file was last accessed. 193 | /// The time the file was last written to. 194 | /// The time the file was last changed. 195 | /// Specifies the symlink target path if the file is a symlink. 196 | /// 197 | /// 198 | /// true if the entry was successfully added to the enumeration buffer, false otherwise. 199 | /// 200 | /// 201 | /// 202 | /// is null or empty. 203 | /// 204 | virtual bool Add( 205 | System::String^ fileName, 206 | long long fileSize, 207 | bool isDirectory, 208 | System::IO::FileAttributes fileAttributes, 209 | System::DateTime creationTime, 210 | System::DateTime lastAccessTime, 211 | System::DateTime lastWriteTime, 212 | System::DateTime changeTime, 213 | System::String^ symlinkTargetOrNull) sealed 214 | { 215 | // This API is supported in Windows 10 version 2004 and above. 216 | if ((symlinkTargetOrNull != nullptr) && 217 | (m_apiHelper->SupportedApi < ApiLevel::v2004)) 218 | { 219 | throw gcnew NotImplementedException("PrjFillDirEntryBuffer2 is not supported in this version of Windows."); 220 | } 221 | 222 | ValidateFileName(fileName); 223 | 224 | pin_ptr pFileName = PtrToStringChars(fileName); 225 | PRJ_FILE_BASIC_INFO basicInfo = BuildFileBasicInfo(fileSize, 226 | isDirectory, 227 | fileAttributes, 228 | creationTime, 229 | lastAccessTime, 230 | lastWriteTime, 231 | changeTime); 232 | 233 | PRJ_EXTENDED_INFO extendedInfo = {}; 234 | if (symlinkTargetOrNull != nullptr) 235 | { 236 | extendedInfo.InfoType = PRJ_EXT_INFO_TYPE_SYMLINK; 237 | pin_ptr targetPath = PtrToStringChars(symlinkTargetOrNull); 238 | extendedInfo.Symlink.TargetName = targetPath; 239 | } 240 | 241 | HRESULT hr; 242 | hr = m_apiHelper->_PrjFillDirEntryBuffer2(m_dirEntryBufferHandle, 243 | pFileName, 244 | &basicInfo, 245 | (symlinkTargetOrNull != nullptr) ? &extendedInfo : nullptr); 246 | 247 | if FAILED(hr) 248 | { 249 | return false; 250 | } 251 | 252 | return true; 253 | } 254 | 255 | private: 256 | 257 | PRJ_DIR_ENTRY_BUFFER_HANDLE m_dirEntryBufferHandle; 258 | ApiHelper^ m_apiHelper; 259 | 260 | void ValidateFileName(System::String^ fileName) 261 | { 262 | if (System::String::IsNullOrEmpty(fileName)) 263 | { 264 | throw gcnew System::ArgumentException(System::String::Format(System::Globalization::CultureInfo::InvariantCulture, 265 | "fileName cannot be empty.")); 266 | } 267 | } 268 | 269 | PRJ_FILE_BASIC_INFO BuildFileBasicInfo(long long fileSize, 270 | bool isDirectory, 271 | System::IO::FileAttributes fileAttributes, 272 | System::DateTime creationTime, 273 | System::DateTime lastAccessTime, 274 | System::DateTime lastWriteTime, 275 | System::DateTime changeTime) 276 | { 277 | PRJ_FILE_BASIC_INFO basicInfo = { 0 }; 278 | 279 | if (creationTime != System::DateTime::MinValue) 280 | { 281 | basicInfo.CreationTime.QuadPart = creationTime.ToFileTime(); 282 | } 283 | 284 | if (lastAccessTime != System::DateTime::MinValue) 285 | { 286 | basicInfo.LastAccessTime.QuadPart = lastAccessTime.ToFileTime(); 287 | } 288 | 289 | if (lastWriteTime != System::DateTime::MinValue) 290 | { 291 | basicInfo.LastWriteTime.QuadPart = lastWriteTime.ToFileTime(); 292 | } 293 | 294 | if (changeTime != System::DateTime::MinValue) 295 | { 296 | basicInfo.ChangeTime.QuadPart = changeTime.ToFileTime(); 297 | } 298 | 299 | basicInfo.FileAttributes = static_cast(fileAttributes); 300 | basicInfo.IsDirectory = isDirectory; 301 | basicInfo.FileSize = fileSize; 302 | 303 | return basicInfo; 304 | } 305 | }; 306 | }}} // namespace Microsoft.Windows.ProjFS -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/HResult.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #pragma once 5 | 6 | namespace Microsoft { 7 | namespace Windows { 8 | namespace ProjFS { 9 | /// 10 | /// HRESULT values that ProjFS may report to a provider, or that a provider may return to ProjFS. 11 | /// 12 | /// 13 | /// 14 | /// .NET methods normally do not return error codes, preferring to throw exceptions. For the most 15 | /// part this API does not throw exceptions, preferring instead to return error codes. We do this 16 | /// for few reasons: 17 | /// 18 | /// 19 | /// 20 | /// This API is a relatively thin wrapper around a native API that itself returns HRESULT codes. 21 | /// This managed library would have to translate those error codes into exceptions to throw. 22 | /// 23 | /// 24 | /// 25 | /// 26 | /// Errors that a provider returns are sent through the file system, back to the user who is 27 | /// performing the I/O. If the provider callbacks threw exceptions, the managed library would 28 | /// just have to catch them and turn them into HRESULT codes. 29 | /// 30 | /// 31 | /// 32 | /// 33 | /// If the API methods described here threw exceptions, either the provider would have to catch 34 | /// them and turn them into error codes to return from its callbacks, or it would allow those 35 | /// exceptions to propagate and this managed library would still have to deal with them as 36 | /// described in the preceding bullet. 37 | /// 38 | /// 39 | /// 40 | /// So rather than deal with the overhead of exceptions just to try to conform to .NET conventions, 41 | /// this API largely dispenses with them and uses HRESULT codes. 42 | /// 43 | /// 44 | /// Note that for the convenience of C# developers the VirtualizationInstance::CreateWriteBuffer 45 | /// method does throw System::OutOfMemoryException if it cannot allocate the buffer. This 46 | /// makes the method convenient to use with the using keyword. 47 | /// 48 | /// 49 | /// Note that when HRESULT codes returned from the provider are sent to the file system, the ProjFS 50 | /// library translates them into NTSTATUS codes. Because there is not a 1-to-1 mapping of HRESULT 51 | /// codes to NTSTATUS codes, the set of HRESULT codes that a provider is allowed to return is 52 | /// necessarily constrained. 53 | /// 54 | /// 55 | /// A provider's IRequiredCallbacks method and On... delegate implementations may 56 | /// return any HResult value returned from a VirtualizationInstance, as well as the 57 | /// following HResult values: 58 | /// 59 | /// 60 | /// 61 | /// 62 | /// 63 | /// 64 | /// 65 | /// 66 | /// 67 | /// 68 | /// 69 | /// 70 | /// 71 | /// 72 | /// 73 | /// 74 | /// 75 | /// 76 | /// 77 | /// 78 | /// 79 | /// 80 | /// 81 | /// 82 | /// 83 | /// The remaining values in the HResult enum may be returned to a provider from ProjFS APIs 84 | /// and are primarily intended to communicate information to the provider. As noted above, if 85 | /// such a value is returned to a provider in its implementation of a callback or delegate, it may 86 | /// return the value to ProjFS. 87 | /// 88 | /// 89 | public enum class HResult : long 90 | { 91 | // In addition to any HResult value returned from a VirtualizationInstance method, a provider 92 | // may return any of the following HResult values. 93 | 94 | ///Success. 95 | Ok = S_OK, 96 | 97 | ///The data necessary to complete this operation is not yet available. 98 | Pending = HRESULT_FROM_WIN32(ERROR_IO_PENDING), 99 | 100 | ///Ran out of memory. 101 | OutOfMemory = E_OUTOFMEMORY, 102 | 103 | ///The data area passed to a system call is too small. 104 | InsufficientBuffer = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER), 105 | 106 | ///The system cannot find the file specified. 107 | FileNotFound = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), 108 | 109 | ///The provider that supports file system virtualization is temporarily unavailable. 110 | VirtualizationUnavaliable = HRESULT_FROM_WIN32(ERROR_FILE_SYSTEM_VIRTUALIZATION_UNAVAILABLE), 111 | 112 | ///The provider is in an invalid state that prevents it from servicing the callback 113 | ///(only use this if none of the other error codes is a better match). 114 | InternalError = HRESULT_FROM_WIN32(ERROR_INTERNAL_ERROR), 115 | 116 | ////////////////////////////////////////////////////////////////////////// 117 | // 118 | // The following HResult values are intended for use by the ProjFS library only. They are 119 | // for communicating information to the provider. 120 | // 121 | // ProjFS internally returns NTSTATUS codes to the I/O system. Because the mapping from NTSTATUS 122 | // to Win32 error/HRESULT codes is not 1:1, ProjFS's user-mode library performs a reverse 123 | // mapping for selected error codes. ProjFS performs such a reverse mapping for the codes 124 | // listed above. Those listed below may not have a reverse mapping. Any code for which ProjFS 125 | // does not have a reverse mapping will be returned to the I/O system as 126 | // STATUS_INTERNAL_ERROR. 127 | // 128 | ////////////////////////////////////////////////////////////////////////// 129 | 130 | ///An attempt was made to perform an initialization operation when initialization 131 | ///has already been completed. 132 | AlreadyInitialized = HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED), 133 | 134 | ///Access is denied. 135 | AccessDenied = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED), 136 | 137 | ///An attempt has been made to remove a file or directory that cannot be deleted. 138 | // There is not a Win32 error code that can reverse-map back to STATUS_CANNOT_DELETE. However 139 | // the I/O system expects the file system to return that status code as an indication that a 140 | // delete is not allowed. To ensure the I/O system gets the correct code we define it here 141 | // by hand. This is equivalent to HRESULT_FROM_NT(STATUS_CANNOT_DELETE). Doing it this way 142 | // saves us from having to do weird things with #include and #define. 143 | CannotDelete = ((HRESULT) (0xc0000121 | 0x10000000)), 144 | 145 | ///The directory name is invalid (it may not be a directory). 146 | Directory = HRESULT_FROM_WIN32(ERROR_DIRECTORY), 147 | 148 | ///The directory is not empty. 149 | DirNotEmpty = HRESULT_FROM_WIN32(ERROR_DIR_NOT_EMPTY), 150 | 151 | ///Invalid handle (it may already be closed). 152 | Handle = E_HANDLE, 153 | 154 | ///One or more arguments are invalid. 155 | InvalidArg = E_INVALIDARG, 156 | 157 | ///The system cannot find the path specified. 158 | PathNotFound = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND), 159 | 160 | ///The object manager encountered a reparse point while retrieving an object. 161 | ReparsePointEncountered = HRESULT_FROM_WIN32(ERROR_REPARSE_POINT_ENCOUNTERED), 162 | 163 | ///The virtualization operation is not allowed on the file in its current state. 164 | VirtualizationInvalidOp = HRESULT_FROM_WIN32(ERROR_FILE_SYSTEM_VIRTUALIZATION_INVALID_OPERATION), 165 | }; 166 | }}} // namespace Microsoft.Windows.ProjFS 167 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/IDirectoryEnumerationResults.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #pragma once 5 | 6 | namespace Microsoft { 7 | namespace Windows { 8 | namespace ProjFS { 9 | 10 | /// 11 | /// Interface to allow for easier unit testing of a virtualization provider. 12 | /// 13 | /// 14 | /// This class defines the interface implemented by the Microsoft.Windows.ProjFS.DirectoryEnumerationResults 15 | /// class. This interface class is provided for use by unit tests to mock up the interface to ProjFS. 16 | /// 17 | public interface class IDirectoryEnumerationResults 18 | { 19 | public: 20 | 21 | /// 22 | /// When overridden in a derived class, adds one entry to a directory enumeration result. 23 | /// 24 | /// 25 | /// 26 | /// In its implementation of a GetDirectoryEnumerationCallback delegate the provider 27 | /// calls this method for each matching file or directory in the enumeration. 28 | /// 29 | /// 30 | /// If this method returns false, the provider returns HResult.Ok and waits for 31 | /// the next GetDirectoryEnumerationCallback. Then it resumes filling the enumeration with 32 | /// the entry it was trying to add when it got false. 33 | /// 34 | /// 35 | /// If the function returns false for the first file or directory in the enumeration, the 36 | /// provider returns HResult.InsufficientBuffer from the GetDirectoryEnumerationCallback 37 | /// method. 38 | /// 39 | /// 40 | /// IMPORTANT: File and directory names passed to this method must be in the sort 41 | /// specified by PrjFileNameCompare 42 | /// (see https://docs.microsoft.com/en-us/windows/win32/api/projectedfslib/nf-projectedfslib-prjfilenamecompare ), 43 | /// or else names can be duplicated or missing from the enumeration results presented to the 44 | /// process enumerating the filesystem. 45 | /// 46 | /// 47 | /// The name of the file or directory. 48 | /// The size of the file. 49 | /// true if this item is a directory, false if it is a file. 50 | /// 51 | /// 52 | /// true if the entry was successfully added to the enumeration buffer, false otherwise. 53 | /// 54 | /// 55 | bool Add( 56 | System::String^ fileName, 57 | long long fileSize, 58 | bool isDirectory 59 | ); 60 | 61 | /// 62 | /// When overridden in a derived class, adds one entry to a directory enumeration result. 63 | /// 64 | /// 65 | /// 66 | /// In its implementation of a GetDirectoryEnumerationCallback delegate the provider 67 | /// calls this method for each matching file or directory in the enumeration. 68 | /// 69 | /// 70 | /// If this method returns false, the provider returns HResult.Ok and waits for 71 | /// the next GetDirectoryEnumerationCallback. Then it resumes filling the enumeration with 72 | /// the entry it was trying to add when it got false. 73 | /// 74 | /// 75 | /// If the function returns false for the first file or directory in the enumeration, the 76 | /// provider returns HResult.InsufficientBuffer from the GetDirectoryEnumerationCallback 77 | /// method. 78 | /// 79 | /// 80 | /// IMPORTANT: File and directory names passed to this method must be in the sort 81 | /// specified by PrjFileNameCompare 82 | /// (see https://docs.microsoft.com/en-us/windows/win32/api/projectedfslib/nf-projectedfslib-prjfilenamecompare ), 83 | /// or else names can be duplicated or missing from the enumeration results presented to the 84 | /// process enumerating the filesystem. 85 | /// 86 | /// 87 | /// The name of the file or directory. 88 | /// The size of the file. 89 | /// true if this item is a directory, false if it is a file. 90 | /// The file attributes. 91 | /// Specifies the time that the file was created. 92 | /// Specifies the time that the file was last accessed. 93 | /// Specifies the time that the file was last written to. 94 | /// Specifies the last time the file was changed. 95 | /// 96 | /// 97 | /// true if the entry was successfully added to the enumeration buffer, false otherwise. 98 | /// 99 | /// 100 | bool Add( 101 | System::String^ fileName, 102 | long long fileSize, 103 | bool isDirectory, 104 | System::IO::FileAttributes fileAttributes, 105 | System::DateTime creationTime, 106 | System::DateTime lastAccessTime, 107 | System::DateTime lastWriteTime, 108 | System::DateTime changeTime 109 | ); 110 | 111 | /// 112 | /// When overridden in a derived class, adds one entry to a directory enumeration result. 113 | /// 114 | /// 115 | /// 116 | /// In its implementation of a GetDirectoryEnumerationCallback delegate the provider 117 | /// calls this method for each matching file or directory in the enumeration. 118 | /// 119 | /// 120 | /// If this method returns false, the provider returns HResult.Ok and waits for 121 | /// the next GetDirectoryEnumerationCallback. Then it resumes filling the enumeration with 122 | /// the entry it was trying to add when it got false. 123 | /// 124 | /// 125 | /// If the function returns false for the first file or directory in the enumeration, the 126 | /// provider returns HResult.InsufficientBuffer from the GetDirectoryEnumerationCallback 127 | /// method. 128 | /// 129 | /// 130 | /// IMPORTANT: File and directory names passed to this method must be in the sort 131 | /// specified by PrjFileNameCompare 132 | /// (see https://docs.microsoft.com/en-us/windows/win32/api/projectedfslib/nf-projectedfslib-prjfilenamecompare ), 133 | /// or else names can be duplicated or missing from the enumeration results presented to the 134 | /// process enumerating the filesystem. 135 | /// 136 | /// 137 | /// The name of the file or directory. 138 | /// The size of the file. 139 | /// true if this item is a directory, false if it is a file. 140 | /// The file attributes. 141 | /// Specifies the time that the file was created. 142 | /// Specifies the time that the file was last accessed. 143 | /// Specifies the time that the file was last written to. 144 | /// Specifies the last time the file was changed. 145 | /// Specifies the symlink target path if the file is a symlink. 146 | /// 147 | /// 148 | /// true if the entry was successfully added to the enumeration buffer, false otherwise. 149 | /// 150 | /// 151 | bool Add( 152 | System::String ^ fileName, 153 | long long fileSize, 154 | bool isDirectory, 155 | System::IO::FileAttributes fileAttributes, 156 | System::DateTime creationTime, 157 | System::DateTime lastAccessTime, 158 | System::DateTime lastWriteTime, 159 | System::DateTime changeTime, 160 | System::String ^ symlinkTargetOrNull 161 | ); 162 | }; 163 | 164 | }}} // namespace Microsoft.Windows.ProjFS -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/IVirtualizationInstance.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ProjFS-Managed-API/3a59d9cb8534a45a1bb0bf3eb9ad1ffd7980dc03/ProjectedFSLib.Managed.API/IVirtualizationInstance.h -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/IWriteBuffer.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #pragma once 5 | 6 | namespace Microsoft { 7 | namespace Windows { 8 | namespace ProjFS { 9 | 10 | /// 11 | /// Interface to allow for easier unit testing of a virtualization provider. 12 | /// 13 | /// 14 | /// This class defines the interface implemented by the Microsoft.Windows.ProjFS.WriteBuffer 15 | /// class. This interface class is provided for use by unit tests to mock up the interface to ProjFS. 16 | /// 17 | public interface class IWriteBuffer : System::IDisposable 18 | { 19 | public: 20 | 21 | /// 22 | /// When overridden in a derived class, gets the allocated length of the buffer. 23 | /// 24 | property long long Length 25 | { 26 | long long get(void); 27 | }; 28 | 29 | /// 30 | /// When overridden in a derived class, gets a 31 | /// representing the internal buffer. 32 | /// 33 | property System::IO::UnmanagedMemoryStream^ Stream 34 | { 35 | System::IO::UnmanagedMemoryStream^ get(void); 36 | }; 37 | 38 | /// 39 | /// When overridden in a derived class, gets a pointer to the internal buffer. 40 | /// 41 | property System::IntPtr Pointer 42 | { 43 | System::IntPtr get(void); 44 | } 45 | }; 46 | 47 | }}} // namespace Microsoft.Windows.ProjFS -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/NetCore/ProjectedFSLib.Managed.Netcore.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {D29E5723-25E6-41C7-AEB9-099CDE30538A} 5 | netcoreapp3.1 6 | NetCore 7 | 8 | 9 | 10 | 13 | 4564 14 | /Zi %(AdditionalOptions) 15 | 16 | 17 | /profile /opt:ref /opt:icf /incremental:no %(AdditionalOptions) 18 | 19 | 20 | 21 | 22 | 25 | 4564 26 | /Zi %(AdditionalOptions) 27 | 28 | 29 | /profile /opt:ref /opt:icf /incremental:no %(AdditionalOptions) 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/NetFramework/ProjectedFSLib.Managed.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {4E5F40B3-B56F-4B62-92CB-68E7E0E36AFA} 5 | v4.8 6 | true 7 | 8 | 9 | 10 | /Zi %(AdditionalOptions) 11 | 12 | 13 | /profile /opt:ref /opt:icf /incremental:no %(AdditionalOptions) 14 | 15 | 16 | 17 | 18 | /Zi %(AdditionalOptions) 19 | 20 | 21 | /profile /opt:ref /opt:icf /incremental:no %(AdditionalOptions) 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/NetFramework/ProjectedFSLib.Managed.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | Header Files 38 | 39 | 40 | Header Files 41 | 42 | 43 | Header Files 44 | 45 | 46 | Header Files 47 | 48 | 49 | Header Files 50 | 51 | 52 | Header Files 53 | 54 | 55 | Header Files 56 | 57 | 58 | Header Files 59 | 60 | 61 | Header Files 62 | 63 | 64 | Header Files 65 | 66 | 67 | Header Files 68 | 69 | 70 | Header Files 71 | 72 | 73 | Header Files 74 | 75 | 76 | 77 | 78 | Source Files 79 | 80 | 81 | Source Files 82 | 83 | 84 | Source Files 85 | 86 | 87 | Source Files 88 | 89 | 90 | Source Files 91 | 92 | 93 | Source Files 94 | 95 | 96 | 97 | 98 | Resource Files 99 | 100 | 101 | 102 | 103 | Resource Files 104 | 105 | 106 | 107 | 108 | Source Files 109 | 110 | 111 | Source Files 112 | 113 | 114 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/NotificationMapping.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #pragma once 5 | 6 | namespace Microsoft { 7 | namespace Windows { 8 | namespace ProjFS { 9 | /// 10 | /// Represents a path relative to a virtualization root and the notification bit mask that should apply to it. 11 | /// 12 | /// 13 | /// 14 | /// A object describes a "notification mapping", which is a pairing between a directory 15 | /// (referred to as a "notification root") and a set of notifications, expressed as a bit mask, which 16 | /// ProjFS should send for that directory and its descendants. 17 | /// 18 | /// 19 | /// The provider passes zero or more objects to the 20 | /// parameter of the VirtualizationInstance::StartVirtualizing method to configure 21 | /// notifications for the virtualization root. 22 | /// 23 | /// 24 | /// If the provider does not specify any notification mappings, ProjFS will default to sending the 25 | /// notifications , , 26 | /// and for all files and directories 27 | /// in the virtualization instance. 28 | /// 29 | /// 30 | /// The property holds the notification root. It is specified 31 | /// relative to the virtualization root, with an empty string representing the virtualization root 32 | /// itself. 33 | /// 34 | /// 35 | /// If the provider specifies multiple notification mappings, and some are descendants of others, 36 | /// the mappings must be specified in descending depth. Notification mappings at deeper levels 37 | /// override higher-level mappings for their descendants. 38 | /// 39 | /// 40 | /// For example, consider the following virtualization instance layout, with C:\VirtRoot as the 41 | /// virtualization root: 42 | /// 43 | /// C:\VirtRoot 44 | /// +--- baz 45 | /// \--- foo 46 | /// +--- subdir1 47 | /// \--- subdir2 48 | /// 49 | /// The provider wants: 50 | /// 51 | /// 52 | /// Notification of new file/directory creates for most of the virtualization instance 53 | /// 54 | /// 55 | /// Notification of new file/directory creates, file opens, and file deletes for C:\VirtRoot\foo 56 | /// 57 | /// 58 | /// No notifications for C:\VirtRoot\foo\subdir1 59 | /// 60 | /// 61 | /// The provider could describe this with the following pseudocode: 62 | /// 63 | /// List<NotificationMapping> notificationMappings = new List<NotificationMapping>() 64 | /// { 65 | /// // Configure default notifications 66 | /// new NotificationMapping(NotificationType.NewFileCreated, 67 | /// string.Empty), 68 | /// // Configure notifications for C:\VirtRoot\foo 69 | /// new NotificationMapping(NotificationType.NewFileCreated | NotificationType.FileOpened | NotificationType.FileHandleClosedFileDeleted, 70 | /// "foo"), 71 | /// // Configure notifications for C:\VirtRoot\foo\subdir1 72 | /// new NotificationMapping(NotificationType.None, 73 | /// "foo\\subdir1"), 74 | /// }; 75 | /// 76 | /// // Call VirtualizationRoot.StartVirtualizing() passing in the notificationMappings List. 77 | /// 78 | /// 79 | /// 80 | public ref class NotificationMapping 81 | { 82 | public: 83 | /// 84 | /// Initializes a new instance of the class with the 85 | /// property set to the virtualization root (i.e. null) 86 | /// and the property set to . 87 | /// 88 | NotificationMapping(); 89 | 90 | /// 91 | /// Initializes a new instance of the class with the 92 | /// specified property values. 93 | /// 94 | /// The set of notifications that ProjFS should return for the 95 | /// virtualization root specified in . 96 | /// The path to the notification root, relative to the virtualization 97 | /// root. The virtualization root itself must be specified as an empty string. 98 | /// 99 | /// is . or begins with .\. 100 | /// must be specified relative to the virtualization root, with the virtualization root itself 101 | /// specified as an empty string. 102 | /// 103 | NotificationMapping(NotificationType notificationMask, System::String^ notificationRoot); 104 | 105 | /// 106 | /// A bit vector of NotificationType values. 107 | /// 108 | /// 109 | /// ProjFS will send to the provider the specified notifications for operations performed on 110 | /// the directory specified by the property and its descendants. 111 | /// 112 | property NotificationType NotificationMask 113 | { 114 | NotificationType get(void); 115 | void set(NotificationType mask); 116 | } 117 | 118 | /// 119 | /// A path to a directory, relative to the virtualization root. The virtualization root itself 120 | /// must be specified as an empty string. 121 | /// 122 | /// 123 | /// ProjFS will send to the provider the notifications specified in 124 | /// for this directory and its descendants. 125 | /// 126 | /// 127 | /// The notification root value is . or begins with .\. The notification root 128 | /// must be specified relative to the virtualization root, with the virtualization root itself 129 | /// specified as an empty string. 130 | /// 131 | property System::String^ NotificationRoot 132 | { 133 | System::String^ get(void); 134 | void set(System::String^ root); 135 | } 136 | 137 | private: 138 | NotificationType notificationMask; 139 | System::String^ notificationRoot; 140 | }; 141 | 142 | inline NotificationMapping::NotificationMapping() 143 | : notificationMask(NotificationType::None) 144 | , notificationRoot(nullptr) 145 | { 146 | } 147 | 148 | inline NotificationMapping::NotificationMapping(NotificationType notificationMask, System::String^ notificationRoot) 149 | : notificationMask(notificationMask) 150 | , notificationRoot(notificationRoot) 151 | { 152 | if ((notificationRoot == ".") || 153 | notificationRoot->StartsWith(".\\")) 154 | { 155 | throw gcnew System::ArgumentException(System::String::Format(System::Globalization::CultureInfo::InvariantCulture, 156 | "notificationRoot cannot be \".\" or begin with \".\\\"")); 157 | } 158 | } 159 | 160 | inline NotificationType NotificationMapping::NotificationMask::get(void) 161 | { 162 | return this->notificationMask; 163 | } 164 | 165 | inline void NotificationMapping::NotificationMask::set(NotificationType mask) 166 | { 167 | this->notificationMask = mask; 168 | } 169 | 170 | inline System::String^ NotificationMapping::NotificationRoot::get(void) 171 | { 172 | return this->notificationRoot; 173 | } 174 | 175 | inline void NotificationMapping::NotificationRoot::set(System::String^ root) 176 | { 177 | if ((root == ".") || 178 | root->StartsWith(".\\")) 179 | { 180 | throw gcnew System::ArgumentException(System::String::Format(System::Globalization::CultureInfo::InvariantCulture, 181 | "The notification root path cannot be \".\" or begin with \".\\\"")); 182 | } 183 | this->notificationRoot = root; 184 | } 185 | }}} // namespace Microsoft.Windows.ProjFS 186 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/NotificationType.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #pragma once 5 | 6 | namespace Microsoft { 7 | namespace Windows { 8 | namespace ProjFS { 9 | /// 10 | /// Defines values for file system operation notifications ProjFS can send to a provider. 11 | /// 12 | /// 13 | /// 14 | /// ProjFS can send notifications of file system activity to a provider. When the provider 15 | /// starts a virtualization instance it specifies which notifications it wishes to receive. 16 | /// It may also specify a new set of notifications for a file when it is created or renamed. 17 | /// The provider must set implementations of Notify...Callback delegates in the OnNotify... 18 | /// properties of ProjFS.VirtualizationInstance in order to receive the notifications 19 | /// for which it registers. 20 | /// 21 | /// 22 | /// ProjFS sends notifications for files and directories managed by an active virtualization 23 | /// instance. That is, ProjFS will send notifications for the virtualization root and its 24 | /// descendants. Symbolic links and junctions within the virtualization root are not traversed 25 | /// when determining what constitutes a descendant of the virtualization root. 26 | /// 27 | /// 28 | /// ProjFS sends notifications only for the primary data stream of a file. ProjFS does not 29 | /// send notifications for operations on alternate data streams. 30 | /// 31 | /// 32 | /// ProjFS does not send notifications for an inactive virtualization instance. A virtualization 33 | /// instance is inactive if any one of the following is true: 34 | /// 35 | /// 36 | /// 37 | /// The provider has not yet started it by calling ProjFS.VirtualizationInstance.StartVirtualizing. 38 | /// 39 | /// 40 | /// 41 | /// 42 | /// The provider has stopped the instance by calling ProjFS.VirtualizationInstance.StopVirtualizing. 43 | /// 44 | /// 45 | /// 46 | /// 47 | /// The provider process has exited. 48 | /// 49 | /// 50 | /// 51 | /// 52 | /// 53 | /// The provider may specify which notifications it wishes to receive when starting a virtualization 54 | /// instance, or in response to a notification that allows a new notification mask to be set. 55 | /// The provider specifies a default set of notifications that it wants ProjFS to send for the 56 | /// virtualization instance when it starts the instance. The provider specifies the default 57 | /// notifications via the parameter of the 58 | /// ProjFS.VirtualizationInstance constructor, which may specify different notification 59 | /// masks for different subtrees of the virtualization instance. 60 | /// 61 | /// 62 | /// The provider may choose to supply a different notification mask in response to a notification 63 | /// of file open, create, overwrite, or rename. ProjFS will continue to send these notifications 64 | /// for the given file until all handles to the file are closed. After that it will revert 65 | /// to the default set of notifications. Naturally if the default set of notifications does 66 | /// not specify that ProjFS should notify for open, create, etc., the provider will not get 67 | /// the opportunity to specify a different mask for those operations. 68 | /// 69 | /// 70 | [System::FlagsAttribute] 71 | public enum class NotificationType : unsigned long 72 | { 73 | /// 74 | /// Indicates that the provider does not want any notifications. This value overrides all others. 75 | /// 76 | None = PRJ_NOTIFY_SUPPRESS_NOTIFICATIONS, 77 | 78 | /// 79 | /// Indicates that ProjFS should call the provider's OnNotifyFileOpened callback when a handle is created to an existing file or directory. 80 | /// 81 | FileOpened = PRJ_NOTIFY_FILE_OPENED, 82 | 83 | /// 84 | /// Indicates that ProjFS should call the provider's OnNotifyNewFileCreated callback when a new file or directory is created. 85 | /// 86 | NewFileCreated = PRJ_NOTIFY_NEW_FILE_CREATED, 87 | 88 | /// 89 | /// Indicates that ProjFS should call the provider's OnNotifyFileOverwritten callback when an existing file is superseded or overwritten. 90 | /// 91 | FileOverwritten = PRJ_NOTIFY_FILE_OVERWRITTEN, 92 | 93 | /// 94 | /// Indicates that ProjFS should call the provider's OnNotifyPreDelete callback when a file or directory is about to be deleted. 95 | /// 96 | PreDelete = PRJ_NOTIFY_PRE_DELETE, 97 | 98 | /// 99 | /// Indicates that ProjFS should call the provider's OnNotifyPreRename callback when a file or directory is about to be renamed. 100 | /// 101 | PreRename = PRJ_NOTIFY_PRE_RENAME, 102 | 103 | /// 104 | /// Indicates that ProjFS should call the provider's OnNotifyPreCreateHardlink callback when a hard link is about to be created for a file. 105 | /// 106 | PreCreateHardlink = PRJ_NOTIFY_PRE_SET_HARDLINK, 107 | 108 | /// 109 | /// Indicates that ProjFS should call the provider's OnNotifyFileRenamed callback when a file or directory has been renamed. 110 | /// 111 | FileRenamed = PRJ_NOTIFY_FILE_RENAMED, 112 | 113 | /// 114 | /// Indicates that ProjFS should call the provider's OnNotifyHardlinkCreated callback when a hard link has been created for a file. 115 | /// 116 | HardlinkCreated = PRJ_NOTIFY_HARDLINK_CREATED, 117 | 118 | /// 119 | /// Indicates that ProjFS should call the provider's OnNotifyFileHandleClosedNoModification callback when a handle is closed on a file or directory 120 | /// and the closing handle neither modified nor deleted it. 121 | /// 122 | FileHandleClosedNoModification = PRJ_NOTIFY_FILE_HANDLE_CLOSED_NO_MODIFICATION, 123 | 124 | /// 125 | /// Indicates that ProjFS should call the provider's OnNotifyFileHandleClosedFileModifiedOrDeleted callback when a handle is closed on a file or 126 | /// directory and the closing handle was used to modify it. 127 | /// 128 | FileHandleClosedFileModified = PRJ_NOTIFY_FILE_HANDLE_CLOSED_FILE_MODIFIED, 129 | 130 | /// 131 | /// Indicates that ProjFS should call the provider's OnNotifyFileHandleClosedFileModifiedOrDeleted callback when a handle is closed on a file or 132 | /// directory and it is deleted as part of closing the handle. 133 | /// 134 | FileHandleClosedFileDeleted = PRJ_NOTIFY_FILE_HANDLE_CLOSED_FILE_DELETED, 135 | 136 | /// 137 | /// Indicates that ProjFS should call the provider's OnNotifyFilePreConvertToFull callback when it is about to convert a placeholder to a full file. 138 | /// 139 | FilePreConvertToFull = PRJ_NOTIFY_FILE_PRE_CONVERT_TO_FULL, 140 | 141 | /// 142 | /// This value is not used when calling the VirtualizationInstance constructor. It 143 | /// is only returned from OnNotify... callbacks that have a 144 | /// parameter, and indicates that the provider wants to continue to receive the notifications 145 | /// it registered for when starting the virtualization instance. 146 | /// 147 | UseExistingMask = static_cast>(PRJ_NOTIFY_USE_EXISTING_MASK) 148 | }; 149 | }}} // namespace Microsoft.Windows.ProjFS 150 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/OnDiskFileState.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #pragma once 5 | 6 | namespace Microsoft { 7 | namespace Windows { 8 | namespace ProjFS { 9 | /// Defines values describing the on-disk state of a virtualized file. 10 | /// 11 | /// 12 | /// The state is used to managed deleted files. When 13 | /// a directory is enumerated ProjFS merges the set of local items (placeholders, full files, 14 | /// etc.) with the set of virtual items projected by the provider's IRequiredCallbacks::GetDirectoryEnumerationCallback 15 | /// method. If an item appears in both the local and projected sets, the local item takes precedence. 16 | /// If a file does not exist there is no local state, so it would appear in the enumeration. 17 | /// However if that item had been deleted, having it appear in the enumeration would be unexpected. 18 | /// ProjFS deals with this by replacing a deleted item with a special hidden placeholder called 19 | /// a "tombstone". This has the following effects: 20 | /// 21 | /// 22 | /// Enumerations do not reveal the item. 23 | /// 24 | /// 25 | /// File opens that expect the item to exist fail with e.g. "file not found". 26 | /// 27 | /// 28 | /// File creates that expect to succeed only if the item does not exist succeed; 29 | /// ProjFS removes the tombstone as part of the operation. 30 | /// 31 | /// 32 | /// 33 | /// 34 | /// To illustrate the on-disk states consider the following sequence, given a ProjFS provider 35 | /// that has a single file "foo.txt" located in the virtualization root C:\root. 36 | /// 37 | /// 38 | /// An app enumerates C:\root. It sees the virtual file "foo.txt". Since the 39 | /// file has not yet been accessed, the file does not exist on disk. 40 | /// 41 | /// 42 | /// The app opens a handle to C:\root\foo.txt. ProjFS tells the provider to 43 | /// create a placeholder for it. The file's state is now 44 | /// 45 | /// 46 | /// The app reads the content of the file. The provider provides the file 47 | /// content to ProjFS and it is cached to C:\root\foo.txt. The file's state is 48 | /// now | . 49 | /// 50 | /// 51 | /// The app updates the Last Modified timestamp. The file's state is now 52 | /// | | . 53 | /// 54 | /// 55 | /// The app writes some new data to the file. C:\root\foo.txt's state 56 | /// is now . 57 | /// 58 | /// 59 | /// The app deletes C:\root\foo.txt. ProjFS replaces the file with a tombstone, 60 | /// so its state is now . 61 | /// Now when the app enumerates C:\root it does not see foo.txt. If it tries to open the 62 | /// file, the open fails with HResult.FileNotFound. 63 | /// 64 | /// 65 | /// 66 | /// 67 | [System::FlagsAttribute] 68 | public enum class OnDiskFileState : unsigned long 69 | { 70 | /// 71 | /// The item's content (primary data stream) is not present on the disk. The item's metadata 72 | /// (name, size, timestamps, attributes, etc.) is cached on the disk. 73 | /// 74 | Placeholder = PRJ_FILE_STATE_PLACEHOLDER, 75 | 76 | /// 77 | /// The item's content and metadata have been cached to the disk. Also referred to as a 78 | /// "partial file/directory". 79 | /// 80 | HydratedPlaceholder = PRJ_FILE_STATE_HYDRATED_PLACEHOLDER, 81 | 82 | /// 83 | /// The item's metadata has been locally modified and is no longer a cache of its state in 84 | /// the provider's store. Note that creating or deleting a file or directory under a placeholder 85 | /// directory causes that placeholder directory to become dirty. 86 | /// 87 | DirtyPlaceholder = PRJ_FILE_STATE_DIRTY_PLACEHOLDER, 88 | 89 | /// 90 | /// The item's content (primary data stream) has been modified. The file is no longer a cache 91 | /// of its state in the provider's store. Files that have been created on the local file 92 | /// system (i.e.that do not exist in the provider's store at all) are also considered to be 93 | /// full files. 94 | /// 95 | Full = PRJ_FILE_STATE_FULL, 96 | 97 | /// 98 | /// A special hidden placeholder that represents an item that has been deleted from the local 99 | /// file system. 100 | /// 101 | Tombstone = PRJ_FILE_STATE_TOMBSTONE 102 | }; 103 | }}} // namespace Microsoft.Windows.ProjFS -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/ProjectedFSLib.Managed.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 16.0 15 | ManagedCProj 16 | Microsoft.Windows.ProjFS 17 | 10.0 18 | 19 | 20 | 21 | 22 | DynamicLibrary 23 | true 24 | v142 25 | Unicode 26 | 27 | 28 | DynamicLibrary 29 | false 30 | v142 31 | Unicode 32 | Spectre 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | true 48 | MixedRecommendedRules.ruleset 49 | true 50 | ProjectedFSLib.Managed 51 | C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041\um;$(IncludePath) 52 | 53 | 54 | false 55 | MixedRecommendedRules.ruleset 56 | false 57 | ProjectedFSLib.Managed 58 | C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um;$(IncludePath) 59 | 60 | 61 | 62 | stdcpp17 63 | true 64 | true 65 | Level4 66 | true 67 | true 68 | C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um;$(BuildOutputDir)\$(ProjectName);%(AdditionalIncludeDirectories) 69 | 70 | 71 | ProjectedFSLib.lib;%(AdditionalDependencies) 72 | C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\um\x64;%(AdditionalLibraryDirectories) 73 | 74 | 75 | $(ProjectDir)\..\Scripts\CreateVersionHeader.bat $(ProjectName) $(ProjFSManagedVersion) $(SolutionDir) && $(ProjectDir)\..\Scripts\CreateCliAssemblyVersion.bat $(ProjectName) $(ProjFSManagedVersion) $(SolutionDir) 76 | 77 | 78 | $(BuildOutputDir)\$(ProjectName);%(AdditionalIncludeDirectories) 79 | 80 | 81 | 82 | 83 | stdcpp17 84 | true 85 | true 86 | Level4 87 | true 88 | false 89 | true 90 | C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um;$(BuildOutputDir)\$(ProjectName);%(AdditionalIncludeDirectories) 91 | 92 | 93 | ProjectedFSLib.lib;%(AdditionalDependencies) 94 | C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\um\x64;%(AdditionalLibraryDirectories) 95 | 96 | 97 | $(ProjectDir)\..\Scripts\CreateVersionHeader.bat $(ProjectName) $(ProjFSManagedVersion) $(SolutionDir) && $(ProjectDir)\..\Scripts\CreateCliAssemblyVersion.bat $(ProjectName) $(ProjFSManagedVersion) $(SolutionDir) 98 | 99 | 100 | $(BuildOutputDir)\$(ProjectName);%(AdditionalIncludeDirectories) 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | Create 129 | Create 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/Resource.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ProjFS-Managed-API/3a59d9cb8534a45a1bb0bf3eb9ad1ffd7980dc03/ProjectedFSLib.Managed.API/Resource.h -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/UpdateFailureCause.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #pragma once 5 | 6 | namespace Microsoft { 7 | namespace Windows { 8 | namespace ProjFS { 9 | /// 10 | /// Defines values that describe why an attempt to update or delete a file in a virtualization 11 | /// root has failed. 12 | /// 13 | /// 14 | /// These values are used in the output parameter of 15 | /// ProjFS.VirtualizationInstance.UpdateFileIfNeeded and ProjFS.VirtualizationInstance.DeleteFile. 16 | /// These are set if the API returns HResult.VirtualizationInvalidOp because the file state 17 | /// does not allow the operation with the value(s) passed to the API. 18 | /// 19 | [System::FlagsAttribute] 20 | public enum class UpdateFailureCause : unsigned long 21 | { 22 | /// 23 | /// The update did not fail. 24 | /// 25 | NoFailure = PRJ_UPDATE_FAILURE_CAUSE_NONE, 26 | 27 | /// 28 | /// The item was a dirty placeholder (hydrated or not), and the provider did not specify 29 | /// UpdateType.AllowDirtyMetadata. 30 | /// 31 | DirtyMetadata = PRJ_UPDATE_FAILURE_CAUSE_DIRTY_METADATA, 32 | 33 | /// 34 | /// The item was a full file and the provider did not specify UpdateType.AllowDirtyData. 35 | /// 36 | DirtyData = PRJ_UPDATE_FAILURE_CAUSE_DIRTY_DATA, 37 | 38 | /// 39 | /// The item was a tombstone and the provider did not specify UpdateType.AllowTombstone. 40 | /// 41 | Tombstone = PRJ_UPDATE_FAILURE_CAUSE_TOMBSTONE, 42 | 43 | /// 44 | /// The item had the DOS read-only bit set and the provider did not specify UpdateType.AllowReadOnly. 45 | /// 46 | ReadOnly = PRJ_UPDATE_FAILURE_CAUSE_READ_ONLY 47 | }; 48 | }}} // namespace Microsoft.Windows.ProjFS -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/UpdateType.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #pragma once 5 | 6 | namespace Microsoft { 7 | namespace Windows { 8 | namespace ProjFS { 9 | /// Defines values describing when to allow a virtualized file to be deleted or updated. 10 | /// 11 | /// 12 | /// These values are used in the input parameter of 13 | /// ProjFS.VirtualizationInstance.UpdateFileIfNeeded and ProjFS.VirtualizationInstance.DeleteFile. 14 | /// The flags control whether ProjFS should allow the update given the state of the file or directory on disk. 15 | /// 16 | /// 17 | /// See the documentation for ProjFS.OnDiskFileState for a description of possible file 18 | /// and directory states in ProjFS. 19 | /// 20 | /// 21 | [System::FlagsAttribute] 22 | public enum class UpdateType : unsigned long 23 | { 24 | /// 25 | /// ProjFS will allow the update if the item is a placeholder or a dirty placeholder (whether hydrated or not). 26 | /// 27 | AllowDirtyMetadata = PRJ_UPDATE_ALLOW_DIRTY_METADATA, 28 | 29 | /// 30 | /// ProjFS will allow the update if the item is a placeholder or is a full file. 31 | /// 32 | AllowDirtyData = PRJ_UPDATE_ALLOW_DIRTY_DATA, 33 | 34 | /// 35 | /// ProjFS will allow the update if the item is a placeholder or is a tombstone. 36 | /// 37 | AllowTombstone = PRJ_UPDATE_ALLOW_TOMBSTONE, 38 | 39 | /// 40 | /// ProjFS will allow the update regardless of whether the DOS read-only bit is set on the item. 41 | /// 42 | AllowReadOnly = PRJ_UPDATE_ALLOW_READ_ONLY 43 | }; 44 | }}} // namespace Microsoft.Windows.ProjFS -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/Utils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #include "stdafx.h" 5 | #include "Utils.h" 6 | 7 | using namespace System; 8 | using namespace Microsoft::Windows::ProjFS; 9 | 10 | bool Utils::TryGetOnDiskFileState(String^ fullPath, [Out] OnDiskFileState% fileState) 11 | { 12 | pin_ptr path = PtrToStringChars(fullPath); 13 | PRJ_FILE_STATE localFileState; 14 | if (FAILED(::PrjGetOnDiskFileState(path, &localFileState))) 15 | { 16 | return false; 17 | } 18 | 19 | fileState = static_cast(localFileState); 20 | 21 | return true; 22 | } 23 | 24 | bool Utils::IsFileNameMatch(String^ fileNameToCheck, String^ pattern) 25 | { 26 | pin_ptr pFileNameToCheck = PtrToStringChars(fileNameToCheck); 27 | pin_ptr pPattern = PtrToStringChars(pattern); 28 | 29 | return ::PrjFileNameMatch(pFileNameToCheck, pPattern); 30 | } 31 | 32 | int Utils::FileNameCompare(String^ fileName1, String^ fileName2) 33 | { 34 | pin_ptr pFileName1 = PtrToStringChars(fileName1); 35 | pin_ptr pFileName2 = PtrToStringChars(fileName2); 36 | 37 | return ::PrjFileNameCompare(pFileName1, pFileName2); 38 | } 39 | 40 | bool Utils::DoesNameContainWildCards(String^ fileName) 41 | { 42 | pin_ptr pFileName = PtrToStringChars(fileName); 43 | 44 | return ::PrjDoesNameContainWildCards(pFileName); 45 | } 46 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/Utils.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #pragma once 5 | 6 | #include "OnDiskFileState.h" 7 | #include "HResult.h" 8 | 9 | using namespace System::Runtime::InteropServices; 10 | 11 | namespace Microsoft { 12 | namespace Windows { 13 | namespace ProjFS { 14 | /// 15 | /// Provides utility methods for ProjFS providers. 16 | /// 17 | public ref class Utils abstract sealed 18 | { 19 | public: 20 | /// 21 | /// Returns the on-disk state of the specified file or directory. 22 | /// 23 | /// 24 | /// 25 | /// This routine tells the caller what the ProjFS caching state is of the specified file or 26 | /// directory. For example, the caller can use this routine to determine whether the given item 27 | /// is a placeholder or full file. 28 | /// 29 | /// 30 | /// A running provider should be cautious if using this routine on files or directories within 31 | /// one of its virtualization instances, as it may cause callbacks to be invoked in the provider. 32 | /// Depending on the design of the provider this may lead to deadlocks. 33 | /// 34 | /// 35 | /// Full path of the file or the directory. 36 | /// On successful return contains a bitwise-OR of 37 | /// values describing the file state. 38 | /// 39 | /// false if does not exist. 40 | /// 41 | static bool TryGetOnDiskFileState( 42 | System::String^ fullPath, 43 | [Out] OnDiskFileState% fileState); 44 | 45 | /// 46 | /// Determines whether a file name string matches a pattern, potentially containing 47 | /// wildcard characters, according to the rules used by the file system. 48 | /// 49 | /// 50 | /// A provider should use this routine in its implementation of the GetDirectoryEnumerationCallback 51 | /// delegate to determine whether a name it its backing store matches the search expression 52 | /// from the filterFileName parameter of the GetDirectoryEnumerationCallback 53 | /// delegate. 54 | /// 55 | /// The file name to check against . 56 | /// The pattern for which to search. 57 | /// true if matches , 58 | /// false otherwise. 59 | static bool IsFileNameMatch( 60 | System::String^ fileNameToCheck, 61 | System::String^ pattern); 62 | 63 | /// 64 | /// Compares two file names and returns a value that indicates their relative collation order. 65 | /// 66 | /// 67 | /// The provider may use this routine to determine how to sort file names in the same order 68 | /// that the file system does. 69 | /// 70 | /// The first name to compare. 71 | /// The second name to compare. 72 | /// 73 | /// A negative number if is before in collation order. 74 | /// 0 if is equal to . 75 | /// A positive number if is after in collation order. 76 | /// 77 | static int FileNameCompare( 78 | System::String^ fileName1, 79 | System::String^ fileName2); 80 | 81 | /// Determines whether a string contains any wildcard characters. 82 | /// 83 | /// 84 | /// This routine checks for the wildcard characters recognized by the file system. These 85 | /// wildcards are sent by programs such as the cmd.exe command interpreter. 86 | /// 87 | /// 88 | /// 89 | /// 90 | /// Character 91 | /// Meaning 92 | /// 93 | /// 94 | /// * 95 | /// Matches 0 or more characters. 96 | /// 97 | /// 98 | /// ? 99 | /// Matches exactly one character. 100 | /// 101 | /// 102 | /// DOS_DOT (") 103 | /// Matches either a ".", or zero characters beyond the name string. 104 | /// 105 | /// 106 | /// DOS_STAR (<) 107 | /// Matches 0 or more characters until encountering and matching the final "." in the name. 108 | /// 109 | /// 110 | /// DOS_QM (>) 111 | /// Matches any single character, or upon encountering a period or end of name string, advances the expression 112 | /// to the end of the set of contiguous DOS_QMs. 113 | /// 114 | /// 115 | /// 116 | /// 117 | /// A string to check for wildcard characters. 118 | /// true if contains any wildcard characters, 119 | /// false otherwise. 120 | static bool DoesNameContainWildCards( 121 | System::String^ fileName); 122 | }; 123 | }}} // namespace Microsoft.Windows.ProjFS -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/VirtualizationInstance.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ProjFS-Managed-API/3a59d9cb8534a45a1bb0bf3eb9ad1ffd7980dc03/ProjectedFSLib.Managed.API/VirtualizationInstance.h -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/WriteBuffer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #include "stdafx.h" 5 | #include "WriteBuffer.h" 6 | 7 | using namespace System; 8 | using namespace System::IO; 9 | using namespace Microsoft::Windows::ProjFS; 10 | 11 | WriteBuffer::WriteBuffer( 12 | unsigned long bufferSize, 13 | unsigned long alignment) 14 | { 15 | this->buffer = (unsigned char*) _aligned_malloc(bufferSize, alignment); 16 | if (this->buffer == nullptr) 17 | { 18 | throw gcnew OutOfMemoryException("Unable to allocate WriteBuffer"); 19 | } 20 | 21 | this->namespaceCtx = nullptr; 22 | this->stream = gcnew UnmanagedMemoryStream(buffer, bufferSize, bufferSize, FileAccess::Write); 23 | } 24 | 25 | WriteBuffer::WriteBuffer( 26 | unsigned long bufferSize, 27 | PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceCtx, 28 | ApiHelper^ apiHelper) 29 | { 30 | this->buffer = (unsigned char*) apiHelper->_PrjAllocateAlignedBuffer(namespaceCtx, bufferSize); 31 | if (this->buffer == nullptr) 32 | { 33 | throw gcnew OutOfMemoryException("Unable to allocate WriteBuffer"); 34 | } 35 | 36 | this->namespaceCtx = namespaceCtx; 37 | this->apiHelper = apiHelper; 38 | this->stream = gcnew UnmanagedMemoryStream(buffer, bufferSize, bufferSize, FileAccess::Write); 39 | } 40 | 41 | WriteBuffer::~WriteBuffer() 42 | { 43 | delete this->stream; 44 | this->!WriteBuffer(); 45 | } 46 | 47 | WriteBuffer::!WriteBuffer() 48 | { 49 | if (this->namespaceCtx) 50 | { 51 | this->apiHelper->_PrjFreeAlignedBuffer(this->buffer); 52 | } 53 | else 54 | { 55 | _aligned_free(this->buffer); 56 | } 57 | } 58 | 59 | long long WriteBuffer::Length::get(void) 60 | { 61 | return this->stream->Length; 62 | } 63 | 64 | UnmanagedMemoryStream^ WriteBuffer::Stream::get(void) 65 | { 66 | return this->stream; 67 | } 68 | 69 | IntPtr WriteBuffer::Pointer::get(void) 70 | { 71 | return IntPtr(this->buffer); 72 | } 73 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/WriteBuffer.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | #include "IWriteBuffer.h" 5 | #include "ApiHelper.h" 6 | 7 | #pragma once 8 | 9 | namespace Microsoft { 10 | namespace Windows { 11 | namespace ProjFS { 12 | /// 13 | /// Helper class to ensure correct alignment when providing file contents for a placeholder. 14 | /// 15 | /// 16 | /// 17 | /// The provider does not instantiate this class directly. It uses the 18 | /// ProjFS.VirtualizationInstance.CreateWriteBuffer method to obtain a properly initialized 19 | /// instance of this class. 20 | /// 21 | /// 22 | /// The ProjFS.VirtualizationInstance.WriteFileData method requires a data buffer containing 23 | /// file data for a placeholder so that ProjFS can convert the placeholder to a hydrated placeholder 24 | /// (see ProjFS.OnDiskFileState for a discussion of file states). Internally ProjFS uses 25 | /// the user's FILE_OBJECT to write this data to the file. Because the user may have opened the 26 | /// file for unbuffered I/O, and unbuffered I/O imposes certain alignment requirements, this 27 | /// class is provided to abstract out those details. 28 | /// 29 | /// 30 | /// When the provider starts its virtualization instance, the VirtualizationInstance class 31 | /// queries the alignment requirements of the underlying physical storage device and uses this 32 | /// information to return a properly-initialized instance of this class from its CreateWriteBuffer 33 | /// method. 34 | /// 35 | /// 36 | public ref class WriteBuffer : IWriteBuffer 37 | { 38 | internal: 39 | WriteBuffer( 40 | unsigned long bufferSize, 41 | unsigned long alignment); 42 | 43 | WriteBuffer( 44 | unsigned long bufferSize, 45 | PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceCtx, 46 | ApiHelper^ apiHelper); 47 | 48 | public: 49 | ~WriteBuffer(); 50 | 51 | /// 52 | /// Gets the allocated length of the buffer. 53 | /// 54 | virtual property long long Length 55 | { 56 | long long get(void) sealed; 57 | }; 58 | 59 | /// 60 | /// Gets a representing the internal buffer. 61 | /// 62 | virtual property System::IO::UnmanagedMemoryStream^ Stream 63 | { 64 | System::IO::UnmanagedMemoryStream^ get(void) sealed; 65 | }; 66 | 67 | /// 68 | /// Gets a pointer to the internal buffer. 69 | /// 70 | virtual property System::IntPtr Pointer 71 | { 72 | System::IntPtr get(void) sealed; 73 | } 74 | 75 | protected: 76 | /// 77 | /// Frees the internal buffer. 78 | /// 79 | !WriteBuffer(); 80 | 81 | private: 82 | System::IO::UnmanagedMemoryStream^ stream; 83 | unsigned char* buffer; 84 | PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceCtx; 85 | ApiHelper^ apiHelper; 86 | }; 87 | }}} // namespace Microsoft.Windows.ProjFS -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/app.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ProjFS-Managed-API/3a59d9cb8534a45a1bb0bf3eb9ad1ffd7980dc03/ProjectedFSLib.Managed.API/app.ico -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/app.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ProjFS-Managed-API/3a59d9cb8534a45a1bb0bf3eb9ad1ffd7980dc03/ProjectedFSLib.Managed.API/app.rc -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/prjlib_deprecated.h: -------------------------------------------------------------------------------- 1 | /*++ 2 | 3 | Copyright (c) Microsoft Corporation. 4 | Licensed under the MIT license. 5 | 6 | 7 | Module Name: 8 | 9 | prjlib_deprecated.h 10 | 11 | Abstract: 12 | 13 | This module defines pre-release ProjFS constants, data structures, and functions that were deprecated 14 | in Windows 10 version 1809. We keep their definitions here to separate them from the final public 15 | API which is available in the Windows 10 SDK. To use the final public API #include ProjectedFSLib.h 16 | and link against ProjectedFSLib.lib. 17 | 18 | The structures and APIs declared in this file will be removed from a future version of Windows. 19 | 20 | --*/ 21 | 22 | #ifndef PRJLIB_DEPRECATED_H 23 | #define PRJLIB_DEPRECATED_H 24 | 25 | #if _MSC_VER > 1000 26 | #pragma once 27 | #endif 28 | 29 | #pragma warning(disable:4201) // nameless struct/union 30 | 31 | #include 32 | 33 | #define STDAPI_DEPRECATED(_MSG) EXTERN_C __declspec(deprecated(_MSG)) HRESULT STDAPICALLTYPE 34 | 35 | #pragma region Desktop Family 36 | #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) 37 | 38 | #if (_WIN32_WINNT >= _WIN32_WINNT_WIN10_RS4) 39 | 40 | #pragma region Common structures 41 | 42 | typedef PRJ_NOTIFICATION PRJ_NOTIFICATION_TYPE; 43 | 44 | typedef HANDLE PRJ_VIRTUALIZATIONINSTANCE_HANDLE; 45 | 46 | #pragma endregion 47 | 48 | #pragma region Virtualization instance APIs 49 | 50 | // 51 | // Forward definitions. 52 | // 53 | 54 | typedef struct _PRJ_COMMAND_CALLBACKS PRJ_COMMAND_CALLBACKS, *PPRJ_COMMAND_CALLBACKS; 55 | 56 | STDAPI_DEPRECATED("Use PrjStartVirtualizing instead of PrjStartVirtualizationInstance.") 57 | PrjStartVirtualizationInstance ( 58 | _In_ LPCWSTR VirtualizationRootPath, 59 | _In_ PPRJ_COMMAND_CALLBACKS Callbacks, 60 | _In_opt_ DWORD Flags, 61 | _In_opt_ DWORD GlobalNotificationMask, 62 | _In_opt_ DWORD PoolThreadCount, 63 | _In_opt_ DWORD ConcurrentThreadCount, 64 | _In_opt_ PVOID InstanceContext, 65 | _Outptr_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE* VirtualizationInstanceHandle 66 | ); 67 | 68 | typedef struct _VIRTUALIZATION_INST_EXTENDED_PARAMETERS 69 | { 70 | DWORD Size; 71 | DWORD Flags; 72 | DWORD PoolThreadCount; 73 | DWORD ConcurrentThreadCount; 74 | PRJ_NOTIFICATION_MAPPING* NotificationMappings; 75 | DWORD NumNotificationMappingsCount; 76 | } VIRTUALIZATION_INST_EXTENDED_PARAMETERS, *PVIRTUALIZATION_INST_EXTENDED_PARAMETERS; 77 | 78 | #define PRJ_FLAG_INSTANCE_NEGATIVE_PATH_CACHE 0x00000002 79 | 80 | STDAPI_DEPRECATED("Use PrjStartVirtualizing instead of PrjStartVirtualizationInstanceEx.") 81 | PrjStartVirtualizationInstanceEx ( 82 | _In_ LPCWSTR VirtualizationRootPath, 83 | _In_ PPRJ_COMMAND_CALLBACKS Callbacks, 84 | _In_opt_ PVOID InstanceContext, 85 | _In_opt_ PVIRTUALIZATION_INST_EXTENDED_PARAMETERS ExtendedParameters, 86 | _Outptr_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE* VirtualizationInstanceHandle 87 | ); 88 | 89 | STDAPI_DEPRECATED("Use PrjStopVirtualizing instead of PrjStopVirtualizationInstance.") 90 | PrjStopVirtualizationInstance ( 91 | _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle 92 | ); 93 | 94 | STDAPI_DEPRECATED("Use PrjGetVirtualizationInstanceInfo instead of PrjGetVirtualizationInstanceIdFromHandle.") 95 | PrjGetVirtualizationInstanceIdFromHandle ( 96 | _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle, 97 | _Out_ LPGUID VirtualizationInstanceID 98 | ); 99 | 100 | #pragma endregion 101 | 102 | #pragma region Placeholder and File APIs 103 | 104 | #define PRJ_FLAG_VIRTUALIZATION_ROOT 0x00000010 105 | 106 | STDAPI_DEPRECATED("Use PrjMarkDirectoryAsPlaceholder instead of PrjConvertDirectoryToPlaceholder.") 107 | PrjConvertDirectoryToPlaceholder ( 108 | _In_ LPCWSTR RootPathName, 109 | _In_ LPCWSTR TargetPathName, 110 | _In_opt_ PRJ_PLACEHOLDER_VERSION_INFO* VersionInfo, 111 | _In_opt_ DWORD Flags, 112 | _In_ LPCGUID VirtualizationInstanceID 113 | ); 114 | 115 | typedef struct _PRJ_EA_INFORMATION 116 | { 117 | DWORD EaBufferSize; 118 | DWORD OffsetToFirstEa; 119 | } PRJ_EA_INFORMATION, *PPRJ_EA_INFORMATION; 120 | 121 | typedef struct _PRJ_SECURITY_INFORMATION 122 | { 123 | DWORD SecurityBufferSize; 124 | DWORD OffsetToSecurityDescriptor; 125 | } PRJ_SECURITY_INFORMATION, *PPRJ_SECURITY_INFORMATION; 126 | 127 | typedef struct _PRJ_STREAMS_INFORMATION 128 | { 129 | DWORD StreamsInfoBufferSize; 130 | DWORD OffsetToFirstStreamInfo; 131 | } PRJ_STREAMS_INFORMATION, *PPRJ_STREAMS_INFORMATION; 132 | 133 | typedef struct _PRJ_PLACEHOLDER_INFORMATION 134 | { 135 | DWORD Size; 136 | PRJ_FILE_BASIC_INFO FileBasicInfo; 137 | PRJ_EA_INFORMATION EaInformation; 138 | PRJ_SECURITY_INFORMATION SecurityInformation; 139 | PRJ_STREAMS_INFORMATION StreamsInformation; 140 | PRJ_PLACEHOLDER_VERSION_INFO VersionInfo; 141 | UCHAR VariableData[ANYSIZE_ARRAY]; 142 | } PRJ_PLACEHOLDER_INFORMATION, *PPRJ_PLACEHOLDER_INFORMATION; 143 | 144 | STDAPI_DEPRECATED("Use PrjWritePlaceholderInfo instead of PrjWritePlaceholderInformation.") 145 | PrjWritePlaceholderInformation ( 146 | _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle, 147 | _In_ LPCWSTR DestinationFileName, 148 | _In_ PPRJ_PLACEHOLDER_INFORMATION PlaceholderInformation, 149 | _In_ DWORD Length 150 | ); 151 | 152 | STDAPI_DEPRECATED("Use PrjUpdateFileIfNeeded instead of PrjUpdatePlaceholderIfNeeded.") 153 | PrjUpdatePlaceholderIfNeeded ( 154 | _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle, 155 | _In_ LPCWSTR DestinationFileName, 156 | _In_ PPRJ_PLACEHOLDER_INFORMATION PlaceholderInformation, 157 | _In_ DWORD Length, 158 | _In_opt_ DWORD UpdateFlags, 159 | _Out_opt_ PDWORD FailureReason 160 | ); 161 | 162 | STDAPI_DEPRECATED("Use PrjWriteFileData instead of PrjWriteFile.") 163 | PrjWriteFile ( 164 | _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle, 165 | _In_ const GUID* StreamId, 166 | _In_reads_bytes_(Length) void* Buffer, 167 | _In_ UINT64 ByteOffset, 168 | _In_ UINT32 Length 169 | ); 170 | 171 | #pragma endregion 172 | 173 | #pragma region Callback support 174 | 175 | STDAPI_DEPRECATED("PrjCommandCallbacksInit is deprecated and will not exist in future versions of Windows.") 176 | PrjCommandCallbacksInit ( 177 | _In_ DWORD CallbacksSize, 178 | _Out_writes_bytes_(CallbacksSize) PPRJ_COMMAND_CALLBACKS Callbacks 179 | ); 180 | 181 | typedef 182 | HRESULT 183 | (CALLBACK PRJ_GET_PLACEHOLDER_INFORMATION_CB) ( 184 | _In_ PRJ_CALLBACK_DATA* CallbackData, 185 | _In_ DWORD DesiredAccess, 186 | _In_ DWORD ShareMode, 187 | _In_ DWORD CreateDisposition, 188 | _In_ DWORD CreateOptions, 189 | _In_ LPCWSTR DestinationFileName 190 | ); 191 | 192 | typedef 193 | HRESULT 194 | (CALLBACK PRJ_GET_FILE_STREAM_CB) ( 195 | _In_ PRJ_CALLBACK_DATA* CallbackData, 196 | _In_ LARGE_INTEGER ByteOffset, 197 | _In_ DWORD Length 198 | ); 199 | 200 | typedef union _PRJ_OPERATION_PARAMETERS { 201 | 202 | struct { 203 | DWORD DesiredAccess; 204 | DWORD ShareMode; 205 | DWORD CreateDisposition; 206 | DWORD CreateOptions; 207 | DWORD IoStatusInformation; 208 | DWORD NotificationMask; 209 | } PostCreate; 210 | 211 | struct { 212 | DWORD NotificationMask; 213 | } FileRenamed; 214 | 215 | struct { 216 | BOOLEAN IsFileModified; 217 | } FileDeletedOnHandleClose; 218 | 219 | } PRJ_OPERATION_PARAMETERS, *PPRJ_OPERATION_PARAMETERS; 220 | 221 | typedef 222 | HRESULT 223 | (CALLBACK PRJ_NOTIFY_OPERATION_CB) ( 224 | _In_ PRJ_CALLBACK_DATA* CallbackData, 225 | _In_ BOOLEAN IsDirectory, 226 | _In_ PRJ_NOTIFICATION_TYPE NotificationType, 227 | _In_opt_ LPCWSTR DestinationFileName, 228 | _Inout_ PPRJ_OPERATION_PARAMETERS OperationParameters 229 | ); 230 | 231 | typedef struct _PRJ_COMMAND_CALLBACKS 232 | { 233 | 234 | // 235 | // Size of this structure. Initialized by PrjCommandCallbacksInit(). 236 | // 237 | 238 | DWORD Size; 239 | 240 | // 241 | // The provider must implement the following callbacks. 242 | // 243 | 244 | PRJ_START_DIRECTORY_ENUMERATION_CB* PrjStartDirectoryEnumeration; 245 | 246 | PRJ_END_DIRECTORY_ENUMERATION_CB* PrjEndDirectoryEnumeration; 247 | 248 | PRJ_GET_DIRECTORY_ENUMERATION_CB* PrjGetDirectoryEnumeration; 249 | 250 | PRJ_GET_PLACEHOLDER_INFORMATION_CB* PrjGetPlaceholderInformation; 251 | 252 | PRJ_GET_FILE_STREAM_CB* PrjGetFileStream; 253 | 254 | // 255 | // Optional. If the provider does not implement this callback, ProjFS will invoke the directory 256 | // enumeration callbacks to determine the existence of a file path in the provider's store. 257 | // 258 | 259 | PRJ_QUERY_FILE_NAME_CB* PrjQueryFileName; 260 | 261 | // 262 | // Optional. If the provider does not implement this callback, it will not get any notifications 263 | // from ProjFS. 264 | // 265 | 266 | PRJ_NOTIFY_OPERATION_CB* PrjNotifyOperation; 267 | 268 | // 269 | // Optional. If the provider does not implement this callback, operations in ProjFS will not 270 | // be able to be canceled. 271 | // 272 | 273 | PRJ_CANCEL_COMMAND_CB* PrjCancelCommand; 274 | 275 | } PRJ_COMMAND_CALLBACKS, *PPRJ_COMMAND_CALLBACKS; 276 | 277 | #pragma endregion 278 | 279 | #endif // _WIN32_WINNT >= _WIN32_WINNT_WIN10_RS4 280 | #endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) 281 | #endif // PRJLIB_DEPRECATED_H 282 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/scripts/CreateCliAssemblyVersion.bat: -------------------------------------------------------------------------------- 1 | REM Usage: 2 | REM CreateCliAssemblyVersion ProjectName VersionString SolutionDir 3 | set ProjectName=%1 4 | set VersionString=%2 5 | set SolutionDir=%3 6 | 7 | mkdir %SolutionDir%\..\BuildOutput 8 | mkdir %SolutionDir%\..\BuildOutput\%ProjectName% 9 | echo #include "stdafx.h" > %SolutionDir%\..\BuildOutput\%ProjectName%\AssemblyVersion.h 10 | echo using namespace System::Reflection; >> %SolutionDir%\..\BuildOutput\%ProjectName%\AssemblyVersion.h 11 | echo [assembly:AssemblyVersion("%VersionString%")]; >> %SolutionDir%\..\BuildOutput\%ProjectName%\AssemblyVersion.h 12 | echo [assembly:AssemblyFileVersion("%VersionString%")]; >> %SolutionDir%\..\BuildOutput\%ProjectName%\AssemblyVersion.h 13 | echo [assembly:AssemblyKeyFileAttribute(LR"(%SolutionDir%ProjectedFSLib.Managed.API\signing\35MSSharedLib1024.snk)")]; >> %SolutionDir%\..\BuildOutput\%ProjectName%\AssemblyVersion.h 14 | echo [assembly:AssemblyDelaySignAttribute(true)]; >> %SolutionDir%\..\BuildOutput\%ProjectName%\AssemblyVersion.h -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/scripts/CreateVersionHeader.bat: -------------------------------------------------------------------------------- 1 | REM Usage: 2 | REM CreateVersionHeader.bat ProjectName VersionString SolutionDir 3 | set ProjectName=%1 4 | set VersionString=%2 5 | set SolutionDir=%3 6 | 7 | mkdir %SolutionDir%\..\BuildOutput 8 | mkdir %SolutionDir%\..\BuildOutput\%ProjectName% 9 | 10 | set comma_version_string=%VersionString% 11 | set comma_version_string=%comma_version_string:.=,% 12 | 13 | echo #define PRJLIB_FILE_VERSION %comma_version_string% > %SolutionDir%\..\BuildOutput\%ProjectName%\VersionHeader.h 14 | echo #define PRJLIB_FILE_VERSION_STRING "%VersionString%" >> %SolutionDir%\..\BuildOutput\%ProjectName%\VersionHeader.h 15 | echo #define PRJLIB_PRODUCT_VERSION %comma_version_string% >> %SolutionDir%\..\BuildOutput\%ProjectName%\VersionHeader.h 16 | echo #define PRJLIB_PRODUCT_VERSION_STRING "%VersionString%" >> %SolutionDir%\..\BuildOutput\%ProjectName%\VersionHeader.h -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/signing/35MSSharedLib1024.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ProjFS-Managed-API/3a59d9cb8534a45a1bb0bf3eb9ad1ffd7980dc03/ProjectedFSLib.Managed.API/signing/35MSSharedLib1024.snk -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/signing/CodeSignConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/signing/NuPkgSignConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/stdafx.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ProjFS-Managed-API/3a59d9cb8534a45a1bb0bf3eb9ad1ffd7980dc03/ProjectedFSLib.Managed.API/stdafx.cpp -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.API/stdafx.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/ProjFS-Managed-API/3a59d9cb8534a45a1bb0bf3eb9ad1ffd7980dc03/ProjectedFSLib.Managed.API/stdafx.h -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.Test/Helpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | using NUnit.Framework; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Diagnostics; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Threading; 11 | 12 | namespace ProjectedFSLib.Managed.Test 13 | { 14 | class Helpers 15 | { 16 | private Process m_providerProcess; 17 | public Process ProviderProcess { get => m_providerProcess; set => m_providerProcess = value; } 18 | 19 | private int m_waitTimeoutInMs; 20 | public int WaitTimeoutInMs { get => m_waitTimeoutInMs; set => m_waitTimeoutInMs = value; } 21 | 22 | internal enum NotifyWaitHandleNames 23 | { 24 | FileOpened, 25 | NewFileCreated, 26 | FileOverwritten, 27 | PreDelete, 28 | PreRename, 29 | PreCreateHardlink, 30 | FileRenamed, 31 | HardlinkCreated, 32 | FileHandleClosedNoModification, 33 | FileHandleClosedFileModifiedOrDeleted, 34 | FilePreConvertToFull, 35 | } 36 | 37 | private List notificationEvents; 38 | public List NotificationEvents { get => notificationEvents; } 39 | 40 | public Helpers( 41 | int waitTimeoutInMs 42 | ) 43 | { 44 | m_waitTimeoutInMs = waitTimeoutInMs; 45 | 46 | // Create the events that the notifications tests use. 47 | notificationEvents = new List(); 48 | foreach (string eventName in Enum.GetNames(typeof(NotifyWaitHandleNames))) 49 | { 50 | notificationEvents.Add(new EventWaitHandle(false, EventResetMode.AutoReset, eventName)); 51 | } 52 | } 53 | 54 | public void StartTestProvider() 55 | { 56 | StartTestProvider(out string sourceRoot, out string virtRoot); 57 | } 58 | 59 | public void StartTestProvider(out string sourceRoot, out string virtRoot) 60 | { 61 | GetRootNamesForTest(out sourceRoot, out virtRoot); 62 | 63 | // Get the provider name from the command line. 64 | var providerExe = TestContext.Parameters.Get("ProviderExe"); 65 | 66 | // Create an event for the provider to signal once it is up and running. 67 | EventWaitHandle waitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, "ProviderTestProceed"); 68 | 69 | // Set up the provider process and start it. 70 | ProviderProcess = new Process(); 71 | ProviderProcess.StartInfo.FileName = providerExe; 72 | 73 | string sourceArg = " --sourceroot " + sourceRoot; 74 | string virtRootArg = " --virtroot " + virtRoot; 75 | 76 | // Add all the arguments, as well as the "test mode" argument. 77 | ProviderProcess.StartInfo.Arguments = sourceArg + virtRootArg + " -t"; 78 | ProviderProcess.StartInfo.UseShellExecute = true; 79 | 80 | ProviderProcess.Start(); 81 | 82 | // Wait for the provider to signal the event. 83 | if (!waitHandle.WaitOne(WaitTimeoutInMs)) 84 | { 85 | throw new Exception("SimpleProviderManaged did not signal the ProviderTestProceed event in a timely manner."); 86 | } 87 | } 88 | 89 | public void StopTestProvider() 90 | { 91 | ProviderProcess.CloseMainWindow(); 92 | } 93 | 94 | // Makes name strings for the source and virtualization roots for a test, using the NUnit 95 | // WorkDirectory as the base. 96 | // 97 | // Example: Given a test method called "TestStuff", if the test's WorkDirectory is C:\MyTestDir, 98 | // the output is: 99 | // sourceName: C:\MyTestDir\TestStuff_source 100 | // virtRootName: C:\MyTestDir\TestStuff_virtRoot 101 | // 102 | // This method expects to be invoked while a test is running, either from the test case 103 | // itself or in a setup/teardown fixture for a test case. 104 | public void GetRootNamesForTest(out string sourceName, out string virtRootName) 105 | { 106 | string baseName = Path.Combine( 107 | TestContext.CurrentContext.WorkDirectory, 108 | TestContext.CurrentContext.Test.MethodName); 109 | 110 | sourceName = baseName + "_source"; 111 | virtRootName = baseName + "_virtRoot"; 112 | } 113 | 114 | public void CreateRootsForTest(out string sourceName, out string virtRootName) 115 | { 116 | GetRootNamesForTest(out sourceName, out virtRootName); 117 | 118 | DirectoryInfo sourceInfo = new DirectoryInfo(sourceName); 119 | if (!sourceInfo.Exists) 120 | { 121 | sourceInfo.Create(); 122 | } 123 | 124 | // The provider create the virtualization root. 125 | } 126 | 127 | // Creates a file in the source so that it is projected into the virtualization root. 128 | // Returns the full path to the virtual file. 129 | public string CreateVirtualFile(string fileName, string fileContent) 130 | { 131 | GetRootNamesForTest(out string sourceRoot, out string virtRoot); 132 | 133 | string sourceFileName = Path.Combine(sourceRoot, fileName); 134 | FileInfo sourceFile = new FileInfo(sourceFileName); 135 | 136 | if (!sourceFile.Exists) 137 | { 138 | DirectoryInfo ancestorPath = new DirectoryInfo(Path.GetDirectoryName(sourceFile.FullName)); 139 | if (!ancestorPath.Exists) 140 | { 141 | ancestorPath.Create(); 142 | } 143 | 144 | using (StreamWriter sw = sourceFile.CreateText()) 145 | { 146 | sw.Write(fileContent); 147 | } 148 | } 149 | 150 | // Tell our caller what the path to the virtual file is. 151 | return Path.Combine(virtRoot, fileName); 152 | } 153 | 154 | // Creates a file in the source so that it is projected into the virtualization root. 155 | // Returns the full path to the virtual file. 156 | public string CreateVirtualFile(string fileName) 157 | { 158 | return CreateVirtualFile(fileName, "Virtual"); 159 | } 160 | 161 | // Creates a symlink in the source to another file in the source so that it is projected into the virtualization root. 162 | // Returns the full path to the virtual symlink. 163 | public string CreateVirtualSymlink(string fileName, string targetName, bool useRootedPaths = false) 164 | { 165 | GetRootNamesForTest(out string sourceRoot, out string virtRoot); 166 | string sourceSymlinkName = Path.Combine(sourceRoot, fileName); 167 | string sourceTargetName = useRootedPaths ? Path.Combine(sourceRoot, targetName) : targetName; 168 | 169 | if (!File.Exists(sourceSymlinkName)) 170 | { 171 | CreateSymlinkAndAncestor(sourceSymlinkName, sourceTargetName, true); 172 | } 173 | 174 | return Path.Combine(virtRoot, fileName); 175 | } 176 | 177 | // Creates a symlink in the source to another directory in the source so that it is projected into the virtualization root. 178 | // Returns the full path to the virtual symlink. 179 | public string CreateVirtualSymlinkDirectory(string symlinkDirectoryName, string targetName, bool useRootedPaths = false) 180 | { 181 | GetRootNamesForTest(out string sourceRoot, out string virtRoot); 182 | string sourceSymlinkName = Path.Combine(sourceRoot, symlinkDirectoryName); 183 | string sourceTargetName = useRootedPaths ? Path.Combine(sourceRoot, targetName) : targetName; 184 | 185 | if (!Directory.Exists(sourceSymlinkName)) 186 | { 187 | CreateSymlinkAndAncestor(sourceSymlinkName, sourceTargetName, false); 188 | } 189 | 190 | return Path.Combine(virtRoot, symlinkDirectoryName); 191 | } 192 | 193 | // Create a file in the virtualization root (i.e. a non-projected or "full" file). 194 | // Returns the full path to the full file. 195 | public string CreateFullFile(string fileName, string fileContent) 196 | { 197 | GetRootNamesForTest(out string sourceRoot, out string virtRoot); 198 | 199 | string fullFileName = Path.Combine(virtRoot, fileName); 200 | FileInfo fullFile = new FileInfo(fullFileName); 201 | 202 | if (!fullFile.Exists) 203 | { 204 | DirectoryInfo ancestorPath = new DirectoryInfo(Path.GetDirectoryName(fullFile.FullName)); 205 | if (!ancestorPath.Exists) 206 | { 207 | ancestorPath.Create(); 208 | } 209 | 210 | using (StreamWriter sw = fullFile.CreateText()) 211 | { 212 | sw.Write(fileContent); 213 | } 214 | } 215 | 216 | return fullFileName; 217 | } 218 | 219 | // Create a file in the virtualization root (i.e. a non-projected or "full" file). 220 | // Returns the full path to the full file. 221 | public string CreateFullFile(string fileName) 222 | { 223 | return CreateFullFile(fileName, "Full"); 224 | } 225 | 226 | public string ReadFileInVirtRoot(string fileName) 227 | { 228 | GetRootNamesForTest(out string sourceRoot, out string virtRoot); 229 | 230 | string destFileName = Path.Combine(virtRoot, fileName); 231 | FileInfo destFile = new FileInfo(destFileName); 232 | string fileContent; 233 | using (StreamReader sr = destFile.OpenText()) 234 | { 235 | fileContent = sr.ReadToEnd(); 236 | } 237 | 238 | return fileContent; 239 | } 240 | 241 | public string ReadReparsePointTargetInVirtualRoot(string symlinkFileName) 242 | { 243 | GetRootNamesForTest(out string sourceRoot, out string virtRoot); 244 | string fullSymlinkName = Path.Combine(virtRoot, symlinkFileName); 245 | 246 | if (!SimpleProviderManaged.FileSystemApi.TryGetReparsePointTarget(fullSymlinkName, out string reparsePointTarget)) 247 | { 248 | throw new Exception($"Failed to get a reparse point of {fullSymlinkName}."); 249 | } 250 | 251 | return reparsePointTarget; 252 | } 253 | 254 | public FileStream OpenFileInVirtRoot(string fileName, FileMode mode) 255 | { 256 | GetRootNamesForTest(out string sourceRoot, out string virtRoot); 257 | 258 | string destFileName = Path.Combine(virtRoot, fileName); 259 | FileStream stream = File.Open(destFileName, mode); 260 | 261 | return stream; 262 | } 263 | 264 | private Random random = new Random(); 265 | public string RandomString(int length) 266 | { 267 | const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 268 | return new string(Enumerable.Repeat(chars, length) 269 | .Select(s => s[random.Next(s.Length)]).ToArray()); 270 | } 271 | 272 | private void CreateSymlinkAndAncestor(string sourceSymlinkName, string sourceTargetName, bool isFile) 273 | { 274 | DirectoryInfo ancestorPath = new DirectoryInfo(Path.GetDirectoryName(sourceSymlinkName)); 275 | if (!ancestorPath.Exists) 276 | { 277 | ancestorPath.Create(); 278 | } 279 | 280 | if (!SimpleProviderManaged.FileSystemApi.TryCreateSymbolicLink(sourceSymlinkName, sourceTargetName, isFile)) 281 | { 282 | throw new Exception($"Failed to create directory symlink {sourceSymlinkName}."); 283 | } 284 | } 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.Test/NUnitRunner.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | using NUnitLite; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Reflection; 9 | 10 | namespace ProjectedFSLib.Managed.Test 11 | { 12 | class NUnitRunner 13 | { 14 | private readonly List args; 15 | 16 | public NUnitRunner(string[] args) 17 | { 18 | this.args = new List(args); 19 | } 20 | 21 | public string GetCustomArgWithParam(string arg) 22 | { 23 | string match = this.args.Where(a => a.StartsWith(arg + "=")).SingleOrDefault(); 24 | if (match == null) 25 | { 26 | return null; 27 | } 28 | 29 | this.args.Remove(match); 30 | return match.Substring(arg.Length + 1); 31 | } 32 | 33 | public bool HasCustomArg(string arg) 34 | { 35 | // We also remove it as we're checking, because nunit wouldn't understand what it means 36 | return this.args.Remove(arg); 37 | } 38 | 39 | public int RunTests(ICollection includeCategories, ICollection excludeCategories) 40 | { 41 | string filters = GetFiltersArgument(includeCategories, excludeCategories); 42 | if (filters.Length > 0) 43 | { 44 | this.args.Add("--where"); 45 | this.args.Add(filters); 46 | } 47 | 48 | DateTime now = DateTime.Now; 49 | int result = new AutoRun(Assembly.GetEntryAssembly()).Execute(this.args.ToArray()); 50 | 51 | Console.WriteLine("Completed test pass in {0}", DateTime.Now - now); 52 | Console.WriteLine(); 53 | 54 | return result; 55 | } 56 | 57 | private static string GetFiltersArgument(ICollection includeCategories, ICollection excludeCategories) 58 | { 59 | string filters = string.Empty; 60 | if (includeCategories != null && includeCategories.Any()) 61 | { 62 | filters = "(" + string.Join("||", includeCategories.Select(x => $"cat=={x}")) + ")"; 63 | } 64 | 65 | if (excludeCategories != null && excludeCategories.Any()) 66 | { 67 | filters += (filters.Length > 0 ? "&&" : string.Empty) + string.Join("&&", excludeCategories.Select(x => $"cat!={x}")); 68 | } 69 | 70 | return filters; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.Test/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Diagnostics; 8 | 9 | namespace ProjectedFSLib.Managed.Test 10 | { 11 | class Program 12 | { 13 | public static void Main(string[] args) 14 | { 15 | NUnitRunner runner = new NUnitRunner(args); 16 | 17 | List excludeCategories = new List(); 18 | 19 | if (!SimpleProviderManaged.EnvironmentHelper.IsFullSymlinkSupportAvailable()) 20 | { 21 | excludeCategories.Add(BasicTests.SymlinkTestCategory); 22 | } 23 | 24 | Environment.ExitCode = runner.RunTests(includeCategories: null, excludeCategories: excludeCategories); 25 | 26 | if (Debugger.IsAttached) 27 | { 28 | Console.WriteLine("Tests completed. Press Enter to exit."); 29 | Console.ReadLine(); 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.Test/ProjectedFSLib.Managed.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | net48;netcoreapp3.1 6 | false 7 | false 8 | x64 9 | Exe 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.Test/README.md: -------------------------------------------------------------------------------- 1 | # ProjectedFSLib.Managed.Test.exe 2 | 3 | ## Command line parameters 4 | 5 | `ProjectedFSLib.Managed.Test.exe [NUnit parameters] --params ProviderExe=` 6 | 7 | Where: 8 | * `[NUnit parameters]` are the [normal NUnit parameters](https://github.com/nunit/docs/wiki/Console-Command-Line) 9 | * `--params ProviderExe` specifies the path to the `SimpleProviderManaged.exe` sample/test provider from the 10 | [simpleProviderManaged](https://github.com/Microsoft/ProjFS-Managed-API/tree/main/simpleProviderManaged) project. 11 | 12 | ## Notes 13 | Each test case creates a source directory and a virtualization root and then uses the SimpleProviderManaged 14 | provider to project the contents of the source directory into the virtualization root. 15 | 16 | By default the test creates the source and virtualization roots in the test's working directory. If you are using 17 | [VFSForGit](https://github.com/Microsoft/VFSForGit) to project GitHub enlistments to your local machine and you try 18 | to run the test under the Visual Studio debugger, then it is possible that the working directory will end up under 19 | your enlistment's virtualization root. Since one ProjFS virtualization root is not allowed to exist as a descendant 20 | of another one, this will cause the tests to fail. You can use NUnit's `--work` parameter to the test executable 21 | to change where it creates the roots. 22 | 23 | For example, to run the tests under your $TEMP directory: 24 | `ProjectedFSLib.Managed.Test.exe --work=$TEMP --params ProviderExe=` 25 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.cpp.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | $(BuildOutputDir)\$(MSBuildProjectName)\bin\$(Platform)\$(Configuration)\ 8 | $(BuildOutputDir)\$(MSBuildProjectName)\intermediate\$(Platform)\$(Configuration)\ 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.cs.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | $(BuildOutputDir)\$(MSBuildProjectName)\bin\$(Platform)\$(Configuration)\ 8 | $(BuildOutputDir)\$(MSBuildProjectName)\obj\$(Platform)\$(Configuration)\ 9 | $(BuildOutputDir)\$(MSBuildProjectName)\intermediate\$(Platform)\$(Configuration)\ 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 0.2.173.2 8 | 9 | 10 | 11 | Debug 12 | x64 13 | $(SolutionDir)..\BuildOutput 14 | $(SolutionDir)..\packages 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /ProjectedFSLib.Managed.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29512.175 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProjectedFSLib.Managed", "ProjectedFSLib.Managed.API\NetFramework\ProjectedFSLib.Managed.vcxproj", "{4E5F40B3-B56F-4B62-92CB-68E7E0E36AFA}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProjectedFSLib.Managed.NetCore", "ProjectedFSLib.Managed.API\NetCore\ProjectedFSLib.Managed.NetCore.vcxproj", "{D29E5723-25E6-41C7-AEB9-099CDE30538A}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProjectedFSLib.Managed.Test", "ProjectedFSLib.Managed.Test\ProjectedFSLib.Managed.Test.csproj", "{B4018C7F-BF11-47BC-8770-72B05C9437EE}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleProviderManaged", "simpleProviderManaged\SimpleProviderManaged.csproj", "{5697F978-E1ED-4C2E-8218-4110F5EA559D}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{028D4D62-E4B9-44F0-A086-921292B7E89B}" 15 | ProjectSection(SolutionItems) = preProject 16 | README.md = README.md 17 | EndProjectSection 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{80F77524-CD14-4BD0-A197-8E7D5DD7B6E0}" 20 | ProjectSection(SolutionItems) = preProject 21 | scripts\BuildProjFS-Managed.bat = scripts\BuildProjFS-Managed.bat 22 | scripts\InitializeEnvironment.bat = scripts\InitializeEnvironment.bat 23 | scripts\NukeBuildOutputs.bat = scripts\NukeBuildOutputs.bat 24 | scripts\RunTests.bat = scripts\RunTests.bat 25 | EndProjectSection 26 | EndProject 27 | Global 28 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 29 | Debug|x64 = Debug|x64 30 | Release|x64 = Release|x64 31 | EndGlobalSection 32 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 33 | {4E5F40B3-B56F-4B62-92CB-68E7E0E36AFA}.Debug|x64.ActiveCfg = Debug|x64 34 | {4E5F40B3-B56F-4B62-92CB-68E7E0E36AFA}.Debug|x64.Build.0 = Debug|x64 35 | {4E5F40B3-B56F-4B62-92CB-68E7E0E36AFA}.Release|x64.ActiveCfg = Release|x64 36 | {4E5F40B3-B56F-4B62-92CB-68E7E0E36AFA}.Release|x64.Build.0 = Release|x64 37 | {D29E5723-25E6-41C7-AEB9-099CDE30538A}.Debug|x64.ActiveCfg = Debug|x64 38 | {D29E5723-25E6-41C7-AEB9-099CDE30538A}.Debug|x64.Build.0 = Debug|x64 39 | {D29E5723-25E6-41C7-AEB9-099CDE30538A}.Release|x64.ActiveCfg = Release|x64 40 | {D29E5723-25E6-41C7-AEB9-099CDE30538A}.Release|x64.Build.0 = Release|x64 41 | {B4018C7F-BF11-47BC-8770-72B05C9437EE}.Debug|x64.ActiveCfg = Debug|Any CPU 42 | {B4018C7F-BF11-47BC-8770-72B05C9437EE}.Debug|x64.Build.0 = Debug|Any CPU 43 | {B4018C7F-BF11-47BC-8770-72B05C9437EE}.Release|x64.ActiveCfg = Release|Any CPU 44 | {B4018C7F-BF11-47BC-8770-72B05C9437EE}.Release|x64.Build.0 = Release|Any CPU 45 | {5697F978-E1ED-4C2E-8218-4110F5EA559D}.Debug|x64.ActiveCfg = Debug|Any CPU 46 | {5697F978-E1ED-4C2E-8218-4110F5EA559D}.Debug|x64.Build.0 = Debug|Any CPU 47 | {5697F978-E1ED-4C2E-8218-4110F5EA559D}.Release|x64.ActiveCfg = Release|Any CPU 48 | {5697F978-E1ED-4C2E-8218-4110F5EA559D}.Release|x64.Build.0 = Release|Any CPU 49 | EndGlobalSection 50 | GlobalSection(SolutionProperties) = preSolution 51 | HideSolutionNode = FALSE 52 | EndGlobalSection 53 | GlobalSection(ExtensibilityGlobals) = postSolution 54 | SolutionGuid = {0F3321A2-4572-44F0-B7F3-BD0E79433A1E} 55 | EndGlobalSection 56 | EndGlobal 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ProjFS Managed API 2 | 3 | |Branch|Functional Tests| 4 | |:--:|:--:| 5 | |**main**|[![Build status](https://dev.azure.com/projfs/ci/_apis/build/status/PR%20-%20Build%20and%20Functional%20Test%20-%202022?branchName=main)](https://dev.azure.com/projfs/ci/_build/latest?definitionId=7)| 6 | 7 | 8 | ## About ProjFS 9 | 10 | ProjFS is short for Windows Projected File System. ProjFS allows a user-mode application called a 11 | "provider" to project hierarchical data into the file system, making it appear as files and directories 12 | in the file system. For example, a simple provider could project the Windows registry into the file 13 | system, making registry keys and values appear as files and directories, respectively. An example of 14 | a more complex provider is [VFS for Git](https://github.com/Microsoft/VFSForGit), used to virtualize 15 | very large git repos. 16 | 17 | Conceptual documentation for ProjFS along with documentation of its Win32 API is at 18 | [docs.microsoft.com](https://docs.microsoft.com/en-us/windows/desktop/projfs/projected-file-system). 19 | 20 | ## Enabling ProjFS 21 | 22 | ProjFS enablement is **required** for this library to work correctly. ProjFS ships as an [optional component](https://docs.microsoft.com/en-us/windows/desktop/projfs/enabling-windows-projected-file-system) starting in Windows 10 version 1809. 23 | 24 | ## About the ProjFS Managed API 25 | 26 | The Windows SDK contains a native C API for ProjFS. The ProjFS Managed API provides a wrapper around 27 | the native API so that developers can write ProjFS providers using managed code. 28 | 29 | Note that to use this library on a computer that does not have Visual Studio installed, you must install the [Visual C++ redistributable](https://visualstudio.microsoft.com/downloads/#microsoft-visual-c-redistributable-for-visual-studio-2019). 30 | 31 | ## Solution Layout 32 | 33 | ### ProjectedFSLib.Managed project 34 | 35 | This project contains the code that builds the API wrapper, ProjectedFSLib.Managed.dll. It is in the 36 | ProjectedFSLib.Managed.API directory. 37 | 38 | ### SimpleProviderManaged project 39 | 40 | This project builds a simple ProjFS provider, SimpleProviderManaged.exe, that uses the managed API. 41 | It projects the contents of one directory (the "source") into another one (the "virtualization root"). 42 | 43 | ### ProjectedFSLib.Managed.Test project 44 | 45 | This project builds an NUnit test, ProjectedFSLib.Managed.Test.exe, that uses the SimpleProviderManaged 46 | provider to exercise the API wrapper. The managed API is a fairly thin wrapper around the native API, 47 | and the native API has its own much more comprehensive tests that are routinely executed at Microsoft 48 | in the normal course of OS development. So this managed test is just a basic functional test to get 49 | coverage of the managed wrapper API surface. 50 | 51 | ## Building the ProjFS Managed API 52 | 53 | * Install [Visual Studio 2022 Community Edition](https://www.visualstudio.com/downloads/). 54 | * Include the following workloads: 55 | * **.NET desktop development** 56 | * **Desktop development with C++** 57 | * Ensure the following individual components are installed: 58 | * **C++/CLI support** 59 | * **Windows 10 SDK (10.0.19041.0)** 60 | * Create a folder to clone into, e.g. `C:\Repos\ProjFS-Managed` 61 | * Clone this repo into a subfolder named `src`, e.g. `C:\Repos\ProjFS-Managed\src` 62 | * Run `src\scripts\BuildProjFS-Managed.bat` 63 | * You can also build in Visual Studio by opening `src\ProjectedFSLib.Managed.sln` and building. 64 | 65 | The build outputs will be placed under a `BuildOutput` subfolder, e.g. `C:\Repos\ProjFS-Managed\BuildOutput`. 66 | 67 | **Note:** The Windows Projected File System optional component must be enabled in Windows before 68 | you can run SimpleProviderManaged.exe or a provider of your own devising. Refer to 69 | [this page](https://docs.microsoft.com/en-us/windows/desktop/projfs/enabling-windows-projected-file-system) 70 | for instructions. 71 | 72 | ### Dealing with BadImageFormatExceptions 73 | The simplest cause for BadImageFormatExceptions is that you still need to [enable ProjFS](#enabling-projfs). 74 | 75 | For .Net Core specific consumers, this can also occur when the .NET Core loader attempts to find Ijwhost.dll from the .NET Core runtime. To force this to be deployed with your application under MSBuild, add the following property to each csproj file that is importing the Microsoft.Windows.ProjFS package: 76 | 77 | 78 | True 79 | 80 | 81 | 82 | ## Contributing 83 | 84 | For details on how to contribute to this project, see the CONTRIBUTING.md file in this repository. 85 | 86 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 87 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 88 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 89 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "msbuild-sdks": { 3 | "Microsoft.Build.NoTargets": "1.0.85" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /scripts/BuildProjFS-Managed.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL ENABLEDELAYEDEXPANSION 3 | 4 | CALL %~dp0\InitializeEnvironment.bat || EXIT /b 10 5 | 6 | IF "%1"=="" (SET "Configuration=Debug") ELSE (SET "Configuration=%1") 7 | IF "%2"=="" (SET "ProjFSManagedVersion=0.2.173.2") ELSE (SET "ProjFSManagedVersion=%2") 8 | 9 | SET SolutionConfiguration=%Configuration% 10 | 11 | :: Make the build version available in the DevOps environment. 12 | @echo ##vso[task.setvariable variable=PROJFS_MANAGED_VERSION]%ProjFSManagedVersion% 13 | 14 | SET nuget="%PROJFS_TOOLSDIR%\nuget.exe" 15 | IF NOT EXIST %nuget% ( 16 | mkdir %nuget%\.. 17 | powershell -ExecutionPolicy Bypass -Command "Invoke-WebRequest 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe' -OutFile %nuget%" 18 | ) 19 | 20 | :: Use vswhere to find the latest VS installation (including prerelease installations) with the msbuild component. 21 | :: See https://github.com/Microsoft/vswhere/wiki/Find-MSBuild 22 | SET vswherever=2.8.4 23 | %nuget% install vswhere -Version %vswherever% -OutputDirectory %PROJFS_PACKAGESDIR% || exit /b 1 24 | SET vswhere=%PROJFS_PACKAGESDIR%\vswhere.%vswherever%\tools\vswhere.exe 25 | set WINSDK_BUILD=19041 26 | echo Checking for VS installation: 27 | echo %vswhere% -all -prerelease -latest -version "[16.4,18.0)" -products * -requires Microsoft.Component.MSBuild Microsoft.VisualStudio.Workload.ManagedDesktop Microsoft.VisualStudio.Workload.NativeDesktop Microsoft.VisualStudio.Component.Windows10SDK.%WINSDK_BUILD% Microsoft.VisualStudio.Component.VC.CLI.Support -property installationPath 28 | for /f "usebackq tokens=*" %%i in (`%vswhere% -all -prerelease -latest -version "[16.4,18.0)" -products * -requires Microsoft.Component.MSBuild Microsoft.VisualStudio.Workload.ManagedDesktop Microsoft.VisualStudio.Workload.NativeDesktop Microsoft.VisualStudio.Component.Windows10SDK.%WINSDK_BUILD% Microsoft.VisualStudio.Component.VC.CLI.Support -property installationPath`) do ( 29 | set VsDir=%%i 30 | ) 31 | 32 | IF NOT DEFINED VsDir ( 33 | echo All installed Visual Studio instances: 34 | %vswhere% -all -prerelease -products * -format json 35 | echo ERROR: Could not locate a Visual Studio installation with required components. 36 | echo Refer to Readme.md for a list of the required Visual Studio components. 37 | exit /b 10 38 | ) 39 | 40 | echo Setting up the VS Developer Command Prompt environment variables from %VsDir% 41 | call "%VsDir%\VC\Auxiliary\Build\vcvarsall.bat" x86_amd64 42 | @rem pushd "%VsDir%" 43 | @rem call "%VsDir%\Common7\Tools\VsDevCmd.bat" 44 | @rem popd 45 | 46 | :: Restore all dependencies and run the build. 47 | pushd "%PROJFS_SRCDIR%" 48 | msbuild /t:Restore ProjectedFSLib.Managed.sln 49 | msbuild ProjectedFSLib.Managed.sln /p:ProjFSManagedVersion=%ProjFSManagedVersion% /p:Configuration=%SolutionConfiguration% /p:Platform=x64 || exit /b 1 50 | popd 51 | 52 | ENDLOCAL 53 | -------------------------------------------------------------------------------- /scripts/InitializeEnvironment.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | :: Set environment variables for interesting paths that scripts might need access to. 4 | PUSHD %~dp0 5 | SET PROJFS_SCRIPTSDIR=%CD% 6 | POPD 7 | 8 | CALL :RESOLVEPATH "%PROJFS_SCRIPTSDIR%\.." 9 | SET PROJFS_SRCDIR=%_PARSED_PATH_% 10 | 11 | CALL :RESOLVEPATH "%PROJFS_SRCDIR%\.." 12 | SET PROJFS_ENLISTMENTDIR=%_PARSED_PATH_% 13 | 14 | SET PROJFS_OUTPUTDIR=%PROJFS_ENLISTMENTDIR%\BuildOutput 15 | SET PROJFS_PACKAGESDIR=%PROJFS_ENLISTMENTDIR%\packages 16 | SET PROJFS_PUBLISHDIR=%PROJFS_ENLISTMENTDIR%\Publish 17 | SET PROJFS_TOOLSDIR=%PROJFS_ENLISTMENTDIR%\.tools 18 | 19 | :: Make the path variables available in the DevOps environment. 20 | @echo ##vso[task.setvariable variable=PROJFS_SRCDIR]%PROJFS_SRCDIR% 21 | @echo ##vso[task.setvariable variable=PROJFS_OUTPUTDIR]%PROJFS_OUTPUTDIR% 22 | @echo ##vso[task.setvariable variable=PROJFS_PACKAGESDIR]%PROJFS_PACKAGESDIR% 23 | @echo ##vso[task.setvariable variable=PROJFS_PUBLISHDIR]%PROJFS_PUBLISHDIR% 24 | @echo ##vso[task.setvariable variable=PROJFS_TOOLSDIR]%PROJFS_TOOLSDIR% 25 | 26 | :: Clean up 27 | SET _PARSED_PATH_= 28 | 29 | GOTO :EOF 30 | 31 | :RESOLVEPATH 32 | SET "_PARSED_PATH_=%~f1" 33 | GOTO :EOF 34 | -------------------------------------------------------------------------------- /scripts/NukeBuildOutputs.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | CALL %~dp0\InitializeEnvironment.bat || EXIT /b 10 3 | 4 | IF EXIST %PROJFS_OUTPUTDIR% ( 5 | ECHO deleting build outputs 6 | rmdir /s /q %PROJFS_OUTPUTDIR% 7 | ) ELSE ( 8 | ECHO no build outputs found 9 | ) 10 | 11 | IF EXIST %PROJFS_PUBLISHDIR% ( 12 | ECHO deleting published output 13 | rmdir /s /q %PROJFS_PUBLISHDIR% 14 | ) ELSE ( 15 | ECHO no published output found 16 | ) 17 | 18 | IF EXIST %PROJFS_PACKAGESDIR% ( 19 | ECHO deleting packages 20 | rmdir /s /q %PROJFS_PACKAGESDIR% 21 | ) ELSE ( 22 | ECHO no packages found 23 | ) 24 | -------------------------------------------------------------------------------- /scripts/RunTests.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | CALL %~dp0\InitializeEnvironment.bat || EXIT /b 10 3 | 4 | IF "%1"=="" (SET "Configuration=Debug") ELSE (SET "Configuration=%1") 5 | 6 | set RESULT_FRAMEWORK=0 7 | set TESTDIR=%PROJFS_OUTPUTDIR%\ProjectedFSLib.Managed.Test\bin\AnyCPU\%Configuration%\net48 8 | pushd %TESTDIR% 9 | %TESTDIR%\ProjectedFSLib.Managed.Test.exe --params ProviderExe=%PROJFS_OUTPUTDIR%\SimpleProviderManaged\bin\AnyCPU\%Configuration%\net48\SimpleProviderManaged.exe || set RESULT_FRAMEWORK=1 10 | popd 11 | 12 | set RESULT_CORE=0 13 | set TESTDIR=%PROJFS_OUTPUTDIR%\ProjectedFSLib.Managed.Test\bin\AnyCPU\%Configuration%\netcoreapp3.1 14 | pushd %TESTDIR% 15 | %TESTDIR%\ProjectedFSLib.Managed.Test.exe --params ProviderExe=%PROJFS_OUTPUTDIR%\SimpleProviderManaged\bin\AnyCPU\%Configuration%\netcoreapp3.1\SimpleProviderManaged.exe || set RESULT_CORE=1 16 | popd 17 | 18 | set RESULT=0 19 | if "%RESULT_FRAMEWORK% %RESULT_CORE%" neq "0 0" set RESULT=1 20 | 21 | exit /b %RESULT% -------------------------------------------------------------------------------- /simpleProviderManaged/ActiveEnumeration.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | using System.Collections.Generic; 5 | using Microsoft.Windows.ProjFS; 6 | 7 | namespace SimpleProviderManaged 8 | { 9 | internal class ActiveEnumeration 10 | { 11 | private readonly IEnumerable fileInfos; 12 | private IEnumerator fileInfoEnumerator; 13 | private string filterString = null; 14 | 15 | public ActiveEnumeration(List fileInfos) 16 | { 17 | this.fileInfos = fileInfos; 18 | this.ResetEnumerator(); 19 | this.MoveNext(); 20 | } 21 | 22 | /// 23 | /// true if Current refers to an element in the enumeration, false if Current is past the end of the collection 24 | /// 25 | public bool IsCurrentValid { get; private set; } 26 | 27 | /// 28 | /// Gets the element in the collection at the current position of the enumerator 29 | /// 30 | public ProjectedFileInfo Current 31 | { 32 | get { return this.fileInfoEnumerator.Current; } 33 | } 34 | 35 | /// 36 | /// Resets the enumerator and advances it to the first ProjectedFileInfo in the enumeration 37 | /// 38 | /// Filter string to save. Can be null. 39 | public void RestartEnumeration( 40 | string filter) 41 | { 42 | this.ResetEnumerator(); 43 | this.IsCurrentValid = this.fileInfoEnumerator.MoveNext(); 44 | this.SaveFilter(filter); 45 | } 46 | 47 | /// 48 | /// Advances the enumerator to the next element of the collection (that is being projected). 49 | /// If a filter string is set, MoveNext will advance to the next entry that matches the filter. 50 | /// 51 | /// 52 | /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection 53 | /// 54 | public bool MoveNext() 55 | { 56 | this.IsCurrentValid = this.fileInfoEnumerator.MoveNext(); 57 | 58 | while (this.IsCurrentValid && this.IsCurrentHidden()) 59 | { 60 | this.IsCurrentValid = this.fileInfoEnumerator.MoveNext(); 61 | } 62 | 63 | return this.IsCurrentValid; 64 | } 65 | 66 | /// 67 | /// Attempts to save the filter string for this enumeration. When setting a filter string, if Current is valid 68 | /// and does not match the specified filter, the enumerator will be advanced until an element is found that 69 | /// matches the filter (or the end of the collection is reached). 70 | /// 71 | /// Filter string to save. Can be null. 72 | /// True if the filter string was saved. False if the filter string was not saved (because a filter string 73 | /// was previously saved). 74 | /// 75 | /// 76 | /// Per MSDN (https://msdn.microsoft.com/en-us/library/windows/hardware/ff567047(v=vs.85).aspx, the filter string 77 | /// specified in the first call to ZwQueryDirectoryFile will be used for all subsequent calls for the handle (and 78 | /// the string specified in subsequent calls should be ignored) 79 | /// 80 | public bool TrySaveFilterString( 81 | string filter) 82 | { 83 | if (this.filterString == null) 84 | { 85 | this.SaveFilter(filter); 86 | return true; 87 | } 88 | 89 | return false; 90 | } 91 | 92 | /// 93 | /// Returns the current filter string or null if no filter string has been saved 94 | /// 95 | /// The current filter string or null if no filter string has been saved 96 | public string GetFilterString() 97 | { 98 | return this.filterString; 99 | } 100 | 101 | private static bool FileNameMatchesFilter( 102 | string name, 103 | string filter) 104 | { 105 | if (string.IsNullOrEmpty(filter)) 106 | { 107 | return true; 108 | } 109 | 110 | if (filter == "*") 111 | { 112 | return true; 113 | } 114 | 115 | return Utils.IsFileNameMatch(name, filter); 116 | } 117 | 118 | private void SaveFilter( 119 | string filter) 120 | { 121 | if (string.IsNullOrEmpty(filter)) 122 | { 123 | this.filterString = string.Empty; 124 | } 125 | else 126 | { 127 | this.filterString = filter; 128 | if (this.IsCurrentValid && this.IsCurrentHidden()) 129 | { 130 | this.MoveNext(); 131 | } 132 | } 133 | } 134 | 135 | private bool IsCurrentHidden() 136 | { 137 | return !FileNameMatchesFilter(this.Current.Name, this.GetFilterString()); 138 | } 139 | 140 | private void ResetEnumerator() 141 | { 142 | this.fileInfoEnumerator = this.fileInfos.GetEnumerator(); 143 | } 144 | } 145 | } 146 | 147 | 148 | -------------------------------------------------------------------------------- /simpleProviderManaged/EnvironmentHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SimpleProviderManaged 9 | { 10 | public static class EnvironmentHelper 11 | { 12 | public static bool IsFullSymlinkSupportAvailable() 13 | { 14 | // Using registry instead of OSVersion due to https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getversionexa?redirectedfrom=MSDN. 15 | // This code can be replaced with Environment.OSVersion on .NET Core 5 and higher. 16 | int build = Convert.ToInt32(Microsoft.Win32.Registry.GetValue( 17 | @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", 18 | "CurrentBuild", 19 | 0)); 20 | 21 | if (build >= 19041) 22 | { 23 | return true; 24 | } 25 | 26 | return false; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /simpleProviderManaged/FileSystemApi.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | using Microsoft.Win32.SafeHandles; 5 | using System; 6 | using System.ComponentModel; 7 | using System.IO; 8 | using System.Runtime.InteropServices; 9 | using System.Text; 10 | 11 | namespace SimpleProviderManaged 12 | { 13 | public sealed class FileSystemApi 14 | { 15 | [Flags] 16 | public enum SymbolicLinkOptions : uint 17 | { 18 | /// 19 | /// The link target is a file. 20 | /// 21 | File = 0x0, 22 | 23 | /// 24 | /// The link target is a directory. 25 | /// 26 | Directory = 0x1, 27 | 28 | /// 29 | /// Specify this flag to allow creation of symbolic links when the process is not elevated. 30 | /// 31 | AllowUnprivilegedCreate = 0x2 32 | } 33 | 34 | public enum DwReserved0Flag : uint 35 | { 36 | IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003, // Used for mount point support, specified in section 2.1.2.5. 37 | IO_REPARSE_TAG_SYMLINK = 0xA000000C, // Used for symbolic link support. See section 2.1.2.4. 38 | } 39 | 40 | [Flags] 41 | public enum FileDesiredAccess : uint 42 | { 43 | /// 44 | /// See http://msdn.microsoft.com/en-us/library/windows/desktop/aa364399(v=vs.85).aspx 45 | /// 46 | GenericRead = 0x80000000 47 | } 48 | 49 | [Flags] 50 | public enum FileFlagsAndAttributes : uint 51 | { 52 | /// 53 | /// No flags. 54 | /// 55 | None = 0, 56 | 57 | /// 58 | /// The handle that identifies a directory. 59 | /// 60 | FileAttributeDirectory = 0x20, 61 | 62 | /// 63 | /// The file should be archived. Applications use this attribute to mark files for backup or removal. 64 | /// 65 | FileAttributeArchive = 0x20, 66 | 67 | /// 68 | /// The file does not have other attributes set. This attribute is valid only if used alone. 69 | /// 70 | FileAttributeNormal = 0x80, 71 | 72 | /// 73 | /// The file is being opened or created for a backup or restore operation. The system ensures that the calling process 74 | /// overrides file security checks when the process has SE_BACKUP_NAME and SE_RESTORE_NAME privileges. For more 75 | /// information, see Changing Privileges in a Token. 76 | /// You must set this flag to obtain a handle to a directory. A directory handle can be passed to some functions instead of 77 | /// a file handle. 78 | /// 79 | FileFlagBackupSemantics = 0x02000000, 80 | 81 | /// 82 | /// Normal reparse point processing will not occur; CreateFile will attempt to open the reparse point. When a file is 83 | /// opened, a file handle is returned, whether or not the filter that controls the reparse point is operational. 84 | /// This flag cannot be used with the CREATE_ALWAYS flag. 85 | /// If the file is not a reparse point, then this flag is ignored. 86 | /// 87 | FileFlagOpenReparsePoint = 0x00200000, 88 | } 89 | 90 | public static class NativeIOConstants 91 | { 92 | /// 93 | /// ERROR_SUCCESS 94 | /// 95 | public const int ErrorSuccess = 0x0; 96 | } 97 | 98 | private const int INITIAL_REPARSE_DATA_BUFFER_SIZE = 1024; 99 | private const int FSCTL_GET_REPARSE_POINT = 0x000900a8; 100 | 101 | private const int ERROR_INSUFFICIENT_BUFFER = 0x7A; 102 | private const int ERROR_MORE_DATA = 0xEA; 103 | private const int ERROR_SUCCESS = 0x0; 104 | private const int SYMLINK_FLAG_RELATIVE = 0x1; 105 | 106 | [DllImport("kernel32.dll", SetLastError = true)] 107 | [return: MarshalAs(UnmanagedType.Bool)] 108 | private static extern bool DeviceIoControl( 109 | SafeFileHandle deviceHandle, 110 | uint ioControlCode, 111 | IntPtr inputBuffer, 112 | int inputBufferSize, 113 | IntPtr outputBuffer, 114 | int outputBufferSize, 115 | out int bytesReturned, 116 | IntPtr overlapped); 117 | 118 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)] 119 | private static extern SafeFileHandle CreateFileW( 120 | string lpFileName, 121 | FileDesiredAccess dwDesiredAccess, 122 | FileShare dwShareMode, 123 | IntPtr lpSecurityAttributes, 124 | FileMode dwCreationDisposition, 125 | FileFlagsAndAttributes dwFlagsAndAttributes, 126 | IntPtr hTemplateFile); 127 | 128 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)] 129 | private static extern int CreateSymbolicLinkW(string lpSymlinkFileName, string lpTargetFileName, SymbolicLinkOptions dwFlags); 130 | 131 | [DllImport("shlwapi.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)] 132 | private static extern bool PathRelativePathToW( 133 | System.Text.StringBuilder pszPath, 134 | string pszFrom, 135 | FileFlagsAndAttributes dwAttrFrom, 136 | string pszTo, 137 | FileFlagsAndAttributes dwAttrTo); 138 | 139 | private static unsafe string GetReparsePointTarget(SafeFileHandle handle) 140 | { 141 | string targetPath = string.Empty; 142 | 143 | int bufferSize = INITIAL_REPARSE_DATA_BUFFER_SIZE; 144 | int errorCode = ERROR_INSUFFICIENT_BUFFER; 145 | 146 | byte[] buffer = null; 147 | while (errorCode == ERROR_MORE_DATA || errorCode == ERROR_INSUFFICIENT_BUFFER) 148 | { 149 | buffer = new byte[bufferSize]; 150 | bool success = false; 151 | 152 | fixed (byte* pBuffer = buffer) 153 | { 154 | int bufferReturnedSize; 155 | success = DeviceIoControl( 156 | handle, 157 | FSCTL_GET_REPARSE_POINT, 158 | IntPtr.Zero, 159 | 0, 160 | (IntPtr)pBuffer, 161 | bufferSize, 162 | out bufferReturnedSize, 163 | IntPtr.Zero); 164 | } 165 | 166 | bufferSize *= 2; 167 | errorCode = success ? 0 : Marshal.GetLastWin32Error(); 168 | } 169 | 170 | if (errorCode != 0) 171 | { 172 | throw new Win32Exception(errorCode, "DeviceIoControl(FSCTL_GET_REPARSE_POINT)"); 173 | } 174 | 175 | // Now get the offsets in the REPARSE_DATA_BUFFER buffer string based on 176 | // the offsets for the different type of reparse points. 177 | 178 | const uint PrintNameOffsetIndex = 12; 179 | const uint PrintNameLengthIndex = 14; 180 | const uint SubsNameOffsetIndex = 8; 181 | const uint SubsNameLengthIndex = 10; 182 | 183 | fixed (byte* pBuffer = buffer) 184 | { 185 | uint reparsePointTag = *(uint*)(pBuffer); 186 | 187 | if (reparsePointTag != (uint)DwReserved0Flag.IO_REPARSE_TAG_SYMLINK 188 | && reparsePointTag != (uint)DwReserved0Flag.IO_REPARSE_TAG_MOUNT_POINT) 189 | { 190 | throw new NotSupportedException($"Reparse point tag {reparsePointTag:X} not supported"); 191 | } 192 | 193 | uint pathBufferOffsetIndex = (uint)((reparsePointTag == (uint)DwReserved0Flag.IO_REPARSE_TAG_SYMLINK) ? 20 : 16); 194 | char* nameStartPtr = (char*)(pBuffer + pathBufferOffsetIndex); 195 | int nameOffset = *(short*)(pBuffer + PrintNameOffsetIndex) / 2; 196 | int nameLength = *(short*)(pBuffer + PrintNameLengthIndex) / 2; 197 | targetPath = new string(nameStartPtr, nameOffset, nameLength); 198 | 199 | if (string.IsNullOrWhiteSpace(targetPath)) 200 | { 201 | nameOffset = *(short*)(pBuffer + SubsNameOffsetIndex) / 2; 202 | nameLength = *(short*)(pBuffer + SubsNameLengthIndex) / 2; 203 | targetPath = new string(nameStartPtr, nameOffset, nameLength); 204 | } 205 | } 206 | 207 | return targetPath; 208 | } 209 | 210 | public static bool TryCreateSymbolicLink(string symLinkFileName, string targetFileName, bool isTargetFile) 211 | { 212 | SymbolicLinkOptions creationFlag = isTargetFile ? SymbolicLinkOptions.File : SymbolicLinkOptions.Directory; 213 | creationFlag |= SymbolicLinkOptions.AllowUnprivilegedCreate; 214 | 215 | int res = CreateSymbolicLinkW(symLinkFileName, targetFileName, creationFlag); 216 | 217 | // The return value of CreateSymbolicLinkW is underspecified in its documentation. 218 | // In non-admin mode where Developer mode is not enabled, the return value can be greater than zero, but the last error 219 | // is ERROR_PRIVILEGE_NOT_HELD, and consequently the symlink is not created. We strenghten the return value by 220 | // also checking that the last error is ERROR_SUCCESS. 221 | int lastError = Marshal.GetLastWin32Error(); 222 | return res > 0 && lastError == NativeIOConstants.ErrorSuccess; 223 | } 224 | 225 | public static bool TryGetReparsePointTarget(string source, out string target) 226 | { 227 | target = null; 228 | using (SafeFileHandle handle = CreateFileW( 229 | source, 230 | FileDesiredAccess.GenericRead, 231 | FileShare.Read | FileShare.Delete, 232 | IntPtr.Zero, 233 | FileMode.Open, 234 | FileFlagsAndAttributes.FileFlagOpenReparsePoint | FileFlagsAndAttributes.FileFlagBackupSemantics, 235 | IntPtr.Zero)) 236 | { 237 | if (handle.IsInvalid) 238 | { 239 | return false; 240 | } 241 | 242 | target = GetReparsePointTarget(handle); 243 | return true; 244 | } 245 | } 246 | 247 | public static string TryGetPathRelativeToRoot(string root, string path, bool isPathToADirectory) 248 | { 249 | const Int32 MaxPath = 260; 250 | StringBuilder relativePathBuilder = new StringBuilder(MaxPath); 251 | 252 | string pathFrom; 253 | if (!root.EndsWith(Path.DirectorySeparatorChar.ToString())) 254 | { 255 | // PathRelativePathToW expects the root to have a trailing slash. 256 | pathFrom = root + Path.DirectorySeparatorChar; 257 | } 258 | else 259 | { 260 | pathFrom = root; 261 | } 262 | 263 | bool result = PathRelativePathToW(relativePathBuilder, 264 | pathFrom, 265 | FileFlagsAndAttributes.FileAttributeDirectory, 266 | path, 267 | isPathToADirectory ? FileFlagsAndAttributes.FileAttributeDirectory : FileFlagsAndAttributes.FileAttributeNormal); 268 | 269 | if (!result) 270 | { 271 | throw new Exception($"Failed to get relative path of {path} with root {root}."); 272 | } 273 | 274 | return relativePathBuilder.ToString(); 275 | } 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /simpleProviderManaged/NotificationCallbacks.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | using Microsoft.Windows.ProjFS; 5 | using Serilog; 6 | using System; 7 | using System.Collections.Generic; 8 | 9 | namespace SimpleProviderManaged 10 | { 11 | class NotificationCallbacks 12 | { 13 | private readonly SimpleProvider provider; 14 | 15 | public NotificationCallbacks( 16 | SimpleProvider provider, 17 | VirtualizationInstance virtInstance, 18 | IReadOnlyCollection notificationMappings) 19 | { 20 | this.provider = provider; 21 | 22 | // Look through notificationMappings for all the set notification bits. Supply a callback 23 | // for each set bit. 24 | NotificationType notification = NotificationType.None; 25 | foreach (NotificationMapping mapping in notificationMappings) 26 | { 27 | notification |= mapping.NotificationMask; 28 | } 29 | 30 | if ((notification & NotificationType.FileOpened) == NotificationType.FileOpened) 31 | { 32 | virtInstance.OnNotifyFileOpened = NotifyFileOpenedCallback; 33 | } 34 | 35 | if ((notification & NotificationType.NewFileCreated) == NotificationType.NewFileCreated) 36 | { 37 | virtInstance.OnNotifyNewFileCreated = NotifyNewFileCreatedCallback; 38 | } 39 | 40 | if ((notification & NotificationType.FileOverwritten) == NotificationType.FileOverwritten) 41 | { 42 | virtInstance.OnNotifyFileOverwritten = NotifyFileOverwrittenCallback; 43 | } 44 | 45 | if ((notification & NotificationType.PreDelete) == NotificationType.PreDelete) 46 | { 47 | virtInstance.OnNotifyPreDelete = NotifyPreDeleteCallback; 48 | } 49 | 50 | if ((notification & NotificationType.PreRename) == NotificationType.PreRename) 51 | { 52 | virtInstance.OnNotifyPreRename = NotifyPreRenameCallback; 53 | } 54 | 55 | if ((notification & NotificationType.PreCreateHardlink) == NotificationType.PreCreateHardlink) 56 | { 57 | virtInstance.OnNotifyPreCreateHardlink = NotifyPreCreateHardlinkCallback; 58 | } 59 | 60 | if ((notification & NotificationType.FileRenamed) == NotificationType.FileRenamed) 61 | { 62 | virtInstance.OnNotifyFileRenamed = NotifyFileRenamedCallback; 63 | } 64 | 65 | if ((notification & NotificationType.HardlinkCreated) == NotificationType.HardlinkCreated) 66 | { 67 | virtInstance.OnNotifyHardlinkCreated = NotifyHardlinkCreatedCallback; 68 | } 69 | 70 | if ((notification & NotificationType.FileHandleClosedNoModification) == NotificationType.FileHandleClosedNoModification) 71 | { 72 | virtInstance.OnNotifyFileHandleClosedNoModification = NotifyFileHandleClosedNoModificationCallback; 73 | } 74 | 75 | if (((notification & NotificationType.FileHandleClosedFileModified) == NotificationType.FileHandleClosedFileModified) || 76 | ((notification & NotificationType.FileHandleClosedFileDeleted) == NotificationType.FileHandleClosedFileDeleted)) 77 | { 78 | virtInstance.OnNotifyFileHandleClosedFileModifiedOrDeleted = NotifyFileHandleClosedFileModifiedOrDeletedCallback; 79 | } 80 | 81 | if ((notification & NotificationType.FilePreConvertToFull) == NotificationType.FilePreConvertToFull) 82 | { 83 | virtInstance.OnNotifyFilePreConvertToFull = NotifyFilePreConvertToFullCallback; 84 | } 85 | } 86 | 87 | public bool NotifyFileOpenedCallback( 88 | string relativePath, 89 | bool isDirectory, 90 | uint triggeringProcessId, 91 | string triggeringProcessImageFileName, 92 | out NotificationType notificationMask) 93 | { 94 | Log.Information("NotifyFileOpenedCallback [{relativePath}]", relativePath); 95 | Log.Information(" Notification triggered by [{triggeringProcessImageFileName} {triggeringProcessId}]", 96 | triggeringProcessImageFileName, triggeringProcessId); 97 | 98 | notificationMask = NotificationType.UseExistingMask; 99 | provider.SignalIfTestMode("FileOpened"); 100 | return true; 101 | } 102 | 103 | 104 | public void NotifyNewFileCreatedCallback( 105 | string relativePath, 106 | bool isDirectory, 107 | uint triggeringProcessId, 108 | string triggeringProcessImageFileName, 109 | out NotificationType notificationMask) 110 | { 111 | Log.Information("NotifyNewFileCreatedCallback [{relativePath}]", relativePath); 112 | Log.Information(" Notification triggered by [{triggeringProcessImageFileName} {triggeringProcessId}]", 113 | triggeringProcessImageFileName, triggeringProcessId); 114 | 115 | notificationMask = NotificationType.UseExistingMask; 116 | provider.SignalIfTestMode("NewFileCreated"); 117 | } 118 | 119 | public void NotifyFileOverwrittenCallback( 120 | string relativePath, 121 | bool isDirectory, 122 | uint triggeringProcessId, 123 | string triggeringProcessImageFileName, 124 | out NotificationType notificationMask) 125 | { 126 | Log.Information("NotifyFileOverwrittenCallback [{relativePath}]", relativePath); 127 | Log.Information(" Notification triggered by [{triggeringProcessImageFileName} {triggeringProcessId}]", 128 | triggeringProcessImageFileName, triggeringProcessId); 129 | 130 | notificationMask = NotificationType.UseExistingMask; 131 | provider.SignalIfTestMode("FileOverwritten"); 132 | } 133 | 134 | public bool NotifyPreDeleteCallback( 135 | string relativePath, 136 | bool isDirectory, 137 | uint triggeringProcessId, 138 | string triggeringProcessImageFileName) 139 | { 140 | Log.Information("NotifyPreDeleteCallback [{relativePath}]", relativePath); 141 | Log.Information(" Notification triggered by [{triggeringProcessImageFileName} {triggeringProcessId}]", 142 | triggeringProcessImageFileName, triggeringProcessId); 143 | 144 | provider.SignalIfTestMode("PreDelete"); 145 | return provider.Options.DenyDeletes ? false : true; 146 | } 147 | 148 | public bool NotifyPreRenameCallback( 149 | string relativePath, 150 | string destinationPath, 151 | uint triggeringProcessId, 152 | string triggeringProcessImageFileName) 153 | { 154 | Log.Information("NotifyPreRenameCallback [{relativePath}] [{destinationPath}]", relativePath, destinationPath); 155 | Log.Information(" Notification triggered by [{triggeringProcessImageFileName} {triggeringProcessId}]", 156 | triggeringProcessImageFileName, triggeringProcessId); 157 | 158 | provider.SignalIfTestMode("PreRename"); 159 | return true; 160 | } 161 | 162 | public bool NotifyPreCreateHardlinkCallback( 163 | string relativePath, 164 | string destinationPath, 165 | uint triggeringProcessId, 166 | string triggeringProcessImageFileName) 167 | { 168 | Log.Information("NotifyPreCreateHardlinkCallback [{relativePath}] [{destinationPath}]", relativePath, destinationPath); 169 | Log.Information(" Notification triggered by [{triggeringProcessImageFileName} {triggeringProcessId}]", 170 | triggeringProcessImageFileName, triggeringProcessId); 171 | 172 | provider.SignalIfTestMode("PreCreateHardlink"); 173 | return true; 174 | } 175 | 176 | public void NotifyFileRenamedCallback( 177 | string relativePath, 178 | string destinationPath, 179 | bool isDirectory, 180 | uint triggeringProcessId, 181 | string triggeringProcessImageFileName, 182 | out NotificationType notificationMask) 183 | { 184 | Log.Information("NotifyFileRenamedCallback [{relativePath}] [{destinationPath}]", relativePath, destinationPath); 185 | Log.Information(" Notification triggered by [{triggeringProcessImageFileName} {triggeringProcessId}]", 186 | triggeringProcessImageFileName, triggeringProcessId); 187 | 188 | notificationMask = NotificationType.UseExistingMask; 189 | provider.SignalIfTestMode("FileRenamed"); 190 | } 191 | 192 | public void NotifyHardlinkCreatedCallback( 193 | string relativePath, 194 | string destinationPath, 195 | uint triggeringProcessId, 196 | string triggeringProcessImageFileName) 197 | { 198 | Log.Information("NotifyHardlinkCreatedCallback [{relativePath}] [{destinationPath}]", relativePath, destinationPath); 199 | Log.Information(" Notification triggered by [{triggeringProcessImageFileName} {triggeringProcessId}]", 200 | triggeringProcessImageFileName, triggeringProcessId); 201 | 202 | provider.SignalIfTestMode("HardlinkCreated"); 203 | } 204 | 205 | public void NotifyFileHandleClosedNoModificationCallback( 206 | string relativePath, 207 | bool isDirectory, 208 | uint triggeringProcessId, 209 | string triggeringProcessImageFileName) 210 | { 211 | Log.Information("NotifyFileHandleClosedNoModificationCallback [{relativePath}]", relativePath); 212 | Log.Information(" Notification triggered by [{triggeringProcessImageFileName} {triggeringProcessId}]", 213 | triggeringProcessImageFileName, triggeringProcessId); 214 | 215 | provider.SignalIfTestMode("FileHandleClosedNoModification"); 216 | } 217 | 218 | public void NotifyFileHandleClosedFileModifiedOrDeletedCallback( 219 | string relativePath, 220 | bool isDirectory, 221 | bool isFileModified, 222 | bool isFileDeleted, 223 | uint triggeringProcessId, 224 | string triggeringProcessImageFileName) 225 | { 226 | Log.Information("NotifyFileHandleClosedFileModifiedOrDeletedCallback [{relativePath}]", relativePath); 227 | Log.Information(" Modified: {isFileModified}, Deleted: {isFileDeleted} ", isFileModified, isFileDeleted); 228 | Log.Information(" Notification triggered by [{triggeringProcessImageFileName} {triggeringProcessId}]", 229 | triggeringProcessImageFileName, triggeringProcessId); 230 | 231 | provider.SignalIfTestMode("FileHandleClosedFileModifiedOrDeleted"); 232 | } 233 | 234 | public bool NotifyFilePreConvertToFullCallback( 235 | string relativePath, 236 | uint triggeringProcessId, 237 | string triggeringProcessImageFileName) 238 | { 239 | Log.Information("NotifyFilePreConvertToFullCallback [{relativePath}]", relativePath); 240 | Log.Information(" Notification triggered by [{triggeringProcessImageFileName} {triggeringProcessId}]", 241 | triggeringProcessImageFileName, triggeringProcessId); 242 | 243 | provider.SignalIfTestMode("FilePreConvertToFull"); 244 | return true; 245 | } 246 | 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /simpleProviderManaged/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | using CommandLine; 5 | using Serilog; 6 | using System; 7 | 8 | namespace SimpleProviderManaged 9 | { 10 | public class Program 11 | { 12 | private enum ReturnCode 13 | { 14 | Success = 0, 15 | InvalidArguments = 1, 16 | GeneralException = 2, 17 | } 18 | 19 | public static int Main(string[] args) 20 | { 21 | try 22 | { 23 | var parser = new Parser(with => 24 | { 25 | with.AutoHelp = true; 26 | with.AutoVersion = true; 27 | with.EnableDashDash = true; 28 | with.CaseSensitive = false; 29 | with.CaseInsensitiveEnumValues = true; 30 | with.HelpWriter = Console.Out; 31 | }); 32 | 33 | var parserResult = parser 34 | .ParseArguments(args) 35 | .WithParsed((ProviderOptions options) => 36 | { 37 | // We want verbose logging so we can see all our callback invocations. 38 | var logConfig = new LoggerConfiguration() 39 | .WriteTo.Console() 40 | .WriteTo.File("SimpleProviderManaged-.log", rollingInterval: RollingInterval.Day); 41 | 42 | if (options.Verbose) 43 | { 44 | logConfig = logConfig.MinimumLevel.Verbose(); 45 | } 46 | 47 | Log.Logger = logConfig.CreateLogger(); 48 | 49 | Log.Information("Start"); 50 | Run(options); 51 | }); 52 | 53 | Log.Information("Exit successfully"); 54 | return (int) ReturnCode.Success; 55 | } 56 | catch (Exception ex) 57 | { 58 | Console.Error.WriteLine($"Unexpected exception: {ex}"); 59 | return (int)ReturnCode.GeneralException; 60 | } 61 | } 62 | 63 | private static void Run(ProviderOptions options) 64 | { 65 | SimpleProvider provider; 66 | try 67 | { 68 | provider = new SimpleProvider(options); 69 | } 70 | catch (Exception ex) 71 | { 72 | Log.Fatal(ex, "Failed to create SimpleProvider."); 73 | throw; 74 | } 75 | 76 | Log.Information("Starting provider"); 77 | 78 | if (!provider.StartVirtualization()) 79 | { 80 | Log.Error("Could not start provider."); 81 | Environment.Exit(1); 82 | } 83 | 84 | Console.WriteLine("Provider is running. Press Enter to exit."); 85 | Console.ReadLine(); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /simpleProviderManaged/ProjFSSorter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | using System.Collections.Generic; 5 | using Microsoft.Windows.ProjFS; 6 | 7 | namespace SimpleProviderManaged 8 | { 9 | /// 10 | /// Implements IComparer using . 11 | /// 12 | internal class ProjFSSorter : Comparer 13 | { 14 | public override int Compare(string x, string y) => Utils.FileNameCompare(x, y); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /simpleProviderManaged/ProjectedFileInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | using System; 5 | using System.IO; 6 | 7 | namespace SimpleProviderManaged 8 | { 9 | public class ProjectedFileInfo 10 | { 11 | public ProjectedFileInfo( 12 | string name, 13 | string fullName, 14 | long size, 15 | bool isDirectory, 16 | DateTime creationTime, 17 | DateTime lastAccessTime, 18 | DateTime lastWriteTime, 19 | DateTime changeTime, 20 | FileAttributes attributes) 21 | { 22 | this.Name = name; 23 | this.FullName = fullName; 24 | this.Size = isDirectory ? 0 : size; 25 | this.IsDirectory = isDirectory; 26 | this.CreationTime = creationTime; 27 | this.LastAccessTime = lastAccessTime; 28 | this.LastWriteTime = lastWriteTime; 29 | this.ChangeTime = changeTime; 30 | // Make sure the directory attribute is stored properly. 31 | this.Attributes = isDirectory ? (attributes | FileAttributes.Directory) : (attributes & ~FileAttributes.Directory); 32 | } 33 | 34 | public ProjectedFileInfo( 35 | string name, 36 | string fullName, 37 | long size, 38 | bool isDirectory) : this( 39 | name: name, 40 | fullName: fullName, 41 | size: size, 42 | isDirectory: isDirectory, 43 | creationTime: DateTime.UtcNow, 44 | lastAccessTime: DateTime.UtcNow, 45 | lastWriteTime: DateTime.UtcNow, 46 | changeTime: DateTime.UtcNow, 47 | attributes: isDirectory ? FileAttributes.Directory : FileAttributes.Normal) 48 | { } 49 | 50 | public string Name { get; } 51 | public string FullName { get; } 52 | public long Size { get; } 53 | public bool IsDirectory { get; } 54 | public DateTime CreationTime { get; } 55 | public DateTime LastAccessTime { get; } 56 | public DateTime LastWriteTime { get; } 57 | public DateTime ChangeTime { get; } 58 | public FileAttributes Attributes { get; } 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /simpleProviderManaged/ProviderOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | using CommandLine; 5 | using CommandLine.Text; 6 | using System.Collections.Generic; 7 | 8 | namespace SimpleProviderManaged 9 | { 10 | public class ProviderOptions 11 | { 12 | [Option(Required = true, HelpText = "Path to the files and directories to project.")] 13 | public string SourceRoot { get; set; } 14 | 15 | [Option(Required = true, HelpText = "Path to the virtualization root.")] 16 | public string VirtRoot { get; set; } 17 | 18 | [Option('t', "testmode", HelpText = "Use this when running the provider with the test package.", Hidden = true)] 19 | public bool TestMode { get; set; } 20 | 21 | [Option('n', "notifications", HelpText = "Enable file system operation notifications.")] 22 | public bool EnableNotifications { get; set; } 23 | 24 | [Option('v', "verbose", HelpText = "Use verbose log level.")] 25 | public bool Verbose { get; set; } 26 | 27 | [Option('d', "denyDeletes", HelpText = "Deny deletes.", Hidden = true)] 28 | public bool DenyDeletes { get; set; } 29 | 30 | [Usage(ApplicationAlias = "SimpleProviderManaged")] 31 | public static IEnumerable Examples 32 | { 33 | get 34 | { 35 | return new List() 36 | { 37 | new Example( 38 | "Start provider, projecting files and directories from 'c:\\source' into 'c:\\virtRoot'", 39 | new ProviderOptions { SourceRoot = "c:\\source", VirtRoot = "c:\\virtRoot" }) 40 | }; 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /simpleProviderManaged/README.md: -------------------------------------------------------------------------------- 1 | # SimpleProviderManaged 2 | 3 | This project is a simple ProjFS provider written in C#. It projects the contents of one directory (the "source") 4 | into another (the "virtualization root"). 5 | 6 | ## Command line parameters 7 | `SimpleProviderManaged.exe --SourceRoot= --VirtRoot=` 8 | 9 | Where: 10 | * `--SourceRoot` specifies the path to a directory whose contents you wish to project. 11 | * `--VirtRoot` specifies the path to a directory into which the contents of `SourceRoot` are projected. When the 12 | provider is running, the contents of `SourceRoot` will appear to exist in `VirtRoot`. 13 | * Note that `VirtRoot` cannot be a descendant or ancestor of another virtualization root. If another instance of 14 | SimpleProviderManaged (or some other ProjFS provider) is running with a virtualization root that is an ancestor 15 | or descendant of `VirtRoot`, then SimpleProviderManaged will fail to start on a `Win32Exception` with the error value 16 | ERROR_FILE_SYSTEM_VIRTUALIZATION_INVALID_OPERATION. 17 | 18 | ## Shutting down the provider 19 | To stop the provider hit the `` key in the provider's console window. 20 | 21 | ## Notices 22 | This project calls [Serilog](https://serilog.net/) APIs, which are under the 23 | [license](https://github.com/serilog/serilog/blob/dev/LICENSE) for Serilog. -------------------------------------------------------------------------------- /simpleProviderManaged/SimpleProviderManaged.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | net48;netcoreapp3.1 7 | Exe 8 | x64 9 | MinimumRecommendedRules.ruleset 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /simpleProviderManaged/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | --------------------------------------------------------------------------------