├── .gitignore ├── Assets └── cd_logo.png ├── LICENSE ├── MiniFSWatcher.sln ├── MiniFSWatcher ├── EventReader.cs ├── EventWatcher.cs ├── Events │ ├── FileSystemEvent.cs │ └── RenameOrMoveEvent.cs ├── FilterConnector.cs ├── MiniFSWatcher.csproj ├── MiniFSWatcher.nuspec ├── NativeMethods.cs ├── PathConverter.cs ├── Properties │ └── AssemblyInfo.cs ├── SafePortHandle.cs ├── Types │ ├── CommandMessage.cs │ ├── DriverVersion.cs │ ├── EventType.cs │ ├── HResult.cs │ ├── LogRecord.cs │ ├── MinispyCommand.cs │ └── RecordData.cs └── runsdvui.cmd ├── MiniFSWatcherApp ├── App.config ├── MiniFSWatcherApp.csproj ├── Program.cs └── Properties │ └── AssemblyInfo.cs ├── MiniFSWatcherDriver ├── RegistrationData.c ├── minifswatcher.inf ├── minispy.c ├── minispy.h ├── minispy.rc ├── minispy.vcxproj ├── minispy.vcxproj.Filters ├── mspyKern.h └── mspyLib.c ├── MiniFSWatcherPackage ├── MiniFSWatcherPackage.vcxproj └── MiniFSWatcherPackage.vcxproj.filters ├── MiniFSWatcherTest ├── BasicFileEventTest.cs ├── FileEventTest.cs ├── MiniFSWatcherTest.csproj ├── NativeMethods.cs └── Properties │ └── AssemblyInfo.cs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | *.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.pfx 193 | *.publishsettings 194 | node_modules/ 195 | orleans.codegen.cs 196 | 197 | # Since there are multiple workflows, uncomment next line to ignore bower_components 198 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 199 | #bower_components/ 200 | 201 | # RIA/Silverlight projects 202 | Generated_Code/ 203 | 204 | # Backup & report files from converting an old project file 205 | # to a newer Visual Studio version. Backup files are not needed, 206 | # because we have git ;-) 207 | _UpgradeReport_Files/ 208 | Backup*/ 209 | UpgradeLog*.XML 210 | UpgradeLog*.htm 211 | 212 | # SQL Server files 213 | *.mdf 214 | *.ldf 215 | 216 | # Business Intelligence projects 217 | *.rdl.data 218 | *.bim.layout 219 | *.bim_*.settings 220 | 221 | # Microsoft Fakes 222 | FakesAssemblies/ 223 | 224 | # GhostDoc plugin setting file 225 | *.GhostDoc.xml 226 | 227 | # Node.js Tools for Visual Studio 228 | .ntvs_analysis.dat 229 | 230 | # Visual Studio 6 build log 231 | *.plg 232 | 233 | # Visual Studio 6 workspace options file 234 | *.opt 235 | 236 | # Visual Studio LightSwitch build output 237 | **/*.HTMLClient/GeneratedArtifacts 238 | **/*.DesktopClient/GeneratedArtifacts 239 | **/*.DesktopClient/ModelManifest.xml 240 | **/*.Server/GeneratedArtifacts 241 | **/*.Server/ModelManifest.xml 242 | _Pvt_Extensions 243 | 244 | # Paket dependency manager 245 | .paket/paket.exe 246 | paket-files/ 247 | 248 | # FAKE - F# Make 249 | .fake/ 250 | 251 | # JetBrains Rider 252 | .idea/ 253 | *.sln.iml 254 | -------------------------------------------------------------------------------- /Assets/cd_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CenterDevice/MiniFSWatcher/81b5c74cdb9a450c4fe2949637c1f7a99517d62b/Assets/cd_logo.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The Microsoft Public License (MS-PL) 2 | Copyright (c) 2015 Microsoft 3 | 4 | This license governs use of the accompanying software. If you use the software, you 5 | accept this license. If you do not accept the license, do not use the software. 6 | 7 | 1. Definitions 8 | The terms "reproduce," "reproduction," "derivative works," and "distribution" have the 9 | same meaning here as under U.S. copyright law. 10 | A "contribution" is the original software, or any additions or changes to the software. 11 | A "contributor" is any person that distributes its contribution under this license. 12 | "Licensed patents" are a contributor's patent claims that read directly on its contribution. 13 | 14 | 2. Grant of Rights 15 | (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. 16 | (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. 17 | 18 | 3. Conditions and Limitations 19 | (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. 20 | (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. 21 | (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. 22 | (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. 23 | (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. 24 | -------------------------------------------------------------------------------- /MiniFSWatcher.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25123.0 5 | MinimumVisualStudioVersion = 12.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Filter", "Filter", "{FE789860-CDFE-48E5-AE02-E418DD3A0C6D}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "User", "User", "{94944192-75BD-4A9E-B4DB-811E26C3A6E5}" 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MiniFSWatcherDriver", "MiniFSWatcherDriver\minispy.vcxproj", "{AB0153F1-D5E4-45E0-9308-5EF32974E9A1}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiniFSWatcher", "MiniFSWatcher\MiniFSWatcher.csproj", "{01BFC4EE-8FD7-44CE-A00E-DA542116F27D}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiniFSWatcherApp", "MiniFSWatcherApp\MiniFSWatcherApp.csproj", "{4FF23EB9-728F-4293-8F8B-F86FF391D36E}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiniFSWatcherTest", "MiniFSWatcherTest\MiniFSWatcherTest.csproj", "{6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}" 17 | EndProject 18 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MiniFSWatcherPackage", "MiniFSWatcherPackage\MiniFSWatcherPackage.vcxproj", "{89047C71-E058-482E-A4AF-CCC8CDA77612}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Debug|ARM = Debug|ARM 24 | Debug|ARM64 = Debug|ARM64 25 | Debug|Win32 = Debug|Win32 26 | Debug|x64 = Debug|x64 27 | Release|Any CPU = Release|Any CPU 28 | Release|ARM = Release|ARM 29 | Release|ARM64 = Release|ARM64 30 | Release|Win32 = Release|Win32 31 | Release|x64 = Release|x64 32 | EndGlobalSection 33 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 34 | {AB0153F1-D5E4-45E0-9308-5EF32974E9A1}.Debug|Any CPU.ActiveCfg = Debug|Win32 35 | {AB0153F1-D5E4-45E0-9308-5EF32974E9A1}.Debug|ARM.ActiveCfg = Debug|Win32 36 | {AB0153F1-D5E4-45E0-9308-5EF32974E9A1}.Debug|ARM64.ActiveCfg = Debug|Win32 37 | {AB0153F1-D5E4-45E0-9308-5EF32974E9A1}.Debug|Win32.ActiveCfg = Debug|Win32 38 | {AB0153F1-D5E4-45E0-9308-5EF32974E9A1}.Debug|Win32.Build.0 = Debug|Win32 39 | {AB0153F1-D5E4-45E0-9308-5EF32974E9A1}.Debug|x64.ActiveCfg = Debug|x64 40 | {AB0153F1-D5E4-45E0-9308-5EF32974E9A1}.Debug|x64.Build.0 = Debug|x64 41 | {AB0153F1-D5E4-45E0-9308-5EF32974E9A1}.Release|Any CPU.ActiveCfg = Release|Win32 42 | {AB0153F1-D5E4-45E0-9308-5EF32974E9A1}.Release|ARM.ActiveCfg = Release|Win32 43 | {AB0153F1-D5E4-45E0-9308-5EF32974E9A1}.Release|ARM64.ActiveCfg = Release|Win32 44 | {AB0153F1-D5E4-45E0-9308-5EF32974E9A1}.Release|Win32.ActiveCfg = Release|Win32 45 | {AB0153F1-D5E4-45E0-9308-5EF32974E9A1}.Release|Win32.Build.0 = Release|Win32 46 | {AB0153F1-D5E4-45E0-9308-5EF32974E9A1}.Release|x64.ActiveCfg = Release|x64 47 | {AB0153F1-D5E4-45E0-9308-5EF32974E9A1}.Release|x64.Build.0 = Release|x64 48 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Debug|ARM.ActiveCfg = Debug|Any CPU 51 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Debug|ARM.Build.0 = Debug|Any CPU 52 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Debug|ARM64.ActiveCfg = Debug|Any CPU 53 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Debug|ARM64.Build.0 = Debug|Any CPU 54 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Debug|Win32.ActiveCfg = Debug|Any CPU 55 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Debug|Win32.Build.0 = Debug|Any CPU 56 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Debug|x64.ActiveCfg = Debug|Any CPU 57 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Debug|x64.Build.0 = Debug|Any CPU 58 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Release|ARM.ActiveCfg = Release|Any CPU 61 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Release|ARM.Build.0 = Release|Any CPU 62 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Release|ARM64.ActiveCfg = Release|Any CPU 63 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Release|ARM64.Build.0 = Release|Any CPU 64 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Release|Win32.ActiveCfg = Release|Any CPU 65 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Release|Win32.Build.0 = Release|Any CPU 66 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Release|x64.ActiveCfg = Release|Any CPU 67 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D}.Release|x64.Build.0 = Release|Any CPU 68 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 69 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Debug|Any CPU.Build.0 = Debug|Any CPU 70 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Debug|ARM.ActiveCfg = Debug|Any CPU 71 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Debug|ARM.Build.0 = Debug|Any CPU 72 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Debug|ARM64.ActiveCfg = Debug|Any CPU 73 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Debug|ARM64.Build.0 = Debug|Any CPU 74 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Debug|Win32.ActiveCfg = Debug|Any CPU 75 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Debug|Win32.Build.0 = Debug|Any CPU 76 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Debug|x64.ActiveCfg = Debug|Any CPU 77 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Debug|x64.Build.0 = Debug|Any CPU 78 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Release|Any CPU.ActiveCfg = Release|Any CPU 79 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Release|Any CPU.Build.0 = Release|Any CPU 80 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Release|ARM.ActiveCfg = Release|Any CPU 81 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Release|ARM.Build.0 = Release|Any CPU 82 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Release|ARM64.ActiveCfg = Release|Any CPU 83 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Release|ARM64.Build.0 = Release|Any CPU 84 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Release|Win32.ActiveCfg = Release|Any CPU 85 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Release|Win32.Build.0 = Release|Any CPU 86 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Release|x64.ActiveCfg = Release|Any CPU 87 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E}.Release|x64.Build.0 = Release|Any CPU 88 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 89 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Debug|Any CPU.Build.0 = Debug|Any CPU 90 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Debug|ARM.ActiveCfg = Debug|Any CPU 91 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Debug|ARM.Build.0 = Debug|Any CPU 92 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Debug|ARM64.ActiveCfg = Debug|Any CPU 93 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Debug|ARM64.Build.0 = Debug|Any CPU 94 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Debug|Win32.ActiveCfg = Debug|Any CPU 95 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Debug|Win32.Build.0 = Debug|Any CPU 96 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Debug|x64.ActiveCfg = Debug|Any CPU 97 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Debug|x64.Build.0 = Debug|Any CPU 98 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Release|Any CPU.ActiveCfg = Release|Any CPU 99 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Release|Any CPU.Build.0 = Release|Any CPU 100 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Release|ARM.ActiveCfg = Release|Any CPU 101 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Release|ARM.Build.0 = Release|Any CPU 102 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Release|ARM64.ActiveCfg = Release|Any CPU 103 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Release|ARM64.Build.0 = Release|Any CPU 104 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Release|Win32.ActiveCfg = Release|Any CPU 105 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Release|Win32.Build.0 = Release|Any CPU 106 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Release|x64.ActiveCfg = Release|Any CPU 107 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50}.Release|x64.Build.0 = Release|Any CPU 108 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Debug|Any CPU.ActiveCfg = Debug|Win32 109 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Debug|ARM.ActiveCfg = Debug|ARM 110 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Debug|ARM.Build.0 = Debug|ARM 111 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Debug|ARM.Deploy.0 = Debug|ARM 112 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Debug|ARM64.ActiveCfg = Debug|ARM64 113 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Debug|ARM64.Build.0 = Debug|ARM64 114 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Debug|ARM64.Deploy.0 = Debug|ARM64 115 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Debug|Win32.ActiveCfg = Debug|Win32 116 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Debug|Win32.Build.0 = Debug|Win32 117 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Debug|Win32.Deploy.0 = Debug|Win32 118 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Debug|x64.ActiveCfg = Debug|x64 119 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Debug|x64.Build.0 = Debug|x64 120 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Debug|x64.Deploy.0 = Debug|x64 121 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Release|Any CPU.ActiveCfg = Release|Win32 122 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Release|ARM.ActiveCfg = Release|ARM 123 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Release|ARM.Build.0 = Release|ARM 124 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Release|ARM.Deploy.0 = Release|ARM 125 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Release|ARM64.ActiveCfg = Release|ARM64 126 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Release|ARM64.Build.0 = Release|ARM64 127 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Release|ARM64.Deploy.0 = Release|ARM64 128 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Release|Win32.ActiveCfg = Release|Win32 129 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Release|Win32.Build.0 = Release|Win32 130 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Release|Win32.Deploy.0 = Release|Win32 131 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Release|x64.ActiveCfg = Release|x64 132 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Release|x64.Build.0 = Release|x64 133 | {89047C71-E058-482E-A4AF-CCC8CDA77612}.Release|x64.Deploy.0 = Release|x64 134 | EndGlobalSection 135 | GlobalSection(SolutionProperties) = preSolution 136 | HideSolutionNode = FALSE 137 | EndGlobalSection 138 | GlobalSection(NestedProjects) = preSolution 139 | {AB0153F1-D5E4-45E0-9308-5EF32974E9A1} = {FE789860-CDFE-48E5-AE02-E418DD3A0C6D} 140 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D} = {94944192-75BD-4A9E-B4DB-811E26C3A6E5} 141 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E} = {94944192-75BD-4A9E-B4DB-811E26C3A6E5} 142 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50} = {94944192-75BD-4A9E-B4DB-811E26C3A6E5} 143 | {89047C71-E058-482E-A4AF-CCC8CDA77612} = {FE789860-CDFE-48E5-AE02-E418DD3A0C6D} 144 | EndGlobalSection 145 | EndGlobal 146 | -------------------------------------------------------------------------------- /MiniFSWatcher/EventReader.cs: -------------------------------------------------------------------------------- 1 | using CenterDevice.MiniFSWatcher.Events; 2 | using CenterDevice.MiniFSWatcher.Types; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | 8 | namespace CenterDevice.MiniFSWatcher 9 | { 10 | class EventReader 11 | { 12 | public static List ReadFromBuffer(IntPtr buffer, long bufferSize) 13 | { 14 | var events = new List(); 15 | int offset = 0; 16 | while (offset + Marshal.SizeOf(typeof(LogRecord)) < bufferSize) 17 | { 18 | var recordAddress = IntPtr.Add(buffer, offset); 19 | LogRecord record = ReadRecordFromBuffer(recordAddress); 20 | 21 | ValidateRecordLength(record, bufferSize); 22 | 23 | var stringBytes = record.Length - Marshal.SizeOf(typeof(LogRecord)); 24 | string[] strings = ReadEventStringsFromBuffer(recordAddress, stringBytes); 25 | 26 | if (record.Data.EventType == EventType.Move) 27 | { 28 | events.Add(CreateRenameOrMoveEvent(record, strings)); 29 | } 30 | else 31 | { 32 | events.Add(CreateFileSystemEvent(record, strings)); 33 | } 34 | 35 | offset += record.Length; 36 | } 37 | 38 | return events; 39 | } 40 | 41 | private static void ValidateRecordLength(LogRecord record, long bufferSize) 42 | { 43 | if (record.Length <= 0 || record.Length > bufferSize) 44 | { 45 | throw new Exception("Invalid record length"); 46 | } 47 | } 48 | 49 | private static LogRecord ReadRecordFromBuffer(IntPtr recordAddress) 50 | { 51 | return Marshal.PtrToStructure(recordAddress); 52 | } 53 | 54 | private static string[] ReadEventStringsFromBuffer(IntPtr recordAddress, int stringBytes) 55 | { 56 | var stringOffset = IntPtr.Add(recordAddress, Marshal.SizeOf(typeof(LogRecord))); 57 | 58 | var data = new byte[stringBytes]; 59 | Marshal.Copy(stringOffset, data, 0, stringBytes); 60 | 61 | return Encoding.Unicode.GetString(data).Split('\0'); 62 | } 63 | 64 | private static FileSystemEvent CreateFileSystemEvent(LogRecord record, string[] strings) 65 | { 66 | var fileSystemEvent = new FileSystemEvent() 67 | { 68 | Filename = PathConverter.ReplaceDevicePath(strings[0]), 69 | ProcessId = record.Data.ProcessId, 70 | Type = record.Data.EventType 71 | }; 72 | return fileSystemEvent; 73 | } 74 | 75 | private static FileSystemEvent CreateRenameOrMoveEvent(LogRecord record, string[] strings) 76 | { 77 | var fileSystemEvent = new RenameOrMoveEvent() 78 | { 79 | Filename = PathConverter.ReplaceDevicePath(strings[1]), 80 | OldFilename = PathConverter.ReplaceDevicePath(strings[0]), 81 | ProcessId = record.Data.ProcessId, 82 | Type = record.Data.EventType 83 | }; 84 | return fileSystemEvent; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /MiniFSWatcher/EventWatcher.cs: -------------------------------------------------------------------------------- 1 | using CenterDevice.MiniFSWatcher.Events; 2 | using CenterDevice.MiniFSWatcher.Types; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Runtime.InteropServices; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace CenterDevice.MiniFSWatcher 11 | { 12 | public delegate void FileEventHandler(string name, ulong processId); 13 | public delegate void MoveEventHandler(string name, string oldName, ulong processId); 14 | 15 | public class EventWatcher: IDisposable 16 | { 17 | public readonly DriverVersion Version = new DriverVersion(2,0); 18 | 19 | private const int ALL = 0; 20 | private const long ERROR_NO_MORE_ITEMS = 259L; 21 | private const int BUFFER_SIZE = 4096; 22 | private const string RECYCLE_BIN_PREFIX = "C:\\$RECYCLE.BIN\\"; 23 | private bool disposed = false; 24 | 25 | private readonly TimeSpan eventReadDelay = TimeSpan.FromMilliseconds(100); 26 | private Dictionary postponedEvents = new Dictionary(); 27 | private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); 28 | private FilterConnector connector = new FilterConnector(); 29 | 30 | public bool AggregateEvents { get; set; } 31 | 32 | public FileEventHandler OnChange { get; set; } 33 | public FileEventHandler OnCreate { get; set; } 34 | public FileEventHandler OnDelete { get; set; } 35 | public MoveEventHandler OnRenameOrMove { get; set; } 36 | 37 | public void Connect() 38 | { 39 | connector.Connect(); 40 | 41 | var driverVersion = GetDriverVersion(); 42 | if (driverVersion.Major != Version.Major) 43 | { 44 | connector.Disconnect(); 45 | throw new Exception("Invalid driver version!"); 46 | } 47 | else if (driverVersion.Minor != Version.Minor) 48 | { 49 | Trace.TraceWarning("Driver version differs from client version!"); 50 | } 51 | 52 | Task.Factory.StartNew(ForwardEvents, TaskCreationOptions.LongRunning, cancellationTokenSource.Token); 53 | } 54 | 55 | private void ForwardEvents(object val) 56 | { 57 | while (!cancellationTokenSource.IsCancellationRequested) 58 | { 59 | var events = GetEvents(); 60 | 61 | foreach (var fileEvent in events) 62 | { 63 | HandleFileEvent(fileEvent); 64 | } 65 | 66 | if (events.Count == 0) 67 | { 68 | Task.Delay(eventReadDelay).Wait(); 69 | } 70 | } 71 | } 72 | 73 | public void Disconnect() 74 | { 75 | cancellationTokenSource.Cancel(); 76 | cancellationTokenSource = new CancellationTokenSource(); 77 | connector.Disconnect(); 78 | } 79 | 80 | private void HandleFileEvent(FileSystemEvent fileEvent) 81 | { 82 | if (fileEvent.Type == EventType.Close) 83 | { 84 | FlushPostponedEvents(fileEvent); 85 | } 86 | else if (CanBePostponed(fileEvent) && AggregateEvents) 87 | { 88 | PostponeEventDelivery(fileEvent); 89 | } 90 | else if (!EventShouldBeIgnored(fileEvent)) 91 | { 92 | DeliverEvent(fileEvent, AggregateEvents); 93 | } 94 | } 95 | 96 | private bool EventShouldBeIgnored(FileSystemEvent fileEvent) 97 | { 98 | if (fileEvent is RenameOrMoveEvent) 99 | { 100 | return fileEvent.Filename.StartsWith(RECYCLE_BIN_PREFIX, StringComparison.CurrentCultureIgnoreCase); 101 | } 102 | 103 | return false; 104 | } 105 | 106 | private void DeliverEvent(FileSystemEvent fileEvent, bool postponeDelivery) 107 | { 108 | switch (fileEvent.Type) 109 | { 110 | case EventType.Change: 111 | OnChange?.Invoke(fileEvent.Filename, fileEvent.ProcessId); 112 | break; 113 | case EventType.Create: 114 | OnCreate?.Invoke(fileEvent.Filename, fileEvent.ProcessId); 115 | break; 116 | case EventType.Delete: 117 | OnDelete?.Invoke(fileEvent.Filename, fileEvent.ProcessId); 118 | break; 119 | case EventType.Move: 120 | OnRenameOrMove?.Invoke(fileEvent.Filename, ((RenameOrMoveEvent) fileEvent).OldFilename, fileEvent.ProcessId); 121 | break; 122 | default: 123 | break; 124 | } 125 | } 126 | 127 | private bool CanBePostponed(FileSystemEvent fileEvent) 128 | { 129 | return fileEvent.Type == EventType.Change || fileEvent.Type == EventType.Create; 130 | } 131 | 132 | private void FlushPostponedEvents(FileSystemEvent fileEvent) 133 | { 134 | if (postponedEvents.ContainsKey(fileEvent.Filename)) 135 | { 136 | var postponedEvent = postponedEvents[fileEvent.Filename]; 137 | postponedEvents.Remove(fileEvent.Filename); 138 | DeliverEvent(postponedEvent, false); 139 | } 140 | } 141 | 142 | private void PostponeEventDelivery(FileSystemEvent fileEvent) 143 | { 144 | if (!postponedEvents.ContainsKey(fileEvent.Filename)) 145 | { 146 | postponedEvents.Add(fileEvent.Filename, fileEvent); 147 | } 148 | } 149 | 150 | public void RemoveProcessFilter() 151 | { 152 | WatchProcess(ALL); 153 | } 154 | 155 | public void NotWatchProcess(long processId) 156 | { 157 | WatchProcess(-1 * processId); 158 | } 159 | 160 | public void WatchProcess(long processId) 161 | { 162 | CommandMessage message = new CommandMessage(); 163 | message.Command = MinispyCommand.SetWatchProcess; 164 | connector.Send(message, BitConverter.GetBytes(processId)); 165 | } 166 | 167 | public void NotWatchThread(long threadId) 168 | { 169 | WatchThread(-1 * threadId); 170 | } 171 | 172 | public void WatchThread(long threadId) 173 | { 174 | CommandMessage message = new CommandMessage(); 175 | message.Command = MinispyCommand.SetWatchThread; 176 | connector.Send(message, BitConverter.GetBytes(threadId)); 177 | } 178 | 179 | public void RemoveThreadFilter() 180 | { 181 | WatchThread(ALL); 182 | } 183 | 184 | public void WatchPath(string path) 185 | { 186 | CommandMessage message = new CommandMessage(); 187 | message.Command = MinispyCommand.SetPathFilter; 188 | connector.Send(message, PathConverter.ReplaceDriveLetter(path)); 189 | } 190 | 191 | public DriverVersion GetDriverVersion() 192 | { 193 | CommandMessage message = new CommandMessage(); 194 | message.Command = MinispyCommand.GetMiniSpyVersion; 195 | 196 | IntPtr resultSize; 197 | var buffer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(DriverVersion))); 198 | 199 | HResult hResult = connector.SendAndRead(message, buffer, out resultSize); 200 | Marshal.ThrowExceptionForHR(hResult.Result); 201 | 202 | var driverVersion = Marshal.PtrToStructure(buffer); 203 | Marshal.FreeHGlobal(buffer); 204 | return driverVersion; 205 | } 206 | 207 | public static uint GetCurrentThreadId() 208 | { 209 | return NativeMethods.GetCurrentThreadId(); 210 | } 211 | 212 | public static int GetCurrentProcessId() 213 | { 214 | return Process.GetCurrentProcess().Id; 215 | } 216 | 217 | private List GetEvents() 218 | { 219 | CommandMessage message = new CommandMessage(); 220 | message.Command = MinispyCommand.GetMiniSpyLog; 221 | var buffer = Marshal.AllocHGlobal(BUFFER_SIZE); 222 | 223 | try 224 | { 225 | return GetFileSystemEvents(message, buffer); 226 | } 227 | finally 228 | { 229 | Marshal.FreeHGlobal(buffer); 230 | } 231 | } 232 | 233 | private List GetFileSystemEvents(CommandMessage message, IntPtr buffer) 234 | { 235 | IntPtr resultSize; 236 | HResult hResult = connector.SendAndRead(message, buffer, out resultSize); 237 | 238 | if (hResult.IsError) 239 | { 240 | if (hResult.Code != ERROR_NO_MORE_ITEMS) 241 | { 242 | Marshal.ThrowExceptionForHR(hResult.Result); 243 | } 244 | 245 | return new List(); 246 | } 247 | else 248 | { 249 | return EventReader.ReadFromBuffer(buffer, resultSize.ToInt64()); 250 | } 251 | } 252 | 253 | public void Dispose() 254 | { 255 | Dispose(true); 256 | GC.SuppressFinalize(this); 257 | } 258 | 259 | protected virtual void Dispose(bool disposing) 260 | { 261 | if (disposed) return; 262 | 263 | if (disposing) 264 | { 265 | cancellationTokenSource.Dispose(); 266 | } 267 | 268 | disposed = true; 269 | } 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /MiniFSWatcher/Events/FileSystemEvent.cs: -------------------------------------------------------------------------------- 1 | using CenterDevice.MiniFSWatcher.Types; 2 | 3 | namespace CenterDevice.MiniFSWatcher.Events 4 | { 5 | public class FileSystemEvent 6 | { 7 | public EventType Type { get; internal set; } 8 | public string Filename { get; internal set; } 9 | public ulong ProcessId { get; internal set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MiniFSWatcher/Events/RenameOrMoveEvent.cs: -------------------------------------------------------------------------------- 1 | namespace CenterDevice.MiniFSWatcher.Events 2 | { 3 | public class RenameOrMoveEvent: FileSystemEvent 4 | { 5 | public string OldFilename { get; internal set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /MiniFSWatcher/FilterConnector.cs: -------------------------------------------------------------------------------- 1 | using CenterDevice.MiniFSWatcher.Types; 2 | using System; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | 6 | namespace CenterDevice.MiniFSWatcher 7 | { 8 | class FilterConnector 9 | { 10 | private const string PORT_NAME = "\\MiniFSWatcherPort"; 11 | 12 | SafePortHandle port; 13 | 14 | public bool Connected 15 | { 16 | get 17 | { 18 | return port != null && !port.IsClosed && !port.IsInvalid; 19 | } 20 | } 21 | 22 | public void Connect() 23 | { 24 | if (!Connected) 25 | { 26 | Marshal.ThrowExceptionForHR(NativeMethods.FilterConnectCommunicationPort(PORT_NAME, 0, IntPtr.Zero, 0, IntPtr.Zero, out port)); 27 | } 28 | } 29 | 30 | public void Disconnect() 31 | { 32 | VerifyConnected(); 33 | port.Dispose(); 34 | port = null; 35 | } 36 | 37 | public void Send(CommandMessage message) 38 | { 39 | VerifyConnected(); 40 | 41 | var size = Marshal.SizeOf(message); 42 | IntPtr pnt = Marshal.AllocHGlobal(size); 43 | 44 | try 45 | { 46 | IntPtr resultSize; 47 | Marshal.StructureToPtr(message, pnt, false); 48 | Marshal.ThrowExceptionForHR(NativeMethods.FilterSendMessage(port, pnt, size, IntPtr.Zero, 0, out resultSize)); 49 | } 50 | finally 51 | { 52 | Marshal.FreeHGlobal(pnt); 53 | } 54 | } 55 | 56 | public void Send(CommandMessage message, string path) 57 | { 58 | Send(message, Encoding.Unicode.GetBytes(path + '\0')); 59 | } 60 | 61 | public void Send(CommandMessage message, byte[] data) 62 | { 63 | VerifyConnected(); 64 | 65 | var size = Marshal.SizeOf(message) + data.Length; 66 | IntPtr pnt = Marshal.AllocHGlobal(size); 67 | 68 | try 69 | { 70 | IntPtr resultSize; 71 | Marshal.StructureToPtr(message, pnt, false); 72 | IntPtr address = IntPtr.Add(pnt, Marshal.SizeOf(typeof(CommandMessage))); 73 | Marshal.Copy(data, 0, address, data.Length); 74 | Marshal.ThrowExceptionForHR(NativeMethods.FilterSendMessage(port, pnt, size, IntPtr.Zero, 0, out resultSize)); 75 | } 76 | finally 77 | { 78 | Marshal.FreeHGlobal(pnt); 79 | } 80 | } 81 | 82 | public HResult SendAndRead(CommandMessage message, IntPtr buffer, out IntPtr resultSize) 83 | { 84 | VerifyConnected(); 85 | 86 | var size = Marshal.SizeOf(message); 87 | IntPtr command = Marshal.AllocHGlobal(size); 88 | 89 | try 90 | { 91 | Marshal.StructureToPtr(message, command, false); 92 | return new HResult(NativeMethods.FilterSendMessage(port, command, size, buffer, 4069, out resultSize)); 93 | } 94 | finally 95 | { 96 | Marshal.FreeHGlobal(command); 97 | } 98 | } 99 | 100 | private void VerifyConnected() 101 | { 102 | if (!Connected) 103 | { 104 | throw new Exception("You need to connect first"); 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /MiniFSWatcher/MiniFSWatcher.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {01BFC4EE-8FD7-44CE-A00E-DA542116F27D} 8 | Library 9 | Properties 10 | CenterDevice.MiniFSWatcher 11 | MiniFSWatcher 12 | v4.5.2 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | AnyCPU 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 76 | -------------------------------------------------------------------------------- /MiniFSWatcher/MiniFSWatcher.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MiniFSWatcher 5 | 0.1.4 6 | Mini FileSystem Watcher 7 | CenterDevice GmbH 8 | https://opensource.org/licenses/MS-PL 9 | https://github.com/CenterDevice/MiniFSWatcher 10 | https://github.com/CenterDevice/MiniFSWatcher/raw/master/Assets/cd_logo.png 11 | false 12 | 13 | A simple alternative for the default FileSystemWatcher class. 14 | 15 | First public release 16 | Copyright 2016 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /MiniFSWatcher/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Text; 4 | 5 | namespace CenterDevice.MiniFSWatcher 6 | { 7 | public class NativeMethods 8 | { 9 | 10 | [DllImport("FltLib.dll", SetLastError = true)] 11 | internal extern static int FilterConnectCommunicationPort([MarshalAs(UnmanagedType.LPWStr)] string lpPortName, 12 | uint dwOptions, 13 | IntPtr lpContext, 14 | uint dwSizeOfContext, 15 | IntPtr lpSecurityAttributes, 16 | out SafePortHandle hPort 17 | ); 18 | 19 | [DllImport("FltLib.dll", SetLastError = true)] 20 | internal extern static int FilterSendMessage(SafePortHandle port, 21 | IntPtr lpInBuffer, 22 | int dwInBufferSize, 23 | IntPtr lpOutBuffer, 24 | int dwOutBufferSize, 25 | out IntPtr lpBytesReturned); 26 | 27 | [DllImport("Kernel32")] 28 | internal extern static bool CloseHandle(IntPtr handle); 29 | 30 | [DllImport("kernel32.dll")] 31 | internal static extern int QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax); 32 | 33 | [DllImport("kernel32.dll")] 34 | internal static extern uint GetCurrentThreadId(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MiniFSWatcher/PathConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | 6 | namespace CenterDevice.MiniFSWatcher 7 | { 8 | class PathConverter 9 | { 10 | private static Dictionary driveToDevice = new Dictionary(); 11 | 12 | static PathConverter() 13 | { 14 | foreach (DriveInfo driveInfo in DriveInfo.GetDrives()) 15 | { 16 | var drive = driveInfo.Name.TrimEnd(Path.DirectorySeparatorChar); 17 | var device = GetDevicePath(drive); 18 | 19 | driveToDevice[drive] = device; 20 | } 21 | } 22 | 23 | public static string ReplaceDriveLetter(string path) 24 | { 25 | foreach(var entry in driveToDevice) 26 | { 27 | if (path.StartsWith(entry.Key)) 28 | { 29 | return path.Replace(entry.Key, entry.Value); 30 | } 31 | } 32 | 33 | return path; 34 | } 35 | 36 | public static string ReplaceDevicePath(string path) 37 | { 38 | foreach (var entry in driveToDevice) 39 | { 40 | if (path.StartsWith(entry.Value)) 41 | { 42 | return path.Replace(entry.Value, entry.Key); 43 | } 44 | } 45 | 46 | return path; 47 | } 48 | 49 | public static string GetDevicePath(string label) 50 | { 51 | var builder = new StringBuilder(64); 52 | Marshal.ThrowExceptionForHR(NativeMethods.QueryDosDevice(label, builder, builder.Capacity)); 53 | return builder.ToString(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /MiniFSWatcher/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("minispySharp")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("minispySharp")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("01bfc4ee-8fd7-44ce-a00e-da542116f27d")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /MiniFSWatcher/SafePortHandle.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32.SafeHandles; 2 | using System.Runtime.ConstrainedExecution; 3 | using System.Security.Permissions; 4 | 5 | namespace CenterDevice.MiniFSWatcher 6 | { 7 | [SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)] 8 | [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)] 9 | internal class SafePortHandle : SafeHandleZeroOrMinusOneIsInvalid 10 | { 11 | private SafePortHandle() : base(true) 12 | { 13 | } 14 | 15 | [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] 16 | override protected bool ReleaseHandle() 17 | { 18 | return NativeMethods.CloseHandle(handle); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /MiniFSWatcher/Types/CommandMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CenterDevice.MiniFSWatcher.Types 4 | { 5 | [StructLayout(LayoutKind.Sequential)] 6 | struct CommandMessage 7 | { 8 | public MinispyCommand Command; 9 | public int Reserved; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MiniFSWatcher/Types/DriverVersion.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CenterDevice.MiniFSWatcher.Types 4 | { 5 | [StructLayout(LayoutKind.Sequential)] 6 | public struct DriverVersion 7 | { 8 | public ushort Major; 9 | public ushort Minor; 10 | 11 | public DriverVersion(ushort major, ushort minor) 12 | { 13 | this.Major = major; 14 | this.Minor = minor; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /MiniFSWatcher/Types/EventType.cs: -------------------------------------------------------------------------------- 1 | namespace CenterDevice.MiniFSWatcher.Types 2 | { 3 | public enum EventType: int 4 | { 5 | Unknown = 0, 6 | Create = 1, 7 | Delete = 2, 8 | Change = 3, 9 | Move = 4, 10 | Close = 5 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MiniFSWatcher/Types/HResult.cs: -------------------------------------------------------------------------------- 1 | namespace CenterDevice.MiniFSWatcher.Types 2 | { 3 | class HResult 4 | { 5 | private const int SEVERITY_ERROR = 1; 6 | private const int FACILITY_WIN32 = 7; 7 | 8 | private readonly int result; 9 | 10 | public HResult(int result) 11 | { 12 | this.result = result; 13 | } 14 | 15 | public int Result 16 | { 17 | get 18 | { 19 | return result; 20 | } 21 | } 22 | 23 | public bool IsError 24 | { 25 | get 26 | { 27 | return (((ulong)(result)) >> 31 == SEVERITY_ERROR); 28 | } 29 | } 30 | 31 | public int Code 32 | { 33 | get 34 | { 35 | return result <= 0 ? result : (int) ((result & 0x0000FFFF) | FACILITY_WIN32 << 16 | 0x80000000); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /MiniFSWatcher/Types/LogRecord.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CenterDevice.MiniFSWatcher.Types 4 | { 5 | [StructLayout(LayoutKind.Sequential)] 6 | struct LogRecord 7 | { 8 | public int Length; // Length of log record. This Does not include 9 | public int SequenceNumber; // space used by other members of RECORD_LIST 10 | 11 | public int RecordType; // The type of log record this is. 12 | int Reserved; // For alignment on IA64 13 | 14 | public RecordData Data; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /MiniFSWatcher/Types/MinispyCommand.cs: -------------------------------------------------------------------------------- 1 | namespace CenterDevice.MiniFSWatcher.Types 2 | { 3 | public enum MinispyCommand 4 | { 5 | GetMiniSpyLog, 6 | GetMiniSpyVersion, 7 | SetWatchProcess, 8 | SetWatchThread, 9 | SetPathFilter 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MiniFSWatcher/Types/RecordData.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CenterDevice.MiniFSWatcher.Types 4 | { 5 | [StructLayout(LayoutKind.Sequential)] 6 | struct RecordData 7 | { 8 | public ulong OriginatingTime; 9 | public ulong CompletionTime; 10 | 11 | public EventType EventType; 12 | 13 | public int Flags; 14 | public ulong ProcessId; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /MiniFSWatcher/runsdvui.cmd: -------------------------------------------------------------------------------- 1 | cd /d "C:\Users\jan\Documents\DriverSamples\filesys\miniFilter\minispy\MiniFSWatcher" &msbuild "MiniFSWatcher.csproj" /t:sdvViewer /p:configuration="Debug" /p:platform=Any CPU 2 | exit %errorlevel% -------------------------------------------------------------------------------- /MiniFSWatcherApp/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /MiniFSWatcherApp/MiniFSWatcherApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {4FF23EB9-728F-4293-8F8B-F86FF391D36E} 8 | Exe 9 | Properties 10 | minispyApp 11 | minispyApp 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {01bfc4ee-8fd7-44ce-a00e-da542116f27d} 55 | MiniFSWatcher 56 | 57 | 58 | 59 | 66 | -------------------------------------------------------------------------------- /MiniFSWatcherApp/Program.cs: -------------------------------------------------------------------------------- 1 | using CenterDevice.MiniFSWatcher; 2 | using System; 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | 6 | namespace CenterDevice.MiniFSWatcherApp 7 | { 8 | class Program 9 | { 10 | static void Main(string[] args) 11 | { 12 | var path = (args.Length > 0) ? args[0] : AppDomain.CurrentDomain.BaseDirectory; 13 | 14 | Console.WriteLine("FileSystemEventFilter Demo Application"); 15 | Console.WriteLine("======================================\n"); 16 | Console.WriteLine("Listing events in \"" + path + "\""); 17 | Console.WriteLine("To watch a different path, run: " + AppDomain.CurrentDomain.FriendlyName + " \n"); 18 | 19 | //CaptureEventsUsingDefaultWatcher(path); 20 | CaptureEventsUsingFilter(path); 21 | 22 | Console.WriteLine("Press to stop exit..."); 23 | do 24 | { 25 | while (!Console.KeyAvailable) 26 | { 27 | Task.Delay(200).Wait(); 28 | } 29 | } while (Console.ReadKey(true).Key != ConsoleKey.Escape); 30 | 31 | Console.WriteLine("Done"); 32 | } 33 | 34 | private static void CaptureEventsUsingDefaultWatcher(string path) 35 | { 36 | Console.WriteLine("Capturing events using default file system watcher..."); 37 | 38 | var watcher = new FileSystemWatcher(path); 39 | watcher.Changed += (sender, e) => 40 | { 41 | Console.WriteLine("Changed: " + e.FullPath); 42 | }; 43 | watcher.Created += (sender, e) => 44 | { 45 | Console.WriteLine("Created: " + e.FullPath); 46 | }; 47 | watcher.Renamed += (sender, e) => 48 | { 49 | Console.WriteLine("Renamed: " + e.OldFullPath + " -> " + e.FullPath); 50 | }; 51 | watcher.Deleted += (sender, e) => 52 | { 53 | Console.WriteLine("Deleted: " + e.FullPath); 54 | }; 55 | 56 | watcher.EnableRaisingEvents = true; 57 | } 58 | 59 | private static void CaptureEventsUsingFilter(string path) 60 | { 61 | var filter = new EventWatcher(); 62 | 63 | 64 | filter.Connect(); 65 | filter.AggregateEvents = true; 66 | filter.NotWatchProcess(EventWatcher.GetCurrentProcessId()); 67 | 68 | filter.WatchPath(path + "*"); 69 | 70 | filter.OnChange += (name, process) => 71 | { 72 | Console.WriteLine("Changed: " + name); 73 | }; 74 | filter.OnCreate += (name, process) => 75 | { 76 | Console.WriteLine("Created: " + name); 77 | }; 78 | filter.OnDelete += (name, process) => 79 | { 80 | Console.WriteLine("Deleted: " + name); 81 | }; 82 | filter.OnRenameOrMove += (name, oldName, process) => 83 | { 84 | Console.WriteLine("Moved: " + oldName + " -> " + name); 85 | }; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /MiniFSWatcherApp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("minispyApp")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("minispyApp")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("4ff23eb9-728f-4293-8f8b-f86ff391d36e")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /MiniFSWatcherDriver/RegistrationData.c: -------------------------------------------------------------------------------- 1 | /*++ 2 | 3 | Copyright (c) 1989-2002 Microsoft Corporation 4 | 5 | Module Name: 6 | 7 | RegistrationData.c 8 | 9 | Abstract: 10 | 11 | This filters registration information. Note that this is in a unique file 12 | so it could be set into the INIT section. 13 | 14 | Environment: 15 | 16 | Kernel mode 17 | 18 | --*/ 19 | 20 | #include "mspyKern.h" 21 | 22 | //--------------------------------------------------------------------------- 23 | // Registration information for FLTMGR. 24 | //--------------------------------------------------------------------------- 25 | 26 | // 27 | // Tells the compiler to define all following DATA and CONSTANT DATA to 28 | // be placed in the INIT segment. 29 | // 30 | 31 | #ifdef ALLOC_DATA_PRAGMA 32 | #pragma data_seg("INIT") 33 | #pragma const_seg("INIT") 34 | #endif 35 | 36 | CONST FLT_OPERATION_REGISTRATION Callbacks[] = { 37 | { IRP_MJ_CREATE, 38 | 0, 39 | SpyPreOperationCallback, 40 | SpyPostOperationCallback }, 41 | 42 | { IRP_MJ_WRITE, 43 | 0, 44 | SpyPreOperationCallback, 45 | SpyPostOperationCallback }, 46 | 47 | { IRP_MJ_SET_INFORMATION, 48 | 0, 49 | SpyPreOperationCallback, 50 | SpyPostOperationCallback }, 51 | 52 | { IRP_MJ_CLOSE, 53 | 0, 54 | SpyPreOperationCallback, 55 | SpyPostOperationCallback }, 56 | 57 | { IRP_MJ_OPERATION_END } 58 | }; 59 | 60 | // 61 | // This defines what we want to filter with FltMgr 62 | // 63 | 64 | CONST FLT_REGISTRATION FilterRegistration = { 65 | 66 | sizeof(FLT_REGISTRATION), // Size 67 | FLT_REGISTRATION_VERSION, // Version 68 | #if MINISPY_WIN8 69 | FLTFL_REGISTRATION_SUPPORT_NPFS_MSFS, // Flags 70 | #else 71 | 0, // Flags 72 | #endif // MINISPY_WIN8 73 | 74 | NULL, // Context 75 | Callbacks, // Operation callbacks 76 | 77 | SpyFilterUnload, // FilterUnload 78 | 79 | NULL, // InstanceSetup 80 | SpyQueryTeardown, // InstanceQueryTeardown 81 | NULL, // InstanceTeardownStart 82 | NULL, // InstanceTeardownComplete 83 | 84 | NULL, // GenerateFileName 85 | NULL, // GenerateDestinationFileName 86 | NULL // NormalizeNameComponent 87 | }; 88 | 89 | 90 | // 91 | // Tells the compiler to restore the given section types back to their previous 92 | // section definition. 93 | // 94 | 95 | #ifdef ALLOC_DATA_PRAGMA 96 | #pragma data_seg() 97 | #pragma const_seg() 98 | #endif 99 | 100 | -------------------------------------------------------------------------------- /MiniFSWatcherDriver/minifswatcher.inf: -------------------------------------------------------------------------------- 1 | ;;; 2 | ;;; MiniFSWatcher 3 | ;;; 4 | ;;; 5 | ;;; Copyright (c) 2001, Microsoft Corporation 6 | ;;; 7 | 8 | [Version] 9 | Signature = "$Windows NT$" 10 | Class = "ActivityMonitor" ;This is determined by the work this filter driver does 11 | ClassGuid = {b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2} ;This value is determined by the Class 12 | Provider = %ProviderString% 13 | DriverVer = 06/16/2007,1.0.0.0 14 | CatalogFile = minifswatcher.cat 15 | 16 | 17 | [DestinationDirs] 18 | DefaultDestDir = 12 19 | MiniFSWatcher.DriverFiles = 12 ;%windir%\system32\drivers 20 | 21 | ;; 22 | ;; Default install sections 23 | ;; 24 | 25 | [DefaultInstall] 26 | OptionDesc = %ServiceDescription% 27 | CopyFiles = MiniFSWatcher.DriverFiles 28 | 29 | [DefaultInstall.Services] 30 | AddService = %ServiceName%,,MiniFSWatcher.Service 31 | 32 | ;; 33 | ;; Default uninstall sections 34 | ;; 35 | 36 | [DefaultUninstall] 37 | DelFiles = MiniFSWatcher.DriverFiles 38 | 39 | [DefaultUninstall.Services] 40 | DelService = %ServiceName%,0x200 ;Ensure service is stopped before deleting 41 | 42 | ; 43 | ; Services Section 44 | ; 45 | 46 | [MiniFSWatcher.Service] 47 | DisplayName = %ServiceName% 48 | Description = %ServiceDescription% 49 | ServiceBinary = %12%\%DriverName%.sys ;%windir%\system32\drivers\ 50 | Dependencies = FltMgr 51 | ServiceType = 2 ;SERVICE_FILE_SYSTEM_DRIVER 52 | StartType = 2 ;SERVICE_AUTO_START 53 | ErrorControl = 1 ;SERVICE_ERROR_NORMAL 54 | LoadOrderGroup = "FSFilter Activity Monitor" 55 | AddReg = MiniFSWatcher.AddRegistry 56 | 57 | ; 58 | ; Registry Modifications 59 | ; 60 | 61 | [MiniFSWatcher.AddRegistry] 62 | HKR,,"SupportedFeatures",0x00010001,0x3 63 | HKR,"Instances","DefaultInstance",0x00000000,%DefaultInstance% 64 | HKR,"Instances\"%Instance.Name%,"Altitude",0x00000000,%Instance.Altitude% 65 | HKR,"Instances\"%Instance.Name%,"Flags",0x00010001,%Instance.Flags% 66 | 67 | ; 68 | ; Copy Files 69 | ; 70 | 71 | [MiniFSWatcher.DriverFiles] 72 | %DriverName%.sys 73 | 74 | [SourceDisksFiles] 75 | minifswatcher.sys = 1,, 76 | 77 | [SourceDisksNames] 78 | 1 = %DiskId1%,,, 79 | 80 | ;; 81 | ;; String Section 82 | ;; 83 | 84 | [Strings] 85 | ProviderString = "TODO-Set-Provider" 86 | ServiceDescription = "MiniFSWatcher mini-filter driver" 87 | ServiceName = "MiniFSWatcher" 88 | DriverName = "minifswatcher" 89 | DiskId1 = "MiniFSWatcher Device Installation Disk" 90 | 91 | ;Instances specific information. 92 | DefaultInstance = "MiniFSWatcher File System Watcher" 93 | Instance.Name = "MiniFSWatcher File System Watcher" 94 | Instance.Altitude = "385100" 95 | Instance.Flags = 0x0 96 | -------------------------------------------------------------------------------- /MiniFSWatcherDriver/minispy.c: -------------------------------------------------------------------------------- 1 | /*++ 2 | 3 | Copyright (c) 1989-2002 Microsoft Corporation 4 | 5 | Module Name: 6 | 7 | MiniSpy.c 8 | 9 | Abstract: 10 | 11 | This is the main module for the MiniSpy mini-filter. 12 | 13 | Environment: 14 | 15 | Kernel mode 16 | 17 | --*/ 18 | 19 | #include "mspyKern.h" 20 | #include 21 | #include 22 | #include 23 | 24 | // 25 | // Global variables 26 | // 27 | 28 | MINIFSWATCHER_DATA MiniFSWatcherData; 29 | NTSTATUS StatusToBreakOn = 0; 30 | 31 | //--------------------------------------------------------------------------- 32 | // Function prototypes 33 | //--------------------------------------------------------------------------- 34 | DRIVER_INITIALIZE DriverEntry; 35 | NTSTATUS 36 | DriverEntry ( 37 | _In_ PDRIVER_OBJECT DriverObject, 38 | _In_ PUNICODE_STRING RegistryPath 39 | ); 40 | 41 | 42 | NTSTATUS 43 | SpyMessage( 44 | _In_ PVOID ConnectionCookie, 45 | _In_reads_bytes_opt_(InputBufferSize) PVOID InputBuffer, 46 | _In_ ULONG InputBufferSize, 47 | _Out_writes_bytes_to_opt_(OutputBufferSize, *ReturnOutputBufferLength) PVOID OutputBuffer, 48 | _In_ ULONG OutputBufferSize, 49 | _Out_ PULONG ReturnOutputBufferLength 50 | ); 51 | 52 | NTSTATUS 53 | SpyConnect( 54 | _In_ PFLT_PORT ClientPort, 55 | _In_ PVOID ServerPortCookie, 56 | _In_reads_bytes_(SizeOfContext) PVOID ConnectionContext, 57 | _In_ ULONG SizeOfContext, 58 | _Flt_ConnectionCookie_Outptr_ PVOID *ConnectionCookie 59 | ); 60 | 61 | VOID 62 | SpyDisconnect( 63 | _In_opt_ PVOID ConnectionCookie 64 | ); 65 | 66 | //--------------------------------------------------------------------------- 67 | // Assign text sections for each routine. 68 | //--------------------------------------------------------------------------- 69 | 70 | #ifdef ALLOC_PRAGMA 71 | #pragma alloc_text(INIT, DriverEntry) 72 | #pragma alloc_text(PAGE, SpyFilterUnload) 73 | #pragma alloc_text(PAGE, SpyQueryTeardown) 74 | #pragma alloc_text(PAGE, SpyConnect) 75 | #pragma alloc_text(PAGE, SpyDisconnect) 76 | #pragma alloc_text(PAGE, SpyMessage) 77 | #endif 78 | 79 | 80 | #define SetFlagInterlocked(_ptrFlags,_flagToSet) \ 81 | ((VOID)InterlockedOr(((volatile LONG *)(_ptrFlags)),_flagToSet)) 82 | 83 | //--------------------------------------------------------------------------- 84 | // ROUTINES 85 | //--------------------------------------------------------------------------- 86 | 87 | NTSTATUS 88 | DriverEntry ( 89 | _In_ PDRIVER_OBJECT DriverObject, 90 | _In_ PUNICODE_STRING RegistryPath 91 | ) 92 | /*++ 93 | 94 | Routine Description: 95 | 96 | This routine is called when a driver first loads. Its purpose is to 97 | initialize global state and then register with FltMgr to start filtering. 98 | 99 | Arguments: 100 | 101 | DriverObject - Pointer to driver object created by the system to 102 | represent this driver. 103 | RegistryPath - Unicode string identifying where the parameters for this 104 | driver are located in the registry. 105 | 106 | Return Value: 107 | 108 | Status of the operation. 109 | 110 | --*/ 111 | { 112 | PSECURITY_DESCRIPTOR sd; 113 | OBJECT_ATTRIBUTES oa; 114 | UNICODE_STRING uniString; 115 | NTSTATUS status = STATUS_SUCCESS; 116 | 117 | try { 118 | 119 | // 120 | // Initialize global data structures. 121 | // 122 | 123 | MiniFSWatcherData.WatchProcess = 0; 124 | MiniFSWatcherData.WatchThread = 0; 125 | MiniFSWatcherData.LogSequenceNumber = 0; 126 | MiniFSWatcherData.MaxRecordsToAllocate = DEFAULT_MAX_RECORDS_TO_ALLOCATE; 127 | MiniFSWatcherData.RecordsAllocated = 0; 128 | MiniFSWatcherData.NameQueryMethod = DEFAULT_NAME_QUERY_METHOD; 129 | MiniFSWatcherData.ClientPort = NULL; 130 | MiniFSWatcherData.WatchPathInUse = FALSE; 131 | 132 | RtlInitUnicodeString(&MiniFSWatcherData.WatchPath, NULL); 133 | 134 | MiniFSWatcherData.DriverObject = DriverObject; 135 | 136 | InitializeListHead( &MiniFSWatcherData.OutputBufferList ); 137 | KeInitializeSpinLock( &MiniFSWatcherData.OutputBufferLock ); 138 | 139 | ExInitializeNPagedLookasideList( &MiniFSWatcherData.FreeBufferList, 140 | NULL, 141 | NULL, 142 | POOL_NX_ALLOCATION, 143 | RECORD_SIZE, 144 | SPY_TAG, 145 | 0 ); 146 | 147 | // 148 | // Read the custom parameters for MiniSpy from the registry 149 | // 150 | 151 | SpyReadDriverParameters(RegistryPath); 152 | 153 | // 154 | // Now that our global configuration is complete, register with FltMgr. 155 | // 156 | 157 | status = FltRegisterFilter( DriverObject, 158 | &FilterRegistration, 159 | &MiniFSWatcherData.Filter ); 160 | 161 | if (!NT_SUCCESS( status )) { 162 | 163 | leave; 164 | } 165 | 166 | status = FltBuildDefaultSecurityDescriptor( &sd, 167 | FLT_PORT_ALL_ACCESS ); 168 | 169 | 170 | 171 | if (!NT_SUCCESS( status )) { 172 | leave; 173 | } 174 | 175 | #pragma warning ( push ) 176 | #pragma warning ( disable:6248 ) // PREFAST -- Yes, we really want no DACL 177 | 178 | RtlSetDaclSecurityDescriptor(sd, TRUE, NULL, FALSE); 179 | 180 | #pragma warning ( pop ) 181 | 182 | RtlInitUnicodeString( &uniString, MINIFSWATCHER_PORT_NAME ); 183 | 184 | InitializeObjectAttributes( &oa, 185 | &uniString, 186 | OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, 187 | NULL, 188 | sd ); 189 | 190 | status = FltCreateCommunicationPort( MiniFSWatcherData.Filter, 191 | &MiniFSWatcherData.ServerPort, 192 | &oa, 193 | NULL, 194 | SpyConnect, 195 | SpyDisconnect, 196 | SpyMessage, 197 | 1 ); 198 | 199 | FltFreeSecurityDescriptor( sd ); 200 | 201 | if (!NT_SUCCESS( status )) { 202 | leave; 203 | } 204 | 205 | // 206 | // We are now ready to start filtering 207 | // 208 | 209 | status = FltStartFiltering( MiniFSWatcherData.Filter ); 210 | 211 | } finally { 212 | 213 | if (!NT_SUCCESS( status ) ) { 214 | 215 | if (NULL != MiniFSWatcherData.ServerPort) { 216 | FltCloseCommunicationPort( MiniFSWatcherData.ServerPort ); 217 | } 218 | 219 | if (NULL != MiniFSWatcherData.Filter) { 220 | FltUnregisterFilter( MiniFSWatcherData.Filter ); 221 | } 222 | 223 | ExDeleteNPagedLookasideList( &MiniFSWatcherData.FreeBufferList ); 224 | } 225 | } 226 | 227 | DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "MiniFSWatcher %i.%i loaded successfully\n", MINIFSWATCHER_MAJ_VERSION, MINIFSWATCHER_MIN_VERSION); 228 | 229 | return status; 230 | } 231 | 232 | NTSTATUS 233 | SpyConnect( 234 | _In_ PFLT_PORT ClientPort, 235 | _In_ PVOID ServerPortCookie, 236 | _In_reads_bytes_(SizeOfContext) PVOID ConnectionContext, 237 | _In_ ULONG SizeOfContext, 238 | _Flt_ConnectionCookie_Outptr_ PVOID *ConnectionCookie 239 | ) 240 | /*++ 241 | 242 | Routine Description 243 | 244 | This is called when user-mode connects to the server 245 | port - to establish a connection 246 | 247 | Arguments 248 | 249 | ClientPort - This is the pointer to the client port that 250 | will be used to send messages from the filter. 251 | ServerPortCookie - unused 252 | ConnectionContext - unused 253 | SizeofContext - unused 254 | ConnectionCookie - unused 255 | 256 | Return Value 257 | 258 | STATUS_SUCCESS - to accept the connection 259 | --*/ 260 | { 261 | 262 | PAGED_CODE(); 263 | 264 | UNREFERENCED_PARAMETER( ServerPortCookie ); 265 | UNREFERENCED_PARAMETER( ConnectionContext ); 266 | UNREFERENCED_PARAMETER( SizeOfContext); 267 | UNREFERENCED_PARAMETER( ConnectionCookie ); 268 | 269 | FLT_ASSERT( MiniFSWatcherData.ClientPort == NULL ); 270 | MiniFSWatcherData.ClientPort = ClientPort; 271 | 272 | DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "Client connected to MiniSpy\n"); 273 | 274 | return STATUS_SUCCESS; 275 | } 276 | 277 | 278 | VOID 279 | SpyDisconnect( 280 | _In_opt_ PVOID ConnectionCookie 281 | ) 282 | /*++ 283 | 284 | Routine Description 285 | 286 | This is called when the connection is torn-down. We use it to close our handle to the connection 287 | 288 | Arguments 289 | 290 | ConnectionCookie - unused 291 | 292 | Return value 293 | 294 | None 295 | --*/ 296 | { 297 | 298 | PAGED_CODE(); 299 | 300 | UNREFERENCED_PARAMETER( ConnectionCookie ); 301 | 302 | // 303 | // Close our handle 304 | // 305 | 306 | FltCloseClientPort( MiniFSWatcherData.Filter, &MiniFSWatcherData.ClientPort ); 307 | MiniFSWatcherData.ClientPort = NULL; 308 | MiniFSWatcherData.WatchProcess = 0; 309 | MiniFSWatcherData.WatchThread = 0; 310 | SpyUpdateWatchedPath(NULL); 311 | DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "Client disconnected from MiniSpy\n"); 312 | } 313 | 314 | NTSTATUS 315 | SpyFilterUnload ( 316 | _In_ FLT_FILTER_UNLOAD_FLAGS Flags 317 | ) 318 | /*++ 319 | 320 | Routine Description: 321 | 322 | This is called when a request has been made to unload the filter. Unload 323 | requests from the Operation System (ex: "sc stop minispy" can not be 324 | failed. Other unload requests may be failed. 325 | 326 | You can disallow OS unload request by setting the 327 | FLTREGFL_DO_NOT_SUPPORT_SERVICE_STOP flag in the FLT_REGISTARTION 328 | structure. 329 | 330 | Arguments: 331 | 332 | Flags - Flags pertinent to this operation 333 | 334 | Return Value: 335 | 336 | Always success 337 | 338 | --*/ 339 | { 340 | UNREFERENCED_PARAMETER( Flags ); 341 | 342 | PAGED_CODE(); 343 | 344 | // 345 | // Close the server port. This will stop new connections. 346 | // 347 | 348 | FltCloseCommunicationPort( MiniFSWatcherData.ServerPort ); 349 | 350 | FltUnregisterFilter( MiniFSWatcherData.Filter ); 351 | 352 | SpyEmptyOutputBufferList(); 353 | ExDeleteNPagedLookasideList( &MiniFSWatcherData.FreeBufferList ); 354 | 355 | return STATUS_SUCCESS; 356 | } 357 | 358 | 359 | NTSTATUS 360 | SpyQueryTeardown ( 361 | _In_ PCFLT_RELATED_OBJECTS FltObjects, 362 | _In_ FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags 363 | ) 364 | /*++ 365 | 366 | Routine Description: 367 | 368 | This allows our filter to be manually detached from a volume. 369 | 370 | Arguments: 371 | 372 | FltObjects - Contains pointer to relevant objects for this operation. 373 | Note that the FileObject field will always be NULL. 374 | 375 | Flags - Flags pertinent to this operation 376 | 377 | Return Value: 378 | 379 | --*/ 380 | { 381 | UNREFERENCED_PARAMETER( FltObjects ); 382 | UNREFERENCED_PARAMETER( Flags ); 383 | PAGED_CODE(); 384 | return STATUS_SUCCESS; 385 | } 386 | 387 | 388 | NTSTATUS 389 | SpyMessage ( 390 | _In_ PVOID ConnectionCookie, 391 | _In_reads_bytes_opt_(InputBufferSize) PVOID InputBuffer, 392 | _In_ ULONG InputBufferSize, 393 | _Out_writes_bytes_to_opt_(OutputBufferSize,*ReturnOutputBufferLength) PVOID OutputBuffer, 394 | _In_ ULONG OutputBufferSize, 395 | _Out_ PULONG ReturnOutputBufferLength 396 | ) 397 | /*++ 398 | 399 | Routine Description: 400 | 401 | This is called whenever a user mode application wishes to communicate 402 | with this minifilter. 403 | 404 | Arguments: 405 | 406 | ConnectionCookie - unused 407 | 408 | OperationCode - An identifier describing what type of message this 409 | is. These codes are defined by the MiniFilter. 410 | InputBuffer - A buffer containing input data, can be NULL if there 411 | is no input data. 412 | InputBufferSize - The size in bytes of the InputBuffer. 413 | OutputBuffer - A buffer provided by the application that originated 414 | the communication in which to store data to be returned to this 415 | application. 416 | OutputBufferSize - The size in bytes of the OutputBuffer. 417 | ReturnOutputBufferSize - The size in bytes of meaningful data 418 | returned in the OutputBuffer. 419 | 420 | Return Value: 421 | 422 | Returns the status of processing the message. 423 | 424 | --*/ 425 | { 426 | MINIFSWATCHER_COMMAND command; 427 | NTSTATUS status; 428 | ULONG dataLength; 429 | UNICODE_STRING dataString; 430 | 431 | PAGED_CODE(); 432 | 433 | UNREFERENCED_PARAMETER( ConnectionCookie ); 434 | 435 | // 436 | // **** PLEASE READ **** 437 | // 438 | // The INPUT and OUTPUT buffers are raw user mode addresses. The filter 439 | // manager has already done a ProbedForRead (on InputBuffer) and 440 | // ProbedForWrite (on OutputBuffer) which guarentees they are valid 441 | // addresses based on the access (user mode vs. kernel mode). The 442 | // minifilter does not need to do their own probe. 443 | // 444 | // The filter manager is NOT doing any alignment checking on the pointers. 445 | // The minifilter must do this themselves if they care (see below). 446 | // 447 | // The minifilter MUST continue to use a try/except around any access to 448 | // these buffers. 449 | // 450 | 451 | if ((InputBuffer != NULL) && 452 | (InputBufferSize >= (FIELD_OFFSET(COMMAND_MESSAGE,Command) + 453 | sizeof(MINIFSWATCHER_COMMAND)))) { 454 | 455 | try { 456 | 457 | // 458 | // Probe and capture input message: the message is raw user mode 459 | // buffer, so need to protect with exception handler 460 | // 461 | 462 | command = ((PCOMMAND_MESSAGE) InputBuffer)->Command; 463 | dataLength = InputBufferSize - FIELD_OFFSET(COMMAND_MESSAGE, Data); 464 | 465 | } except (SpyExceptionFilter( GetExceptionInformation(), TRUE )) { 466 | 467 | return GetExceptionCode(); 468 | } 469 | 470 | switch (command) { 471 | 472 | case GetMiniSpyLog: 473 | 474 | // 475 | // Return as many log records as can fit into the OutputBuffer 476 | // 477 | 478 | if ((OutputBuffer == NULL) || (OutputBufferSize == 0)) { 479 | 480 | status = STATUS_INVALID_PARAMETER; 481 | break; 482 | } 483 | 484 | // 485 | // We want to validate that the given buffer is POINTER 486 | // aligned. But if this is a 64bit system and we want to 487 | // support 32bit applications we need to be careful with how 488 | // we do the check. Note that the way SpyGetLog is written 489 | // it actually does not care about alignment but we are 490 | // demonstrating how to do this type of check. 491 | // 492 | 493 | #if defined(_WIN64) 494 | 495 | if (IoIs32bitProcess(NULL)) { 496 | 497 | // 498 | // Validate alignment for the 32bit process on a 64bit 499 | // system 500 | // 501 | 502 | if (!IS_ALIGNED(OutputBuffer, sizeof(ULONG))) { 503 | 504 | status = STATUS_DATATYPE_MISALIGNMENT; 505 | break; 506 | } 507 | 508 | } 509 | else { 510 | 511 | #endif 512 | 513 | if (!IS_ALIGNED(OutputBuffer, sizeof(PVOID))) { 514 | 515 | status = STATUS_DATATYPE_MISALIGNMENT; 516 | break; 517 | } 518 | 519 | #if defined(_WIN64) 520 | 521 | } 522 | 523 | #endif 524 | 525 | // 526 | // Get the log record. 527 | // 528 | 529 | status = SpyGetLog(OutputBuffer, 530 | OutputBufferSize, 531 | ReturnOutputBufferLength); 532 | break; 533 | 534 | 535 | case GetMiniSpyVersion: 536 | 537 | // 538 | // Return version of the MiniSpy filter driver. Verify 539 | // we have a valid user buffer including valid 540 | // alignment 541 | // 542 | 543 | if ((OutputBufferSize < sizeof(MINIFSWATCHERVER)) || 544 | (OutputBuffer == NULL)) { 545 | 546 | status = STATUS_INVALID_PARAMETER; 547 | break; 548 | } 549 | 550 | // 551 | // Validate Buffer alignment. If a minifilter cares about 552 | // the alignment value of the buffer pointer they must do 553 | // this check themselves. Note that a try/except will not 554 | // capture alignment faults. 555 | // 556 | 557 | if (!IS_ALIGNED(OutputBuffer, sizeof(ULONG))) { 558 | 559 | status = STATUS_DATATYPE_MISALIGNMENT; 560 | break; 561 | } 562 | 563 | // 564 | // Protect access to raw user-mode output buffer with an 565 | // exception handler 566 | // 567 | 568 | try { 569 | 570 | ((PMINIFSWATCHERVER)OutputBuffer)->Major = MINIFSWATCHER_MAJ_VERSION; 571 | ((PMINIFSWATCHERVER)OutputBuffer)->Minor = MINIFSWATCHER_MIN_VERSION; 572 | 573 | } except(SpyExceptionFilter(GetExceptionInformation(), TRUE)) { 574 | return GetExceptionCode(); 575 | } 576 | 577 | *ReturnOutputBufferLength = sizeof(MINIFSWATCHERVER); 578 | status = STATUS_SUCCESS; 579 | break; 580 | 581 | case SetWatchProcess: 582 | if (dataLength < sizeof(LONGLONG)) 583 | { 584 | status = STATUS_INVALID_PARAMETER; 585 | break; 586 | } 587 | 588 | try { 589 | MiniFSWatcherData.WatchProcess = *((LONGLONG*)((PCOMMAND_MESSAGE)InputBuffer)->Data); 590 | DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "Watching process %li\n", MiniFSWatcherData.WatchProcess); 591 | status = STATUS_SUCCESS; 592 | } except(SpyExceptionFilter(GetExceptionInformation(), TRUE)) { 593 | return GetExceptionCode(); 594 | } 595 | 596 | break; 597 | case SetWatchThread: 598 | if (dataLength < sizeof(LONGLONG)) 599 | { 600 | status = STATUS_INVALID_PARAMETER; 601 | break; 602 | } 603 | 604 | try { 605 | MiniFSWatcherData.WatchThread = *((LONGLONG*)((PCOMMAND_MESSAGE)InputBuffer)->Data); 606 | DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "Watching thread %li\n", MiniFSWatcherData.WatchThread); 607 | status = STATUS_SUCCESS; 608 | } except(SpyExceptionFilter(GetExceptionInformation(), TRUE)) { 609 | return GetExceptionCode(); 610 | } 611 | 612 | break; 613 | case SetPathFilter: 614 | try { 615 | if (dataLength <= sizeof(WCHAR) 616 | || ((PCWSTR)((PCOMMAND_MESSAGE)InputBuffer)->Data)[dataLength / sizeof(WCHAR) - 1] != UNICODE_NULL) 617 | { 618 | status = STATUS_INVALID_PARAMETER; 619 | break; 620 | } 621 | 622 | RtlInitUnicodeString(&dataString, (PCWSTR)((PCOMMAND_MESSAGE)InputBuffer)->Data); 623 | if (SpyUpdateWatchedPath(&dataString)) 624 | { 625 | DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "Watching path %wZ\n", &MiniFSWatcherData.WatchPath); 626 | status = STATUS_SUCCESS; 627 | } 628 | else 629 | { 630 | status = STATUS_OPERATION_IN_PROGRESS; 631 | } 632 | } except(SpyExceptionFilter(GetExceptionInformation(), TRUE)) { 633 | return GetExceptionCode(); 634 | } 635 | 636 | break; 637 | default: 638 | status = STATUS_INVALID_PARAMETER; 639 | break; 640 | } 641 | 642 | } 643 | else { 644 | 645 | status = STATUS_INVALID_PARAMETER; 646 | } 647 | 648 | return status; 649 | } 650 | 651 | 652 | //--------------------------------------------------------------------------- 653 | // Operation filtering routines 654 | //--------------------------------------------------------------------------- 655 | 656 | 657 | FLT_PREOP_CALLBACK_STATUS 658 | #pragma warning(suppress: 6262) // higher than usual stack usage is considered safe in this case 659 | SpyPreOperationCallback( 660 | _Inout_ PFLT_CALLBACK_DATA Data, 661 | _In_ PCFLT_RELATED_OBJECTS FltObjects, 662 | _Flt_CompletionContext_Outptr_ PVOID *CompletionContext 663 | ) 664 | /*++ 665 | 666 | Routine Description: 667 | 668 | This routine receives ALL pre-operation callbacks for this filter. It then 669 | tries to log information about the given operation. If we are able 670 | to log information then we will call our post-operation callback routine. 671 | 672 | NOTE: This routine must be NON-PAGED because it can be called on the 673 | paging path. 674 | 675 | Arguments: 676 | 677 | Data - Contains information about the given operation. 678 | 679 | FltObjects - Contains pointers to the various objects that are pertinent 680 | to this operation. 681 | 682 | CompletionContext - This receives the address of our log buffer for this 683 | operation. Our completion routine then receives this buffer address. 684 | 685 | Return Value: 686 | 687 | Identifies how processing should continue for this operation 688 | 689 | --*/ 690 | { 691 | FLT_PREOP_CALLBACK_STATUS returnStatus = FLT_PREOP_SUCCESS_NO_CALLBACK; //assume we are NOT going to call our completion routine 692 | PRECORD_LIST recordList; 693 | 694 | NTSTATUS nameStatus = STATUS_UNSUCCESSFUL; 695 | NTSTATUS targetNameStatus = STATUS_UNSUCCESSFUL; 696 | 697 | PFLT_FILE_NAME_INFORMATION nameInfo = NULL; 698 | PFLT_FILE_NAME_INFORMATION targetNameInfo = NULL; 699 | 700 | if (MiniFSWatcherData.ClientPort == NULL || MiniFSWatcherData.WatchPath.Buffer == NULL) 701 | { 702 | return FLT_PREOP_SUCCESS_NO_CALLBACK; 703 | } 704 | 705 | if (!FlagOn(Data->Flags, FLTFL_CALLBACK_DATA_IRP_OPERATION)) 706 | { 707 | return FLT_PREOP_SUCCESS_NO_CALLBACK; 708 | } 709 | 710 | if (FltObjects->FileObject == NULL || FltObjects->FileObject->FileName.Buffer == NULL || FltObjects->FileObject->DeviceObject == NULL) 711 | { 712 | return FLT_PREOP_SUCCESS_NO_CALLBACK; 713 | } 714 | 715 | CONTINUE_IF_MATCHES(MiniFSWatcherData.WatchProcess, PsGetCurrentProcessId()); 716 | 717 | CONTINUE_IF_MATCHES(MiniFSWatcherData.WatchThread, PsGetCurrentThreadId()); 718 | 719 | if (Data->Iopb->MajorFunction == IRP_MJ_SET_INFORMATION && Data->Iopb->Parameters.SetFileInformation.FileInformationClass == FileRenameInformation) 720 | { 721 | PFILE_RENAME_INFORMATION info = (PFILE_RENAME_INFORMATION)Data->Iopb->Parameters.SetFileInformation.InfoBuffer; 722 | if (info != NULL) 723 | { 724 | targetNameStatus = FltGetDestinationFileNameInformation(FltObjects->Instance, FltObjects->FileObject, info->RootDirectory, info->FileName, info->FileNameLength, FLT_FILE_NAME_NORMALIZED | MiniFSWatcherData.NameQueryMethod, &targetNameInfo); 725 | } 726 | } 727 | 728 | nameStatus = FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | MiniFSWatcherData.NameQueryMethod, &nameInfo); 729 | 730 | if ((NT_SUCCESS(nameStatus) && SpyIsWatchedPath(&nameInfo->Name)) 731 | || (NT_SUCCESS(targetNameStatus) && SpyIsWatchedPath(&targetNameInfo->Name))) 732 | { 733 | recordList = SpyNewRecord(); 734 | 735 | if (recordList) 736 | { 737 | USHORT offset = SpyAddRecordName(&recordList->LogRecord, &nameInfo->Name, 0); 738 | if (NT_SUCCESS(targetNameStatus) && targetNameInfo != NULL) 739 | { 740 | SpyAddRecordName(&recordList->LogRecord, &targetNameInfo->Name, offset); 741 | } 742 | 743 | SpyLogPreOperationData(recordList); 744 | 745 | *CompletionContext = recordList; 746 | returnStatus = FLT_PREOP_SUCCESS_WITH_CALLBACK; 747 | } 748 | } 749 | 750 | if (nameInfo != NULL) 751 | { 752 | FltReleaseFileNameInformation(nameInfo); 753 | } 754 | 755 | if (targetNameInfo != NULL) 756 | { 757 | FltReleaseFileNameInformation(targetNameInfo); 758 | } 759 | 760 | return returnStatus; 761 | } 762 | 763 | 764 | FLT_POSTOP_CALLBACK_STATUS 765 | SpyPostOperationCallback ( 766 | _Inout_ PFLT_CALLBACK_DATA Data, 767 | _In_ PCFLT_RELATED_OBJECTS FltObjects, 768 | _In_ PVOID CompletionContext, 769 | _In_ FLT_POST_OPERATION_FLAGS Flags 770 | ) 771 | /*++ 772 | 773 | Routine Description: 774 | 775 | This routine receives ALL post-operation callbacks. This will take 776 | the log record passed in the context parameter and update it with 777 | the completion information. It will then insert it on a list to be 778 | sent to the usermode component. 779 | 780 | NOTE: This routine must be NON-PAGED because it can be called at DPC level 781 | 782 | Arguments: 783 | 784 | Data - Contains information about the given operation. 785 | 786 | FltObjects - Contains pointers to the various objects that are pertinent 787 | to this operation. 788 | 789 | CompletionContext - Pointer to the RECORD_LIST structure in which we 790 | store the information we are logging. This was passed from the 791 | pre-operation callback 792 | 793 | Flags - Contains information as to why this routine was called. 794 | 795 | Return Value: 796 | 797 | Identifies how processing should continue for this operation 798 | 799 | --*/ 800 | { 801 | PRECORD_LIST recordList; 802 | 803 | recordList = (PRECORD_LIST)CompletionContext; 804 | 805 | if (recordList == NULL) 806 | { 807 | return FLT_POSTOP_FINISHED_PROCESSING; 808 | } 809 | 810 | if (FlagOn(Flags,FLTFL_POST_OPERATION_DRAINING) || !NT_SUCCESS(Data->IoStatus.Status) 811 | || (recordList->LogRecord.Data.EventType = SpyGetEventType(Data, FltObjects)) == FILE_SYSTEM_EVENT_UNKNOWN) 812 | { 813 | SpyFreeRecord( recordList ); 814 | return FLT_POSTOP_FINISHED_PROCESSING; 815 | } 816 | 817 | SpyLogPostOperationData( FltObjects, recordList ); 818 | SpyLog( recordList ); 819 | 820 | return FLT_POSTOP_FINISHED_PROCESSING; 821 | } 822 | 823 | LONG 824 | SpyExceptionFilter ( 825 | _In_ PEXCEPTION_POINTERS ExceptionPointer, 826 | _In_ BOOLEAN AccessingUserBuffer 827 | ) 828 | /*++ 829 | 830 | Routine Description: 831 | 832 | Exception filter to catch errors touching user buffers. 833 | 834 | Arguments: 835 | 836 | ExceptionPointer - The exception record. 837 | 838 | AccessingUserBuffer - If TRUE, overrides FsRtlIsNtStatusExpected to allow 839 | the caller to munge the error to a desired status. 840 | 841 | Return Value: 842 | 843 | EXCEPTION_EXECUTE_HANDLER - If the exception handler should be run. 844 | 845 | EXCEPTION_CONTINUE_SEARCH - If a higher exception handler should take care of 846 | this exception. 847 | 848 | --*/ 849 | { 850 | NTSTATUS Status; 851 | 852 | Status = ExceptionPointer->ExceptionRecord->ExceptionCode; 853 | 854 | // 855 | // Certain exceptions shouldn't be dismissed within the namechanger filter 856 | // unless we're touching user memory. 857 | // 858 | 859 | if (!FsRtlIsNtstatusExpected( Status ) && 860 | !AccessingUserBuffer) { 861 | 862 | return EXCEPTION_CONTINUE_SEARCH; 863 | } 864 | 865 | return EXCEPTION_EXECUTE_HANDLER; 866 | } 867 | 868 | 869 | -------------------------------------------------------------------------------- /MiniFSWatcherDriver/minispy.h: -------------------------------------------------------------------------------- 1 | /*++ 2 | 3 | Copyright (c) 1989-2002 Microsoft Corporation 4 | 5 | Module Name: 6 | 7 | minispy.h 8 | 9 | Abstract: 10 | 11 | Header file which contains the structures, type definitions, 12 | and constants that are shared between the kernel mode driver, 13 | minispy.sys, and the user mode executable, minispy.exe. 14 | 15 | Environment: 16 | 17 | Kernel and user mode 18 | 19 | --*/ 20 | #ifndef __MINISPY_H__ 21 | #define __MINISPY_H__ 22 | 23 | #define FILE_SYSTEM_EVENT_UNKNOWN 0 24 | #define FILE_SYSTEM_EVENT_CREATE 1 25 | #define FILE_SYSTEM_EVENT_DELETE 2 26 | #define FILE_SYSTEM_EVENT_CHANGE 3 27 | #define FILE_SYSTEM_EVENT_MOVE 4 28 | #define FILE_SYSTEM_EVENT_CLOSE 5 29 | 30 | #define DOS_DEVICE_PREFIX_LENGTH 4 31 | 32 | // 33 | // My own definition for transaction notify command 34 | // 35 | 36 | #define IRP_MJ_TRANSACTION_NOTIFY ((UCHAR)-40) 37 | 38 | 39 | // 40 | // Version definition 41 | // 42 | 43 | #define MINIFSWATCHER_MAJ_VERSION 2 44 | #define MINIFSWATCHER_MIN_VERSION 0 45 | 46 | typedef struct _MINIFSWATCHERVER { 47 | 48 | USHORT Major; 49 | USHORT Minor; 50 | 51 | } MINIFSWATCHERVER, *PMINIFSWATCHERVER; 52 | 53 | // 54 | // Name of minispy's communication server port 55 | // 56 | 57 | #define MINIFSWATCHER_PORT_NAME L"\\MiniFSWatcherPort" 58 | 59 | // 60 | // Local definitions for passing parameters between the filter and user mode 61 | // 62 | 63 | typedef ULONG_PTR FILE_ID; 64 | typedef _Return_type_success_(return >= 0) LONG NTSTATUS; 65 | 66 | // 67 | // The maximum size of a record that can be passed from the filter 68 | // 69 | 70 | #define RECORD_SIZE 1024 71 | 72 | // 73 | // This defines the type of record buffer this is along with certain flags. 74 | // 75 | 76 | #define RECORD_TYPE_NORMAL 0x00000000 77 | #define RECORD_TYPE_FILETAG 0x00000004 78 | 79 | #define RECORD_TYPE_FLAG_STATIC 0x80000000 80 | #define RECORD_TYPE_FLAG_EXCEED_MEMORY_ALLOWANCE 0x20000000 81 | #define RECORD_TYPE_FLAG_OUT_OF_MEMORY 0x10000000 82 | #define RECORD_TYPE_FLAG_MASK 0xffff0000 83 | 84 | // 85 | // The fixed data received for RECORD_TYPE_NORMAL 86 | // 87 | 88 | typedef struct _RECORD_DATA { 89 | 90 | LARGE_INTEGER OriginatingTime; 91 | LARGE_INTEGER CompletionTime; 92 | 93 | ULONG EventType; 94 | ULONG Flags; 95 | 96 | FILE_ID ProcessId; 97 | } RECORD_DATA, *PRECORD_DATA; 98 | 99 | // 100 | // What information we actually log. 101 | // 102 | 103 | #pragma warning(push) 104 | #pragma warning(disable:4200) // disable warnings for structures with zero length arrays. 105 | 106 | typedef struct _LOG_RECORD { 107 | 108 | ULONG Length; // Length of log record. This Does not include 109 | ULONG SequenceNumber; // space used by other members of RECORD_LIST 110 | 111 | ULONG RecordType; // The type of log record this is. 112 | ULONG Reserved; // For alignment on IA64 113 | 114 | RECORD_DATA Data; 115 | 116 | WCHAR Names[]; // This is a null terminated string 117 | } LOG_RECORD, *PLOG_RECORD; 118 | 119 | #pragma warning(pop) 120 | 121 | // 122 | // How the mini-filter manages the log records. 123 | // 124 | 125 | typedef struct _RECORD_LIST { 126 | 127 | LIST_ENTRY List; 128 | 129 | // 130 | // Must always be last item. See MAX_LOG_RECORD_LENGTH macro below. 131 | // Must be aligned on PVOID boundary in this structure. This is because the 132 | // log records are going to be packed one after another & accessed directly 133 | // Size of log record must also be multiple of PVOID size to avoid alignment 134 | // faults while accessing the log records on IA64 135 | // 136 | 137 | LOG_RECORD LogRecord; 138 | 139 | } RECORD_LIST, *PRECORD_LIST; 140 | 141 | // 142 | // Defines the commands between the utility and the filter 143 | // 144 | 145 | typedef enum _MINIFSWATCHER_COMMAND { 146 | 147 | GetMiniSpyLog, 148 | GetMiniSpyVersion, 149 | SetWatchProcess, 150 | SetWatchThread, 151 | SetPathFilter 152 | 153 | } MINIFSWATCHER_COMMAND; 154 | 155 | // 156 | // Defines the command structure between the utility and the filter. 157 | // 158 | 159 | #pragma warning(push) 160 | #pragma warning(disable:4200) // disable warnings for structures with zero length arrays. 161 | 162 | typedef struct _COMMAND_MESSAGE { 163 | MINIFSWATCHER_COMMAND Command; 164 | ULONG Reserved; // Alignment on IA64 165 | UCHAR Data[]; 166 | } COMMAND_MESSAGE, *PCOMMAND_MESSAGE; 167 | 168 | #pragma warning(pop) 169 | 170 | // 171 | // The maximum number of BYTES that can be used to store the file name in the 172 | // RECORD_LIST structure 173 | // 174 | 175 | #define MAX_NAME_SPACE ROUND_TO_SIZE( (RECORD_SIZE - sizeof(RECORD_LIST)), sizeof( PVOID )) 176 | 177 | // 178 | // The maximum space, in bytes and WCHARs, available for the name (and ECP 179 | // if present) string, not including the space that must be reserved for a NULL 180 | // 181 | 182 | #define MAX_NAME_SPACE_LESS_NULL (MAX_NAME_SPACE - sizeof(UNICODE_NULL)) 183 | #define MAX_NAME_WCHARS_LESS_NULL MAX_NAME_SPACE_LESS_NULL / sizeof(WCHAR) 184 | 185 | // 186 | // Returns the number of BYTES unused in the RECORD_LIST structure. Note that 187 | // LogRecord->Length already contains the size of LOG_RECORD which is why we 188 | // have to remove it. 189 | // 190 | 191 | #define REMAINING_NAME_SPACE(LogRecord) \ 192 | (FLT_ASSERT((LogRecord)->Length >= sizeof(LOG_RECORD)), \ 193 | (USHORT)(MAX_NAME_SPACE - ((LogRecord)->Length - sizeof(LOG_RECORD)))) 194 | 195 | #define MAX_LOG_RECORD_LENGTH (RECORD_SIZE - FIELD_OFFSET( RECORD_LIST, LogRecord )) 196 | 197 | 198 | // 199 | // Macros available in kernel mode which are not available in user mode 200 | // 201 | 202 | #ifndef Add2Ptr 203 | #define Add2Ptr(P,I) ((PVOID)((PUCHAR)(P) + (I))) 204 | #endif 205 | 206 | #ifndef ROUND_TO_SIZE 207 | #define ROUND_TO_SIZE(_length, _alignment) \ 208 | (((_length) + ((_alignment)-1)) & ~((_alignment) - 1)) 209 | #endif 210 | 211 | #ifndef FlagOn 212 | #define FlagOn(_F,_SF) ((_F) & (_SF)) 213 | #endif 214 | 215 | #define CONTINUE_IF_MATCHES(_expected, _actual) \ 216 | if (_expected > 0) { \ 217 | if (((LONGLONG)_actual) != _expected) { \ 218 | return FLT_PREOP_SUCCESS_NO_CALLBACK; \ 219 | } \ 220 | } else if (_expected < 0) {\ 221 | if (((LONGLONG)_actual) == -1 * _expected) { \ 222 | return FLT_PREOP_SUCCESS_NO_CALLBACK; \ 223 | } \ 224 | } 225 | 226 | #endif /* __MINISPY_H__ */ 227 | 228 | -------------------------------------------------------------------------------- /MiniFSWatcherDriver/minispy.rc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #define VER_FILETYPE VFT_DRV 6 | #define VER_FILESUBTYPE VFT2_DRV_SYSTEM 7 | #define VER_FILEDESCRIPTION_STR "MiniSpy Filter Driver" 8 | #define VER_INTERNALNAME_STR "minispy.sys" 9 | 10 | #include "common.ver" 11 | -------------------------------------------------------------------------------- /MiniFSWatcherDriver/minispy.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {AB0153F1-D5E4-45E0-9308-5EF32974E9A1} 23 | $(MSBuildProjectName) 24 | Debug 25 | Win32 26 | {2F2D5822-720A-47AB-8E4A-7337206F83F5} 27 | $(LatestTargetPlatformVersion) 28 | MiniFSWatcherDriver 29 | 30 | 31 | 32 | Windows7 33 | False 34 | Desktop 35 | WDM 36 | WindowsKernelModeDriver10.0 37 | Driver 38 | 39 | 40 | Windows7 41 | True 42 | Desktop 43 | WDM 44 | WindowsKernelModeDriver10.0 45 | Driver 46 | 47 | 48 | Windows7 49 | False 50 | Desktop 51 | WDM 52 | WindowsKernelModeDriver10.0 53 | Driver 54 | 55 | 56 | Windows7 57 | True 58 | Desktop 59 | WDM 60 | WindowsKernelModeDriver10.0 61 | Driver 62 | 63 | 64 | 65 | $(IntDir) 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | minifswatcher 85 | 86 | 87 | minifswatcher 88 | 89 | 90 | minifswatcher 91 | 92 | 93 | minifswatcher 94 | 95 | 96 | 97 | %(AdditionalOptions) /map 98 | %(AdditionalDependencies);$(DDK_LIB_PATH)\fltMgr.lib 99 | 100 | 101 | true 102 | Level4 103 | %(AdditionalIncludeDirectories); 104 | %(PreprocessorDefinitions);_WIN2K_COMPAT_SLIST_USAGE 105 | 106 | 107 | 108 | 109 | %(AdditionalIncludeDirectories);..\inc 110 | %(PreprocessorDefinitions);_WIN2K_COMPAT_SLIST_USAGE 111 | 112 | 113 | %(AdditionalIncludeDirectories);..\inc 114 | %(PreprocessorDefinitions);_WIN2K_COMPAT_SLIST_USAGE 115 | 116 | 117 | 118 | 119 | %(AdditionalOptions) /map 120 | %(AdditionalDependencies);$(DDK_LIB_PATH)\fltMgr.lib 121 | 122 | 123 | true 124 | Level4 125 | %(AdditionalIncludeDirectories); 126 | %(PreprocessorDefinitions);_WIN2K_COMPAT_SLIST_USAGE 127 | 128 | 129 | 130 | 131 | %(AdditionalIncludeDirectories);..\inc 132 | %(PreprocessorDefinitions);_WIN2K_COMPAT_SLIST_USAGE 133 | 134 | 135 | %(AdditionalIncludeDirectories);..\inc 136 | %(PreprocessorDefinitions);_WIN2K_COMPAT_SLIST_USAGE 137 | 138 | 139 | 140 | 141 | %(AdditionalOptions) /map 142 | %(AdditionalDependencies);$(DDK_LIB_PATH)\fltMgr.lib 143 | 144 | 145 | true 146 | Level4 147 | %(AdditionalIncludeDirectories); 148 | %(PreprocessorDefinitions);_WIN2K_COMPAT_SLIST_USAGE 149 | 150 | 151 | 152 | 153 | %(AdditionalIncludeDirectories);..\inc 154 | %(PreprocessorDefinitions);_WIN2K_COMPAT_SLIST_USAGE 155 | 156 | 157 | %(AdditionalIncludeDirectories);..\inc 158 | %(PreprocessorDefinitions);_WIN2K_COMPAT_SLIST_USAGE 159 | 160 | 161 | 162 | 163 | %(AdditionalOptions) /map 164 | %(AdditionalDependencies);$(DDK_LIB_PATH)\fltMgr.lib 165 | 166 | 167 | true 168 | Level4 169 | %(AdditionalIncludeDirectories); 170 | %(PreprocessorDefinitions);_WIN2K_COMPAT_SLIST_USAGE 171 | 172 | 173 | 174 | 175 | %(AdditionalIncludeDirectories);..\inc 176 | %(PreprocessorDefinitions);_WIN2K_COMPAT_SLIST_USAGE 177 | 178 | 179 | %(AdditionalIncludeDirectories);..\inc 180 | %(PreprocessorDefinitions);_WIN2K_COMPAT_SLIST_USAGE 181 | 182 | 183 | 184 | 185 | %(AdditionalIncludeDirectories); 186 | %(AdditionalIncludeDirectories); 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /MiniFSWatcherDriver/minispy.vcxproj.Filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx;* 6 | {01075B84-BB81-4AA3-8C21-A7041C702B6F} 7 | 8 | 9 | h;hpp;hxx;hm;inl;inc;xsd 10 | {EC4FB901-91AF-470A-B669-B79EAE9CD07B} 11 | 12 | 13 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms;man;xml 14 | {2ECFFA50-EC77-4CA0-8217-F41C7A6E893D} 15 | 16 | 17 | inf;inv;inx;mof;mc; 18 | {39F948D3-F781-4C4B-A2E9-788D724DC0F3} 19 | 20 | 21 | 22 | 23 | Source Files 24 | 25 | 26 | Source Files 27 | 28 | 29 | Source Files 30 | 31 | 32 | 33 | 34 | Resource Files 35 | 36 | 37 | 38 | 39 | Header Files 40 | 41 | 42 | Header Files 43 | 44 | 45 | Header Files 46 | 47 | 48 | 49 | 50 | Driver Files 51 | 52 | 53 | -------------------------------------------------------------------------------- /MiniFSWatcherDriver/mspyKern.h: -------------------------------------------------------------------------------- 1 | /*++ 2 | 3 | Copyright (c) 1989-2002 Microsoft Corporation 4 | 5 | Module Name: 6 | 7 | mspyKern.h 8 | 9 | Abstract: 10 | Header file which contains the structures, type definitions, 11 | constants, global variables and function prototypes that are 12 | only visible within the kernel. 13 | 14 | Environment: 15 | 16 | Kernel mode 17 | 18 | --*/ 19 | #ifndef __MSPYKERN_H__ 20 | #define __MSPYKERN_H__ 21 | 22 | #include 23 | //#include 24 | #include 25 | #include "minispy.h" 26 | 27 | #pragma prefast(disable:__WARNING_ENCODE_MEMBER_FUNCTION_POINTER, "Not valid for kernel mode drivers") 28 | 29 | // 30 | // Memory allocation tag 31 | // 32 | 33 | #define SPY_TAG 'wsfM' 34 | 35 | // 36 | // Win8 define for support of NPFS/MSFS 37 | // Win7 define for support of new ECPs. 38 | // Vista define for including transaction support, 39 | // older ECPs 40 | // 41 | 42 | //--------------------------------------------------------------------------- 43 | // Global variables 44 | //--------------------------------------------------------------------------- 45 | 46 | typedef struct _MINISPY_DATA { 47 | 48 | // 49 | // The object that identifies this driver. 50 | // 51 | 52 | PDRIVER_OBJECT DriverObject; 53 | 54 | // 55 | // The filter that results from a call to 56 | // FltRegisterFilter. 57 | // 58 | 59 | PFLT_FILTER Filter; 60 | 61 | // 62 | // Server port: user mode connects to this port 63 | // 64 | 65 | PFLT_PORT ServerPort; 66 | 67 | // 68 | // Client connection port: only one connection is allowed at a time., 69 | // 70 | 71 | PFLT_PORT ClientPort; 72 | 73 | // 74 | // List of buffers with data to send to user mode. 75 | // 76 | 77 | KSPIN_LOCK OutputBufferLock; 78 | LIST_ENTRY OutputBufferList; 79 | 80 | // 81 | // Lookaside list used for allocating buffers. 82 | // 83 | 84 | NPAGED_LOOKASIDE_LIST FreeBufferList; 85 | 86 | // 87 | // Variables used to throttle how many records buffer we can use 88 | // 89 | 90 | LONG MaxRecordsToAllocate; 91 | __volatile LONG RecordsAllocated; 92 | 93 | // 94 | // static buffer used for sending an "out-of-memory" message 95 | // to user mode. 96 | // 97 | 98 | __volatile LONG StaticBufferInUse; 99 | 100 | // 101 | // We need to make sure this buffer aligns on a PVOID boundary because 102 | // minispy casts this buffer to a RECORD_LIST structure. 103 | // That can cause alignment faults unless the structure starts on the 104 | // proper PVOID boundary 105 | // 106 | 107 | PVOID OutOfMemoryBuffer[RECORD_SIZE/sizeof( PVOID )]; 108 | 109 | // 110 | // Variable and lock for maintaining LogRecord sequence numbers. 111 | // 112 | 113 | __volatile LONG LogSequenceNumber; 114 | 115 | // 116 | // The name query method to use. By default, it is set to 117 | // FLT_FILE_NAME_QUERY_ALWAYS_ALLOW_CACHE_LOOKUP, but it can be overridden 118 | // by a setting in the registery. 119 | // 120 | 121 | ULONG NameQueryMethod; 122 | 123 | // 124 | // Global debug flags 125 | // 126 | 127 | ULONG DebugFlags; 128 | 129 | LONGLONG WatchProcess; 130 | 131 | LONGLONG WatchThread; 132 | 133 | UNICODE_STRING WatchPath; 134 | 135 | __volatile LONG WatchPathInUse; 136 | 137 | } MINIFSWATCHER_DATA, *PMINIFSWATCHER_DATA; 138 | 139 | // 140 | // Minispy's global variables 141 | // 142 | 143 | extern MINIFSWATCHER_DATA MiniFSWatcherData; 144 | 145 | #define DEFAULT_MAX_RECORDS_TO_ALLOCATE 500 146 | #define MAX_RECORDS_TO_ALLOCATE L"MaxRecords" 147 | 148 | #define DEFAULT_NAME_QUERY_METHOD FLT_FILE_NAME_QUERY_ALWAYS_ALLOW_CACHE_LOOKUP 149 | #define NAME_QUERY_METHOD L"NameQueryMethod" 150 | 151 | //--------------------------------------------------------------------------- 152 | // Registration structure 153 | //--------------------------------------------------------------------------- 154 | 155 | extern const FLT_REGISTRATION FilterRegistration; 156 | 157 | //--------------------------------------------------------------------------- 158 | // Function prototypes 159 | //--------------------------------------------------------------------------- 160 | 161 | FLT_PREOP_CALLBACK_STATUS 162 | SpyPreOperationCallback ( 163 | _Inout_ PFLT_CALLBACK_DATA Data, 164 | _In_ PCFLT_RELATED_OBJECTS FltObjects, 165 | _Flt_CompletionContext_Outptr_ PVOID *CompletionContext 166 | ); 167 | 168 | FLT_POSTOP_CALLBACK_STATUS 169 | SpyPostOperationCallback ( 170 | _Inout_ PFLT_CALLBACK_DATA Data, 171 | _In_ PCFLT_RELATED_OBJECTS FltObjects, 172 | _In_ PVOID CompletionContext, 173 | _In_ FLT_POST_OPERATION_FLAGS Flags 174 | ); 175 | 176 | NTSTATUS 177 | SpyFilterUnload ( 178 | _In_ FLT_FILTER_UNLOAD_FLAGS Flags 179 | ); 180 | 181 | NTSTATUS 182 | SpyQueryTeardown ( 183 | _In_ PCFLT_RELATED_OBJECTS FltObjects, 184 | _In_ FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags 185 | ); 186 | 187 | VOID 188 | SpyReadDriverParameters ( 189 | _In_ PUNICODE_STRING RegistryPath 190 | ); 191 | 192 | LONG 193 | SpyExceptionFilter ( 194 | _In_ PEXCEPTION_POINTERS ExceptionPointer, 195 | _In_ BOOLEAN AccessingUserBuffer 196 | ); 197 | 198 | //--------------------------------------------------------------------------- 199 | // Memory allocation routines 200 | //--------------------------------------------------------------------------- 201 | 202 | PRECORD_LIST 203 | SpyAllocateBuffer ( 204 | _Out_ PULONG RecordType 205 | ); 206 | 207 | VOID 208 | SpyFreeBuffer ( 209 | _In_ PVOID Buffer 210 | ); 211 | 212 | //--------------------------------------------------------------------------- 213 | // Logging routines 214 | //--------------------------------------------------------------------------- 215 | PRECORD_LIST 216 | SpyNewRecord ( 217 | VOID 218 | ); 219 | 220 | ULONG SpyGetEventType( 221 | _In_ PFLT_CALLBACK_DATA Data, 222 | _In_ PCFLT_RELATED_OBJECTS FltObjects 223 | ); 224 | 225 | BOOLEAN SpyIsWatchedPath(_In_ PUNICODE_STRING path); 226 | 227 | BOOLEAN SpyUpdateWatchedPath(_In_ PUNICODE_STRING path); 228 | 229 | VOID 230 | SpyFreeRecord ( 231 | _In_ PRECORD_LIST Record 232 | ); 233 | 234 | USHORT 235 | SpyAddRecordName( 236 | _Inout_ PLOG_RECORD LogRecord, 237 | _In_ PUNICODE_STRING Name, 238 | _In_ USHORT ByteOffset 239 | ); 240 | 241 | VOID 242 | SpyLogPreOperationData ( 243 | _Inout_ PRECORD_LIST RecordList 244 | ); 245 | 246 | VOID 247 | SpyLogPostOperationData ( 248 | _In_ PCFLT_RELATED_OBJECTS FltObjects, 249 | _Inout_ PRECORD_LIST RecordList 250 | ); 251 | 252 | VOID 253 | SpyLog ( 254 | _In_ PRECORD_LIST RecordList 255 | ); 256 | 257 | NTSTATUS 258 | SpyGetLog ( 259 | _Out_writes_bytes_to_(OutputBufferLength,*ReturnOutputBufferLength) PUCHAR OutputBuffer, 260 | _In_ ULONG OutputBufferLength, 261 | _Out_ PULONG ReturnOutputBufferLength 262 | ); 263 | 264 | VOID 265 | SpyEmptyOutputBufferList ( 266 | VOID 267 | ); 268 | 269 | #endif //__MSPYKERN_H__ 270 | 271 | -------------------------------------------------------------------------------- /MiniFSWatcherDriver/mspyLib.c: -------------------------------------------------------------------------------- 1 | /*++ 2 | 3 | Copyright (c) 1989-2002 Microsoft Corporation 4 | 5 | Module Name: 6 | 7 | mspyLib.c 8 | 9 | Abstract: 10 | This contains library support routines for MiniSpy 11 | 12 | Environment: 13 | 14 | Kernel mode 15 | 16 | --*/ 17 | 18 | #include 19 | #include 20 | 21 | #include "mspyKern.h" 22 | 23 | // 24 | // Can't pull in wsk.h until after MINISPY_VISTA is defined 25 | // 26 | 27 | #if MINISPY_VISTA 28 | #include 29 | #include 30 | #endif 31 | 32 | //--------------------------------------------------------------------------- 33 | // Assign text sections for each routine. 34 | //--------------------------------------------------------------------------- 35 | 36 | #ifdef ALLOC_PRAGMA 37 | #pragma alloc_text(INIT, SpyReadDriverParameters) 38 | #endif 39 | 40 | UCHAR TxNotificationToMinorCode ( 41 | _In_ ULONG TxNotification 42 | ) 43 | /*++ 44 | 45 | Routine Description: 46 | 47 | This routine has been written to convert a transaction notification code 48 | to an Irp minor code. This function is needed because RECORD_DATA has a 49 | UCHAR field for the Irp minor code whereas TxNotification is ULONG. As 50 | of now all this function does is compute log_base_2(TxNotification) + 1. 51 | That fits our need for now but might have to be evolved later. This 52 | function is intricately tied with the enumeration TRANSACTION_NOTIFICATION_CODES 53 | in mspyLog.h and the case statements related to transactions in the function 54 | PrintIrpCode (Minispy\User\mspyLog.c). 55 | 56 | Arguments: 57 | 58 | TxNotification - The transaction notification received. 59 | 60 | Return Value: 61 | 62 | 0 if TxNotification is 0; 63 | log_base_2(TxNotification) + 1 otherwise. 64 | 65 | --*/ 66 | { 67 | UCHAR count = 0; 68 | 69 | if (TxNotification == 0) 70 | return 0; 71 | 72 | // 73 | // This assert verifies if no more than one flag is set 74 | // in the TxNotification variable. TxNotification flags are 75 | // supposed to be mutually exclusive. The assert below verifies 76 | // if the value of TxNotification is a power of 2. If it is not 77 | // then we will break. 78 | // 79 | 80 | FLT_ASSERT( !(( TxNotification ) & ( TxNotification - 1 )) ); 81 | 82 | while (TxNotification) { 83 | 84 | count++; 85 | 86 | TxNotification >>= 1; 87 | 88 | // 89 | // If we hit this assert then we have more notification codes than 90 | // can fit in a UCHAR. We need to revaluate our approach for 91 | // storing minor codes now. 92 | // 93 | 94 | FLT_ASSERT( count != 0 ); 95 | } 96 | 97 | return ( count ); 98 | } 99 | 100 | 101 | //--------------------------------------------------------------------------- 102 | // Log Record allocation routines 103 | //--------------------------------------------------------------------------- 104 | 105 | PRECORD_LIST 106 | SpyAllocateBuffer ( 107 | _Out_ PULONG RecordType 108 | ) 109 | /*++ 110 | 111 | Routine Description: 112 | 113 | Allocates a new buffer from the MiniSpyData.FreeBufferList if there is 114 | enough memory to do so and we have not exceed our maximum buffer 115 | count. 116 | 117 | NOTE: Because there is no interlock between testing if we have exceeded 118 | the record allocation limit and actually increment the in use 119 | count it is possible to temporarily allocate one or two buffers 120 | more then the limit. Because this is such a rare situation there 121 | is not point to handling this. 122 | 123 | NOTE: This code must be NON-PAGED because it can be called on the 124 | paging path or at DPC level. 125 | 126 | Arguments: 127 | 128 | RecordType - Receives information on what type of record was allocated. 129 | 130 | Return Value: 131 | 132 | Pointer to the allocated buffer, or NULL if the allocation failed. 133 | 134 | --*/ 135 | { 136 | PVOID newBuffer; 137 | ULONG newRecordType = RECORD_TYPE_NORMAL; 138 | 139 | // 140 | // See if we have room to allocate more buffers 141 | // 142 | 143 | if (MiniFSWatcherData.RecordsAllocated < MiniFSWatcherData.MaxRecordsToAllocate) { 144 | 145 | InterlockedIncrement( &MiniFSWatcherData.RecordsAllocated ); 146 | 147 | newBuffer = ExAllocateFromNPagedLookasideList( &MiniFSWatcherData.FreeBufferList ); 148 | 149 | if (newBuffer == NULL) { 150 | 151 | // 152 | // We failed to allocate the memory. Decrement our global count 153 | // and return what type of memory we have. 154 | // 155 | 156 | InterlockedDecrement( &MiniFSWatcherData.RecordsAllocated ); 157 | 158 | newRecordType = RECORD_TYPE_FLAG_OUT_OF_MEMORY; 159 | } 160 | 161 | } else { 162 | 163 | // 164 | // No more room to allocate memory, return we didn't get a buffer 165 | // and why. 166 | // 167 | 168 | newRecordType = RECORD_TYPE_FLAG_EXCEED_MEMORY_ALLOWANCE; 169 | newBuffer = NULL; 170 | } 171 | 172 | *RecordType = newRecordType; 173 | return newBuffer; 174 | } 175 | 176 | 177 | VOID 178 | SpyFreeBuffer ( 179 | _In_ PVOID Buffer 180 | ) 181 | /*++ 182 | 183 | Routine Description: 184 | 185 | Free an allocate buffer. 186 | 187 | NOTE: This code must be NON-PAGED because it can be called on the 188 | paging path or at DPC level. 189 | 190 | Arguments: 191 | 192 | Buffer - The buffer to free. 193 | 194 | Return Value: 195 | 196 | None. 197 | 198 | --*/ 199 | { 200 | // 201 | // Free the memory, update the counter 202 | // 203 | 204 | InterlockedDecrement( &MiniFSWatcherData.RecordsAllocated ); 205 | ExFreeToNPagedLookasideList( &MiniFSWatcherData.FreeBufferList, Buffer ); 206 | } 207 | 208 | 209 | //--------------------------------------------------------------------------- 210 | // Logging routines 211 | //--------------------------------------------------------------------------- 212 | 213 | PRECORD_LIST 214 | SpyNewRecord ( 215 | VOID 216 | ) 217 | /*++ 218 | 219 | Routine Description: 220 | 221 | Allocates a new RECORD_LIST structure if there is enough memory to do so. A 222 | sequence number is updated for each request for a new record. 223 | 224 | NOTE: This code must be NON-PAGED because it can be called on the 225 | paging path or at DPC level. 226 | 227 | Arguments: 228 | 229 | None 230 | 231 | Return Value: 232 | 233 | Pointer to the RECORD_LIST allocated, or NULL if no memory is available. 234 | 235 | --*/ 236 | { 237 | PRECORD_LIST newRecord; 238 | ULONG initialRecordType; 239 | 240 | // 241 | // Allocate the buffer 242 | // 243 | 244 | newRecord = SpyAllocateBuffer( &initialRecordType ); 245 | 246 | if (newRecord == NULL) { 247 | 248 | // 249 | // We could not allocate a record, see if the static buffer is 250 | // in use. If not, we will use it 251 | // 252 | 253 | if (!InterlockedExchange( &MiniFSWatcherData.StaticBufferInUse, TRUE )) { 254 | 255 | newRecord = (PRECORD_LIST)MiniFSWatcherData.OutOfMemoryBuffer; 256 | initialRecordType |= RECORD_TYPE_FLAG_STATIC; 257 | } 258 | } 259 | 260 | // 261 | // If we got a record (doesn't matter if it is static or not), init it 262 | // 263 | 264 | if (newRecord != NULL) { 265 | 266 | // 267 | // Init the new record 268 | // 269 | 270 | newRecord->LogRecord.RecordType = initialRecordType; 271 | newRecord->LogRecord.Length = sizeof(LOG_RECORD); 272 | newRecord->LogRecord.SequenceNumber = InterlockedIncrement( &MiniFSWatcherData.LogSequenceNumber ); 273 | RtlZeroMemory( &newRecord->LogRecord.Data, sizeof( RECORD_DATA ) ); 274 | } 275 | 276 | return( newRecord ); 277 | } 278 | 279 | 280 | VOID 281 | SpyFreeRecord ( 282 | _In_ PRECORD_LIST Record 283 | ) 284 | /*++ 285 | 286 | Routine Description: 287 | 288 | Free the given buffer 289 | 290 | NOTE: This code must be NON-PAGED because it can be called on the 291 | paging path or at DPC level. 292 | 293 | Arguments: 294 | 295 | Record - the buffer to free 296 | 297 | Return Value: 298 | 299 | None. 300 | 301 | --*/ 302 | { 303 | if (FlagOn(Record->LogRecord.RecordType,RECORD_TYPE_FLAG_STATIC)) { 304 | 305 | // 306 | // This was our static buffer, mark it available. 307 | // 308 | 309 | FLT_ASSERT(MiniFSWatcherData.StaticBufferInUse); 310 | MiniFSWatcherData.StaticBufferInUse = FALSE; 311 | 312 | } else { 313 | 314 | SpyFreeBuffer( Record ); 315 | } 316 | } 317 | 318 | USHORT 319 | SpyAddRecordName( 320 | _Inout_ PLOG_RECORD LogRecord, 321 | _In_ PUNICODE_STRING Name, 322 | _In_ USHORT ByteOffset 323 | ) 324 | /*++ 325 | 326 | Routine Description: 327 | 328 | Sets the given file name in the LogRecord. 329 | 330 | NOTE: This code must be NON-PAGED because it can be called on the 331 | paging path. 332 | 333 | Arguments: 334 | 335 | LogRecord - The record in which to set the name. 336 | 337 | Name - The name to insert 338 | 339 | Return Value: 340 | 341 | None. 342 | 343 | --*/ 344 | { 345 | 346 | PWCHAR printPointer = (PWCHAR)LogRecord->Names; 347 | SHORT charsToSkip = ByteOffset / sizeof(WCHAR); 348 | SHORT wcharsCopied; 349 | USHORT stringLength; 350 | 351 | if (Name == NULL || charsToSkip >= MAX_NAME_WCHARS_LESS_NULL) 352 | { 353 | return 0; 354 | } 355 | 356 | #pragma prefast(suppress:__WARNING_BANNED_API_USAGE, "reviewed and safe usage") 357 | wcharsCopied = (SHORT)_snwprintf(printPointer + charsToSkip, 358 | MAX_NAME_WCHARS_LESS_NULL - charsToSkip, 359 | L"%wZ", 360 | Name); 361 | 362 | if (wcharsCopied >= 0) 363 | { 364 | stringLength = ByteOffset + (wcharsCopied * sizeof(WCHAR)); 365 | } 366 | else 367 | { 368 | 369 | // 370 | // There wasn't enough buffer space, so manually truncate in a NULL 371 | // because we can't trust _snwprintf to do so in that case. 372 | // 373 | 374 | stringLength = MAX_NAME_SPACE_LESS_NULL; 375 | printPointer[MAX_NAME_WCHARS_LESS_NULL] = UNICODE_NULL; 376 | } 377 | 378 | // 379 | // We will always round up log-record length to sizeof(PVOID) so that 380 | // the next log record starts on the right PVOID boundary to prevent 381 | // IA64 alignment faults. The length of the record of course 382 | // includes the additional NULL at the end. 383 | // 384 | 385 | LogRecord->Length = ROUND_TO_SIZE((sizeof(LOG_RECORD) + 386 | stringLength + 387 | sizeof(UNICODE_NULL)), 388 | sizeof(PVOID)); 389 | 390 | FLT_ASSERT(LogRecord->Length <= MAX_LOG_RECORD_LENGTH); 391 | 392 | return stringLength + sizeof(UNICODE_NULL); 393 | } 394 | 395 | BOOLEAN SpyIsWatchedPath(_In_ PUNICODE_STRING path) 396 | { 397 | BOOLEAN result = FALSE; 398 | if (!InterlockedExchange(&MiniFSWatcherData.WatchPathInUse, TRUE)) 399 | { 400 | result = FsRtlIsNameInExpression(&MiniFSWatcherData.WatchPath, path, TRUE, NULL); 401 | MiniFSWatcherData.WatchPathInUse = FALSE; 402 | } 403 | return result; 404 | } 405 | 406 | BOOLEAN SpyUpdateWatchedPath(_In_ PUNICODE_STRING path) 407 | { 408 | BOOLEAN result = FALSE; 409 | if (!InterlockedExchange(&MiniFSWatcherData.WatchPathInUse, TRUE)) 410 | { 411 | if (MiniFSWatcherData.WatchPath.Buffer != NULL) 412 | { 413 | RtlFreeUnicodeString(&MiniFSWatcherData.WatchPath); 414 | } 415 | if (path != NULL && path->Buffer != NULL) 416 | { 417 | RtlUpcaseUnicodeString(&MiniFSWatcherData.WatchPath, path, TRUE); 418 | } 419 | 420 | result = TRUE; 421 | MiniFSWatcherData.WatchPathInUse = FALSE; 422 | } 423 | return result; 424 | } 425 | 426 | ULONG SpyGetEventType( 427 | _In_ PFLT_CALLBACK_DATA Data, 428 | _In_ PCFLT_RELATED_OBJECTS FltObjects 429 | ) 430 | { 431 | // Get file rename information 432 | if (Data->Iopb->MajorFunction == IRP_MJ_SET_INFORMATION) { 433 | 434 | if (Data->Iopb->Parameters.SetFileInformation.FileInformationClass == FileRenameInformation) 435 | { 436 | return FILE_SYSTEM_EVENT_MOVE; 437 | } 438 | // Get File delete information 439 | else if (Data->Iopb->Parameters.SetFileInformation.FileInformationClass == FileDispositionInformation) 440 | { 441 | PFILE_DISPOSITION_INFORMATION info = (PFILE_DISPOSITION_INFORMATION)Data->Iopb->Parameters.SetFileInformation.InfoBuffer; 442 | if (info != NULL && info->DeleteFile) 443 | { 444 | return FILE_SYSTEM_EVENT_DELETE; 445 | } 446 | } 447 | else if (Data->Iopb->Parameters.SetFileInformation.FileInformationClass == FileAllocationInformation) 448 | { 449 | // Special handling if file is set to empty 450 | PFILE_ALLOCATION_INFORMATION info = (PFILE_ALLOCATION_INFORMATION)Data->Iopb->Parameters.SetFileInformation.InfoBuffer; 451 | if (info != NULL && info->AllocationSize.QuadPart == 0) 452 | { 453 | return FILE_SYSTEM_EVENT_CHANGE; 454 | } 455 | } 456 | } 457 | else if (Data->Iopb->MajorFunction == IRP_MJ_CREATE && FlagOn(Data->Iopb->IrpFlags, IRP_CREATE_OPERATION)) 458 | { 459 | if (Data->IoStatus.Information == FILE_CREATED) 460 | { 461 | return FILE_SYSTEM_EVENT_CREATE; 462 | } 463 | else if (Data->IoStatus.Information == FILE_OVERWRITTEN) 464 | { 465 | DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "File has been overwritten\n"); 466 | return FILE_SYSTEM_EVENT_CHANGE; 467 | } 468 | } 469 | else if (Data->Iopb->MajorFunction == IRP_MJ_WRITE && Data->Iopb->Parameters.Write.Length > 0 && FlagOn(FltObjects->FileObject->Flags, FO_FILE_MODIFIED)) 470 | { 471 | return FILE_SYSTEM_EVENT_CHANGE; 472 | } 473 | else if (Data->Iopb->MajorFunction == IRP_MJ_CLOSE) 474 | { 475 | return FILE_SYSTEM_EVENT_CLOSE; 476 | } 477 | 478 | return FILE_SYSTEM_EVENT_UNKNOWN; 479 | } 480 | 481 | VOID 482 | SpyLogPreOperationData ( 483 | _Inout_ PRECORD_LIST RecordList 484 | ) 485 | /*++ 486 | 487 | Routine Description: 488 | 489 | This is called from the pre-operation callback routine to copy the 490 | necessary information into the log record. 491 | 492 | NOTE: This code must be NON-PAGED because it can be called on the 493 | paging path. 494 | 495 | Arguments: 496 | 497 | Data - The Data structure that contains the information we want to record. 498 | 499 | FltObjects - Pointer to the io objects involved in this operation. 500 | 501 | RecordList - Where we want to save the data 502 | 503 | Return Value: 504 | 505 | None. 506 | 507 | --*/ 508 | { 509 | PRECORD_DATA recordData = &RecordList->LogRecord.Data; 510 | 511 | recordData->Flags = 0L; 512 | recordData->ProcessId = (FILE_ID)PsGetCurrentProcessId(); 513 | 514 | KeQuerySystemTime( &recordData->OriginatingTime ); 515 | } 516 | 517 | 518 | VOID 519 | SpyLogPostOperationData ( 520 | _In_ PCFLT_RELATED_OBJECTS FltObjects, 521 | _Inout_ PRECORD_LIST RecordList 522 | ) 523 | /*++ 524 | 525 | Routine Description: 526 | 527 | This is called from the post-operation callback routine to copy the 528 | necessary information into the log record. 529 | 530 | NOTE: This code must be NON-PAGED because it can be called on the 531 | paging path or at DPC level. 532 | 533 | Arguments: 534 | 535 | Data - The Data structure that contains the information we want to record. 536 | 537 | RecordList - Where we want to save the data 538 | 539 | Return Value: 540 | 541 | None. 542 | 543 | --*/ 544 | { 545 | UNREFERENCED_PARAMETER(FltObjects); 546 | 547 | PRECORD_DATA recordData = &RecordList->LogRecord.Data; 548 | recordData->Flags = 0; 549 | KeQuerySystemTime( &recordData->CompletionTime ); 550 | } 551 | 552 | VOID 553 | SpyLog ( 554 | _In_ PRECORD_LIST RecordList 555 | ) 556 | /*++ 557 | 558 | Routine Description: 559 | 560 | This routine inserts the given log record into the list to be sent 561 | to the user mode application. 562 | 563 | NOTE: This code must be NON-PAGED because it can be called on the 564 | paging path or at DPC level and uses a spin-lock 565 | 566 | Arguments: 567 | 568 | RecordList - The record to append to the MiniSpyData.OutputBufferList 569 | 570 | Return Value: 571 | 572 | The function returns STATUS_SUCCESS. 573 | 574 | 575 | 576 | --*/ 577 | { 578 | KIRQL oldIrql; 579 | 580 | KeAcquireSpinLock(&MiniFSWatcherData.OutputBufferLock, &oldIrql); 581 | InsertTailList(&MiniFSWatcherData.OutputBufferList, &RecordList->List); 582 | KeReleaseSpinLock(&MiniFSWatcherData.OutputBufferLock, oldIrql); 583 | } 584 | 585 | 586 | NTSTATUS 587 | SpyGetLog ( 588 | _Out_writes_bytes_to_(OutputBufferLength,*ReturnOutputBufferLength) PUCHAR OutputBuffer, 589 | _In_ ULONG OutputBufferLength, 590 | _Out_ PULONG ReturnOutputBufferLength 591 | ) 592 | /*++ 593 | 594 | Routine Description: 595 | This function fills OutputBuffer with as many LOG_RECORDs as possible. 596 | The LOG_RECORDs are variable sizes and are tightly packed in the 597 | OutputBuffer. 598 | 599 | NOTE: This code must be NON-PAGED because it uses a spin-lock. 600 | 601 | Arguments: 602 | OutputBuffer - The user's buffer to fill with the log data we have 603 | collected 604 | 605 | OutputBufferLength - The size in bytes of OutputBuffer 606 | 607 | ReturnOutputBufferLength - The amount of data actually written into the 608 | OutputBuffer. 609 | 610 | Return Value: 611 | STATUS_SUCCESS if some records were able to be written to the OutputBuffer. 612 | 613 | STATUS_NO_MORE_ENTRIES if we have no data to return. 614 | 615 | STATUS_BUFFER_TOO_SMALL if the OutputBuffer is too small to 616 | hold even one record and we have data to return. 617 | 618 | --*/ 619 | { 620 | PLIST_ENTRY pList; 621 | ULONG bytesWritten = 0; 622 | PLOG_RECORD pLogRecord; 623 | NTSTATUS status = STATUS_NO_MORE_ENTRIES; 624 | PRECORD_LIST pRecordList; 625 | KIRQL oldIrql; 626 | BOOLEAN recordsAvailable = FALSE; 627 | 628 | KeAcquireSpinLock( &MiniFSWatcherData.OutputBufferLock, &oldIrql ); 629 | 630 | while (!IsListEmpty( &MiniFSWatcherData.OutputBufferList ) && (OutputBufferLength > 0)) { 631 | 632 | // 633 | // Mark we have records 634 | // 635 | 636 | recordsAvailable = TRUE; 637 | 638 | // 639 | // Get the next available record 640 | // 641 | 642 | pList = RemoveHeadList( &MiniFSWatcherData.OutputBufferList ); 643 | 644 | pRecordList = CONTAINING_RECORD( pList, RECORD_LIST, List ); 645 | 646 | pLogRecord = &pRecordList->LogRecord; 647 | 648 | // 649 | // If no filename was set then make it into a NULL file name. 650 | // 651 | 652 | if (REMAINING_NAME_SPACE( pLogRecord ) == MAX_NAME_SPACE) { 653 | 654 | // 655 | // We don't have a name, so return an empty string. 656 | // We have to always start a new log record on a PVOID aligned boundary. 657 | // 658 | 659 | pLogRecord->Length += ROUND_TO_SIZE( sizeof( UNICODE_NULL ), sizeof( PVOID ) ); 660 | pLogRecord->Names[0] = UNICODE_NULL; 661 | } 662 | 663 | // 664 | // Put it back if we've run out of room. 665 | // 666 | 667 | if (OutputBufferLength < pLogRecord->Length) { 668 | 669 | InsertHeadList( &MiniFSWatcherData.OutputBufferList, pList ); 670 | break; 671 | } 672 | 673 | KeReleaseSpinLock( &MiniFSWatcherData.OutputBufferLock, oldIrql ); 674 | 675 | // 676 | // The lock is released, return the data, adjust pointers. 677 | // Protect access to raw user-mode OutputBuffer with an exception handler 678 | // 679 | 680 | try { 681 | RtlCopyMemory( OutputBuffer, pLogRecord, pLogRecord->Length ); 682 | } except (SpyExceptionFilter( GetExceptionInformation(), TRUE )) { 683 | 684 | // 685 | // Put the record back in 686 | // 687 | 688 | KeAcquireSpinLock( &MiniFSWatcherData.OutputBufferLock, &oldIrql ); 689 | InsertHeadList( &MiniFSWatcherData.OutputBufferList, pList ); 690 | KeReleaseSpinLock( &MiniFSWatcherData.OutputBufferLock, oldIrql ); 691 | 692 | return GetExceptionCode(); 693 | 694 | } 695 | 696 | bytesWritten += pLogRecord->Length; 697 | 698 | OutputBufferLength -= pLogRecord->Length; 699 | 700 | OutputBuffer += pLogRecord->Length; 701 | 702 | SpyFreeRecord( pRecordList ); 703 | 704 | // 705 | // Relock the list 706 | // 707 | 708 | KeAcquireSpinLock( &MiniFSWatcherData.OutputBufferLock, &oldIrql ); 709 | } 710 | 711 | KeReleaseSpinLock( &MiniFSWatcherData.OutputBufferLock, oldIrql ); 712 | 713 | // 714 | // Set proper status 715 | // 716 | 717 | if ((bytesWritten == 0) && recordsAvailable) { 718 | 719 | // 720 | // There were records to be sent up but 721 | // there was not enough room in the buffer. 722 | // 723 | 724 | status = STATUS_BUFFER_TOO_SMALL; 725 | 726 | } else if (bytesWritten > 0) { 727 | 728 | // 729 | // We were able to write some data to the output buffer, 730 | // so this was a success. 731 | // 732 | 733 | status = STATUS_SUCCESS; 734 | } 735 | 736 | *ReturnOutputBufferLength = bytesWritten; 737 | 738 | return status; 739 | } 740 | 741 | 742 | VOID 743 | SpyEmptyOutputBufferList ( 744 | VOID 745 | ) 746 | /*++ 747 | 748 | Routine Description: 749 | 750 | This routine frees all the remaining log records in the OutputBufferList 751 | that are not going to get sent up to the user mode application since 752 | MiniSpy is shutting down. 753 | 754 | NOTE: This code must be NON-PAGED because it uses a spin-lock 755 | 756 | Arguments: 757 | 758 | None. 759 | 760 | Return Value: 761 | 762 | None. 763 | 764 | --*/ 765 | { 766 | PLIST_ENTRY pList; 767 | PRECORD_LIST pRecordList; 768 | KIRQL oldIrql; 769 | 770 | KeAcquireSpinLock( &MiniFSWatcherData.OutputBufferLock, &oldIrql ); 771 | 772 | while (!IsListEmpty( &MiniFSWatcherData.OutputBufferList )) { 773 | 774 | pList = RemoveHeadList( &MiniFSWatcherData.OutputBufferList ); 775 | KeReleaseSpinLock( &MiniFSWatcherData.OutputBufferLock, oldIrql ); 776 | 777 | pRecordList = CONTAINING_RECORD( pList, RECORD_LIST, List ); 778 | 779 | SpyFreeRecord( pRecordList ); 780 | 781 | KeAcquireSpinLock( &MiniFSWatcherData.OutputBufferLock, &oldIrql ); 782 | } 783 | 784 | KeReleaseSpinLock( &MiniFSWatcherData.OutputBufferLock, oldIrql ); 785 | } 786 | 787 | //--------------------------------------------------------------------------- 788 | // Logging routines 789 | //--------------------------------------------------------------------------- 790 | 791 | VOID 792 | SpyReadDriverParameters ( 793 | _In_ PUNICODE_STRING RegistryPath 794 | ) 795 | /*++ 796 | 797 | Routine Description: 798 | 799 | This routine tries to read the MiniSpy-specific parameters from 800 | the registry. These values will be found in the registry location 801 | indicated by the RegistryPath passed in. 802 | 803 | This processes the following registry keys: 804 | hklm\system\CurrentControlSet\Services\Minispy\MaxRecords 805 | hklm\system\CurrentControlSet\Services\Minispy\NameQueryMethod 806 | 807 | 808 | Arguments: 809 | 810 | RegistryPath - the path key which contains the values that are 811 | the MiniSpy parameters 812 | 813 | Return Value: 814 | 815 | None. 816 | 817 | --*/ 818 | { 819 | OBJECT_ATTRIBUTES attributes; 820 | HANDLE driverRegKey; 821 | NTSTATUS status; 822 | ULONG resultLength; 823 | UNICODE_STRING valueName; 824 | PKEY_VALUE_PARTIAL_INFORMATION pValuePartialInfo; 825 | UCHAR buffer[sizeof( KEY_VALUE_PARTIAL_INFORMATION ) + sizeof( LONG )]; 826 | 827 | // 828 | // Open the registry 829 | // 830 | 831 | InitializeObjectAttributes( &attributes, 832 | RegistryPath, 833 | OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, 834 | NULL, 835 | NULL ); 836 | 837 | status = ZwOpenKey( &driverRegKey, 838 | KEY_READ, 839 | &attributes ); 840 | 841 | if (!NT_SUCCESS( status )) { 842 | 843 | return; 844 | } 845 | 846 | // 847 | // Read the MaxRecordsToAllocate entry from the registry 848 | // 849 | 850 | RtlInitUnicodeString( &valueName, MAX_RECORDS_TO_ALLOCATE ); 851 | 852 | status = ZwQueryValueKey( driverRegKey, 853 | &valueName, 854 | KeyValuePartialInformation, 855 | buffer, 856 | sizeof(buffer), 857 | &resultLength ); 858 | 859 | if (NT_SUCCESS( status )) { 860 | 861 | pValuePartialInfo = (PKEY_VALUE_PARTIAL_INFORMATION) buffer; 862 | FLT_ASSERT( pValuePartialInfo->Type == REG_DWORD ); 863 | MiniFSWatcherData.MaxRecordsToAllocate = *((PLONG)&(pValuePartialInfo->Data)); 864 | } 865 | 866 | // 867 | // Read the NameQueryMethod entry from the registry 868 | // 869 | 870 | RtlInitUnicodeString( &valueName, NAME_QUERY_METHOD ); 871 | 872 | status = ZwQueryValueKey( driverRegKey, 873 | &valueName, 874 | KeyValuePartialInformation, 875 | buffer, 876 | sizeof(buffer), 877 | &resultLength ); 878 | 879 | if (NT_SUCCESS( status )) { 880 | 881 | pValuePartialInfo = (PKEY_VALUE_PARTIAL_INFORMATION) buffer; 882 | FLT_ASSERT( pValuePartialInfo->Type == REG_DWORD ); 883 | MiniFSWatcherData.NameQueryMethod = *((PLONG)&(pValuePartialInfo->Data)); 884 | } 885 | 886 | ZwClose(driverRegKey); 887 | } 888 | 889 | -------------------------------------------------------------------------------- /MiniFSWatcherPackage/MiniFSWatcherPackage.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | Debug 22 | ARM 23 | 24 | 25 | Release 26 | ARM 27 | 28 | 29 | Debug 30 | ARM64 31 | 32 | 33 | Release 34 | ARM64 35 | 36 | 37 | 38 | 39 | {ab0153f1-d5e4-45e0-9308-5ef32974e9a1} 40 | 41 | 42 | 43 | {89047C71-E058-482E-A4AF-CCC8CDA77612} 44 | {4605da2c-74a5-4865-98e1-152ef136825f} 45 | v4.5 46 | 12.0 47 | Debug 48 | Win32 49 | MiniFSWatcherPackage 50 | $(LatestTargetPlatformVersion) 51 | 52 | 53 | 54 | Windows7 55 | true 56 | WindowsKernelModeDriver10.0 57 | Utility 58 | Package 59 | true 60 | Desktop 61 | 62 | 63 | Windows10 64 | false 65 | WindowsKernelModeDriver10.0 66 | Utility 67 | Package 68 | true 69 | 70 | 71 | Windows7 72 | true 73 | WindowsKernelModeDriver10.0 74 | Utility 75 | Package 76 | true 77 | Desktop 78 | 79 | 80 | Windows10 81 | false 82 | WindowsKernelModeDriver10.0 83 | Utility 84 | Package 85 | true 86 | 87 | 88 | Windows7 89 | true 90 | WindowsKernelModeDriver10.0 91 | Utility 92 | Package 93 | true 94 | Desktop 95 | 96 | 97 | Windows10 98 | false 99 | WindowsKernelModeDriver10.0 100 | Utility 101 | Package 102 | true 103 | 104 | 105 | Windows7 106 | true 107 | WindowsKernelModeDriver10.0 108 | Utility 109 | Package 110 | true 111 | Desktop 112 | 113 | 114 | Windows10 115 | false 116 | WindowsKernelModeDriver10.0 117 | Utility 118 | Package 119 | true 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | DbgengKernelDebugger 131 | 132 | 133 | 134 | False 135 | False 136 | True 137 | 138 | 133563 139 | $(SolutionDir)$(ConfigurationName)\$(Platform)\ 140 | 141 | 142 | DbgengKernelDebugger 143 | 144 | 145 | 146 | False 147 | False 148 | True 149 | 150 | 133563 151 | 152 | 153 | DbgengKernelDebugger 154 | 155 | 156 | 157 | False 158 | False 159 | True 160 | 161 | 133563 162 | $(SolutionDir)$(ConfigurationName)\$(Platform)\ 163 | 164 | 165 | DbgengKernelDebugger 166 | 167 | 168 | 169 | False 170 | False 171 | True 172 | 173 | 133563 174 | 175 | 176 | DbgengKernelDebugger 177 | 178 | 179 | 180 | False 181 | False 182 | True 183 | 184 | 133563 185 | 186 | 187 | DbgengKernelDebugger 188 | 189 | 190 | 191 | False 192 | False 193 | True 194 | 195 | 133563 196 | 197 | 198 | DbgengKernelDebugger 199 | 200 | 201 | 202 | False 203 | False 204 | True 205 | 206 | 133563 207 | 208 | 209 | DbgengKernelDebugger 210 | 211 | 212 | 213 | False 214 | False 215 | True 216 | 217 | 133563 218 | 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /MiniFSWatcherPackage/MiniFSWatcherPackage.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {8E41214B-6785-4CFE-B992-037D68949A14} 6 | inf;inv;inx;mof;mc; 7 | 8 | 9 | -------------------------------------------------------------------------------- /MiniFSWatcherTest/BasicFileEventTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System.IO; 4 | using CenterDevice.MiniFSWatcher; 5 | using System.Threading.Tasks; 6 | using System.Diagnostics; 7 | using System.Threading; 8 | 9 | namespace CenterDevice.MiniFSWatcherTest 10 | { 11 | [TestClass] 12 | public class BasicFileEventTest: FileEventTest 13 | { 14 | [TestInitialize] 15 | public void Setup() 16 | { 17 | Initialize(); 18 | } 19 | 20 | [TestMethod] 21 | public void TestCreateFile() 22 | { 23 | var result = new TaskCompletionSource>(); 24 | filter.OnCreate += (path, process) => 25 | { 26 | result.SetResult(new Tuple(path, process)); 27 | }; 28 | 29 | var filePath = Path.Combine(watchDir, Path.GetRandomFileName()); 30 | File.Create(filePath).Dispose(); 31 | 32 | var callbackData = result.Task.Result; 33 | Assert.AreEqual(filePath, callbackData.Item1); 34 | Assert.AreEqual((ulong) Process.GetCurrentProcess().Id, callbackData.Item2); 35 | } 36 | 37 | [TestMethod] 38 | public void TestDeleteFile() 39 | { 40 | var result = new TaskCompletionSource>(); 41 | filter.OnDelete += (path, process) => 42 | { 43 | result.SetResult(new Tuple(path, process)); 44 | }; 45 | 46 | File.Delete(tmpFile); 47 | 48 | var callbackData = result.Task.Result; 49 | Assert.AreEqual(tmpFile, callbackData.Item1); 50 | Assert.AreEqual((ulong)Process.GetCurrentProcess().Id, callbackData.Item2); 51 | } 52 | 53 | [TestMethod] 54 | public void TestRenameFile() 55 | { 56 | var result = new TaskCompletionSource>(); 57 | filter.OnRenameOrMove += (path, oldPath, process) => 58 | { 59 | result.SetResult(new Tuple(path, oldPath, process)); 60 | }; 61 | 62 | var newPath = Path.Combine(watchDir, Path.GetRandomFileName()); 63 | File.Move(tmpFile, newPath); 64 | 65 | var callbackData = result.Task.Result; 66 | Assert.AreEqual(newPath, callbackData.Item1); 67 | Assert.AreEqual(tmpFile, callbackData.Item2); 68 | Assert.AreEqual((ulong)Process.GetCurrentProcess().Id, callbackData.Item3); 69 | } 70 | 71 | [TestMethod] 72 | public void TestChangeFileAppend() 73 | { 74 | var result = new TaskCompletionSource>(); 75 | filter.OnChange += (path, process) => 76 | { 77 | result.SetResult(new Tuple(path, process)); 78 | }; 79 | 80 | File.AppendAllText(tmpFile, "Some text"); 81 | 82 | var callbackData = result.Task.Result; 83 | Assert.AreEqual(tmpFile, callbackData.Item1); 84 | Assert.AreEqual((ulong)Process.GetCurrentProcess().Id, callbackData.Item2); 85 | } 86 | 87 | [TestMethod] 88 | public void TestChangeFileOverwrite() 89 | { 90 | var result = new TaskCompletionSource>(); 91 | filter.OnChange += (path, process) => 92 | { 93 | result.SetResult(new Tuple(path, process)); 94 | }; 95 | 96 | File.WriteAllText(tmpFile, "Some text"); 97 | 98 | var callbackData = result.Task.Result; 99 | Assert.AreEqual(tmpFile, callbackData.Item1); 100 | Assert.AreEqual((ulong)Process.GetCurrentProcess().Id, callbackData.Item2); 101 | } 102 | 103 | [TestMethod] 104 | public void TestOverwriteFile() 105 | { 106 | var result = new TaskCompletionSource>(); 107 | filter.OnChange += (path, process) => 108 | { 109 | result.SetResult(new Tuple(path, process)); 110 | }; 111 | 112 | File.Create(tmpFile).Dispose(); 113 | 114 | var callbackData = result.Task.Result; 115 | Assert.AreEqual(tmpFile, callbackData.Item1); 116 | Assert.AreEqual((ulong)Process.GetCurrentProcess().Id, callbackData.Item2); 117 | } 118 | 119 | [TestMethod] 120 | public void TestReplaceFile() 121 | { 122 | var unobservedFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); 123 | File.Create(unobservedFile).Dispose(); 124 | 125 | var result = new TaskCompletionSource>(); 126 | filter.OnRenameOrMove += (path, oldPath, process) => 127 | { 128 | result.SetResult(new Tuple(path, process)); 129 | }; 130 | 131 | var moved = NativeMethods.MoveFileEx(unobservedFile, tmpFile, NativeMethods.MoveFileFlags.MOVEFILE_REPLACE_EXISTING); 132 | 133 | var callbackData = result.Task.Result; 134 | Assert.IsTrue(moved); 135 | Assert.AreEqual(tmpFile, callbackData.Item1); 136 | Assert.AreEqual((ulong)Process.GetCurrentProcess().Id, callbackData.Item2); 137 | } 138 | 139 | [TestCleanup] 140 | public void Teardown() 141 | { 142 | filter.Disconnect(); 143 | Directory.Delete(watchDir, true); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /MiniFSWatcherTest/FileEventTest.cs: -------------------------------------------------------------------------------- 1 | using CenterDevice.MiniFSWatcher; 2 | using System.IO; 3 | 4 | namespace CenterDevice.MiniFSWatcherTest 5 | { 6 | public class FileEventTest 7 | { 8 | protected string watchDir = null; 9 | protected string tmpFile = null; 10 | 11 | protected EventWatcher filter = null; 12 | 13 | protected void Initialize() 14 | { 15 | watchDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); 16 | Directory.CreateDirectory(watchDir); 17 | 18 | tmpFile = Path.Combine(watchDir, Path.GetRandomFileName()); 19 | File.Create(tmpFile).Dispose(); 20 | 21 | filter = new EventWatcher(); 22 | filter.Connect(); 23 | filter.WatchPath(watchDir + "*"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MiniFSWatcherTest/MiniFSWatcherTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {6AEC70E0-4191-4373-ABC3-EBAD6B1BBE50} 7 | Library 8 | Properties 9 | CenterDevice.MiniFSWatcherTest 10 | MiniFSWatcherTest 11 | v4.5.2 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {01bfc4ee-8fd7-44ce-a00e-da542116f27d} 61 | MiniFSWatcher 62 | 63 | 64 | 65 | 66 | 67 | 68 | False 69 | 70 | 71 | False 72 | 73 | 74 | False 75 | 76 | 77 | False 78 | 79 | 80 | 81 | 82 | 83 | 84 | 91 | -------------------------------------------------------------------------------- /MiniFSWatcherTest/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace CenterDevice.MiniFSWatcherTest 5 | { 6 | class NativeMethods 7 | { 8 | [Flags] 9 | internal enum MoveFileFlags 10 | { 11 | MOVEFILE_REPLACE_EXISTING = 0x00000001, 12 | MOVEFILE_COPY_ALLOWED = 0x00000002, 13 | MOVEFILE_DELAY_UNTIL_REBOOT = 0x00000004, 14 | MOVEFILE_WRITE_THROUGH = 0x00000008, 15 | MOVEFILE_CREATE_HARDLINK = 0x00000010, 16 | MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x00000020 17 | } 18 | 19 | [return: MarshalAs(UnmanagedType.Bool)] 20 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 21 | internal static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, MoveFileFlags dwFlags); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /MiniFSWatcherTest/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("minispyTest")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("minispyTest")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("6aec70e0-4191-4373-abc3-ebad6b1bbe50")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MiniFSWatcher 2 | 3 | MiniFSWatcher is a file system event watcher for C# based on the [minispy](https://github.com/Microsoft/Windows-driver-samples/tree/master/filesys/miniFilter/minispy) driver example from Microsoft. 4 | Compared to the default `FileSystemWatcher` class, MiniFSWatcher provides the following benefits: 5 | 6 | ### Capturing file moved events 7 | 8 | The default `FileSystemWatcher` has no notion of moved files, it'll only generate "created" and "deleted" 9 | events instead. `MiniFSWatcher` will let you track file movement within one partition. 10 | 11 | ### Reducing the amount of changed events 12 | 13 | With the `AggregateEvents` option, you'll only receive one file event when the respective file handle is closed 14 | and thus no more consecutive changes will occure. If multiple write operations were performed, only one "changed" 15 | event is triggered. If a file is created _and_ changed (i.e. due to a copy operation), you'll only receive one 16 | "created" event. 17 | 18 | ### Getting information about the process causing the change 19 | 20 | Sometimes it is useful to know who caused the change, for example to ignore changes performed by a certain 21 | application. `MiniFSWatcher` provides the ID of the causing process with every event and further allows 22 | to directly filter out all events caused by its own process ID. 23 | 24 | # Usage 25 | 26 | The following example shows how to use MiniFSWatcher to watch a directory and all subdirectories. 27 | 28 | var eventWatcher = new EventWatcher(); 29 | 30 | eventWatcher.OnRenameOrMove += (filename, oldFilename, process) => 31 | { 32 | Console.WriteLine("File " + oldFilename + " has been moved to " + filename + " by process " + process ); 33 | }; 34 | 35 | eventWatcher.Connect(); 36 | eventWatcher.WatchPath("C:\\Users\\MyUser\\*"); 37 | 38 | # Installation 39 | 40 | MiniFSWatcher consists of a user mode C# library and a minifilter driver running in kernel mode. 41 | Both components need to be installed to observe file system events. 42 | 43 | ## Installing the user mode library 44 | 45 | You can install the user mode library from [nuget](https://www.nuget.org/packages/MiniFSWatcher/) or simply add the `MiniFSWatcher` project to your project references. 46 | 47 | ## Installing the kernel mode driver 48 | 49 | The kernel driver needs to be compiled and installed manually. Therefore, 50 | make sure you have the [Windows Driver Kit (WDK) 10](https://msdn.microsoft.com/en-us/library/windows/hardware/ff557573(v=vs.85).aspx) installed. 51 | After compiling the driver with VS, you can install the driver with the following command 52 | 53 | RUNDLL32.EXE SETUPAPI.DLL,InstallHinfSection DefaultInstall 132 ./minifswatcher.inf 54 | 55 | The driver will be loaded automatically after reboot. To load it manually without reboot, run 56 | 57 | fltmc.exe load minifswatcher 58 | 59 | If you don't have a valid code signing certificate and try to install the driver on 64bit Windows, 60 | you need to enable test signed drivers as described [here](https://msdn.microsoft.com/en-us/library/windows/hardware/ff553484(v=vs.85).aspx). 61 | 62 | Please make sure to always compile and install the correct driver version (32/64bit) depending on your operating system! 63 | 64 | To uninstall the driver, run 65 | 66 | RUNDLL32.EXE SETUPAPI.DLL,InstallHinfSection DefaultUninstall 132 ./minifswatcher.inf 67 | --------------------------------------------------------------------------------