aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/dtf/CSharp.Build.props13
-rw-r--r--src/dtf/Cpp.Build.props104
-rw-r--r--src/dtf/Custom.Build.props20
-rw-r--r--src/dtf/Directory.Build.props29
-rw-r--r--src/dtf/Directory.Build.targets56
-rw-r--r--src/dtf/README.md2
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/AssemblyInfo.cs5
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/CabEngine.cs164
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/CabException.cs154
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/CabFileInfo.cs141
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/CabInfo.cs82
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/CabPacker.cs653
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/CabUnpacker.cs566
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/CabWorker.cs337
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/Errors.resourcesbin0 -> 1465 bytes
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/Errors.txt35
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/HandleManager.cs76
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/NativeMethods.cs407
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/WixToolset.Dtf.Compression.Cab.csproj26
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/AssemblyInfo.cs5
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ConcatStream.cs157
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/CrcStream.cs250
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/WixToolset.Dtf.Compression.Zip.csproj21
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ZipCompressionMethod.cs80
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ZipEngine.cs478
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ZipException.cs60
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ZipFileInfo.cs104
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ZipFormat.cs697
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ZipInfo.cs82
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ZipPacker.cs489
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ZipUnpacker.cs336
-rw-r--r--src/dtf/WixToolset.Dtf.Compression/ArchiveException.cs57
-rw-r--r--src/dtf/WixToolset.Dtf.Compression/ArchiveFileInfo.cs430
-rw-r--r--src/dtf/WixToolset.Dtf.Compression/ArchiveFileStreamContext.cs664
-rw-r--r--src/dtf/WixToolset.Dtf.Compression/ArchiveInfo.cs781
-rw-r--r--src/dtf/WixToolset.Dtf.Compression/ArchiveProgressEventArgs.cs307
-rw-r--r--src/dtf/WixToolset.Dtf.Compression/ArchiveProgressType.cs69
-rw-r--r--src/dtf/WixToolset.Dtf.Compression/BasicUnpackStreamContext.cs90
-rw-r--r--src/dtf/WixToolset.Dtf.Compression/CargoStream.cs192
-rw-r--r--src/dtf/WixToolset.Dtf.Compression/Compression.cd175
-rw-r--r--src/dtf/WixToolset.Dtf.Compression/CompressionEngine.cs371
-rw-r--r--src/dtf/WixToolset.Dtf.Compression/CompressionLevel.cs31
-rw-r--r--src/dtf/WixToolset.Dtf.Compression/DuplicateStream.cs212
-rw-r--r--src/dtf/WixToolset.Dtf.Compression/IPackStreamContext.cs117
-rw-r--r--src/dtf/WixToolset.Dtf.Compression/IUnpackStreamContext.cs71
-rw-r--r--src/dtf/WixToolset.Dtf.Compression/OffsetStream.cs206
-rw-r--r--src/dtf/WixToolset.Dtf.Compression/SafeNativeMethods.cs22
-rw-r--r--src/dtf/WixToolset.Dtf.Compression/WixToolset.Dtf.Compression.csproj21
-rw-r--r--src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.csproj44
-rw-r--r--src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.nuspec18
-rw-r--r--src/dtf/WixToolset.Dtf.MSBuild/build/WixToolset.Dtf.MSBuild.props8
-rw-r--r--src/dtf/WixToolset.Dtf.MSBuild/tools/wix.ca.targets123
-rw-r--r--src/dtf/WixToolset.Dtf.Resources/AssemblyInfo.cs5
-rw-r--r--src/dtf/WixToolset.Dtf.Resources/BitmapResource.cs57
-rw-r--r--src/dtf/WixToolset.Dtf.Resources/FixedFileVersionInfo.cs183
-rw-r--r--src/dtf/WixToolset.Dtf.Resources/GroupIconInfo.cs119
-rw-r--r--src/dtf/WixToolset.Dtf.Resources/GroupIconResource.cs120
-rw-r--r--src/dtf/WixToolset.Dtf.Resources/NativeMethods.cs55
-rw-r--r--src/dtf/WixToolset.Dtf.Resources/Resource.cs225
-rw-r--r--src/dtf/WixToolset.Dtf.Resources/ResourceCollection.cs340
-rw-r--r--src/dtf/WixToolset.Dtf.Resources/ResourceType.cs198
-rw-r--r--src/dtf/WixToolset.Dtf.Resources/VersionEnums.cs86
-rw-r--r--src/dtf/WixToolset.Dtf.Resources/VersionInfo.cs270
-rw-r--r--src/dtf/WixToolset.Dtf.Resources/VersionResource.cs415
-rw-r--r--src/dtf/WixToolset.Dtf.Resources/VersionStringTable.cs231
-rw-r--r--src/dtf/WixToolset.Dtf.Resources/WixToolset.Dtf.Resources.csproj17
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/AssemblyInfo.cs6
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Attributes.cs60
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Entities.cs150
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QDatabase.cs214
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QRecord.cs501
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QTable.cs296
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Query.cs992
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/WixToolset.Dtf.WindowsInstaller.Linq.csproj21
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller.Package/InstallPackage.cs1169
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller.Package/InstallPath.cs1073
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller.Package/PatchPackage.cs259
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller.Package/TransformInfo.cs154
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller.Package/WixToolset.Dtf.WindowsInstaller.Package.csproj23
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnCollection.cs333
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnEnums.cs689
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnInfo.cs297
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/ComponentInfo.cs276
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/ComponentInstallation.cs382
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionAttribute.cs55
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionProxy.cs321
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/Database.cs933
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/DatabaseQuery.cs412
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/DatabaseTransform.cs278
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/EmbeddedUIProxy.cs231
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/Enums.cs909
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/Errors.resourcesbin0 -> 31461 bytes
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/Errors.txt404
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/Exceptions.cs573
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/ExternalUIHandler.cs223
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/FeatureInfo.cs497
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/FeatureInstallation.cs174
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/Handle.cs154
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/IEmbeddedUI.cs67
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/InstallCost.cs67
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/Installation.cs100
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/InstallationPart.cs82
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/Installer.cs890
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/InstallerAdvertise.cs270
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/InstallerUtils.cs472
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/MediaDisk.cs58
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/NativeMethods.cs309
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/PatchInstallation.cs413
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/ProductInstallation.cs801
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/Record.cs1048
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/RecordStream.cs92
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/RemotableNativeMethods.cs1171
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/Session.cs946
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/ShortcutTarget.cs104
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/SourceList.cs525
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/SourceMediaList.cs229
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/SummaryInfo.cs612
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/TableCollection.cs192
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/TableInfo.cs264
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/Transaction.cs201
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/ValidationErrorInfo.cs46
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/View.cs625
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/WindowsInstaller.cd943
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/WixToolset.Dtf.WindowsInstaller.csproj30
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/customactiondata.cs469
-rw-r--r--src/dtf/WixToolset.Dtf.sln281
-rw-r--r--src/dtf/WixToolsetTests.Dtf.Compression.Cab/CabTest.cs1165
-rw-r--r--src/dtf/WixToolsetTests.Dtf.Compression.Cab/WixToolsetTests.Dtf.Compression.Cab.csproj32
-rw-r--r--src/dtf/WixToolsetTests.Dtf.Compression.Zip/WixToolsetTests.Dtf.Compression.Zip.csproj30
-rw-r--r--src/dtf/WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs518
-rw-r--r--src/dtf/WixToolsetTests.Dtf.Compression/CompressionTestUtil.cs649
-rw-r--r--src/dtf/WixToolsetTests.Dtf.Compression/MisbehavingStreamContext.cs202
-rw-r--r--src/dtf/WixToolsetTests.Dtf.Compression/OptionStreamContext.cs42
-rw-r--r--src/dtf/WixToolsetTests.Dtf.Compression/WixToolsetTests.Dtf.Compression.csproj31
-rw-r--r--src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/CustomActionTest.cs206
-rw-r--r--src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj28
-rw-r--r--src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/LinqTest.cs509
-rw-r--r--src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj31
-rw-r--r--src/dtf/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs173
-rw-r--r--src/dtf/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs238
-rw-r--r--src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs409
-rw-r--r--src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs161
-rw-r--r--src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs174
-rw-r--r--src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj34
-rw-r--r--src/dtf/appveyor.cmd28
-rw-r--r--src/dtf/appveyor.yml42
-rw-r--r--src/dtf/nuget.config7
-rw-r--r--src/internal/CSharp.Build.props13
-rw-r--r--src/internal/Cpp.Build.props104
-rw-r--r--src/internal/Directory.Build.props29
-rw-r--r--src/internal/Directory.Build.targets56
-rw-r--r--src/internal/MessagesToMessages/MessagesToMessages.csproj8
-rw-r--r--src/internal/MessagesToMessages/Program.cs261
-rw-r--r--src/internal/MessagesToMessages/Properties/launchSettings.json9
-rw-r--r--src/internal/MessagesToMessages/SimpleJson.cs2127
-rw-r--r--src/internal/README.md2
-rw-r--r--src/internal/TablesAndTuples/ColumnDefinitionEnums.cs56
-rw-r--r--src/internal/TablesAndTuples/Program.cs528
-rw-r--r--src/internal/TablesAndTuples/SimpleJson.cs2127
-rw-r--r--src/internal/TablesAndTuples/TablesAndTuples.csproj8
-rw-r--r--src/internal/TablesAndTuples/WixColumnDefinition.cs296
-rw-r--r--src/internal/TablesAndTuples/WixTableDefinition.cs169
-rw-r--r--src/internal/WixBuildTools.MsgGen/AssemblyInfo.cs8
-rw-r--r--src/internal/WixBuildTools.MsgGen/GenerateMessageFiles.cs250
-rw-r--r--src/internal/WixBuildTools.MsgGen/MsgGen.cs261
-rw-r--r--src/internal/WixBuildTools.MsgGen/WixBuildTools.MsgGen.csproj27
-rw-r--r--src/internal/WixBuildTools.MsgGen/Xsd/messages.xsd101
-rw-r--r--src/internal/WixBuildTools.MsgGen/build/WixBuildTools.MsgGen.targets102
-rw-r--r--src/internal/WixBuildTools.MsgGen/buildCrossTargeting/WixBuildTools.MsgGen.targets6
-rw-r--r--src/internal/WixBuildTools.TestSupport.Native/AssemblyInfo.cpp17
-rw-r--r--src/internal/WixBuildTools.TestSupport.Native/NativeAssert.h85
-rw-r--r--src/internal/WixBuildTools.TestSupport.Native/WixBuildTools.TestSupport.Native.nuspec26
-rw-r--r--src/internal/WixBuildTools.TestSupport.Native/WixBuildTools.TestSupport.Native.vcxproj85
-rw-r--r--src/internal/WixBuildTools.TestSupport.Native/WixBuildTools.TestSupport.Native.vcxproj.filters33
-rw-r--r--src/internal/WixBuildTools.TestSupport.Native/build/WixBuildTools.TestSupport.Native.props29
-rw-r--r--src/internal/WixBuildTools.TestSupport.Native/build/WixBuildTools.TestSupport.Native.targets19
-rw-r--r--src/internal/WixBuildTools.TestSupport.Native/packages.config17
-rw-r--r--src/internal/WixBuildTools.TestSupport.Native/precomp.cpp3
-rw-r--r--src/internal/WixBuildTools.TestSupport.Native/precomp.h11
-rw-r--r--src/internal/WixBuildTools.TestSupport/Builder.cs70
-rw-r--r--src/internal/WixBuildTools.TestSupport/DisposableFileSystem.cs93
-rw-r--r--src/internal/WixBuildTools.TestSupport/DotnetRunner.cs57
-rw-r--r--src/internal/WixBuildTools.TestSupport/ExternalExecutable.cs88
-rw-r--r--src/internal/WixBuildTools.TestSupport/ExternalExecutableResult.cs17
-rw-r--r--src/internal/WixBuildTools.TestSupport/FakeBuildEngine.cs33
-rw-r--r--src/internal/WixBuildTools.TestSupport/MsbuildRunner.cs168
-rw-r--r--src/internal/WixBuildTools.TestSupport/MsbuildRunnerResult.cs19
-rw-r--r--src/internal/WixBuildTools.TestSupport/Pushd.cs46
-rw-r--r--src/internal/WixBuildTools.TestSupport/Query.cs172
-rw-r--r--src/internal/WixBuildTools.TestSupport/RobocopyRunner.cs16
-rw-r--r--src/internal/WixBuildTools.TestSupport/SucceededException.cs18
-rw-r--r--src/internal/WixBuildTools.TestSupport/TestData.cs16
-rw-r--r--src/internal/WixBuildTools.TestSupport/TestDataFolderFileSystem.cs42
-rw-r--r--src/internal/WixBuildTools.TestSupport/VswhereRunner.cs41
-rw-r--r--src/internal/WixBuildTools.TestSupport/WixAssert.cs47
-rw-r--r--src/internal/WixBuildTools.TestSupport/WixBuildTools.TestSupport.csproj31
-rw-r--r--src/internal/WixBuildTools.XsdGen/AssemblyInfo.cs9
-rw-r--r--src/internal/WixBuildTools.XsdGen/CodeDomInterfaces.cs96
-rw-r--r--src/internal/WixBuildTools.XsdGen/CodeDomReader.cs159
-rw-r--r--src/internal/WixBuildTools.XsdGen/ElementCollection.cs642
-rw-r--r--src/internal/WixBuildTools.XsdGen/StronglyTypedClasses.cs1498
-rw-r--r--src/internal/WixBuildTools.XsdGen/WixBuildTools.XsdGen.csproj28
-rw-r--r--src/internal/WixBuildTools.XsdGen/XsdGen.cs124
-rw-r--r--src/internal/WixBuildTools.XsdGen/build/WixBuildTools.XsdGen.targets67
-rw-r--r--src/internal/WixBuildTools.XsdGen/buildCrossTargeting/WixBuildTools.XsdGen.targets6
-rw-r--r--src/internal/WixBuildTools.sln81
-rw-r--r--src/internal/WixBuildTools.v3.ncrunchsolution6
-rw-r--r--src/internal/appveyor.cmd13
-rw-r--r--src/internal/appveyor.yml42
-rw-r--r--src/internal/nuget.config8
-rw-r--r--src/libs/dutil/CustomizedNativeRecommendedRules.ruleset8
-rw-r--r--src/libs/dutil/Directory.Build.props26
-rw-r--r--src/libs/dutil/Directory.Build.targets73
-rw-r--r--src/libs/dutil/Directory.csproj.props13
-rw-r--r--src/libs/dutil/Directory.vcxproj.props115
-rw-r--r--src/libs/dutil/NativeMultiTargeting.Build.props10
-rw-r--r--src/libs/dutil/README.md2
-rw-r--r--src/libs/dutil/WixToolset.DUtil/acl2util.cpp135
-rw-r--r--src/libs/dutil/WixToolset.DUtil/aclutil.cpp1044
-rw-r--r--src/libs/dutil/WixToolset.DUtil/apputil.cpp124
-rw-r--r--src/libs/dutil/WixToolset.DUtil/apuputil.cpp700
-rw-r--r--src/libs/dutil/WixToolset.DUtil/atomutil.cpp1297
-rw-r--r--src/libs/dutil/WixToolset.DUtil/buffutil.cpp529
-rw-r--r--src/libs/dutil/WixToolset.DUtil/build/WixToolset.DUtil.props28
-rw-r--r--src/libs/dutil/WixToolset.DUtil/butil.cpp257
-rw-r--r--src/libs/dutil/WixToolset.DUtil/cabcutil.cpp1539
-rw-r--r--src/libs/dutil/WixToolset.DUtil/cabutil.cpp617
-rw-r--r--src/libs/dutil/WixToolset.DUtil/certutil.cpp342
-rw-r--r--src/libs/dutil/WixToolset.DUtil/conutil.cpp673
-rw-r--r--src/libs/dutil/WixToolset.DUtil/cryputil.cpp404
-rw-r--r--src/libs/dutil/WixToolset.DUtil/deputil.cpp699
-rw-r--r--src/libs/dutil/WixToolset.DUtil/dictutil.cpp784
-rw-r--r--src/libs/dutil/WixToolset.DUtil/dirutil.cpp441
-rw-r--r--src/libs/dutil/WixToolset.DUtil/dlutil.cpp802
-rw-r--r--src/libs/dutil/WixToolset.DUtil/dpiutil.cpp274
-rw-r--r--src/libs/dutil/WixToolset.DUtil/dutil.cpp524
-rw-r--r--src/libs/dutil/WixToolset.DUtil/dutil.nuspec27
-rw-r--r--src/libs/dutil/WixToolset.DUtil/dutil.vcxproj183
-rw-r--r--src/libs/dutil/WixToolset.DUtil/dutil.vcxproj.filters372
-rw-r--r--src/libs/dutil/WixToolset.DUtil/eseutil.cpp1340
-rw-r--r--src/libs/dutil/WixToolset.DUtil/fileutil.cpp2032
-rw-r--r--src/libs/dutil/WixToolset.DUtil/gdiputil.cpp227
-rw-r--r--src/libs/dutil/WixToolset.DUtil/guidutil.cpp54
-rw-r--r--src/libs/dutil/WixToolset.DUtil/iis7util.cpp535
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/aclutil.h154
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/apputil.h45
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/apuputil.h87
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/atomutil.h146
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/buffutil.h91
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/butil.h60
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/cabcutil.h62
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/cabutil.h56
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/certutil.h66
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/conutil.h80
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/cryputil.h106
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/deputil.h147
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/dictutil.h69
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/dirutil.h59
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/dlutil.h59
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/dpiutil.h120
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/dutil.h190
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/dutilsources.h76
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/eseutil.h223
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/fileutil.h247
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/gdiputil.h39
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/guidutil.h21
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/iis7util.h222
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/inetutil.h39
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/iniutil.h79
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/jsonutil.h112
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/locutil.h120
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/logutil.h192
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/memutil.h80
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/metautil.h52
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/monutil.h108
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/osutil.h42
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/pathutil.h252
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/perfutil.h24
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/polcutil.h39
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/procutil.h75
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/regutil.h246
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/resrutil.h43
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/reswutil.h31
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/rexutil.h54
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/rmutil.h46
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/rssutil.h89
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/sceutil.h273
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/sczutil.h30
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/shelutil.h47
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/sqlutil.h136
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/srputil.h45
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/strutil.h316
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/svcutil.h21
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/thmutil.h765
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/timeutil.h38
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/uncutil.h20
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/uriutil.h100
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/userutil.h32
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/verutil.h93
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/wiutil.h402
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/wuautil.h19
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/xmlutil.h167
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inetutil.cpp155
-rw-r--r--src/libs/dutil/WixToolset.DUtil/iniutil.cpp768
-rw-r--r--src/libs/dutil/WixToolset.DUtil/jsonutil.cpp687
-rw-r--r--src/libs/dutil/WixToolset.DUtil/locutil.cpp628
-rw-r--r--src/libs/dutil/WixToolset.DUtil/logutil.cpp961
-rw-r--r--src/libs/dutil/WixToolset.DUtil/memutil.cpp336
-rw-r--r--src/libs/dutil/WixToolset.DUtil/metautil.cpp378
-rw-r--r--src/libs/dutil/WixToolset.DUtil/monutil.cpp2019
-rw-r--r--src/libs/dutil/WixToolset.DUtil/osutil.cpp251
-rw-r--r--src/libs/dutil/WixToolset.DUtil/packages.config7
-rw-r--r--src/libs/dutil/WixToolset.DUtil/path2utl.cpp104
-rw-r--r--src/libs/dutil/WixToolset.DUtil/pathutil.cpp1083
-rw-r--r--src/libs/dutil/WixToolset.DUtil/perfutil.cpp82
-rw-r--r--src/libs/dutil/WixToolset.DUtil/polcutil.cpp126
-rw-r--r--src/libs/dutil/WixToolset.DUtil/precomp.h98
-rw-r--r--src/libs/dutil/WixToolset.DUtil/proc2utl.cpp83
-rw-r--r--src/libs/dutil/WixToolset.DUtil/proc3utl.cpp129
-rw-r--r--src/libs/dutil/WixToolset.DUtil/procutil.cpp522
-rw-r--r--src/libs/dutil/WixToolset.DUtil/regutil.cpp1035
-rw-r--r--src/libs/dutil/WixToolset.DUtil/resrutil.cpp266
-rw-r--r--src/libs/dutil/WixToolset.DUtil/reswutil.cpp386
-rw-r--r--src/libs/dutil/WixToolset.DUtil/rexutil.cpp601
-rw-r--r--src/libs/dutil/WixToolset.DUtil/rmutil.cpp488
-rw-r--r--src/libs/dutil/WixToolset.DUtil/rssutil.cpp647
-rw-r--r--src/libs/dutil/WixToolset.DUtil/sceutil.cpp2489
-rw-r--r--src/libs/dutil/WixToolset.DUtil/shelutil.cpp342
-rw-r--r--src/libs/dutil/WixToolset.DUtil/sqlutil.cpp1002
-rw-r--r--src/libs/dutil/WixToolset.DUtil/srputil.cpp252
-rw-r--r--src/libs/dutil/WixToolset.DUtil/strutil.cpp2824
-rw-r--r--src/libs/dutil/WixToolset.DUtil/svcutil.cpp59
-rw-r--r--src/libs/dutil/WixToolset.DUtil/thmutil.cpp5709
-rw-r--r--src/libs/dutil/WixToolset.DUtil/timeutil.cpp385
-rw-r--r--src/libs/dutil/WixToolset.DUtil/uncutil.cpp69
-rw-r--r--src/libs/dutil/WixToolset.DUtil/uriutil.cpp553
-rw-r--r--src/libs/dutil/WixToolset.DUtil/userutil.cpp300
-rw-r--r--src/libs/dutil/WixToolset.DUtil/verutil.cpp647
-rw-r--r--src/libs/dutil/WixToolset.DUtil/wiutil.cpp1629
-rw-r--r--src/libs/dutil/WixToolset.DUtil/wuautil.cpp104
-rw-r--r--src/libs/dutil/WixToolset.DUtil/xmlutil.cpp1332
-rw-r--r--src/libs/dutil/WixToolset.DUtil/xsd/thmutil.xsd1188
-rw-r--r--src/libs/dutil/appveyor.cmd24
-rw-r--r--src/libs/dutil/appveyor.yml44
-rw-r--r--src/libs/dutil/dutil.sln47
-rw-r--r--src/libs/dutil/nuget.config8
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/ApupUtilTests.cpp46
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/AssemblyInfo.cpp12
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/DUtilTests.cpp35
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj99
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters80
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/DictUtilTest.cpp191
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/DirUtilTests.cpp70
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/FileUtilTest.cpp125
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/GuidUtilTest.cpp60
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/IniUtilTest.cpp345
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/MemUtilTest.cpp505
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/MonUtilTest.cpp487
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp80
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/SceUtilTest.cpp488
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/StrUtilTest.cpp192
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/TestData/ApupUtilTests/FeedBv2.0.xml68
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/UnitTest.rc6
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/UriUtilTest.cpp98
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/VerUtilTests.cpp933
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/error.cpp26
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/error.h14
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/packages.config13
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/precomp.cpp3
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/precomp.h32
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/resource.h2
-rw-r--r--src/samples/Dtf/DDiff/CabDiffEngine.cs131
-rw-r--r--src/samples/Dtf/DDiff/DDiff.cs72
-rw-r--r--src/samples/Dtf/DDiff/DDiff.csproj39
-rw-r--r--src/samples/Dtf/DDiff/DirectoryDiffEngine.cs154
-rw-r--r--src/samples/Dtf/DDiff/FileDiffEngine.cs83
-rw-r--r--src/samples/Dtf/DDiff/IDiffEngine.cs68
-rw-r--r--src/samples/Dtf/DDiff/MsiDiffEngine.cs276
-rw-r--r--src/samples/Dtf/DDiff/MspDiffEngine.cs127
-rw-r--r--src/samples/Dtf/DDiff/TextFileDiffEngine.cs83
-rw-r--r--src/samples/Dtf/DDiff/VersionedFileDiffEngine.cs90
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/about.htm59
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/buildingcas.htm94
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/cabpack.htm63
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/cabs.htm101
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/cabwrapper.htm63
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/caconfig.htm83
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/caproxy.htm74
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/databases.htm120
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/debuggingcas.htm66
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/dependencies.htm88
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/filepatchwrapper.htm34
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/history.htm437
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/installutil.htm94
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/inventory.htm78
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/managedcas.htm53
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/msihelper.htm59
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/msiwrapper.htm80
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/packages.htm86
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/powerdiff.htm71
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/samplecas.htm84
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/samples.htm59
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/support.htm52
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/using.htm50
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/whatsnew.htm257
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/wifile.htm73
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/writingcas.htm114
-rw-r--r--src/samples/Dtf/Documents/Guide/DTF.hhc132
-rw-r--r--src/samples/Dtf/Documents/Guide/DTF.hhk126
-rw-r--r--src/samples/Dtf/Documents/Guide/DTF.hhp49
-rw-r--r--src/samples/Dtf/Documents/Guide/dtfguide.helpproj29
-rw-r--r--src/samples/Dtf/Documents/Guide/styles/presentation.css394
-rw-r--r--src/samples/Dtf/Documents/Reference/Compression.htm13
-rw-r--r--src/samples/Dtf/Documents/Reference/Compression1.pngbin0 -> 79032 bytes
-rw-r--r--src/samples/Dtf/Documents/Reference/Compression2.pngbin0 -> 68312 bytes
-rw-r--r--src/samples/Dtf/Documents/Reference/WindowsInstaller.htm14
-rw-r--r--src/samples/Dtf/Documents/Reference/WindowsInstaller1.pngbin0 -> 207803 bytes
-rw-r--r--src/samples/Dtf/Documents/Reference/WindowsInstaller2.pngbin0 -> 180714 bytes
-rw-r--r--src/samples/Dtf/Documents/Reference/WindowsInstaller3.pngbin0 -> 120423 bytes
-rw-r--r--src/samples/Dtf/Documents/Reference/dtfref.shfbproj75
-rw-r--r--src/samples/Dtf/Documents/Reference/helplink.js184
-rw-r--r--src/samples/Dtf/EmbeddedUI/AssemblyInfo.cs5
-rw-r--r--src/samples/Dtf/EmbeddedUI/EmbeddedUI.csproj56
-rw-r--r--src/samples/Dtf/EmbeddedUI/InstallProgressCounter.cs176
-rw-r--r--src/samples/Dtf/EmbeddedUI/SampleEmbeddedUI.cs132
-rw-r--r--src/samples/Dtf/EmbeddedUI/SetupWizard.xaml17
-rw-r--r--src/samples/Dtf/EmbeddedUI/SetupWizard.xaml.cs111
-rw-r--r--src/samples/Dtf/Inventory/Columns.resx252
-rw-r--r--src/samples/Dtf/Inventory/Features.cs107
-rw-r--r--src/samples/Dtf/Inventory/IInventoryDataProvider.cs67
-rw-r--r--src/samples/Dtf/Inventory/Inventory.cs1231
-rw-r--r--src/samples/Dtf/Inventory/Inventory.csproj42
-rw-r--r--src/samples/Dtf/Inventory/Inventory.icobin0 -> 4710 bytes
-rw-r--r--src/samples/Dtf/Inventory/Inventory.resx265
-rw-r--r--src/samples/Dtf/Inventory/components.cs626
-rw-r--r--src/samples/Dtf/Inventory/msiutils.cs46
-rw-r--r--src/samples/Dtf/Inventory/patches.cs227
-rw-r--r--src/samples/Dtf/Inventory/products.cs145
-rw-r--r--src/samples/Dtf/Inventory/xp.manifest15
-rw-r--r--src/samples/Dtf/ManagedCA/AssemblyInfo.cs5
-rw-r--r--src/samples/Dtf/ManagedCA/ManagedCA.csproj33
-rw-r--r--src/samples/Dtf/ManagedCA/SampleCAs.cs127
-rw-r--r--src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs711
-rw-r--r--src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.csproj33
-rw-r--r--src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.exe.manifest20
-rw-r--r--src/samples/Dtf/Tools/MakeSfxCA/app.config10
-rw-r--r--src/samples/Dtf/Tools/SfxCA/ClrHost.cpp262
-rw-r--r--src/samples/Dtf/Tools/SfxCA/EmbeddedUI.cpp281
-rw-r--r--src/samples/Dtf/Tools/SfxCA/EntryPoints.def140
-rw-r--r--src/samples/Dtf/Tools/SfxCA/EntryPoints.h162
-rw-r--r--src/samples/Dtf/Tools/SfxCA/Extract.cpp282
-rw-r--r--src/samples/Dtf/Tools/SfxCA/RemoteMsi.cpp629
-rw-r--r--src/samples/Dtf/Tools/SfxCA/RemoteMsiSession.h898
-rw-r--r--src/samples/Dtf/Tools/SfxCA/SfxCA.cpp363
-rw-r--r--src/samples/Dtf/Tools/SfxCA/SfxCA.rc10
-rw-r--r--src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj68
-rw-r--r--src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj.filters62
-rw-r--r--src/samples/Dtf/Tools/SfxCA/SfxUtil.cpp209
-rw-r--r--src/samples/Dtf/Tools/SfxCA/SfxUtil.h31
-rw-r--r--src/samples/Dtf/Tools/SfxCA/packages.config4
-rw-r--r--src/samples/Dtf/Tools/SfxCA/precomp.cpp3
-rw-r--r--src/samples/Dtf/Tools/SfxCA/precomp.h18
-rw-r--r--src/samples/Dtf/Tools/Tools.proj15
-rw-r--r--src/samples/Dtf/WiFile/WiFile.cs147
-rw-r--r--src/samples/Dtf/WiFile/WiFile.csproj27
-rw-r--r--src/samples/Dtf/XPack/AssemblyInfo.cs5
-rw-r--r--src/samples/Dtf/XPack/XPack.cs80
-rw-r--r--src/samples/Dtf/XPack/XPack.csproj27
-rw-r--r--src/signing.json13
-rw-r--r--src/version.json2
-rw-r--r--src/wix.snkbin0 -> 596 bytes
471 files changed, 121226 insertions, 1 deletions
diff --git a/src/dtf/CSharp.Build.props b/src/dtf/CSharp.Build.props
new file mode 100644
index 00000000..81d24ad1
--- /dev/null
+++ b/src/dtf/CSharp.Build.props
@@ -0,0 +1,13 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<!--
3 Do NOT modify this file. Update the canonical version in Home\repo-template\src\CSharp.Build.props
4 then update all of the repos.
5-->
6<Project>
7 <PropertyGroup>
8 <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
9 <SignAssembly>true</SignAssembly>
10 <AssemblyOriginatorKeyFile>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)wix.snk))</AssemblyOriginatorKeyFile>
11 <NBGV_EmitThisAssemblyClass>false</NBGV_EmitThisAssemblyClass>
12 </PropertyGroup>
13</Project>
diff --git a/src/dtf/Cpp.Build.props b/src/dtf/Cpp.Build.props
new file mode 100644
index 00000000..44a042c7
--- /dev/null
+++ b/src/dtf/Cpp.Build.props
@@ -0,0 +1,104 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project>
5 <PropertyGroup>
6 <Platform Condition=" '$(Platform)' == '' OR '$(Platform)' == 'AnyCPU' ">Win32</Platform>
7 <IntDir>$(BaseIntermediateOutputPath)$(Configuration)\$(Platform)\</IntDir>
8 <OutDir>$(OutputPath)$(Platform)\</OutDir>
9 </PropertyGroup>
10
11 <PropertyGroup Condition="'$(WindowsTargetPlatformVersion)'=='' AND '$(VisualStudioVersion)'>='15.0'">
12 <WindowsTargetPlatformVersion>$([Microsoft.Build.Utilities.ToolLocationHelper]::GetLatestSDKTargetPlatformVersion('Windows', '10.0'))</WindowsTargetPlatformVersion>
13 </PropertyGroup>
14
15 <ItemDefinitionGroup>
16 <ClCompile>
17 <DisableSpecificWarnings>$(DisableSpecificCompilerWarnings)</DisableSpecificWarnings>
18 <WarningLevel>Level4</WarningLevel>
19 <AdditionalIncludeDirectories>$(ProjectDir)inc;$(MSBuildProjectDirectory);$(IntDir);$(SqlCESdkIncludePath);$(ProjectAdditionalIncludeDirectories);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
20 <PreprocessorDefinitions>WIN32;_WINDOWS;_WIN32_MSI=500;_WIN32_WINNT=0x0501;$(ArmPreprocessorDefinitions);$(UnicodePreprocessorDefinitions);_CRT_STDIO_LEGACY_WIDE_SPECIFIERS;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
21 <PrecompiledHeader>Use</PrecompiledHeader>
22 <PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
23 <CallingConvention Condition="'$(Platform)'=='Win32'">StdCall</CallingConvention>
24 <TreatWarningAsError>true</TreatWarningAsError>
25 <ExceptionHandling>false</ExceptionHandling>
26 <AdditionalOptions>-YlprecompDefine</AdditionalOptions>
27 <AdditionalOptions Condition=" $(PlatformToolset.StartsWith('v14')) ">/Zc:threadSafeInit- %(AdditionalOptions)</AdditionalOptions>
28 <MultiProcessorCompilation Condition=" $(NUMBER_OF_PROCESSORS) &gt; 4 ">true</MultiProcessorCompilation>
29 </ClCompile>
30 <ResourceCompile>
31 <PreprocessorDefinitions>$(ArmPreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
32 <AdditionalIncludeDirectories>$(ProjectAdditionalResourceIncludeDirectories);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
33 </ResourceCompile>
34 <Lib>
35 <AdditionalLibraryDirectories>$(OutDir);$(AdditionalMultiTargetLibraryPath);$(ProjectAdditionalLibraryDirectories);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
36 </Lib>
37 <Link>
38 <SubSystem>$(ProjectSubSystem)</SubSystem>
39 <ModuleDefinitionFile>$(ProjectModuleDefinitionFile)</ModuleDefinitionFile>
40 <NoEntryPoint>$(ResourceOnlyDll)</NoEntryPoint>
41 <GenerateDebugInformation>true</GenerateDebugInformation>
42 <AdditionalDependencies>$(ProjectAdditionalLinkLibraries);advapi32.lib;comdlg32.lib;user32.lib;oleaut32.lib;gdi32.lib;shell32.lib;ole32.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
43 <AdditionalLibraryDirectories>$(OutDir);$(AdditionalMultiTargetLibraryPath);$(ArmLibraryDirectories);$(ProjectAdditionalLinkLibraryDirectories);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
44 <AdditionalOptions Condition=" $(PlatformToolset.StartsWith('v14')) ">/IGNORE:4099 %(AdditionalOptions)</AdditionalOptions>
45 </Link>
46 </ItemDefinitionGroup>
47
48 <ItemDefinitionGroup Condition=" '$(Platform)'=='Win32' and '$(PlatformToolset)'!='v100'">
49 <ClCompile>
50 <EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
51 </ClCompile>
52 </ItemDefinitionGroup>
53 <ItemDefinitionGroup Condition=" '$(Platform)'=='arm' ">
54 <ClCompile>
55 <CallingConvention>CDecl</CallingConvention>
56 </ClCompile>
57 </ItemDefinitionGroup>
58 <ItemDefinitionGroup Condition=" '$(ConfigurationType)'=='StaticLibrary' ">
59 <ClCompile>
60 <DebugInformationFormat>OldStyle</DebugInformationFormat>
61 <OmitDefaultLibName>true</OmitDefaultLibName>
62 <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
63 </ClCompile>
64 </ItemDefinitionGroup>
65 <ItemDefinitionGroup Condition=" '$(Configuration)'=='Debug' ">
66 <ClCompile>
67 <Optimization>Disabled</Optimization>
68 <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
69 <PreprocessorDefinitions>_DEBUG;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
70 <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
71 </ClCompile>
72 </ItemDefinitionGroup>
73 <ItemDefinitionGroup Condition=" '$(Configuration)'=='Debug' and '$(CLRSupport)'=='true' ">
74 <ClCompile>
75 <BasicRuntimeChecks></BasicRuntimeChecks>
76 <RuntimeLibrary>MultiThreadedDebugDll</RuntimeLibrary>
77 </ClCompile>
78 </ItemDefinitionGroup>
79 <ItemDefinitionGroup Condition=" '$(Configuration)'=='Release' ">
80 <ClCompile>
81 <Optimization>MinSpace</Optimization>
82 <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
83 <FunctionLevelLinking>true</FunctionLevelLinking>
84 <IntrinsicFunctions>true</IntrinsicFunctions>
85 <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
86 </ClCompile>
87 <Link>
88 <EnableCOMDATFolding>true</EnableCOMDATFolding>
89 <OptimizeReferences>true</OptimizeReferences>
90 </Link>
91 </ItemDefinitionGroup>
92 <ItemDefinitionGroup Condition=" '$(Configuration)'=='Release' and '$(CLRSupport)'=='true' ">
93 <ClCompile>
94 <BasicRuntimeChecks></BasicRuntimeChecks>
95 <RuntimeLibrary>MultiThreadedDll</RuntimeLibrary>
96 </ClCompile>
97 </ItemDefinitionGroup>
98 <ItemDefinitionGroup Condition=" '$(CLRSupport)'=='true' ">
99 <Link>
100 <KeyFile>$(LinkKeyFile)</KeyFile>
101 <DelaySign>$(LinkDelaySign)</DelaySign>
102 </Link>
103 </ItemDefinitionGroup>
104</Project>
diff --git a/src/dtf/Custom.Build.props b/src/dtf/Custom.Build.props
new file mode 100644
index 00000000..50429f77
--- /dev/null
+++ b/src/dtf/Custom.Build.props
@@ -0,0 +1,20 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project>
5 <PropertyGroup>
6 <DebugType>embedded</DebugType>
7 <PublishRepositoryUrl>true</PublishRepositoryUrl>
8 </PropertyGroup>
9
10 <PropertyGroup Condition=" '$(UsingMicrosoftNETSdk)'!='true' and '$(Configuration)' == 'Debug' ">
11 <DebugSymbols>true</DebugSymbols>
12 <Optimize>false</Optimize>
13 <DefineConstants>$(DefineConstants);DEBUG;TRACE</DefineConstants>
14 </PropertyGroup>
15 <PropertyGroup Condition=" '$(UsingMicrosoftNETSdk)'!='true' and '$(Configuration)' == 'Release' ">
16 <DebugSymbols>false</DebugSymbols>
17 <Optimize>true</Optimize>
18 <DefineConstants>$(DefineConstants);TRACE</DefineConstants>
19 </PropertyGroup>
20</Project>
diff --git a/src/dtf/Directory.Build.props b/src/dtf/Directory.Build.props
new file mode 100644
index 00000000..f83cc154
--- /dev/null
+++ b/src/dtf/Directory.Build.props
@@ -0,0 +1,29 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<!--
4 Do NOT modify this file. Update the canonical version in Home\repo-template\src\Directory.Build.props
5 then update all of the repos.
6-->
7<Project>
8 <PropertyGroup>
9 <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
10 <EnableSourceLink Condition=" '$(NCrunch)' == '1' ">false</EnableSourceLink>
11 <MSBuildWarningsAsMessages>MSB3246</MSBuildWarningsAsMessages>
12
13 <ProjectName Condition=" '$(ProjectName)' == '' ">$(MSBuildProjectName)</ProjectName>
14 <BaseOutputPath>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)..\build\))</BaseOutputPath>
15 <BaseIntermediateOutputPath>$(BaseOutputPath)obj\$(ProjectName)\</BaseIntermediateOutputPath>
16 <OutputPath>$(BaseOutputPath)$(Configuration)\</OutputPath>
17
18 <Authors>WiX Toolset Team</Authors>
19 <Company>WiX Toolset</Company>
20 <Copyright>Copyright (c) .NET Foundation and contributors. All rights reserved.</Copyright>
21 <PackageLicenseExpression>MS-RL</PackageLicenseExpression>
22 <Product>WiX Toolset</Product>
23 </PropertyGroup>
24
25 <Import Project="CSharp.Build.props" Condition=" '$(MSBuildProjectExtension)'=='.csproj' and Exists('CSharp.Build.props') " />
26 <Import Project="Cpp.Build.props" Condition=" Exists('Cpp.Build.props') And '$(MSBuildProjectExtension)'=='.vcxproj' " />
27 <Import Project="Wix.Build.props" Condition=" Exists('Wix.Build.props') And '$(MSBuildProjectExtension)'=='.wixproj' " />
28 <Import Project="Custom.Build.props" Condition=" Exists('Custom.Build.props') " />
29</Project>
diff --git a/src/dtf/Directory.Build.targets b/src/dtf/Directory.Build.targets
new file mode 100644
index 00000000..cb988931
--- /dev/null
+++ b/src/dtf/Directory.Build.targets
@@ -0,0 +1,56 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<!--
4 Do NOT modify this file. Update the canonical version in Home\repo-template\src\Directory.Build.targets
5 then update all of the repos.
6-->
7<!--
8 Replace PackageReferences with ProjectReferences when the projects can be found in .sln.
9 See the original here: https://github.com/dotnet/sdk/issues/1151#issuecomment-385133284
10-->
11<Project>
12 <PropertyGroup>
13 <CreateDocumentation Condition=" '$(CreateDocumentationFile)'!='true' ">false</CreateDocumentation>
14 <DocumentationFile Condition=" '$(CreateDocumentationFile)'=='true' ">$(OutputPath)\$(AssemblyName).xml</DocumentationFile>
15 </PropertyGroup>
16
17 <PropertyGroup>
18 <ReplacePackageReferences>true</ReplacePackageReferences>
19 <TheSolutionPath Condition=" '$(NCrunch)'=='' ">$(SolutionPath)</TheSolutionPath>
20 <TheSolutionPath Condition=" '$(NCrunch)'=='1' ">$(NCrunchOriginalSolutionPath)</TheSolutionPath>
21 </PropertyGroup>
22
23 <Choose>
24 <When Condition="$(ReplacePackageReferences) AND '$(TheSolutionPath)' != '' AND '$(TheSolutionPath)' != '*undefined*' AND Exists('$(TheSolutionPath)')">
25
26 <PropertyGroup>
27 <SolutionFileContent>$([System.IO.File]::ReadAllText($(TheSolutionPath)))</SolutionFileContent>
28 <SmartSolutionDir>$([System.IO.Path]::GetDirectoryName( $(TheSolutionPath) ))</SmartSolutionDir>
29 <RegexPattern>(?&lt;="[PackageName]", ")(.*)(?=", ")</RegexPattern>
30 </PropertyGroup>
31
32 <ItemGroup>
33 <!-- Keep the identity of the PackageReference -->
34 <SmartPackageReference Include="@(PackageReference)">
35 <PackageName>%(Identity)</PackageName>
36 <InSolution>$(SolutionFileContent.Contains('\%(Identity).csproj'))</InSolution>
37 </SmartPackageReference>
38
39 <!-- Filter them by mapping them to another ItemGroup using the WithMetadataValue item function -->
40 <PackageInSolution Include="@(SmartPackageReference->WithMetadataValue('InSolution', True))">
41 <Pattern>$(RegexPattern.Replace('[PackageName]','%(PackageName)') )</Pattern>
42 <SmartPath>$([System.Text.RegularExpressions.Regex]::Match('$(SolutionFileContent)', '%(Pattern)'))</SmartPath>
43 </PackageInSolution>
44
45 <ProjectReference Include="@(PackageInSolution->'$(SmartSolutionDir)\%(SmartPath)' )"/>
46
47 <!-- Remove the package references that are now referenced as projects -->
48 <PackageReference Remove="@(PackageInSolution->'%(PackageName)' )"/>
49 </ItemGroup>
50
51 </When>
52 </Choose>
53
54 <Import Project="Wix.Build.targets" Condition=" Exists('Wix.Build.targets') And '$(MSBuildProjectExtension)'=='.wixproj' " />
55 <Import Project="Custom.Build.targets" Condition=" Exists('Custom.Build.targets') " />
56</Project>
diff --git a/src/dtf/README.md b/src/dtf/README.md
new file mode 100644
index 00000000..80f3b0e5
--- /dev/null
+++ b/src/dtf/README.md
@@ -0,0 +1,2 @@
1# Dtf
2WixToolset.Dtf - managed libraries for the Windows Installer
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/AssemblyInfo.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/AssemblyInfo.cs
new file mode 100644
index 00000000..aea5bf2e
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/AssemblyInfo.cs
@@ -0,0 +1,5 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System.Diagnostics.CodeAnalysis;
4
5[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "WixToolset.Dtf.Compression.Cab")]
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/CabEngine.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/CabEngine.cs
new file mode 100644
index 00000000..ab135fd8
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/CabEngine.cs
@@ -0,0 +1,164 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Cab
4{
5 using System;
6 using System.IO;
7 using System.Collections.Generic;
8
9 /// <summary>
10 /// Engine capable of packing and unpacking archives in the cabinet format.
11 /// </summary>
12 public class CabEngine : CompressionEngine
13 {
14 private CabPacker packer;
15 private CabUnpacker unpacker;
16
17 /// <summary>
18 /// Creates a new instance of the cabinet engine.
19 /// </summary>
20 public CabEngine()
21 : base()
22 {
23 }
24
25 /// <summary>
26 /// Disposes of resources allocated by the cabinet engine.
27 /// </summary>
28 /// <param name="disposing">If true, the method has been called directly
29 /// or indirectly by a user's code, so managed and unmanaged resources
30 /// will be disposed. If false, the method has been called by the runtime
31 /// from inside the finalizer, and only unmanaged resources will be
32 /// disposed.</param>
33 protected override void Dispose(bool disposing)
34 {
35 if (disposing)
36 {
37 if (packer != null)
38 {
39 packer.Dispose();
40 packer = null;
41 }
42 if (unpacker != null)
43 {
44 unpacker.Dispose();
45 unpacker = null;
46 }
47 }
48
49 base.Dispose(disposing);
50 }
51
52 private CabPacker Packer
53 {
54 get
55 {
56 if (this.packer == null)
57 {
58 this.packer = new CabPacker(this);
59 }
60
61 return this.packer;
62 }
63 }
64
65 private CabUnpacker Unpacker
66 {
67 get
68 {
69 if (this.unpacker == null)
70 {
71 this.unpacker = new CabUnpacker(this);
72 }
73
74 return this.unpacker;
75 }
76 }
77
78 /// <summary>
79 /// Creates a cabinet or chain of cabinets.
80 /// </summary>
81 /// <param name="streamContext">A context interface to handle opening
82 /// and closing of cabinet and file streams.</param>
83 /// <param name="files">The paths of the files in the archive (not
84 /// external file paths).</param>
85 /// <param name="maxArchiveSize">The maximum number of bytes for one
86 /// cabinet before the contents are chained to the next cabinet, or zero
87 /// for unlimited cabinet size.</param>
88 /// <exception cref="ArchiveException">The cabinet could not be
89 /// created.</exception>
90 /// <remarks>
91 /// The stream context implementation may provide a mapping from the
92 /// file paths within the cabinet to the external file paths.
93 /// <para>Smaller folder sizes can make it more efficient to extract
94 /// individual files out of large cabinet packages.</para>
95 /// </remarks>
96 public override void Pack(
97 IPackStreamContext streamContext,
98 IEnumerable<string> files,
99 long maxArchiveSize)
100 {
101 this.Packer.CompressionLevel = this.CompressionLevel;
102 this.Packer.UseTempFiles = this.UseTempFiles;
103 this.Packer.Pack(streamContext, files, maxArchiveSize);
104 }
105
106 /// <summary>
107 /// Checks whether a Stream begins with a header that indicates
108 /// it is a valid cabinet file.
109 /// </summary>
110 /// <param name="stream">Stream for reading the cabinet file.</param>
111 /// <returns>True if the stream is a valid cabinet file
112 /// (with no offset); false otherwise.</returns>
113 public override bool IsArchive(Stream stream)
114 {
115 return this.Unpacker.IsArchive(stream);
116 }
117
118 /// <summary>
119 /// Gets information about files in a cabinet or cabinet chain.
120 /// </summary>
121 /// <param name="streamContext">A context interface to handle opening
122 /// and closing of cabinet and file streams.</param>
123 /// <param name="fileFilter">A predicate that can determine
124 /// which files to process, optional.</param>
125 /// <returns>Information about files in the cabinet stream.</returns>
126 /// <exception cref="ArchiveException">The cabinet provided
127 /// by the stream context is not valid.</exception>
128 /// <remarks>
129 /// The <paramref name="fileFilter"/> predicate takes an internal file
130 /// path and returns true to include the file or false to exclude it.
131 /// </remarks>
132 public override IList<ArchiveFileInfo> GetFileInfo(
133 IUnpackStreamContext streamContext,
134 Predicate<string> fileFilter)
135 {
136 return this.Unpacker.GetFileInfo(streamContext, fileFilter);
137 }
138
139 /// <summary>
140 /// Extracts files from a cabinet or cabinet chain.
141 /// </summary>
142 /// <param name="streamContext">A context interface to handle opening
143 /// and closing of cabinet and file streams.</param>
144 /// <param name="fileFilter">An optional predicate that can determine
145 /// which files to process.</param>
146 /// <exception cref="ArchiveException">The cabinet provided
147 /// by the stream context is not valid.</exception>
148 /// <remarks>
149 /// The <paramref name="fileFilter"/> predicate takes an internal file
150 /// path and returns true to include the file or false to exclude it.
151 /// </remarks>
152 public override void Unpack(
153 IUnpackStreamContext streamContext,
154 Predicate<string> fileFilter)
155 {
156 this.Unpacker.Unpack(streamContext, fileFilter);
157 }
158
159 internal void ReportProgress(ArchiveProgressEventArgs e)
160 {
161 base.OnProgress(e);
162 }
163 }
164}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/CabException.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/CabException.cs
new file mode 100644
index 00000000..e03f9f3a
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/CabException.cs
@@ -0,0 +1,154 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Cab
4{
5 using System;
6 using System.IO;
7 using System.Resources;
8 using System.Globalization;
9 using System.Runtime.Serialization;
10
11 /// <summary>
12 /// Exception class for cabinet operations.
13 /// </summary>
14 [Serializable]
15 public class CabException : ArchiveException
16 {
17 private static ResourceManager errorResources;
18 private int error;
19 private int errorCode;
20
21 /// <summary>
22 /// Creates a new CabException with a specified error message and a reference to the
23 /// inner exception that is the cause of this exception.
24 /// </summary>
25 /// <param name="message">The message that describes the error.</param>
26 /// <param name="innerException">The exception that is the cause of the current exception. If the
27 /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
28 /// is raised in a catch block that handles the inner exception.</param>
29 public CabException(string message, Exception innerException)
30 : this(0, 0, message, innerException) { }
31
32 /// <summary>
33 /// Creates a new CabException with a specified error message.
34 /// </summary>
35 /// <param name="message">The message that describes the error.</param>
36 public CabException(string message)
37 : this(0, 0, message, null) { }
38
39 /// <summary>
40 /// Creates a new CabException.
41 /// </summary>
42 public CabException()
43 : this(0, 0, null, null) { }
44
45 internal CabException(int error, int errorCode, string message, Exception innerException)
46 : base(message, innerException)
47 {
48 this.error = error;
49 this.errorCode = errorCode;
50 }
51
52 internal CabException(int error, int errorCode, string message)
53 : this(error, errorCode, message, null) { }
54
55 /// <summary>
56 /// Initializes a new instance of the CabException class with serialized data.
57 /// </summary>
58 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
59 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
60 protected CabException(SerializationInfo info, StreamingContext context) : base(info, context)
61 {
62 if (info == null)
63 {
64 throw new ArgumentNullException("info");
65 }
66
67 this.error = info.GetInt32("cabError");
68 this.errorCode = info.GetInt32("cabErrorCode");
69 }
70
71 /// <summary>
72 /// Gets the FCI or FDI cabinet engine error number.
73 /// </summary>
74 /// <value>A cabinet engine error number, or 0 if the exception was
75 /// not related to a cabinet engine error number.</value>
76 public int Error
77 {
78 get
79 {
80 return this.error;
81 }
82 }
83
84 /// <summary>
85 /// Gets the Win32 error code.
86 /// </summary>
87 /// <value>A Win32 error code, or 0 if the exception was
88 /// not related to a Win32 error.</value>
89 public int ErrorCode
90 {
91 get
92 {
93 return this.errorCode;
94 }
95 }
96
97 internal static ResourceManager ErrorResources
98 {
99 get
100 {
101 if (errorResources == null)
102 {
103 errorResources = new ResourceManager(
104 typeof(CabException).Namespace + ".Errors",
105 typeof(CabException).Assembly);
106 }
107 return errorResources;
108 }
109 }
110
111 /// <summary>
112 /// Sets the SerializationInfo with information about the exception.
113 /// </summary>
114 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
115 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
116 public override void GetObjectData(SerializationInfo info, StreamingContext context)
117 {
118 if (info == null)
119 {
120 throw new ArgumentNullException("info");
121 }
122
123 info.AddValue("cabError", this.error);
124 info.AddValue("cabErrorCode", this.errorCode);
125 base.GetObjectData(info, context);
126 }
127
128 internal static string GetErrorMessage(int error, int errorCode, bool extracting)
129 {
130 const int FCI_ERROR_RESOURCE_OFFSET = 1000;
131 const int FDI_ERROR_RESOURCE_OFFSET = 2000;
132 int resourceOffset = (extracting ? FDI_ERROR_RESOURCE_OFFSET : FCI_ERROR_RESOURCE_OFFSET);
133
134 string msg = CabException.ErrorResources.GetString(
135 (resourceOffset + error).ToString(CultureInfo.InvariantCulture.NumberFormat),
136 CultureInfo.CurrentCulture);
137
138 if (msg == null)
139 {
140 msg = CabException.ErrorResources.GetString(
141 resourceOffset.ToString(CultureInfo.InvariantCulture.NumberFormat),
142 CultureInfo.CurrentCulture);
143 }
144
145 if (errorCode != 0)
146 {
147 const string GENERIC_ERROR_RESOURCE = "1";
148 string msg2 = CabException.ErrorResources.GetString(GENERIC_ERROR_RESOURCE, CultureInfo.CurrentCulture);
149 msg = String.Format(CultureInfo.InvariantCulture, "{0} " + msg2, msg, errorCode);
150 }
151 return msg;
152 }
153 }
154}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/CabFileInfo.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/CabFileInfo.cs
new file mode 100644
index 00000000..ad4a813c
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/CabFileInfo.cs
@@ -0,0 +1,141 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Cab
4{
5 using System;
6 using System.IO;
7 using System.Runtime.Serialization;
8
9 /// <summary>
10 /// Object representing a compressed file within a cabinet package; provides operations for getting
11 /// the file properties and extracting the file.
12 /// </summary>
13 [Serializable]
14 public class CabFileInfo : ArchiveFileInfo
15 {
16 private int cabFolder;
17
18 /// <summary>
19 /// Creates a new CabinetFileInfo object representing a file within a cabinet in a specified path.
20 /// </summary>
21 /// <param name="cabinetInfo">An object representing the cabinet containing the file.</param>
22 /// <param name="filePath">The path to the file within the cabinet. Usually, this is a simple file
23 /// name, but if the cabinet contains a directory structure this may include the directory.</param>
24 public CabFileInfo(CabInfo cabinetInfo, string filePath)
25 : base(cabinetInfo, filePath)
26 {
27 if (cabinetInfo == null)
28 {
29 throw new ArgumentNullException("cabinetInfo");
30 }
31
32 this.cabFolder = -1;
33 }
34
35 /// <summary>
36 /// Creates a new CabinetFileInfo object with all parameters specified,
37 /// used internally when reading the metadata out of a cab.
38 /// </summary>
39 /// <param name="filePath">The internal path and name of the file in the cab.</param>
40 /// <param name="cabFolder">The folder number containing the file.</param>
41 /// <param name="cabNumber">The cabinet number where the file starts.</param>
42 /// <param name="attributes">The stored attributes of the file.</param>
43 /// <param name="lastWriteTime">The stored last write time of the file.</param>
44 /// <param name="length">The uncompressed size of the file.</param>
45 internal CabFileInfo(
46 string filePath,
47 int cabFolder,
48 int cabNumber,
49 FileAttributes attributes,
50 DateTime lastWriteTime,
51 long length)
52 : base(filePath, cabNumber, attributes, lastWriteTime, length)
53 {
54 this.cabFolder = cabFolder;
55 }
56
57 /// <summary>
58 /// Initializes a new instance of the CabinetFileInfo class with serialized data.
59 /// </summary>
60 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
61 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
62 protected CabFileInfo(SerializationInfo info, StreamingContext context)
63 : base(info, context)
64 {
65 this.cabFolder = info.GetInt32("cabFolder");
66 }
67
68 /// <summary>
69 /// Sets the SerializationInfo with information about the archive.
70 /// </summary>
71 /// <param name="info">The SerializationInfo that holds the serialized object data.</param>
72 /// <param name="context">The StreamingContext that contains contextual information
73 /// about the source or destination.</param>
74 public override void GetObjectData(SerializationInfo info, StreamingContext context)
75 {
76 base.GetObjectData(info, context);
77 info.AddValue("cabFolder", this.cabFolder);
78 }
79
80 /// <summary>
81 /// Gets or sets the cabinet that contains this file.
82 /// </summary>
83 /// <value>
84 /// The CabinetInfo instance that retrieved this file information -- this
85 /// may be null if the CabinetFileInfo object was returned directly from a
86 /// stream.
87 /// </value>
88 public CabInfo Cabinet
89 {
90 get
91 {
92 return (CabInfo) this.Archive;
93 }
94 }
95
96 /// <summary>
97 /// Gets the full path of the cabinet that contains this file.
98 /// </summary>
99 /// <value>The full path of the cabinet that contains this file.</value>
100 public string CabinetName
101 {
102 get
103 {
104 return this.ArchiveName;
105 }
106 }
107
108 /// <summary>
109 /// Gets the number of the folder containing this file.
110 /// </summary>
111 /// <value>The number of the cabinet folder containing this file.</value>
112 /// <remarks>A single folder or the first folder of a cabinet
113 /// (or chain of cabinets) is numbered 0.</remarks>
114 public int CabinetFolderNumber
115 {
116 get
117 {
118 if (this.cabFolder < 0)
119 {
120 this.Refresh();
121 }
122 return this.cabFolder;
123 }
124 }
125
126 /// <summary>
127 /// Refreshes the information in this object with new data retrieved
128 /// from an archive.
129 /// </summary>
130 /// <param name="newFileInfo">Fresh instance for the same file just
131 /// read from the archive.</param>
132 /// <remarks>
133 /// This implementation refreshes the <see cref="CabinetFolderNumber"/>.
134 /// </remarks>
135 protected override void Refresh(ArchiveFileInfo newFileInfo)
136 {
137 base.Refresh(newFileInfo);
138 this.cabFolder = ((CabFileInfo) newFileInfo).cabFolder;
139 }
140 }
141}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/CabInfo.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/CabInfo.cs
new file mode 100644
index 00000000..969dcbef
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/CabInfo.cs
@@ -0,0 +1,82 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Cab
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Runtime.Serialization;
8
9 /// <summary>
10 /// Object representing a cabinet file on disk; provides access to
11 /// file-based operations on the cabinet file.
12 /// </summary>
13 /// <remarks>
14 /// Generally, the methods on this class are much easier to use than the
15 /// stream-based interfaces provided by the <see cref="CabEngine"/> class.
16 /// </remarks>
17 [Serializable]
18 public class CabInfo : ArchiveInfo
19 {
20 /// <summary>
21 /// Creates a new CabinetInfo object representing a cabinet file in a specified path.
22 /// </summary>
23 /// <param name="path">The path to the cabinet file. When creating a cabinet file, this file does not
24 /// necessarily exist yet.</param>
25 public CabInfo(string path)
26 : base(path)
27 {
28 }
29
30 /// <summary>
31 /// Initializes a new instance of the CabinetInfo class with serialized data.
32 /// </summary>
33 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
34 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
35 protected CabInfo(SerializationInfo info, StreamingContext context)
36 : base(info, context)
37 {
38 }
39
40 /// <summary>
41 /// Creates a compression engine that does the low-level work for
42 /// this object.
43 /// </summary>
44 /// <returns>A new <see cref="CabEngine"/> instance.</returns>
45 /// <remarks>
46 /// Each instance will be <see cref="CompressionEngine.Dispose()"/>d
47 /// immediately after use.
48 /// </remarks>
49 protected override CompressionEngine CreateCompressionEngine()
50 {
51 return new CabEngine();
52 }
53
54 /// <summary>
55 /// Gets information about the files contained in the archive.
56 /// </summary>
57 /// <returns>A list of <see cref="CabFileInfo"/> objects, each
58 /// containing information about a file in the archive.</returns>
59 public new IList<CabFileInfo> GetFiles()
60 {
61 IList<ArchiveFileInfo> files = base.GetFiles();
62 List<CabFileInfo> cabFiles = new List<CabFileInfo>(files.Count);
63 foreach (CabFileInfo cabFile in files) cabFiles.Add(cabFile);
64 return cabFiles.AsReadOnly();
65 }
66
67 /// <summary>
68 /// Gets information about the certain files contained in the archive file.
69 /// </summary>
70 /// <param name="searchPattern">The search string, such as
71 /// &quot;*.txt&quot;.</param>
72 /// <returns>A list of <see cref="CabFileInfo"/> objects, each containing
73 /// information about a file in the archive.</returns>
74 public new IList<CabFileInfo> GetFiles(string searchPattern)
75 {
76 IList<ArchiveFileInfo> files = base.GetFiles(searchPattern);
77 List<CabFileInfo> cabFiles = new List<CabFileInfo>(files.Count);
78 foreach (CabFileInfo cabFile in files) cabFiles.Add(cabFile);
79 return cabFiles.AsReadOnly();
80 }
81 }
82}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/CabPacker.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/CabPacker.cs
new file mode 100644
index 00000000..ec6e3bda
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/CabPacker.cs
@@ -0,0 +1,653 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Cab
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Collections.Generic;
9 using System.Globalization;
10 using System.Runtime.InteropServices;
11 using System.Diagnostics.CodeAnalysis;
12
13 internal class CabPacker : CabWorker
14 {
15 private const string TempStreamName = "%%TEMP%%";
16
17 private NativeMethods.FCI.Handle fciHandle;
18
19 // These delegates need to be saved as member variables
20 // so that they don't get GC'd.
21 private NativeMethods.FCI.PFNALLOC fciAllocMemHandler;
22 private NativeMethods.FCI.PFNFREE fciFreeMemHandler;
23 private NativeMethods.FCI.PFNOPEN fciOpenStreamHandler;
24 private NativeMethods.FCI.PFNREAD fciReadStreamHandler;
25 private NativeMethods.FCI.PFNWRITE fciWriteStreamHandler;
26 private NativeMethods.FCI.PFNCLOSE fciCloseStreamHandler;
27 private NativeMethods.FCI.PFNSEEK fciSeekStreamHandler;
28 private NativeMethods.FCI.PFNFILEPLACED fciFilePlacedHandler;
29 private NativeMethods.FCI.PFNDELETE fciDeleteFileHandler;
30 private NativeMethods.FCI.PFNGETTEMPFILE fciGetTempFileHandler;
31
32 private NativeMethods.FCI.PFNGETNEXTCABINET fciGetNextCabinet;
33 private NativeMethods.FCI.PFNSTATUS fciCreateStatus;
34 private NativeMethods.FCI.PFNGETOPENINFO fciGetOpenInfo;
35
36 private IPackStreamContext context;
37
38 private FileAttributes fileAttributes;
39 private DateTime fileLastWriteTime;
40
41 private int maxCabBytes;
42
43 private long totalFolderBytesProcessedInCurrentCab;
44
45 private CompressionLevel compressionLevel;
46 private bool dontUseTempFiles;
47 private IList<Stream> tempStreams;
48
49 public CabPacker(CabEngine cabEngine)
50 : base(cabEngine)
51 {
52 this.fciAllocMemHandler = this.CabAllocMem;
53 this.fciFreeMemHandler = this.CabFreeMem;
54 this.fciOpenStreamHandler = this.CabOpenStreamEx;
55 this.fciReadStreamHandler = this.CabReadStreamEx;
56 this.fciWriteStreamHandler = this.CabWriteStreamEx;
57 this.fciCloseStreamHandler = this.CabCloseStreamEx;
58 this.fciSeekStreamHandler = this.CabSeekStreamEx;
59 this.fciFilePlacedHandler = this.CabFilePlaced;
60 this.fciDeleteFileHandler = this.CabDeleteFile;
61 this.fciGetTempFileHandler = this.CabGetTempFile;
62 this.fciGetNextCabinet = this.CabGetNextCabinet;
63 this.fciCreateStatus = this.CabCreateStatus;
64 this.fciGetOpenInfo = this.CabGetOpenInfo;
65 this.tempStreams = new List<Stream>();
66 this.compressionLevel = CompressionLevel.Normal;
67 }
68
69 public bool UseTempFiles
70 {
71 get
72 {
73 return !this.dontUseTempFiles;
74 }
75
76 set
77 {
78 this.dontUseTempFiles = !value;
79 }
80 }
81
82 public CompressionLevel CompressionLevel
83 {
84 get
85 {
86 return this.compressionLevel;
87 }
88
89 set
90 {
91 this.compressionLevel = value;
92 }
93 }
94
95 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
96 private void CreateFci(long maxArchiveSize)
97 {
98 NativeMethods.FCI.CCAB ccab = new NativeMethods.FCI.CCAB();
99 if (maxArchiveSize > 0 && maxArchiveSize < ccab.cb)
100 {
101 ccab.cb = Math.Max(
102 NativeMethods.FCI.MIN_DISK, (int) maxArchiveSize);
103 }
104
105 object maxFolderSizeOption = this.context.GetOption(
106 "maxFolderSize", null);
107 if (maxFolderSizeOption != null)
108 {
109 long maxFolderSize = Convert.ToInt64(
110 maxFolderSizeOption, CultureInfo.InvariantCulture);
111 if (maxFolderSize > 0 && maxFolderSize < ccab.cbFolderThresh)
112 {
113 ccab.cbFolderThresh = (int) maxFolderSize;
114 }
115 }
116
117 this.maxCabBytes = ccab.cb;
118 ccab.szCab = this.context.GetArchiveName(0);
119 if (ccab.szCab == null)
120 {
121 throw new FileNotFoundException(
122 "Cabinet name not provided by stream context.");
123 }
124 ccab.setID = (short) new Random().Next(
125 Int16.MinValue, Int16.MaxValue + 1);
126 this.CabNumbers[ccab.szCab] = 0;
127 this.currentArchiveName = ccab.szCab;
128 this.totalArchives = 1;
129 this.CabStream = null;
130
131 this.Erf.Clear();
132 this.fciHandle = NativeMethods.FCI.Create(
133 this.ErfHandle.AddrOfPinnedObject(),
134 this.fciFilePlacedHandler,
135 this.fciAllocMemHandler,
136 this.fciFreeMemHandler,
137 this.fciOpenStreamHandler,
138 this.fciReadStreamHandler,
139 this.fciWriteStreamHandler,
140 this.fciCloseStreamHandler,
141 this.fciSeekStreamHandler,
142 this.fciDeleteFileHandler,
143 this.fciGetTempFileHandler,
144 ccab,
145 IntPtr.Zero);
146 this.CheckError(false);
147 }
148
149 public void Pack(
150 IPackStreamContext streamContext,
151 IEnumerable<string> files,
152 long maxArchiveSize)
153 {
154 if (streamContext == null)
155 {
156 throw new ArgumentNullException("streamContext");
157 }
158
159 if (files == null)
160 {
161 throw new ArgumentNullException("files");
162 }
163
164 lock (this)
165 {
166 try
167 {
168 this.context = streamContext;
169
170 this.ResetProgressData();
171
172 this.CreateFci(maxArchiveSize);
173
174 foreach (string file in files)
175 {
176 FileAttributes attributes;
177 DateTime lastWriteTime;
178 Stream fileStream = this.context.OpenFileReadStream(
179 file,
180 out attributes,
181 out lastWriteTime);
182 if (fileStream != null)
183 {
184 this.totalFileBytes += fileStream.Length;
185 this.totalFiles++;
186 this.context.CloseFileReadStream(file, fileStream);
187 }
188 }
189
190 long uncompressedBytesInFolder = 0;
191 this.currentFileNumber = -1;
192
193 foreach (string file in files)
194 {
195 FileAttributes attributes;
196 DateTime lastWriteTime;
197 Stream fileStream = this.context.OpenFileReadStream(
198 file, out attributes, out lastWriteTime);
199 if (fileStream == null)
200 {
201 continue;
202 }
203
204 if (fileStream.Length >= (long) NativeMethods.FCI.MAX_FOLDER)
205 {
206 throw new NotSupportedException(String.Format(
207 CultureInfo.InvariantCulture,
208 "File {0} exceeds maximum file size " +
209 "for cabinet format.",
210 file));
211 }
212
213 if (uncompressedBytesInFolder > 0)
214 {
215 // Automatically create a new folder if this file
216 // won't fit in the current folder.
217 bool nextFolder = uncompressedBytesInFolder
218 + fileStream.Length >= (long) NativeMethods.FCI.MAX_FOLDER;
219
220 // Otherwise ask the client if it wants to
221 // move to the next folder.
222 if (!nextFolder)
223 {
224 object nextFolderOption = streamContext.GetOption(
225 "nextFolder",
226 new object[] { file, this.currentFolderNumber });
227 nextFolder = Convert.ToBoolean(
228 nextFolderOption, CultureInfo.InvariantCulture);
229 }
230
231 if (nextFolder)
232 {
233 this.FlushFolder();
234 uncompressedBytesInFolder = 0;
235 }
236 }
237
238 if (this.currentFolderTotalBytes > 0)
239 {
240 this.currentFolderTotalBytes = 0;
241 this.currentFolderNumber++;
242 uncompressedBytesInFolder = 0;
243 }
244
245 this.currentFileName = file;
246 this.currentFileNumber++;
247
248 this.currentFileTotalBytes = fileStream.Length;
249 this.currentFileBytesProcessed = 0;
250 this.OnProgress(ArchiveProgressType.StartFile);
251
252 uncompressedBytesInFolder += fileStream.Length;
253
254 this.AddFile(
255 file,
256 fileStream,
257 attributes,
258 lastWriteTime,
259 false,
260 this.CompressionLevel);
261 }
262
263 this.FlushFolder();
264 this.FlushCabinet();
265 }
266 finally
267 {
268 if (this.CabStream != null)
269 {
270 this.context.CloseArchiveWriteStream(
271 this.currentArchiveNumber,
272 this.currentArchiveName,
273 this.CabStream);
274 this.CabStream = null;
275 }
276
277 if (this.FileStream != null)
278 {
279 this.context.CloseFileReadStream(
280 this.currentFileName, this.FileStream);
281 this.FileStream = null;
282 }
283 this.context = null;
284
285 if (this.fciHandle != null)
286 {
287 this.fciHandle.Dispose();
288 this.fciHandle = null;
289 }
290 }
291 }
292 }
293
294 internal override int CabOpenStreamEx(string path, int openFlags, int shareMode, out int err, IntPtr pv)
295 {
296 if (this.CabNumbers.ContainsKey(path))
297 {
298 Stream stream = this.CabStream;
299 if (stream == null)
300 {
301 short cabNumber = this.CabNumbers[path];
302
303 this.currentFolderTotalBytes = 0;
304
305 stream = this.context.OpenArchiveWriteStream(cabNumber, path, true, this.CabEngine);
306 if (stream == null)
307 {
308 throw new FileNotFoundException(
309 String.Format(CultureInfo.InvariantCulture, "Cabinet {0} not provided.", cabNumber));
310 }
311 this.currentArchiveName = path;
312
313 this.currentArchiveTotalBytes = Math.Min(
314 this.totalFolderBytesProcessedInCurrentCab, this.maxCabBytes);
315 this.currentArchiveBytesProcessed = 0;
316
317 this.OnProgress(ArchiveProgressType.StartArchive);
318 this.CabStream = stream;
319 }
320 path = CabWorker.CabStreamName;
321 }
322 else if (path == CabPacker.TempStreamName)
323 {
324 // Opening memory stream for a temp file.
325 Stream stream = new MemoryStream();
326 this.tempStreams.Add(stream);
327 int streamHandle = this.StreamHandles.AllocHandle(stream);
328 err = 0;
329 return streamHandle;
330 }
331 else if (path != CabWorker.CabStreamName)
332 {
333 // Opening a file on disk for a temp file.
334 path = Path.Combine(Path.GetTempPath(), path);
335 Stream stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite);
336 this.tempStreams.Add(stream);
337 stream = new DuplicateStream(stream);
338 int streamHandle = this.StreamHandles.AllocHandle(stream);
339 err = 0;
340 return streamHandle;
341 }
342 return base.CabOpenStreamEx(path, openFlags, shareMode, out err, pv);
343 }
344
345 internal override int CabWriteStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
346 {
347 int count = base.CabWriteStreamEx(streamHandle, memory, cb, out err, pv);
348 if (count > 0 && err == 0)
349 {
350 Stream stream = this.StreamHandles[streamHandle];
351 if (DuplicateStream.OriginalStream(stream) ==
352 DuplicateStream.OriginalStream(this.CabStream))
353 {
354 this.currentArchiveBytesProcessed += cb;
355 if (this.currentArchiveBytesProcessed > this.currentArchiveTotalBytes)
356 {
357 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
358 }
359 }
360 }
361 return count;
362 }
363
364 internal override int CabCloseStreamEx(int streamHandle, out int err, IntPtr pv)
365 {
366 Stream stream = DuplicateStream.OriginalStream(this.StreamHandles[streamHandle]);
367
368 if (stream == DuplicateStream.OriginalStream(this.FileStream))
369 {
370 this.context.CloseFileReadStream(this.currentFileName, stream);
371 this.FileStream = null;
372 long remainder = this.currentFileTotalBytes - this.currentFileBytesProcessed;
373 this.currentFileBytesProcessed += remainder;
374 this.fileBytesProcessed += remainder;
375 this.OnProgress(ArchiveProgressType.FinishFile);
376
377 this.currentFileTotalBytes = 0;
378 this.currentFileBytesProcessed = 0;
379 this.currentFileName = null;
380 }
381 else if (stream == DuplicateStream.OriginalStream(this.CabStream))
382 {
383 if (stream.CanWrite)
384 {
385 stream.Flush();
386 }
387
388 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
389 this.OnProgress(ArchiveProgressType.FinishArchive);
390 this.currentArchiveNumber++;
391 this.totalArchives++;
392
393 this.context.CloseArchiveWriteStream(
394 this.currentArchiveNumber,
395 this.currentArchiveName,
396 stream);
397
398 this.currentArchiveName = this.NextCabinetName;
399 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes = 0;
400 this.totalFolderBytesProcessedInCurrentCab = 0;
401
402 this.CabStream = null;
403 }
404 else // Must be a temp stream
405 {
406 stream.Close();
407 this.tempStreams.Remove(stream);
408 }
409 return base.CabCloseStreamEx(streamHandle, out err, pv);
410 }
411
412 /// <summary>
413 /// Disposes of resources allocated by the cabinet engine.
414 /// </summary>
415 /// <param name="disposing">If true, the method has been called directly or indirectly by a user's code,
416 /// so managed and unmanaged resources will be disposed. If false, the method has been called by the
417 /// runtime from inside the finalizer, and only unmanaged resources will be disposed.</param>
418 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
419 protected override void Dispose(bool disposing)
420 {
421 try
422 {
423 if (disposing)
424 {
425 if (this.fciHandle != null)
426 {
427 this.fciHandle.Dispose();
428 this.fciHandle = null;
429 }
430 }
431 }
432 finally
433 {
434 base.Dispose(disposing);
435 }
436 }
437
438 private static NativeMethods.FCI.TCOMP GetCompressionType(CompressionLevel compLevel)
439 {
440 if (compLevel < CompressionLevel.Min)
441 {
442 return NativeMethods.FCI.TCOMP.TYPE_NONE;
443 }
444 else
445 {
446 if (compLevel > CompressionLevel.Max)
447 {
448 compLevel = CompressionLevel.Max;
449 }
450
451 int lzxWindowMax =
452 ((int) NativeMethods.FCI.TCOMP.LZX_WINDOW_HI >> (int) NativeMethods.FCI.TCOMP.SHIFT_LZX_WINDOW) -
453 ((int) NativeMethods.FCI.TCOMP.LZX_WINDOW_LO >> (int) NativeMethods.FCI.TCOMP.SHIFT_LZX_WINDOW);
454 int lzxWindow = lzxWindowMax *
455 (compLevel - CompressionLevel.Min) / (CompressionLevel.Max - CompressionLevel.Min);
456
457 return (NativeMethods.FCI.TCOMP) ((int) NativeMethods.FCI.TCOMP.TYPE_LZX |
458 ((int) NativeMethods.FCI.TCOMP.LZX_WINDOW_LO +
459 (lzxWindow << (int) NativeMethods.FCI.TCOMP.SHIFT_LZX_WINDOW)));
460 }
461 }
462
463 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
464 private void AddFile(
465 string name,
466 Stream stream,
467 FileAttributes attributes,
468 DateTime lastWriteTime,
469 bool execute,
470 CompressionLevel compLevel)
471 {
472 this.FileStream = stream;
473 this.fileAttributes = attributes &
474 (FileAttributes.Archive | FileAttributes.Hidden | FileAttributes.ReadOnly | FileAttributes.System);
475 this.fileLastWriteTime = lastWriteTime;
476 this.currentFileName = name;
477
478 NativeMethods.FCI.TCOMP tcomp = CabPacker.GetCompressionType(compLevel);
479
480 IntPtr namePtr = IntPtr.Zero;
481 try
482 {
483 Encoding nameEncoding = Encoding.ASCII;
484 if (Encoding.UTF8.GetByteCount(name) > name.Length)
485 {
486 nameEncoding = Encoding.UTF8;
487 this.fileAttributes |= FileAttributes.Normal; // _A_NAME_IS_UTF
488 }
489
490 byte[] nameBytes = nameEncoding.GetBytes(name);
491 namePtr = Marshal.AllocHGlobal(nameBytes.Length + 1);
492 Marshal.Copy(nameBytes, 0, namePtr, nameBytes.Length);
493 Marshal.WriteByte(namePtr, nameBytes.Length, 0);
494
495 this.Erf.Clear();
496 NativeMethods.FCI.AddFile(
497 this.fciHandle,
498 String.Empty,
499 namePtr,
500 execute,
501 this.fciGetNextCabinet,
502 this.fciCreateStatus,
503 this.fciGetOpenInfo,
504 tcomp);
505 }
506 finally
507 {
508 if (namePtr != IntPtr.Zero)
509 {
510 Marshal.FreeHGlobal(namePtr);
511 }
512 }
513
514 this.CheckError(false);
515 this.FileStream = null;
516 this.currentFileName = null;
517 }
518
519 private void FlushFolder()
520 {
521 this.Erf.Clear();
522 NativeMethods.FCI.FlushFolder(this.fciHandle, this.fciGetNextCabinet, this.fciCreateStatus);
523 this.CheckError(false);
524 }
525
526 private void FlushCabinet()
527 {
528 this.Erf.Clear();
529 NativeMethods.FCI.FlushCabinet(this.fciHandle, false, this.fciGetNextCabinet, this.fciCreateStatus);
530 this.CheckError(false);
531 }
532
533 private int CabGetOpenInfo(
534 string path,
535 out short date,
536 out short time,
537 out short attribs,
538 out int err,
539 IntPtr pv)
540 {
541 CompressionEngine.DateTimeToDosDateAndTime(this.fileLastWriteTime, out date, out time);
542 attribs = (short) this.fileAttributes;
543
544 Stream stream = this.FileStream;
545 this.FileStream = new DuplicateStream(stream);
546 int streamHandle = this.StreamHandles.AllocHandle(stream);
547 err = 0;
548 return streamHandle;
549 }
550
551 private int CabFilePlaced(
552 IntPtr pccab,
553 string filePath,
554 long fileSize,
555 int continuation,
556 IntPtr pv)
557 {
558 return 0;
559 }
560
561 private int CabGetNextCabinet(IntPtr pccab, uint prevCabSize, IntPtr pv)
562 {
563 NativeMethods.FCI.CCAB nextCcab = new NativeMethods.FCI.CCAB();
564 Marshal.PtrToStructure(pccab, nextCcab);
565
566 nextCcab.szDisk = String.Empty;
567 nextCcab.szCab = this.context.GetArchiveName(nextCcab.iCab);
568 this.CabNumbers[nextCcab.szCab] = (short) nextCcab.iCab;
569 this.NextCabinetName = nextCcab.szCab;
570
571 Marshal.StructureToPtr(nextCcab, pccab, false);
572 return 1;
573 }
574
575 private int CabCreateStatus(NativeMethods.FCI.STATUS typeStatus, uint cb1, uint cb2, IntPtr pv)
576 {
577 switch (typeStatus)
578 {
579 case NativeMethods.FCI.STATUS.FILE:
580 if (cb2 > 0 && this.currentFileBytesProcessed < this.currentFileTotalBytes)
581 {
582 if (this.currentFileBytesProcessed + cb2 > this.currentFileTotalBytes)
583 {
584 cb2 = (uint) this.currentFileTotalBytes - (uint) this.currentFileBytesProcessed;
585 }
586 this.currentFileBytesProcessed += cb2;
587 this.fileBytesProcessed += cb2;
588
589 this.OnProgress(ArchiveProgressType.PartialFile);
590 }
591 break;
592
593 case NativeMethods.FCI.STATUS.FOLDER:
594 if (cb1 == 0)
595 {
596 this.currentFolderTotalBytes = cb2 - this.totalFolderBytesProcessedInCurrentCab;
597 this.totalFolderBytesProcessedInCurrentCab = cb2;
598 }
599 else if (this.currentFolderTotalBytes == 0)
600 {
601 this.OnProgress(ArchiveProgressType.PartialArchive);
602 }
603 break;
604
605 case NativeMethods.FCI.STATUS.CABINET:
606 break;
607 }
608 return 0;
609 }
610
611 private int CabGetTempFile(IntPtr tempNamePtr, int tempNameSize, IntPtr pv)
612 {
613 string tempFileName;
614 if (this.UseTempFiles)
615 {
616 tempFileName = Path.GetFileName(Path.GetTempFileName());
617 }
618 else
619 {
620 tempFileName = CabPacker.TempStreamName;
621 }
622
623 byte[] tempNameBytes = Encoding.ASCII.GetBytes(tempFileName);
624 if (tempNameBytes.Length >= tempNameSize)
625 {
626 return -1;
627 }
628
629 Marshal.Copy(tempNameBytes, 0, tempNamePtr, tempNameBytes.Length);
630 Marshal.WriteByte(tempNamePtr, tempNameBytes.Length, 0); // null-terminator
631 return 1;
632 }
633
634 private int CabDeleteFile(string path, out int err, IntPtr pv)
635 {
636 try
637 {
638 // Deleting a temp file - don't bother if it is only a memory stream.
639 if (path != CabPacker.TempStreamName)
640 {
641 path = Path.Combine(Path.GetTempPath(), path);
642 File.Delete(path);
643 }
644 }
645 catch (IOException)
646 {
647 // Failure to delete a temp file is not fatal.
648 }
649 err = 0;
650 return 1;
651 }
652 }
653}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/CabUnpacker.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/CabUnpacker.cs
new file mode 100644
index 00000000..b0be4a15
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/CabUnpacker.cs
@@ -0,0 +1,566 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Cab
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Collections.Generic;
9 using System.Globalization;
10 using System.Runtime.InteropServices;
11 using System.Diagnostics.CodeAnalysis;
12
13 internal class CabUnpacker : CabWorker
14 {
15 private NativeMethods.FDI.Handle fdiHandle;
16
17 // These delegates need to be saved as member variables
18 // so that they don't get GC'd.
19 private NativeMethods.FDI.PFNALLOC fdiAllocMemHandler;
20 private NativeMethods.FDI.PFNFREE fdiFreeMemHandler;
21 private NativeMethods.FDI.PFNOPEN fdiOpenStreamHandler;
22 private NativeMethods.FDI.PFNREAD fdiReadStreamHandler;
23 private NativeMethods.FDI.PFNWRITE fdiWriteStreamHandler;
24 private NativeMethods.FDI.PFNCLOSE fdiCloseStreamHandler;
25 private NativeMethods.FDI.PFNSEEK fdiSeekStreamHandler;
26
27 private IUnpackStreamContext context;
28
29 private List<ArchiveFileInfo> fileList;
30
31 private int folderId;
32
33 private Predicate<string> filter;
34
35 public CabUnpacker(CabEngine cabEngine)
36 : base(cabEngine)
37 {
38 this.fdiAllocMemHandler = this.CabAllocMem;
39 this.fdiFreeMemHandler = this.CabFreeMem;
40 this.fdiOpenStreamHandler = this.CabOpenStream;
41 this.fdiReadStreamHandler = this.CabReadStream;
42 this.fdiWriteStreamHandler = this.CabWriteStream;
43 this.fdiCloseStreamHandler = this.CabCloseStream;
44 this.fdiSeekStreamHandler = this.CabSeekStream;
45
46 this.fdiHandle = NativeMethods.FDI.Create(
47 this.fdiAllocMemHandler,
48 this.fdiFreeMemHandler,
49 this.fdiOpenStreamHandler,
50 this.fdiReadStreamHandler,
51 this.fdiWriteStreamHandler,
52 this.fdiCloseStreamHandler,
53 this.fdiSeekStreamHandler,
54 NativeMethods.FDI.CPU_80386,
55 this.ErfHandle.AddrOfPinnedObject());
56 if (this.Erf.Error)
57 {
58 int error = this.Erf.Oper;
59 int errorCode = this.Erf.Type;
60 this.ErfHandle.Free();
61 throw new CabException(
62 error,
63 errorCode,
64 CabException.GetErrorMessage(error, errorCode, true));
65 }
66 }
67
68 public bool IsArchive(Stream stream)
69 {
70 if (stream == null)
71 {
72 throw new ArgumentNullException("stream");
73 }
74
75 lock (this)
76 {
77 short id;
78 int folderCount, fileCount;
79 return this.IsCabinet(stream, out id, out folderCount, out fileCount);
80 }
81 }
82
83 public IList<ArchiveFileInfo> GetFileInfo(
84 IUnpackStreamContext streamContext,
85 Predicate<string> fileFilter)
86 {
87 if (streamContext == null)
88 {
89 throw new ArgumentNullException("streamContext");
90 }
91
92 lock (this)
93 {
94 this.context = streamContext;
95 this.filter = fileFilter;
96 this.NextCabinetName = String.Empty;
97 this.fileList = new List<ArchiveFileInfo>();
98 bool tmpSuppress = this.SuppressProgressEvents;
99 this.SuppressProgressEvents = true;
100 try
101 {
102 for (short cabNumber = 0;
103 this.NextCabinetName != null;
104 cabNumber++)
105 {
106 this.Erf.Clear();
107 this.CabNumbers[this.NextCabinetName] = cabNumber;
108
109 NativeMethods.FDI.Copy(
110 this.fdiHandle,
111 this.NextCabinetName,
112 String.Empty,
113 0,
114 this.CabListNotify,
115 IntPtr.Zero,
116 IntPtr.Zero);
117 this.CheckError(true);
118 }
119
120 List<ArchiveFileInfo> tmpFileList = this.fileList;
121 this.fileList = null;
122 return tmpFileList.AsReadOnly();
123 }
124 finally
125 {
126 this.SuppressProgressEvents = tmpSuppress;
127
128 if (this.CabStream != null)
129 {
130 this.context.CloseArchiveReadStream(
131 this.currentArchiveNumber,
132 this.currentArchiveName,
133 this.CabStream);
134 this.CabStream = null;
135 }
136
137 this.context = null;
138 }
139 }
140 }
141
142 public void Unpack(
143 IUnpackStreamContext streamContext,
144 Predicate<string> fileFilter)
145 {
146 lock (this)
147 {
148 IList<ArchiveFileInfo> files =
149 this.GetFileInfo(streamContext, fileFilter);
150
151 this.ResetProgressData();
152
153 if (files != null)
154 {
155 this.totalFiles = files.Count;
156
157 for (int i = 0; i < files.Count; i++)
158 {
159 this.totalFileBytes += files[i].Length;
160 if (files[i].ArchiveNumber >= this.totalArchives)
161 {
162 int totalArchives = files[i].ArchiveNumber + 1;
163 this.totalArchives = (short) totalArchives;
164 }
165 }
166 }
167
168 this.context = streamContext;
169 this.fileList = null;
170 this.NextCabinetName = String.Empty;
171 this.folderId = -1;
172 this.currentFileNumber = -1;
173
174 try
175 {
176 for (short cabNumber = 0;
177 this.NextCabinetName != null;
178 cabNumber++)
179 {
180 this.Erf.Clear();
181 this.CabNumbers[this.NextCabinetName] = cabNumber;
182
183 NativeMethods.FDI.Copy(
184 this.fdiHandle,
185 this.NextCabinetName,
186 String.Empty,
187 0,
188 this.CabExtractNotify,
189 IntPtr.Zero,
190 IntPtr.Zero);
191 this.CheckError(true);
192 }
193 }
194 finally
195 {
196 if (this.CabStream != null)
197 {
198 this.context.CloseArchiveReadStream(
199 this.currentArchiveNumber,
200 this.currentArchiveName,
201 this.CabStream);
202 this.CabStream = null;
203 }
204
205 if (this.FileStream != null)
206 {
207 this.context.CloseFileWriteStream(this.currentFileName, this.FileStream, FileAttributes.Normal, DateTime.Now);
208 this.FileStream = null;
209 }
210
211 this.context = null;
212 }
213 }
214 }
215
216 internal override int CabOpenStreamEx(string path, int openFlags, int shareMode, out int err, IntPtr pv)
217 {
218 if (this.CabNumbers.ContainsKey(path))
219 {
220 Stream stream = this.CabStream;
221 if (stream == null)
222 {
223 short cabNumber = this.CabNumbers[path];
224
225 stream = this.context.OpenArchiveReadStream(cabNumber, path, this.CabEngine);
226 if (stream == null)
227 {
228 throw new FileNotFoundException(String.Format(CultureInfo.InvariantCulture, "Cabinet {0} not provided.", cabNumber));
229 }
230 this.currentArchiveName = path;
231 this.currentArchiveNumber = cabNumber;
232 if (this.totalArchives <= this.currentArchiveNumber)
233 {
234 int totalArchives = this.currentArchiveNumber + 1;
235 this.totalArchives = (short) totalArchives;
236 }
237 this.currentArchiveTotalBytes = stream.Length;
238 this.currentArchiveBytesProcessed = 0;
239
240 if (this.folderId != -3) // -3 is a special folderId that requires re-opening the same cab
241 {
242 this.OnProgress(ArchiveProgressType.StartArchive);
243 }
244 this.CabStream = stream;
245 }
246 path = CabWorker.CabStreamName;
247 }
248 return base.CabOpenStreamEx(path, openFlags, shareMode, out err, pv);
249 }
250
251 internal override int CabReadStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
252 {
253 int count = base.CabReadStreamEx(streamHandle, memory, cb, out err, pv);
254 if (err == 0 && this.CabStream != null)
255 {
256 if (this.fileList == null)
257 {
258 Stream stream = this.StreamHandles[streamHandle];
259 if (DuplicateStream.OriginalStream(stream) ==
260 DuplicateStream.OriginalStream(this.CabStream))
261 {
262 this.currentArchiveBytesProcessed += cb;
263 if (this.currentArchiveBytesProcessed > this.currentArchiveTotalBytes)
264 {
265 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
266 }
267 }
268 }
269 }
270 return count;
271 }
272
273 internal override int CabWriteStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
274 {
275 int count = base.CabWriteStreamEx(streamHandle, memory, cb, out err, pv);
276 if (count > 0 && err == 0)
277 {
278 this.currentFileBytesProcessed += cb;
279 this.fileBytesProcessed += cb;
280 this.OnProgress(ArchiveProgressType.PartialFile);
281 }
282 return count;
283 }
284
285 internal override int CabCloseStreamEx(int streamHandle, out int err, IntPtr pv)
286 {
287 Stream stream = DuplicateStream.OriginalStream(this.StreamHandles[streamHandle]);
288
289 if (stream == DuplicateStream.OriginalStream(this.CabStream))
290 {
291 if (this.folderId != -3) // -3 is a special folderId that requires re-opening the same cab
292 {
293 this.OnProgress(ArchiveProgressType.FinishArchive);
294 }
295
296 this.context.CloseArchiveReadStream(this.currentArchiveNumber, this.currentArchiveName, stream);
297
298 this.currentArchiveName = this.NextCabinetName;
299 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes = 0;
300
301 this.CabStream = null;
302 }
303 return base.CabCloseStreamEx(streamHandle, out err, pv);
304 }
305
306 /// <summary>
307 /// Disposes of resources allocated by the cabinet engine.
308 /// </summary>
309 /// <param name="disposing">If true, the method has been called directly or indirectly by a user's code,
310 /// so managed and unmanaged resources will be disposed. If false, the method has been called by the
311 /// runtime from inside the finalizer, and only unmanaged resources will be disposed.</param>
312 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
313 protected override void Dispose(bool disposing)
314 {
315 try
316 {
317 if (disposing)
318 {
319 if (this.fdiHandle != null)
320 {
321 this.fdiHandle.Dispose();
322 this.fdiHandle = null;
323 }
324 }
325 }
326 finally
327 {
328 base.Dispose(disposing);
329 }
330 }
331
332 private static string GetFileName(NativeMethods.FDI.NOTIFICATION notification)
333 {
334 bool utf8Name = (notification.attribs & (ushort) FileAttributes.Normal) != 0; // _A_NAME_IS_UTF
335
336 // Non-utf8 names should be completely ASCII. But for compatibility with
337 // legacy tools, interpret them using the current (Default) ANSI codepage.
338 Encoding nameEncoding = utf8Name ? Encoding.UTF8 : Encoding.Default;
339
340 // Find how many bytes are in the string.
341 // Unfortunately there is no faster way.
342 int nameBytesCount = 0;
343 while (Marshal.ReadByte(notification.psz1, nameBytesCount) != 0)
344 {
345 nameBytesCount++;
346 }
347
348 byte[] nameBytes = new byte[nameBytesCount];
349 Marshal.Copy(notification.psz1, nameBytes, 0, nameBytesCount);
350 string name = nameEncoding.GetString(nameBytes);
351 if (Path.IsPathRooted(name))
352 {
353 name = name.Replace("" + Path.VolumeSeparatorChar, "");
354 }
355
356 return name;
357 }
358
359 private bool IsCabinet(Stream cabStream, out short id, out int cabFolderCount, out int fileCount)
360 {
361 int streamHandle = this.StreamHandles.AllocHandle(cabStream);
362 try
363 {
364 this.Erf.Clear();
365 NativeMethods.FDI.CABINFO fdici;
366 bool isCabinet = 0 != NativeMethods.FDI.IsCabinet(this.fdiHandle, streamHandle, out fdici);
367
368 if (this.Erf.Error)
369 {
370 if (((NativeMethods.FDI.ERROR) this.Erf.Oper) == NativeMethods.FDI.ERROR.UNKNOWN_CABINET_VERSION)
371 {
372 isCabinet = false;
373 }
374 else
375 {
376 throw new CabException(
377 this.Erf.Oper,
378 this.Erf.Type,
379 CabException.GetErrorMessage(this.Erf.Oper, this.Erf.Type, true));
380 }
381 }
382
383 id = fdici.setID;
384 cabFolderCount = (int) fdici.cFolders;
385 fileCount = (int) fdici.cFiles;
386 return isCabinet;
387 }
388 finally
389 {
390 this.StreamHandles.FreeHandle(streamHandle);
391 }
392 }
393
394 private int CabListNotify(NativeMethods.FDI.NOTIFICATIONTYPE notificationType, NativeMethods.FDI.NOTIFICATION notification)
395 {
396 switch (notificationType)
397 {
398 case NativeMethods.FDI.NOTIFICATIONTYPE.CABINET_INFO:
399 {
400 string nextCab = Marshal.PtrToStringAnsi(notification.psz1);
401 this.NextCabinetName = (nextCab.Length != 0 ? nextCab : null);
402 return 0; // Continue
403 }
404 case NativeMethods.FDI.NOTIFICATIONTYPE.PARTIAL_FILE:
405 {
406 // This notification can occur when examining the contents of a non-first cab file.
407 return 0; // Continue
408 }
409 case NativeMethods.FDI.NOTIFICATIONTYPE.COPY_FILE:
410 {
411 //bool execute = (notification.attribs & (ushort) FileAttributes.Device) != 0; // _A_EXEC
412
413 string name = CabUnpacker.GetFileName(notification);
414
415 if (this.filter == null || this.filter(name))
416 {
417 if (this.fileList != null)
418 {
419 FileAttributes attributes = (FileAttributes) notification.attribs &
420 (FileAttributes.Archive | FileAttributes.Hidden | FileAttributes.ReadOnly | FileAttributes.System);
421 if (attributes == (FileAttributes) 0)
422 {
423 attributes = FileAttributes.Normal;
424 }
425 DateTime lastWriteTime;
426 CompressionEngine.DosDateAndTimeToDateTime(notification.date, notification.time, out lastWriteTime);
427 long length = notification.cb;
428
429 CabFileInfo fileInfo = new CabFileInfo(
430 name,
431 notification.iFolder,
432 notification.iCabinet,
433 attributes,
434 lastWriteTime,
435 length);
436 this.fileList.Add(fileInfo);
437 this.currentFileNumber = this.fileList.Count - 1;
438 this.fileBytesProcessed += notification.cb;
439 }
440 }
441
442 this.totalFiles++;
443 this.totalFileBytes += notification.cb;
444 return 0; // Continue
445 }
446 }
447 return 0;
448 }
449
450 private int CabExtractNotify(NativeMethods.FDI.NOTIFICATIONTYPE notificationType, NativeMethods.FDI.NOTIFICATION notification)
451 {
452 switch (notificationType)
453 {
454 case NativeMethods.FDI.NOTIFICATIONTYPE.CABINET_INFO:
455 {
456 if (this.NextCabinetName != null && this.NextCabinetName.StartsWith("?", StringComparison.Ordinal))
457 {
458 // We are just continuing the copy of a file that spanned cabinets.
459 // The next cabinet name needs to be preserved.
460 this.NextCabinetName = this.NextCabinetName.Substring(1);
461 }
462 else
463 {
464 string nextCab = Marshal.PtrToStringAnsi(notification.psz1);
465 this.NextCabinetName = (nextCab.Length != 0 ? nextCab : null);
466 }
467 return 0; // Continue
468 }
469 case NativeMethods.FDI.NOTIFICATIONTYPE.NEXT_CABINET:
470 {
471 string nextCab = Marshal.PtrToStringAnsi(notification.psz1);
472 this.CabNumbers[nextCab] = (short) notification.iCabinet;
473 this.NextCabinetName = "?" + this.NextCabinetName;
474 return 0; // Continue
475 }
476 case NativeMethods.FDI.NOTIFICATIONTYPE.COPY_FILE:
477 {
478 return this.CabExtractCopyFile(notification);
479 }
480 case NativeMethods.FDI.NOTIFICATIONTYPE.CLOSE_FILE_INFO:
481 {
482 return this.CabExtractCloseFile(notification);
483 }
484 }
485 return 0;
486 }
487
488 private int CabExtractCopyFile(NativeMethods.FDI.NOTIFICATION notification)
489 {
490 if (notification.iFolder != this.folderId)
491 {
492 if (notification.iFolder != -3) // -3 is a special folderId used when continuing a folder from a previous cab
493 {
494 if (this.folderId != -1) // -1 means we just started the extraction sequence
495 {
496 this.currentFolderNumber++;
497 }
498 }
499 this.folderId = notification.iFolder;
500 }
501
502 //bool execute = (notification.attribs & (ushort) FileAttributes.Device) != 0; // _A_EXEC
503
504 string name = CabUnpacker.GetFileName(notification);
505
506 if (this.filter == null || this.filter(name))
507 {
508 this.currentFileNumber++;
509 this.currentFileName = name;
510
511 this.currentFileBytesProcessed = 0;
512 this.currentFileTotalBytes = notification.cb;
513 this.OnProgress(ArchiveProgressType.StartFile);
514
515 DateTime lastWriteTime;
516 CompressionEngine.DosDateAndTimeToDateTime(notification.date, notification.time, out lastWriteTime);
517
518 Stream stream = this.context.OpenFileWriteStream(name, notification.cb, lastWriteTime);
519 if (stream != null)
520 {
521 this.FileStream = stream;
522 int streamHandle = this.StreamHandles.AllocHandle(stream);
523 return streamHandle;
524 }
525 else
526 {
527 this.fileBytesProcessed += notification.cb;
528 this.OnProgress(ArchiveProgressType.FinishFile);
529 this.currentFileName = null;
530 }
531 }
532 return 0; // Continue
533 }
534
535 private int CabExtractCloseFile(NativeMethods.FDI.NOTIFICATION notification)
536 {
537 Stream stream = this.StreamHandles[notification.hf];
538 this.StreamHandles.FreeHandle(notification.hf);
539
540 //bool execute = (notification.attribs & (ushort) FileAttributes.Device) != 0; // _A_EXEC
541
542 string name = CabUnpacker.GetFileName(notification);
543
544 FileAttributes attributes = (FileAttributes) notification.attribs &
545 (FileAttributes.Archive | FileAttributes.Hidden | FileAttributes.ReadOnly | FileAttributes.System);
546 if (attributes == (FileAttributes) 0)
547 {
548 attributes = FileAttributes.Normal;
549 }
550 DateTime lastWriteTime;
551 CompressionEngine.DosDateAndTimeToDateTime(notification.date, notification.time, out lastWriteTime);
552
553 stream.Flush();
554 this.context.CloseFileWriteStream(name, stream, attributes, lastWriteTime);
555 this.FileStream = null;
556
557 long remainder = this.currentFileTotalBytes - this.currentFileBytesProcessed;
558 this.currentFileBytesProcessed += remainder;
559 this.fileBytesProcessed += remainder;
560 this.OnProgress(ArchiveProgressType.FinishFile);
561 this.currentFileName = null;
562
563 return 1; // Continue
564 }
565 }
566}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/CabWorker.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/CabWorker.cs
new file mode 100644
index 00000000..cb2a7263
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/CabWorker.cs
@@ -0,0 +1,337 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Cab
4{
5 using System;
6 using System.IO;
7 using System.IO.IsolatedStorage;
8 using System.Text;
9 using System.Security;
10 using System.Collections.Generic;
11 using System.Runtime.InteropServices;
12 using System.Diagnostics.CodeAnalysis;
13
14 internal abstract class CabWorker : IDisposable
15 {
16 internal const string CabStreamName = "%%CAB%%";
17
18 private CabEngine cabEngine;
19
20 private HandleManager<Stream> streamHandles;
21 private Stream cabStream;
22 private Stream fileStream;
23
24 private NativeMethods.ERF erf;
25 private GCHandle erfHandle;
26
27 private IDictionary<string, short> cabNumbers;
28 private string nextCabinetName;
29
30 private bool suppressProgressEvents;
31
32 private byte[] buf;
33
34 // Progress data
35 protected string currentFileName;
36 protected int currentFileNumber;
37 protected int totalFiles;
38 protected long currentFileBytesProcessed;
39 protected long currentFileTotalBytes;
40 protected short currentFolderNumber;
41 protected long currentFolderTotalBytes;
42 protected string currentArchiveName;
43 protected short currentArchiveNumber;
44 protected short totalArchives;
45 protected long currentArchiveBytesProcessed;
46 protected long currentArchiveTotalBytes;
47 protected long fileBytesProcessed;
48 protected long totalFileBytes;
49
50 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
51 protected CabWorker(CabEngine cabEngine)
52 {
53 this.cabEngine = cabEngine;
54 this.streamHandles = new HandleManager<Stream>();
55 this.erf = new NativeMethods.ERF();
56 this.erfHandle = GCHandle.Alloc(this.erf, GCHandleType.Pinned);
57 this.cabNumbers = new Dictionary<string, short>(1);
58
59 // 32K seems to be the size of the largest chunks processed by cabinet.dll.
60 // But just in case, this buffer will auto-enlarge.
61 this.buf = new byte[32768];
62 }
63
64 ~CabWorker()
65 {
66 this.Dispose(false);
67 }
68
69 public CabEngine CabEngine
70 {
71 get
72 {
73 return this.cabEngine;
74 }
75 }
76
77 internal NativeMethods.ERF Erf
78 {
79 get
80 {
81 return this.erf;
82 }
83 }
84
85 internal GCHandle ErfHandle
86 {
87 get
88 {
89 return this.erfHandle;
90 }
91 }
92
93 internal HandleManager<Stream> StreamHandles
94 {
95 get
96 {
97 return this.streamHandles;
98 }
99 }
100
101 internal bool SuppressProgressEvents
102 {
103 get
104 {
105 return this.suppressProgressEvents;
106 }
107
108 set
109 {
110 this.suppressProgressEvents = value;
111 }
112 }
113
114 internal IDictionary<string, short> CabNumbers
115 {
116 get
117 {
118 return this.cabNumbers;
119 }
120 }
121
122 internal string NextCabinetName
123 {
124 get
125 {
126 return this.nextCabinetName;
127 }
128
129 set
130 {
131 this.nextCabinetName = value;
132 }
133 }
134
135 internal Stream CabStream
136 {
137 get
138 {
139 return this.cabStream;
140 }
141
142 set
143 {
144 this.cabStream = value;
145 }
146 }
147
148 internal Stream FileStream
149 {
150 get
151 {
152 return this.fileStream;
153 }
154
155 set
156 {
157 this.fileStream = value;
158 }
159 }
160
161 public void Dispose()
162 {
163 this.Dispose(true);
164 GC.SuppressFinalize(this);
165 }
166
167 protected void ResetProgressData()
168 {
169 this.currentFileName = null;
170 this.currentFileNumber = 0;
171 this.totalFiles = 0;
172 this.currentFileBytesProcessed = 0;
173 this.currentFileTotalBytes = 0;
174 this.currentFolderNumber = 0;
175 this.currentFolderTotalBytes = 0;
176 this.currentArchiveName = null;
177 this.currentArchiveNumber = 0;
178 this.totalArchives = 0;
179 this.currentArchiveBytesProcessed = 0;
180 this.currentArchiveTotalBytes = 0;
181 this.fileBytesProcessed = 0;
182 this.totalFileBytes = 0;
183 }
184
185 protected void OnProgress(ArchiveProgressType progressType)
186 {
187 if (!this.suppressProgressEvents)
188 {
189 ArchiveProgressEventArgs e = new ArchiveProgressEventArgs(
190 progressType,
191 this.currentFileName,
192 this.currentFileNumber >= 0 ? this.currentFileNumber : 0,
193 this.totalFiles,
194 this.currentFileBytesProcessed,
195 this.currentFileTotalBytes,
196 this.currentArchiveName,
197 this.currentArchiveNumber,
198 this.totalArchives,
199 this.currentArchiveBytesProcessed,
200 this.currentArchiveTotalBytes,
201 this.fileBytesProcessed,
202 this.totalFileBytes);
203 this.CabEngine.ReportProgress(e);
204 }
205 }
206
207 internal IntPtr CabAllocMem(int byteCount)
208 {
209 IntPtr memPointer = Marshal.AllocHGlobal((IntPtr) byteCount);
210 return memPointer;
211 }
212
213 internal void CabFreeMem(IntPtr memPointer)
214 {
215 Marshal.FreeHGlobal(memPointer);
216 }
217
218 internal int CabOpenStream(string path, int openFlags, int shareMode)
219 {
220 int err; return this.CabOpenStreamEx(path, openFlags, shareMode, out err, IntPtr.Zero);
221 }
222
223 internal virtual int CabOpenStreamEx(string path, int openFlags, int shareMode, out int err, IntPtr pv)
224 {
225 path = path.Trim();
226 Stream stream = this.cabStream;
227 this.cabStream = new DuplicateStream(stream);
228 int streamHandle = this.streamHandles.AllocHandle(stream);
229 err = 0;
230 return streamHandle;
231 }
232
233 internal int CabReadStream(int streamHandle, IntPtr memory, int cb)
234 {
235 int err; return this.CabReadStreamEx(streamHandle, memory, cb, out err, IntPtr.Zero);
236 }
237
238 internal virtual int CabReadStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
239 {
240 Stream stream = this.streamHandles[streamHandle];
241 int count = (int) cb;
242 if (count > this.buf.Length)
243 {
244 this.buf = new byte[count];
245 }
246 count = stream.Read(this.buf, 0, count);
247 Marshal.Copy(this.buf, 0, memory, count);
248 err = 0;
249 return count;
250 }
251
252 internal int CabWriteStream(int streamHandle, IntPtr memory, int cb)
253 {
254 int err; return this.CabWriteStreamEx(streamHandle, memory, cb, out err, IntPtr.Zero);
255 }
256
257 internal virtual int CabWriteStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
258 {
259 Stream stream = this.streamHandles[streamHandle];
260 int count = (int) cb;
261 if (count > this.buf.Length)
262 {
263 this.buf = new byte[count];
264 }
265 Marshal.Copy(memory, this.buf, 0, count);
266 stream.Write(this.buf, 0, count);
267 err = 0;
268 return cb;
269 }
270
271 internal int CabCloseStream(int streamHandle)
272 {
273 int err; return this.CabCloseStreamEx(streamHandle, out err, IntPtr.Zero);
274 }
275
276 internal virtual int CabCloseStreamEx(int streamHandle, out int err, IntPtr pv)
277 {
278 this.streamHandles.FreeHandle(streamHandle);
279 err = 0;
280 return 0;
281 }
282
283 internal int CabSeekStream(int streamHandle, int offset, int seekOrigin)
284 {
285 int err; return this.CabSeekStreamEx(streamHandle, offset, seekOrigin, out err, IntPtr.Zero);
286 }
287
288 internal virtual int CabSeekStreamEx(int streamHandle, int offset, int seekOrigin, out int err, IntPtr pv)
289 {
290 Stream stream = this.streamHandles[streamHandle];
291 offset = (int) stream.Seek(offset, (SeekOrigin) seekOrigin);
292 err = 0;
293 return offset;
294 }
295
296 /// <summary>
297 /// Disposes of resources allocated by the cabinet engine.
298 /// </summary>
299 /// <param name="disposing">If true, the method has been called directly or indirectly by a user's code,
300 /// so managed and unmanaged resources will be disposed. If false, the method has been called by the
301 /// runtime from inside the finalizer, and only unmanaged resources will be disposed.</param>
302 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
303 protected virtual void Dispose(bool disposing)
304 {
305 if (disposing)
306 {
307 if (this.cabStream != null)
308 {
309 this.cabStream.Close();
310 this.cabStream = null;
311 }
312
313 if (this.fileStream != null)
314 {
315 this.fileStream.Close();
316 this.fileStream = null;
317 }
318 }
319
320 if (this.erfHandle.IsAllocated)
321 {
322 this.erfHandle.Free();
323 }
324 }
325
326 protected void CheckError(bool extracting)
327 {
328 if (this.Erf.Error)
329 {
330 throw new CabException(
331 this.Erf.Oper,
332 this.Erf.Type,
333 CabException.GetErrorMessage(this.Erf.Oper, this.Erf.Type, extracting));
334 }
335 }
336 }
337}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/Errors.resources b/src/dtf/WixToolset.Dtf.Compression.Cab/Errors.resources
new file mode 100644
index 00000000..d53d263c
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/Errors.resources
Binary files differ
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/Errors.txt b/src/dtf/WixToolset.Dtf.Compression.Cab/Errors.txt
new file mode 100644
index 00000000..df5a95d3
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/Errors.txt
@@ -0,0 +1,35 @@
1;
2; Cabinet Error Messages
3;
4
5; Generic error message.
61=Error code: {1}
7
8;
9; Cabinet creation messages - offset by 1000
10;
111000=Unknown error creating cabinet.
121001=Failure opening file to be stored in cabinet.
131002=Failure reading file to be stored in cabinet.
141003=Could not allocate enough memory to create cabinet.
151004=Could not create a temporary file.
161005=Unknown compression type.
171006=Could not create cabinet file.
181007=Client requested abort.
191008=Failure compressing data.
20
21;
22; Cabinet extraction messages - offset by 2000
23;
242000=Unknown error extracting cabinet.
252001=Cabinet not found.
262002=Cabinet file does not have the correct format.
272003=Cabinet file has an unknown version number.
282004=Cabinet file is corrupt.
292005=Could not allocate enough memory to extract cabinet.
302006=Unknown compression type in a cabinet folder.
312007=Failure decompressing data from a cabinet file.
322008=Failure writing to target file.
332009=Cabinets in a set do not have the same RESERVE sizes.
342010=Cabinet returned on NEXT_CABINET is incorrect.
352011=Client requested abort.
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/HandleManager.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/HandleManager.cs
new file mode 100644
index 00000000..aad9a317
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/HandleManager.cs
@@ -0,0 +1,76 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Cab
4{
5 using System;
6 using System.Collections.Generic;
7
8 /// <summary>
9 /// Generic class for managing allocations of integer handles
10 /// for objects of a certain type.
11 /// </summary>
12 /// <typeparam name="T">The type of objects the handles refer to.</typeparam>
13 internal sealed class HandleManager<T> where T : class
14 {
15 /// <summary>
16 /// Auto-resizing list of objects for which handles have been allocated.
17 /// Each handle is just an index into this list. When a handle is freed,
18 /// the list item at that index is set to null.
19 /// </summary>
20 private List<T> handles;
21
22 /// <summary>
23 /// Creates a new HandleManager instance.
24 /// </summary>
25 public HandleManager()
26 {
27 this.handles = new List<T>();
28 }
29
30 /// <summary>
31 /// Gets the object of a handle, or null if the handle is invalid.
32 /// </summary>
33 /// <param name="handle">The integer handle previously allocated
34 /// for the desired object.</param>
35 /// <returns>The object for which the handle was allocated.</returns>
36 public T this[int handle]
37 {
38 get
39 {
40 if (handle > 0 && handle <= this.handles.Count)
41 {
42 return this.handles[handle - 1];
43 }
44 else
45 {
46 return null;
47 }
48 }
49 }
50
51 /// <summary>
52 /// Allocates a new handle for an object.
53 /// </summary>
54 /// <param name="obj">Object that the handle will refer to.</param>
55 /// <returns>New handle that can be later used to retrieve the object.</returns>
56 public int AllocHandle(T obj)
57 {
58 this.handles.Add(obj);
59 int handle = this.handles.Count;
60 return handle;
61 }
62
63 /// <summary>
64 /// Frees a handle that was previously allocated. Afterward the handle
65 /// will be invalid and the object it referred to can no longer retrieved.
66 /// </summary>
67 /// <param name="handle">Handle to be freed.</param>
68 public void FreeHandle(int handle)
69 {
70 if (handle > 0 && handle <= this.handles.Count)
71 {
72 this.handles[handle - 1] = null;
73 }
74 }
75 }
76}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/NativeMethods.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/NativeMethods.cs
new file mode 100644
index 00000000..562e96dd
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/NativeMethods.cs
@@ -0,0 +1,407 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Cab
4{
5using System;
6using System.Text;
7using System.Security;
8using System.Runtime.InteropServices;
9using System.Diagnostics.CodeAnalysis;
10
11/// <summary>
12/// Native DllImport methods and related structures and constants used for
13/// cabinet creation and extraction via cabinet.dll.
14/// </summary>
15internal static class NativeMethods
16{
17 /// <summary>
18 /// A direct import of constants, enums, structures, delegates, and functions from fci.h.
19 /// Refer to comments in fci.h for documentation.
20 /// </summary>
21 internal static class FCI
22 {
23 internal const int MIN_DISK = 32768;
24 internal const int MAX_DISK = Int32.MaxValue;
25 internal const int MAX_FOLDER = 0x7FFF8000;
26 internal const int MAX_FILENAME = 256;
27 internal const int MAX_CABINET_NAME = 256;
28 internal const int MAX_CAB_PATH = 256;
29 internal const int MAX_DISK_NAME = 256;
30
31 internal const int CPU_80386 = 1;
32
33 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate IntPtr PFNALLOC(int cb);
34 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void PFNFREE(IntPtr pv);
35
36 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNOPEN(string path, int oflag, int pmode, out int err, IntPtr pv);
37 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNREAD(int fileHandle, IntPtr memory, int cb, out int err, IntPtr pv);
38 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNWRITE(int fileHandle, IntPtr memory, int cb, out int err, IntPtr pv);
39 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNCLOSE(int fileHandle, out int err, IntPtr pv);
40 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNSEEK(int fileHandle, int dist, int seekType, out int err, IntPtr pv);
41 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNDELETE(string path, out int err, IntPtr pv);
42
43 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNGETNEXTCABINET(IntPtr pccab, uint cbPrevCab, IntPtr pv);
44 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNFILEPLACED(IntPtr pccab, string path, long fileSize, int continuation, IntPtr pv);
45 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNGETOPENINFO(string path, out short date, out short time, out short pattribs, out int err, IntPtr pv);
46 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNSTATUS(STATUS typeStatus, uint cb1, uint cb2, IntPtr pv);
47 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNGETTEMPFILE(IntPtr tempNamePtr, int tempNameSize, IntPtr pv);
48
49 /// <summary>
50 /// Error codes that can be returned by FCI.
51 /// </summary>
52 internal enum ERROR : int
53 {
54 NONE,
55 OPEN_SRC,
56 READ_SRC,
57 ALLOC_FAIL,
58 TEMP_FILE,
59 BAD_COMPR_TYPE,
60 CAB_FILE,
61 USER_ABORT,
62 MCI_FAIL,
63 }
64
65 /// <summary>
66 /// FCI compression algorithm types and parameters.
67 /// </summary>
68 internal enum TCOMP : ushort
69 {
70 MASK_TYPE = 0x000F,
71 TYPE_NONE = 0x0000,
72 TYPE_MSZIP = 0x0001,
73 TYPE_QUANTUM = 0x0002,
74 TYPE_LZX = 0x0003,
75 BAD = 0x000F,
76
77 MASK_LZX_WINDOW = 0x1F00,
78 LZX_WINDOW_LO = 0x0F00,
79 LZX_WINDOW_HI = 0x1500,
80 SHIFT_LZX_WINDOW = 0x0008,
81
82 MASK_QUANTUM_LEVEL = 0x00F0,
83 QUANTUM_LEVEL_LO = 0x0010,
84 QUANTUM_LEVEL_HI = 0x0070,
85 SHIFT_QUANTUM_LEVEL = 0x0004,
86
87 MASK_QUANTUM_MEM = 0x1F00,
88 QUANTUM_MEM_LO = 0x0A00,
89 QUANTUM_MEM_HI = 0x1500,
90 SHIFT_QUANTUM_MEM = 0x0008,
91
92 MASK_RESERVED = 0xE000,
93 }
94
95 /// <summary>
96 /// Reason for FCI status callback.
97 /// </summary>
98 internal enum STATUS : uint
99 {
100 FILE = 0,
101 FOLDER = 1,
102 CABINET = 2,
103 }
104
105 [SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments")]
106 [DllImport("cabinet.dll", EntryPoint = "FCICreate", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
107 internal static extern Handle Create(IntPtr perf, PFNFILEPLACED pfnfcifp, PFNALLOC pfna, PFNFREE pfnf, PFNOPEN pfnopen, PFNREAD pfnread, PFNWRITE pfnwrite, PFNCLOSE pfnclose, PFNSEEK pfnseek, PFNDELETE pfndelete, PFNGETTEMPFILE pfnfcigtf, [MarshalAs(UnmanagedType.LPStruct)] CCAB pccab, IntPtr pv);
108
109 [DllImport("cabinet.dll", EntryPoint = "FCIAddFile", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
110 internal static extern int AddFile(Handle hfci, string pszSourceFile, IntPtr pszFileName, [MarshalAs(UnmanagedType.Bool)] bool fExecute, PFNGETNEXTCABINET pfnfcignc, PFNSTATUS pfnfcis, PFNGETOPENINFO pfnfcigoi, TCOMP typeCompress);
111
112 [DllImport("cabinet.dll", EntryPoint = "FCIFlushCabinet", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
113 internal static extern int FlushCabinet(Handle hfci, [MarshalAs(UnmanagedType.Bool)] bool fGetNextCab, PFNGETNEXTCABINET pfnfcignc, PFNSTATUS pfnfcis);
114
115 [DllImport("cabinet.dll", EntryPoint = "FCIFlushFolder", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
116 internal static extern int FlushFolder(Handle hfci, PFNGETNEXTCABINET pfnfcignc, PFNSTATUS pfnfcis);
117
118 [SuppressUnmanagedCodeSecurity]
119 [DllImport("cabinet.dll", EntryPoint = "FCIDestroy", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
120 [return: MarshalAs(UnmanagedType.Bool)]
121 internal static extern bool Destroy(IntPtr hfci);
122
123 /// <summary>
124 /// Cabinet information structure used for FCI initialization and GetNextCabinet callback.
125 /// </summary>
126 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
127 internal class CCAB
128 {
129 internal int cb = MAX_DISK;
130 internal int cbFolderThresh = MAX_FOLDER;
131 internal int cbReserveCFHeader;
132 internal int cbReserveCFFolder;
133 internal int cbReserveCFData;
134 internal int iCab;
135 internal int iDisk;
136 internal int fFailOnIncompressible;
137 internal short setID;
138 [MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_DISK_NAME )] internal string szDisk = String.Empty;
139 [MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_CABINET_NAME)] internal string szCab = String.Empty;
140 [MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_CAB_PATH )] internal string szCabPath = String.Empty;
141 }
142
143 /// <summary>
144 /// Ensures that the FCI handle is safely released.
145 /// </summary>
146 internal class Handle : SafeHandle
147 {
148 /// <summary>
149 /// Creates a new unintialized handle. The handle will be initialized
150 /// when it is marshalled back from native code.
151 /// </summary>
152 internal Handle()
153 : base(IntPtr.Zero, true)
154 {
155 }
156
157 /// <summary>
158 /// Checks if the handle is invalid. An FCI handle is invalid when it is zero.
159 /// </summary>
160 public override bool IsInvalid
161 {
162 get
163 {
164 return this.handle == IntPtr.Zero;
165 }
166 }
167
168 /// <summary>
169 /// Releases the handle by calling FDIDestroy().
170 /// </summary>
171 /// <returns>True if the release succeeded.</returns>
172 protected override bool ReleaseHandle()
173 {
174 return FCI.Destroy(this.handle);
175 }
176 }
177 }
178
179 /// <summary>
180 /// A direct import of constants, enums, structures, delegates, and functions from fdi.h.
181 /// Refer to comments in fdi.h for documentation.
182 /// </summary>
183 internal static class FDI
184 {
185 internal const int MAX_DISK = Int32.MaxValue;
186 internal const int MAX_FILENAME = 256;
187 internal const int MAX_CABINET_NAME = 256;
188 internal const int MAX_CAB_PATH = 256;
189 internal const int MAX_DISK_NAME = 256;
190
191 internal const int CPU_80386 = 1;
192
193 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate IntPtr PFNALLOC(int cb);
194 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void PFNFREE(IntPtr pv);
195
196 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNOPEN(string path, int oflag, int pmode);
197 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNREAD(int hf, IntPtr pv, int cb);
198 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNWRITE(int hf, IntPtr pv, int cb);
199 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNCLOSE(int hf);
200 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNSEEK(int hf, int dist, int seektype);
201
202 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNNOTIFY(NOTIFICATIONTYPE fdint, NOTIFICATION fdin);
203
204 /// <summary>
205 /// Error codes that can be returned by FDI.
206 /// </summary>
207 internal enum ERROR : int
208 {
209 NONE,
210 CABINET_NOT_FOUND,
211 NOT_A_CABINET,
212 UNKNOWN_CABINET_VERSION,
213 CORRUPT_CABINET,
214 ALLOC_FAIL,
215 BAD_COMPR_TYPE,
216 MDI_FAIL,
217 TARGET_FILE,
218 RESERVE_MISMATCH,
219 WRONG_CABINET,
220 USER_ABORT,
221 }
222
223 /// <summary>
224 /// Type of notification message for the FDI Notify callback.
225 /// </summary>
226 internal enum NOTIFICATIONTYPE : int
227 {
228 CABINET_INFO,
229 PARTIAL_FILE,
230 COPY_FILE,
231 CLOSE_FILE_INFO,
232 NEXT_CABINET,
233 ENUMERATE,
234 }
235
236 [DllImport("cabinet.dll", EntryPoint = "FDICreate", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
237 internal static extern Handle Create([MarshalAs(UnmanagedType.FunctionPtr)] PFNALLOC pfnalloc, [MarshalAs(UnmanagedType.FunctionPtr)] PFNFREE pfnfree, PFNOPEN pfnopen, PFNREAD pfnread, PFNWRITE pfnwrite, PFNCLOSE pfnclose, PFNSEEK pfnseek, int cpuType, IntPtr perf);
238
239 [DllImport("cabinet.dll", EntryPoint = "FDICopy", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
240 internal static extern int Copy(Handle hfdi, string pszCabinet, string pszCabPath, int flags, PFNNOTIFY pfnfdin, IntPtr pfnfdid, IntPtr pvUser);
241
242 [SuppressUnmanagedCodeSecurity]
243 [DllImport("cabinet.dll", EntryPoint = "FDIDestroy", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
244 [return: MarshalAs(UnmanagedType.Bool)]
245 internal static extern bool Destroy(IntPtr hfdi);
246
247 [DllImport("cabinet.dll", EntryPoint = "FDIIsCabinet", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
248 [SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable", Justification="FDI file handles definitely remain 4 bytes on 64bit platforms.")]
249 internal static extern int IsCabinet(Handle hfdi, int hf, out CABINFO pfdici);
250
251 /// <summary>
252 /// Cabinet information structure filled in by FDI IsCabinet.
253 /// </summary>
254 [StructLayout(LayoutKind.Sequential)]
255 internal struct CABINFO
256 {
257 internal int cbCabinet;
258 internal short cFolders;
259 internal short cFiles;
260 internal short setID;
261 internal short iCabinet;
262 internal int fReserve;
263 internal int hasprev;
264 internal int hasnext;
265 }
266
267 /// <summary>
268 /// Cabinet notification details passed to the FDI Notify callback.
269 /// </summary>
270 [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")]
271 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
272 internal class NOTIFICATION
273 {
274 internal int cb;
275 internal IntPtr psz1;
276 internal IntPtr psz2;
277 internal IntPtr psz3;
278 internal IntPtr pv;
279
280 internal IntPtr hf_ptr;
281
282 internal short date;
283 internal short time;
284 internal short attribs;
285 internal short setID;
286 internal short iCabinet;
287 internal short iFolder;
288 internal int fdie;
289
290 // Unlike all the other file handles in FCI/FDI, this one is
291 // actually pointer-sized. Use a property to pretend it isn't.
292 internal int hf
293 {
294 get { return (int) this.hf_ptr; }
295 }
296 }
297
298 /// <summary>
299 /// Ensures that the FDI handle is safely released.
300 /// </summary>
301 internal class Handle : SafeHandle
302 {
303 /// <summary>
304 /// Creates a new unintialized handle. The handle will be initialized
305 /// when it is marshalled back from native code.
306 /// </summary>
307 internal Handle()
308 : base(IntPtr.Zero, true)
309 {
310 }
311
312 /// <summary>
313 /// Checks if the handle is invalid. An FDI handle is invalid when it is zero.
314 /// </summary>
315 public override bool IsInvalid
316 {
317 get
318 {
319 return this.handle == IntPtr.Zero;
320 }
321 }
322
323 /// <summary>
324 /// Releases the handle by calling FDIDestroy().
325 /// </summary>
326 /// <returns>True if the release succeeded.</returns>
327 protected override bool ReleaseHandle()
328 {
329 return FDI.Destroy(this.handle);
330 }
331 }
332 }
333
334 /// <summary>
335 /// Error info structure for FCI and FDI.
336 /// </summary>
337 /// <remarks>Before being passed to FCI or FDI, this structure is
338 /// pinned in memory via a GCHandle. The pinning is necessary
339 /// to be able to read the results, since the ERF structure doesn't
340 /// get marshalled back out after an error.</remarks>
341 [StructLayout(LayoutKind.Sequential)]
342 internal class ERF
343 {
344 private int erfOper;
345 private int erfType;
346 private int fError;
347
348 /// <summary>
349 /// Gets or sets the cabinet error code.
350 /// </summary>
351 internal int Oper
352 {
353 get
354 {
355 return this.erfOper;
356 }
357
358 set
359 {
360 this.erfOper = value;
361 }
362 }
363
364 /// <summary>
365 /// Gets or sets the Win32 error code.
366 /// </summary>
367 internal int Type
368 {
369 get
370 {
371 return this.erfType;
372 }
373
374 set
375 {
376 this.erfType = value;
377 }
378 }
379
380 /// <summary>
381 /// GCHandle doesn't like the bool type, so use an int underneath.
382 /// </summary>
383 internal bool Error
384 {
385 get
386 {
387 return this.fError != 0;
388 }
389
390 set
391 {
392 this.fError = value ? 1 : 0;
393 }
394 }
395
396 /// <summary>
397 /// Clears the error information.
398 /// </summary>
399 internal void Clear()
400 {
401 this.Oper = 0;
402 this.Type = 0;
403 this.Error = false;
404 }
405 }
406}
407}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/WixToolset.Dtf.Compression.Cab.csproj b/src/dtf/WixToolset.Dtf.Compression.Cab/WixToolset.Dtf.Compression.Cab.csproj
new file mode 100644
index 00000000..6b2c8cf8
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/WixToolset.Dtf.Compression.Cab.csproj
@@ -0,0 +1,26 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <RootNamespace>WixToolset.Dtf.Compression.Cab</RootNamespace>
7 <AssemblyName>WixToolset.Dtf.Compression.Cab</AssemblyName>
8 <TargetFrameworks>netstandard2.0;net20</TargetFrameworks>
9 <Description>Managed libraries for cabinet archive packing and unpacking</Description>
10 <CreateDocumentationFile>true</CreateDocumentationFile>
11 </PropertyGroup>
12
13 <ItemGroup>
14 <None Include="Errors.txt" />
15 <EmbeddedResource Include="Errors.resources" />
16 </ItemGroup>
17
18 <ItemGroup>
19 <ProjectReference Include="..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" />
20 </ItemGroup>
21
22 <ItemGroup>
23 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
24 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
25 </ItemGroup>
26</Project>
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/AssemblyInfo.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/AssemblyInfo.cs
new file mode 100644
index 00000000..f782bbb8
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/AssemblyInfo.cs
@@ -0,0 +1,5 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System.Diagnostics.CodeAnalysis;
4
5[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "WixToolset.Dtf.Compression.Zip")]
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ConcatStream.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ConcatStream.cs
new file mode 100644
index 00000000..20d675d9
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ConcatStream.cs
@@ -0,0 +1,157 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Zip
4{
5 using System;
6 using System.IO;
7
8 /// <summary>
9 /// Used to trick a DeflateStream into reading from or writing to
10 /// a series of (chunked) streams instead of a single steream.
11 /// </summary>
12 internal class ConcatStream : Stream
13 {
14 private Stream source;
15 private long position;
16 private long length;
17 private Action<ConcatStream> nextStreamHandler;
18
19 public ConcatStream(Action<ConcatStream> nextStreamHandler)
20 {
21 if (nextStreamHandler == null)
22 {
23 throw new ArgumentNullException("nextStreamHandler");
24 }
25
26 this.nextStreamHandler = nextStreamHandler;
27 this.length = Int64.MaxValue;
28 }
29
30 public Stream Source
31 {
32 get { return this.source; }
33 set { this.source = value; }
34 }
35
36 public override bool CanRead
37 {
38 get { return true; }
39 }
40
41 public override bool CanWrite
42 {
43 get { return true; }
44 }
45
46 public override bool CanSeek
47 {
48 get { return false; }
49 }
50
51 public override long Length
52 {
53 get
54 {
55 return this.length;
56 }
57 }
58
59 public override long Position
60 {
61 get { return this.position; }
62 set { throw new NotSupportedException(); }
63 }
64
65 public override int Read(byte[] buffer, int offset, int count)
66 {
67 if (this.source == null)
68 {
69 this.nextStreamHandler(this);
70 }
71
72 count = (int) Math.Min(count, this.length - this.position);
73
74 int bytesRemaining = count;
75 while (bytesRemaining > 0)
76 {
77 if (this.source == null)
78 {
79 throw new InvalidOperationException();
80 }
81
82 int partialCount = (int) Math.Min(bytesRemaining,
83 this.source.Length - this.source.Position);
84
85 if (partialCount == 0)
86 {
87 this.nextStreamHandler(this);
88 continue;
89 }
90
91 partialCount = this.source.Read(
92 buffer, offset + count - bytesRemaining, partialCount);
93 bytesRemaining -= partialCount;
94 this.position += partialCount;
95 }
96
97 return count;
98 }
99
100 public override void Write(byte[] buffer, int offset, int count)
101 {
102 if (this.source == null)
103 {
104 this.nextStreamHandler(this);
105 }
106
107 int bytesRemaining = count;
108 while (bytesRemaining > 0)
109 {
110 if (this.source == null)
111 {
112 throw new InvalidOperationException();
113 }
114
115 int partialCount = (int) Math.Min(bytesRemaining,
116 Math.Max(0, this.length - this.source.Position));
117
118 if (partialCount == 0)
119 {
120 this.nextStreamHandler(this);
121 continue;
122 }
123
124 this.source.Write(
125 buffer, offset + count - bytesRemaining, partialCount);
126 bytesRemaining -= partialCount;
127 this.position += partialCount;
128 }
129 }
130
131 public override void Flush()
132 {
133 if (this.source != null)
134 {
135 this.source.Flush();
136 }
137 }
138
139 public override long Seek(long offset, SeekOrigin origin)
140 {
141 throw new NotSupportedException();
142 }
143
144 public override void SetLength(long value)
145 {
146 this.length = value;
147 }
148
149 public override void Close()
150 {
151 if (this.source != null)
152 {
153 this.source.Close();
154 }
155 }
156 }
157}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/CrcStream.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/CrcStream.cs
new file mode 100644
index 00000000..c645ecc1
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/CrcStream.cs
@@ -0,0 +1,250 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Zip
4{
5 using System;
6 using System.IO;
7 using System.Diagnostics.CodeAnalysis;
8
9 /// <summary>
10 /// Wraps a source stream and calcaluates a CRC over all bytes that are read or written.
11 /// </summary>
12 /// <remarks>
13 /// The CRC algorithm matches that used in the standard ZIP file format.
14 /// </remarks>
15 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Crc")]
16 public class CrcStream : Stream
17 {
18 private Stream source;
19 private uint crc;
20
21 /// <summary>
22 /// Creates a new CrcStream instance from a source stream.
23 /// </summary>
24 /// <param name="source">Underlying stream where bytes will be read from or written to.</param>
25 public CrcStream(Stream source)
26 {
27 this.source = source;
28 }
29
30 /// <summary>
31 /// Gets the current CRC over all bytes that have been read or written
32 /// since this instance was created.
33 /// </summary>
34 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Crc")]
35 public uint Crc
36 {
37 get
38 {
39 return this.crc;
40 }
41 }
42
43 /// <summary>
44 /// Gets the underlying stream that this stream reads from or writes to.
45 /// </summary>
46 public Stream Source
47 {
48 get
49 {
50 return this.source;
51 }
52 }
53
54 /// <summary>
55 /// Gets a value indicating whether the source stream supports reading.
56 /// </summary>
57 /// <value>true if the stream supports reading; otherwise, false.</value>
58 public override bool CanRead
59 {
60 get
61 {
62 return this.source.CanRead;
63 }
64 }
65
66 /// <summary>
67 /// Gets a value indicating whether the source stream supports writing.
68 /// </summary>
69 /// <value>true if the stream supports writing; otherwise, false.</value>
70 public override bool CanWrite
71 {
72 get
73 {
74 return this.source.CanWrite;
75 }
76 }
77
78 /// <summary>
79 /// Gets a value indicating whether the source stream supports seeking.
80 /// </summary>
81 /// <value>true if the stream supports seeking; otherwise, false.</value>
82 public override bool CanSeek
83 {
84 get
85 {
86 return this.source.CanSeek;
87 }
88 }
89
90 /// <summary>
91 /// Gets the length of the source stream.
92 /// </summary>
93 public override long Length
94 {
95 get
96 {
97 return this.source.Length;
98 }
99 }
100
101 /// <summary>
102 /// Gets or sets the position of the source stream.
103 /// </summary>
104 public override long Position
105 {
106 get
107 {
108 return this.source.Position;
109 }
110
111 set
112 {
113 this.source.Position = value;
114 }
115 }
116
117 /// <summary>
118 /// Sets the position within the source stream.
119 /// </summary>
120 /// <param name="offset">A byte offset relative to the origin parameter.</param>
121 /// <param name="origin">A value of type SeekOrigin indicating
122 /// the reference point used to obtain the new position.</param>
123 /// <returns>The new position within the source stream.</returns>
124 /// <remarks>
125 /// Note the CRC is only calculated over bytes that are actually read or
126 /// written, so any bytes skipped by seeking will not contribute to the CRC.
127 /// </remarks>
128 public override long Seek(long offset, SeekOrigin origin)
129 {
130 return this.source.Seek(offset, origin);
131 }
132
133 /// <summary>
134 /// Sets the length of the source stream.
135 /// </summary>
136 /// <param name="value">The desired length of the
137 /// stream in bytes.</param>
138 public override void SetLength(long value)
139 {
140 this.source.SetLength(value);
141 }
142
143 /// <summary>
144 /// Reads a sequence of bytes from the source stream and advances
145 /// the position within the stream by the number of bytes read.
146 /// </summary>
147 /// <param name="buffer">An array of bytes. When this method returns, the buffer
148 /// contains the specified byte array with the values between offset and
149 /// (offset + count - 1) replaced by the bytes read from the current source.</param>
150 /// <param name="offset">The zero-based byte offset in buffer at which to begin
151 /// storing the data read from the current stream.</param>
152 /// <param name="count">The maximum number of bytes to be read from the current stream.</param>
153 /// <returns>The total number of bytes read into the buffer. This can be less
154 /// than the number of bytes requested if that many bytes are not currently available,
155 /// or zero (0) if the end of the stream has been reached.</returns>
156 public override int Read(byte[] buffer, int offset, int count)
157 {
158 count = this.source.Read(buffer, offset, count);
159 this.UpdateCrc(buffer, offset, count);
160 return count;
161 }
162
163 /// <summary>
164 /// Writes a sequence of bytes to the source stream and advances the
165 /// current position within this stream by the number of bytes written.
166 /// </summary>
167 /// <param name="buffer">An array of bytes. This method copies count
168 /// bytes from buffer to the current stream.</param>
169 /// <param name="offset">The zero-based byte offset in buffer at which
170 /// to begin copying bytes to the current stream.</param>
171 /// <param name="count">The number of bytes to be written to the
172 /// current stream.</param>
173 public override void Write(byte[] buffer, int offset, int count)
174 {
175 this.source.Write(buffer, offset, count);
176 this.UpdateCrc(buffer, offset, count);
177 }
178
179 /// <summary>
180 /// Flushes the source stream.
181 /// </summary>
182 public override void Flush()
183 {
184 this.source.Flush();
185 }
186
187 /// <summary>
188 /// Closes the underlying stream.
189 /// </summary>
190 public override void Close()
191 {
192 this.source.Close();
193 base.Close();
194 }
195
196 /// <summary>
197 /// Updates the CRC with a range of bytes that were read or written.
198 /// </summary>
199 private void UpdateCrc(byte[] buffer, int offset, int count)
200 {
201 this.crc = ~this.crc;
202 for( ; count > 0; count--, offset++)
203 {
204 this.crc = (this.crc >> 8) ^
205 CrcStream.crcTable[(this.crc & 0xFF) ^ buffer[offset]];
206 }
207 this.crc = ~this.crc;
208 }
209
210 private static uint[] crcTable = MakeCrcTable();
211
212 /// <summary>
213 /// Computes a table that speeds up calculation of the CRC.
214 /// </summary>
215 private static uint[] MakeCrcTable()
216 {
217 const uint poly = 0x04C11DB7u;
218 uint[] crcTable = new uint[256];
219 for(uint n = 0; n < 256; n++)
220 {
221 uint c = CrcStream.Reflect(n, 8);
222 c = c << 24;
223 for(uint k = 0; k < 8; k++)
224 {
225 c = (c << 1) ^ ((c & 0x80000000u) != 0 ? poly : 0);
226 }
227 crcTable[n] = CrcStream.Reflect(c, 32);
228 }
229 return crcTable;
230 }
231
232 /// <summary>
233 /// Reflects the ordering of certain number of bits. For exmample when reflecting
234 /// one byte, bit one is swapped with bit eight, bit two with bit seven, etc.
235 /// </summary>
236 private static uint Reflect(uint value, int bits)
237 {
238 for (int i = 0; i < bits / 2; i++)
239 {
240 uint leftBit = 1u << (bits - 1 - i);
241 uint rightBit = 1u << i;
242 if (((value & leftBit) != 0) != ((value & rightBit) != 0))
243 {
244 value ^= leftBit | rightBit;
245 }
246 }
247 return value;
248 }
249 }
250}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/WixToolset.Dtf.Compression.Zip.csproj b/src/dtf/WixToolset.Dtf.Compression.Zip/WixToolset.Dtf.Compression.Zip.csproj
new file mode 100644
index 00000000..2f5f2f27
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/WixToolset.Dtf.Compression.Zip.csproj
@@ -0,0 +1,21 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <RootNamespace>WixToolset.Dtf.Compression.Zip</RootNamespace>
7 <AssemblyName>WixToolset.Dtf.Compression.Zip</AssemblyName>
8 <TargetFrameworks>netstandard2.0;net20</TargetFrameworks>
9 <Description>Managed libraries for zip archive packing and unpacking</Description>
10 <CreateDocumentationFile>true</CreateDocumentationFile>
11 </PropertyGroup>
12
13 <ItemGroup>
14 <ProjectReference Include="..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" />
15 </ItemGroup>
16
17 <ItemGroup>
18 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
19 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
20 </ItemGroup>
21</Project>
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ZipCompressionMethod.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipCompressionMethod.cs
new file mode 100644
index 00000000..2e1c7567
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipCompressionMethod.cs
@@ -0,0 +1,80 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Zip
4{
5 using System.Diagnostics.CodeAnalysis;
6
7 /// <summary>
8 /// Identifies the compression method or &quot;algorithm&quot;
9 /// used for a single file within a zip archive.
10 /// </summary>
11 /// <remarks>
12 /// Proprietary zip implementations may define additional compression
13 /// methods outside of those included here.
14 /// </remarks>
15 public enum ZipCompressionMethod
16 {
17 /// <summary>
18 /// The file is stored (no compression)
19 /// </summary>
20 Store = 0,
21
22 /// <summary>
23 /// The file is Shrunk
24 /// </summary>
25 Shrink = 1,
26
27 /// <summary>
28 /// The file is Reduced with compression factor 1
29 /// </summary>
30 Reduce1 = 2,
31
32 /// <summary>
33 /// The file is Reduced with compression factor 2
34 /// </summary>
35 Reduce2 = 3,
36
37 /// <summary>
38 /// The file is Reduced with compression factor 3
39 /// </summary>
40 Reduce3 = 4,
41
42 /// <summary>
43 /// The file is Reduced with compression factor 4
44 /// </summary>
45 Reduce4 = 5,
46
47 /// <summary>
48 /// The file is Imploded
49 /// </summary>
50 Implode = 6,
51
52 /// <summary>
53 /// The file is Deflated;
54 /// the most common and widely-compatible form of zip compression.
55 /// </summary>
56 Deflate = 8,
57
58 /// <summary>
59 /// The file is Deflated using the enhanced Deflate64 method.
60 /// </summary>
61 Deflate64 = 9,
62
63 /// <summary>
64 /// The file is compressed using the BZIP2 algorithm.
65 /// </summary>
66 BZip2 = 12,
67
68 /// <summary>
69 /// The file is compressed using the LZMA algorithm.
70 /// </summary>
71 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Lzma")]
72 Lzma = 14,
73
74 /// <summary>
75 /// The file is compressed using the PPMd algorithm.
76 /// </summary>
77 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ppmd")]
78 Ppmd = 98
79 }
80}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ZipEngine.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipEngine.cs
new file mode 100644
index 00000000..36b4db89
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipEngine.cs
@@ -0,0 +1,478 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Zip
4{
5 using System;
6 using System.IO;
7 using System.IO.Compression;
8 using System.Collections.Generic;
9 using System.Reflection;
10 using System.Diagnostics.CodeAnalysis;
11
12 /// <summary>
13 /// Engine capable of packing and unpacking archives in the zip format.
14 /// </summary>
15 public partial class ZipEngine : CompressionEngine
16 {
17 private static Dictionary<ZipCompressionMethod, Converter<Stream, Stream>>
18 compressionStreamCreators;
19 private static Dictionary<ZipCompressionMethod, Converter<Stream, Stream>>
20 decompressionStreamCreators;
21
22 private static void InitCompressionStreamCreators()
23 {
24 if (ZipEngine.compressionStreamCreators == null)
25 {
26 ZipEngine.compressionStreamCreators = new
27 Dictionary<ZipCompressionMethod, Converter<Stream, Stream>>();
28 ZipEngine.decompressionStreamCreators = new
29 Dictionary<ZipCompressionMethod, Converter<Stream, Stream>>();
30
31 ZipEngine.RegisterCompressionStreamCreator(
32 ZipCompressionMethod.Store,
33 CompressionMode.Compress,
34 delegate(Stream stream) {
35 return stream;
36 });
37 ZipEngine.RegisterCompressionStreamCreator(
38 ZipCompressionMethod.Deflate,
39 CompressionMode.Compress,
40 delegate(Stream stream) {
41 return new DeflateStream(stream, CompressionMode.Compress, true);
42 });
43 ZipEngine.RegisterCompressionStreamCreator(
44 ZipCompressionMethod.Store,
45 CompressionMode.Decompress,
46 delegate(Stream stream) {
47 return stream;
48 });
49 ZipEngine.RegisterCompressionStreamCreator(
50 ZipCompressionMethod.Deflate,
51 CompressionMode.Decompress,
52 delegate(Stream stream) {
53 return new DeflateStream(stream, CompressionMode.Decompress, true);
54 });
55 }
56 }
57
58 /// <summary>
59 /// Registers a delegate that can create a warpper stream for
60 /// compressing or uncompressing the data of a source stream.
61 /// </summary>
62 /// <param name="compressionMethod">Compression method being registered.</param>
63 /// <param name="compressionMode">Indicates registration for ether
64 /// compress or decompress mode.</param>
65 /// <param name="creator">Delegate being registered.</param>
66 /// <remarks>
67 /// For compression, the delegate accepts a stream that writes to the archive
68 /// and returns a wrapper stream that compresses bytes as they are written.
69 /// For decompression, the delegate accepts a stream that reads from the archive
70 /// and returns a wrapper stream that decompresses bytes as they are read.
71 /// This wrapper stream model follows the design used by
72 /// System.IO.Compression.DeflateStream, and indeed that class is used
73 /// to implement the Deflate compression method by default.
74 /// <para>To unregister a delegate, call this method again and pass
75 /// null for the delegate parameter.</para>
76 /// </remarks>
77 /// <example>
78 /// When the ZipEngine class is initialized, the Deflate compression method
79 /// is automatically registered like this:
80 /// <code>
81 /// ZipEngine.RegisterCompressionStreamCreator(
82 /// ZipCompressionMethod.Deflate,
83 /// CompressionMode.Compress,
84 /// delegate(Stream stream) {
85 /// return new DeflateStream(stream, CompressionMode.Compress, true);
86 /// });
87 /// ZipEngine.RegisterCompressionStreamCreator(
88 /// ZipCompressionMethod.Deflate,
89 /// CompressionMode.Decompress,
90 /// delegate(Stream stream) {
91 /// return new DeflateStream(stream, CompressionMode.Decompress, true);
92 /// });
93 /// </code></example>
94 public static void RegisterCompressionStreamCreator(
95 ZipCompressionMethod compressionMethod,
96 CompressionMode compressionMode,
97 Converter<Stream, Stream> creator)
98 {
99 ZipEngine.InitCompressionStreamCreators();
100 if (compressionMode == CompressionMode.Compress)
101 {
102 ZipEngine.compressionStreamCreators[compressionMethod] = creator;
103 }
104 else
105 {
106 ZipEngine.decompressionStreamCreators[compressionMethod] = creator;
107 }
108 }
109
110 // Progress data
111 private string currentFileName;
112 private int currentFileNumber;
113 private int totalFiles;
114 private long currentFileBytesProcessed;
115 private long currentFileTotalBytes;
116 private string mainArchiveName;
117 private string currentArchiveName;
118 private short currentArchiveNumber;
119 private short totalArchives;
120 private long currentArchiveBytesProcessed;
121 private long currentArchiveTotalBytes;
122 private long fileBytesProcessed;
123 private long totalFileBytes;
124 private string comment;
125
126 /// <summary>
127 /// Creates a new instance of the zip engine.
128 /// </summary>
129 public ZipEngine()
130 : base()
131 {
132 ZipEngine.InitCompressionStreamCreators();
133 }
134
135 /// <summary>
136 /// Gets the comment from the last-examined archive,
137 /// or sets the comment to be added to any created archives.
138 /// </summary>
139 public string ArchiveComment
140 {
141 get
142 {
143 return this.comment;
144 }
145 set
146 {
147 this.comment = value;
148 }
149 }
150
151 /// <summary>
152 /// Checks whether a Stream begins with a header that indicates
153 /// it is a valid archive file.
154 /// </summary>
155 /// <param name="stream">Stream for reading the archive file.</param>
156 /// <returns>True if the stream is a valid zip archive
157 /// (with no offset); false otherwise.</returns>
158 public override bool IsArchive(Stream stream)
159 {
160 if (stream == null)
161 {
162 throw new ArgumentNullException("stream");
163 }
164
165 if (stream.Length - stream.Position < 4)
166 {
167 return false;
168 }
169
170 BinaryReader reader = new BinaryReader(stream);
171 uint sig = reader.ReadUInt32();
172 switch (sig)
173 {
174 case ZipFileHeader.LFHSIG:
175 case ZipEndOfCentralDirectory.EOCDSIG:
176 case ZipEndOfCentralDirectory.EOCD64SIG:
177 case ZipFileHeader.SPANSIG:
178 case ZipFileHeader.SPANSIG2:
179 return true;
180 default:
181 return false;
182 }
183 }
184
185 /// <summary>
186 /// Gets the offset of an archive that is positioned 0 or more bytes
187 /// from the start of the Stream.
188 /// </summary>
189 /// <param name="stream">A stream for reading the archive.</param>
190 /// <returns>The offset in bytes of the archive,
191 /// or -1 if no archive is found in the Stream.</returns>
192 /// <remarks>The archive must begin on a 4-byte boundary.</remarks>
193 public override long FindArchiveOffset(Stream stream)
194 {
195 long offset = base.FindArchiveOffset(stream);
196 if (offset > 0)
197 {
198 // Some self-extract packages include the exe stub in file offset calculations.
199 // Check the first header directory offset to decide whether the entire
200 // archive needs to be offset or not.
201
202 ZipEndOfCentralDirectory eocd = this.GetEOCD(null, stream);
203 if (eocd != null && eocd.totalEntries > 0)
204 {
205 stream.Seek(eocd.dirOffset, SeekOrigin.Begin);
206
207 ZipFileHeader header = new ZipFileHeader();
208 if (header.Read(stream, true) && header.localHeaderOffset < stream.Length)
209 {
210 stream.Seek(header.localHeaderOffset, SeekOrigin.Begin);
211 if (header.Read(stream, false))
212 {
213 return 0;
214 }
215 }
216 }
217 }
218
219 return offset;
220 }
221
222 /// <summary>
223 /// Gets information about files in a zip archive or archive chain.
224 /// </summary>
225 /// <param name="streamContext">A context interface to handle opening
226 /// and closing of archive and file streams.</param>
227 /// <param name="fileFilter">A predicate that can determine
228 /// which files to process, optional.</param>
229 /// <returns>Information about files in the archive stream.</returns>
230 /// <exception cref="ArchiveException">The archive provided
231 /// by the stream context is not valid.</exception>
232 /// <remarks>
233 /// The <paramref name="fileFilter"/> predicate takes an internal file
234 /// path and returns true to include the file or false to exclude it.
235 /// </remarks>
236 public override IList<ArchiveFileInfo> GetFileInfo(
237 IUnpackStreamContext streamContext,
238 Predicate<string> fileFilter)
239 {
240 if (streamContext == null)
241 {
242 throw new ArgumentNullException("streamContext");
243 }
244
245 lock (this)
246 {
247 IList<ZipFileHeader> headers = this.GetCentralDirectory(streamContext);
248 if (headers == null)
249 {
250 throw new ZipException("Zip central directory not found.");
251 }
252
253 List<ArchiveFileInfo> files = new List<ArchiveFileInfo>(headers.Count);
254 foreach (ZipFileHeader header in headers)
255 {
256 if (!header.IsDirectory &&
257 (fileFilter == null || fileFilter(header.fileName)))
258 {
259 files.Add(header.ToZipFileInfo());
260 }
261 }
262
263 return files.AsReadOnly();
264 }
265 }
266
267 /// <summary>
268 /// Reads all the file headers from the central directory in the main archive.
269 /// </summary>
270 private IList<ZipFileHeader> GetCentralDirectory(IUnpackStreamContext streamContext)
271 {
272 Stream archiveStream = null;
273 this.currentArchiveNumber = 0;
274 try
275 {
276 List<ZipFileHeader> headers = new List<ZipFileHeader>();
277 archiveStream = this.OpenArchive(streamContext, 0);
278
279 ZipEndOfCentralDirectory eocd = this.GetEOCD(streamContext, archiveStream);
280 if (eocd == null)
281 {
282 return null;
283 }
284 else if (eocd.totalEntries == 0)
285 {
286 return headers;
287 }
288
289 headers.Capacity = (int) eocd.totalEntries;
290
291 if (eocd.dirOffset > archiveStream.Length - ZipFileHeader.CFH_FIXEDSIZE)
292 {
293 streamContext.CloseArchiveReadStream(
294 this.currentArchiveNumber, String.Empty, archiveStream);
295 archiveStream = null;
296 }
297 else
298 {
299 archiveStream.Seek(eocd.dirOffset, SeekOrigin.Begin);
300 uint sig = new BinaryReader(archiveStream).ReadUInt32();
301 if (sig != ZipFileHeader.CFHSIG)
302 {
303 streamContext.CloseArchiveReadStream(
304 this.currentArchiveNumber, String.Empty, archiveStream);
305 archiveStream = null;
306 }
307 }
308
309 if (archiveStream == null)
310 {
311 this.currentArchiveNumber = (short) (eocd.dirStartDiskNumber + 1);
312 archiveStream = streamContext.OpenArchiveReadStream(
313 this.currentArchiveNumber, String.Empty, this);
314
315 if (archiveStream == null)
316 {
317 return null;
318 }
319 }
320
321 archiveStream.Seek(eocd.dirOffset, SeekOrigin.Begin);
322
323 while (headers.Count < eocd.totalEntries)
324 {
325 ZipFileHeader header = new ZipFileHeader();
326 if (!header.Read(archiveStream, true))
327 {
328 throw new ZipException(
329 "Missing or invalid central directory file header");
330 }
331
332 headers.Add(header);
333
334 if (headers.Count < eocd.totalEntries &&
335 archiveStream.Position == archiveStream.Length)
336 {
337 streamContext.CloseArchiveReadStream(
338 this.currentArchiveNumber, String.Empty, archiveStream);
339 this.currentArchiveNumber++;
340 archiveStream = streamContext.OpenArchiveReadStream(
341 this.currentArchiveNumber, String.Empty, this);
342 if (archiveStream == null)
343 {
344 this.currentArchiveNumber = 0;
345 archiveStream = streamContext.OpenArchiveReadStream(
346 this.currentArchiveNumber, String.Empty, this);
347 }
348 }
349 }
350
351 return headers;
352 }
353 finally
354 {
355 if (archiveStream != null)
356 {
357 streamContext.CloseArchiveReadStream(
358 this.currentArchiveNumber, String.Empty, archiveStream);
359 }
360 }
361 }
362
363 /// <summary>
364 /// Locates and reads the end of central directory record near the
365 /// end of the archive.
366 /// </summary>
367 [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
368 [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "streamContext")]
369 private ZipEndOfCentralDirectory GetEOCD(
370 IUnpackStreamContext streamContext, Stream archiveStream)
371 {
372 BinaryReader reader = new BinaryReader(archiveStream);
373 long offset = archiveStream.Length
374 - ZipEndOfCentralDirectory.EOCD_RECORD_FIXEDSIZE;
375 while (offset >= 0)
376 {
377 archiveStream.Seek(offset, SeekOrigin.Begin);
378
379 uint sig = reader.ReadUInt32();
380 if (sig == ZipEndOfCentralDirectory.EOCDSIG)
381 {
382 break;
383 }
384
385 offset--;
386 }
387
388 if (offset < 0)
389 {
390 return null;
391 }
392
393 ZipEndOfCentralDirectory eocd = new ZipEndOfCentralDirectory();
394 archiveStream.Seek(offset, SeekOrigin.Begin);
395 if (!eocd.Read(archiveStream))
396 {
397 throw new ZipException("Invalid end of central directory record");
398 }
399
400 if (eocd.dirOffset == (long) UInt32.MaxValue)
401 {
402 string saveComment = eocd.comment;
403
404 archiveStream.Seek(
405 offset - Zip64EndOfCentralDirectoryLocator.EOCDL64_SIZE,
406 SeekOrigin.Begin);
407
408 Zip64EndOfCentralDirectoryLocator eocdl =
409 new Zip64EndOfCentralDirectoryLocator();
410 if (!eocdl.Read(archiveStream))
411 {
412 throw new ZipException("Missing or invalid end of " +
413 "central directory record locator");
414 }
415
416 if (eocdl.dirStartDiskNumber == eocdl.totalDisks - 1)
417 {
418 // ZIP64 eocd is entirely in current stream.
419 archiveStream.Seek(eocdl.dirOffset, SeekOrigin.Begin);
420 if (!eocd.Read(archiveStream))
421 {
422 throw new ZipException("Missing or invalid ZIP64 end of " +
423 "central directory record");
424 }
425 }
426 else if (streamContext == null)
427 {
428 return null;
429 }
430 else
431 {
432 // TODO: handle EOCD64 spanning archives!
433 throw new NotImplementedException("Zip implementation does not " +
434 "handle end of central directory record that spans archives.");
435 }
436
437 eocd.comment = saveComment;
438 }
439
440 return eocd;
441 }
442
443 private void ResetProgressData()
444 {
445 this.currentFileName = null;
446 this.currentFileNumber = 0;
447 this.totalFiles = 0;
448 this.currentFileBytesProcessed = 0;
449 this.currentFileTotalBytes = 0;
450 this.currentArchiveName = null;
451 this.currentArchiveNumber = 0;
452 this.totalArchives = 0;
453 this.currentArchiveBytesProcessed = 0;
454 this.currentArchiveTotalBytes = 0;
455 this.fileBytesProcessed = 0;
456 this.totalFileBytes = 0;
457 }
458
459 private void OnProgress(ArchiveProgressType progressType)
460 {
461 ArchiveProgressEventArgs e = new ArchiveProgressEventArgs(
462 progressType,
463 this.currentFileName,
464 this.currentFileNumber >= 0 ? this.currentFileNumber : 0,
465 this.totalFiles,
466 this.currentFileBytesProcessed,
467 this.currentFileTotalBytes,
468 this.currentArchiveName,
469 this.currentArchiveNumber,
470 this.totalArchives,
471 this.currentArchiveBytesProcessed,
472 this.currentArchiveTotalBytes,
473 this.fileBytesProcessed,
474 this.totalFileBytes);
475 this.OnProgress(e);
476 }
477 }
478}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ZipException.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipException.cs
new file mode 100644
index 00000000..50fd6156
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipException.cs
@@ -0,0 +1,60 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Zip
4{
5 using System;
6 using System.IO;
7 using System.Resources;
8 using System.Globalization;
9 using System.Runtime.Serialization;
10
11 /// <summary>
12 /// Exception class for zip operations.
13 /// </summary>
14 [Serializable]
15 public class ZipException : ArchiveException
16 {
17 /// <summary>
18 /// Creates a new ZipException with a specified error message and a reference to the
19 /// inner exception that is the cause of this exception.
20 /// </summary>
21 /// <param name="message">The message that describes the error.</param>
22 /// <param name="innerException">The exception that is the cause of the current exception. If the
23 /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
24 /// is raised in a catch block that handles the inner exception.</param>
25 public ZipException(string message, Exception innerException)
26 : base(message, innerException) { }
27
28 /// <summary>
29 /// Creates a new ZipException with a specified error message.
30 /// </summary>
31 /// <param name="message">The message that describes the error.</param>
32 public ZipException(string message)
33 : this(message, null) { }
34
35 /// <summary>
36 /// Creates a new ZipException.
37 /// </summary>
38 public ZipException()
39 : this(null, null) { }
40
41 /// <summary>
42 /// Initializes a new instance of the ZipException class with serialized data.
43 /// </summary>
44 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
45 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
46 protected ZipException(SerializationInfo info, StreamingContext context) : base(info, context)
47 {
48 }
49
50 /// <summary>
51 /// Sets the SerializationInfo with information about the exception.
52 /// </summary>
53 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
54 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
55 public override void GetObjectData(SerializationInfo info, StreamingContext context)
56 {
57 base.GetObjectData(info, context);
58 }
59 }
60}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ZipFileInfo.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipFileInfo.cs
new file mode 100644
index 00000000..d865bbba
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipFileInfo.cs
@@ -0,0 +1,104 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Zip
4{
5 using System;
6 using System.IO;
7 using System.Runtime.Serialization;
8
9 /// <summary>
10 /// Object representing a compressed file within a zip package; provides operations for getting
11 /// the file properties and extracting the file.
12 /// </summary>
13 [Serializable]
14 public class ZipFileInfo : ArchiveFileInfo
15 {
16 private long compressedLength;
17 private ZipCompressionMethod compressionMethod;
18
19 /// <summary>
20 /// Creates a new ZipFileInfo object representing a file within a zip in a specified path.
21 /// </summary>
22 /// <param name="zipInfo">An object representing the zip archive containing the file.</param>
23 /// <param name="filePath">The path to the file within the zip archive. Usually, this is a simple file
24 /// name, but if the zip archive contains a directory structure this may include the directory.</param>
25 public ZipFileInfo(ZipInfo zipInfo, string filePath)
26 : base(zipInfo, filePath)
27 {
28 if (zipInfo == null)
29 {
30 throw new ArgumentNullException("zipInfo");
31 }
32 }
33
34 /// <summary>
35 /// Creates a new ZipFileInfo object with all parameters specified,
36 /// used internally when reading the metadata out of a zip archive.
37 /// </summary>
38 /// <param name="filePath">The internal path and name of the file in the zip archive.</param>
39 /// <param name="zipNumber">The zip archive number where the file starts.</param>
40 /// <param name="attributes">The stored attributes of the file.</param>
41 /// <param name="lastWriteTime">The stored last write time of the file.</param>
42 /// <param name="length">The uncompressed size of the file.</param>
43 /// <param name="compressedLength">The compressed size of the file.</param>
44 /// <param name="compressionMethod">Compression algorithm used for this file.</param>
45 internal ZipFileInfo(
46 string filePath,
47 int zipNumber,
48 FileAttributes attributes,
49 DateTime lastWriteTime,
50 long length,
51 long compressedLength,
52 ZipCompressionMethod compressionMethod)
53 : base(filePath, zipNumber, attributes, lastWriteTime, length)
54 {
55 this.compressedLength = compressedLength;
56 this.compressionMethod = compressionMethod;
57 }
58
59 /// <summary>
60 /// Initializes a new instance of the ZipFileInfo class with serialized data.
61 /// </summary>
62 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
63 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
64 protected ZipFileInfo(SerializationInfo info, StreamingContext context)
65 : base(info, context)
66 {
67 this.compressedLength = info.GetInt64("compressedLength");
68 }
69
70 /// <summary>
71 /// Gets the compressed size of the file in bytes.
72 /// </summary>
73 public long CompressedLength
74 {
75 get
76 {
77 return this.compressedLength;
78 }
79 }
80
81 /// <summary>
82 /// Gets the method used to compress this file.
83 /// </summary>
84 public ZipCompressionMethod CompressionMethod
85 {
86 get
87 {
88 return this.compressionMethod;
89 }
90 }
91
92 /// <summary>
93 /// Sets the SerializationInfo with information about the archive.
94 /// </summary>
95 /// <param name="info">The SerializationInfo that holds the serialized object data.</param>
96 /// <param name="context">The StreamingContext that contains contextual information
97 /// about the source or destination.</param>
98 public override void GetObjectData(SerializationInfo info, StreamingContext context)
99 {
100 base.GetObjectData(info, context);
101 info.AddValue("compressedLength", this.compressedLength);
102 }
103 }
104}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ZipFormat.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipFormat.cs
new file mode 100644
index 00000000..dc5e1137
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipFormat.cs
@@ -0,0 +1,697 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Zip
4{
5 using System;
6 using System.Globalization;
7 using System.IO;
8 using System.Text;
9 using System.Collections.Generic;
10 using System.Diagnostics.CodeAnalysis;
11
12 [Flags]
13 internal enum ZipFileFlags : ushort
14 {
15 None = 0x0000,
16 Encrypt = 0x0001,
17 CompressOption1 = 0x0002,
18 CompressOption2 = 0x0004,
19 DataDescriptor = 0x0008,
20 StrongEncrypt = 0x0040,
21 UTF8 = 0x0800
22 }
23
24 internal enum ZipExtraFileFieldType : ushort
25 {
26 ZIP64 = 0x0001,
27 NTFS_TIMES = 0x000A,
28 NTFS_ACLS = 0x4453,
29 EXTIME = 0x5455
30 }
31
32 internal class ZipFileHeader
33 {
34 public const uint LFHSIG = 0x04034B50;
35 public const uint CFHSIG = 0x02014B50;
36
37 public const uint SPANSIG = 0x08074b50;
38 public const uint SPANSIG2 = 0x30304b50;
39
40 public const uint LFH_FIXEDSIZE = 30;
41 public const uint CFH_FIXEDSIZE = 46;
42
43 public ushort versionMadeBy;
44 public ushort versionNeeded;
45 public ZipFileFlags flags;
46 public ZipCompressionMethod compressionMethod;
47 public short lastModTime;
48 public short lastModDate;
49 public uint crc32;
50 public uint compressedSize;
51 public uint uncompressedSize;
52 public ushort diskStart;
53 public ushort internalFileAttrs;
54 public uint externalFileAttrs;
55 public uint localHeaderOffset;
56 public string fileName;
57 public ZipExtraFileField[] extraFields;
58 public string fileComment;
59 public bool zip64;
60
61 public ZipFileHeader()
62 {
63 this.versionMadeBy = 20;
64 this.versionNeeded = 20;
65 }
66
67 public ZipFileHeader(ZipFileInfo fileInfo, bool zip64)
68 : this()
69 {
70 this.flags = ZipFileFlags.None;
71 this.compressionMethod = fileInfo.CompressionMethod;
72 this.fileName = Path.Combine(fileInfo.Path, fileInfo.Name);
73 CompressionEngine.DateTimeToDosDateAndTime(
74 fileInfo.LastWriteTime, out this.lastModDate, out this.lastModTime);
75 this.zip64 = zip64;
76
77 if (this.zip64)
78 {
79 this.compressedSize = UInt32.MaxValue;
80 this.uncompressedSize = UInt32.MaxValue;
81 this.diskStart = UInt16.MaxValue;
82 this.versionMadeBy = 45;
83 this.versionNeeded = 45;
84 ZipExtraFileField field = new ZipExtraFileField();
85 field.fieldType = ZipExtraFileFieldType.ZIP64;
86 field.SetZip64Data(
87 fileInfo.CompressedLength,
88 fileInfo.Length,
89 0,
90 fileInfo.ArchiveNumber);
91 this.extraFields = new ZipExtraFileField[] { field };
92 }
93 else
94 {
95 this.compressedSize = (uint) fileInfo.CompressedLength;
96 this.uncompressedSize = (uint) fileInfo.Length;
97 this.diskStart = (ushort) fileInfo.ArchiveNumber;
98 }
99 }
100
101 [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "compressedSize")]
102 [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "uncompressedSize")]
103 [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "crc32")]
104 [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "localHeaderOffset")]
105 public void Update(
106 long compressedSize,
107 long uncompressedSize,
108 uint crc32,
109 long localHeaderOffset,
110 int archiveNumber)
111 {
112 this.crc32 = crc32;
113
114 if (this.zip64)
115 {
116 this.compressedSize = UInt32.MaxValue;
117 this.uncompressedSize = UInt32.MaxValue;
118 this.localHeaderOffset = UInt32.MaxValue;
119 this.diskStart = UInt16.MaxValue;
120
121 if (this.extraFields != null)
122 {
123 foreach (ZipExtraFileField field in this.extraFields)
124 {
125 if (field.fieldType == ZipExtraFileFieldType.ZIP64)
126 {
127 field.SetZip64Data(
128 compressedSize,
129 uncompressedSize,
130 localHeaderOffset,
131 archiveNumber);
132 }
133 }
134 }
135 }
136 else
137 {
138 this.compressedSize = (uint) compressedSize;
139 this.uncompressedSize = (uint) uncompressedSize;
140 this.localHeaderOffset = (uint) localHeaderOffset;
141 this.diskStart = (ushort) archiveNumber;
142 }
143 }
144
145 public bool Read(Stream stream, bool central)
146 {
147 long startPos = stream.Position;
148
149 if (stream.Length - startPos <
150 (central ? CFH_FIXEDSIZE : LFH_FIXEDSIZE))
151 {
152 return false;
153 }
154
155 BinaryReader reader = new BinaryReader(stream);
156 uint sig = reader.ReadUInt32();
157
158 if (sig == SPANSIG || sig == SPANSIG2)
159 {
160 // Spanned zip files may optionally begin with a special marker.
161 // Just ignore it and move on.
162 sig = reader.ReadUInt32();
163 }
164
165 if (sig != (central ? CFHSIG : LFHSIG))
166 {
167 return false;
168 }
169
170 this.versionMadeBy = (central ? reader.ReadUInt16() : (ushort) 0);
171 this.versionNeeded = reader.ReadUInt16();
172 this.flags = (ZipFileFlags) reader.ReadUInt16();
173 this.compressionMethod = (ZipCompressionMethod) reader.ReadUInt16();
174 this.lastModTime = reader.ReadInt16();
175 this.lastModDate = reader.ReadInt16();
176 this.crc32 = reader.ReadUInt32();
177 this.compressedSize = reader.ReadUInt32();
178 this.uncompressedSize = reader.ReadUInt32();
179
180 this.zip64 = this.uncompressedSize == UInt32.MaxValue;
181
182 int fileNameLength = reader.ReadUInt16();
183 int extraFieldLength = reader.ReadUInt16();
184 int fileCommentLength;
185
186 if (central)
187 {
188 fileCommentLength = reader.ReadUInt16();
189
190 this.diskStart = reader.ReadUInt16();
191 this.internalFileAttrs = reader.ReadUInt16();
192 this.externalFileAttrs = reader.ReadUInt32();
193 this.localHeaderOffset = reader.ReadUInt32();
194 }
195 else
196 {
197 fileCommentLength = 0;
198 this.diskStart = 0;
199 this.internalFileAttrs = 0;
200 this.externalFileAttrs = 0;
201 this.localHeaderOffset = 0;
202 }
203
204 if (stream.Length - stream.Position <
205 fileNameLength + extraFieldLength + fileCommentLength)
206 {
207 return false;
208 }
209
210 Encoding headerEncoding = ((this.flags | ZipFileFlags.UTF8) != 0 ?
211 Encoding.UTF8 : Encoding.GetEncoding(CultureInfo.CurrentCulture.TextInfo.OEMCodePage));
212
213 byte[] fileNameBytes = reader.ReadBytes(fileNameLength);
214 this.fileName = headerEncoding.GetString(fileNameBytes).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
215
216 List<ZipExtraFileField> fields = new List<ZipExtraFileField>();
217 while (extraFieldLength > 0)
218 {
219 ZipExtraFileField field = new ZipExtraFileField();
220 if (!field.Read(stream, ref extraFieldLength))
221 {
222 return false;
223 }
224 fields.Add(field);
225 if (field.fieldType == ZipExtraFileFieldType.ZIP64)
226 {
227 this.zip64 = true;
228 }
229 }
230 this.extraFields = fields.ToArray();
231
232 byte[] fileCommentBytes = reader.ReadBytes(fileCommentLength);
233 this.fileComment = headerEncoding.GetString(fileCommentBytes);
234
235 return true;
236 }
237
238 public void Write(Stream stream, bool central)
239 {
240 byte[] fileNameBytes = (this.fileName != null
241 ? Encoding.UTF8.GetBytes(this.fileName) : new byte[0]);
242 byte[] fileCommentBytes = (this.fileComment != null
243 ? Encoding.UTF8.GetBytes(this.fileComment) : new byte[0]);
244 bool useUtf8 =
245 (this.fileName != null && fileNameBytes.Length > this.fileName.Length) ||
246 (this.fileComment != null && fileCommentBytes.Length > this.fileComment.Length);
247 if (useUtf8)
248 {
249 this.flags |= ZipFileFlags.UTF8;
250 }
251
252 BinaryWriter writer = new BinaryWriter(stream);
253 writer.Write(central ? CFHSIG : LFHSIG);
254 if (central)
255 {
256 writer.Write(this.versionMadeBy);
257 }
258 writer.Write(this.versionNeeded);
259 writer.Write((ushort) this.flags);
260 writer.Write((ushort) this.compressionMethod);
261 writer.Write(this.lastModTime);
262 writer.Write(this.lastModDate);
263 writer.Write(this.crc32);
264 writer.Write(this.compressedSize);
265 writer.Write(this.uncompressedSize);
266
267 ushort extraFieldLength = 0;
268 if (this.extraFields != null)
269 {
270 foreach (ZipExtraFileField field in this.extraFields)
271 {
272 if (field.data != null)
273 {
274 extraFieldLength += (ushort) (4 + field.data.Length);
275 }
276 }
277 }
278
279 writer.Write((ushort) fileNameBytes.Length);
280 writer.Write(extraFieldLength);
281
282 if (central)
283 {
284 writer.Write((ushort) fileCommentBytes.Length);
285
286 writer.Write(this.diskStart);
287 writer.Write(this.internalFileAttrs);
288 writer.Write(this.externalFileAttrs);
289 writer.Write(this.localHeaderOffset);
290 }
291
292 writer.Write(fileNameBytes);
293
294 if (this.extraFields != null)
295 {
296 foreach (ZipExtraFileField field in this.extraFields)
297 {
298 if (field.data != null)
299 {
300 field.Write(stream);
301 }
302 }
303 }
304
305 if (central)
306 {
307 writer.Write(fileCommentBytes);
308 }
309 }
310
311 [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "compressedSize")]
312 [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "uncompressedSize")]
313 [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "crc32")]
314 [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "localHeaderOffset")]
315 public void GetZip64Fields(
316 out long compressedSize,
317 out long uncompressedSize,
318 out long localHeaderOffset,
319 out int archiveNumber,
320 out uint crc)
321 {
322 compressedSize = this.compressedSize;
323 uncompressedSize = this.uncompressedSize;
324 localHeaderOffset = this.localHeaderOffset;
325 archiveNumber = this.diskStart;
326 crc = this.crc32;
327
328 foreach (ZipExtraFileField field in this.extraFields)
329 {
330 if (field.fieldType == ZipExtraFileFieldType.ZIP64)
331 {
332 field.GetZip64Data(
333 out compressedSize,
334 out uncompressedSize,
335 out localHeaderOffset,
336 out archiveNumber);
337 }
338 }
339 }
340
341 public ZipFileInfo ToZipFileInfo()
342 {
343 string name = this.fileName;
344
345 long compressedSizeL;
346 long uncompressedSizeL;
347 long localHeaderOffsetL;
348 int archiveNumberL;
349 uint crc;
350 this.GetZip64Fields(
351 out compressedSizeL,
352 out uncompressedSizeL,
353 out localHeaderOffsetL,
354 out archiveNumberL,
355 out crc);
356
357 DateTime dateTime;
358 CompressionEngine.DosDateAndTimeToDateTime(
359 this.lastModDate,
360 this.lastModTime,
361 out dateTime);
362 FileAttributes attrs = FileAttributes.Normal;
363 // TODO: look for attrs or times in extra fields
364
365 return new ZipFileInfo(name, archiveNumberL, attrs, dateTime,
366 uncompressedSizeL, compressedSizeL, this.compressionMethod);
367 }
368
369 public bool IsDirectory
370 {
371 get
372 {
373 return this.fileName != null &&
374 (this.fileName.EndsWith("/", StringComparison.Ordinal) ||
375 this.fileName.EndsWith("\\", StringComparison.Ordinal));
376 }
377 }
378
379 public int GetSize(bool central)
380 {
381 int size = 30;
382
383 int fileNameSize = (this.fileName != null
384 ? Encoding.UTF8.GetByteCount(this.fileName) : 0);
385 size += fileNameSize;
386
387 if (this.extraFields != null)
388 {
389 foreach (ZipExtraFileField field in this.extraFields)
390 {
391 if (field.data != null)
392 {
393 size += 4 + field.data.Length;
394 }
395 }
396 }
397
398 if (central)
399 {
400 size += 16;
401
402 int fileCommentSize = (this.fileComment != null
403 ? Encoding.UTF8.GetByteCount(this.fileComment) : 0);
404 size += fileCommentSize;
405 }
406
407 return size;
408 }
409 }
410
411 internal class ZipExtraFileField
412 {
413 public ZipExtraFileFieldType fieldType;
414 public byte[] data;
415
416 public bool Read(Stream stream, ref int bytesRemaining)
417 {
418 if (bytesRemaining < 4)
419 {
420 return false;
421 }
422
423 BinaryReader reader = new BinaryReader(stream);
424
425 this.fieldType = (ZipExtraFileFieldType) reader.ReadUInt16();
426 ushort dataSize = reader.ReadUInt16();
427 bytesRemaining -= 4;
428
429 if (bytesRemaining < dataSize)
430 {
431 return false;
432 }
433
434 this.data = reader.ReadBytes(dataSize);
435 bytesRemaining -= dataSize;
436
437 return true;
438 }
439
440 public void Write(Stream stream)
441 {
442 BinaryWriter writer = new BinaryWriter(stream);
443 writer.Write((ushort) this.fieldType);
444
445 byte[] dataBytes = (this.data != null ? this.data : new byte[0]);
446 writer.Write((ushort) dataBytes.Length);
447 writer.Write(dataBytes);
448 }
449
450 public bool GetZip64Data(
451 out long compressedSize,
452 out long uncompressedSize,
453 out long localHeaderOffset,
454 out int diskStart)
455 {
456 uncompressedSize = 0;
457 compressedSize = 0;
458 localHeaderOffset = 0;
459 diskStart = 0;
460
461 if (this.fieldType != ZipExtraFileFieldType.ZIP64 ||
462 this.data == null || this.data.Length != 28)
463 {
464 return false;
465 }
466
467 using (MemoryStream dataStream = new MemoryStream(this.data))
468 {
469 BinaryReader reader = new BinaryReader(dataStream);
470 uncompressedSize = reader.ReadInt64();
471 compressedSize = reader.ReadInt64();
472 localHeaderOffset = reader.ReadInt64();
473 diskStart = reader.ReadInt32();
474 }
475
476 return true;
477 }
478
479 public bool SetZip64Data(
480 long compressedSize,
481 long uncompressedSize,
482 long localHeaderOffset,
483 int diskStart)
484 {
485 if (this.fieldType != ZipExtraFileFieldType.ZIP64)
486 {
487 return false;
488 }
489
490 using (MemoryStream dataStream = new MemoryStream())
491 {
492 BinaryWriter writer = new BinaryWriter(dataStream);
493 writer.Write(uncompressedSize);
494 writer.Write(compressedSize);
495 writer.Write(localHeaderOffset);
496 writer.Write(diskStart);
497 this.data = dataStream.ToArray();
498 }
499
500 return true;
501 }
502 }
503
504 internal class ZipEndOfCentralDirectory
505 {
506 public const uint EOCDSIG = 0x06054B50;
507 public const uint EOCD64SIG = 0x06064B50;
508
509 public const uint EOCD_RECORD_FIXEDSIZE = 22;
510 public const uint EOCD64_RECORD_FIXEDSIZE = 56;
511
512 public ushort versionMadeBy;
513 public ushort versionNeeded;
514 public uint diskNumber;
515 public uint dirStartDiskNumber;
516 public long entriesOnDisk;
517 public long totalEntries;
518 public long dirSize;
519 public long dirOffset;
520 public string comment;
521 public bool zip64;
522
523 public ZipEndOfCentralDirectory()
524 {
525 this.versionMadeBy = 20;
526 this.versionNeeded = 20;
527 }
528
529 public bool Read(Stream stream)
530 {
531 long startPos = stream.Position;
532
533 if (stream.Length - startPos < EOCD_RECORD_FIXEDSIZE)
534 {
535 return false;
536 }
537
538 BinaryReader reader = new BinaryReader(stream);
539 uint sig = reader.ReadUInt32();
540
541 this.zip64 = false;
542 if (sig != EOCDSIG)
543 {
544 if (sig == EOCD64SIG)
545 {
546 this.zip64 = true;
547 }
548 else
549 {
550 return false;
551 }
552 }
553
554 if (this.zip64)
555 {
556 if (stream.Length - startPos < EOCD64_RECORD_FIXEDSIZE)
557 {
558 return false;
559 }
560
561 long recordSize = reader.ReadInt64();
562 this.versionMadeBy = reader.ReadUInt16();
563 this.versionNeeded = reader.ReadUInt16();
564 this.diskNumber = reader.ReadUInt32();
565 this.dirStartDiskNumber = reader.ReadUInt32();
566 this.entriesOnDisk = reader.ReadInt64();
567 this.totalEntries = reader.ReadInt64();
568 this.dirSize = reader.ReadInt64();
569 this.dirOffset = reader.ReadInt64();
570
571 // Ignore any extended zip64 eocd data.
572 long exDataSize = recordSize + 12 - EOCD64_RECORD_FIXEDSIZE;
573
574 if (stream.Length - stream.Position < exDataSize)
575 {
576 return false;
577 }
578
579 stream.Seek(exDataSize, SeekOrigin.Current);
580
581 this.comment = null;
582 }
583 else
584 {
585 this.diskNumber = reader.ReadUInt16();
586 this.dirStartDiskNumber = reader.ReadUInt16();
587 this.entriesOnDisk = reader.ReadUInt16();
588 this.totalEntries = reader.ReadUInt16();
589 this.dirSize = reader.ReadUInt32();
590 this.dirOffset = reader.ReadUInt32();
591
592 int commentLength = reader.ReadUInt16();
593
594 if (stream.Length - stream.Position < commentLength)
595 {
596 return false;
597 }
598
599 byte[] commentBytes = reader.ReadBytes(commentLength);
600 this.comment = Encoding.UTF8.GetString(commentBytes);
601 }
602
603 return true;
604 }
605
606 public void Write(Stream stream)
607 {
608 BinaryWriter writer = new BinaryWriter(stream);
609
610 if (this.zip64)
611 {
612 writer.Write(EOCD64SIG);
613 writer.Write((long) EOCD64_RECORD_FIXEDSIZE);
614 writer.Write(this.versionMadeBy);
615 writer.Write(this.versionNeeded);
616 writer.Write(this.diskNumber);
617 writer.Write(this.dirStartDiskNumber);
618 writer.Write(this.entriesOnDisk);
619 writer.Write(this.totalEntries);
620 writer.Write(this.dirSize);
621 writer.Write(this.dirOffset);
622 }
623 else
624 {
625 writer.Write(EOCDSIG);
626 writer.Write((ushort) Math.Min((int) UInt16.MaxValue, this.diskNumber));
627 writer.Write((ushort) Math.Min((int) UInt16.MaxValue, this.dirStartDiskNumber));
628 writer.Write((ushort) Math.Min((int) UInt16.MaxValue, this.entriesOnDisk));
629 writer.Write((ushort) Math.Min((int) UInt16.MaxValue, this.totalEntries));
630 writer.Write((uint) Math.Min((long) UInt32.MaxValue, this.dirSize));
631 writer.Write((uint) Math.Min((long) UInt32.MaxValue, this.dirOffset));
632
633 byte[] commentBytes = (this.comment != null
634 ? Encoding.UTF8.GetBytes(this.comment) : new byte[0]);
635 writer.Write((ushort) commentBytes.Length);
636 writer.Write(commentBytes);
637 }
638 }
639
640 public int GetSize(bool zip64Size)
641 {
642 if (zip64Size)
643 {
644 return 56;
645 }
646 else
647 {
648 int commentSize = (this.comment != null
649 ? Encoding.UTF8.GetByteCount(this.comment) : 0);
650 return 22 + commentSize;
651 }
652 }
653 }
654
655 internal class Zip64EndOfCentralDirectoryLocator
656 {
657 public const uint EOCDL64SIG = 0x07064B50;
658
659 public const uint EOCDL64_SIZE = 20;
660
661 public uint dirStartDiskNumber;
662 public long dirOffset;
663 public uint totalDisks;
664
665 public bool Read(Stream stream)
666 {
667 long startPos = stream.Position;
668 if (stream.Length - startPos < EOCDL64_SIZE)
669 {
670 return false;
671 }
672
673 BinaryReader reader = new BinaryReader(stream);
674 uint sig = reader.ReadUInt32();
675
676 if (sig != EOCDL64SIG)
677 {
678 return false;
679 }
680
681 this.dirStartDiskNumber = reader.ReadUInt32();
682 this.dirOffset = reader.ReadInt64();
683 this.totalDisks = reader.ReadUInt32();
684
685 return true;
686 }
687
688 public void Write(Stream stream)
689 {
690 BinaryWriter writer = new BinaryWriter(stream);
691 writer.Write(EOCDL64SIG);
692 writer.Write(this.dirStartDiskNumber);
693 writer.Write(this.dirOffset);
694 writer.Write(this.totalDisks);
695 }
696 }
697}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ZipInfo.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipInfo.cs
new file mode 100644
index 00000000..73f65fa0
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipInfo.cs
@@ -0,0 +1,82 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Zip
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Runtime.Serialization;
8
9 /// <summary>
10 /// Object representing a zip file on disk; provides access to
11 /// file-based operations on the zip file.
12 /// </summary>
13 /// <remarks>
14 /// Generally, the methods on this class are much easier to use than the
15 /// stream-based interfaces provided by the <see cref="ZipEngine"/> class.
16 /// </remarks>
17 [Serializable]
18 public class ZipInfo : ArchiveInfo
19 {
20 /// <summary>
21 /// Creates a new CabinetInfo object representing a zip file in a specified path.
22 /// </summary>
23 /// <param name="path">The path to the zip file. When creating a zip file, this file does not
24 /// necessarily exist yet.</param>
25 public ZipInfo(string path)
26 : base(path)
27 {
28 }
29
30 /// <summary>
31 /// Initializes a new instance of the CabinetInfo class with serialized data.
32 /// </summary>
33 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
34 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
35 protected ZipInfo(SerializationInfo info, StreamingContext context)
36 : base(info, context)
37 {
38 }
39
40 /// <summary>
41 /// Creates a compression engine that does the low-level work for
42 /// this object.
43 /// </summary>
44 /// <returns>A new <see cref="ZipEngine"/> instance.</returns>
45 /// <remarks>
46 /// Each instance will be <see cref="CompressionEngine.Dispose()"/>d
47 /// immediately after use.
48 /// </remarks>
49 protected override CompressionEngine CreateCompressionEngine()
50 {
51 return new ZipEngine();
52 }
53
54 /// <summary>
55 /// Gets information about the files contained in the archive.
56 /// </summary>
57 /// <returns>A list of <see cref="ZipFileInfo"/> objects, each
58 /// containing information about a file in the archive.</returns>
59 public new IList<ZipFileInfo> GetFiles()
60 {
61 IList<ArchiveFileInfo> files = base.GetFiles();
62 List<ZipFileInfo> zipFiles = new List<ZipFileInfo>(files.Count);
63 foreach (ZipFileInfo zipFile in files) zipFiles.Add(zipFile);
64 return zipFiles.AsReadOnly();
65 }
66
67 /// <summary>
68 /// Gets information about the certain files contained in the archive file.
69 /// </summary>
70 /// <param name="searchPattern">The search string, such as
71 /// &quot;*.txt&quot;.</param>
72 /// <returns>A list of <see cref="ZipFileInfo"/> objects, each containing
73 /// information about a file in the archive.</returns>
74 public new IList<ZipFileInfo> GetFiles(string searchPattern)
75 {
76 IList<ArchiveFileInfo> files = base.GetFiles(searchPattern);
77 List<ZipFileInfo> zipFiles = new List<ZipFileInfo>(files.Count);
78 foreach (ZipFileInfo zipFile in files) zipFiles.Add(zipFile);
79 return zipFiles.AsReadOnly();
80 }
81 }
82}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ZipPacker.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipPacker.cs
new file mode 100644
index 00000000..b9c183d3
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipPacker.cs
@@ -0,0 +1,489 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Zip
4{
5 using System;
6 using System.IO;
7 using System.IO.Compression;
8 using System.Collections.Generic;
9 using System.Globalization;
10
11 public partial class ZipEngine
12 {
13 /// <summary>
14 /// Creates a zip archive or chain of zip archives.
15 /// </summary>
16 /// <param name="streamContext">A context interface to handle opening
17 /// and closing of archive and file streams.</param>
18 /// <param name="files">An array of file lists. Each list is
19 /// compressed into one stream in the archive.</param>
20 /// <param name="maxArchiveSize">The maximum number of bytes for one archive
21 /// before the contents are chained to the next archive, or zero for unlimited
22 /// archive size.</param>
23 /// <exception cref="ArchiveException">The archive could not be
24 /// created.</exception>
25 /// <remarks>
26 /// The stream context implementation may provide a mapping from the file
27 /// paths within the archive to the external file paths.
28 /// </remarks>
29 public override void Pack(
30 IPackStreamContext streamContext,
31 IEnumerable<string> files,
32 long maxArchiveSize)
33 {
34 if (streamContext == null)
35 {
36 throw new ArgumentNullException("streamContext");
37 }
38
39 if (files == null)
40 {
41 throw new ArgumentNullException("files");
42 }
43
44 lock (this)
45 {
46 Stream archiveStream = null;
47 try
48 {
49 this.ResetProgressData();
50 this.totalArchives = 1;
51
52 object forceZip64Value = streamContext.GetOption("forceZip64", null);
53 bool forceZip64 = Convert.ToBoolean(
54 forceZip64Value, CultureInfo.InvariantCulture);
55
56 // Count the total number of files and bytes to be compressed.
57 foreach (string file in files)
58 {
59 FileAttributes attributes;
60 DateTime lastWriteTime;
61 Stream fileStream = streamContext.OpenFileReadStream(
62 file,
63 out attributes,
64 out lastWriteTime);
65 if (fileStream != null)
66 {
67 this.totalFileBytes += fileStream.Length;
68 this.totalFiles++;
69 streamContext.CloseFileReadStream(file, fileStream);
70 }
71 }
72
73 List<ZipFileHeader> fileHeaders = new List<ZipFileHeader>();
74 this.currentFileNumber = -1;
75
76 if (this.currentArchiveName == null)
77 {
78 this.mainArchiveName = streamContext.GetArchiveName(0);
79 this.currentArchiveName = this.mainArchiveName;
80
81 if (String.IsNullOrEmpty(this.currentArchiveName))
82 {
83 throw new FileNotFoundException("No name provided for archive.");
84 }
85 }
86
87 this.OnProgress(ArchiveProgressType.StartArchive);
88
89 // Compress files one by one, saving header info for each.
90 foreach (string file in files)
91 {
92 ZipFileHeader fileHeader = this.PackOneFile(
93 streamContext,
94 file,
95 maxArchiveSize,
96 forceZip64,
97 ref archiveStream);
98
99 if (fileHeader != null)
100 {
101 fileHeaders.Add(fileHeader);
102 }
103
104 this.currentArchiveTotalBytes = (archiveStream != null ?
105 archiveStream.Position : 0);
106 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
107 }
108
109 bool zip64 = forceZip64 || this.totalFiles > UInt16.MaxValue;
110
111 // Write the central directory composed of all the file headers.
112 uint centralDirStartArchiveNumber = 0;
113 long centralDirStartPosition = 0;
114 long centralDirSize = 0;
115 for (int i = 0; i < fileHeaders.Count; i++)
116 {
117 ZipFileHeader fileHeader = fileHeaders[i];
118
119 int headerSize = fileHeader.GetSize(true);
120 centralDirSize += headerSize;
121
122 this.CheckArchiveWriteStream(
123 streamContext,
124 maxArchiveSize,
125 headerSize,
126 ref archiveStream);
127
128 if (i == 0)
129 {
130 centralDirStartArchiveNumber = (uint) this.currentArchiveNumber;
131 centralDirStartPosition = archiveStream.Position;
132 }
133
134 fileHeader.Write(archiveStream, true);
135 if (fileHeader.zip64)
136 {
137 zip64 = true;
138 }
139 }
140
141 this.currentArchiveTotalBytes =
142 (archiveStream != null ? archiveStream.Position : 0);
143 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
144
145 ZipEndOfCentralDirectory eocd = new ZipEndOfCentralDirectory();
146 eocd.dirStartDiskNumber = centralDirStartArchiveNumber;
147 eocd.entriesOnDisk = fileHeaders.Count;
148 eocd.totalEntries = fileHeaders.Count;
149 eocd.dirSize = centralDirSize;
150 eocd.dirOffset = centralDirStartPosition;
151 eocd.comment = this.comment;
152
153 Zip64EndOfCentralDirectoryLocator eocdl =
154 new Zip64EndOfCentralDirectoryLocator();
155
156 int maxFooterSize = eocd.GetSize(false);
157 if (archiveStream != null && (zip64 || archiveStream.Position >
158 ((long) UInt32.MaxValue) - eocd.GetSize(false)))
159 {
160 maxFooterSize += eocd.GetSize(true) + (int)
161 Zip64EndOfCentralDirectoryLocator.EOCDL64_SIZE;
162 zip64 = true;
163 }
164
165 this.CheckArchiveWriteStream(
166 streamContext,
167 maxArchiveSize,
168 maxFooterSize,
169 ref archiveStream);
170 eocd.diskNumber = (uint) this.currentArchiveNumber;
171
172 if (zip64)
173 {
174 eocd.versionMadeBy = 45;
175 eocd.versionNeeded = 45;
176 eocd.zip64 = true;
177 eocdl.dirOffset = archiveStream.Position;
178 eocdl.dirStartDiskNumber = (uint) this.currentArchiveNumber;
179 eocdl.totalDisks = (uint) this.currentArchiveNumber + 1;
180 eocd.Write(archiveStream);
181 eocdl.Write(archiveStream);
182
183 eocd.dirOffset = UInt32.MaxValue;
184 eocd.dirStartDiskNumber = UInt16.MaxValue;
185 }
186
187 eocd.zip64 = false;
188 eocd.Write(archiveStream);
189
190 this.currentArchiveTotalBytes = archiveStream.Position;
191 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
192 }
193 finally
194 {
195 if (archiveStream != null)
196 {
197 streamContext.CloseArchiveWriteStream(
198 this.currentArchiveNumber, this.mainArchiveName, archiveStream);
199 this.OnProgress(ArchiveProgressType.FinishArchive);
200 }
201 }
202 }
203 }
204
205 /// <summary>
206 /// Moves to the next archive in the sequence if necessary.
207 /// </summary>
208 private void CheckArchiveWriteStream(
209 IPackStreamContext streamContext,
210 long maxArchiveSize,
211 long requiredSize,
212 ref Stream archiveStream)
213 {
214 if (archiveStream != null &&
215 archiveStream.Length > 0 && maxArchiveSize > 0)
216 {
217 long sizeRemaining = maxArchiveSize - archiveStream.Length;
218 if (sizeRemaining < requiredSize)
219 {
220 string nextArchiveName = streamContext.GetArchiveName(
221 this.currentArchiveNumber + 1);
222
223 if (String.IsNullOrEmpty(nextArchiveName))
224 {
225 throw new FileNotFoundException("No name provided for archive #" +
226 this.currentArchiveNumber + 1);
227 }
228
229 this.currentArchiveTotalBytes = archiveStream.Position;
230 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
231
232 streamContext.CloseArchiveWriteStream(
233 this.currentArchiveNumber,
234 nextArchiveName,
235 archiveStream);
236 archiveStream = null;
237
238 this.OnProgress(ArchiveProgressType.FinishArchive);
239
240 this.currentArchiveNumber++;
241 this.totalArchives++;
242 this.currentArchiveBytesProcessed = 0;
243 this.currentArchiveTotalBytes = 0;
244 }
245 }
246
247 if (archiveStream == null)
248 {
249 if (this.currentArchiveNumber > 0)
250 {
251 this.OnProgress(ArchiveProgressType.StartArchive);
252 }
253
254 archiveStream = streamContext.OpenArchiveWriteStream(
255 this.currentArchiveNumber, this.mainArchiveName, true, this);
256
257 if (archiveStream == null)
258 {
259 throw new FileNotFoundException("Stream not provided for archive #" +
260 this.currentArchiveNumber);
261 }
262 }
263 }
264
265 /// <summary>
266 /// Adds one file to a zip archive in the process of being created.
267 /// </summary>
268 private ZipFileHeader PackOneFile(
269 IPackStreamContext streamContext,
270 string file,
271 long maxArchiveSize,
272 bool forceZip64,
273 ref Stream archiveStream)
274 {
275 Stream fileStream = null;
276 int headerArchiveNumber = 0;
277 try
278 {
279 // TODO: call GetOption to get compression method for the specific file
280 ZipCompressionMethod compressionMethod = ZipCompressionMethod.Deflate;
281 if (this.CompressionLevel == WixToolset.Dtf.Compression.CompressionLevel.None)
282 {
283 compressionMethod = ZipCompressionMethod.Store;
284 }
285
286 Converter<Stream, Stream> compressionStreamCreator;
287 if (!ZipEngine.compressionStreamCreators.TryGetValue(
288 compressionMethod, out compressionStreamCreator))
289 {
290 return null;
291 }
292
293 FileAttributes attributes;
294 DateTime lastWriteTime;
295 fileStream = streamContext.OpenFileReadStream(
296 file, out attributes, out lastWriteTime);
297 if (fileStream == null)
298 {
299 return null;
300 }
301
302 this.currentFileName = file;
303 this.currentFileNumber++;
304
305 this.currentFileTotalBytes = fileStream.Length;
306 this.currentFileBytesProcessed = 0;
307 this.OnProgress(ArchiveProgressType.StartFile);
308
309 ZipFileInfo fileInfo = new ZipFileInfo(
310 file,
311 this.currentArchiveNumber,
312 attributes,
313 lastWriteTime,
314 fileStream.Length,
315 0,
316 compressionMethod);
317
318 bool zip64 = forceZip64 || fileStream.Length >= (long) UInt32.MaxValue;
319 ZipFileHeader fileHeader = new ZipFileHeader(fileInfo, zip64);
320
321 this.CheckArchiveWriteStream(
322 streamContext,
323 maxArchiveSize,
324 fileHeader.GetSize(false),
325 ref archiveStream);
326
327 long headerPosition = archiveStream.Position;
328 fileHeader.Write(archiveStream, false);
329 headerArchiveNumber = this.currentArchiveNumber;
330
331 uint crc;
332 long bytesWritten = this.PackFileBytes(
333 streamContext,
334 fileStream,
335 maxArchiveSize,
336 compressionStreamCreator,
337 ref archiveStream,
338 out crc);
339
340 fileHeader.Update(
341 bytesWritten,
342 fileStream.Length,
343 crc,
344 headerPosition,
345 headerArchiveNumber);
346
347 streamContext.CloseFileReadStream(file, fileStream);
348 fileStream = null;
349
350 // Go back and rewrite the updated file header.
351 if (this.currentArchiveNumber == headerArchiveNumber)
352 {
353 long fileEndPosition = archiveStream.Position;
354 archiveStream.Seek(headerPosition, SeekOrigin.Begin);
355 fileHeader.Write(archiveStream, false);
356 archiveStream.Seek(fileEndPosition, SeekOrigin.Begin);
357 }
358 else
359 {
360 // The file spanned archives, so temporarily reopen
361 // the archive where it started.
362 string headerArchiveName = streamContext.GetArchiveName(
363 headerArchiveNumber + 1);
364 Stream headerStream = null;
365 try
366 {
367 headerStream = streamContext.OpenArchiveWriteStream(
368 headerArchiveNumber, headerArchiveName, false, this);
369 headerStream.Seek(headerPosition, SeekOrigin.Begin);
370 fileHeader.Write(headerStream, false);
371 }
372 finally
373 {
374 if (headerStream != null)
375 {
376 streamContext.CloseArchiveWriteStream(
377 headerArchiveNumber, headerArchiveName, headerStream);
378 }
379 }
380 }
381
382 this.OnProgress(ArchiveProgressType.FinishFile);
383
384 return fileHeader;
385 }
386 finally
387 {
388 if (fileStream != null)
389 {
390 streamContext.CloseFileReadStream(
391 this.currentFileName, fileStream);
392 }
393 }
394 }
395
396 /// <summary>
397 /// Writes compressed bytes of one file to the archive,
398 /// keeping track of the CRC and number of bytes written.
399 /// </summary>
400 private long PackFileBytes(
401 IPackStreamContext streamContext,
402 Stream fileStream,
403 long maxArchiveSize,
404 Converter<Stream, Stream> compressionStreamCreator,
405 ref Stream archiveStream,
406 out uint crc)
407 {
408 long writeStartPosition = archiveStream.Position;
409 long bytesWritten = 0;
410 CrcStream fileCrcStream = new CrcStream(fileStream);
411
412 ConcatStream concatStream = new ConcatStream(
413 delegate(ConcatStream s)
414 {
415 Stream sourceStream = s.Source;
416 bytesWritten += sourceStream.Position - writeStartPosition;
417
418 this.CheckArchiveWriteStream(
419 streamContext,
420 maxArchiveSize,
421 1,
422 ref sourceStream);
423
424 writeStartPosition = sourceStream.Position;
425 s.Source = sourceStream;
426 });
427
428 concatStream.Source = archiveStream;
429
430 if (maxArchiveSize > 0)
431 {
432 concatStream.SetLength(maxArchiveSize);
433 }
434
435 Stream compressionStream = compressionStreamCreator(concatStream);
436
437 try
438 {
439 byte[] buf = new byte[4096];
440 long bytesRemaining = fileStream.Length;
441 int counter = 0;
442 while (bytesRemaining > 0)
443 {
444 int count = (int) Math.Min(
445 bytesRemaining, (long) buf.Length);
446
447 count = fileCrcStream.Read(buf, 0, count);
448 if (count <= 0)
449 {
450 throw new ZipException(
451 "Failed to read file: " + this.currentFileName);
452 }
453
454 compressionStream.Write(buf, 0, count);
455 bytesRemaining -= count;
456
457 this.fileBytesProcessed += count;
458 this.currentFileBytesProcessed += count;
459 this.currentArchiveTotalBytes = concatStream.Source.Position;
460 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
461
462 if (++counter % 16 == 0) // Report every 64K
463 {
464 this.OnProgress(ArchiveProgressType.PartialFile);
465 }
466 }
467
468 if (compressionStream is DeflateStream)
469 {
470 compressionStream.Close();
471 }
472 else
473 {
474 compressionStream.Flush();
475 }
476 }
477 finally
478 {
479 archiveStream = concatStream.Source;
480 }
481
482 bytesWritten += archiveStream.Position - writeStartPosition;
483
484 crc = fileCrcStream.Crc;
485
486 return bytesWritten;
487 }
488 }
489}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ZipUnpacker.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipUnpacker.cs
new file mode 100644
index 00000000..681c0e46
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipUnpacker.cs
@@ -0,0 +1,336 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Zip
4{
5 using System;
6 using System.IO;
7 using System.IO.Compression;
8 using System.Collections.Generic;
9
10 public partial class ZipEngine
11 {
12 /// <summary>
13 /// Extracts files from a zip archive or archive chain.
14 /// </summary>
15 /// <param name="streamContext">A context interface to handle opening
16 /// and closing of archive and file streams.</param>
17 /// <param name="fileFilter">An optional predicate that can determine
18 /// which files to process.</param>
19 /// <exception cref="ArchiveException">The archive provided
20 /// by the stream context is not valid.</exception>
21 /// <remarks>
22 /// The <paramref name="fileFilter"/> predicate takes an internal file
23 /// path and returns true to include the file or false to exclude it.
24 /// </remarks>
25 public override void Unpack(
26 IUnpackStreamContext streamContext,
27 Predicate<string> fileFilter)
28 {
29 if (streamContext == null)
30 {
31 throw new ArgumentNullException("streamContext");
32 }
33
34 lock (this)
35 {
36 IList<ZipFileHeader> allHeaders = this.GetCentralDirectory(streamContext);
37 if (allHeaders == null)
38 {
39 throw new ZipException("Zip central directory not found.");
40 }
41
42 IList<ZipFileHeader> headers = new List<ZipFileHeader>(allHeaders.Count);
43 foreach (ZipFileHeader header in allHeaders)
44 {
45 if (!header.IsDirectory &&
46 (fileFilter == null || fileFilter(header.fileName)))
47 {
48 headers.Add(header);
49 }
50 }
51
52 this.ResetProgressData();
53
54 // Count the total number of files and bytes to be compressed.
55 this.totalFiles = headers.Count;
56 foreach (ZipFileHeader header in headers)
57 {
58 long compressedSize;
59 long uncompressedSize;
60 long localHeaderOffset;
61 int archiveNumber;
62 uint crc;
63 header.GetZip64Fields(
64 out compressedSize,
65 out uncompressedSize,
66 out localHeaderOffset,
67 out archiveNumber,
68 out crc);
69
70 this.totalFileBytes += uncompressedSize;
71 if (archiveNumber >= this.totalArchives)
72 {
73 this.totalArchives = (short) (archiveNumber + 1);
74 }
75 }
76
77 this.currentArchiveNumber = -1;
78 this.currentFileNumber = -1;
79 Stream archiveStream = null;
80 try
81 {
82 foreach (ZipFileHeader header in headers)
83 {
84 this.currentFileNumber++;
85 this.UnpackOneFile(streamContext, header, ref archiveStream);
86 }
87 }
88 finally
89 {
90 if (archiveStream != null)
91 {
92 streamContext.CloseArchiveReadStream(
93 0, String.Empty, archiveStream);
94 this.currentArchiveNumber--;
95 this.OnProgress(ArchiveProgressType.FinishArchive);
96 }
97 }
98 }
99 }
100
101 /// <summary>
102 /// Unpacks a single file from an archive or archive chain.
103 /// </summary>
104 private void UnpackOneFile(
105 IUnpackStreamContext streamContext,
106 ZipFileHeader header,
107 ref Stream archiveStream)
108 {
109 ZipFileInfo fileInfo = null;
110 Stream fileStream = null;
111 try
112 {
113 Converter<Stream, Stream> compressionStreamCreator;
114 if (!ZipEngine.decompressionStreamCreators.TryGetValue(
115 header.compressionMethod, out compressionStreamCreator))
116 {
117 // Silently skip files of an unsupported compression method.
118 return;
119 }
120
121 long compressedSize;
122 long uncompressedSize;
123 long localHeaderOffset;
124 int archiveNumber;
125 uint crc;
126 header.GetZip64Fields(
127 out compressedSize,
128 out uncompressedSize,
129 out localHeaderOffset,
130 out archiveNumber,
131 out crc);
132
133 if (this.currentArchiveNumber != archiveNumber + 1)
134 {
135 if (archiveStream != null)
136 {
137 streamContext.CloseArchiveReadStream(
138 this.currentArchiveNumber,
139 String.Empty,
140 archiveStream);
141 archiveStream = null;
142
143 this.OnProgress(ArchiveProgressType.FinishArchive);
144 this.currentArchiveName = null;
145 }
146
147 this.currentArchiveNumber = (short) (archiveNumber + 1);
148 this.currentArchiveBytesProcessed = 0;
149 this.currentArchiveTotalBytes = 0;
150
151 archiveStream = this.OpenArchive(
152 streamContext, this.currentArchiveNumber);
153
154 FileStream archiveFileStream = archiveStream as FileStream;
155 this.currentArchiveName = (archiveFileStream != null ?
156 Path.GetFileName(archiveFileStream.Name) : null);
157
158 this.currentArchiveTotalBytes = archiveStream.Length;
159 this.currentArchiveNumber--;
160 this.OnProgress(ArchiveProgressType.StartArchive);
161 this.currentArchiveNumber++;
162 }
163
164 archiveStream.Seek(localHeaderOffset, SeekOrigin.Begin);
165
166 ZipFileHeader localHeader = new ZipFileHeader();
167 if (!localHeader.Read(archiveStream, false) ||
168 !ZipEngine.AreFilePathsEqual(localHeader.fileName, header.fileName))
169 {
170 string msg = "Could not read file: " + header.fileName;
171 throw new ZipException(msg);
172 }
173
174 fileInfo = header.ToZipFileInfo();
175
176 fileStream = streamContext.OpenFileWriteStream(
177 fileInfo.FullName,
178 fileInfo.Length,
179 fileInfo.LastWriteTime);
180
181 if (fileStream != null)
182 {
183 this.currentFileName = header.fileName;
184 this.currentFileBytesProcessed = 0;
185 this.currentFileTotalBytes = fileInfo.Length;
186 this.currentArchiveNumber--;
187 this.OnProgress(ArchiveProgressType.StartFile);
188 this.currentArchiveNumber++;
189
190 this.UnpackFileBytes(
191 streamContext,
192 fileInfo.FullName,
193 fileInfo.CompressedLength,
194 fileInfo.Length,
195 header.crc32,
196 fileStream,
197 compressionStreamCreator,
198 ref archiveStream);
199 }
200 }
201 finally
202 {
203 if (fileStream != null)
204 {
205 streamContext.CloseFileWriteStream(
206 fileInfo.FullName,
207 fileStream,
208 fileInfo.Attributes,
209 fileInfo.LastWriteTime);
210
211 this.currentArchiveNumber--;
212 this.OnProgress(ArchiveProgressType.FinishFile);
213 this.currentArchiveNumber++;
214 }
215 }
216 }
217
218 /// <summary>
219 /// Compares two internal file paths while ignoring case and slash differences.
220 /// </summary>
221 /// <param name="path1">The first path to compare.</param>
222 /// <param name="path2">The second path to compare.</param>
223 /// <returns>True if the paths are equivalent.</returns>
224 private static bool AreFilePathsEqual(string path1, string path2)
225 {
226 path1 = path1.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
227 path2 = path2.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
228 return String.Compare(path1, path2, StringComparison.OrdinalIgnoreCase) == 0;
229 }
230
231 private Stream OpenArchive(IUnpackStreamContext streamContext, int archiveNumber)
232 {
233 Stream archiveStream = streamContext.OpenArchiveReadStream(
234 archiveNumber, String.Empty, this);
235 if (archiveStream == null && archiveNumber != 0)
236 {
237 archiveStream = streamContext.OpenArchiveReadStream(
238 0, String.Empty, this);
239 }
240
241 if (archiveStream == null)
242 {
243 throw new FileNotFoundException("Archive stream not provided.");
244 }
245
246 return archiveStream;
247 }
248
249 /// <summary>
250 /// Decompresses bytes for one file from an archive or archive chain,
251 /// checking the crc at the end.
252 /// </summary>
253 private void UnpackFileBytes(
254 IUnpackStreamContext streamContext,
255 string fileName,
256 long compressedSize,
257 long uncompressedSize,
258 uint crc,
259 Stream fileStream,
260 Converter<Stream, Stream> compressionStreamCreator,
261 ref Stream archiveStream)
262 {
263 CrcStream crcStream = new CrcStream(fileStream);
264
265 ConcatStream concatStream = new ConcatStream(
266 delegate(ConcatStream s)
267 {
268 this.currentArchiveBytesProcessed = s.Source.Position;
269 streamContext.CloseArchiveReadStream(
270 this.currentArchiveNumber,
271 String.Empty,
272 s.Source);
273
274 this.currentArchiveNumber--;
275 this.OnProgress(ArchiveProgressType.FinishArchive);
276 this.currentArchiveNumber += 2;
277 this.currentArchiveName = null;
278 this.currentArchiveBytesProcessed = 0;
279 this.currentArchiveTotalBytes = 0;
280
281 s.Source = this.OpenArchive(streamContext, this.currentArchiveNumber);
282
283 FileStream archiveFileStream = s.Source as FileStream;
284 this.currentArchiveName = (archiveFileStream != null ?
285 Path.GetFileName(archiveFileStream.Name) : null);
286
287 this.currentArchiveTotalBytes = s.Source.Length;
288 this.currentArchiveNumber--;
289 this.OnProgress(ArchiveProgressType.StartArchive);
290 this.currentArchiveNumber++;
291 });
292
293 concatStream.Source = archiveStream;
294 concatStream.SetLength(compressedSize);
295
296 Stream decompressionStream = compressionStreamCreator(concatStream);
297
298 try
299 {
300 byte[] buf = new byte[4096];
301 long bytesRemaining = uncompressedSize;
302 int counter = 0;
303 while (bytesRemaining > 0)
304 {
305 int count = (int) Math.Min(buf.Length, bytesRemaining);
306 count = decompressionStream.Read(buf, 0, count);
307 crcStream.Write(buf, 0, count);
308 bytesRemaining -= count;
309
310 this.fileBytesProcessed += count;
311 this.currentFileBytesProcessed += count;
312 this.currentArchiveBytesProcessed = concatStream.Source.Position;
313
314 if (++counter % 16 == 0) // Report every 64K
315 {
316 this.currentArchiveNumber--;
317 this.OnProgress(ArchiveProgressType.PartialFile);
318 this.currentArchiveNumber++;
319 }
320 }
321 }
322 finally
323 {
324 archiveStream = concatStream.Source;
325 }
326
327 crcStream.Flush();
328
329 if (crcStream.Crc != crc)
330 {
331 throw new ZipException("CRC check failed for file: " + fileName);
332 }
333 }
334 }
335}
336
diff --git a/src/dtf/WixToolset.Dtf.Compression/ArchiveException.cs b/src/dtf/WixToolset.Dtf.Compression/ArchiveException.cs
new file mode 100644
index 00000000..a53e862c
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression/ArchiveException.cs
@@ -0,0 +1,57 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression
4{
5 using System;
6 using System.IO;
7 using System.Runtime.Serialization;
8
9 /// <summary>
10 /// Base exception class for compression operations. Compression libraries should
11 /// derive subclass exceptions with more specific error information relevent to the
12 /// file format.
13 /// </summary>
14 [Serializable]
15 public class ArchiveException : IOException
16 {
17 /// <summary>
18 /// Creates a new ArchiveException with a specified error message and a reference to the
19 /// inner exception that is the cause of this exception.
20 /// </summary>
21 /// <param name="message">The message that describes the error.</param>
22 /// <param name="innerException">The exception that is the cause of the current exception. If the
23 /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
24 /// is raised in a catch block that handles the inner exception.</param>
25 public ArchiveException(string message, Exception innerException)
26 : base(message, innerException)
27 {
28 }
29
30 /// <summary>
31 /// Creates a new ArchiveException with a specified error message.
32 /// </summary>
33 /// <param name="message">The message that describes the error.</param>
34 public ArchiveException(string message)
35 : this(message, null)
36 {
37 }
38
39 /// <summary>
40 /// Creates a new ArchiveException.
41 /// </summary>
42 public ArchiveException()
43 : this(null, null)
44 {
45 }
46
47 /// <summary>
48 /// Initializes a new instance of the ArchiveException class with serialized data.
49 /// </summary>
50 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
51 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
52 protected ArchiveException(SerializationInfo info, StreamingContext context)
53 : base(info, context)
54 {
55 }
56 }
57}
diff --git a/src/dtf/WixToolset.Dtf.Compression/ArchiveFileInfo.cs b/src/dtf/WixToolset.Dtf.Compression/ArchiveFileInfo.cs
new file mode 100644
index 00000000..adcae3ec
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression/ArchiveFileInfo.cs
@@ -0,0 +1,430 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression
4{
5 using System;
6 using System.IO;
7 using System.Runtime.Serialization;
8 using System.Diagnostics.CodeAnalysis;
9
10 /// <summary>
11 /// Abstract object representing a compressed file within an archive;
12 /// provides operations for getting the file properties and unpacking
13 /// the file.
14 /// </summary>
15 [Serializable]
16 public abstract class ArchiveFileInfo : FileSystemInfo
17 {
18 private ArchiveInfo archiveInfo;
19 private string name;
20 private string path;
21
22 private bool initialized;
23 private bool exists;
24 private int archiveNumber;
25 private FileAttributes attributes;
26 private DateTime lastWriteTime;
27 private long length;
28
29 /// <summary>
30 /// Creates a new ArchiveFileInfo object representing a file within
31 /// an archive in a specified path.
32 /// </summary>
33 /// <param name="archiveInfo">An object representing the archive
34 /// containing the file.</param>
35 /// <param name="filePath">The path to the file within the archive.
36 /// Usually, this is a simple file name, but if the archive contains
37 /// a directory structure this may include the directory.</param>
38 protected ArchiveFileInfo(ArchiveInfo archiveInfo, string filePath)
39 : base()
40 {
41 if (filePath == null)
42 {
43 throw new ArgumentNullException("filePath");
44 }
45
46 this.Archive = archiveInfo;
47
48 this.name = System.IO.Path.GetFileName(filePath);
49 this.path = System.IO.Path.GetDirectoryName(filePath);
50
51 this.attributes = FileAttributes.Normal;
52 this.lastWriteTime = DateTime.MinValue;
53 }
54
55 /// <summary>
56 /// Creates a new ArchiveFileInfo object with all parameters specified;
57 /// used by subclasses when reading the metadata out of an archive.
58 /// </summary>
59 /// <param name="filePath">The internal path and name of the file in
60 /// the archive.</param>
61 /// <param name="archiveNumber">The archive number where the file
62 /// starts.</param>
63 /// <param name="attributes">The stored attributes of the file.</param>
64 /// <param name="lastWriteTime">The stored last write time of the
65 /// file.</param>
66 /// <param name="length">The uncompressed size of the file.</param>
67 protected ArchiveFileInfo(
68 string filePath,
69 int archiveNumber,
70 FileAttributes attributes,
71 DateTime lastWriteTime,
72 long length)
73 : this(null, filePath)
74 {
75 this.exists = true;
76 this.archiveNumber = archiveNumber;
77 this.attributes = attributes;
78 this.lastWriteTime = lastWriteTime;
79 this.length = length;
80 this.initialized = true;
81 }
82
83 /// <summary>
84 /// Initializes a new instance of the ArchiveFileInfo class with
85 /// serialized data.
86 /// </summary>
87 /// <param name="info">The SerializationInfo that holds the serialized
88 /// object data about the exception being thrown.</param>
89 /// <param name="context">The StreamingContext that contains contextual
90 /// information about the source or destination.</param>
91 protected ArchiveFileInfo(SerializationInfo info, StreamingContext context)
92 : base(info, context)
93 {
94 this.archiveInfo = (ArchiveInfo) info.GetValue(
95 "archiveInfo", typeof(ArchiveInfo));
96 this.name = info.GetString("name");
97 this.path = info.GetString("path");
98 this.initialized = info.GetBoolean("initialized");
99 this.exists = info.GetBoolean("exists");
100 this.archiveNumber = info.GetInt32("archiveNumber");
101 this.attributes = (FileAttributes) info.GetValue(
102 "attributes", typeof(FileAttributes));
103 this.lastWriteTime = info.GetDateTime("lastWriteTime");
104 this.length = info.GetInt64("length");
105 }
106
107 /// <summary>
108 /// Gets the name of the file.
109 /// </summary>
110 /// <value>The name of the file, not including any path.</value>
111 public override string Name
112 {
113 get
114 {
115 return this.name;
116 }
117 }
118
119 /// <summary>
120 /// Gets the internal path of the file in the archive.
121 /// </summary>
122 /// <value>The internal path of the file in the archive, not including
123 /// the file name.</value>
124 public string Path
125 {
126 get
127 {
128 return this.path;
129 }
130 }
131
132 /// <summary>
133 /// Gets the full path to the file.
134 /// </summary>
135 /// <value>The full path to the file, including the full path to the
136 /// archive, the internal path in the archive, and the file name.</value>
137 /// <remarks>
138 /// For example, the path <c>"C:\archive.cab\file.txt"</c> refers to
139 /// a file "file.txt" inside the archive "archive.cab".
140 /// </remarks>
141 public override string FullName
142 {
143 get
144 {
145 string fullName = System.IO.Path.Combine(this.Path, this.Name);
146
147 if (this.Archive != null)
148 {
149 fullName = System.IO.Path.Combine(this.ArchiveName, fullName);
150 }
151
152 return fullName;
153 }
154 }
155
156 /// <summary>
157 /// Gets or sets the archive that contains this file.
158 /// </summary>
159 /// <value>
160 /// The ArchiveInfo instance that retrieved this file information -- this
161 /// may be null if the ArchiveFileInfo object was returned directly from
162 /// a stream.
163 /// </value>
164 public ArchiveInfo Archive
165 {
166 get
167 {
168 return (ArchiveInfo) this.archiveInfo;
169 }
170
171 internal set
172 {
173 this.archiveInfo = value;
174
175 // protected instance members inherited from FileSystemInfo:
176 this.OriginalPath = (value != null ? value.FullName : null);
177 this.FullPath = this.OriginalPath;
178 }
179 }
180
181 /// <summary>
182 /// Gets the full path of the archive that contains this file.
183 /// </summary>
184 /// <value>The full path of the archive that contains this file.</value>
185 public string ArchiveName
186 {
187 get
188 {
189 return this.Archive != null ? this.Archive.FullName : null;
190 }
191 }
192
193 /// <summary>
194 /// Gets the number of the archive where this file starts.
195 /// </summary>
196 /// <value>The number of the archive where this file starts.</value>
197 /// <remarks>A single archive or the first archive in a chain is
198 /// numbered 0.</remarks>
199 public int ArchiveNumber
200 {
201 get
202 {
203 return this.archiveNumber;
204 }
205 }
206
207 /// <summary>
208 /// Checks if the file exists within the archive.
209 /// </summary>
210 /// <value>True if the file exists, false otherwise.</value>
211 public override bool Exists
212 {
213 get
214 {
215 if (!this.initialized)
216 {
217 this.Refresh();
218 }
219
220 return this.exists;
221 }
222 }
223
224 /// <summary>
225 /// Gets the uncompressed size of the file.
226 /// </summary>
227 /// <value>The uncompressed size of the file in bytes.</value>
228 public long Length
229 {
230 get
231 {
232 if (!this.initialized)
233 {
234 this.Refresh();
235 }
236
237 return this.length;
238 }
239 }
240
241 /// <summary>
242 /// Gets the attributes of the file.
243 /// </summary>
244 /// <value>The attributes of the file as stored in the archive.</value>
245 public new FileAttributes Attributes
246 {
247 get
248 {
249 if (!this.initialized)
250 {
251 this.Refresh();
252 }
253
254 return this.attributes;
255 }
256 }
257
258 /// <summary>
259 /// Gets the last modification time of the file.
260 /// </summary>
261 /// <value>The last modification time of the file as stored in the
262 /// archive.</value>
263 public new DateTime LastWriteTime
264 {
265 get
266 {
267 if (!this.initialized)
268 {
269 this.Refresh();
270 }
271
272 return this.lastWriteTime;
273 }
274 }
275
276 /// <summary>
277 /// Sets the SerializationInfo with information about the archive.
278 /// </summary>
279 /// <param name="info">The SerializationInfo that holds the serialized
280 /// object data.</param>
281 /// <param name="context">The StreamingContext that contains contextual
282 /// information about the source or destination.</param>
283 public override void GetObjectData(
284 SerializationInfo info, StreamingContext context)
285 {
286 base.GetObjectData(info, context);
287 info.AddValue("archiveInfo", this.archiveInfo);
288 info.AddValue("name", this.name);
289 info.AddValue("path", this.path);
290 info.AddValue("initialized", this.initialized);
291 info.AddValue("exists", this.exists);
292 info.AddValue("archiveNumber", this.archiveNumber);
293 info.AddValue("attributes", this.attributes);
294 info.AddValue("lastWriteTime", this.lastWriteTime);
295 info.AddValue("length", this.length);
296 }
297
298 /// <summary>
299 /// Gets the full path to the file.
300 /// </summary>
301 /// <returns>The same as <see cref="FullName"/></returns>
302 public override string ToString()
303 {
304 return this.FullName;
305 }
306
307 /// <summary>
308 /// Deletes the file. NOT SUPPORTED.
309 /// </summary>
310 /// <exception cref="NotSupportedException">Files cannot be deleted
311 /// from an existing archive.</exception>
312 public override void Delete()
313 {
314 throw new NotSupportedException();
315 }
316
317 /// <summary>
318 /// Refreshes the attributes and other cached information about the file,
319 /// by re-reading the information from the archive.
320 /// </summary>
321 public new void Refresh()
322 {
323 base.Refresh();
324
325 if (this.Archive != null)
326 {
327 string filePath = System.IO.Path.Combine(this.Path, this.Name);
328 ArchiveFileInfo updatedFile = this.Archive.GetFile(filePath);
329 if (updatedFile == null)
330 {
331 throw new FileNotFoundException(
332 "File not found in archive.", filePath);
333 }
334
335 this.Refresh(updatedFile);
336 }
337 }
338
339 /// <summary>
340 /// Extracts the file.
341 /// </summary>
342 /// <param name="destFileName">The destination path where the file
343 /// will be extracted.</param>
344 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
345 public void CopyTo(string destFileName)
346 {
347 this.CopyTo(destFileName, false);
348 }
349
350 /// <summary>
351 /// Extracts the file, optionally overwriting any existing file.
352 /// </summary>
353 /// <param name="destFileName">The destination path where the file
354 /// will be extracted.</param>
355 /// <param name="overwrite">If true, <paramref name="destFileName"/>
356 /// will be overwritten if it exists.</param>
357 /// <exception cref="IOException"><paramref name="overwrite"/> is false
358 /// and <paramref name="destFileName"/> exists.</exception>
359 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
360 public void CopyTo(string destFileName, bool overwrite)
361 {
362 if (destFileName == null)
363 {
364 throw new ArgumentNullException("destFileName");
365 }
366
367 if (!overwrite && File.Exists(destFileName))
368 {
369 throw new IOException();
370 }
371
372 if (this.Archive == null)
373 {
374 throw new InvalidOperationException();
375 }
376
377 this.Archive.UnpackFile(
378 System.IO.Path.Combine(this.Path, this.Name), destFileName);
379 }
380
381 /// <summary>
382 /// Opens the archive file for reading without actually extracting the
383 /// file to disk.
384 /// </summary>
385 /// <returns>
386 /// A stream for reading directly from the packed file. Like any stream
387 /// this should be closed/disposed as soon as it is no longer needed.
388 /// </returns>
389 public Stream OpenRead()
390 {
391 return this.Archive.OpenRead(System.IO.Path.Combine(this.Path, this.Name));
392 }
393
394 /// <summary>
395 /// Opens the archive file reading text with UTF-8 encoding without
396 /// actually extracting the file to disk.
397 /// </summary>
398 /// <returns>
399 /// A reader for reading text directly from the packed file. Like any reader
400 /// this should be closed/disposed as soon as it is no longer needed.
401 /// </returns>
402 /// <remarks>
403 /// To open an archived text file with different encoding, use the
404 /// <see cref="OpenRead" /> method and pass the returned stream to one of
405 /// the <see cref="StreamReader" /> constructor overloads.
406 /// </remarks>
407 public StreamReader OpenText()
408 {
409 return this.Archive.OpenText(System.IO.Path.Combine(this.Path, this.Name));
410 }
411
412 /// <summary>
413 /// Refreshes the information in this object with new data retrieved
414 /// from an archive.
415 /// </summary>
416 /// <param name="newFileInfo">Fresh instance for the same file just
417 /// read from the archive.</param>
418 /// <remarks>
419 /// Subclasses may override this method to refresh sublcass fields.
420 /// However they should always call the base implementation first.
421 /// </remarks>
422 protected virtual void Refresh(ArchiveFileInfo newFileInfo)
423 {
424 this.exists = newFileInfo.exists;
425 this.length = newFileInfo.length;
426 this.attributes = newFileInfo.attributes;
427 this.lastWriteTime = newFileInfo.lastWriteTime;
428 }
429 }
430}
diff --git a/src/dtf/WixToolset.Dtf.Compression/ArchiveFileStreamContext.cs b/src/dtf/WixToolset.Dtf.Compression/ArchiveFileStreamContext.cs
new file mode 100644
index 00000000..8be3bff4
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression/ArchiveFileStreamContext.cs
@@ -0,0 +1,664 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression
4{
5 using System;
6 using System.IO;
7 using System.Collections.Generic;
8
9 /// <summary>
10 /// Provides a basic implementation of the archive pack and unpack stream context
11 /// interfaces, based on a list of archive files, a default directory, and an
12 /// optional mapping from internal to external file paths.
13 /// </summary>
14 /// <remarks>
15 /// This class can also handle creating or extracting chained archive packages.
16 /// </remarks>
17 public class ArchiveFileStreamContext
18 : IPackStreamContext, IUnpackStreamContext
19 {
20 private IList<string> archiveFiles;
21 private string directory;
22 private IDictionary<string, string> files;
23 private bool extractOnlyNewerFiles;
24 private bool enableOffsetOpen;
25
26 #region Constructors
27
28 /// <summary>
29 /// Creates a new ArchiveFileStreamContext with a archive file and
30 /// no default directory or file mapping.
31 /// </summary>
32 /// <param name="archiveFile">The path to a archive file that will be
33 /// created or extracted.</param>
34 public ArchiveFileStreamContext(string archiveFile)
35 : this(archiveFile, null, null)
36 {
37 }
38
39 /// <summary>
40 /// Creates a new ArchiveFileStreamContext with a archive file, default
41 /// directory and mapping from internal to external file paths.
42 /// </summary>
43 /// <param name="archiveFile">The path to a archive file that will be
44 /// created or extracted.</param>
45 /// <param name="directory">The default root directory where files will be
46 /// located, optional.</param>
47 /// <param name="files">A mapping from internal file paths to external file
48 /// paths, optional.</param>
49 /// <remarks>
50 /// If the mapping is not null and a file is not included in the mapping,
51 /// the file will be skipped.
52 /// <para>If the external path in the mapping is a simple file name or
53 /// relative file path, it will be concatenated onto the default directory,
54 /// if one was specified.</para>
55 /// <para>For more about how the default directory and files mapping are
56 /// used, see <see cref="OpenFileReadStream"/> and
57 /// <see cref="OpenFileWriteStream"/>.</para>
58 /// </remarks>
59 public ArchiveFileStreamContext(
60 string archiveFile,
61 string directory,
62 IDictionary<string, string> files)
63 : this(new string[] { archiveFile }, directory, files)
64 {
65 if (archiveFile == null)
66 {
67 throw new ArgumentNullException("archiveFile");
68 }
69 }
70
71 /// <summary>
72 /// Creates a new ArchiveFileStreamContext with a list of archive files,
73 /// a default directory and a mapping from internal to external file paths.
74 /// </summary>
75 /// <param name="archiveFiles">A list of paths to archive files that will be
76 /// created or extracted.</param>
77 /// <param name="directory">The default root directory where files will be
78 /// located, optional.</param>
79 /// <param name="files">A mapping from internal file paths to external file
80 /// paths, optional.</param>
81 /// <remarks>
82 /// When creating chained archives, the <paramref name="archiveFiles"/> list
83 /// should include at least enough archives to handle the entire set of
84 /// input files, based on the maximum archive size that is passed to the
85 /// <see cref="CompressionEngine"/>.<see
86 /// cref="CompressionEngine.Pack(IPackStreamContext,IEnumerable&lt;string&gt;,long)"/>.
87 /// <para>If the mapping is not null and a file is not included in the mapping,
88 /// the file will be skipped.</para>
89 /// <para>If the external path in the mapping is a simple file name or
90 /// relative file path, it will be concatenated onto the default directory,
91 /// if one was specified.</para>
92 /// <para>For more about how the default directory and files mapping are used,
93 /// see <see cref="OpenFileReadStream"/> and
94 /// <see cref="OpenFileWriteStream"/>.</para>
95 /// </remarks>
96 public ArchiveFileStreamContext(
97 IList<string> archiveFiles,
98 string directory,
99 IDictionary<string, string> files)
100 {
101 if (archiveFiles == null || archiveFiles.Count == 0)
102 {
103 throw new ArgumentNullException("archiveFiles");
104 }
105
106 this.archiveFiles = archiveFiles;
107 this.directory = directory;
108 this.files = files;
109 }
110
111 #endregion
112
113 #region Properties
114
115 /// <summary>
116 /// Gets or sets the list of archive files that are created or extracted.
117 /// </summary>
118 /// <value>The list of archive files that are created or extracted.</value>
119 public IList<string> ArchiveFiles
120 {
121 get
122 {
123 return this.archiveFiles;
124 }
125 }
126
127 /// <summary>
128 /// Gets or sets the default root directory where files are located.
129 /// </summary>
130 /// <value>The default root directory where files are located.</value>
131 /// <remarks>
132 /// For details about how the default directory is used,
133 /// see <see cref="OpenFileReadStream"/> and <see cref="OpenFileWriteStream"/>.
134 /// </remarks>
135 public string Directory
136 {
137 get
138 {
139 return this.directory;
140 }
141 }
142
143 /// <summary>
144 /// Gets or sets the mapping from internal file paths to external file paths.
145 /// </summary>
146 /// <value>A mapping from internal file paths to external file paths.</value>
147 /// <remarks>
148 /// For details about how the files mapping is used,
149 /// see <see cref="OpenFileReadStream"/> and <see cref="OpenFileWriteStream"/>.
150 /// </remarks>
151 public IDictionary<string, string> Files
152 {
153 get
154 {
155 return this.files;
156 }
157 }
158
159 /// <summary>
160 /// Gets or sets a flag that can prevent extracted files from overwriting
161 /// newer files that already exist.
162 /// </summary>
163 /// <value>True to prevent overwriting newer files that already exist
164 /// during extraction; false to always extract from the archive regardless
165 /// of existing files.</value>
166 public bool ExtractOnlyNewerFiles
167 {
168 get
169 {
170 return this.extractOnlyNewerFiles;
171 }
172
173 set
174 {
175 this.extractOnlyNewerFiles = value;
176 }
177 }
178
179 /// <summary>
180 /// Gets or sets a flag that enables creating or extracting an archive
181 /// at an offset within an existing file. (This is typically used to open
182 /// archive-based self-extracting packages.)
183 /// </summary>
184 /// <value>True to search an existing package file for an archive offset
185 /// or the end of the file;/ false to always create or open a plain
186 /// archive file.</value>
187 public bool EnableOffsetOpen
188 {
189 get
190 {
191 return this.enableOffsetOpen;
192 }
193
194 set
195 {
196 this.enableOffsetOpen = value;
197 }
198 }
199
200 #endregion
201
202 #region IPackStreamContext Members
203
204 /// <summary>
205 /// Gets the name of the archive with a specified number.
206 /// </summary>
207 /// <param name="archiveNumber">The 0-based index of the archive within
208 /// the chain.</param>
209 /// <returns>The name of the requested archive. May be an empty string
210 /// for non-chained archives, but may never be null.</returns>
211 /// <remarks>This method returns the file name of the archive from the
212 /// <see cref="archiveFiles"/> list with the specified index, or an empty
213 /// string if the archive number is outside the bounds of the list. The
214 /// file name should not include any directory path.</remarks>
215 public virtual string GetArchiveName(int archiveNumber)
216 {
217 if (archiveNumber < this.archiveFiles.Count)
218 {
219 return Path.GetFileName(this.archiveFiles[archiveNumber]);
220 }
221
222 return String.Empty;
223 }
224
225 /// <summary>
226 /// Opens a stream for writing an archive.
227 /// </summary>
228 /// <param name="archiveNumber">The 0-based index of the archive within
229 /// the chain.</param>
230 /// <param name="archiveName">The name of the archive that was returned
231 /// by <see cref="GetArchiveName"/>.</param>
232 /// <param name="truncate">True if the stream should be truncated when
233 /// opened (if it already exists); false if an existing stream is being
234 /// re-opened for writing additional data.</param>
235 /// <param name="compressionEngine">Instance of the compression engine
236 /// doing the operations.</param>
237 /// <returns>A writable Stream where the compressed archive bytes will be
238 /// written, or null to cancel the archive creation.</returns>
239 /// <remarks>
240 /// This method opens the file from the <see cref="ArchiveFiles"/> list
241 /// with the specified index. If the archive number is outside the bounds
242 /// of the list, this method returns null.
243 /// <para>If the <see cref="EnableOffsetOpen"/> flag is set, this method
244 /// will seek to the start of any existing archive in the file, or to the
245 /// end of the file if the existing file is not an archive.</para>
246 /// </remarks>
247 public virtual Stream OpenArchiveWriteStream(
248 int archiveNumber,
249 string archiveName,
250 bool truncate,
251 CompressionEngine compressionEngine)
252 {
253 if (archiveNumber >= this.archiveFiles.Count)
254 {
255 return null;
256 }
257
258 if (String.IsNullOrEmpty(archiveName))
259 {
260 throw new ArgumentNullException("archiveName");
261 }
262
263 // All archives must be in the same directory,
264 // so always use the directory from the first archive.
265 string archiveFile = Path.Combine(
266 Path.GetDirectoryName(this.archiveFiles[0]), archiveName);
267 Stream stream = File.Open(
268 archiveFile,
269 (truncate ? FileMode.OpenOrCreate : FileMode.Open),
270 FileAccess.ReadWrite);
271
272 if (this.enableOffsetOpen)
273 {
274 long offset = compressionEngine.FindArchiveOffset(
275 new DuplicateStream(stream));
276
277 // If this is not an archive file, append the archive to it.
278 if (offset < 0)
279 {
280 offset = stream.Length;
281 }
282
283 if (offset > 0)
284 {
285 stream = new OffsetStream(stream, offset);
286 }
287
288 stream.Seek(0, SeekOrigin.Begin);
289 }
290
291 if (truncate)
292 {
293 // Truncate the stream, in case a larger old archive starts here.
294 stream.SetLength(0);
295 }
296
297 return stream;
298 }
299
300 /// <summary>
301 /// Closes a stream where an archive package was written.
302 /// </summary>
303 /// <param name="archiveNumber">The 0-based index of the archive within
304 /// the chain.</param>
305 /// <param name="archiveName">The name of the archive that was previously
306 /// returned by <see cref="GetArchiveName"/>.</param>
307 /// <param name="stream">A stream that was previously returned by
308 /// <see cref="OpenArchiveWriteStream"/> and is now ready to be closed.</param>
309 public virtual void CloseArchiveWriteStream(
310 int archiveNumber,
311 string archiveName,
312 Stream stream)
313 {
314 if (stream != null)
315 {
316 stream.Close();
317
318 FileStream fileStream = stream as FileStream;
319 if (fileStream != null)
320 {
321 string streamFile = fileStream.Name;
322 if (!String.IsNullOrEmpty(archiveName) &&
323 archiveName != Path.GetFileName(streamFile))
324 {
325 string archiveFile = Path.Combine(
326 Path.GetDirectoryName(this.archiveFiles[0]), archiveName);
327 if (File.Exists(archiveFile))
328 {
329 File.Delete(archiveFile);
330 }
331 File.Move(streamFile, archiveFile);
332 }
333 }
334 }
335 }
336
337 /// <summary>
338 /// Opens a stream to read a file that is to be included in an archive.
339 /// </summary>
340 /// <param name="path">The path of the file within the archive.</param>
341 /// <param name="attributes">The returned attributes of the opened file,
342 /// to be stored in the archive.</param>
343 /// <param name="lastWriteTime">The returned last-modified time of the
344 /// opened file, to be stored in the archive.</param>
345 /// <returns>A readable Stream where the file bytes will be read from
346 /// before they are compressed, or null to skip inclusion of the file and
347 /// continue to the next file.</returns>
348 /// <remarks>
349 /// This method opens a file using the following logic:
350 /// <list>
351 /// <item>If the <see cref="Directory"/> and the <see cref="Files"/> mapping
352 /// are both null, the path is treated as relative to the current directory,
353 /// and that file is opened.</item>
354 /// <item>If the <see cref="Directory"/> is not null but the <see cref="Files"/>
355 /// mapping is null, the path is treated as relative to that directory, and
356 /// that file is opened.</item>
357 /// <item>If the <see cref="Directory"/> is null but the <see cref="Files"/>
358 /// mapping is not null, the path parameter is used as a key into the mapping,
359 /// and the resulting value is the file path that is opened, relative to the
360 /// current directory (or it may be an absolute path). If no mapping exists,
361 /// the file is skipped.</item>
362 /// <item>If both the <see cref="Directory"/> and the <see cref="Files"/>
363 /// mapping are specified, the path parameter is used as a key into the
364 /// mapping, and the resulting value is the file path that is opened, relative
365 /// to the specified directory (or it may be an absolute path). If no mapping
366 /// exists, the file is skipped.</item>
367 /// </list>
368 /// </remarks>
369 public virtual Stream OpenFileReadStream(
370 string path, out FileAttributes attributes, out DateTime lastWriteTime)
371 {
372 string filePath = this.TranslateFilePath(path);
373
374 if (filePath == null)
375 {
376 attributes = FileAttributes.Normal;
377 lastWriteTime = DateTime.Now;
378 return null;
379 }
380
381 attributes = File.GetAttributes(filePath);
382 lastWriteTime = File.GetLastWriteTime(filePath);
383 return File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
384 }
385
386 /// <summary>
387 /// Closes a stream that has been used to read a file.
388 /// </summary>
389 /// <param name="path">The path of the file within the archive; the same as
390 /// the path provided when the stream was opened.</param>
391 /// <param name="stream">A stream that was previously returned by
392 /// <see cref="OpenFileReadStream"/> and is now ready to be closed.</param>
393 public virtual void CloseFileReadStream(string path, Stream stream)
394 {
395 if (stream != null)
396 {
397 stream.Close();
398 }
399 }
400
401 /// <summary>
402 /// Gets extended parameter information specific to the compression format
403 /// being used.
404 /// </summary>
405 /// <param name="optionName">Name of the option being requested.</param>
406 /// <param name="parameters">Parameters for the option; for per-file options,
407 /// the first parameter is typically the internal file path.</param>
408 /// <returns>Option value, or null to use the default behavior.</returns>
409 /// <remarks>
410 /// This implementation does not handle any options. Subclasses may override
411 /// this method to allow for non-default behavior.
412 /// </remarks>
413 public virtual object GetOption(string optionName, object[] parameters)
414 {
415 return null;
416 }
417
418 #endregion
419
420 #region IUnpackStreamContext Members
421
422 /// <summary>
423 /// Opens the archive stream for reading.
424 /// </summary>
425 /// <param name="archiveNumber">The zero-based index of the archive to
426 /// open.</param>
427 /// <param name="archiveName">The name of the archive being opened.</param>
428 /// <param name="compressionEngine">Instance of the compression engine
429 /// doing the operations.</param>
430 /// <returns>A stream from which archive bytes are read, or null to cancel
431 /// extraction of the archive.</returns>
432 /// <remarks>
433 /// This method opens the file from the <see cref="ArchiveFiles"/> list with
434 /// the specified index. If the archive number is outside the bounds of the
435 /// list, this method returns null.
436 /// <para>If the <see cref="EnableOffsetOpen"/> flag is set, this method will
437 /// seek to the start of any existing archive in the file, or to the end of
438 /// the file if the existing file is not an archive.</para>
439 /// </remarks>
440 public virtual Stream OpenArchiveReadStream(
441 int archiveNumber, string archiveName, CompressionEngine compressionEngine)
442 {
443 if (archiveNumber >= this.archiveFiles.Count)
444 {
445 return null;
446 }
447
448 string archiveFile = this.archiveFiles[archiveNumber];
449 Stream stream = File.Open(
450 archiveFile, FileMode.Open, FileAccess.Read, FileShare.Read);
451
452 if (this.enableOffsetOpen)
453 {
454 long offset = compressionEngine.FindArchiveOffset(
455 new DuplicateStream(stream));
456 if (offset > 0)
457 {
458 stream = new OffsetStream(stream, offset);
459 }
460 else
461 {
462 stream.Seek(0, SeekOrigin.Begin);
463 }
464 }
465
466 return stream;
467 }
468
469 /// <summary>
470 /// Closes a stream where an archive was read.
471 /// </summary>
472 /// <param name="archiveNumber">The archive number of the stream
473 /// to close.</param>
474 /// <param name="archiveName">The name of the archive being closed.</param>
475 /// <param name="stream">The stream that was previously returned by
476 /// <see cref="OpenArchiveReadStream"/> and is now ready to be closed.</param>
477 public virtual void CloseArchiveReadStream(
478 int archiveNumber, string archiveName, Stream stream)
479 {
480 if (stream != null)
481 {
482 stream.Close();
483 }
484 }
485
486 /// <summary>
487 /// Opens a stream for writing extracted file bytes.
488 /// </summary>
489 /// <param name="path">The path of the file within the archive.</param>
490 /// <param name="fileSize">The uncompressed size of the file to be
491 /// extracted.</param>
492 /// <param name="lastWriteTime">The last write time of the file to be
493 /// extracted.</param>
494 /// <returns>A stream where extracted file bytes are to be written, or null
495 /// to skip extraction of the file and continue to the next file.</returns>
496 /// <remarks>
497 /// This method opens a file using the following logic:
498 /// <list>
499 /// <item>If the <see cref="Directory"/> and the <see cref="Files"/> mapping
500 /// are both null, the path is treated as relative to the current directory,
501 /// and that file is opened.</item>
502 /// <item>If the <see cref="Directory"/> is not null but the <see cref="Files"/>
503 /// mapping is null, the path is treated as relative to that directory, and
504 /// that file is opened.</item>
505 /// <item>If the <see cref="Directory"/> is null but the <see cref="Files"/>
506 /// mapping is not null, the path parameter is used as a key into the mapping,
507 /// and the resulting value is the file path that is opened, relative to the
508 /// current directory (or it may be an absolute path). If no mapping exists,
509 /// the file is skipped.</item>
510 /// <item>If both the <see cref="Directory"/> and the <see cref="Files"/>
511 /// mapping are specified, the path parameter is used as a key into the
512 /// mapping, and the resulting value is the file path that is opened,
513 /// relative to the specified directory (or it may be an absolute path).
514 /// If no mapping exists, the file is skipped.</item>
515 /// </list>
516 /// <para>If the <see cref="ExtractOnlyNewerFiles"/> flag is set, the file
517 /// is skipped if a file currently exists in the same path with an equal
518 /// or newer write time.</para>
519 /// </remarks>
520 public virtual Stream OpenFileWriteStream(
521 string path,
522 long fileSize,
523 DateTime lastWriteTime)
524 {
525 string filePath = this.TranslateFilePath(path);
526
527 if (filePath == null)
528 {
529 return null;
530 }
531
532 FileInfo file = new FileInfo(filePath);
533 if (file.Exists)
534 {
535 if (this.extractOnlyNewerFiles && lastWriteTime != DateTime.MinValue)
536 {
537 if (file.LastWriteTime >= lastWriteTime)
538 {
539 return null;
540 }
541 }
542
543 // Clear attributes that will prevent overwriting the file.
544 // (The final attributes will be set after the file is unpacked.)
545 FileAttributes attributesToClear =
546 FileAttributes.ReadOnly | FileAttributes.Hidden | FileAttributes.System;
547 if ((file.Attributes & attributesToClear) != 0)
548 {
549 file.Attributes &= ~attributesToClear;
550 }
551 }
552
553 if (!file.Directory.Exists)
554 {
555 file.Directory.Create();
556 }
557
558 return File.Open(
559 filePath, FileMode.Create, FileAccess.Write, FileShare.None);
560 }
561
562 /// <summary>
563 /// Closes a stream where an extracted file was written.
564 /// </summary>
565 /// <param name="path">The path of the file within the archive.</param>
566 /// <param name="stream">The stream that was previously returned by
567 /// <see cref="OpenFileWriteStream"/> and is now ready to be closed.</param>
568 /// <param name="attributes">The attributes of the extracted file.</param>
569 /// <param name="lastWriteTime">The last write time of the file.</param>
570 /// <remarks>
571 /// After closing the extracted file stream, this method applies the date
572 /// and attributes to that file.
573 /// </remarks>
574 public virtual void CloseFileWriteStream(
575 string path,
576 Stream stream,
577 FileAttributes attributes,
578 DateTime lastWriteTime)
579 {
580 if (stream != null)
581 {
582 stream.Close();
583 }
584
585 string filePath = this.TranslateFilePath(path);
586 if (filePath != null)
587 {
588 FileInfo file = new FileInfo(filePath);
589
590 if (lastWriteTime != DateTime.MinValue)
591 {
592 try
593 {
594 file.LastWriteTime = lastWriteTime;
595 }
596 catch (ArgumentException)
597 {
598 }
599 catch (IOException)
600 {
601 }
602 }
603
604 try
605 {
606 file.Attributes = attributes;
607 }
608 catch (IOException)
609 {
610 }
611 }
612 }
613
614 #endregion
615
616 #region Private utility methods
617
618 /// <summary>
619 /// Translates an internal file path to an external file path using the
620 /// <see cref="Directory"/> and the <see cref="Files"/> mapping, according to
621 /// rules documented in <see cref="OpenFileReadStream"/> and
622 /// <see cref="OpenFileWriteStream"/>.
623 /// </summary>
624 /// <param name="path">The path of the file with the archive.</param>
625 /// <returns>The external path of the file, or null if there is no
626 /// valid translation.</returns>
627 private string TranslateFilePath(string path)
628 {
629 string filePath;
630 if (this.files != null)
631 {
632 filePath = this.files[path];
633 }
634 else
635 {
636 this.ValidateArchivePath(path);
637
638 filePath = path;
639 }
640
641 if (filePath != null)
642 {
643 if (this.directory != null)
644 {
645 filePath = Path.Combine(this.directory, filePath);
646 }
647 }
648
649 return filePath;
650 }
651
652 private void ValidateArchivePath(string filePath)
653 {
654 string basePath = Path.GetFullPath(String.IsNullOrEmpty(this.directory) ? Environment.CurrentDirectory : this.directory);
655 string path = Path.GetFullPath(Path.Combine(basePath, filePath));
656 if (!path.StartsWith(basePath, StringComparison.InvariantCultureIgnoreCase))
657 {
658 throw new InvalidDataException("Archive cannot contain files with absolute or traversal paths.");
659 }
660 }
661
662 #endregion
663 }
664}
diff --git a/src/dtf/WixToolset.Dtf.Compression/ArchiveInfo.cs b/src/dtf/WixToolset.Dtf.Compression/ArchiveInfo.cs
new file mode 100644
index 00000000..b5da4ea8
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression/ArchiveInfo.cs
@@ -0,0 +1,781 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression
4{
5 using System;
6 using System.IO;
7 using System.Collections.Generic;
8 using System.Globalization;
9 using System.Text;
10 using System.Text.RegularExpressions;
11 using System.Runtime.Serialization;
12 using System.Diagnostics.CodeAnalysis;
13
14 /// <summary>
15 /// Abstract object representing a compressed archive on disk;
16 /// provides access to file-based operations on the archive.
17 /// </summary>
18 [Serializable]
19 public abstract class ArchiveInfo : FileSystemInfo
20 {
21 /// <summary>
22 /// Creates a new ArchiveInfo object representing an archive in a
23 /// specified path.
24 /// </summary>
25 /// <param name="path">The path to the archive. When creating an archive,
26 /// this file does not necessarily exist yet.</param>
27 protected ArchiveInfo(string path) : base()
28 {
29 if (path == null)
30 {
31 throw new ArgumentNullException("path");
32 }
33
34 // protected instance members inherited from FileSystemInfo:
35 this.OriginalPath = path;
36 this.FullPath = Path.GetFullPath(path);
37 }
38
39 /// <summary>
40 /// Initializes a new instance of the ArchiveInfo class with serialized data.
41 /// </summary>
42 /// <param name="info">The SerializationInfo that holds the serialized object
43 /// data about the exception being thrown.</param>
44 /// <param name="context">The StreamingContext that contains contextual
45 /// information about the source or destination.</param>
46 protected ArchiveInfo(SerializationInfo info, StreamingContext context)
47 : base(info, context)
48 {
49 }
50
51 /// <summary>
52 /// Gets the directory that contains the archive.
53 /// </summary>
54 /// <value>A DirectoryInfo object representing the parent directory of the
55 /// archive.</value>
56 public DirectoryInfo Directory
57 {
58 get
59 {
60 return new DirectoryInfo(Path.GetDirectoryName(this.FullName));
61 }
62 }
63
64 /// <summary>
65 /// Gets the full path of the directory that contains the archive.
66 /// </summary>
67 /// <value>The full path of the directory that contains the archive.</value>
68 public string DirectoryName
69 {
70 get
71 {
72 return Path.GetDirectoryName(this.FullName);
73 }
74 }
75
76 /// <summary>
77 /// Gets the size of the archive.
78 /// </summary>
79 /// <value>The size of the archive in bytes.</value>
80 public long Length
81 {
82 get
83 {
84 return new FileInfo(this.FullName).Length;
85 }
86 }
87
88 /// <summary>
89 /// Gets the file name of the archive.
90 /// </summary>
91 /// <value>The file name of the archive, not including any path.</value>
92 public override string Name
93 {
94 get
95 {
96 return Path.GetFileName(this.FullName);
97 }
98 }
99
100 /// <summary>
101 /// Checks if the archive exists.
102 /// </summary>
103 /// <value>True if the archive exists; else false.</value>
104 public override bool Exists
105 {
106 get
107 {
108 return File.Exists(this.FullName);
109 }
110 }
111
112 /// <summary>
113 /// Gets the full path of the archive.
114 /// </summary>
115 /// <returns>The full path of the archive.</returns>
116 public override string ToString()
117 {
118 return this.FullName;
119 }
120
121 /// <summary>
122 /// Deletes the archive.
123 /// </summary>
124 public override void Delete()
125 {
126 File.Delete(this.FullName);
127 }
128
129 /// <summary>
130 /// Copies an existing archive to another location.
131 /// </summary>
132 /// <param name="destFileName">The destination file path.</param>
133 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
134 public void CopyTo(string destFileName)
135 {
136 File.Copy(this.FullName, destFileName);
137 }
138
139 /// <summary>
140 /// Copies an existing archive to another location, optionally
141 /// overwriting the destination file.
142 /// </summary>
143 /// <param name="destFileName">The destination file path.</param>
144 /// <param name="overwrite">If true, the destination file will be
145 /// overwritten if it exists.</param>
146 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
147 public void CopyTo(string destFileName, bool overwrite)
148 {
149 File.Copy(this.FullName, destFileName, overwrite);
150 }
151
152 /// <summary>
153 /// Moves an existing archive to another location.
154 /// </summary>
155 /// <param name="destFileName">The destination file path.</param>
156 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
157 public void MoveTo(string destFileName)
158 {
159 File.Move(this.FullName, destFileName);
160 this.FullPath = Path.GetFullPath(destFileName);
161 }
162
163 /// <summary>
164 /// Checks if the archive contains a valid archive header.
165 /// </summary>
166 /// <returns>True if the file is a valid archive; false otherwise.</returns>
167 public bool IsValid()
168 {
169 using (Stream stream = File.OpenRead(this.FullName))
170 {
171 using (CompressionEngine compressionEngine = this.CreateCompressionEngine())
172 {
173 return compressionEngine.FindArchiveOffset(stream) >= 0;
174 }
175 }
176 }
177
178 /// <summary>
179 /// Gets information about the files contained in the archive.
180 /// </summary>
181 /// <returns>A list of <see cref="ArchiveFileInfo"/> objects, each
182 /// containing information about a file in the archive.</returns>
183 public IList<ArchiveFileInfo> GetFiles()
184 {
185 return this.InternalGetFiles((Predicate<string>) null);
186 }
187
188 /// <summary>
189 /// Gets information about the certain files contained in the archive file.
190 /// </summary>
191 /// <param name="searchPattern">The search string, such as
192 /// &quot;*.txt&quot;.</param>
193 /// <returns>A list of <see cref="ArchiveFileInfo"/> objects, each containing
194 /// information about a file in the archive.</returns>
195 public IList<ArchiveFileInfo> GetFiles(string searchPattern)
196 {
197 if (searchPattern == null)
198 {
199 throw new ArgumentNullException("searchPattern");
200 }
201
202 string regexPattern = String.Format(
203 CultureInfo.InvariantCulture,
204 "^{0}$",
205 Regex.Escape(searchPattern).Replace("\\*", ".*").Replace("\\?", "."));
206 Regex regex = new Regex(
207 regexPattern,
208 RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
209
210 return this.InternalGetFiles(
211 delegate(string match)
212 {
213 return regex.IsMatch(match);
214 });
215 }
216
217 /// <summary>
218 /// Extracts all files from an archive to a destination directory.
219 /// </summary>
220 /// <param name="destDirectory">Directory where the files are to be
221 /// extracted.</param>
222 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
223 public void Unpack(string destDirectory)
224 {
225 this.Unpack(destDirectory, null);
226 }
227
228 /// <summary>
229 /// Extracts all files from an archive to a destination directory,
230 /// optionally extracting only newer files.
231 /// </summary>
232 /// <param name="destDirectory">Directory where the files are to be
233 /// extracted.</param>
234 /// <param name="progressHandler">Handler for receiving progress
235 /// information; this may be null if progress is not desired.</param>
236 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
237 public void Unpack(
238 string destDirectory,
239 EventHandler<ArchiveProgressEventArgs> progressHandler)
240 {
241 using (CompressionEngine compressionEngine = this.CreateCompressionEngine())
242 {
243 compressionEngine.Progress += progressHandler;
244 ArchiveFileStreamContext streamContext =
245 new ArchiveFileStreamContext(this.FullName, destDirectory, null);
246 streamContext.EnableOffsetOpen = true;
247 compressionEngine.Unpack(streamContext, null);
248 }
249 }
250
251 /// <summary>
252 /// Extracts a single file from the archive.
253 /// </summary>
254 /// <param name="fileName">The name of the file in the archive. Also
255 /// includes the internal path of the file, if any. File name matching
256 /// is case-insensitive.</param>
257 /// <param name="destFileName">The path where the file is to be
258 /// extracted on disk.</param>
259 /// <remarks>If <paramref name="destFileName"/> already exists,
260 /// it will be overwritten.</remarks>
261 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
262 public void UnpackFile(string fileName, string destFileName)
263 {
264 if (fileName == null)
265 {
266 throw new ArgumentNullException("fileName");
267 }
268
269 if (destFileName == null)
270 {
271 throw new ArgumentNullException("destFileName");
272 }
273
274 this.UnpackFiles(
275 new string[] { fileName },
276 null,
277 new string[] { destFileName });
278 }
279
280 /// <summary>
281 /// Extracts multiple files from the archive.
282 /// </summary>
283 /// <param name="fileNames">The names of the files in the archive.
284 /// Each name includes the internal path of the file, if any. File name
285 /// matching is case-insensitive.</param>
286 /// <param name="destDirectory">This parameter may be null, but if
287 /// specified it is the root directory for any relative paths in
288 /// <paramref name="destFileNames"/>.</param>
289 /// <param name="destFileNames">The paths where the files are to be
290 /// extracted on disk. If this parameter is null, the files will be
291 /// extracted with the names from the archive.</param>
292 /// <remarks>
293 /// If any extracted files already exist on disk, they will be overwritten.
294 /// <p>The <paramref name="destDirectory"/> and
295 /// <paramref name="destFileNames"/> parameters cannot both be null.</p>
296 /// </remarks>
297 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
298 public void UnpackFiles(
299 IList<string> fileNames,
300 string destDirectory,
301 IList<string> destFileNames)
302 {
303 this.UnpackFiles(fileNames, destDirectory, destFileNames, null);
304 }
305
306 /// <summary>
307 /// Extracts multiple files from the archive, optionally extracting
308 /// only newer files.
309 /// </summary>
310 /// <param name="fileNames">The names of the files in the archive.
311 /// Each name includes the internal path of the file, if any. File name
312 /// matching is case-insensitive.</param>
313 /// <param name="destDirectory">This parameter may be null, but if
314 /// specified it is the root directory for any relative paths in
315 /// <paramref name="destFileNames"/>.</param>
316 /// <param name="destFileNames">The paths where the files are to be
317 /// extracted on disk. If this parameter is null, the files will be
318 /// extracted with the names from the archive.</param>
319 /// <param name="progressHandler">Handler for receiving progress information;
320 /// this may be null if progress is not desired.</param>
321 /// <remarks>
322 /// If any extracted files already exist on disk, they will be overwritten.
323 /// <p>The <paramref name="destDirectory"/> and
324 /// <paramref name="destFileNames"/> parameters cannot both be null.</p>
325 /// </remarks>
326 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
327 public void UnpackFiles(
328 IList<string> fileNames,
329 string destDirectory,
330 IList<string> destFileNames,
331 EventHandler<ArchiveProgressEventArgs> progressHandler)
332 {
333 if (fileNames == null)
334 {
335 throw new ArgumentNullException("fileNames");
336 }
337
338 if (destFileNames == null)
339 {
340 if (destDirectory == null)
341 {
342 throw new ArgumentNullException("destFileNames");
343 }
344
345 destFileNames = fileNames;
346 }
347
348 if (destFileNames.Count != fileNames.Count)
349 {
350 throw new ArgumentOutOfRangeException("destFileNames");
351 }
352
353 IDictionary<string, string> files =
354 ArchiveInfo.CreateStringDictionary(fileNames, destFileNames);
355 this.UnpackFileSet(files, destDirectory, progressHandler);
356 }
357
358 /// <summary>
359 /// Extracts multiple files from the archive.
360 /// </summary>
361 /// <param name="fileNames">A mapping from internal file paths to
362 /// external file paths. Case-senstivity when matching internal paths
363 /// depends on the IDictionary implementation.</param>
364 /// <param name="destDirectory">This parameter may be null, but if
365 /// specified it is the root directory for any relative external paths
366 /// in <paramref name="fileNames"/>.</param>
367 /// <remarks>
368 /// If any extracted files already exist on disk, they will be overwritten.
369 /// </remarks>
370 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
371 public void UnpackFileSet(
372 IDictionary<string, string> fileNames,
373 string destDirectory)
374 {
375 this.UnpackFileSet(fileNames, destDirectory, null);
376 }
377
378 /// <summary>
379 /// Extracts multiple files from the archive.
380 /// </summary>
381 /// <param name="fileNames">A mapping from internal file paths to
382 /// external file paths. Case-senstivity when matching internal
383 /// paths depends on the IDictionary implementation.</param>
384 /// <param name="destDirectory">This parameter may be null, but if
385 /// specified it is the root directory for any relative external
386 /// paths in <paramref name="fileNames"/>.</param>
387 /// <param name="progressHandler">Handler for receiving progress
388 /// information; this may be null if progress is not desired.</param>
389 /// <remarks>
390 /// If any extracted files already exist on disk, they will be overwritten.
391 /// </remarks>
392 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
393 public void UnpackFileSet(
394 IDictionary<string, string> fileNames,
395 string destDirectory,
396 EventHandler<ArchiveProgressEventArgs> progressHandler)
397 {
398 if (fileNames == null)
399 {
400 throw new ArgumentNullException("fileNames");
401 }
402
403 using (CompressionEngine compressionEngine = this.CreateCompressionEngine())
404 {
405 compressionEngine.Progress += progressHandler;
406 ArchiveFileStreamContext streamContext =
407 new ArchiveFileStreamContext(this.FullName, destDirectory, fileNames);
408 streamContext.EnableOffsetOpen = true;
409 compressionEngine.Unpack(
410 streamContext,
411 delegate(string match)
412 {
413 return fileNames.ContainsKey(match);
414 });
415 }
416 }
417
418 /// <summary>
419 /// Opens a file inside the archive for reading without actually
420 /// extracting the file to disk.
421 /// </summary>
422 /// <param name="fileName">The name of the file in the archive. Also
423 /// includes the internal path of the file, if any. File name matching
424 /// is case-insensitive.</param>
425 /// <returns>
426 /// A stream for reading directly from the packed file. Like any stream
427 /// this should be closed/disposed as soon as it is no longer needed.
428 /// </returns>
429 public Stream OpenRead(string fileName)
430 {
431 Stream archiveStream = File.OpenRead(this.FullName);
432 CompressionEngine compressionEngine = this.CreateCompressionEngine();
433 Stream fileStream = compressionEngine.Unpack(archiveStream, fileName);
434
435 // Attach the archiveStream and compressionEngine to the
436 // fileStream so they get disposed when the fileStream is disposed.
437 return new CargoStream(fileStream, archiveStream, compressionEngine);
438 }
439
440 /// <summary>
441 /// Opens a file inside the archive for reading text with UTF-8 encoding
442 /// without actually extracting the file to disk.
443 /// </summary>
444 /// <param name="fileName">The name of the file in the archive. Also
445 /// includes the internal path of the file, if any. File name matching
446 /// is case-insensitive.</param>
447 /// <returns>
448 /// A reader for reading text directly from the packed file. Like any reader
449 /// this should be closed/disposed as soon as it is no longer needed.
450 /// </returns>
451 /// <remarks>
452 /// To open an archived text file with different encoding, use the
453 /// <see cref="OpenRead" /> method and pass the returned stream to one of
454 /// the <see cref="StreamReader" /> constructor overloads.
455 /// </remarks>
456 public StreamReader OpenText(string fileName)
457 {
458 return new StreamReader(this.OpenRead(fileName));
459 }
460
461 /// <summary>
462 /// Compresses all files in a directory into the archive.
463 /// Does not include subdirectories.
464 /// </summary>
465 /// <param name="sourceDirectory">The directory containing the
466 /// files to be included.</param>
467 /// <remarks>
468 /// Uses maximum compression level.
469 /// </remarks>
470 public void Pack(string sourceDirectory)
471 {
472 this.Pack(sourceDirectory, false, CompressionLevel.Max, null);
473 }
474
475 /// <summary>
476 /// Compresses all files in a directory into the archive, optionally
477 /// including subdirectories.
478 /// </summary>
479 /// <param name="sourceDirectory">This is the root directory
480 /// for to pack all files.</param>
481 /// <param name="includeSubdirectories">If true, recursively include
482 /// files in subdirectories.</param>
483 /// <param name="compLevel">The compression level used when creating
484 /// the archive.</param>
485 /// <param name="progressHandler">Handler for receiving progress information;
486 /// this may be null if progress is not desired.</param>
487 /// <remarks>
488 /// The files are stored in the archive using their relative file paths in
489 /// the directory tree, if supported by the archive file format.
490 /// </remarks>
491 public void Pack(
492 string sourceDirectory,
493 bool includeSubdirectories,
494 CompressionLevel compLevel,
495 EventHandler<ArchiveProgressEventArgs> progressHandler)
496 {
497 IList<string> files = this.GetRelativeFilePathsInDirectoryTree(
498 sourceDirectory, includeSubdirectories);
499 this.PackFiles(sourceDirectory, files, files, compLevel, progressHandler);
500 }
501
502 /// <summary>
503 /// Compresses files into the archive, specifying the names used to
504 /// store the files in the archive.
505 /// </summary>
506 /// <param name="sourceDirectory">This parameter may be null, but
507 /// if specified it is the root directory
508 /// for any relative paths in <paramref name="sourceFileNames"/>.</param>
509 /// <param name="sourceFileNames">The list of files to be included in
510 /// the archive.</param>
511 /// <param name="fileNames">The names of the files as they are stored
512 /// in the archive. Each name
513 /// includes the internal path of the file, if any. This parameter may
514 /// be null, in which case the files are stored in the archive with their
515 /// source file names and no path information.</param>
516 /// <remarks>
517 /// Uses maximum compression level.
518 /// <p>Duplicate items in the <paramref name="fileNames"/> array will cause
519 /// an <see cref="ArchiveException"/>.</p>
520 /// </remarks>
521 public void PackFiles(
522 string sourceDirectory,
523 IList<string> sourceFileNames,
524 IList<string> fileNames)
525 {
526 this.PackFiles(
527 sourceDirectory,
528 sourceFileNames,
529 fileNames,
530 CompressionLevel.Max,
531 null);
532 }
533
534 /// <summary>
535 /// Compresses files into the archive, specifying the names used to
536 /// store the files in the archive.
537 /// </summary>
538 /// <param name="sourceDirectory">This parameter may be null, but if
539 /// specified it is the root directory
540 /// for any relative paths in <paramref name="sourceFileNames"/>.</param>
541 /// <param name="sourceFileNames">The list of files to be included in
542 /// the archive.</param>
543 /// <param name="fileNames">The names of the files as they are stored in
544 /// the archive. Each name includes the internal path of the file, if any.
545 /// This parameter may be null, in which case the files are stored in the
546 /// archive with their source file names and no path information.</param>
547 /// <param name="compLevel">The compression level used when creating the
548 /// archive.</param>
549 /// <param name="progressHandler">Handler for receiving progress information;
550 /// this may be null if progress is not desired.</param>
551 /// <remarks>
552 /// Duplicate items in the <paramref name="fileNames"/> array will cause
553 /// an <see cref="ArchiveException"/>.
554 /// </remarks>
555 public void PackFiles(
556 string sourceDirectory,
557 IList<string> sourceFileNames,
558 IList<string> fileNames,
559 CompressionLevel compLevel,
560 EventHandler<ArchiveProgressEventArgs> progressHandler)
561 {
562 if (sourceFileNames == null)
563 {
564 throw new ArgumentNullException("sourceFileNames");
565 }
566
567 if (fileNames == null)
568 {
569 string[] fileNamesArray = new string[sourceFileNames.Count];
570 for (int i = 0; i < sourceFileNames.Count; i++)
571 {
572 fileNamesArray[i] = Path.GetFileName(sourceFileNames[i]);
573 }
574
575 fileNames = fileNamesArray;
576 }
577 else if (fileNames.Count != sourceFileNames.Count)
578 {
579 throw new ArgumentOutOfRangeException("fileNames");
580 }
581
582 using (CompressionEngine compressionEngine = this.CreateCompressionEngine())
583 {
584 compressionEngine.Progress += progressHandler;
585 IDictionary<string, string> contextFiles =
586 ArchiveInfo.CreateStringDictionary(fileNames, sourceFileNames);
587 ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext(
588 this.FullName, sourceDirectory, contextFiles);
589 streamContext.EnableOffsetOpen = true;
590 compressionEngine.CompressionLevel = compLevel;
591 compressionEngine.Pack(streamContext, fileNames);
592 }
593 }
594
595 /// <summary>
596 /// Compresses files into the archive, specifying the names used
597 /// to store the files in the archive.
598 /// </summary>
599 /// <param name="sourceDirectory">This parameter may be null, but if
600 /// specified it is the root directory
601 /// for any relative paths in <paramref name="fileNames"/>.</param>
602 /// <param name="fileNames">A mapping from internal file paths to
603 /// external file paths.</param>
604 /// <remarks>
605 /// Uses maximum compression level.
606 /// </remarks>
607 public void PackFileSet(
608 string sourceDirectory,
609 IDictionary<string, string> fileNames)
610 {
611 this.PackFileSet(sourceDirectory, fileNames, CompressionLevel.Max, null);
612 }
613
614 /// <summary>
615 /// Compresses files into the archive, specifying the names used to
616 /// store the files in the archive.
617 /// </summary>
618 /// <param name="sourceDirectory">This parameter may be null, but if
619 /// specified it is the root directory
620 /// for any relative paths in <paramref name="fileNames"/>.</param>
621 /// <param name="fileNames">A mapping from internal file paths to
622 /// external file paths.</param>
623 /// <param name="compLevel">The compression level used when creating
624 /// the archive.</param>
625 /// <param name="progressHandler">Handler for receiving progress information;
626 /// this may be null if progress is not desired.</param>
627 public void PackFileSet(
628 string sourceDirectory,
629 IDictionary<string, string> fileNames,
630 CompressionLevel compLevel,
631 EventHandler<ArchiveProgressEventArgs> progressHandler)
632 {
633 if (fileNames == null)
634 {
635 throw new ArgumentNullException("fileNames");
636 }
637
638 string[] fileNamesArray = new string[fileNames.Count];
639 fileNames.Keys.CopyTo(fileNamesArray, 0);
640
641 using (CompressionEngine compressionEngine = this.CreateCompressionEngine())
642 {
643 compressionEngine.Progress += progressHandler;
644 ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext(
645 this.FullName, sourceDirectory, fileNames);
646 streamContext.EnableOffsetOpen = true;
647 compressionEngine.CompressionLevel = compLevel;
648 compressionEngine.Pack(streamContext, fileNamesArray);
649 }
650 }
651
652 /// <summary>
653 /// Given a directory, gets the relative paths of all files in the
654 /// directory, optionally including all subdirectories.
655 /// </summary>
656 /// <param name="dir">The directory to search.</param>
657 /// <param name="includeSubdirectories">True to include subdirectories
658 /// in the search.</param>
659 /// <returns>A list of file paths relative to the directory.</returns>
660 internal IList<string> GetRelativeFilePathsInDirectoryTree(
661 string dir, bool includeSubdirectories)
662 {
663 IList<string> fileList = new List<string>();
664 this.RecursiveGetRelativeFilePathsInDirectoryTree(
665 dir, String.Empty, includeSubdirectories, fileList);
666 return fileList;
667 }
668
669 /// <summary>
670 /// Retrieves information about one file from this archive.
671 /// </summary>
672 /// <param name="path">Path of the file in the archive.</param>
673 /// <returns>File information, or null if the file was not found
674 /// in the archive.</returns>
675 internal ArchiveFileInfo GetFile(string path)
676 {
677 IList<ArchiveFileInfo> files = this.InternalGetFiles(
678 delegate(string match)
679 {
680 return String.Compare(
681 match, path, true, CultureInfo.InvariantCulture) == 0;
682 });
683 return (files != null && files.Count > 0 ? files[0] : null);
684 }
685
686 /// <summary>
687 /// Creates a compression engine that does the low-level work for
688 /// this object.
689 /// </summary>
690 /// <returns>A new compression engine instance that matches the specific
691 /// subclass of archive.</returns>
692 /// <remarks>
693 /// Each instance will be <see cref="CompressionEngine.Dispose()"/>d
694 /// immediately after use.
695 /// </remarks>
696 protected abstract CompressionEngine CreateCompressionEngine();
697
698 /// <summary>
699 /// Creates a case-insensitive dictionary mapping from one list of
700 /// strings to the other.
701 /// </summary>
702 /// <param name="keys">List of keys.</param>
703 /// <param name="values">List of values that are mapped 1-to-1 to
704 /// the keys.</param>
705 /// <returns>A filled dictionary of the strings.</returns>
706 private static IDictionary<string, string> CreateStringDictionary(
707 IList<string> keys, IList<string> values)
708 {
709 IDictionary<string, string> stringDict =
710 new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
711 for (int i = 0; i < keys.Count; i++)
712 {
713 stringDict.Add(keys[i], values[i]);
714 }
715
716 return stringDict;
717 }
718
719 /// <summary>
720 /// Recursive-descent helper function for
721 /// GetRelativeFilePathsInDirectoryTree.
722 /// </summary>
723 /// <param name="dir">The root directory of the search.</param>
724 /// <param name="relativeDir">The relative directory to be
725 /// processed now.</param>
726 /// <param name="includeSubdirectories">True to descend into
727 /// subdirectories.</param>
728 /// <param name="fileList">List of files found so far.</param>
729 private void RecursiveGetRelativeFilePathsInDirectoryTree(
730 string dir,
731 string relativeDir,
732 bool includeSubdirectories,
733 IList<string> fileList)
734 {
735 foreach (string file in System.IO.Directory.GetFiles(dir))
736 {
737 string fileName = Path.GetFileName(file);
738 fileList.Add(Path.Combine(relativeDir, fileName));
739 }
740
741 if (includeSubdirectories)
742 {
743 foreach (string subDir in System.IO.Directory.GetDirectories(dir))
744 {
745 string subDirName = Path.GetFileName(subDir);
746 this.RecursiveGetRelativeFilePathsInDirectoryTree(
747 Path.Combine(dir, subDirName),
748 Path.Combine(relativeDir, subDirName),
749 includeSubdirectories,
750 fileList);
751 }
752 }
753 }
754
755 /// <summary>
756 /// Uses a CompressionEngine to get ArchiveFileInfo objects from this
757 /// archive, and then associates them with this ArchiveInfo instance.
758 /// </summary>
759 /// <param name="fileFilter">Optional predicate that can determine
760 /// which files to process.</param>
761 /// <returns>A list of <see cref="ArchiveFileInfo"/> objects, each
762 /// containing information about a file in the archive.</returns>
763 private IList<ArchiveFileInfo> InternalGetFiles(Predicate<string> fileFilter)
764 {
765 using (CompressionEngine compressionEngine = this.CreateCompressionEngine())
766 {
767 ArchiveFileStreamContext streamContext =
768 new ArchiveFileStreamContext(this.FullName, null, null);
769 streamContext.EnableOffsetOpen = true;
770 IList<ArchiveFileInfo> files =
771 compressionEngine.GetFileInfo(streamContext, fileFilter);
772 for (int i = 0; i < files.Count; i++)
773 {
774 files[i].Archive = this;
775 }
776
777 return files;
778 }
779 }
780 }
781}
diff --git a/src/dtf/WixToolset.Dtf.Compression/ArchiveProgressEventArgs.cs b/src/dtf/WixToolset.Dtf.Compression/ArchiveProgressEventArgs.cs
new file mode 100644
index 00000000..5d96d714
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression/ArchiveProgressEventArgs.cs
@@ -0,0 +1,307 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression
4{
5using System;
6using System.Collections.Generic;
7using System.Text;
8
9 /// <summary>
10 /// Contains the data reported in an archive progress event.
11 /// </summary>
12 public class ArchiveProgressEventArgs : EventArgs
13 {
14 private ArchiveProgressType progressType;
15
16 private string currentFileName;
17 private int currentFileNumber;
18 private int totalFiles;
19 private long currentFileBytesProcessed;
20 private long currentFileTotalBytes;
21
22 private string currentArchiveName;
23 private short currentArchiveNumber;
24 private short totalArchives;
25 private long currentArchiveBytesProcessed;
26 private long currentArchiveTotalBytes;
27
28 private long fileBytesProcessed;
29 private long totalFileBytes;
30
31 /// <summary>
32 /// Creates a new ArchiveProgressEventArgs object from specified event parameters.
33 /// </summary>
34 /// <param name="progressType">type of status message</param>
35 /// <param name="currentFileName">name of the file being processed</param>
36 /// <param name="currentFileNumber">number of the current file being processed</param>
37 /// <param name="totalFiles">total number of files to be processed</param>
38 /// <param name="currentFileBytesProcessed">number of bytes processed so far when compressing or extracting a file</param>
39 /// <param name="currentFileTotalBytes">total number of bytes in the current file</param>
40 /// <param name="currentArchiveName">name of the current Archive</param>
41 /// <param name="currentArchiveNumber">current Archive number, when processing a chained set of Archives</param>
42 /// <param name="totalArchives">total number of Archives in a chained set</param>
43 /// <param name="currentArchiveBytesProcessed">number of compressed bytes processed so far during an extraction</param>
44 /// <param name="currentArchiveTotalBytes">total number of compressed bytes to be processed during an extraction</param>
45 /// <param name="fileBytesProcessed">number of uncompressed file bytes processed so far</param>
46 /// <param name="totalFileBytes">total number of uncompressed file bytes to be processed</param>
47 public ArchiveProgressEventArgs(
48 ArchiveProgressType progressType,
49 string currentFileName,
50 int currentFileNumber,
51 int totalFiles,
52 long currentFileBytesProcessed,
53 long currentFileTotalBytes,
54 string currentArchiveName,
55 int currentArchiveNumber,
56 int totalArchives,
57 long currentArchiveBytesProcessed,
58 long currentArchiveTotalBytes,
59 long fileBytesProcessed,
60 long totalFileBytes)
61 {
62 this.progressType = progressType;
63 this.currentFileName = currentFileName;
64 this.currentFileNumber = currentFileNumber;
65 this.totalFiles = totalFiles;
66 this.currentFileBytesProcessed = currentFileBytesProcessed;
67 this.currentFileTotalBytes = currentFileTotalBytes;
68 this.currentArchiveName = currentArchiveName;
69 this.currentArchiveNumber = (short) currentArchiveNumber;
70 this.totalArchives = (short) totalArchives;
71 this.currentArchiveBytesProcessed = currentArchiveBytesProcessed;
72 this.currentArchiveTotalBytes = currentArchiveTotalBytes;
73 this.fileBytesProcessed = fileBytesProcessed;
74 this.totalFileBytes = totalFileBytes;
75 }
76
77 /// <summary>
78 /// Gets the type of status message.
79 /// </summary>
80 /// <value>A <see cref="ArchiveProgressType"/> value indicating what type of progress event occurred.</value>
81 /// <remarks>
82 /// The handler may choose to ignore some types of progress events.
83 /// For example, if the handler will only list each file as it is
84 /// compressed/extracted, it can ignore events that
85 /// are not of type <see cref="ArchiveProgressType.FinishFile"/>.
86 /// </remarks>
87 public ArchiveProgressType ProgressType
88 {
89 get
90 {
91 return this.progressType;
92 }
93 }
94
95 /// <summary>
96 /// Gets the name of the file being processed. (The name of the file within the Archive; not the external
97 /// file path.) Also includes the internal path of the file, if any. Valid for
98 /// <see cref="ArchiveProgressType.StartFile"/>, <see cref="ArchiveProgressType.PartialFile"/>,
99 /// and <see cref="ArchiveProgressType.FinishFile"/> messages.
100 /// </summary>
101 /// <value>The name of the file currently being processed, or null if processing
102 /// is currently at the stream or archive level.</value>
103 public string CurrentFileName
104 {
105 get
106 {
107 return this.currentFileName;
108 }
109 }
110
111 /// <summary>
112 /// Gets the number of the current file being processed. The first file is number 0, and the last file
113 /// is <see cref="TotalFiles"/>-1. Valid for <see cref="ArchiveProgressType.StartFile"/>,
114 /// <see cref="ArchiveProgressType.PartialFile"/>, and <see cref="ArchiveProgressType.FinishFile"/> messages.
115 /// </summary>
116 /// <value>The number of the file currently being processed, or the most recent
117 /// file processed if processing is currently at the stream or archive level.</value>
118 public int CurrentFileNumber
119 {
120 get
121 {
122 return this.currentFileNumber;
123 }
124 }
125
126 /// <summary>
127 /// Gets the total number of files to be processed. Valid for all message types.
128 /// </summary>
129 /// <value>The total number of files to be processed that are known so far.</value>
130 public int TotalFiles
131 {
132 get
133 {
134 return this.totalFiles;
135 }
136 }
137
138 /// <summary>
139 /// Gets the number of bytes processed so far when compressing or extracting a file. Valid for
140 /// <see cref="ArchiveProgressType.StartFile"/>, <see cref="ArchiveProgressType.PartialFile"/>,
141 /// and <see cref="ArchiveProgressType.FinishFile"/> messages.
142 /// </summary>
143 /// <value>The number of uncompressed bytes processed so far for the current file,
144 /// or 0 if processing is currently at the stream or archive level.</value>
145 public long CurrentFileBytesProcessed
146 {
147 get
148 {
149 return this.currentFileBytesProcessed;
150 }
151 }
152
153 /// <summary>
154 /// Gets the total number of bytes in the current file. Valid for <see cref="ArchiveProgressType.StartFile"/>,
155 /// <see cref="ArchiveProgressType.PartialFile"/>, and <see cref="ArchiveProgressType.FinishFile"/> messages.
156 /// </summary>
157 /// <value>The uncompressed size of the current file being processed,
158 /// or 0 if processing is currently at the stream or archive level.</value>
159 public long CurrentFileTotalBytes
160 {
161 get
162 {
163 return this.currentFileTotalBytes;
164 }
165 }
166
167 /// <summary>
168 /// Gets the name of the current archive. Not necessarily the name of the archive on disk.
169 /// Valid for all message types.
170 /// </summary>
171 /// <value>The name of the current archive, or an empty string if no name was specified.</value>
172 public string CurrentArchiveName
173 {
174 get
175 {
176 return this.currentArchiveName;
177 }
178 }
179
180 /// <summary>
181 /// Gets the current archive number, when processing a chained set of archives. Valid for all message types.
182 /// </summary>
183 /// <value>The number of the current archive.</value>
184 /// <remarks>The first archive is number 0, and the last archive is
185 /// <see cref="TotalArchives"/>-1.</remarks>
186 public int CurrentArchiveNumber
187 {
188 get
189 {
190 return this.currentArchiveNumber;
191 }
192 }
193
194 /// <summary>
195 /// Gets the total number of known archives in a chained set. Valid for all message types.
196 /// </summary>
197 /// <value>The total number of known archives in a chained set.</value>
198 /// <remarks>
199 /// When using the compression option to auto-split into multiple archives based on data size,
200 /// this value will not be accurate until the end.
201 /// </remarks>
202 public int TotalArchives
203 {
204 get
205 {
206 return this.totalArchives;
207 }
208 }
209
210 /// <summary>
211 /// Gets the number of compressed bytes processed so far during extraction
212 /// of the current archive. Valid for all extraction messages.
213 /// </summary>
214 /// <value>The number of compressed bytes processed so far during extraction
215 /// of the current archive.</value>
216 public long CurrentArchiveBytesProcessed
217 {
218 get
219 {
220 return this.currentArchiveBytesProcessed;
221 }
222 }
223
224 /// <summary>
225 /// Gets the total number of compressed bytes to be processed during extraction
226 /// of the current archive. Valid for all extraction messages.
227 /// </summary>
228 /// <value>The total number of compressed bytes to be processed during extraction
229 /// of the current archive.</value>
230 public long CurrentArchiveTotalBytes
231 {
232 get
233 {
234 return this.currentArchiveTotalBytes;
235 }
236 }
237
238 /// <summary>
239 /// Gets the number of uncompressed bytes processed so far among all files. Valid for all message types.
240 /// </summary>
241 /// <value>The number of uncompressed file bytes processed so far among all files.</value>
242 /// <remarks>
243 /// When compared to <see cref="TotalFileBytes"/>, this can be used as a measure of overall progress.
244 /// </remarks>
245 public long FileBytesProcessed
246 {
247 get
248 {
249 return this.fileBytesProcessed;
250 }
251 }
252
253 /// <summary>
254 /// Gets the total number of uncompressed file bytes to be processed. Valid for all message types.
255 /// </summary>
256 /// <value>The total number of uncompressed bytes to be processed among all files.</value>
257 public long TotalFileBytes
258 {
259 get
260 {
261 return this.totalFileBytes;
262 }
263 }
264
265#if DEBUG
266
267 /// <summary>
268 /// Creates a string representation of the progress event.
269 /// </summary>
270 /// <returns>a listing of all event parameters and values</returns>
271 public override string ToString()
272 {
273 string formatString =
274 "{0}\n" +
275 "\t CurrentFileName = {1}\n" +
276 "\t CurrentFileNumber = {2}\n" +
277 "\t TotalFiles = {3}\n" +
278 "\t CurrentFileBytesProcessed = {4}\n" +
279 "\t CurrentFileTotalBytes = {5}\n" +
280 "\t CurrentArchiveName = {6}\n" +
281 "\t CurrentArchiveNumber = {7}\n" +
282 "\t TotalArchives = {8}\n" +
283 "\t CurrentArchiveBytesProcessed = {9}\n" +
284 "\t CurrentArchiveTotalBytes = {10}\n" +
285 "\t FileBytesProcessed = {11}\n" +
286 "\t TotalFileBytes = {12}\n";
287 return String.Format(
288 System.Globalization.CultureInfo.InvariantCulture,
289 formatString,
290 this.ProgressType,
291 this.CurrentFileName,
292 this.CurrentFileNumber,
293 this.TotalFiles,
294 this.CurrentFileBytesProcessed,
295 this.CurrentFileTotalBytes,
296 this.CurrentArchiveName,
297 this.CurrentArchiveNumber,
298 this.TotalArchives,
299 this.CurrentArchiveBytesProcessed,
300 this.CurrentArchiveTotalBytes,
301 this.FileBytesProcessed,
302 this.TotalFileBytes);
303 }
304
305#endif
306 }
307}
diff --git a/src/dtf/WixToolset.Dtf.Compression/ArchiveProgressType.cs b/src/dtf/WixToolset.Dtf.Compression/ArchiveProgressType.cs
new file mode 100644
index 00000000..2307c28e
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression/ArchiveProgressType.cs
@@ -0,0 +1,69 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression
4{
5using System;
6using System.Collections.Generic;
7using System.Text;
8
9 /// <summary>
10 /// The type of progress event.
11 /// </summary>
12 /// <remarks>
13 /// <p>PACKING EXAMPLE: The following sequence of events might be received when
14 /// extracting a simple archive file with 2 files.</p>
15 /// <list type="table">
16 /// <listheader><term>Message Type</term><description>Description</description></listheader>
17 /// <item><term>StartArchive</term> <description>Begin extracting archive</description></item>
18 /// <item><term>StartFile</term> <description>Begin extracting first file</description></item>
19 /// <item><term>PartialFile</term> <description>Extracting first file</description></item>
20 /// <item><term>PartialFile</term> <description>Extracting first file</description></item>
21 /// <item><term>FinishFile</term> <description>Finished extracting first file</description></item>
22 /// <item><term>StartFile</term> <description>Begin extracting second file</description></item>
23 /// <item><term>PartialFile</term> <description>Extracting second file</description></item>
24 /// <item><term>FinishFile</term> <description>Finished extracting second file</description></item>
25 /// <item><term>FinishArchive</term><description>Finished extracting archive</description></item>
26 /// </list>
27 /// <p></p>
28 /// <p>UNPACKING EXAMPLE: Packing 3 files into 2 archive chunks, where the second file is
29 /// continued to the second archive chunk.</p>
30 /// <list type="table">
31 /// <listheader><term>Message Type</term><description>Description</description></listheader>
32 /// <item><term>StartFile</term> <description>Begin compressing first file</description></item>
33 /// <item><term>FinishFile</term> <description>Finished compressing first file</description></item>
34 /// <item><term>StartFile</term> <description>Begin compressing second file</description></item>
35 /// <item><term>PartialFile</term> <description>Compressing second file</description></item>
36 /// <item><term>PartialFile</term> <description>Compressing second file</description></item>
37 /// <item><term>FinishFile</term> <description>Finished compressing second file</description></item>
38 /// <item><term>StartArchive</term> <description>Begin writing first archive</description></item>
39 /// <item><term>PartialArchive</term><description>Writing first archive</description></item>
40 /// <item><term>FinishArchive</term> <description>Finished writing first archive</description></item>
41 /// <item><term>StartFile</term> <description>Begin compressing third file</description></item>
42 /// <item><term>PartialFile</term> <description>Compressing third file</description></item>
43 /// <item><term>FinishFile</term> <description>Finished compressing third file</description></item>
44 /// <item><term>StartArchive</term> <description>Begin writing second archive</description></item>
45 /// <item><term>PartialArchive</term><description>Writing second archive</description></item>
46 /// <item><term>FinishArchive</term> <description>Finished writing second archive</description></item>
47 /// </list>
48 /// </remarks>
49 public enum ArchiveProgressType : int
50 {
51 /// <summary>Status message before beginning the packing or unpacking an individual file.</summary>
52 StartFile,
53
54 /// <summary>Status message (possibly reported multiple times) during the process of packing or unpacking a file.</summary>
55 PartialFile,
56
57 /// <summary>Status message after completion of the packing or unpacking an individual file.</summary>
58 FinishFile,
59
60 /// <summary>Status message before beginning the packing or unpacking an archive.</summary>
61 StartArchive,
62
63 /// <summary>Status message (possibly reported multiple times) during the process of packing or unpacking an archiv.</summary>
64 PartialArchive,
65
66 /// <summary>Status message after completion of the packing or unpacking of an archive.</summary>
67 FinishArchive,
68 }
69}
diff --git a/src/dtf/WixToolset.Dtf.Compression/BasicUnpackStreamContext.cs b/src/dtf/WixToolset.Dtf.Compression/BasicUnpackStreamContext.cs
new file mode 100644
index 00000000..94d13b9c
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression/BasicUnpackStreamContext.cs
@@ -0,0 +1,90 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression
4{
5 using System;
6 using System.IO;
7 using System.Diagnostics.CodeAnalysis;
8
9 /// <summary>
10 /// Stream context used to extract a single file from an archive into a memory stream.
11 /// </summary>
12 [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
13 public class BasicUnpackStreamContext : IUnpackStreamContext
14 {
15 private Stream archiveStream;
16 private Stream fileStream;
17
18 /// <summary>
19 /// Creates a new BasicExtractStreamContext that reads from the specified archive stream.
20 /// </summary>
21 /// <param name="archiveStream">Archive stream to read from.</param>
22 public BasicUnpackStreamContext(Stream archiveStream)
23 {
24 this.archiveStream = archiveStream;
25 }
26
27 /// <summary>
28 /// Gets the stream for the extracted file, or null if no file was extracted.
29 /// </summary>
30 public Stream FileStream
31 {
32 get
33 {
34 return this.fileStream;
35 }
36 }
37
38 /// <summary>
39 /// Opens the archive stream for reading. Returns a DuplicateStream instance,
40 /// so the stream may be virtually opened multiple times.
41 /// </summary>
42 /// <param name="archiveNumber">The archive number to open (ignored; 0 is assumed).</param>
43 /// <param name="archiveName">The name of the archive being opened.</param>
44 /// <param name="compressionEngine">Instance of the compression engine doing the operations.</param>
45 /// <returns>A stream from which archive bytes are read.</returns>
46 public Stream OpenArchiveReadStream(int archiveNumber, string archiveName, CompressionEngine compressionEngine)
47 {
48 return new DuplicateStream(this.archiveStream);
49 }
50
51 /// <summary>
52 /// Does *not* close the stream. The archive stream should be managed by
53 /// the code that invokes the archive extraction.
54 /// </summary>
55 /// <param name="archiveNumber">The archive number of the stream to close.</param>
56 /// <param name="archiveName">The name of the archive being closed.</param>
57 /// <param name="stream">The stream being closed.</param>
58 public void CloseArchiveReadStream(int archiveNumber, string archiveName, Stream stream)
59 {
60 // Do nothing.
61 }
62
63 /// <summary>
64 /// Opens a stream for writing extracted file bytes. The returned stream is a MemoryStream
65 /// instance, so the file is extracted straight into memory.
66 /// </summary>
67 /// <param name="path">Path of the file within the archive.</param>
68 /// <param name="fileSize">The uncompressed size of the file to be extracted.</param>
69 /// <param name="lastWriteTime">The last write time of the file.</param>
70 /// <returns>A stream where extracted file bytes are to be written.</returns>
71 public Stream OpenFileWriteStream(string path, long fileSize, DateTime lastWriteTime)
72 {
73 this.fileStream = new MemoryStream(new byte[fileSize], 0, (int) fileSize, true, true);
74 return this.fileStream;
75 }
76
77 /// <summary>
78 /// Does *not* close the file stream. The file stream is saved in memory so it can
79 /// be read later.
80 /// </summary>
81 /// <param name="path">Path of the file within the archive.</param>
82 /// <param name="stream">The file stream to be closed.</param>
83 /// <param name="attributes">The attributes of the extracted file.</param>
84 /// <param name="lastWriteTime">The last write time of the file.</param>
85 public void CloseFileWriteStream(string path, Stream stream, FileAttributes attributes, DateTime lastWriteTime)
86 {
87 // Do nothing.
88 }
89 }
90}
diff --git a/src/dtf/WixToolset.Dtf.Compression/CargoStream.cs b/src/dtf/WixToolset.Dtf.Compression/CargoStream.cs
new file mode 100644
index 00000000..78798a35
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression/CargoStream.cs
@@ -0,0 +1,192 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8
9 /// <summary>
10 /// Wraps a source stream and carries additional items that are disposed when the stream is closed.
11 /// </summary>
12 public class CargoStream : Stream
13 {
14 private Stream source;
15 private List<IDisposable> cargo;
16
17 /// <summary>
18 /// Creates a new a cargo stream.
19 /// </summary>
20 /// <param name="source">source of the stream</param>
21 /// <param name="cargo">List of additional items that are disposed when the stream is closed.
22 /// The order of the list is the order in which the items are disposed.</param>
23 public CargoStream(Stream source, params IDisposable[] cargo)
24 {
25 if (source == null)
26 {
27 throw new ArgumentNullException("source");
28 }
29
30 this.source = source;
31 this.cargo = new List<IDisposable>(cargo);
32 }
33
34 /// <summary>
35 /// Gets the source stream of the cargo stream.
36 /// </summary>
37 public Stream Source
38 {
39 get
40 {
41 return this.source;
42 }
43 }
44
45 /// <summary>
46 /// Gets the list of additional items that are disposed when the stream is closed.
47 /// The order of the list is the order in which the items are disposed. The contents can be modified any time.
48 /// </summary>
49 public IList<IDisposable> Cargo
50 {
51 get
52 {
53 return this.cargo;
54 }
55 }
56
57 /// <summary>
58 /// Gets a value indicating whether the source stream supports reading.
59 /// </summary>
60 /// <value>true if the stream supports reading; otherwise, false.</value>
61 public override bool CanRead
62 {
63 get
64 {
65 return this.source.CanRead;
66 }
67 }
68
69 /// <summary>
70 /// Gets a value indicating whether the source stream supports writing.
71 /// </summary>
72 /// <value>true if the stream supports writing; otherwise, false.</value>
73 public override bool CanWrite
74 {
75 get
76 {
77 return this.source.CanWrite;
78 }
79 }
80
81 /// <summary>
82 /// Gets a value indicating whether the source stream supports seeking.
83 /// </summary>
84 /// <value>true if the stream supports seeking; otherwise, false.</value>
85 public override bool CanSeek
86 {
87 get
88 {
89 return this.source.CanSeek;
90 }
91 }
92
93 /// <summary>
94 /// Gets the length of the source stream.
95 /// </summary>
96 public override long Length
97 {
98 get
99 {
100 return this.source.Length;
101 }
102 }
103
104 /// <summary>
105 /// Gets or sets the position of the source stream.
106 /// </summary>
107 public override long Position
108 {
109 get
110 {
111 return this.source.Position;
112 }
113
114 set
115 {
116 this.source.Position = value;
117 }
118 }
119
120 /// <summary>
121 /// Flushes the source stream.
122 /// </summary>
123 public override void Flush()
124 {
125 this.source.Flush();
126 }
127
128 /// <summary>
129 /// Sets the length of the source stream.
130 /// </summary>
131 /// <param name="value">The desired length of the stream in bytes.</param>
132 public override void SetLength(long value)
133 {
134 this.source.SetLength(value);
135 }
136
137 /// <summary>
138 /// Closes the source stream and also closes the additional objects that are carried.
139 /// </summary>
140 public override void Close()
141 {
142 this.source.Close();
143
144 foreach (IDisposable cargoObject in this.cargo)
145 {
146 cargoObject.Dispose();
147 }
148 }
149
150 /// <summary>
151 /// Reads from the source stream.
152 /// </summary>
153 /// <param name="buffer">An array of bytes. When this method returns, the buffer
154 /// contains the specified byte array with the values between offset and
155 /// (offset + count - 1) replaced by the bytes read from the source.</param>
156 /// <param name="offset">The zero-based byte offset in buffer at which to begin
157 /// storing the data read from the stream.</param>
158 /// <param name="count">The maximum number of bytes to be read from the stream.</param>
159 /// <returns>The total number of bytes read into the buffer. This can be less
160 /// than the number of bytes requested if that many bytes are not currently available,
161 /// or zero (0) if the end of the stream has been reached.</returns>
162 public override int Read(byte[] buffer, int offset, int count)
163 {
164 return this.source.Read(buffer, offset, count);
165 }
166
167 /// <summary>
168 /// Writes to the source stream.
169 /// </summary>
170 /// <param name="buffer">An array of bytes. This method copies count
171 /// bytes from buffer to the stream.</param>
172 /// <param name="offset">The zero-based byte offset in buffer at which
173 /// to begin copying bytes to the stream.</param>
174 /// <param name="count">The number of bytes to be written to the stream.</param>
175 public override void Write(byte[] buffer, int offset, int count)
176 {
177 this.source.Write(buffer, offset, count);
178 }
179
180 /// <summary>
181 /// Changes the position of the source stream.
182 /// </summary>
183 /// <param name="offset">A byte offset relative to the origin parameter.</param>
184 /// <param name="origin">A value of type SeekOrigin indicating the reference
185 /// point used to obtain the new position.</param>
186 /// <returns>The new position within the stream.</returns>
187 public override long Seek(long offset, SeekOrigin origin)
188 {
189 return this.source.Seek(offset, origin);
190 }
191 }
192}
diff --git a/src/dtf/WixToolset.Dtf.Compression/Compression.cd b/src/dtf/WixToolset.Dtf.Compression/Compression.cd
new file mode 100644
index 00000000..95012be0
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression/Compression.cd
@@ -0,0 +1,175 @@
1<?xml version="1.0" encoding="utf-8"?>
2<ClassDiagram MajorVersion="1" MinorVersion="1">
3 <Comment CommentText="File-based classes">
4 <Position X="2.35" Y="1.442" Height="0.408" Width="0.783" />
5 </Comment>
6 <Comment CommentText="Stream-based classes">
7 <Position X="9.649" Y="1.317" Height="0.4" Width="0.996" />
8 </Comment>
9 <Class Name="WixToolset.Dtf.Compression.ArchiveException" Collapsed="true">
10 <Position X="3" Y="4.25" Width="2" />
11 <TypeIdentifier>
12 <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
13 <FileName>ArchiveException.cs</FileName>
14 </TypeIdentifier>
15 </Class>
16 <Class Name="WixToolset.Dtf.Compression.ArchiveFileInfo">
17 <Position X="3" Y="0.5" Width="2" />
18 <Members>
19 <Method Name="ArchiveFileInfo" Hidden="true" />
20 <Field Name="archiveInfo" Hidden="true" />
21 <Field Name="archiveNumber" Hidden="true" />
22 <Field Name="attributes" Hidden="true" />
23 <Field Name="exists" Hidden="true" />
24 <Method Name="GetObjectData" Hidden="true" />
25 <Field Name="initialized" Hidden="true" />
26 <Field Name="lastWriteTime" Hidden="true" />
27 <Field Name="length" Hidden="true" />
28 <Field Name="name" Hidden="true" />
29 <Field Name="path" Hidden="true" />
30 </Members>
31 <TypeIdentifier>
32 <HashCode>AAAgAAAAIRJAAIMEAEACgARwAAEEEAAAASAAAAEAIAA=</HashCode>
33 <FileName>ArchiveFileInfo.cs</FileName>
34 </TypeIdentifier>
35 </Class>
36 <Class Name="WixToolset.Dtf.Compression.ArchiveInfo">
37 <Position X="0.5" Y="0.5" Width="2.25" />
38 <Members>
39 <Method Name="ArchiveInfo" Hidden="true" />
40 <Method Name="CreateStringDictionary" Hidden="true" />
41 <Method Name="GetFile" Hidden="true" />
42 <Method Name="GetRelativeFilePathsInDirectoryTree" Hidden="true" />
43 <Method Name="InternalGetFiles" Hidden="true" />
44 <Method Name="RecursiveGetRelativeFilePathsInDirectoryTree" Hidden="true" />
45 </Members>
46 <TypeIdentifier>
47 <HashCode>AAEAABAAIAAAAgQEAAgBAARAHAEJACAAAABEAAkAMAI=</HashCode>
48 <FileName>ArchiveInfo.cs</FileName>
49 </TypeIdentifier>
50 </Class>
51 <Class Name="WixToolset.Dtf.Compression.ArchiveFileStreamContext">
52 <Position X="12.75" Y="0.75" Width="2.25" />
53 <Members>
54 <Field Name="archiveFiles" Hidden="true" />
55 <Method Name="ArchiveFileStreamContext" Hidden="true" />
56 <Method Name="CloseArchiveReadStream" Hidden="true" />
57 <Method Name="CloseArchiveWriteStream" Hidden="true" />
58 <Method Name="CloseFileReadStream" Hidden="true" />
59 <Method Name="CloseFileWriteStream" Hidden="true" />
60 <Field Name="directory" Hidden="true" />
61 <Field Name="enableOffsetOpen" Hidden="true" />
62 <Field Name="extractOnlyNewerFiles" Hidden="true" />
63 <Field Name="files" Hidden="true" />
64 <Method Name="GetArchiveName" Hidden="true" />
65 <Method Name="GetOption" Hidden="true" />
66 <Method Name="OpenArchiveReadStream" Hidden="true" />
67 <Method Name="OpenArchiveWriteStream" Hidden="true" />
68 <Method Name="OpenFileReadStream" Hidden="true" />
69 <Method Name="OpenFileWriteStream" Hidden="true" />
70 <Method Name="TranslateFilePath" Hidden="true" />
71 </Members>
72 <TypeIdentifier>
73 <HashCode>AEQAABgAAACQAACACACAAgAQAAIgAAAAACAMgAAEAKA=</HashCode>
74 <FileName>ArchiveFileStreamContext.cs</FileName>
75 </TypeIdentifier>
76 <Lollipop Position="0.2" />
77 </Class>
78 <Class Name="WixToolset.Dtf.Compression.ArchiveProgressEventArgs">
79 <Position X="5.25" Y="0.5" Width="2.25" />
80 <Members>
81 <Method Name="ArchiveProgressEventArgs" Hidden="true" />
82 <Field Name="currentArchiveBytesProcessed" Hidden="true" />
83 <Field Name="currentArchiveName" Hidden="true" />
84 <Field Name="currentArchiveNumber" Hidden="true" />
85 <Field Name="currentArchiveTotalBytes" Hidden="true" />
86 <Field Name="currentFileBytesProcessed" Hidden="true" />
87 <Field Name="currentFileName" Hidden="true" />
88 <Field Name="currentFileNumber" Hidden="true" />
89 <Field Name="currentFileTotalBytes" Hidden="true" />
90 <Field Name="fileBytesProcessed" Hidden="true" />
91 <Field Name="progressType" Hidden="true" />
92 <Field Name="totalArchives" Hidden="true" />
93 <Field Name="totalFileBytes" Hidden="true" />
94 <Field Name="totalFiles" Hidden="true" />
95 </Members>
96 <TypeIdentifier>
97 <HashCode>AAMCAQASACAAABBBAAASUAAAQBAAAMAAAAGQAAgBEAA=</HashCode>
98 <FileName>ArchiveProgressEventArgs.cs</FileName>
99 </TypeIdentifier>
100 </Class>
101 <Class Name="WixToolset.Dtf.Compression.BasicUnpackStreamContext">
102 <Position X="12.75" Y="3" Width="2.25" />
103 <Members>
104 <Field Name="archiveStream" Hidden="true" />
105 <Method Name="BasicUnpackStreamContext" Hidden="true" />
106 <Method Name="CloseArchiveReadStream" Hidden="true" />
107 <Method Name="CloseFileWriteStream" Hidden="true" />
108 <Field Name="fileStream" Hidden="true" />
109 <Method Name="OpenArchiveReadStream" Hidden="true" />
110 <Method Name="OpenFileWriteStream" Hidden="true" />
111 </Members>
112 <TypeIdentifier>
113 <HashCode>AAAAAAgAAACEAAAAAAAAAAAAAAAgAAAAIAAMAAAAAAA=</HashCode>
114 <FileName>BasicUnpackStreamContext.cs</FileName>
115 </TypeIdentifier>
116 <Lollipop Position="0.2" />
117 </Class>
118 <Class Name="WixToolset.Dtf.Compression.CompressionEngine">
119 <Position X="8" Y="0.5" Width="2.25" />
120 <Members>
121 <Method Name="~CompressionEngine" Hidden="true" />
122 <Method Name="CompressionEngine" Hidden="true" />
123 <Field Name="compressionLevel" Hidden="true" />
124 <Field Name="dontUseTempFiles" Hidden="true" />
125 </Members>
126 <TypeIdentifier>
127 <HashCode>AAAEAAAABCBAACRgAAAAAAQAAEAAAAAAQAEAAAiAAAI=</HashCode>
128 <FileName>CompressionEngine.cs</FileName>
129 </TypeIdentifier>
130 <Lollipop Position="0.2" />
131 </Class>
132 <Class Name="WixToolset.Dtf.Compression.DuplicateStream" Collapsed="true">
133 <Position X="10.5" Y="4.25" Width="2" />
134 <TypeIdentifier>
135 <HashCode>AAAAAEAAAgAAQAIgGAAAIABgAAAAAAAAAAAAAAGIACA=</HashCode>
136 <FileName>DuplicateStream.cs</FileName>
137 </TypeIdentifier>
138 </Class>
139 <Class Name="WixToolset.Dtf.Compression.OffsetStream" Collapsed="true">
140 <Position X="8" Y="4.25" Width="2" />
141 <TypeIdentifier>
142 <HashCode>AAAAAAAAAgAAQAIgGAAAAABgAAAAAEAgAAAAAAGIwCA=</HashCode>
143 <FileName>OffsetStream.cs</FileName>
144 </TypeIdentifier>
145 </Class>
146 <Interface Name="WixToolset.Dtf.Compression.IPackStreamContext">
147 <Position X="10.5" Y="0.5" Width="2" />
148 <TypeIdentifier>
149 <HashCode>AAAAAAAAAAAAAACAAACAAAAQAAAgAAAAACAIAAAAAAA=</HashCode>
150 <FileName>IPackStreamContext.cs</FileName>
151 </TypeIdentifier>
152 </Interface>
153 <Interface Name="WixToolset.Dtf.Compression.IUnpackStreamContext">
154 <Position X="10.5" Y="2.5" Width="2" />
155 <TypeIdentifier>
156 <HashCode>AAAAAAgAAACAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAA=</HashCode>
157 <FileName>IUnpackStreamContext.cs</FileName>
158 </TypeIdentifier>
159 </Interface>
160 <Enum Name="WixToolset.Dtf.Compression.ArchiveProgressType" Collapsed="true">
161 <Position X="5.25" Y="3.75" Width="2" />
162 <TypeIdentifier>
163 <HashCode>QAAAAAAAAAAAAIAAgAAAAAAAAAQAAAAACIAAAAAAAAA=</HashCode>
164 <FileName>ArchiveProgressType.cs</FileName>
165 </TypeIdentifier>
166 </Enum>
167 <Enum Name="WixToolset.Dtf.Compression.CompressionLevel" Collapsed="true">
168 <Position X="5.25" Y="4.5" Width="2" />
169 <TypeIdentifier>
170 <HashCode>AAAAAAAAABAAAAAAEAAAAAAAAAAIAAAAAAAAAAEAAAA=</HashCode>
171 <FileName>CompressionLevel.cs</FileName>
172 </TypeIdentifier>
173 </Enum>
174 <Font Name="Verdana" Size="8" />
175</ClassDiagram> \ No newline at end of file
diff --git a/src/dtf/WixToolset.Dtf.Compression/CompressionEngine.cs b/src/dtf/WixToolset.Dtf.Compression/CompressionEngine.cs
new file mode 100644
index 00000000..7758ea98
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression/CompressionEngine.cs
@@ -0,0 +1,371 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression
4{
5using System;
6using System.IO;
7using System.Collections.Generic;
8using System.Globalization;
9
10 /// <summary>
11 /// Base class for an engine capable of packing and unpacking a particular
12 /// compressed file format.
13 /// </summary>
14 public abstract class CompressionEngine : IDisposable
15 {
16 private CompressionLevel compressionLevel;
17 private bool dontUseTempFiles;
18
19 /// <summary>
20 /// Creates a new instance of the compression engine base class.
21 /// </summary>
22 protected CompressionEngine()
23 {
24 this.compressionLevel = CompressionLevel.Normal;
25 }
26
27 /// <summary>
28 /// Disposes the compression engine.
29 /// </summary>
30 ~CompressionEngine()
31 {
32 this.Dispose(false);
33 }
34
35 /// <summary>
36 /// Occurs when the compression engine reports progress in packing
37 /// or unpacking an archive.
38 /// </summary>
39 /// <seealso cref="ArchiveProgressType"/>
40 public event EventHandler<ArchiveProgressEventArgs> Progress;
41
42 /// <summary>
43 /// Gets or sets a flag indicating whether temporary files are created
44 /// and used during compression.
45 /// </summary>
46 /// <value>True if temporary files are used; false if compression is done
47 /// entirely in-memory.</value>
48 /// <remarks>The value of this property is true by default. Using temporary
49 /// files can greatly reduce the memory requirement of compression,
50 /// especially when compressing large archives. However, setting this property
51 /// to false may yield slightly better performance when creating small
52 /// archives. Or it may be necessary if the process does not have sufficient
53 /// privileges to create temporary files.</remarks>
54 public bool UseTempFiles
55 {
56 get
57 {
58 return !this.dontUseTempFiles;
59 }
60
61 set
62 {
63 this.dontUseTempFiles = !value;
64 }
65 }
66
67 /// <summary>
68 /// Compression level to use when compressing files.
69 /// </summary>
70 /// <value>A compression level ranging from minimum to maximum compression,
71 /// or no compression.</value>
72 public CompressionLevel CompressionLevel
73 {
74 get
75 {
76 return this.compressionLevel;
77 }
78
79 set
80 {
81 this.compressionLevel = value;
82 }
83 }
84
85 /// <summary>
86 /// Disposes of resources allocated by the compression engine.
87 /// </summary>
88 public void Dispose()
89 {
90 this.Dispose(true);
91 GC.SuppressFinalize(this);
92 }
93
94 /// <summary>
95 /// Creates an archive.
96 /// </summary>
97 /// <param name="streamContext">A context interface to handle opening
98 /// and closing of archive and file streams.</param>
99 /// <param name="files">The paths of the files in the archive
100 /// (not external file paths).</param>
101 /// <exception cref="ArchiveException">The archive could not be
102 /// created.</exception>
103 /// <remarks>
104 /// The stream context implementation may provide a mapping from the
105 /// file paths within the archive to the external file paths.
106 /// </remarks>
107 public void Pack(IPackStreamContext streamContext, IEnumerable<string> files)
108 {
109 if (files == null)
110 {
111 throw new ArgumentNullException("files");
112 }
113
114 this.Pack(streamContext, files, 0);
115 }
116
117 /// <summary>
118 /// Creates an archive or chain of archives.
119 /// </summary>
120 /// <param name="streamContext">A context interface to handle opening
121 /// and closing of archive and file streams.</param>
122 /// <param name="files">The paths of the files in the archive (not
123 /// external file paths).</param>
124 /// <param name="maxArchiveSize">The maximum number of bytes for one
125 /// archive before the contents are chained to the next archive, or zero
126 /// for unlimited archive size.</param>
127 /// <exception cref="ArchiveException">The archive could not be
128 /// created.</exception>
129 /// <remarks>
130 /// The stream context implementation may provide a mapping from the file
131 /// paths within the archive to the external file paths.
132 /// </remarks>
133 public abstract void Pack(
134 IPackStreamContext streamContext,
135 IEnumerable<string> files,
136 long maxArchiveSize);
137
138 /// <summary>
139 /// Checks whether a Stream begins with a header that indicates
140 /// it is a valid archive.
141 /// </summary>
142 /// <param name="stream">Stream for reading the archive file.</param>
143 /// <returns>True if the stream is a valid archive
144 /// (with no offset); false otherwise.</returns>
145 public abstract bool IsArchive(Stream stream);
146
147 /// <summary>
148 /// Gets the offset of an archive that is positioned 0 or more bytes
149 /// from the start of the Stream.
150 /// </summary>
151 /// <param name="stream">A stream for reading the archive.</param>
152 /// <returns>The offset in bytes of the archive,
153 /// or -1 if no archive is found in the Stream.</returns>
154 /// <remarks>The archive must begin on a 4-byte boundary.</remarks>
155 public virtual long FindArchiveOffset(Stream stream)
156 {
157 if (stream == null)
158 {
159 throw new ArgumentNullException("stream");
160 }
161
162 long sectionSize = 4;
163 long length = stream.Length;
164 for (long offset = 0; offset <= length - sectionSize; offset += sectionSize)
165 {
166 stream.Seek(offset, SeekOrigin.Begin);
167 if (this.IsArchive(stream))
168 {
169 return offset;
170 }
171 }
172
173 return -1;
174 }
175
176 /// <summary>
177 /// Gets information about all files in an archive stream.
178 /// </summary>
179 /// <param name="stream">A stream for reading the archive.</param>
180 /// <returns>Information about all files in the archive stream.</returns>
181 /// <exception cref="ArchiveException">The stream is not a valid
182 /// archive.</exception>
183 public IList<ArchiveFileInfo> GetFileInfo(Stream stream)
184 {
185 return this.GetFileInfo(new BasicUnpackStreamContext(stream), null);
186 }
187
188 /// <summary>
189 /// Gets information about files in an archive or archive chain.
190 /// </summary>
191 /// <param name="streamContext">A context interface to handle opening
192 /// and closing of archive and file streams.</param>
193 /// <param name="fileFilter">A predicate that can determine
194 /// which files to process, optional.</param>
195 /// <returns>Information about files in the archive stream.</returns>
196 /// <exception cref="ArchiveException">The archive provided
197 /// by the stream context is not valid.</exception>
198 /// <remarks>
199 /// The <paramref name="fileFilter"/> predicate takes an internal file
200 /// path and returns true to include the file or false to exclude it.
201 /// </remarks>
202 public abstract IList<ArchiveFileInfo> GetFileInfo(
203 IUnpackStreamContext streamContext,
204 Predicate<string> fileFilter);
205
206 /// <summary>
207 /// Gets the list of files in an archive Stream.
208 /// </summary>
209 /// <param name="stream">A stream for reading the archive.</param>
210 /// <returns>A list of the paths of all files contained in the
211 /// archive.</returns>
212 /// <exception cref="ArchiveException">The stream is not a valid
213 /// archive.</exception>
214 public IList<string> GetFiles(Stream stream)
215 {
216 return this.GetFiles(new BasicUnpackStreamContext(stream), null);
217 }
218
219 /// <summary>
220 /// Gets the list of files in an archive or archive chain.
221 /// </summary>
222 /// <param name="streamContext">A context interface to handle opening
223 /// and closing of archive and file streams.</param>
224 /// <param name="fileFilter">A predicate that can determine
225 /// which files to process, optional.</param>
226 /// <returns>An array containing the names of all files contained in
227 /// the archive or archive chain.</returns>
228 /// <exception cref="ArchiveException">The archive provided
229 /// by the stream context is not valid.</exception>
230 /// <remarks>
231 /// The <paramref name="fileFilter"/> predicate takes an internal file
232 /// path and returns true to include the file or false to exclude it.
233 /// </remarks>
234 public IList<string> GetFiles(
235 IUnpackStreamContext streamContext,
236 Predicate<string> fileFilter)
237 {
238 if (streamContext == null)
239 {
240 throw new ArgumentNullException("streamContext");
241 }
242
243 IList<ArchiveFileInfo> files =
244 this.GetFileInfo(streamContext, fileFilter);
245 IList<string> fileNames = new List<string>(files.Count);
246 for (int i = 0; i < files.Count; i++)
247 {
248 fileNames.Add(files[i].Name);
249 }
250
251 return fileNames;
252 }
253
254 /// <summary>
255 /// Reads a single file from an archive stream.
256 /// </summary>
257 /// <param name="stream">A stream for reading the archive.</param>
258 /// <param name="path">The path of the file within the archive
259 /// (not the external file path).</param>
260 /// <returns>A stream for reading the extracted file, or null
261 /// if the file does not exist in the archive.</returns>
262 /// <exception cref="ArchiveException">The stream is not a valid
263 /// archive.</exception>
264 /// <remarks>The entire extracted file is cached in memory, so this
265 /// method requires enough free memory to hold the file.</remarks>
266 public Stream Unpack(Stream stream, string path)
267 {
268 if (stream == null)
269 {
270 throw new ArgumentNullException("stream");
271 }
272
273 if (path == null)
274 {
275 throw new ArgumentNullException("path");
276 }
277
278 BasicUnpackStreamContext streamContext =
279 new BasicUnpackStreamContext(stream);
280 this.Unpack(
281 streamContext,
282 delegate(string match)
283 {
284 return String.Compare(
285 match, path, true, CultureInfo.InvariantCulture) == 0;
286 });
287
288 Stream extractStream = streamContext.FileStream;
289 if (extractStream != null)
290 {
291 extractStream.Position = 0;
292 }
293
294 return extractStream;
295 }
296
297 /// <summary>
298 /// Extracts files from an archive or archive chain.
299 /// </summary>
300 /// <param name="streamContext">A context interface to handle opening
301 /// and closing of archive and file streams.</param>
302 /// <param name="fileFilter">An optional predicate that can determine
303 /// which files to process.</param>
304 /// <exception cref="ArchiveException">The archive provided
305 /// by the stream context is not valid.</exception>
306 /// <remarks>
307 /// The <paramref name="fileFilter"/> predicate takes an internal file
308 /// path and returns true to include the file or false to exclude it.
309 /// </remarks>
310 public abstract void Unpack(
311 IUnpackStreamContext streamContext,
312 Predicate<string> fileFilter);
313
314 /// <summary>
315 /// Called by sublcasses to distribute a packing or unpacking progress
316 /// event to listeners.
317 /// </summary>
318 /// <param name="e">Event details.</param>
319 protected void OnProgress(ArchiveProgressEventArgs e)
320 {
321 if (this.Progress != null)
322 {
323 this.Progress(this, e);
324 }
325 }
326
327 /// <summary>
328 /// Disposes of resources allocated by the compression engine.
329 /// </summary>
330 /// <param name="disposing">If true, the method has been called
331 /// directly or indirectly by a user's code, so managed and unmanaged
332 /// resources will be disposed. If false, the method has been called by
333 /// the runtime from inside the finalizer, and only unmanaged resources
334 /// will be disposed.</param>
335 protected virtual void Dispose(bool disposing)
336 {
337 }
338
339 /// <summary>
340 /// Compresion utility function for converting old-style
341 /// date and time values to a DateTime structure.
342 /// </summary>
343 public static void DosDateAndTimeToDateTime(
344 short dosDate, short dosTime, out DateTime dateTime)
345 {
346 if (dosDate == 0 && dosTime == 0)
347 {
348 dateTime = DateTime.MinValue;
349 }
350 else
351 {
352 long fileTime;
353 SafeNativeMethods.DosDateTimeToFileTime(dosDate, dosTime, out fileTime);
354 dateTime = DateTime.FromFileTimeUtc(fileTime);
355 dateTime = new DateTime(dateTime.Ticks, DateTimeKind.Local);
356 }
357 }
358
359 /// <summary>
360 /// Compresion utility function for converting a DateTime structure
361 /// to old-style date and time values.
362 /// </summary>
363 public static void DateTimeToDosDateAndTime(
364 DateTime dateTime, out short dosDate, out short dosTime)
365 {
366 dateTime = new DateTime(dateTime.Ticks, DateTimeKind.Utc);
367 long filetime = dateTime.ToFileTimeUtc();
368 SafeNativeMethods.FileTimeToDosDateTime(ref filetime, out dosDate, out dosTime);
369 }
370 }
371}
diff --git a/src/dtf/WixToolset.Dtf.Compression/CompressionLevel.cs b/src/dtf/WixToolset.Dtf.Compression/CompressionLevel.cs
new file mode 100644
index 00000000..84ec8fc4
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression/CompressionLevel.cs
@@ -0,0 +1,31 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression
4{
5using System;
6using System.Collections.Generic;
7using System.Text;
8
9 /// <summary>
10 /// Specifies the compression level ranging from minimum compresion to
11 /// maximum compression, or no compression at all.
12 /// </summary>
13 /// <remarks>
14 /// Although only four values are enumerated, any integral value between
15 /// <see cref="CompressionLevel.Min"/> and <see cref="CompressionLevel.Max"/> can also be used.
16 /// </remarks>
17 public enum CompressionLevel
18 {
19 /// <summary>Do not compress files, only store.</summary>
20 None = 0,
21
22 /// <summary>Minimum compression; fastest.</summary>
23 Min = 1,
24
25 /// <summary>A compromize between speed and compression efficiency.</summary>
26 Normal = 6,
27
28 /// <summary>Maximum compression; slowest.</summary>
29 Max = 10
30 }
31}
diff --git a/src/dtf/WixToolset.Dtf.Compression/DuplicateStream.cs b/src/dtf/WixToolset.Dtf.Compression/DuplicateStream.cs
new file mode 100644
index 00000000..50e62e73
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression/DuplicateStream.cs
@@ -0,0 +1,212 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression
4{
5 using System;
6 using System.IO;
7
8 /// <summary>
9 /// Duplicates a source stream by maintaining a separate position.
10 /// </summary>
11 /// <remarks>
12 /// WARNING: duplicate streams are not thread-safe with respect to each other or the original stream.
13 /// If multiple threads use duplicate copies of the same stream, they must synchronize for any operations.
14 /// </remarks>
15 public class DuplicateStream : Stream
16 {
17 private Stream source;
18 private long position;
19
20 /// <summary>
21 /// Creates a new duplicate of a stream.
22 /// </summary>
23 /// <param name="source">source of the duplicate</param>
24 public DuplicateStream(Stream source)
25 {
26 if (source == null)
27 {
28 throw new ArgumentNullException("source");
29 }
30
31 this.source = DuplicateStream.OriginalStream(source);
32 }
33
34 /// <summary>
35 /// Gets the original stream that was used to create the duplicate.
36 /// </summary>
37 public Stream Source
38 {
39 get
40 {
41 return this.source;
42 }
43 }
44
45 /// <summary>
46 /// Gets a value indicating whether the source stream supports reading.
47 /// </summary>
48 /// <value>true if the stream supports reading; otherwise, false.</value>
49 public override bool CanRead
50 {
51 get
52 {
53 return this.source.CanRead;
54 }
55 }
56
57 /// <summary>
58 /// Gets a value indicating whether the source stream supports writing.
59 /// </summary>
60 /// <value>true if the stream supports writing; otherwise, false.</value>
61 public override bool CanWrite
62 {
63 get
64 {
65 return this.source.CanWrite;
66 }
67 }
68
69 /// <summary>
70 /// Gets a value indicating whether the source stream supports seeking.
71 /// </summary>
72 /// <value>true if the stream supports seeking; otherwise, false.</value>
73 public override bool CanSeek
74 {
75 get
76 {
77 return this.source.CanSeek;
78 }
79 }
80
81 /// <summary>
82 /// Gets the length of the source stream.
83 /// </summary>
84 public override long Length
85 {
86 get
87 {
88 return this.source.Length;
89 }
90 }
91
92 /// <summary>
93 /// Gets or sets the position of the current stream,
94 /// ignoring the position of the source stream.
95 /// </summary>
96 public override long Position
97 {
98 get
99 {
100 return this.position;
101 }
102
103 set
104 {
105 this.position = value;
106 }
107 }
108
109 /// <summary>
110 /// Retrieves the original stream from a possible duplicate stream.
111 /// </summary>
112 /// <param name="stream">Possible duplicate stream.</param>
113 /// <returns>If the stream is a DuplicateStream, returns
114 /// the duplicate's source; otherwise returns the same stream.</returns>
115 public static Stream OriginalStream(Stream stream)
116 {
117 DuplicateStream dupStream = stream as DuplicateStream;
118 return dupStream != null ? dupStream.Source : stream;
119 }
120
121 /// <summary>
122 /// Flushes the source stream.
123 /// </summary>
124 public override void Flush()
125 {
126 this.source.Flush();
127 }
128
129 /// <summary>
130 /// Sets the length of the source stream.
131 /// </summary>
132 /// <param name="value">The desired length of the stream in bytes.</param>
133 public override void SetLength(long value)
134 {
135 this.source.SetLength(value);
136 }
137
138 /// <summary>
139 /// Closes the underlying stream, effectively closing ALL duplicates.
140 /// </summary>
141 public override void Close()
142 {
143 this.source.Close();
144 }
145
146 /// <summary>
147 /// Reads from the source stream while maintaining a separate position
148 /// and not impacting the source stream's position.
149 /// </summary>
150 /// <param name="buffer">An array of bytes. When this method returns, the buffer
151 /// contains the specified byte array with the values between offset and
152 /// (offset + count - 1) replaced by the bytes read from the current source.</param>
153 /// <param name="offset">The zero-based byte offset in buffer at which to begin
154 /// storing the data read from the current stream.</param>
155 /// <param name="count">The maximum number of bytes to be read from the current stream.</param>
156 /// <returns>The total number of bytes read into the buffer. This can be less
157 /// than the number of bytes requested if that many bytes are not currently available,
158 /// or zero (0) if the end of the stream has been reached.</returns>
159 public override int Read(byte[] buffer, int offset, int count)
160 {
161 long saveSourcePosition = this.source.Position;
162 this.source.Position = this.position;
163 int read = this.source.Read(buffer, offset, count);
164 this.position = this.source.Position;
165 this.source.Position = saveSourcePosition;
166 return read;
167 }
168
169 /// <summary>
170 /// Writes to the source stream while maintaining a separate position
171 /// and not impacting the source stream's position.
172 /// </summary>
173 /// <param name="buffer">An array of bytes. This method copies count
174 /// bytes from buffer to the current stream.</param>
175 /// <param name="offset">The zero-based byte offset in buffer at which
176 /// to begin copying bytes to the current stream.</param>
177 /// <param name="count">The number of bytes to be written to the
178 /// current stream.</param>
179 public override void Write(byte[] buffer, int offset, int count)
180 {
181 long saveSourcePosition = this.source.Position;
182 this.source.Position = this.position;
183 this.source.Write(buffer, offset, count);
184 this.position = this.source.Position;
185 this.source.Position = saveSourcePosition;
186 }
187
188 /// <summary>
189 /// Changes the position of this stream without impacting the
190 /// source stream's position.
191 /// </summary>
192 /// <param name="offset">A byte offset relative to the origin parameter.</param>
193 /// <param name="origin">A value of type SeekOrigin indicating the reference
194 /// point used to obtain the new position.</param>
195 /// <returns>The new position within the current stream.</returns>
196 public override long Seek(long offset, SeekOrigin origin)
197 {
198 long originPosition = 0;
199 if (origin == SeekOrigin.Current)
200 {
201 originPosition = this.position;
202 }
203 else if (origin == SeekOrigin.End)
204 {
205 originPosition = this.Length;
206 }
207
208 this.position = originPosition + offset;
209 return this.position;
210 }
211 }
212}
diff --git a/src/dtf/WixToolset.Dtf.Compression/IPackStreamContext.cs b/src/dtf/WixToolset.Dtf.Compression/IPackStreamContext.cs
new file mode 100644
index 00000000..19d77be5
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression/IPackStreamContext.cs
@@ -0,0 +1,117 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression
4{
5 using System;
6 using System.IO;
7 using System.Diagnostics.CodeAnalysis;
8
9 /// <summary>
10 /// This interface provides the methods necessary for the
11 /// <see cref="CompressionEngine"/> to open and close streams for archives
12 /// and files. The implementor of this interface can use any kind of logic
13 /// to determine what kind of streams to open and where.
14 /// </summary>
15 public interface IPackStreamContext
16 {
17 /// <summary>
18 /// Gets the name of the archive with a specified number.
19 /// </summary>
20 /// <param name="archiveNumber">The 0-based index of the archive
21 /// within the chain.</param>
22 /// <returns>The name of the requested archive. May be an empty string
23 /// for non-chained archives, but may never be null.</returns>
24 /// <remarks>The archive name is the name stored within the archive, used for
25 /// identification of the archive especially among archive chains. That
26 /// name is often, but not necessarily the same as the filename of the
27 /// archive package.</remarks>
28 string GetArchiveName(int archiveNumber);
29
30 /// <summary>
31 /// Opens a stream for writing an archive package.
32 /// </summary>
33 /// <param name="archiveNumber">The 0-based index of the archive within
34 /// the chain.</param>
35 /// <param name="archiveName">The name of the archive that was returned
36 /// by <see cref="GetArchiveName"/>.</param>
37 /// <param name="truncate">True if the stream should be truncated when
38 /// opened (if it already exists); false if an existing stream is being
39 /// re-opened for writing additional data.</param>
40 /// <param name="compressionEngine">Instance of the compression engine
41 /// doing the operations.</param>
42 /// <returns>A writable Stream where the compressed archive bytes will be
43 /// written, or null to cancel the archive creation.</returns>
44 /// <remarks>
45 /// If this method returns null, the archive engine will throw a
46 /// FileNotFoundException.
47 /// </remarks>
48 Stream OpenArchiveWriteStream(
49 int archiveNumber,
50 string archiveName,
51 bool truncate,
52 CompressionEngine compressionEngine);
53
54 /// <summary>
55 /// Closes a stream where an archive package was written.
56 /// </summary>
57 /// <param name="archiveNumber">The 0-based index of the archive within
58 /// the chain.</param>
59 /// <param name="archiveName">The name of the archive that was previously
60 /// returned by
61 /// <see cref="GetArchiveName"/>.</param>
62 /// <param name="stream">A stream that was previously returned by
63 /// <see cref="OpenArchiveWriteStream"/> and is now ready to be closed.</param>
64 /// <remarks>
65 /// If there is another archive package in the chain, then after this stream
66 /// is closed a new stream will be opened.
67 /// </remarks>
68 void CloseArchiveWriteStream(int archiveNumber, string archiveName, Stream stream);
69
70 /// <summary>
71 /// Opens a stream to read a file that is to be included in an archive.
72 /// </summary>
73 /// <param name="path">The path of the file within the archive. This is often,
74 /// but not necessarily, the same as the relative path of the file outside
75 /// the archive.</param>
76 /// <param name="attributes">Returned attributes of the opened file, to be
77 /// stored in the archive.</param>
78 /// <param name="lastWriteTime">Returned last-modified time of the opened file,
79 /// to be stored in the archive.</param>
80 /// <returns>A readable Stream where the file bytes will be read from before
81 /// they are compressed, or null to skip inclusion of the file and continue to
82 /// the next file.</returns>
83 [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters")]
84 Stream OpenFileReadStream(
85 string path,
86 out FileAttributes attributes,
87 out DateTime lastWriteTime);
88
89 /// <summary>
90 /// Closes a stream that has been used to read a file.
91 /// </summary>
92 /// <param name="path">The path of the file within the archive; the same as
93 /// the path provided
94 /// when the stream was opened.</param>
95 /// <param name="stream">A stream that was previously returned by
96 /// <see cref="OpenFileReadStream"/> and is now ready to be closed.</param>
97 void CloseFileReadStream(string path, Stream stream);
98
99 /// <summary>
100 /// Gets extended parameter information specific to the compression
101 /// format being used.
102 /// </summary>
103 /// <param name="optionName">Name of the option being requested.</param>
104 /// <param name="parameters">Parameters for the option; for per-file options,
105 /// the first parameter is typically the internal file path.</param>
106 /// <returns>Option value, or null to use the default behavior.</returns>
107 /// <remarks>
108 /// This method provides a way to set uncommon options during packaging, or a
109 /// way to handle aspects of compression formats not supported by the base library.
110 /// <para>For example, this may be used by the zip compression library to
111 /// specify different compression methods/levels on a per-file basis.</para>
112 /// <para>The available option names, parameters, and expected return values
113 /// should be documented by each compression library.</para>
114 /// </remarks>
115 object GetOption(string optionName, object[] parameters);
116 }
117}
diff --git a/src/dtf/WixToolset.Dtf.Compression/IUnpackStreamContext.cs b/src/dtf/WixToolset.Dtf.Compression/IUnpackStreamContext.cs
new file mode 100644
index 00000000..f0bc6aad
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression/IUnpackStreamContext.cs
@@ -0,0 +1,71 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression
4{
5 using System;
6 using System.IO;
7
8 /// <summary>
9 /// This interface provides the methods necessary for the <see cref="CompressionEngine"/> to open
10 /// and close streams for archives and files. The implementor of this interface can use any
11 /// kind of logic to determine what kind of streams to open and where
12 /// </summary>
13 public interface IUnpackStreamContext
14 {
15 /// <summary>
16 /// Opens the archive stream for reading.
17 /// </summary>
18 /// <param name="archiveNumber">The zero-based index of the archive to open.</param>
19 /// <param name="archiveName">The name of the archive being opened.</param>
20 /// <param name="compressionEngine">Instance of the compression engine doing the operations.</param>
21 /// <returns>A stream from which archive bytes are read, or null to cancel extraction
22 /// of the archive.</returns>
23 /// <remarks>
24 /// When the first archive in a chain is opened, the name is not yet known, so the
25 /// provided value will be an empty string. When opening further archives, the
26 /// provided value is the next-archive name stored in the previous archive. This
27 /// name is often, but not necessarily, the same as the filename of the archive
28 /// package to be opened.
29 /// <para>If this method returns null, the archive engine will throw a
30 /// FileNotFoundException.</para>
31 /// </remarks>
32 Stream OpenArchiveReadStream(int archiveNumber, string archiveName, CompressionEngine compressionEngine);
33
34 /// <summary>
35 /// Closes a stream where an archive package was read.
36 /// </summary>
37 /// <param name="archiveNumber">The archive number of the stream to close.</param>
38 /// <param name="archiveName">The name of the archive being closed.</param>
39 /// <param name="stream">The stream that was previously returned by
40 /// <see cref="OpenArchiveReadStream"/> and is now ready to be closed.</param>
41 void CloseArchiveReadStream(int archiveNumber, string archiveName, Stream stream);
42
43 /// <summary>
44 /// Opens a stream for writing extracted file bytes.
45 /// </summary>
46 /// <param name="path">The path of the file within the archive. This is often, but
47 /// not necessarily, the same as the relative path of the file outside the archive.</param>
48 /// <param name="fileSize">The uncompressed size of the file to be extracted.</param>
49 /// <param name="lastWriteTime">The last write time of the file to be extracted.</param>
50 /// <returns>A stream where extracted file bytes are to be written, or null to skip
51 /// extraction of the file and continue to the next file.</returns>
52 /// <remarks>
53 /// The implementor may use the path, size and date information to dynamically
54 /// decide whether or not the file should be extracted.
55 /// </remarks>
56 Stream OpenFileWriteStream(string path, long fileSize, DateTime lastWriteTime);
57
58 /// <summary>
59 /// Closes a stream where an extracted file was written.
60 /// </summary>
61 /// <param name="path">The path of the file within the archive.</param>
62 /// <param name="stream">The stream that was previously returned by <see cref="OpenFileWriteStream"/>
63 /// and is now ready to be closed.</param>
64 /// <param name="attributes">The attributes of the extracted file.</param>
65 /// <param name="lastWriteTime">The last write time of the file.</param>
66 /// <remarks>
67 /// The implementor may wish to apply the attributes and date to the newly-extracted file.
68 /// </remarks>
69 void CloseFileWriteStream(string path, Stream stream, FileAttributes attributes, DateTime lastWriteTime);
70 }
71}
diff --git a/src/dtf/WixToolset.Dtf.Compression/OffsetStream.cs b/src/dtf/WixToolset.Dtf.Compression/OffsetStream.cs
new file mode 100644
index 00000000..65562524
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression/OffsetStream.cs
@@ -0,0 +1,206 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression
4{
5 using System;
6 using System.IO;
7
8 /// <summary>
9 /// Wraps a source stream and offsets all read/write/seek calls by a given value.
10 /// </summary>
11 /// <remarks>
12 /// This class is used to trick archive an packing or unpacking process
13 /// into reading or writing at an offset into a file, primarily for
14 /// self-extracting packages.
15 /// </remarks>
16 public class OffsetStream : Stream
17 {
18 private Stream source;
19 private long sourceOffset;
20
21 /// <summary>
22 /// Creates a new OffsetStream instance from a source stream
23 /// and using a specified offset.
24 /// </summary>
25 /// <param name="source">Underlying stream for which all calls will be offset.</param>
26 /// <param name="offset">Positive or negative number of bytes to offset.</param>
27 public OffsetStream(Stream source, long offset)
28 {
29 if (source == null)
30 {
31 throw new ArgumentNullException("source");
32 }
33
34 this.source = source;
35 this.sourceOffset = offset;
36
37 this.source.Seek(this.sourceOffset, SeekOrigin.Current);
38 }
39
40 /// <summary>
41 /// Gets the underlying stream that this OffsetStream calls into.
42 /// </summary>
43 public Stream Source
44 {
45 get { return this.source; }
46 }
47
48 /// <summary>
49 /// Gets the number of bytes to offset all calls before
50 /// redirecting to the underlying stream.
51 /// </summary>
52 public long Offset
53 {
54 get { return this.sourceOffset; }
55 }
56
57 /// <summary>
58 /// Gets a value indicating whether the source stream supports reading.
59 /// </summary>
60 /// <value>true if the stream supports reading; otherwise, false.</value>
61 public override bool CanRead
62 {
63 get
64 {
65 return this.source.CanRead;
66 }
67 }
68
69 /// <summary>
70 /// Gets a value indicating whether the source stream supports writing.
71 /// </summary>
72 /// <value>true if the stream supports writing; otherwise, false.</value>
73 public override bool CanWrite
74 {
75 get
76 {
77 return this.source.CanWrite;
78 }
79 }
80
81 /// <summary>
82 /// Gets a value indicating whether the source stream supports seeking.
83 /// </summary>
84 /// <value>true if the stream supports seeking; otherwise, false.</value>
85 public override bool CanSeek
86 {
87 get
88 {
89 return this.source.CanSeek;
90 }
91 }
92
93 /// <summary>
94 /// Gets the effective length of the stream, which is equal to
95 /// the length of the source stream minus the offset.
96 /// </summary>
97 public override long Length
98 {
99 get { return this.source.Length - this.sourceOffset; }
100 }
101
102 /// <summary>
103 /// Gets or sets the effective position of the stream, which
104 /// is equal to the position of the source stream minus the offset.
105 /// </summary>
106 public override long Position
107 {
108 get { return this.source.Position - this.sourceOffset; }
109 set { this.source.Position = value + this.sourceOffset; }
110 }
111
112 /// <summary>
113 /// Reads a sequence of bytes from the source stream and advances
114 /// the position within the stream by the number of bytes read.
115 /// </summary>
116 /// <param name="buffer">An array of bytes. When this method returns, the buffer
117 /// contains the specified byte array with the values between offset and
118 /// (offset + count - 1) replaced by the bytes read from the current source.</param>
119 /// <param name="offset">The zero-based byte offset in buffer at which to begin
120 /// storing the data read from the current stream.</param>
121 /// <param name="count">The maximum number of bytes to be read from the current stream.</param>
122 /// <returns>The total number of bytes read into the buffer. This can be less
123 /// than the number of bytes requested if that many bytes are not currently available,
124 /// or zero (0) if the end of the stream has been reached.</returns>
125 public override int Read(byte[] buffer, int offset, int count)
126 {
127 return this.source.Read(buffer, offset, count);
128 }
129
130 /// <summary>
131 /// Writes a sequence of bytes to the source stream and advances the
132 /// current position within this stream by the number of bytes written.
133 /// </summary>
134 /// <param name="buffer">An array of bytes. This method copies count
135 /// bytes from buffer to the current stream.</param>
136 /// <param name="offset">The zero-based byte offset in buffer at which
137 /// to begin copying bytes to the current stream.</param>
138 /// <param name="count">The number of bytes to be written to the
139 /// current stream.</param>
140 public override void Write(byte[] buffer, int offset, int count)
141 {
142 this.source.Write(buffer, offset, count);
143 }
144
145 /// <summary>
146 /// Reads a byte from the stream and advances the position within the
147 /// source stream by one byte, or returns -1 if at the end of the stream.
148 /// </summary>
149 /// <returns>The unsigned byte cast to an Int32, or -1 if at the
150 /// end of the stream.</returns>
151 public override int ReadByte()
152 {
153 return this.source.ReadByte();
154 }
155
156 /// <summary>
157 /// Writes a byte to the current position in the source stream and
158 /// advances the position within the stream by one byte.
159 /// </summary>
160 /// <param name="value">The byte to write to the stream.</param>
161 public override void WriteByte(byte value)
162 {
163 this.source.WriteByte(value);
164 }
165
166 /// <summary>
167 /// Flushes the source stream.
168 /// </summary>
169 public override void Flush()
170 {
171 this.source.Flush();
172 }
173
174 /// <summary>
175 /// Sets the position within the current stream, which is
176 /// equal to the position within the source stream minus the offset.
177 /// </summary>
178 /// <param name="offset">A byte offset relative to the origin parameter.</param>
179 /// <param name="origin">A value of type SeekOrigin indicating
180 /// the reference point used to obtain the new position.</param>
181 /// <returns>The new position within the current stream.</returns>
182 public override long Seek(long offset, SeekOrigin origin)
183 {
184 return this.source.Seek(offset + (origin == SeekOrigin.Begin ? this.sourceOffset : 0), origin) - this.sourceOffset;
185 }
186
187 /// <summary>
188 /// Sets the effective length of the stream, which is equal to
189 /// the length of the source stream minus the offset.
190 /// </summary>
191 /// <param name="value">The desired length of the
192 /// current stream in bytes.</param>
193 public override void SetLength(long value)
194 {
195 this.source.SetLength(value + this.sourceOffset);
196 }
197
198 /// <summary>
199 /// Closes the underlying stream.
200 /// </summary>
201 public override void Close()
202 {
203 this.source.Close();
204 }
205 }
206}
diff --git a/src/dtf/WixToolset.Dtf.Compression/SafeNativeMethods.cs b/src/dtf/WixToolset.Dtf.Compression/SafeNativeMethods.cs
new file mode 100644
index 00000000..1829ba81
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression/SafeNativeMethods.cs
@@ -0,0 +1,22 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression
4{
5 using System;
6 using System.Security;
7 using System.Runtime.InteropServices;
8
9 [SuppressUnmanagedCodeSecurity]
10 internal static class SafeNativeMethods
11 {
12 [DllImport("kernel32.dll", SetLastError = true)]
13 [return: MarshalAs(UnmanagedType.Bool)]
14 internal static extern bool DosDateTimeToFileTime(
15 short wFatDate, short wFatTime, out long fileTime);
16
17 [DllImport("kernel32.dll", SetLastError = true)]
18 [return: MarshalAs(UnmanagedType.Bool)]
19 internal static extern bool FileTimeToDosDateTime(
20 ref long fileTime, out short wFatDate, out short wFatTime);
21 }
22}
diff --git a/src/dtf/WixToolset.Dtf.Compression/WixToolset.Dtf.Compression.csproj b/src/dtf/WixToolset.Dtf.Compression/WixToolset.Dtf.Compression.csproj
new file mode 100644
index 00000000..e49a446b
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression/WixToolset.Dtf.Compression.csproj
@@ -0,0 +1,21 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <RootNamespace>WixToolset.Dtf.Compression</RootNamespace>
7 <AssemblyName>WixToolset.Dtf.Compression</AssemblyName>
8 <TargetFrameworks>netstandard2.0;net20</TargetFrameworks>
9 <Description>Abstract base libraries for archive packing and unpacking</Description>
10 <CreateDocumentationFile>true</CreateDocumentationFile>
11 </PropertyGroup>
12
13 <ItemGroup>
14 <None Include="Compression.cd" />
15 </ItemGroup>
16
17 <ItemGroup>
18 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
19 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
20 </ItemGroup>
21</Project>
diff --git a/src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.csproj b/src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.csproj
new file mode 100644
index 00000000..b1b3faa7
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.csproj
@@ -0,0 +1,44 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5
6 <PropertyGroup>
7 <TargetFramework>netcoreapp3.1</TargetFramework>
8 <IncludeBuildOutput>false</IncludeBuildOutput>
9 <Description>WiX Toolset Dtf MSBuild integration</Description>
10 <NuspecFile>$(MSBuildThisFileName).nuspec</NuspecFile>
11 <NuspecBasePath>$(OutputPath)publish\WixToolset.Dtf.MSBuild\</NuspecBasePath>
12 <NuspecProperties>Id=$(MSBuildThisFileName);Authors=$(Authors);Copyright=$(Copyright);Description=$(Description)</NuspecProperties>
13 </PropertyGroup>
14
15 <ItemGroup>
16 <None Remove="build\WixToolset.Dtf.MSBuild.props" />
17 <None Remove="tools\wix.ca.targets" />
18 </ItemGroup>
19
20 <ItemGroup>
21 <Content Include="build\WixToolset.Dtf.MSBuild.props">
22 <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
23 </Content>
24 <Content Include="tools\wix.ca.targets">
25 <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
26 </Content>
27 </ItemGroup>
28
29 <ItemGroup>
30 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
31 </ItemGroup>
32
33 <PropertyGroup>
34 <GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);SetNuspecVersion</GenerateNuspecDependsOn>
35 </PropertyGroup>
36
37 <Target Name="SetNuspecVersion">
38 <Error Text="Cannot pack $(MSBuildThisFileName) until all projects are published to: '$(NuspecBasePath)'. Run appveyor.cmd to publish projects properly." Condition=" !Exists('$(NuspecBasePath)') " />
39
40 <PropertyGroup>
41 <NuspecProperties>$(NuspecProperties);Version=$(Version);ProjectFolder=$(MSBuildThisFileDirectory)</NuspecProperties>
42 </PropertyGroup>
43 </Target>
44</Project>
diff --git a/src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.nuspec b/src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.nuspec
new file mode 100644
index 00000000..7f819cdb
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.nuspec
@@ -0,0 +1,18 @@
1<?xml version="1.0" encoding="utf-8"?>
2<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
3 <metadata>
4 <id>$id$</id>
5 <version>$version$</version>
6 <authors>$authors$</authors>
7 <owners>$authors$</owners>
8 <license type="expression">MS-RL</license>
9 <requireLicenseAcceptance>false</requireLicenseAcceptance>
10 <description>$description$</description>
11 <copyright>$copyright$</copyright>
12 </metadata>
13
14 <files>
15 <file src="build\**\*" target="build" />
16 <file src="tools\**\*" target="tools" />
17 </files>
18</package>
diff --git a/src/dtf/WixToolset.Dtf.MSBuild/build/WixToolset.Dtf.MSBuild.props b/src/dtf/WixToolset.Dtf.MSBuild/build/WixToolset.Dtf.MSBuild.props
new file mode 100644
index 00000000..06a98d6e
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.MSBuild/build/WixToolset.Dtf.MSBuild.props
@@ -0,0 +1,8 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
5 <PropertyGroup>
6 <WixCATargetsPath Condition=" '$(WixCATargetsPath)' == '' ">$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\tools\wix.ca.targets'))</WixCATargetsPath>
7 </PropertyGroup>
8</Project>
diff --git a/src/dtf/WixToolset.Dtf.MSBuild/tools/wix.ca.targets b/src/dtf/WixToolset.Dtf.MSBuild/tools/wix.ca.targets
new file mode 100644
index 00000000..4578c2d8
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.MSBuild/tools/wix.ca.targets
@@ -0,0 +1,123 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
6
7 <Import Project="$(CustomBeforeWixCATargets)" Condition=" '$(CustomBeforeWixCATargets)' != '' and Exists('$(CustomBeforeWixCATargets)')" />
8
9 <PropertyGroup>
10 <WixCATargetsImported>true</WixCATargetsImported>
11
12 <TargetCAFileName Condition=" '$(TargetCAFileName)' == '' ">$(TargetName).CA$(TargetExt)</TargetCAFileName>
13
14 <WixSdkPath Condition=" '$(WixSdkPath)' == '' ">$(MSBuildThisFileDirectory)</WixSdkPath>
15 <WixSdkX86Path Condition=" '$(WixSdkX86Path)' == '' ">$(WixSdkPath)x86\</WixSdkX86Path>
16 <WixSdkX64Path Condition=" '$(WixSdkX64Path)' == '' ">$(WixSdkPath)x64\</WixSdkX64Path>
17
18 <MakeSfxCA Condition=" '$(MakeSfxCA)' == '' ">$(WixSdkPath)MakeSfxCA.exe</MakeSfxCA>
19 <SfxCADll Condition=" '$(SfxCADll)' == '' and '$(Platform)' == 'x64' ">$(WixSdkX64Path)SfxCA.dll</SfxCADll>
20 <SfxCADll Condition=" '$(SfxCADll)' == '' ">$(WixSdkX86Path)SfxCA.dll</SfxCADll>
21 </PropertyGroup>
22
23 <!--
24 ==================================================================================================
25 PackCustomAction
26
27 Creates an MSI managed custom action package that includes the custom action assembly,
28 local assembly dependencies, and project content files.
29
30 [IN]
31 @(IntermediateAssembly) - Managed custom action assembly.
32 @(Content) - Project items of type Content will be included in the package.
33 $(CustomActionContents) - Optional space-delimited list of additional files to include.
34
35 [OUT]
36 $(IntermediateOutputPath)$(TargetCAFileName) - Managed custom action package with unmanaged stub.
37 ==================================================================================================
38 -->
39 <Target Name="PackCustomAction"
40 Inputs="@(IntermediateAssembly);@(Content);$(CustomActionContents)"
41 Outputs="$(IntermediateOutputPath)$(TargetCAFileName)">
42
43 <!-- Find all referenced items marked CopyLocal, but exclude non-binary files. -->
44 <ItemGroup>
45 <CustomActionReferenceContents Include="@(ReferenceCopyLocalPaths)"
46 Condition=" '%(Extension)' == '.dll' or '%(Extension)' == '.exe' " />
47 <CustomActionReferenceContents Include="@(ReferenceComWrappersToCopyLocal)"
48 Condition=" '%(Extension)' == '.dll' or '%(Extension)' == '.exe' " />
49
50 <!-- include PDBs for Debug only -->
51 <CustomActionReferenceContents Include="@(IntermediateAssembly->'%(RootDir)%(Directory)%(Filename).pdb')"
52 Condition=" Exists('%(RootDir)%(Directory)%(Filename).pdb') and '$(Configuration)' == 'Debug' " />
53 <CustomActionReferenceContents Include="@(ReferenceCopyLocalPaths)"
54 Condition=" '%(Extension)' == '.pdb' and '$(Configuration)' == 'Debug' " />
55 <CustomActionReferenceContents Include="@(ReferenceComWrappersToCopyLocal)"
56 Condition=" '%(Extension)' == '.pdb' and '$(Configuration)' == 'Debug' " />
57 </ItemGroup>
58
59 <!--
60 Items to include in the CA package:
61 - Reference assemblies marked CopyLocal
62 - Project items of type Content
63 - Additional items in the CustomActionContents property
64 -->
65 <PropertyGroup>
66 <CustomActionContents>@(CustomActionReferenceContents);@(Content->'%(FullPath)');$(CustomActionContents)</CustomActionContents>
67 </PropertyGroup>
68
69 <ItemGroup>
70 <IntermediateCAAssembly Include="@(IntermediateAssembly->'%(FullPath)')" />
71 <IntermediateCAPackage Include="@(IntermediateAssembly->'%(RootDir)%(Directory)$(TargetCAFileName)')" />
72 </ItemGroup>
73
74 <!-- Run the MakeSfxCA.exe CA packaging tool. -->
75 <Exec Command='"$(MakeSfxCA)" "@(IntermediateCAPackage)" "$(SfxCADll)" "@(IntermediateCAAssembly)" "$(CustomActionContents)"'
76 WorkingDirectory="$(ProjectDir)" />
77
78 <!-- Add modules to be copied to output dir. -->
79 <ItemGroup>
80 <AddModules Include="@(IntermediateCAPackage)" />
81 </ItemGroup>
82 </Target>
83
84 <!--
85 ==================================================================================================
86 CleanCustomAction
87
88 Cleans the .CA.dll binary created by the PackCustomAction target.
89
90 ==================================================================================================
91 -->
92 <Target Name="CleanCustomAction">
93 <Delete Files="$(IntermediateOutputPath)$(TargetCAFileName)"
94 TreatErrorsAsWarnings="true" />
95 </Target>
96
97 <!--
98 ==================================================================================================
99 AfterCompile (redefinition)
100
101 Calls the PackCustomAction target after compiling.
102 Overrides the empty AfterCompile target from Microsoft.Common.targets.
103
104 ==================================================================================================
105 -->
106 <Target Name="AfterCompile"
107 DependsOnTargets="PackCustomAction" />
108
109 <!--
110 ==================================================================================================
111 BeforeClean (redefinition)
112
113 Calls the CleanCustomAction target before cleaning.
114 Overrides the empty AfterCompile target from Microsoft.Common.targets.
115
116 ==================================================================================================
117 -->
118 <Target Name="BeforeClean"
119 DependsOnTargets="CleanCustomAction" />
120
121 <Import Project="$(CustomAfterWixCATargets)" Condition=" '$(CustomAfterWixCATargets)' != '' and Exists('$(CustomAfterWixCATargets)')" />
122
123</Project>
diff --git a/src/dtf/WixToolset.Dtf.Resources/AssemblyInfo.cs b/src/dtf/WixToolset.Dtf.Resources/AssemblyInfo.cs
new file mode 100644
index 00000000..77f33f97
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Resources/AssemblyInfo.cs
@@ -0,0 +1,5 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System.Diagnostics.CodeAnalysis;
4
5[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "WixToolset.Dtf.Resources.ResourceCollection.#System.Collections.Generic.ICollection`1<WixToolset.Dtf.Resources.Resource>.IsReadOnly")]
diff --git a/src/dtf/WixToolset.Dtf.Resources/BitmapResource.cs b/src/dtf/WixToolset.Dtf.Resources/BitmapResource.cs
new file mode 100644
index 00000000..42c886cb
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Resources/BitmapResource.cs
@@ -0,0 +1,57 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Resources
4{
5 using System;
6 using System.IO;
7
8 /// <summary>
9 /// A subclass of Resource which provides specific methods for manipulating the resource data.
10 /// </summary>
11 /// <remarks>
12 /// The resource is of type <see cref="ResourceType.Bitmap"/> (RT_GROUPICON).
13 /// </remarks>
14 public sealed class BitmapResource : Resource
15 {
16 private const int SizeOfBitmapFileHeader = 14; // this is the sizeof(BITMAPFILEHEADER)
17
18 /// <summary>
19 /// Creates a new BitmapResource object without any data. The data can be later loaded from a file.
20 /// </summary>
21 /// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
22 /// <param name="locale">Locale of the resource</param>
23 public BitmapResource(string name, int locale)
24 : this(name, locale, null)
25 {
26 }
27
28 /// <summary>
29 /// Creates a new BitmapResource object with data. The data can be later saved to a file.
30 /// </summary>
31 /// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
32 /// <param name="locale">Locale of the resource</param>
33 /// <param name="data">Raw resource data</param>
34 public BitmapResource(string name, int locale, byte[] data)
35 : base(ResourceType.Bitmap, name, locale, data)
36 {
37 }
38
39 /// <summary>
40 /// Reads the bitmap from a .bmp file.
41 /// </summary>
42 /// <param name="path">Path to a bitmap file (.bmp).</param>
43 public void ReadFromFile(string path)
44 {
45 using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
46 {
47 // Move past the BITMAPFILEHEADER, and copy the rest of the bitmap as the resource data. Resource
48 // functions expect only the BITMAPINFO struct which exists just beyond the BITMAPFILEHEADER
49 // struct in bitmap files.
50 fs.Seek(BitmapResource.SizeOfBitmapFileHeader, SeekOrigin.Begin);
51
52 base.Data = new byte[fs.Length - BitmapResource.SizeOfBitmapFileHeader];
53 fs.Read(base.Data, 0, base.Data.Length);
54 }
55 }
56 }
57}
diff --git a/src/dtf/WixToolset.Dtf.Resources/FixedFileVersionInfo.cs b/src/dtf/WixToolset.Dtf.Resources/FixedFileVersionInfo.cs
new file mode 100644
index 00000000..e0d081db
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Resources/FixedFileVersionInfo.cs
@@ -0,0 +1,183 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Resources
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Reflection;
9 using System.Collections;
10 using System.Globalization;
11 using System.Diagnostics.CodeAnalysis;
12
13 internal class FixedFileVersionInfo
14 {
15 public FixedFileVersionInfo()
16 {
17 // Set reasonable defaults
18 this.signature = 0xFEEF04BD;
19 this.structVersion = 0x00010000; // v1.0
20 this.FileVersion = new Version(0, 0, 0, 0);
21 this.ProductVersion = new Version(0, 0, 0, 0);
22 this.FileFlagsMask = VersionBuildTypes.Debug | VersionBuildTypes.Prerelease;
23 this.FileFlags = VersionBuildTypes.None;
24 this.FileOS = VersionFileOS.NT_WINDOWS32;
25 this.FileType = VersionFileType.Application;
26 this.FileSubtype = VersionFileSubtype.Unknown;
27 this.Timestamp = DateTime.MinValue;
28 }
29
30 private uint signature;
31 private uint structVersion;
32
33 public Version FileVersion
34 {
35 get
36 {
37 return this.fileVersion;
38 }
39
40 set
41 {
42 if (value == null)
43 {
44 throw new InvalidOperationException();
45 }
46
47 this.fileVersion = value;
48 }
49 }
50 private Version fileVersion;
51
52 public Version ProductVersion
53 {
54 get
55 {
56 return this.productVersion;
57 }
58
59 set
60 {
61 if (value == null)
62 {
63 throw new InvalidOperationException();
64 }
65
66 this.productVersion = value;
67 }
68 }
69 private Version productVersion;
70
71 public VersionBuildTypes FileFlagsMask
72 {
73 get { return this.fileFlagsMask; }
74 set { this.fileFlagsMask = value; }
75 }
76 private VersionBuildTypes fileFlagsMask;
77
78 public VersionBuildTypes FileFlags
79 {
80 get { return this.fileFlags; }
81 set { this.fileFlags = value; }
82 }
83 private VersionBuildTypes fileFlags;
84
85 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
86 public VersionFileOS FileOS
87 {
88 get { return this.fileOS; }
89 set { this.fileOS = value; }
90 }
91 private VersionFileOS fileOS;
92
93 public VersionFileType FileType
94 {
95 get { return this.fileType; }
96 set { this.fileType = value; }
97 }
98 private VersionFileType fileType;
99
100 public VersionFileSubtype FileSubtype
101 {
102 get { return this.fileSubtype; }
103 set { this.fileSubtype = value; }
104 }
105 private VersionFileSubtype fileSubtype;
106
107 public DateTime Timestamp
108 {
109 get { return this.timestamp; }
110 set { this.timestamp = value; }
111 }
112 private DateTime timestamp;
113
114 public void Read(BinaryReader reader)
115 {
116 this.signature = reader.ReadUInt32();
117 this.structVersion = reader.ReadUInt32();
118 this.fileVersion = UInt64ToVersion(reader.ReadUInt64());
119 this.productVersion = UInt64ToVersion(reader.ReadUInt64());
120 this.fileFlagsMask = (VersionBuildTypes) reader.ReadInt32();
121 this.fileFlags = (VersionBuildTypes) reader.ReadInt32();
122 this.fileOS = (VersionFileOS) reader.ReadInt32();
123 this.fileType = (VersionFileType) reader.ReadInt32();
124 this.fileSubtype = (VersionFileSubtype) reader.ReadInt32();
125 this.timestamp = UInt64ToDateTime(reader.ReadUInt64());
126 }
127
128 public void Write(BinaryWriter writer)
129 {
130 writer.Write(this.signature);
131 writer.Write(this.structVersion);
132 writer.Write(VersionToUInt64(this.fileVersion));
133 writer.Write(VersionToUInt64(this.productVersion));
134 writer.Write((int) this.fileFlagsMask);
135 writer.Write((int) this.fileFlags);
136 writer.Write((int) this.fileOS);
137 writer.Write((int) this.fileType);
138 writer.Write((int) this.fileSubtype);
139 writer.Write(DateTimeToUInt64(this.timestamp));
140 }
141
142 public static explicit operator FixedFileVersionInfo(byte[] bytesValue)
143 {
144 FixedFileVersionInfo ffviValue = new FixedFileVersionInfo();
145 using (BinaryReader reader = new BinaryReader(new MemoryStream(bytesValue, false)))
146 {
147 ffviValue.Read(reader);
148 }
149 return ffviValue;
150 }
151
152 public static explicit operator byte[](FixedFileVersionInfo ffviValue)
153 {
154 const int FFVI_LENGTH = 52;
155
156 byte[] bytesValue = new byte[FFVI_LENGTH];
157 using (BinaryWriter writer = new BinaryWriter(new MemoryStream(bytesValue, true)))
158 {
159 ffviValue.Write(writer);
160 }
161 return bytesValue;
162 }
163
164 private static Version UInt64ToVersion(ulong version)
165 {
166 return new Version((int) ((version >> 16) & 0xFFFF), (int) (version & 0xFFFF), (int) (version >> 48), (int) ((version >> 32) & 0xFFFF));
167 }
168 private static ulong VersionToUInt64(Version version)
169 {
170 return (((ulong) (ushort) version.Major) << 16) | ((ulong) (ushort) version.Minor)
171 | (((ulong) (ushort) version.Build) << 48) | (((ulong) (ushort) version.Revision) << 32);
172 }
173
174 private static DateTime UInt64ToDateTime(ulong dateTime)
175 {
176 return (dateTime == 0 ? DateTime.MinValue : DateTime.FromFileTime((long) dateTime));
177 }
178 private static ulong DateTimeToUInt64(DateTime dateTime)
179 {
180 return (dateTime == DateTime.MinValue ? 0 : (ulong) dateTime.ToFileTime());
181 }
182 }
183}
diff --git a/src/dtf/WixToolset.Dtf.Resources/GroupIconInfo.cs b/src/dtf/WixToolset.Dtf.Resources/GroupIconInfo.cs
new file mode 100644
index 00000000..0fb56223
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Resources/GroupIconInfo.cs
@@ -0,0 +1,119 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Resources
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Reflection;
9 using System.Collections;
10 using System.Globalization;
11 using System.Diagnostics.CodeAnalysis;
12
13 internal enum GroupIconType
14 {
15 Unknown,
16 Icon,
17 Cursor,
18 }
19
20 internal struct GroupIconDirectoryInfo
21 {
22 public byte width;
23 public byte height;
24 public byte colors;
25 public byte reserved;
26 public ushort planes;
27 public ushort bitsPerPixel;
28 public uint imageSize;
29 public uint imageOffset; // only valid when icon group is read from .ico file.
30 public ushort imageIndex; // only valid when icon group is read from PE resource.
31 }
32
33 internal class GroupIconInfo
34 {
35 private ushort reserved;
36 private GroupIconType type;
37 private GroupIconDirectoryInfo[] images;
38
39 public GroupIconInfo()
40 {
41 this.images = new GroupIconDirectoryInfo[0];
42 }
43
44 public GroupIconDirectoryInfo[] DirectoryInfo { get { return this.images; } }
45
46 public void ReadFromFile(Stream stream)
47 {
48 BinaryReader reader = new BinaryReader(stream);
49 this.Read(reader, true);
50 }
51
52 public void ReadFromResource(byte[] data)
53 {
54 using (BinaryReader reader = new BinaryReader(new MemoryStream(data, false)))
55 {
56 this.Read(reader, false);
57 }
58 }
59
60 public byte[] GetResourceData()
61 {
62 byte[] data = null;
63
64 using (MemoryStream stream = new MemoryStream())
65 {
66 BinaryWriter writer = new BinaryWriter(stream);
67 writer.Write(this.reserved);
68 writer.Write((ushort)this.type);
69 writer.Write((ushort)this.images.Length);
70 for (int i = 0; i < this.images.Length; ++i)
71 {
72 writer.Write(this.images[i].width);
73 writer.Write(this.images[i].height);
74 writer.Write(this.images[i].colors);
75 writer.Write(this.images[i].reserved);
76 writer.Write(this.images[i].planes);
77 writer.Write(this.images[i].bitsPerPixel);
78 writer.Write(this.images[i].imageSize);
79 writer.Write(this.images[i].imageIndex);
80 }
81
82 data = new byte[stream.Length];
83 stream.Seek(0, SeekOrigin.Begin);
84 stream.Read(data, 0, data.Length);
85 }
86
87 return data;
88 }
89
90 private void Read(BinaryReader reader, bool readFromFile)
91 {
92 this.reserved = reader.ReadUInt16();
93 this.type = (GroupIconType)reader.ReadUInt16();
94
95 int imageCount = reader.ReadUInt16();
96 this.images = new GroupIconDirectoryInfo[imageCount];
97 for (int i = 0; i < imageCount; ++i)
98 {
99 this.images[i].width = reader.ReadByte();
100 this.images[i].height = reader.ReadByte();
101 this.images[i].colors = reader.ReadByte();
102 this.images[i].reserved = reader.ReadByte();
103 this.images[i].planes = reader.ReadUInt16();
104 this.images[i].bitsPerPixel = reader.ReadUInt16();
105 this.images[i].imageSize = reader.ReadUInt32();
106 if (readFromFile)
107 {
108 this.images[i].imageOffset = reader.ReadUInt32();
109 this.images[i].imageIndex = (ushort)(i + 1);
110 }
111 else
112 {
113 this.images[i].imageIndex = reader.ReadUInt16();
114 }
115 }
116 }
117
118 }
119}
diff --git a/src/dtf/WixToolset.Dtf.Resources/GroupIconResource.cs b/src/dtf/WixToolset.Dtf.Resources/GroupIconResource.cs
new file mode 100644
index 00000000..8820ce75
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Resources/GroupIconResource.cs
@@ -0,0 +1,120 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Resources
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Reflection;
9 using System.Collections;
10 using System.Collections.Generic;
11 using System.Globalization;
12 using System.Diagnostics.CodeAnalysis;
13
14 /// <summary>
15 /// A subclass of Resource which provides specific methods for manipulating the resource data.
16 /// </summary>
17 /// <remarks>
18 /// The resource is of type <see cref="ResourceType.GroupIcon"/> (RT_GROUPICON).
19 /// </remarks>
20 public sealed class GroupIconResource : Resource
21 {
22 internal bool dirty;
23 private GroupIconInfo rawGroupIconInfo;
24 private List<Resource> icons;
25
26 /// <summary>
27 /// Creates a new GroupIconResource object without any data. The data can be later loaded from a file.
28 /// </summary>
29 /// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
30 /// <param name="locale">Locale of the resource</param>
31 public GroupIconResource(string name, int locale)
32 : this(name, locale, null)
33 {
34 }
35
36 /// <summary>
37 /// Creates a new GroupIconResource object with data. The data can be later saved to a file.
38 /// </summary>
39 /// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
40 /// <param name="locale">Locale of the resource</param>
41 /// <param name="data">Raw resource data</param>
42 public GroupIconResource(string name, int locale, byte[] data)
43 : base(ResourceType.GroupIcon, name, locale, data)
44 {
45 this.RefreshIconGroupInfo(data);
46 }
47
48 /// <summary>
49 /// Gets or sets the raw data of the resource. The data is in the format of the RT_GROUPICON resource structure.
50 /// </summary>
51 public override byte[] Data
52 {
53 get
54 {
55 if (this.dirty)
56 {
57 base.Data = this.rawGroupIconInfo.GetResourceData();
58 this.dirty = false;
59 }
60
61 return base.Data;
62 }
63 set
64 {
65 this.RefreshIconGroupInfo(value);
66
67 base.Data = value;
68 this.dirty = false;
69 }
70 }
71
72 /// <summary>
73 /// Enumerates the the icons in the icon group.
74 /// </summary>
75 public IEnumerable<Resource> Icons { get { return this.icons; } }
76
77 /// <summary>
78 /// Reads the icon group from a .ico file.
79 /// </summary>
80 /// <param name="path">Path to an icon file (.ico).</param>
81 public void ReadFromFile(string path)
82 {
83 this.rawGroupIconInfo = new GroupIconInfo();
84 this.icons = new List<Resource>();
85 using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))
86 {
87 this.rawGroupIconInfo.ReadFromFile(fs);
88
89 // After reading the group icon info header from the file, read all the icons.
90 for (int i = 0; i < this.rawGroupIconInfo.DirectoryInfo.Length; ++i)
91 {
92 ushort index = this.rawGroupIconInfo.DirectoryInfo[i].imageIndex;
93 uint offset = this.rawGroupIconInfo.DirectoryInfo[i].imageOffset;
94 uint size = this.rawGroupIconInfo.DirectoryInfo[i].imageSize;
95 byte[] data = new byte[size];
96
97 fs.Seek(offset, SeekOrigin.Begin);
98 fs.Read(data, 0, data.Length);
99
100 Resource resource = new Resource(ResourceType.Icon, String.Concat("#", index), this.Locale, data);
101 this.icons.Add(resource);
102 }
103 }
104
105 this.dirty = true;
106 }
107
108 private void RefreshIconGroupInfo(byte[] refreshData)
109 {
110 this.rawGroupIconInfo = new GroupIconInfo();
111 this.icons = new List<Resource>();
112 if (refreshData != null)
113 {
114 this.rawGroupIconInfo.ReadFromResource(refreshData);
115 }
116
117 this.dirty = true;
118 }
119 }
120}
diff --git a/src/dtf/WixToolset.Dtf.Resources/NativeMethods.cs b/src/dtf/WixToolset.Dtf.Resources/NativeMethods.cs
new file mode 100644
index 00000000..bf1bd1dc
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Resources/NativeMethods.cs
@@ -0,0 +1,55 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Resources
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Reflection;
9 using System.Collections;
10 using System.Globalization;
11 using System.Runtime.InteropServices;
12
13 internal static class NativeMethods
14 {
15 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
16 internal static extern IntPtr LoadLibraryEx(string fileName, IntPtr hFile, uint flags);
17 internal const uint LOAD_LIBRARY_AS_DATAFILE = 2;
18 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
19 internal static extern bool FreeLibrary(IntPtr module);
20 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
21 internal static extern bool EnumResourceTypes(IntPtr module, EnumResTypesProc enumFunc, IntPtr param);
22 [return: MarshalAs(UnmanagedType.Bool)]
23 internal delegate bool EnumResTypesProc(IntPtr module, IntPtr type, IntPtr param);
24 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
25 internal static extern bool EnumResourceNames(IntPtr module, IntPtr type, EnumResNamesProc enumFunc, IntPtr param);
26 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
27 internal static extern bool EnumResourceNames(IntPtr module, string type, EnumResNamesProc enumFunc, IntPtr param);
28 [return: MarshalAs(UnmanagedType.Bool)]
29 internal delegate bool EnumResNamesProc(IntPtr module, IntPtr type, IntPtr name, IntPtr param);
30 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
31 internal static extern bool EnumResourceLanguages(IntPtr module, IntPtr type, IntPtr name, EnumResLangsProc enumFunc, IntPtr param);
32 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
33 internal static extern bool EnumResourceLanguages(IntPtr module, string type, string name, EnumResLangsProc enumFunc, IntPtr param);
34 [return: MarshalAs(UnmanagedType.Bool)]
35 internal delegate bool EnumResLangsProc(IntPtr module, IntPtr type, IntPtr name, ushort langId, IntPtr param);
36 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
37 internal static extern IntPtr FindResourceEx(IntPtr module, string type, string name, ushort langId);
38 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
39 internal static extern IntPtr LoadResource(IntPtr module, IntPtr resourceInfo);
40 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
41 internal static extern IntPtr LockResource(IntPtr resourceData);
42 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
43 internal static extern uint SizeofResource(IntPtr module, IntPtr resourceInfo);
44 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
45 internal static extern IntPtr BeginUpdateResource(string fileName, [MarshalAs(UnmanagedType.Bool)] bool deleteExistingResources);
46 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
47 internal static extern bool UpdateResource(IntPtr updateHandle, IntPtr type, IntPtr name, ushort lcid, IntPtr data, uint dataSize);
48 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]
49 internal static extern bool UpdateResource(IntPtr updateHandle, IntPtr type, string name, ushort lcid, IntPtr data, uint dataSize);
50 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
51 internal static extern bool UpdateResource(IntPtr updateHandle, string type, string name, ushort lcid, IntPtr data, uint dataSize);
52 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
53 internal static extern bool EndUpdateResource(IntPtr updateHandle, [MarshalAs(UnmanagedType.Bool)] bool discardChanges);
54 }
55}
diff --git a/src/dtf/WixToolset.Dtf.Resources/Resource.cs b/src/dtf/WixToolset.Dtf.Resources/Resource.cs
new file mode 100644
index 00000000..23b4621f
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Resources/Resource.cs
@@ -0,0 +1,225 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Resources
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Reflection;
9 using System.Collections;
10 using System.Globalization;
11 using System.Runtime.InteropServices;
12 using System.Diagnostics.CodeAnalysis;
13
14 /// <summary>
15 /// Represents a Win32 resource which can be loaded from and saved to a PE file.
16 /// </summary>
17 public class Resource
18 {
19 private ResourceType type;
20 private string name;
21 private int locale;
22 private byte[] data;
23
24 /// <summary>
25 /// Creates a new Resource object without any data. The data can be later loaded from a file.
26 /// </summary>
27 /// <param name="type">Type of the resource; may be one of the ResourceType constants or a user-defined type.</param>
28 /// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
29 /// <param name="locale">Locale of the resource</param>
30 public Resource(ResourceType type, string name, int locale)
31 : this(type, name, locale, null)
32 {
33 }
34
35 /// <summary>
36 /// Creates a new Resource object with data. The data can be later saved to a file.
37 /// </summary>
38 /// <param name="type">Type of the resource; may be one of the ResourceType constants or a user-defined type.</param>
39 /// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
40 /// <param name="locale">Locale of the resource</param>
41 /// <param name="data">Raw resource data</param>
42 public Resource(ResourceType type, string name, int locale, byte[] data)
43 {
44 if (name == null)
45 {
46 throw new ArgumentNullException("name");
47 }
48
49 this.type = type;
50 this.name = name;
51 this.locale = locale;
52 this.data = data;
53 }
54
55 /// <summary>
56 /// Gets or sets the type of the resource. This may be one of the ResourceType constants
57 /// or a user-defined type name.
58 /// </summary>
59 public ResourceType ResourceType
60 {
61 get { return this.type; }
62 set { this.type = value; }
63 }
64
65 /// <summary>
66 /// Gets or sets the name of the resource. For a numeric resource identifier, the decimal number is prefixed with a "#".
67 /// </summary>
68 public string Name
69 {
70 get
71 {
72 return this.name;
73 }
74
75 set
76 {
77 if (value == null)
78 {
79 throw new ArgumentNullException("value");
80 }
81
82 this.name = value;
83 }
84 }
85
86 /// <summary>
87 /// Gets or sets the locale of the resource.
88 /// </summary>
89 public int Locale
90 {
91 get { return this.locale; }
92 set { this.locale = value; }
93 }
94
95 /// <summary>
96 /// Gets or sets the raw data of the resource.
97 /// </summary>
98 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
99 public virtual byte[] Data
100 {
101 get { return this.data; }
102 set { this.data = value; }
103 }
104
105 /// <summary>
106 /// Loads the resource data from a file. The file is searched for a resource with matching type, name, and locale.
107 /// </summary>
108 /// <param name="file">Win32 PE file containing the resource</param>
109 public void Load(string file)
110 {
111 IntPtr module = NativeMethods.LoadLibraryEx(file, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
112 try
113 {
114 this.Load(module);
115 }
116 finally
117 {
118 NativeMethods.FreeLibrary(module);
119 }
120 }
121
122 internal void Load(IntPtr module)
123 {
124 IntPtr resourceInfo = NativeMethods.FindResourceEx(module, (string) this.ResourceType, this.Name, (ushort) this.Locale);
125 if (resourceInfo != IntPtr.Zero)
126 {
127 uint resourceLength = NativeMethods.SizeofResource(module, resourceInfo);
128 IntPtr resourceData = NativeMethods.LoadResource(module, resourceInfo);
129 IntPtr resourcePtr = NativeMethods.LockResource(resourceData);
130 byte[] resourceBytes = new byte[resourceLength];
131 Marshal.Copy(resourcePtr, resourceBytes, 0, resourceBytes.Length);
132 this.Data = resourceBytes;
133 }
134 else
135 {
136 this.Data = null;
137 }
138 }
139
140 /// <summary>
141 /// Saves the resource to a file. Any existing resource data with matching type, name, and locale is overwritten.
142 /// </summary>
143 /// <param name="file">Win32 PE file to contain the resource</param>
144 public void Save(string file)
145 {
146 IntPtr updateHandle = IntPtr.Zero;
147 try
148 {
149 updateHandle = NativeMethods.BeginUpdateResource(file, false);
150 this.Save(updateHandle);
151 if (!NativeMethods.EndUpdateResource(updateHandle, false))
152 {
153 int err = Marshal.GetLastWin32Error();
154 throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to save resource. Error code: {0}", err));
155 }
156 updateHandle = IntPtr.Zero;
157 }
158 finally
159 {
160 if (updateHandle != IntPtr.Zero)
161 {
162 NativeMethods.EndUpdateResource(updateHandle, true);
163 }
164 }
165 }
166
167 internal void Save(IntPtr updateHandle)
168 {
169 IntPtr dataPtr = IntPtr.Zero;
170 try
171 {
172 int dataLength = 0;
173 if (this.Data != null)
174 {
175 dataLength = this.Data.Length;
176 dataPtr = Marshal.AllocHGlobal(dataLength);
177 Marshal.Copy(this.Data, 0, dataPtr, dataLength);
178 }
179 bool updateSuccess;
180 if (this.Name.StartsWith("#", StringComparison.Ordinal))
181 {
182 // A numeric-named resource must be saved via the integer version of UpdateResource.
183 IntPtr intName = new IntPtr(Int32.Parse(this.Name.Substring(1), CultureInfo.InvariantCulture));
184 updateSuccess = NativeMethods.UpdateResource(updateHandle, new IntPtr(this.ResourceType.IntegerValue), intName, (ushort) this.Locale, dataPtr, (uint) dataLength);
185 }
186 else
187 {
188 updateSuccess = NativeMethods.UpdateResource(updateHandle, (string) this.ResourceType, this.Name, (ushort) this.Locale, dataPtr, (uint) dataLength);
189 }
190 if (!updateSuccess)
191 {
192 throw new IOException("Failed to save resource. Error: " + Marshal.GetLastWin32Error());
193 }
194 }
195 finally
196 {
197 if (dataPtr != IntPtr.Zero)
198 {
199 Marshal.FreeHGlobal(dataPtr);
200 }
201 }
202 }
203
204 /// <summary>
205 /// Tests if type, name, and locale of this Resource object match another Resource object.
206 /// </summary>
207 /// <param name="obj">Resource object to be compared</param>
208 /// <returns>True if the objects represent the same resource; false otherwise.</returns>
209 public override bool Equals(object obj)
210 {
211 Resource res = obj as Resource;
212 if (res == null) return false;
213 return this.ResourceType == res.ResourceType && this.Name == res.Name && this.Locale == res.Locale;
214 }
215
216 /// <summary>
217 /// Gets a hash code for this Resource object.
218 /// </summary>
219 /// <returns>Hash code generated from the resource type, name, and locale.</returns>
220 public override int GetHashCode()
221 {
222 return this.ResourceType.GetHashCode() ^ this.Name.GetHashCode() ^ this.Locale.GetHashCode();
223 }
224 }
225}
diff --git a/src/dtf/WixToolset.Dtf.Resources/ResourceCollection.cs b/src/dtf/WixToolset.Dtf.Resources/ResourceCollection.cs
new file mode 100644
index 00000000..5d9e5653
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Resources/ResourceCollection.cs
@@ -0,0 +1,340 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Resources
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Reflection;
9 using System.Collections;
10 using System.Collections.Generic;
11 using System.Globalization;
12 using System.Runtime.InteropServices;
13 using System.Diagnostics.CodeAnalysis;
14
15 /// <summary>
16 /// Allows reading and editing of resource data in a Win32 PE file.
17 /// </summary>
18 /// <remarks>
19 /// To use this class:<list type="number">
20 /// <item>Create a new ResourceCollection</item>
21 /// <item>Locate resources for the collection by calling one of the <see cref="ResourceCollection.Find(string)"/> methods</item>
22 /// <item>Load data of one or more <see cref="Resource"/>s from a file by calling the <see cref="Load"/> method of the
23 /// Resource class, or load them all at once (more efficient) with the <see cref="Load"/> method of the ResourceCollection.</item>
24 /// <item>Read and/or edit data of the individual Resource objects using the methods on that class.</item>
25 /// <item>Save data of one or more <see cref="Resource"/>s to a file by calling the <see cref="Save"/> method of the
26 /// Resource class, or save them all at once (more efficient) with the <see cref="Save"/> method of the ResourceCollection.</item>
27 /// </list>
28 /// </remarks>
29 public class ResourceCollection : ICollection<Resource>
30 {
31 private List<Resource> resources;
32
33 /// <summary>
34 /// Creates a new, empty ResourceCollection.
35 /// </summary>
36 public ResourceCollection()
37 {
38 this.resources = new List<Resource>();
39 }
40
41 /// <summary>
42 /// Locates all resources in a file, including all resource types and languages. For each located resource,
43 /// a <see cref="Resource"/> instance (or subclass) is added to the collection.
44 /// </summary>
45 /// <param name="resFile">The file to be searched for resources.</param>
46 /// <exception cref="IOException">resources could not be read from the file</exception>
47 public void Find(string resFile)
48 {
49 this.Clear();
50
51 IntPtr module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
52 if (module == IntPtr.Zero)
53 {
54 int err = Marshal.GetLastWin32Error();
55 throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to load resource file. Error code: {0}", err));
56 }
57 try
58 {
59 if (!NativeMethods.EnumResourceTypes(module, new NativeMethods.EnumResTypesProc(this.EnumResTypes), IntPtr.Zero))
60 {
61 int err = Marshal.GetLastWin32Error();
62 throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to enumerate resources. Error code: {0}", err));
63 }
64 }
65 finally
66 {
67 NativeMethods.FreeLibrary(module);
68 }
69 }
70
71 /// <summary>
72 /// Locates all resources in a file of a given type, including all languages. For each located resource,
73 /// a <see cref="Resource"/> instance (or subclass) is added to the collection.
74 /// </summary>
75 /// <param name="resFile">The file to be searched for resources.</param>
76 /// <param name="type">The type of resource to search for; may be one of the ResourceType constants or a user-defined type.</param>
77 /// <exception cref="IOException">resources could not be read from the file</exception>
78 public void Find(string resFile, ResourceType type)
79 {
80 this.Clear();
81
82 IntPtr module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
83 try
84 {
85 if (!NativeMethods.EnumResourceNames(module, (string) type, new NativeMethods.EnumResNamesProc(this.EnumResNames), IntPtr.Zero))
86 {
87 int err = Marshal.GetLastWin32Error();
88 throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceNames error. Error code: {0}", err));
89 }
90 }
91 finally
92 {
93 NativeMethods.FreeLibrary(module);
94 }
95 }
96
97 /// <summary>
98 /// Locates all resources in a file of a given type and language. For each located resource,
99 /// a <see cref="Resource"/> instance (or subclass) is added to the collection.
100 /// </summary>
101 /// <param name="resFile">The file to be searched for resources.</param>
102 /// <param name="type">The type of resource to search for; may be one of the ResourceType constants or a user-defined type.</param>
103 /// <param name="name">The name of the resource to search for.</param>
104 /// <exception cref="IOException">resources could not be read from the file</exception>
105 public void Find(string resFile, ResourceType type, string name)
106 {
107 this.Clear();
108
109 IntPtr module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
110 try
111 {
112 if (!NativeMethods.EnumResourceLanguages(module, (string) type, name, new NativeMethods.EnumResLangsProc(this.EnumResLangs), IntPtr.Zero))
113 {
114 int err = Marshal.GetLastWin32Error();
115 throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceLanguages error. Error code: {0}", err));
116 }
117 }
118 finally
119 {
120 NativeMethods.FreeLibrary(module);
121 }
122 }
123
124 private bool EnumResTypes(IntPtr module, IntPtr type, IntPtr param)
125 {
126 if (!NativeMethods.EnumResourceNames(module, type, new NativeMethods.EnumResNamesProc(EnumResNames), IntPtr.Zero))
127 {
128 int err = Marshal.GetLastWin32Error();
129 throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceNames error! Error code: {0}", err));
130 }
131 return true;
132 }
133
134 private bool EnumResNames(IntPtr module, IntPtr type, IntPtr name, IntPtr param)
135 {
136 if (!NativeMethods.EnumResourceLanguages(module, type, name, new NativeMethods.EnumResLangsProc(EnumResLangs), IntPtr.Zero))
137 {
138 int err = Marshal.GetLastWin32Error();
139 throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceLanguages error. Error code: {0}", err));
140 }
141 return true;
142 }
143
144 private bool EnumResLangs(IntPtr module, IntPtr type, IntPtr name, ushort langId, IntPtr param)
145 {
146 Resource res;
147 if (((int) type) == ResourceType.Version.IntegerValue)
148 {
149 res = new VersionResource(ResourceNameToString(name), langId);
150 }
151 else
152 {
153 res = new Resource(ResourceNameToString(type), ResourceNameToString(name), langId);
154 }
155
156 if (!this.Contains(res))
157 {
158 this.Add(res);
159 }
160
161 return true;
162 }
163
164 private static string ResourceNameToString(IntPtr resName)
165 {
166 if ((resName.ToInt64() >> 16) == 0)
167 {
168 return "#" + resName.ToString();
169 }
170 else
171 {
172 return Marshal.PtrToStringAuto(resName);
173 }
174 }
175
176 /// <summary>
177 /// For all resources in the collection, loads their data from a resource file.
178 /// </summary>
179 /// <param name="file">The file from which resources are loaded.</param>
180 public void Load(string file)
181 {
182 IntPtr module = NativeMethods.LoadLibraryEx(file, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
183 try
184 {
185 foreach (Resource res in this)
186 {
187 res.Load(module);
188 }
189 }
190 finally
191 {
192 NativeMethods.FreeLibrary(module);
193 }
194 }
195
196 /// <summary>
197 /// For all resources in the collection, saves their data to a resource file.
198 /// </summary>
199 /// <param name="file">The file to which resources are saved.</param>
200 public void Save(string file)
201 {
202 IntPtr updateHandle = IntPtr.Zero;
203 try
204 {
205 updateHandle = NativeMethods.BeginUpdateResource(file, false);
206 foreach (Resource res in this)
207 {
208 res.Save(updateHandle);
209 }
210 if (!NativeMethods.EndUpdateResource(updateHandle, false))
211 {
212 int err = Marshal.GetLastWin32Error();
213 throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to save resource. Error {0}", err));
214 }
215 updateHandle = IntPtr.Zero;
216 }
217 finally
218 {
219 if (updateHandle != IntPtr.Zero)
220 {
221 NativeMethods.EndUpdateResource(updateHandle, true);
222 }
223 }
224 }
225
226 /// <summary>
227 /// Gets or sets the element at the specified index.
228 /// </summary>
229 public Resource this[int index]
230 {
231 get
232 {
233 return (Resource) this.resources[index];
234 }
235 set
236 {
237 this.resources[index] = value;
238 }
239 }
240
241 /// <summary>
242 /// Adds a new item to the collection.
243 /// </summary>
244 /// <param name="item">The Resource to add.</param>
245 public void Add(Resource item)
246 {
247 this.resources.Add(item);
248 }
249
250 /// <summary>
251 /// Removes an item to the collection.
252 /// </summary>
253 /// <param name="item">The Resource to remove.</param>
254 public bool Remove(Resource item)
255 {
256 return this.resources.Remove(item);
257 }
258
259 /// <summary>
260 /// Gets the index of an item in the collection.
261 /// </summary>
262 /// <param name="item">The Resource to search for.</param>
263 /// <returns>The index of the item, or -1 if not found.</returns>
264 public int IndexOf(Resource item)
265 {
266 return this.resources.IndexOf(item);
267 }
268
269 /// <summary>
270 /// Inserts a item into the collection.
271 /// </summary>
272 /// <param name="index">The insertion index.</param>
273 /// <param name="item">The Resource to insert.</param>
274 public void Insert(int index, Resource item)
275 {
276 this.resources.Insert(index, item);
277 }
278
279 /// <summary>
280 /// Tests if the collection contains an item.
281 /// </summary>
282 /// <param name="item">The Resource to search for.</param>
283 /// <returns>true if the item is found; false otherwise</returns>
284 public bool Contains(Resource item)
285 {
286 return this.resources.Contains(item);
287 }
288
289 /// <summary>
290 /// Copies the collection into an array.
291 /// </summary>
292 /// <param name="array">The array to copy into.</param>
293 /// <param name="arrayIndex">The starting index in the destination array.</param>
294 public void CopyTo(Resource[] array, int arrayIndex)
295 {
296 this.resources.CopyTo(array, arrayIndex);
297 }
298
299 /// <summary>
300 /// Removes all resources from the collection.
301 /// </summary>
302 public void Clear()
303 {
304 this.resources.Clear();
305 }
306
307 /// <summary>
308 /// Gets the number of resources in the collection.
309 /// </summary>
310 public int Count
311 {
312 get
313 {
314 return this.resources.Count;
315 }
316 }
317
318 /// <summary>
319 /// Gets an enumerator over all resources in the collection.
320 /// </summary>
321 /// <returns></returns>
322 public IEnumerator<Resource> GetEnumerator()
323 {
324 return this.resources.GetEnumerator();
325 }
326
327 IEnumerator IEnumerable.GetEnumerator()
328 {
329 return ((IEnumerable) this.resources).GetEnumerator();
330 }
331
332 bool ICollection<Resource>.IsReadOnly
333 {
334 get
335 {
336 return false;
337 }
338 }
339 }
340}
diff --git a/src/dtf/WixToolset.Dtf.Resources/ResourceType.cs b/src/dtf/WixToolset.Dtf.Resources/ResourceType.cs
new file mode 100644
index 00000000..4b3971ab
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Resources/ResourceType.cs
@@ -0,0 +1,198 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Resources
4{
5 using System;
6 using System.Diagnostics.CodeAnalysis;
7 using System.Globalization;
8
9 /// <summary>
10 /// Represents either a standard integer resource type or a custom resource type name.
11 /// </summary>
12 public class ResourceType
13 {
14 // Silence warnings about doc-comments
15 #pragma warning disable 1591
16
17 public static ResourceType None { get { return "#0"; } }
18 public static ResourceType Cursor { get { return "#1"; } }
19 public static ResourceType Bitmap { get { return "#2"; } }
20 public static ResourceType Icon { get { return "#3"; } }
21 public static ResourceType Menu { get { return "#4"; } }
22 public static ResourceType Dialog { get { return "#5"; } }
23 public static ResourceType String { get { return "#6"; } }
24 public static ResourceType FontDir { get { return "#7"; } }
25 public static ResourceType Font { get { return "#8"; } }
26 public static ResourceType Accelerator { get { return "#9"; } }
27 public static ResourceType RCData { get { return "#10"; } }
28 public static ResourceType MessageTable { get { return "#11"; } }
29 public static ResourceType GroupCursor { get { return "#12"; } }
30 public static ResourceType GroupIcon { get { return "#14"; } }
31 public static ResourceType Version { get { return "#16"; } }
32 public static ResourceType DialogInclude { get { return "#17"; } }
33 public static ResourceType PlugPlay { get { return "#19"; } }
34 public static ResourceType Vxd { get { return "#20"; } }
35 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ani")]
36 public static ResourceType AniCursor { get { return "#21"; } }
37 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ani")]
38 public static ResourceType AniIcon { get { return "#22"; } }
39 public static ResourceType Html { get { return "#23"; } }
40 public static ResourceType Manifest { get { return "#24"; } }
41
42 #pragma warning restore 1591
43
44 private string resourceType;
45
46 /// <summary>
47 /// Creates a new resource type from a string resource name.
48 /// </summary>
49 /// <param name="resourceType">String resource name,
50 /// or an integer resource type prefixed by a #.</param>
51 public ResourceType(string resourceType)
52 {
53 if (string.IsNullOrEmpty(resourceType))
54 {
55 throw new ArgumentNullException("resourceType");
56 }
57
58 this.resourceType = resourceType;
59
60 if (this.IsInteger && this.IntegerValue < 0)
61 {
62 throw new ArgumentOutOfRangeException("Invalid integer resource type value.");
63 }
64 }
65
66 /// <summary>
67 /// Creates a new integer resource type.
68 /// </summary>
69 /// <param name="resourceType">Integer value of a well-known resource type.</param>
70 public ResourceType(int resourceType)
71 : this("#" + resourceType)
72 {
73 }
74
75 /// <summary>
76 /// Gets a flag indicating whether the resource type is an integer type.
77 /// </summary>
78 public bool IsInteger
79 {
80 get
81 {
82 return this.resourceType.StartsWith("#", StringComparison.Ordinal);
83 }
84 }
85
86 /// <summary>
87 /// Gets the integer value of the resource type, or -1 if the resource type is not an integer.
88 /// </summary>
89 public int IntegerValue
90 {
91 get
92 {
93 int value;
94 if (!this.IsInteger ||
95 !Int32.TryParse(this.resourceType.Substring(1), out value))
96 {
97 value = -1;
98 }
99
100 return value;
101 }
102 }
103
104 /// <summary>
105 /// Gets a string representation of the resource type.
106 /// </summary>
107 /// <returns>The custom resource name, or the name of a well-known resource type.</returns>
108 public override string ToString()
109 {
110 if (this.IsInteger)
111 {
112 switch (this.IntegerValue)
113 {
114 case 0: return "None";
115 case 1: return "Cursor";
116 case 2: return "Bitmap";
117 case 3: return "Icon";
118 case 4: return "Menu";
119 case 5: return "Dialog";
120 case 6: return "String";
121 case 7: return "FontDir";
122 case 8: return "Font";
123 case 9: return "Accelerator";
124 case 10: return "RCData";
125 case 11: return "MessageTable";
126 case 12: return "GroupCursor";
127 case 14: return "GroupIcon";
128 case 16: return "Version";
129 case 17: return "DialogInclude";
130 case 19: return "PlugPlay";
131 case 20: return "Vxd";
132 case 21: return "AniCursor";
133 case 22: return "AniIcon";
134 case 23: return "Html";
135 case 24: return "Manifest";
136 }
137 }
138
139 return this.resourceType;
140 }
141
142 /// <summary>
143 /// Tests whether one resource type equals another object.
144 /// </summary>
145 /// <param name="obj">Other object.</param>
146 /// <returns>True if equal, else false.</returns>
147 public override bool Equals(object obj)
148 {
149 return this.Equals(obj as ResourceType);
150 }
151
152 /// <summary>
153 /// Tests whether one resource type equals another.
154 /// </summary>
155 /// <param name="otherType">Other resource type.</param>
156 /// <returns>True if equal, else false.</returns>
157 public bool Equals(ResourceType otherType)
158 {
159 return otherType != null && this.resourceType.Equals(otherType.resourceType, StringComparison.Ordinal);
160 }
161
162 /// <summary>
163 /// Gets a hash code suitable for using the resource type as a dictionary key.
164 /// </summary>
165 /// <returns>Hash code based on the resource type string.</returns>
166 public override int GetHashCode()
167 {
168 return this.resourceType.GetHashCode();
169 }
170
171 /// <summary>
172 /// Implicitly converts a string to a ResourceType.
173 /// </summary>
174 /// <param name="resourceType">String resource type to convert.</param>
175 /// <returns>ResourceType object.</returns>
176 public static implicit operator ResourceType(string resourceType)
177 {
178 return new ResourceType(resourceType);
179 }
180
181 /// <summary>
182 /// Explicitly converts a ResourceType to a string.
183 /// </summary>
184 /// <param name="resourceType">ResourceType object to convert.</param>
185 /// <returns>The resource type string.</returns>
186 /// <remarks>
187 /// Unlike <see cref="ToString" />, this conversion does not return
188 /// the common name of well-known integer resource types. Therefore,
189 /// the returned string is suitable for passing directly to Win32
190 /// resource APIs that accept resource type strings.
191 /// </remarks>
192 public static explicit operator string(ResourceType resourceType)
193 {
194 return resourceType.resourceType;
195 }
196 }
197}
198
diff --git a/src/dtf/WixToolset.Dtf.Resources/VersionEnums.cs b/src/dtf/WixToolset.Dtf.Resources/VersionEnums.cs
new file mode 100644
index 00000000..96e54b51
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Resources/VersionEnums.cs
@@ -0,0 +1,86 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Resources
4{
5 // Silence warnings about doc-comments
6 #pragma warning disable 1591
7
8 using System;
9 using System.Diagnostics.CodeAnalysis;
10
11 /// <summary>
12 /// Identifies build types of a versioned file.
13 /// </summary>
14 [Flags]
15 public enum VersionBuildTypes : int
16 {
17 None = 0x00,
18 Debug = 0x01,
19 Prerelease = 0x02,
20 Patched = 0x04,
21 PrivateBuild = 0x08,
22 InfoInferred = 0x10,
23 SpecialBuild = 0x20,
24 }
25
26 [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")]
27 internal enum VersionFileOS : int
28 {
29 Unknown = 0x00000000,
30 DOS = 0x00010000,
31 OS216 = 0x00020000,
32 OS232 = 0x00030000,
33 NT = 0x00040000,
34 WINCE = 0x00050000,
35 WINDOWS16 = 0x00000001,
36 PM16 = 0x00000002,
37 PM32 = 0x00000003,
38 WINDOWS32 = 0x00000004,
39 DOS_WINDOWS16 = 0x00010001,
40 DOS_WINDOWS32 = 0x00010004,
41 OS216_PM16 = 0x00020002,
42 OS232_PM32 = 0x00030003,
43 NT_WINDOWS32 = 0x00040004,
44 }
45
46 /// <summary>
47 /// Identifies the type of a versioned file.
48 /// </summary>
49 [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")]
50 public enum VersionFileType : int
51 {
52 Unknown = 0,
53 Application = 1,
54 Dll = 2,
55 Driver = 3,
56 Font = 4,
57 VirtualDevice = 5,
58 StaticLibrary = 7,
59 }
60
61 /// <summary>
62 /// Identifies the sub-type of a versioned file.
63 /// </summary>
64 public enum VersionFileSubtype : int
65 {
66 Unknown = 0,
67 PrinterDriver = 1,
68 KeyboardDriver = 2,
69 LanguageDriver = 3,
70 DisplayDriver = 4,
71 MouseDriver = 5,
72 NetworkDriver = 6,
73 SystemDriver = 7,
74 InstallableDriver = 8,
75 SoundDriver = 9,
76 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Comm")]
77 CommDriver = 10,
78 InputMethodDriver = 11,
79 VersionedPrinterDriver = 12,
80 RasterFont = 1,
81 VectorFont = 2,
82 TrueTypeFont = 3,
83 }
84
85 #pragma warning restore 1591
86}
diff --git a/src/dtf/WixToolset.Dtf.Resources/VersionInfo.cs b/src/dtf/WixToolset.Dtf.Resources/VersionInfo.cs
new file mode 100644
index 00000000..38224d12
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Resources/VersionInfo.cs
@@ -0,0 +1,270 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Resources
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Reflection;
9 using System.Collections;
10 using System.Collections.Generic;
11 using System.Globalization;
12
13 internal class VersionInfo : ICollection<VersionInfo>
14 {
15 private string key;
16 private bool isString;
17 private byte[] data;
18 private List<VersionInfo> children;
19
20 public VersionInfo(string key)
21 : base()
22 {
23 if (key == null)
24 {
25 throw new ArgumentNullException("key");
26 }
27
28 this.key = key;
29 this.children = new List<VersionInfo>();
30 }
31
32 public string Key
33 {
34 get
35 {
36 return this.key;
37 }
38
39 set
40 {
41 if (value == null)
42 {
43 throw new ArgumentNullException("value");
44 }
45
46 this.key = value;
47 }
48 }
49
50 public bool IsString
51 {
52 get
53 {
54 return this.isString;
55 }
56
57 set
58 {
59 this.isString = value;
60 }
61 }
62
63 public byte[] Data
64 {
65 get
66 {
67 return this.data;
68 }
69
70 set
71 {
72 this.data = value;
73 this.isString = false;
74 }
75 }
76
77 public void Read(BinaryReader reader)
78 {
79 long basePosition = reader.BaseStream.Position;
80 int verInfoSize = (int) reader.ReadUInt16();
81 int valueSize = (int) reader.ReadUInt16();
82 bool dataIsString = (reader.ReadUInt16() != 0);
83 StringBuilder keyStringBuilder = new StringBuilder();
84 char c;
85 while ((c = (char) reader.ReadUInt16()) != 0)
86 {
87 keyStringBuilder.Append(c);
88 }
89 this.Key = keyStringBuilder.ToString();
90 Pad(reader, basePosition);
91 if (valueSize == 0)
92 {
93 this.data = null;
94 }
95 else
96 {
97 if (dataIsString) valueSize *= 2; // Count is # of chars instead of bytes
98 this.data = reader.ReadBytes(valueSize);
99 this.isString = dataIsString;
100 Pad(reader, basePosition);
101 }
102
103 while (reader.BaseStream.Position - basePosition < verInfoSize)
104 {
105 Pad(reader, basePosition);
106 VersionInfo childVerInfo = new VersionInfo("");
107 childVerInfo.Read(reader);
108 this.children.Add(childVerInfo);
109 }
110 }
111
112 public void Write(BinaryWriter writer)
113 {
114 long basePosition = writer.BaseStream.Position;
115 writer.Write((ushort) this.Length);
116 byte[] valueBytes = this.data;
117 writer.Write((ushort) ((valueBytes != null ? valueBytes.Length : 0) / (this.IsString ? 2 : 1)));
118 writer.Write((ushort) (this.IsString ? 1 : 0));
119 byte[] keyBytes = new byte[Encoding.Unicode.GetByteCount(this.Key) + 2];
120 Encoding.Unicode.GetBytes(this.Key, 0, this.Key.Length, keyBytes, 0);
121 writer.Write(keyBytes);
122 Pad(writer, basePosition);
123 if (valueBytes != null)
124 {
125 writer.Write(valueBytes);
126 Pad(writer, basePosition);
127 }
128
129 foreach (VersionInfo childVersionInfo in this.children)
130 {
131 Pad(writer, basePosition);
132 childVersionInfo.Write(writer);
133 }
134 }
135
136 private static void Pad(BinaryReader reader, long basePosition)
137 {
138 long position = reader.BaseStream.Position;
139 int diff = (int) (position - basePosition) % 4;
140 if (diff > 0) while (diff++ < 4 && reader.BaseStream.Position < reader.BaseStream.Length) reader.ReadByte();
141 }
142 private static void Pad(BinaryWriter writer, long basePosition)
143 {
144 long position = writer.BaseStream.Position;
145 int diff = (int) (position - basePosition) % 4;
146 if (diff > 0) while (diff++ < 4) writer.Write((byte) 0);
147 }
148
149 private int Length
150 {
151 get
152 {
153 int len = 6 + Encoding.Unicode.GetByteCount(this.Key) + 2;
154 if (len % 4 > 0) len += (4 - len % 4);
155 len += (this.data != null ? this.data.Length : 0);
156 if (len % 4 > 0) len += (4 - len % 4);
157 foreach (VersionInfo childVersionInfo in this.children)
158 {
159 if (len % 4 > 0) len += (4 - len % 4);
160 len += childVersionInfo.Length;
161 }
162 return len;
163 }
164 }
165
166 public static explicit operator VersionInfo(byte[] bytesValue)
167 {
168 VersionInfo viValue = new VersionInfo("");
169 using (BinaryReader reader = new BinaryReader(new MemoryStream(bytesValue, false)))
170 {
171 viValue.Read(reader);
172 }
173 return viValue;
174 }
175
176 public static explicit operator byte[](VersionInfo viValue)
177 {
178 byte[] bytesValue = new byte[viValue.Length];
179 using (BinaryWriter writer = new BinaryWriter(new MemoryStream(bytesValue, true)))
180 {
181 viValue.Write(writer);
182 }
183 return bytesValue;
184 }
185
186 public VersionInfo this[string itemKey]
187 {
188 get
189 {
190 int index = this.IndexOf(itemKey);
191 if (index < 0) return null;
192 return this.children[index];
193 }
194 }
195
196 public void Add(VersionInfo item)
197 {
198 this.children.Add(item);
199 }
200
201 public bool Remove(VersionInfo item)
202 {
203 return this.children.Remove(item);
204 }
205
206 public bool Remove(string itemKey)
207 {
208 int index = this.IndexOf(itemKey);
209 if (index >= 0)
210 {
211 this.children.RemoveAt(index);
212 return true;
213 }
214 else
215 {
216 return false;
217 }
218 }
219
220 private int IndexOf(string itemKey)
221 {
222 for (int i = 0; i < this.children.Count; i++)
223 {
224 if (this.children[i].Key == itemKey) return i;
225 }
226 return -1;
227 }
228
229 public bool Contains(VersionInfo item)
230 {
231 return this.children.Contains(item);
232 }
233
234 public void CopyTo(VersionInfo[] array, int index)
235 {
236 this.children.CopyTo(array, index);
237 }
238
239 public void Clear()
240 {
241 this.children.Clear();
242 }
243
244 public int Count
245 {
246 get
247 {
248 return this.children.Count;
249 }
250 }
251
252 public bool IsReadOnly
253 {
254 get
255 {
256 return false;
257 }
258 }
259
260 public IEnumerator<VersionInfo> GetEnumerator()
261 {
262 return this.children.GetEnumerator();
263 }
264
265 IEnumerator IEnumerable.GetEnumerator()
266 {
267 return this.GetEnumerator();
268 }
269 }
270}
diff --git a/src/dtf/WixToolset.Dtf.Resources/VersionResource.cs b/src/dtf/WixToolset.Dtf.Resources/VersionResource.cs
new file mode 100644
index 00000000..9955fa03
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Resources/VersionResource.cs
@@ -0,0 +1,415 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Resources
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Reflection;
9 using System.Collections;
10 using System.Collections.Generic;
11 using System.Globalization;
12 using System.Diagnostics.CodeAnalysis;
13
14 /// <summary>
15 /// A subclass of Resource which provides specific methods for manipulating the resource data.
16 /// </summary>
17 /// <remarks>
18 /// The resource is of type <see cref="ResourceType.Version"/> (RT_VERSION).
19 /// </remarks>
20 [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
21 public sealed class VersionResource : Resource, ICollection<VersionStringTable>
22 {
23 internal bool dirty;
24 private VersionInfo rawVersionInfo;
25 private FixedFileVersionInfo rawFileVersionInfo;
26
27 /// <summary>
28 /// Creates a new VersionResource object without any data. The data can be later loaded from a file.
29 /// </summary>
30 /// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
31 /// <param name="locale">Locale of the resource</param>
32 public VersionResource(string name, int locale)
33 : this(name, locale, null)
34 {
35 }
36
37 /// <summary>
38 /// Creates a new VersionResource object with data. The data can be later saved to a file.
39 /// </summary>
40 /// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
41 /// <param name="locale">Locale of the resource</param>
42 /// <param name="data">Raw resource data</param>
43 public VersionResource(string name, int locale, byte[] data)
44 : base(ResourceType.Version, name, locale, data)
45 {
46 this.RefreshVersionInfo(data);
47 }
48
49 /// <summary>
50 /// Gets or sets the raw data of the resource. The data is in the format of the VS_VERSIONINFO structure.
51 /// </summary>
52 public override byte[] Data
53 {
54 get
55 {
56 if (this.dirty)
57 {
58 this.rawVersionInfo.Data = (byte[]) this.rawFileVersionInfo;
59 base.Data = (byte[]) this.rawVersionInfo;
60 this.dirty = false;
61 }
62
63 return base.Data;
64 }
65 set
66 {
67 base.Data = value;
68 this.RefreshVersionInfo(value);
69 this.dirty = false;
70 }
71 }
72
73 private void RefreshVersionInfo(byte[] refreshData)
74 {
75 if (refreshData == null)
76 {
77 this.rawVersionInfo = new VersionInfo("VS_VERSION_INFO");
78 this.rawFileVersionInfo = new FixedFileVersionInfo();
79 }
80 else
81 {
82 this.rawVersionInfo = (VersionInfo) refreshData;
83 this.rawFileVersionInfo = (FixedFileVersionInfo) this.rawVersionInfo.Data;
84 }
85 }
86
87 /// <summary>
88 /// Gets or sets the binary locale-independent file version of the version resource.
89 /// </summary>
90 public Version FileVersion
91 {
92 get
93 {
94 return this.rawFileVersionInfo.FileVersion;
95 }
96 set
97 {
98 this.rawFileVersionInfo.FileVersion = value;
99 this.dirty = true;
100 }
101 }
102
103 /// <summary>
104 /// Gets or sets the binary locale-independent product version of the version resource.
105 /// </summary>
106 public Version ProductVersion
107 {
108 get
109 {
110 return this.rawFileVersionInfo.ProductVersion;
111 }
112 set
113 {
114 this.rawFileVersionInfo.ProductVersion = value;
115 this.dirty = true;
116 }
117 }
118
119 /// <summary>
120 /// Gets or sets a bitmask that specifies the build types of the file.
121 /// </summary>
122 public VersionBuildTypes BuildTypes
123 {
124 get
125 {
126 return this.rawFileVersionInfo.FileFlags &
127 this.rawFileVersionInfo.FileFlagsMask;
128 }
129 set
130 {
131 this.rawFileVersionInfo.FileFlags = value;
132 this.rawFileVersionInfo.FileFlagsMask = value;
133 this.dirty = true;
134 }
135 }
136
137 /// <summary>
138 /// Gets or sets the general type of the file.
139 /// </summary>
140 public VersionFileType FileType
141 {
142 get
143 {
144 return this.rawFileVersionInfo.FileType;
145 }
146 set
147 {
148 this.rawFileVersionInfo.FileType = value;
149 this.dirty = true;
150 }
151 }
152
153 /// <summary>
154 /// Gets or sets the specific type of the file.
155 /// </summary>
156 public VersionFileSubtype FileSubtype
157 {
158 get
159 {
160 return this.rawFileVersionInfo.FileSubtype;
161 }
162 set
163 {
164 this.rawFileVersionInfo.FileSubtype = value;
165 this.dirty = true;
166 }
167 }
168
169 /// <summary>
170 /// Gets or sets the binary creation date and time.
171 /// </summary>
172 public DateTime Timestamp
173 {
174 get
175 {
176 return this.rawFileVersionInfo.Timestamp;
177 }
178 set
179 {
180 this.rawFileVersionInfo.Timestamp = value;
181 this.dirty = true;
182 }
183 }
184
185 /// <summary>
186 /// Gets the string table for a specific locale, or null if there is no table for that locale.
187 /// </summary>
188 /// <seealso cref="Add(int)"/>
189 /// <seealso cref="Remove(int)"/>
190 public VersionStringTable this[int locale]
191 {
192 get
193 {
194 VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
195 if (svi != null)
196 {
197 foreach (VersionInfo strings in svi)
198 {
199 int stringsLocale = UInt16.Parse(strings.Key.Substring(0, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
200 if (stringsLocale == locale)
201 {
202 return new VersionStringTable(this, strings);
203 }
204 }
205 }
206 return null;
207 }
208 }
209
210 /// <summary>
211 /// Adds a new version string table for a locale.
212 /// </summary>
213 /// <param name="locale">Locale of the table</param>
214 /// <returns>The new string table, or the existing table if the locale already existed.</returns>
215 public VersionStringTable Add(int locale)
216 {
217 VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
218 if (svi == null)
219 {
220 svi = new VersionInfo("StringFileInfo");
221 this.rawVersionInfo.Add(svi);
222 }
223 foreach (VersionInfo strings in svi)
224 {
225 int stringsLocale = UInt16.Parse(strings.Key.Substring(0, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
226 if (stringsLocale == locale)
227 {
228 return new VersionStringTable(this, strings);
229 }
230 }
231
232 VersionInfo newStrings = new VersionInfo(
233 ((ushort) locale).ToString("x4", CultureInfo.InvariantCulture) + ((ushort) 1200).ToString("x4", CultureInfo.InvariantCulture));
234 svi.Add(newStrings);
235 this.dirty = true;
236
237 VersionInfo vvi = this.rawVersionInfo["VarFileInfo"];
238 if (vvi == null)
239 {
240 vvi = new VersionInfo("VarFileInfo");
241 vvi.Add(new VersionInfo("Translation"));
242 this.rawVersionInfo.Add(vvi);
243 }
244 VersionInfo tVerInfo = vvi["Translation"];
245 if (tVerInfo != null)
246 {
247 byte[] oldValue = tVerInfo.Data;
248 if (oldValue == null) oldValue = new byte[0];
249 tVerInfo.Data = new byte[oldValue.Length + 4];
250 Array.Copy(oldValue, tVerInfo.Data, oldValue.Length);
251 using (BinaryWriter bw = new BinaryWriter(new MemoryStream(tVerInfo.Data, oldValue.Length, 4, true)))
252 {
253 bw.Write((ushort) locale);
254 bw.Write((ushort) 1200);
255 }
256 }
257
258 return new VersionStringTable(this, newStrings);
259 }
260
261 /// <summary>
262 /// Removes a version string table for a locale.
263 /// </summary>
264 /// <param name="locale">Locale of the table</param>
265 public void Remove(int locale)
266 {
267 VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
268 if (svi != null)
269 {
270 foreach (VersionInfo strings in svi)
271 {
272 int stringsLocale = UInt16.Parse(strings.Key.Substring(0, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
273 if (stringsLocale == locale)
274 {
275 svi.Remove(strings);
276 this.dirty = true;
277 break;
278 }
279 }
280
281 }
282
283 VersionInfo vvi = this.rawVersionInfo["VarFileInfo"];
284 if (vvi != null)
285 {
286 VersionInfo tVerInfo = vvi["Translation"];
287 if (tVerInfo != null)
288 {
289 byte[] newValue = new byte[tVerInfo.Data.Length];
290 int j = 0;
291 using (BinaryWriter bw = new BinaryWriter(new MemoryStream(newValue, 0, newValue.Length, true)))
292 {
293 using (BinaryReader br = new BinaryReader(new MemoryStream(tVerInfo.Data)))
294 {
295 for (int i = tVerInfo.Data.Length / 4; i > 0; i--)
296 {
297 ushort tLocale = br.ReadUInt16();
298 ushort cp = br.ReadUInt16();
299 if (tLocale != locale)
300 {
301 bw.Write((ushort) tLocale);
302 bw.Write((ushort) cp);
303 j++;
304 }
305 }
306 }
307 }
308 tVerInfo.Data = new byte[j * 4];
309 Array.Copy(newValue, tVerInfo.Data, tVerInfo.Data.Length);
310 }
311 }
312 }
313
314 /// <summary>
315 /// Checks if a version string table exists for a given locale.
316 /// </summary>
317 /// <param name="locale">Locale to search for</param>
318 /// <returns>True if a string table was found for the locale; false otherwise.</returns>
319 public bool Contains(int locale)
320 {
321 return this[locale] != null;
322 }
323
324 /// <summary>
325 /// Gets the number string tables in the version resource.
326 /// </summary>
327 public int Count
328 {
329 get
330 {
331 VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
332 return svi != null ? svi.Count : 0;
333 }
334 }
335
336 /// <summary>
337 /// Removes all string tables from the version resource.
338 /// </summary>
339 public void Clear()
340 {
341 VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
342 if (svi != null)
343 {
344 svi.Clear();
345 this.dirty = true;
346 }
347 }
348
349 bool ICollection<VersionStringTable>.IsReadOnly
350 {
351 get
352 {
353 return false;
354 }
355 }
356
357 void ICollection<VersionStringTable>.Add(VersionStringTable item)
358 {
359 throw new NotSupportedException();
360 }
361
362 bool ICollection<VersionStringTable>.Remove(VersionStringTable item)
363 {
364 throw new NotSupportedException();
365 }
366
367 bool ICollection<VersionStringTable>.Contains(VersionStringTable item)
368 {
369 throw new NotSupportedException();
370 }
371
372 /// <summary>
373 /// Copies the version string tables to an array, starting at a particular array index.
374 /// </summary>
375 /// <param name="array">The one-dimensional Array that is the destination of the elements copied
376 /// from the collection. The Array must have zero-based indexing.</param>
377 /// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
378 public void CopyTo(VersionStringTable[] array, int arrayIndex)
379 {
380 VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
381 if (svi != null)
382 {
383 foreach (VersionInfo strings in svi)
384 {
385 array[arrayIndex++] = new VersionStringTable(this, strings);
386 }
387 }
388 }
389
390 /// <summary>
391 /// Gets an enumerator that can iterate over the version string tables in the collection.
392 /// </summary>
393 /// <returns>An enumerator that returns <see cref="VersionStringTable"/> objects.</returns>
394 public IEnumerator<VersionStringTable> GetEnumerator()
395 {
396 VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
397 if (svi != null)
398 {
399 foreach (VersionInfo strings in svi)
400 {
401 yield return new VersionStringTable(this, strings);
402 }
403 }
404 }
405
406 /// <summary>
407 /// Gets an enumerator that can iterate over the version string tables in the collection.
408 /// </summary>
409 /// <returns>An enumerator that returns <see cref="VersionStringTable"/> objects.</returns>
410 IEnumerator IEnumerable.GetEnumerator()
411 {
412 return this.GetEnumerator();
413 }
414 }
415}
diff --git a/src/dtf/WixToolset.Dtf.Resources/VersionStringTable.cs b/src/dtf/WixToolset.Dtf.Resources/VersionStringTable.cs
new file mode 100644
index 00000000..6aad68c6
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Resources/VersionStringTable.cs
@@ -0,0 +1,231 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Resources
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Reflection;
9 using System.Collections;
10 using System.Collections.Generic;
11 using System.Globalization;
12 using System.Diagnostics.CodeAnalysis;
13
14 /// <summary>
15 /// Represents a string table of a file version resource.
16 /// </summary>
17 [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
18 public sealed class VersionStringTable : IDictionary<string, string>
19 {
20 private VersionResource parent;
21 private VersionInfo rawStringVersionInfo;
22
23 internal VersionStringTable(VersionResource parent, VersionInfo rawStringVersionInfo)
24 {
25 this.parent = parent;
26 this.rawStringVersionInfo = rawStringVersionInfo;
27 }
28
29 /// <summary>
30 /// Gets the locale (LCID) of the string table.
31 /// </summary>
32 public int Locale
33 {
34 get
35 {
36 return UInt16.Parse(rawStringVersionInfo.Key.Substring(0, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
37 }
38 set
39 {
40 rawStringVersionInfo.Key = ((ushort) value).ToString("x4", CultureInfo.InvariantCulture) + rawStringVersionInfo.Key.Substring(4, 4);
41 this.parent.dirty = true;
42 }
43 }
44
45 /// <summary>
46 /// Gets or sets a string value.
47 /// </summary>
48 /// <param name="key">Name of the string.</param>
49 public string this[string key]
50 {
51 get
52 {
53 VersionInfo verValue = this.rawStringVersionInfo[key];
54 if (verValue == null)
55 {
56 return null;
57 }
58 else
59 {
60 return Encoding.Unicode.GetString(verValue.Data, 0, verValue.Data.Length - 2);
61 }
62 }
63 set
64 {
65 if (value == null)
66 {
67 rawStringVersionInfo.Remove(key);
68 }
69 else
70 {
71 VersionInfo verValue = rawStringVersionInfo[key];
72 if (verValue == null)
73 {
74 verValue = new VersionInfo(key);
75 verValue.IsString = true;
76 rawStringVersionInfo.Add(verValue);
77 }
78 verValue.Data = new byte[Encoding.Unicode.GetByteCount(value) + 2];
79 Encoding.Unicode.GetBytes(value, 0, value.Length, verValue.Data, 0);
80 }
81 this.parent.dirty = true;
82 }
83 }
84
85 bool ICollection<KeyValuePair<string, string>>.IsReadOnly
86 {
87 get
88 {
89 return false;
90 }
91 }
92
93 bool IDictionary<string, string>.TryGetValue(string key, out string value)
94 {
95 value = this[key];
96 return value != null;
97 }
98
99
100 void ICollection<KeyValuePair<string, string>>.Add(KeyValuePair<string, string> item)
101 {
102 this[item.Key] = item.Value;
103 }
104
105 bool ICollection<KeyValuePair<string, string>>.Remove(KeyValuePair<string, string> item)
106 {
107 string value = this[item.Key];
108 if (value == item.Value)
109 {
110 this[item.Key] = null;
111 return true;
112 }
113 else
114 {
115 return false;
116 }
117 }
118
119 bool ICollection<KeyValuePair<string, string>>.Contains(KeyValuePair<string, string> item)
120 {
121 string value = this[item.Key];
122 if (value == item.Value)
123 {
124 return true;
125 }
126 else
127 {
128 return false;
129 }
130 }
131
132 bool IDictionary<string, string>.ContainsKey(string key)
133 {
134 return this[key] != null;
135 }
136
137 void IDictionary<string, string>.Add(string key, string value)
138 {
139 this[key] = value;
140 }
141
142 bool IDictionary<string, string>.Remove(string key)
143 {
144 if (this[key] != null)
145 {
146 this[key] = null;
147 return true;
148 }
149 else
150 {
151 return false;
152 }
153 }
154
155 /// <summary>
156 /// Removes all strings from the string table.
157 /// </summary>
158 public void Clear()
159 {
160 this.rawStringVersionInfo.Clear();
161 }
162
163 /// <summary>
164 /// Gets a collection of all the names of the strings in the table.
165 /// </summary>
166 public ICollection<string> Keys
167 {
168 get
169 {
170 List<string> keys = new List<string>(this.rawStringVersionInfo.Count);
171 foreach (VersionInfo verValue in this.rawStringVersionInfo)
172 {
173 keys.Add(verValue.Key);
174 }
175 return keys;
176 }
177 }
178
179 /// <summary>
180 /// Gets a collection of all the values in the table.
181 /// </summary>
182 public ICollection<string> Values
183 {
184 get
185 {
186 List<string> values = new List<string>(this.rawStringVersionInfo.Count);
187 foreach (VersionInfo verValue in this.rawStringVersionInfo)
188 {
189 values.Add(Encoding.Unicode.GetString(verValue.Data, 0, verValue.Data.Length - 2));
190 }
191 return values;
192 }
193 }
194
195 /// <summary>
196 /// Gets the number of strings in the table.
197 /// </summary>
198 public int Count
199 {
200 get
201 {
202 return this.rawStringVersionInfo.Count;
203 }
204 }
205
206 void ICollection<KeyValuePair<string, string>>.CopyTo(KeyValuePair<string, string>[] array, int index)
207 {
208 foreach (VersionInfo verValue in this.rawStringVersionInfo)
209 {
210 array[index++] = new KeyValuePair<string, string>(verValue.Key, Encoding.Unicode.GetString(verValue.Data, 0, verValue.Data.Length - 2));
211 }
212 }
213
214 /// <summary>
215 /// Gets an enumeration over all strings in the table.
216 /// </summary>
217 /// <returns>Enumeration of string name and value pairs</returns>
218 public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
219 {
220 foreach (VersionInfo verValue in this.rawStringVersionInfo)
221 {
222 yield return new KeyValuePair<string, string>(verValue.Key, Encoding.Unicode.GetString(verValue.Data, 0, verValue.Data.Length - 2));
223 }
224 }
225
226 IEnumerator IEnumerable.GetEnumerator()
227 {
228 return this.GetEnumerator();
229 }
230 }
231}
diff --git a/src/dtf/WixToolset.Dtf.Resources/WixToolset.Dtf.Resources.csproj b/src/dtf/WixToolset.Dtf.Resources/WixToolset.Dtf.Resources.csproj
new file mode 100644
index 00000000..b5f5e9a2
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Resources/WixToolset.Dtf.Resources.csproj
@@ -0,0 +1,17 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <RootNamespace>WixToolset.Dtf.Resources</RootNamespace>
7 <AssemblyName>WixToolset.Dtf.Resources</AssemblyName>
8 <TargetFrameworks>netstandard2.0;net20</TargetFrameworks>
9 <Description>Classes for reading and writing resource data in executable files</Description>
10 <CreateDocumentationFile>true</CreateDocumentationFile>
11 </PropertyGroup>
12
13 <ItemGroup>
14 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
15 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
16 </ItemGroup>
17</Project>
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/AssemblyInfo.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/AssemblyInfo.cs
new file mode 100644
index 00000000..94abf1dc
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/AssemblyInfo.cs
@@ -0,0 +1,6 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System.Diagnostics.CodeAnalysis;
4
5[assembly: SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "WixToolset.Dtf.WindowsInstaller.Linq.QTable`1.System.Linq.IQueryable<TRecord>.CreateQuery(System.Linq.Expressions.Expression):System.Linq.IQueryable`1<TElement>")]
6[assembly: SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "WixToolset.Dtf.WindowsInstaller.Linq.QTable`1.System.Linq.IQueryable<TRecord>.Execute(System.Linq.Expressions.Expression):TResult")]
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Attributes.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Attributes.cs
new file mode 100644
index 00000000..60008bc8
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Attributes.cs
@@ -0,0 +1,60 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller.Linq
4{
5 using System;
6
7 /// <summary>
8 /// Apply to a subclass of QRecord to indicate the name of
9 /// the table the record type is to be used with.
10 /// </summary>
11 /// <remarks>
12 /// If this attribute is not used on a record type, the default
13 /// table name will be derived from the record type name. (An
14 /// optional underscore suffix is stripped.)
15 /// </remarks>
16 [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
17 public class DatabaseTableAttribute : Attribute
18 {
19 /// <summary>
20 /// Creates a new DatabaseTableAttribute for the specified table.
21 /// </summary>
22 /// <param name="table">name of the table associated with the record type</param>
23 public DatabaseTableAttribute(string table)
24 {
25 this.Table = table;
26 }
27
28 /// <summary>
29 /// Gets or sets the table associated with the record type.
30 /// </summary>
31 public string Table { get; set; }
32 }
33
34 /// <summary>
35 /// Apply to a property on a subclass of QRecord to indicate
36 /// the name of the column the property is to be associated with.
37 /// </summary>
38 /// <remarks>
39 /// If this attribute is not used on a property, the default
40 /// column name will be the same as the property name.
41 /// </remarks>
42 [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
43 public class DatabaseColumnAttribute : Attribute
44 {
45 /// <summary>
46 /// Creates a new DatabaseColumnAttribute which maps a
47 /// record property to a column.
48 /// </summary>
49 /// <param name="column">name of the column associated with the property</param>
50 public DatabaseColumnAttribute(string column)
51 {
52 this.Column = column;
53 }
54
55 /// <summary>
56 /// Gets or sets the column associated with the record property.
57 /// </summary>
58 public string Column { get; set; }
59 }
60}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Entities.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Entities.cs
new file mode 100644
index 00000000..1c51b861
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Entities.cs
@@ -0,0 +1,150 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller.Linq.Entities
4{
5 // Silence warnings about style and doc-comments
6 #if !CODE_ANALYSIS
7 #pragma warning disable 1591
8 #region Generated code
9
10 public class Component_ : QRecord
11 {
12 public string Component { get { return this[0]; } set { this[0] = value; } }
13 public string ComponentId { get { return this[1]; } set { this[1] = value; } }
14 public string Directory_ { get { return this[2]; } set { this[2] = value; } }
15 public string Condition { get { return this[4]; } set { this[4] = value; } }
16 public string KeyPath { get { return this[5]; } set { this[5] = value; } }
17 public ComponentAttributes Attributes
18 { get { return (ComponentAttributes) this.I(3); } set { this[3] = ((int) value).ToString(); } }
19 }
20
21 public class CreateFolder_ : QRecord
22 {
23 public string Directory_ { get { return this[0]; } set { this[0] = value; } }
24 public string Component_ { get { return this[1]; } set { this[1] = value; } }
25 }
26
27 public class CustomAction_ : QRecord
28 {
29 public string Action { get { return this[0]; } set { this[0] = value; } }
30 public string Source { get { return this[2]; } set { this[2] = value; } }
31 public string Target { get { return this[3]; } set { this[3] = value; } }
32 public CustomActionTypes Type
33 { get { return (CustomActionTypes) this.I(1); } set { this[1] = ((int) value).ToString(); } }
34 }
35
36 public class Directory_ : QRecord
37 {
38 public string Directory { get { return this[0]; } set { this[0] = value; } }
39 public string Directory_Parent { get { return this[1]; } set { this[1] = value; } }
40 public string DefaultDir { get { return this[2]; } set { this[2] = value; } }
41 }
42
43 public class DuplicateFile_ : QRecord
44 {
45 public string FileKey { get { return this[0]; } set { this[0] = value; } }
46 public string Component_ { get { return this[1]; } set { this[1] = value; } }
47 public string File_ { get { return this[2]; } set { this[2] = value; } }
48 public string DestName { get { return this[4]; } set { this[4] = value; } }
49 public string DestFolder { get { return this[5]; } set { this[5] = value; } }
50 }
51
52 public class Feature_ : QRecord
53 {
54 public string Feature { get { return this[0]; } set { this[0] = value; } }
55 public string Feature_Parent { get { return this[1]; } set { this[1] = value; } }
56 public string Title { get { return this[2]; } set { this[2] = value; } }
57 public string Description { get { return this[3]; } set { this[3] = value; } }
58 public int? Display { get { return this.NI(4); } set { this[4] = value.ToString(); } }
59 public int Level { get { return this.I(5); } set { this[5] = value.ToString(); } }
60 public string Directory_ { get { return this[6]; } set { this[6] = value; } }
61 public FeatureAttributes Attributes
62 { get { return (FeatureAttributes) this.I(7); } set { this[7] = ((int) value).ToString(); } }
63 }
64
65 [DatabaseTable("FeatureComponents")]
66 public class FeatureComponent_ : QRecord
67 {
68 public string Feature_ { get { return this[0]; } set { this[0] = value; } }
69 public string Component_ { get { return this[1]; } set { this[1] = value; } }
70 }
71
72 public class File_ : QRecord
73 {
74 public string File { get { return this[0]; } set { this[0] = value; } }
75 public string Component_ { get { return this[1]; } set { this[1] = value; } }
76 public string FileName { get { return this[2]; } set { this[2] = value; } }
77 public int FileSize { get { return this.I(3); } set { this[3] = value.ToString(); } }
78 public string Version { get { return this[4]; } set { this[4] = value; } }
79 public string Language { get { return this[5]; } set { this[5] = value; } }
80 public int Sequence { get { return this.I(7); } set { this[7] = value.ToString(); } }
81 public FileAttributes Attributes
82 { get { return (FileAttributes) this.I(6); } set { this[6] = ((int) value).ToString(); } }
83 }
84
85 [DatabaseTable("MsiFileHash")]
86 public class FileHash_ : QRecord
87 {
88 public string File_ { get { return this[0]; } set { this[0] = value; } }
89 public int Options { get { return this.I(1); } set { this[1] = value.ToString(); } }
90 public int HashPart1 { get { return this.I(2); } set { this[2] = value.ToString(); } }
91 public int HashPart2 { get { return this.I(3); } set { this[3] = value.ToString(); } }
92 public int HashPart3 { get { return this.I(4); } set { this[4] = value.ToString(); } }
93 public int HashPart4 { get { return this.I(5); } set { this[5] = value.ToString(); } }
94 }
95
96 [DatabaseTable("InstallExecuteSequence")]
97 public class InstallSequence_ : QRecord
98 {
99 public string Action { get { return this[0]; } set { this[0] = value; } }
100 public string Condition { get { return this[1]; } set { this[1] = value; } }
101 public int Sequence { get { return this.I(2); } set { this[2] = value.ToString(); } }
102 }
103
104 public class LaunchCondition_ : QRecord
105 {
106 public string Condition { get { return this[0]; } set { this[0] = value; } }
107 public string Description { get { return this[1]; } set { this[1] = value; } }
108 }
109
110 public class Media_ : QRecord
111 {
112 public int DiskId { get { return this.I(0); } set { this[0] = value.ToString(); } }
113 public int LastSequence { get { return this.I(1); } set { this[1] = value.ToString(); } }
114 public string DiskPrompt { get { return this[2]; } set { this[2] = value; } }
115 public string Cabinet { get { return this[3]; } set { this[3] = value; } }
116 public string VolumeLabel { get { return this[4]; } set { this[4] = value; } }
117 public string Source { get { return this[5]; } set { this[5] = value; } }
118 }
119
120 public class Property_ : QRecord
121 {
122 public string Property { get { return this[0]; } set { this[0] = value; } }
123 public string Value { get { return this[1]; } set { this[1] = value; } }
124 }
125
126 public class Registry_ : QRecord
127 {
128 public string Registry { get { return this[0]; } set { this[0] = value; } }
129 public string Key { get { return this[2]; } set { this[2] = value; } }
130 public string Name { get { return this[3]; } set { this[3] = value; } }
131 public string Value { get { return this[4]; } set { this[4] = value; } }
132 public string Component_ { get { return this[5]; } set { this[5] = value; } }
133 public RegistryRoot Root
134 { get { return (RegistryRoot) this.I(1); } set { this[0] = ((int) value).ToString(); } }
135 }
136
137 public class RemoveFile_ : QRecord
138 {
139 public string FileKey { get { return this[0]; } set { this[0] = value; } }
140 public string Component_ { get { return this[2]; } set { this[2] = value; } }
141 public string FileName { get { return this[3]; } set { this[3] = value; } }
142 public string DirProperty { get { return this[4]; } set { this[4] = value; } }
143 public RemoveFileModes InstallMode
144 { get { return (RemoveFileModes) this.I(5); } set { this[5] = ((int) value).ToString(); } }
145 }
146
147 #endregion // Generated code
148 #pragma warning restore 1591
149 #endif // !CODE_ANALYSIS
150}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QDatabase.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QDatabase.cs
new file mode 100644
index 00000000..b4de2f60
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QDatabase.cs
@@ -0,0 +1,214 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller.Linq
4{
5 using System;
6 using System.IO;
7 using WixToolset.Dtf.WindowsInstaller.Linq.Entities;
8
9 /// <summary>
10 /// Allows any Database instance to be converted into a queryable database.
11 /// </summary>
12 public static class Queryable
13 {
14 /// <summary>
15 /// Converts any Database instance into a queryable database.
16 /// </summary>
17 /// <param name="db"></param>
18 /// <returns>Queryable database instance that operates on the same
19 /// MSI handle.</returns>
20 /// <remarks>
21 /// This extension method is meant for convenient on-the-fly conversion.
22 /// If the existing database instance already happens to be a QDatabase,
23 /// then it is returned unchanged. Otherwise since the new database
24 /// carries the same MSI handle, only one of the instances needs to be
25 /// closed, not both.
26 /// </remarks>
27 public static QDatabase AsQueryable(this Database db)
28 {
29 QDatabase qdb = db as QDatabase;
30 if (qdb == null && db != null)
31 {
32 qdb = new QDatabase(db.Handle, true, db.FilePath, db.OpenMode);
33 }
34 return qdb;
35 }
36 }
37
38 /// <summary>
39 /// Queryable MSI database - extends the base Database class with
40 /// LINQ query functionality along with predefined entity types
41 /// for common tables.
42 /// </summary>
43 public class QDatabase : Database
44 {
45 /// <summary>
46 /// Opens an existing database in read-only mode.
47 /// </summary>
48 /// <param name="filePath">Path to the database file.</param>
49 /// <exception cref="InstallerException">the database could not be created/opened</exception>
50 /// <remarks>
51 /// Because this constructor initiates database access, it cannot be used with a
52 /// running installation.
53 /// <para>The Database object should be <see cref="InstallerHandle.Close"/>d after use.
54 /// The finalizer will close the handle if it is still open, however due to the nondeterministic
55 /// nature of finalization it is best that the handle be closed manually as soon as it is no
56 /// longer needed, as leaving lots of unused handles open can degrade performance.</para>
57 /// </remarks>
58 public QDatabase(string filePath)
59 : base(filePath)
60 {
61 }
62
63 /// <summary>
64 /// Opens an existing database with another database as output.
65 /// </summary>
66 /// <param name="filePath">Path to the database to be read.</param>
67 /// <param name="outputPath">Open mode for the database</param>
68 /// <returns>Database object representing the created or opened database</returns>
69 /// <exception cref="InstallerException">the database could not be created/opened</exception>
70 /// <remarks>
71 /// When a database is opened as the output of another database, the summary information stream
72 /// of the output database is actually a read-only mirror of the original database and thus cannot
73 /// be changed. Additionally, it is not persisted with the database. To create or modify the
74 /// summary information for the output database it must be closed and re-opened.
75 /// <para>The returned Database object should be <see cref="InstallerHandle.Close"/>d after use.
76 /// The finalizer will close the handle if it is still open, however due to the nondeterministic
77 /// nature of finalization it is best that the handle be closed manually as soon as it is no
78 /// longer needed, as leaving lots of unused handles open can degrade performance.</para>
79 /// </remarks>
80 public QDatabase(string filePath, string outputPath)
81 : base(filePath, outputPath)
82 {
83 }
84
85 /// <summary>
86 /// Opens an existing database or creates a new one.
87 /// </summary>
88 /// <param name="filePath">Path to the database file. If an empty string
89 /// is supplied, a temporary database is created that is not persisted.</param>
90 /// <param name="mode">Open mode for the database</param>
91 /// <exception cref="InstallerException">the database could not be created/opened</exception>
92 /// <remarks>
93 /// To make and save changes to a database first open the database in transaction,
94 /// create or, or direct mode. After making the changes, always call the Commit method
95 /// before closing the database handle. The Commit method flushes all buffers.
96 /// <para>Always call the Commit method on a database that has been opened in direct
97 /// mode before closing the database. Failure to do this may corrupt the database.</para>
98 /// <para>Because this constructor initiates database access, it cannot be used with a
99 /// running installation.</para>
100 /// <para>The Database object should be <see cref="InstallerHandle.Close"/>d after use.
101 /// The finalizer will close the handle if it is still open, however due to the nondeterministic
102 /// nature of finalization it is best that the handle be closed manually as soon as it is no
103 /// longer needed, as leaving lots of unused handles open can degrade performance.</para>
104 /// </remarks>
105 public QDatabase(string filePath, DatabaseOpenMode mode)
106 : base(filePath, mode)
107 {
108 }
109
110 /// <summary>
111 /// Creates a new database from an MSI handle.
112 /// </summary>
113 /// <param name="handle">Native MSI database handle.</param>
114 /// <param name="ownsHandle">True if the handle should be closed
115 /// when the database object is disposed</param>
116 /// <param name="filePath">Path of the database file, if known</param>
117 /// <param name="openMode">Mode the handle was originally opened in</param>
118 protected internal QDatabase(
119 IntPtr handle, bool ownsHandle, string filePath, DatabaseOpenMode openMode)
120 : base(handle, ownsHandle, filePath, openMode)
121 {
122 }
123
124 /// <summary>
125 /// Gets or sets a log where all MSI SQL queries are written.
126 /// </summary>
127 /// <remarks>
128 /// The log can be useful for debugging, or simply to watch the LINQ magic in action.
129 /// </remarks>
130 public TextWriter Log { get; set; }
131
132 /// <summary>
133 /// Gets a queryable table from the datbaase.
134 /// </summary>
135 /// <param name="table">name of the table</param>
136 public QTable<QRecord> this[string table]
137 {
138 get
139 {
140 return new QTable<QRecord>(this, table);
141 }
142 }
143
144 #if !CODE_ANALYSIS
145 #region Queryable tables
146
147 /// <summary>Queryable standard table with predefined specialized record type.</summary>
148 public QTable<Component_> Components
149 { get { return new QTable<Component_>(this); } }
150
151 /// <summary>Queryable standard table with predefined specialized record type.</summary>
152 public QTable<CreateFolder_> CreateFolders
153 { get { return new QTable<CreateFolder_>(this); } }
154
155 /// <summary>Queryable standard table with predefined specialized record type.</summary>
156 public QTable<CustomAction_> CustomActions
157 { get { return new QTable<CustomAction_>(this); } }
158
159 /// <summary>Queryable standard table with predefined specialized record type.</summary>
160 public QTable<Directory_> Directories
161 { get { return new QTable<Directory_>(this); } }
162
163 /// <summary>Queryable standard table with predefined specialized record type.</summary>
164 public QTable<DuplicateFile_> DuplicateFiles
165 { get { return new QTable<DuplicateFile_>(this); } }
166
167 /// <summary>Queryable standard table with predefined specialized record type.</summary>
168 public QTable<Feature_> Features
169 { get { return new QTable<Feature_>(this); } }
170
171 /// <summary>Queryable standard table with predefined specialized record type.</summary>
172 public QTable<FeatureComponent_> FeatureComponents
173 { get { return new QTable<FeatureComponent_>(this); } }
174
175 /// <summary>Queryable standard table with predefined specialized record type.</summary>
176 public QTable<File_> Files
177 { get { return new QTable<File_>(this); } }
178
179 /// <summary>Queryable standard table with predefined specialized record type.</summary>
180 public QTable<FileHash_> FileHashes
181 { get { return new QTable<FileHash_>(this); } }
182
183 /// <summary>Queryable standard table with predefined specialized record type.</summary>
184 public QTable<InstallSequence_> InstallExecuteSequences
185 { get { return new QTable<InstallSequence_>(this, "InstallExecuteSequence"); } }
186
187 /// <summary>Queryable standard table with predefined specialized record type.</summary>
188 public QTable<InstallSequence_> InstallUISequences
189 { get { return new QTable<InstallSequence_>(this, "InstallUISequence"); } }
190
191 /// <summary>Queryable standard table with predefined specialized record type.</summary>
192 public QTable<LaunchCondition_> LaunchConditions
193 { get { return new QTable<LaunchCondition_>(this); } }
194
195 /// <summary>Queryable standard table with predefined specialized record type.</summary>
196 public QTable<Media_> Medias
197 { get { return new QTable<Media_>(this); } }
198
199 /// <summary>Queryable standard table with predefined specialized record type.</summary>
200 public QTable<Property_> Properties
201 { get { return new QTable<Property_>(this); } }
202
203 /// <summary>Queryable standard table with predefined specialized record type.</summary>
204 public QTable<Registry_> Registries
205 { get { return new QTable<Registry_>(this); } }
206
207 /// <summary>Queryable standard table with predefined specialized record type.</summary>
208 public QTable<RemoveFile_> RemoveFiles
209 { get { return new QTable<RemoveFile_>(this); } }
210
211 #endregion // Queryable tables
212 #endif // !CODE_ANALYSIS
213 }
214}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QRecord.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QRecord.cs
new file mode 100644
index 00000000..4b3145fd
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QRecord.cs
@@ -0,0 +1,501 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller.Linq
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Globalization;
9 using System.Collections.Generic;
10 using System.Diagnostics.CodeAnalysis;
11
12 /// <summary>
13 /// Generic record entity for queryable databases,
14 /// and base for strongly-typed entity subclasses.
15 /// </summary>
16 /// <remarks>
17 /// Several predefined specialized subclasses are provided for common
18 /// standard tables. Subclasses for additional standard tables
19 /// or custom tables are not necessary, but they are easy to create
20 /// and make the coding experience much nicer.
21 /// <para>When creating subclasses, the following attributes may be
22 /// useful: <see cref="DatabaseTableAttribute"/>,
23 /// <see cref="DatabaseColumnAttribute"/></para>
24 /// </remarks>
25 public class QRecord
26 {
27 /// <summary>
28 /// Do not call. Use QTable.NewRecord() instead.
29 /// </summary>
30 /// <remarks>
31 /// Subclasses must also provide a public parameterless constructor.
32 /// <para>QRecord constructors are only public due to implementation
33 /// reasons (to satisfy the new() constraint on the QTable generic
34 /// class). They are not intended to be called by user code other than
35 /// a subclass constructor. If the constructor is invoked directly,
36 /// the record instance will not be properly initialized (associated
37 /// with a database table) and calls to methods on the instance
38 /// will throw a NullReferenceException.</para>
39 /// </remarks>
40 /// <seealso cref="QTable&lt;TRecord&gt;.NewRecord()"/>
41 public QRecord()
42 {
43 }
44
45 internal QDatabase Database { get; set; }
46
47 internal TableInfo TableInfo { get; set; }
48
49 internal IList<string> Values { get; set; }
50
51 internal bool Exists { get; set; }
52
53 /// <summary>
54 /// Gets the number of fields in the record.
55 /// </summary>
56 public int FieldCount
57 {
58 get
59 {
60 return this.Values.Count;
61 }
62 }
63
64 /// <summary>
65 /// Gets or sets a record field.
66 /// </summary>
67 /// <param name="field">column name of the field</param>
68 /// <remarks>
69 /// Setting a field value will automatically update the database.
70 /// </remarks>
71 public string this[string field]
72 {
73 get
74 {
75 if (field == null)
76 {
77 throw new ArgumentNullException("field");
78 }
79
80 int index = this.TableInfo.Columns.IndexOf(field);
81 if (index < 0)
82 {
83 throw new ArgumentOutOfRangeException("field");
84 }
85
86 return this[index];
87 }
88
89 set
90 {
91 if (field == null)
92 {
93 throw new ArgumentNullException("field");
94 }
95
96 this.Update(new string[] { field }, new string[] { value });
97 }
98 }
99
100 /// <summary>
101 /// Gets or sets a record field.
102 /// </summary>
103 /// <param name="index">zero-based column index of the field</param>
104 /// <remarks>
105 /// Setting a field value will automatically update the database.
106 /// </remarks>
107 public string this[int index]
108 {
109 get
110 {
111 if (index < 0 || index >= this.FieldCount)
112 {
113 throw new ArgumentOutOfRangeException("index");
114 }
115
116 return this.Values[index];
117 }
118
119 set
120 {
121 if (index < 0 || index >= this.FieldCount)
122 {
123 throw new ArgumentOutOfRangeException("index");
124 }
125
126 this.Update(new int[] { index }, new string[] { value });
127 }
128 }
129
130 /// <summary>
131 /// Used by subclasses to get a field as an integer.
132 /// </summary>
133 /// <param name="index">zero-based column index of the field</param>
134 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "I")]
135 protected int I(int index)
136 {
137 string value = this[index];
138 return value.Length > 0 ?
139 Int32.Parse(value, CultureInfo.InvariantCulture) : 0;
140 }
141
142 /// <summary>
143 /// Used by subclasses to get a field as a nullable integer.
144 /// </summary>
145 /// <param name="index">zero-based column index of the field</param>
146 protected int? NI(int index)
147 {
148 string value = this[index];
149 return value.Length > 0 ?
150 new int?(Int32.Parse(value, CultureInfo.InvariantCulture)) : null;
151 }
152
153 /// <summary>
154 /// Dumps all record fields to a string.
155 /// </summary>
156 public override string ToString()
157 {
158 StringBuilder buf = new StringBuilder(this.GetType().Name);
159 buf.Append(" {");
160 for (int i = 0; i < this.FieldCount; i++)
161 {
162 buf.AppendFormat("{0} {1} = {2}",
163 (i > 0 ? "," : String.Empty),
164 this.TableInfo.Columns[i].Name,
165 this[i]);
166 }
167 buf.Append(" }");
168 return buf.ToString();
169 }
170
171 /// <summary>
172 /// Update multiple fields in the record (and the database).
173 /// </summary>
174 /// <param name="fields">column names of fields to update</param>
175 /// <param name="values">new values for each field being updated</param>
176 public void Update(IList<string> fields, IList<string> values)
177 {
178 if (fields == null)
179 {
180 throw new ArgumentNullException("fields");
181 }
182
183 if (values == null)
184 {
185 throw new ArgumentNullException("values");
186 }
187
188 if (fields.Count == 0 || values.Count == 0 ||
189 fields.Count > this.FieldCount ||
190 values.Count != fields.Count)
191 {
192 throw new ArgumentOutOfRangeException("fields");
193 }
194
195 int[] indexes = new int[fields.Count];
196 for (int i = 0; i < indexes.Length; i++)
197 {
198 if (fields[i] == null)
199 {
200 throw new ArgumentNullException("fields[" + i + "]");
201 }
202
203 indexes[i] = this.TableInfo.Columns.IndexOf(fields[i]);
204
205 if (indexes[i] < 0)
206 {
207 throw new ArgumentOutOfRangeException("fields[" + i + "]");
208 }
209 }
210
211 this.Update(indexes, values);
212 }
213
214 /// <summary>
215 /// Update multiple fields in the record (and the database).
216 /// </summary>
217 /// <param name="indexes">column indexes of fields to update</param>
218 /// <param name="values">new values for each field being updated</param>
219 /// <remarks>
220 /// The record (primary keys) must already exist in the table.
221 /// <para>Updating primary key fields is not yet implemented; use Delete()
222 /// and Insert() instead.</para>
223 /// </remarks>
224 public void Update(IList<int> indexes, IList<string> values)
225 {
226 if (indexes == null)
227 {
228 throw new ArgumentNullException("indexes");
229 }
230
231 if (values == null)
232 {
233 throw new ArgumentNullException("values");
234 }
235
236 if (indexes.Count == 0 || values.Count == 0 ||
237 indexes.Count > this.FieldCount ||
238 values.Count != indexes.Count)
239 {
240 throw new ArgumentOutOfRangeException("indexes");
241 }
242
243 bool primaryKeyChanged = false;
244 for (int i = 0; i < indexes.Count; i++)
245 {
246 int index = indexes[i];
247 if (index < 0 || index >= this.FieldCount)
248 {
249 throw new ArgumentOutOfRangeException("index[" + i + "]");
250 }
251
252 ColumnInfo col = this.TableInfo.Columns[index];
253 if (this.TableInfo.PrimaryKeys.Contains(col.Name))
254 {
255 if (values[i] == null)
256 {
257 throw new ArgumentNullException("values[" + i + "]");
258 }
259
260 primaryKeyChanged = true;
261 }
262 else if (values[i] == null)
263 {
264 if (col.IsRequired)
265 {
266 throw new ArgumentNullException("values[" + i + "]");
267 }
268 }
269
270 this.Values[index] = values[i];
271 }
272
273 if (this.Exists)
274 {
275 if (!primaryKeyChanged)
276 {
277 int updateRecSize = indexes.Count + this.TableInfo.PrimaryKeys.Count;
278 using (Record updateRec = this.Database.CreateRecord(updateRecSize))
279 {
280 StringBuilder s = new StringBuilder("UPDATE `");
281 s.Append(this.TableInfo.Name);
282 s.Append("` SET");
283
284 for (int i = 0; i < indexes.Count; i++)
285 {
286 ColumnInfo col = this.TableInfo.Columns[indexes[i]];
287 if (col.Type == typeof(Stream))
288 {
289 throw new NotSupportedException(
290 "Cannot update stream columns via QRecord.");
291 }
292
293 int index = indexes[i];
294 s.AppendFormat("{0} `{1}` = ?",
295 (i > 0 ? "," : String.Empty),
296 col.Name);
297
298 if (values[i] != null)
299 {
300 updateRec[i + 1] = values[i];
301 }
302 }
303
304 for (int i = 0; i < this.TableInfo.PrimaryKeys.Count; i++)
305 {
306 string key = this.TableInfo.PrimaryKeys[i];
307 s.AppendFormat(" {0} `{1}` = ?", (i == 0 ? "WHERE" : "AND"), key);
308 int index = this.TableInfo.Columns.IndexOf(key);
309 updateRec[indexes.Count + i + 1] = this.Values[index];
310
311 }
312
313 string updateSql = s.ToString();
314 TextWriter log = this.Database.Log;
315 if (log != null)
316 {
317 log.WriteLine();
318 log.WriteLine(updateSql);
319 for (int field = 1; field <= updateRecSize; field++)
320 {
321 log.WriteLine(" ? = " + updateRec.GetString(field));
322 }
323 }
324
325 this.Database.Execute(updateSql, updateRec);
326 }
327 }
328 else
329 {
330 throw new NotImplementedException(
331 "Update() cannot handle changed primary keys yet.");
332 // TODO:
333 // query using old values
334 // update values
335 // View.Replace
336 }
337 }
338 }
339
340 /// <summary>
341 /// Inserts the record in the database.
342 /// </summary>
343 /// <remarks>
344 /// The record (primary keys) may not already exist in the table.
345 /// <para>Use <see cref="QTable&lt;TRecord&gt;.NewRecord()"/> to get a new
346 /// record. Prmary keys and all required fields
347 /// must be filled in before insertion.</para>
348 /// </remarks>
349 public void Insert()
350 {
351 this.Insert(false);
352 }
353
354 /// <summary>
355 /// Inserts the record into the table.
356 /// </summary>
357 /// <param name="temporary">true if the record is temporarily
358 /// inserted, to be visible only as long as the database is open</param>
359 /// <remarks>
360 /// The record (primary keys) may not already exist in the table.
361 /// <para>Use <see cref="QTable&lt;TRecord&gt;.NewRecord()"/> to get a new
362 /// record. Prmary keys and all required fields
363 /// must be filled in before insertion.</para>
364 /// </remarks>
365 public void Insert(bool temporary)
366 {
367 using (Record updateRec = this.Database.CreateRecord(this.FieldCount))
368 {
369 string insertSql = this.TableInfo.SqlInsertString;
370 if (temporary)
371 {
372 insertSql += " TEMPORARY";
373 }
374
375 TextWriter log = this.Database.Log;
376 if (log != null)
377 {
378 log.WriteLine();
379 log.WriteLine(insertSql);
380 }
381
382 for (int index = 0; index < this.FieldCount; index++)
383 {
384 ColumnInfo col = this.TableInfo.Columns[index];
385 if (col.Type == typeof(Stream))
386 {
387 throw new NotSupportedException(
388 "Cannot insert stream columns via QRecord.");
389 }
390
391 if (this.Values[index] != null)
392 {
393 updateRec[index + 1] = this.Values[index];
394 }
395
396 if (log != null)
397 {
398 log.WriteLine(" ? = " + this.Values[index]);
399 }
400 }
401
402 this.Database.Execute(insertSql, updateRec);
403 this.Exists = true;
404 }
405 }
406
407 /// <summary>
408 /// Deletes the record from the table if it exists.
409 /// </summary>
410 public void Delete()
411 {
412 using (Record keyRec = this.Database.CreateRecord(this.TableInfo.PrimaryKeys.Count))
413 {
414 StringBuilder s = new StringBuilder("DELETE FROM `");
415 s.Append(this.TableInfo.Name);
416 s.Append("`");
417 for (int i = 0; i < this.TableInfo.PrimaryKeys.Count; i++)
418 {
419 string key = this.TableInfo.PrimaryKeys[i];
420 s.AppendFormat(" {0} `{1}` = ?", (i == 0 ? "WHERE" : "AND"), key);
421 int index = this.TableInfo.Columns.IndexOf(key);
422 keyRec[i + 1] = this.Values[index];
423 }
424
425 string deleteSql = s.ToString();
426
427 TextWriter log = this.Database.Log;
428 if (log != null)
429 {
430 log.WriteLine();
431 log.WriteLine(deleteSql);
432
433 for (int i = 0; i < this.TableInfo.PrimaryKeys.Count; i++)
434 {
435 log.WriteLine(" ? = " + keyRec.GetString(i + 1));
436 }
437 }
438
439 this.Database.Execute(deleteSql, keyRec);
440 this.Exists = false;
441 }
442 }
443
444 /// <summary>
445 /// Not yet implemented.
446 /// </summary>
447 public void Refresh()
448 {
449 throw new NotImplementedException();
450 }
451
452 /// <summary>
453 /// Not yet implemented.
454 /// </summary>
455 public void Assign()
456 {
457 throw new NotImplementedException();
458 }
459
460 /// <summary>
461 /// Not yet implemented.
462 /// </summary>
463 public bool Merge()
464 {
465 throw new NotImplementedException();
466 }
467
468 /// <summary>
469 /// Not yet implemented.
470 /// </summary>
471 public ICollection<ValidationErrorInfo> Validate()
472 {
473 throw new NotImplementedException();
474 }
475
476 /// <summary>
477 /// Not yet implemented.
478 /// </summary>
479 [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
480 public ICollection<ValidationErrorInfo> ValidateNew()
481 {
482 throw new NotImplementedException();
483 }
484
485 /// <summary>
486 /// Not yet implemented.
487 /// </summary>
488 public ICollection<ValidationErrorInfo> ValidateFields()
489 {
490 throw new NotImplementedException();
491 }
492
493 /// <summary>
494 /// Not yet implemented.
495 /// </summary>
496 public ICollection<ValidationErrorInfo> ValidateDelete()
497 {
498 throw new NotImplementedException();
499 }
500 }
501}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QTable.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QTable.cs
new file mode 100644
index 00000000..e0e1c154
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QTable.cs
@@ -0,0 +1,296 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller.Linq
4{
5 using System;
6 using System.IO;
7 using System.Collections;
8 using System.Collections.Generic;
9 using System.Reflection;
10 using System.Linq;
11 using System.Linq.Expressions;
12 using System.Diagnostics.CodeAnalysis;
13
14 /// <summary>
15 /// Represents one table in a LINQ-queryable Database.
16 /// </summary>
17 /// <typeparam name="TRecord">type that represents one record in the table</typeparam>
18 /// <remarks>
19 /// This class is the primary gateway to all LINQ to MSI query functionality.
20 /// <para>The TRecord generic parameter may be the general <see cref="QRecord" />
21 /// class, or a specialized subclass of QRecord.</para>
22 /// </remarks>
23 [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
24 public sealed class QTable<TRecord> : IOrderedQueryable<TRecord>, IQueryProvider
25 where TRecord : QRecord, new()
26 {
27 private QDatabase db;
28 private TableInfo tableInfo;
29
30 /// <summary>
31 /// Infers the name of the table this instance will be
32 /// associated with.
33 /// </summary>
34 /// <returns>table name</returns>
35 /// <remarks>
36 /// The table name is retrieved from a DatabaseTableAttribute
37 /// on the record type if it exists; otherwise the name is
38 /// derived from the name of the record type itself.
39 /// (An optional underscore suffix on the record type name is dropped.)
40 /// </remarks>
41 private static string InferTableName()
42 {
43 foreach (DatabaseTableAttribute attr in typeof(TRecord).GetCustomAttributes(
44 typeof(DatabaseTableAttribute), false))
45 {
46 string tableName = attr.Table;
47 if (!String.IsNullOrEmpty(tableName))
48 {
49 return tableName;
50 }
51 }
52
53 string recordTypeName = typeof(TRecord).Name;
54 if (recordTypeName[recordTypeName.Length - 1] == '_')
55 {
56 return recordTypeName.Substring(0, recordTypeName.Length - 1);
57 }
58 else
59 {
60 return recordTypeName;
61 }
62 }
63
64 /// <summary>
65 /// Creates a new QTable, inferring the table name
66 /// from the name of the record type parameter.
67 /// </summary>
68 /// <param name="db">database that contains the table</param>
69 public QTable(QDatabase db)
70 : this(db, InferTableName())
71 {
72 }
73
74 /// <summary>
75 /// Creates a new QTable with an explicit table name.
76 /// </summary>
77 /// <param name="db">database that contains the table</param>
78 /// <param name="table">name of the table</param>
79 public QTable(QDatabase db, string table)
80 {
81 if (db == null)
82 {
83 throw new ArgumentNullException("db");
84 }
85
86 if (String.IsNullOrEmpty(table))
87 {
88 throw new ArgumentNullException("table");
89 }
90
91 this.db = db;
92 this.tableInfo = db.Tables[table];
93 if (this.tableInfo == null)
94 {
95 throw new ArgumentException(
96 "Table does not exist in database: " + table);
97 }
98 }
99
100 /// <summary>
101 /// Gets schema information about the table.
102 /// </summary>
103 public TableInfo TableInfo
104 {
105 get
106 {
107 return this.tableInfo;
108 }
109 }
110
111 /// <summary>
112 /// Gets the database this table is associated with.
113 /// </summary>
114 public QDatabase Database
115 {
116 get
117 {
118 return this.db;
119 }
120 }
121
122 /// <summary>
123 /// Enumerates over all records in the table.
124 /// </summary>
125 /// <returns></returns>
126 public IEnumerator<TRecord> GetEnumerator()
127 {
128 string query = this.tableInfo.SqlSelectString;
129
130 TextWriter log = this.db.Log;
131 if (log != null)
132 {
133 log.WriteLine();
134 log.WriteLine(query);
135 }
136
137 using (View view = db.OpenView(query))
138 {
139 view.Execute();
140
141 ColumnCollection columns = this.tableInfo.Columns;
142 int columnCount = columns.Count;
143 bool[] isBinary = new bool[columnCount];
144
145 for (int i = 0; i < isBinary.Length; i++)
146 {
147 isBinary[i] = columns[i].Type == typeof(System.IO.Stream);
148 }
149
150 foreach (Record rec in view) using (rec)
151 {
152 string[] values = new string[columnCount];
153 for (int i = 0; i < values.Length; i++)
154 {
155 values[i] = isBinary[i] ? "[Binary Data]" : rec.GetString(i + 1);
156 }
157
158 TRecord trec = new TRecord();
159 trec.Database = this.Database;
160 trec.TableInfo = this.TableInfo;
161 trec.Values = values;
162 trec.Exists = true;
163 yield return trec;
164 }
165 }
166 }
167
168 IEnumerator IEnumerable.GetEnumerator()
169 {
170 return ((IEnumerable<TRecord>) this).GetEnumerator();
171 }
172
173 IQueryable<TElement> IQueryProvider.CreateQuery<TElement>(Expression expression)
174 {
175 if (expression == null)
176 {
177 throw new ArgumentNullException("expression");
178 }
179
180 Query<TElement> q = new Query<TElement>(this.Database, expression);
181
182 MethodCallExpression methodCallExpression = (MethodCallExpression) expression;
183 string methodName = methodCallExpression.Method.Name;
184 if (methodName == "Where")
185 {
186 LambdaExpression argumentExpression = (LambdaExpression)
187 ((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
188 q.BuildQuery(this.TableInfo, argumentExpression);
189 }
190 else if (methodName == "OrderBy")
191 {
192 LambdaExpression argumentExpression = (LambdaExpression)
193 ((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
194 q.BuildSequence(this.TableInfo, argumentExpression);
195 }
196 else if (methodName == "Select")
197 {
198 LambdaExpression argumentExpression = (LambdaExpression)
199 ((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
200 q.BuildNullQuery(this.TableInfo, typeof(TRecord), argumentExpression);
201 q.BuildProjection(null, argumentExpression);
202 }
203 else if (methodName == "Join")
204 {
205 ConstantExpression constantExpression = (ConstantExpression)
206 methodCallExpression.Arguments[1];
207 IQueryable inner = (IQueryable) constantExpression.Value;
208 q.PerformJoin(
209 this.TableInfo,
210 typeof(TRecord),
211 inner,
212 GetJoinLambda(methodCallExpression.Arguments[2]),
213 GetJoinLambda(methodCallExpression.Arguments[3]),
214 GetJoinLambda(methodCallExpression.Arguments[4]));
215 }
216 else
217 {
218 throw new NotSupportedException(
219 "Query operation not supported: " + methodName);
220 }
221
222 return q;
223 }
224
225 private static LambdaExpression GetJoinLambda(Expression expresion)
226 {
227 UnaryExpression unaryExpression = (UnaryExpression) expresion;
228 return (LambdaExpression) unaryExpression.Operand;
229 }
230
231 IQueryable IQueryProvider.CreateQuery(Expression expression)
232 {
233 return ((IQueryProvider) this).CreateQuery<TRecord>(expression);
234 }
235
236 TResult IQueryProvider.Execute<TResult>(Expression expression)
237 {
238 throw new NotSupportedException(
239 "Direct method calls not supported -- use AsEnumerable() instead.");
240 }
241
242 object IQueryProvider.Execute(Expression expression)
243 {
244 throw new NotSupportedException(
245 "Direct method calls not supported -- use AsEnumerable() instead.");
246 }
247
248 IQueryProvider IQueryable.Provider
249 {
250 get
251 {
252 return this;
253 }
254 }
255
256 Type IQueryable.ElementType
257 {
258 get
259 {
260 return typeof(TRecord);
261 }
262 }
263
264 Expression IQueryable.Expression
265 {
266 get
267 {
268 return Expression.Constant(this);
269 }
270 }
271
272 /// <summary>
273 /// Creates a new record that can be inserted into this table.
274 /// </summary>
275 /// <returns>a record with all fields initialized to null</returns>
276 /// <remarks>
277 /// Primary keys and required fields must be filled in with
278 /// non-null values before the record can be inserted.
279 /// <para>The record is tied to this table in this database;
280 /// it cannot be inserted into another table or database.</para>
281 /// </remarks>
282 public TRecord NewRecord()
283 {
284 TRecord rec = new TRecord();
285 rec.Database = this.Database;
286 rec.TableInfo = this.TableInfo;
287 IList<string> values = new List<string>(this.TableInfo.Columns.Count);
288 for (int i = 0; i < this.TableInfo.Columns.Count; i++)
289 {
290 values.Add(null);
291 }
292 rec.Values = values;
293 return rec;
294 }
295 }
296}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Query.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Query.cs
new file mode 100644
index 00000000..ea58757c
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Query.cs
@@ -0,0 +1,992 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller.Linq
4{
5 using System;
6 using System.IO;
7 using System.Collections;
8 using System.Collections.Generic;
9 using System.Linq;
10 using System.Linq.Expressions;
11 using System.Text;
12 using System.Reflection;
13 using System.Globalization;
14 using System.Diagnostics.CodeAnalysis;
15
16 /// <summary>
17 /// Implements the LINQ to MSI query functionality.
18 /// </summary>
19 /// <typeparam name="T">the result type of the current query --
20 /// either some kind of QRecord, or some projection of record data</typeparam>
21 internal sealed class Query<T> : IOrderedQueryable<T>, IQueryProvider
22 {
23 private QDatabase db;
24 private Expression queryableExpression;
25 private List<TableInfo> tables;
26 private List<Type> recordTypes;
27 private List<string> selectors;
28 private string where;
29 private List<object> whereParameters;
30 private List<TableColumn> orderbyColumns;
31 private List<TableColumn> selectColumns;
32 private List<TableColumn> joinColumns;
33 private List<Delegate> projectionDelegates;
34
35 internal Query(QDatabase db, Expression expression)
36 {
37 if (expression == null)
38 {
39 throw new ArgumentNullException("expression");
40 }
41
42 this.db = db;
43 this.queryableExpression = expression;
44 this.tables = new List<TableInfo>();
45 this.recordTypes = new List<Type>();
46 this.selectors = new List<string>();
47 this.whereParameters = new List<object>();
48 this.orderbyColumns = new List<TableColumn>();
49 this.selectColumns = new List<TableColumn>();
50 this.joinColumns = new List<TableColumn>();
51 this.projectionDelegates = new List<Delegate>();
52 }
53
54 public IEnumerator<T> GetEnumerator()
55 {
56 if (this.selectColumns.Count == 0)
57 {
58 AddAllColumns(this.tables[0], this.selectColumns);
59 }
60
61 string query = this.CompileQuery();
62 return this.InvokeQuery(query);
63 }
64
65 private string CompileQuery()
66 {
67 bool explicitTables = this.tables.Count > 1;
68
69 StringBuilder queryBuilder = new StringBuilder("SELECT");
70
71 for (int i = 0; i < this.selectColumns.Count; i++)
72 {
73 queryBuilder.AppendFormat(
74 CultureInfo.InvariantCulture,
75 (explicitTables ? "{0} `{1}`.`{2}`" : "{0} `{2}`"),
76 (i > 0 ? "," : String.Empty),
77 this.selectColumns[i].Table.Name,
78 this.selectColumns[i].Column.Name);
79 }
80
81 for (int i = 0; i < this.tables.Count; i++)
82 {
83 queryBuilder.AppendFormat(
84 CultureInfo.InvariantCulture,
85 "{0} `{1}`",
86 (i == 0 ? " FROM" : ","),
87 this.tables[i].Name);
88 }
89
90 bool startedWhere = false;
91 for (int i = 0; i < this.joinColumns.Count - 1; i += 2)
92 {
93 queryBuilder.AppendFormat(
94 CultureInfo.InvariantCulture,
95 "{0} `{1}`.`{2}` = `{3}`.`{4}` ",
96 (i == 0 ? " WHERE" : "AND"),
97 this.joinColumns[i].Table,
98 this.joinColumns[i].Column,
99 this.joinColumns[i + 1].Table,
100 this.joinColumns[i + 1].Column);
101 startedWhere = true;
102 }
103
104 if (this.where != null)
105 {
106 queryBuilder.Append(startedWhere ? "AND " : " WHERE");
107 queryBuilder.Append(this.where);
108 }
109
110 for (int i = 0; i < this.orderbyColumns.Count; i++)
111 {
112 VerifyOrderByColumn(this.orderbyColumns[i]);
113
114 queryBuilder.AppendFormat(
115 CultureInfo.InvariantCulture,
116 (explicitTables ? "{0} `{1}`.`{2}`" : "{0} `{2}`"),
117 (i == 0 ? " ORDER BY" : ","),
118 this.orderbyColumns[i].Table.Name,
119 this.orderbyColumns[i].Column.Name);
120 }
121
122 return queryBuilder.ToString();
123 }
124
125 private static void VerifyOrderByColumn(TableColumn tableColumn)
126 {
127 if (tableColumn.Column.Type != typeof(int) &&
128 tableColumn.Column.Type != typeof(short))
129 {
130 throw new NotSupportedException(
131 "Cannot orderby column: " + tableColumn.Column.Name +
132 "; orderby is only supported on integer fields");
133 }
134 }
135
136 private IEnumerator<T> InvokeQuery(string query)
137 {
138 TextWriter log = this.db.Log;
139 if (log != null)
140 {
141 log.WriteLine();
142 log.WriteLine(query);
143 }
144
145 using (View queryView = this.db.OpenView(query))
146 {
147 if (this.whereParameters != null && this.whereParameters.Count > 0)
148 {
149 using (Record paramsRec = this.db.CreateRecord(this.whereParameters.Count))
150 {
151 for (int i = 0; i < this.whereParameters.Count; i++)
152 {
153 paramsRec[i + 1] = this.whereParameters[i];
154
155 if (log != null)
156 {
157 log.WriteLine(" ? = " + this.whereParameters[i]);
158 }
159 }
160
161 queryView.Execute(paramsRec);
162 }
163 }
164 else
165 {
166 queryView.Execute();
167 }
168
169 foreach (Record resultRec in queryView) using (resultRec)
170 {
171 yield return this.GetResult(resultRec);
172 }
173 }
174 }
175
176 private T GetResult(Record resultRec)
177 {
178 object[] results = new object[this.tables.Count];
179
180 for (int i = 0; i < this.tables.Count; i++)
181 {
182 string[] values = new string[this.tables[i].Columns.Count];
183 for (int j = 0; j < this.selectColumns.Count; j++)
184 {
185 TableColumn col = this.selectColumns[j];
186 if (col.Table.Name == this.tables[i].Name)
187 {
188 int index = this.tables[i].Columns.IndexOf(
189 col.Column.Name);
190 if (index >= 0)
191 {
192 if (col.Column.Type == typeof(Stream))
193 {
194 values[index] = "[Binary Data]";
195 }
196 else
197 {
198 values[index] = resultRec.GetString(j + 1);
199 }
200 }
201 }
202 }
203
204 QRecord result = (QRecord) this.recordTypes[i]
205 .GetConstructor(Type.EmptyTypes).Invoke(null);
206 result.Database = this.db;
207 result.TableInfo = this.tables[i];
208 result.Values = values;
209 result.Exists = true;
210 results[i] = result;
211 }
212
213 if (this.projectionDelegates.Count > 0)
214 {
215 object resultsProjection = results[0];
216 for (int i = 1; i <= results.Length; i++)
217 {
218 if (i < results.Length)
219 {
220 resultsProjection = this.projectionDelegates[i - 1]
221 .DynamicInvoke(new object[] { resultsProjection, results[i] });
222 }
223 else
224 {
225 resultsProjection = this.projectionDelegates[i - 1]
226 .DynamicInvoke(resultsProjection);
227 }
228 }
229
230 return (T) resultsProjection;
231 }
232 else
233 {
234 return (T) (object) results[0];
235 }
236 }
237
238 IEnumerator IEnumerable.GetEnumerator()
239 {
240 return ((IEnumerable<T>) this).GetEnumerator();
241 }
242
243 public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
244 {
245 if (expression == null)
246 {
247 throw new ArgumentNullException("expression");
248 }
249
250 Query<TElement> q = new Query<TElement>(this.db, expression);
251 q.tables.AddRange(this.tables);
252 q.recordTypes.AddRange(this.recordTypes);
253 q.selectors.AddRange(this.selectors);
254 q.where = this.where;
255 q.whereParameters.AddRange(this.whereParameters);
256 q.orderbyColumns.AddRange(this.orderbyColumns);
257 q.selectColumns.AddRange(this.selectColumns);
258 q.joinColumns.AddRange(this.joinColumns);
259 q.projectionDelegates.AddRange(this.projectionDelegates);
260
261 MethodCallExpression methodCallExpression = (MethodCallExpression) expression;
262 string methodName = methodCallExpression.Method.Name;
263 if (methodName == "Select")
264 {
265 LambdaExpression argumentExpression = (LambdaExpression)
266 ((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
267 q.BuildProjection(null, argumentExpression);
268 }
269 else if (methodName == "Where")
270 {
271 LambdaExpression argumentExpression = (LambdaExpression)
272 ((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
273 q.BuildQuery(null, argumentExpression);
274 }
275 else if (methodName == "ThenBy")
276 {
277 LambdaExpression argumentExpression = (LambdaExpression)
278 ((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
279 q.BuildSequence(null, argumentExpression);
280 }
281 else if (methodName == "Join")
282 {
283 ConstantExpression constantExpression = (ConstantExpression)
284 methodCallExpression.Arguments[1];
285 IQueryable inner = (IQueryable) constantExpression.Value;
286 q.PerformJoin(
287 null,
288 null,
289 inner,
290 GetJoinLambda(methodCallExpression.Arguments[2]),
291 GetJoinLambda(methodCallExpression.Arguments[3]),
292 GetJoinLambda(methodCallExpression.Arguments[4]));
293 }
294 else
295 {
296 throw new NotSupportedException(
297 "Query operation not supported: " + methodName);
298 }
299
300 return q;
301 }
302
303 public IQueryable CreateQuery(Expression expression)
304 {
305 return this.CreateQuery<T>(expression);
306 }
307
308 private static LambdaExpression GetJoinLambda(Expression expresion)
309 {
310 UnaryExpression unaryExpression = (UnaryExpression) expresion;
311 return (LambdaExpression) unaryExpression.Operand;
312 }
313
314 public TResult Execute<TResult>(Expression expression)
315 {
316 throw new NotSupportedException(
317 "Direct method calls not supported -- use AsEnumerable() instead.");
318 }
319
320 object IQueryProvider.Execute(Expression expression)
321 {
322 throw new NotSupportedException(
323 "Direct method calls not supported -- use AsEnumerable() instead.");
324 }
325
326 public IQueryProvider Provider
327 {
328 get
329 {
330 return this;
331 }
332 }
333
334 public Type ElementType
335 {
336 get
337 {
338 return typeof(T);
339 }
340 }
341
342 public Expression Expression
343 {
344 get
345 {
346 return this.queryableExpression;
347 }
348 }
349
350 internal void BuildQuery(TableInfo tableInfo, LambdaExpression expression)
351 {
352 if (tableInfo != null)
353 {
354 this.tables.Add(tableInfo);
355 this.recordTypes.Add(typeof(T));
356 this.selectors.Add(expression.Parameters[0].Name);
357 }
358
359 StringBuilder queryBuilder = new StringBuilder();
360
361 this.ParseQuery(expression.Body, queryBuilder);
362
363 this.where = queryBuilder.ToString();
364 }
365
366 internal void BuildNullQuery(TableInfo tableInfo, Type recordType, LambdaExpression expression)
367 {
368 this.tables.Add(tableInfo);
369 this.recordTypes.Add(recordType);
370 this.selectors.Add(expression.Parameters[0].Name);
371 }
372
373 private void ParseQuery(Expression expression, StringBuilder queryBuilder)
374 {
375 queryBuilder.Append("(");
376
377 BinaryExpression binaryExpression;
378 UnaryExpression unaryExpression;
379 MethodCallExpression methodCallExpression;
380
381 if ((binaryExpression = expression as BinaryExpression) != null)
382 {
383 switch (binaryExpression.NodeType)
384 {
385 case ExpressionType.AndAlso:
386 this.ParseQuery(binaryExpression.Left, queryBuilder);
387 queryBuilder.Append(" AND ");
388 this.ParseQuery(binaryExpression.Right, queryBuilder);
389 break;
390
391 case ExpressionType.OrElse:
392 this.ParseQuery(binaryExpression.Left, queryBuilder);
393 queryBuilder.Append(" OR ");
394 this.ParseQuery(binaryExpression.Right, queryBuilder);
395 break;
396
397 case ExpressionType.Equal:
398 case ExpressionType.NotEqual:
399 case ExpressionType.GreaterThan:
400 case ExpressionType.LessThan:
401 case ExpressionType.GreaterThanOrEqual:
402 case ExpressionType.LessThanOrEqual:
403 this.ParseQueryCondition(binaryExpression, queryBuilder);
404 break;
405
406 default:
407 throw new NotSupportedException(
408 "Expression type not supported: " + binaryExpression.NodeType );
409 }
410 }
411 else if ((unaryExpression = expression as UnaryExpression) != null)
412 {
413 throw new NotSupportedException(
414 "Expression type not supported: " + unaryExpression.NodeType);
415 }
416 else if ((methodCallExpression = expression as MethodCallExpression) != null)
417 {
418 throw new NotSupportedException(
419 "Method call not supported: " + methodCallExpression.Method.Name + "()");
420 }
421 else
422 {
423 throw new NotSupportedException(
424 "Query filter expression not supported: " + expression);
425 }
426
427 queryBuilder.Append(")");
428 }
429
430 private static ExpressionType OppositeExpression(ExpressionType e)
431 {
432 switch (e)
433 {
434 case ExpressionType.LessThan:
435 return ExpressionType.GreaterThan;
436 case ExpressionType.LessThanOrEqual:
437 return ExpressionType.GreaterThanOrEqual;
438 case ExpressionType.GreaterThan:
439 return ExpressionType.LessThan;
440 case ExpressionType.GreaterThanOrEqual:
441 return ExpressionType.LessThanOrEqual;
442 default:
443 return e;
444 }
445 }
446
447 private static bool IsIntegerType(Type t)
448 {
449 return
450 t == typeof(sbyte) ||
451 t == typeof(byte) ||
452 t == typeof(short) ||
453 t == typeof(ushort) ||
454 t == typeof(int) ||
455 t == typeof(uint) ||
456 t == typeof(long) ||
457 t == typeof(ulong);
458 }
459
460 private void ParseQueryCondition(
461 BinaryExpression binaryExpression, StringBuilder queryBuilder)
462 {
463 bool swap;
464 string column = this.GetConditionColumn(binaryExpression, out swap);
465 queryBuilder.Append(column);
466
467 ExpressionType expressionType = binaryExpression.NodeType;
468 if (swap)
469 {
470 expressionType = OppositeExpression(expressionType);
471 }
472
473 LambdaExpression valueExpression = Expression.Lambda(
474 swap ? binaryExpression.Left : binaryExpression.Right);
475 object value = valueExpression.Compile().DynamicInvoke();
476
477 bool valueIsInt = false;
478 if (value != null)
479 {
480 if (IsIntegerType(value.GetType()))
481 {
482 valueIsInt = true;
483 }
484 else
485 {
486 value = value.ToString();
487 }
488 }
489
490 switch (expressionType)
491 {
492 case ExpressionType.Equal:
493 if (value == null)
494 {
495 queryBuilder.Append(" IS NULL");
496 }
497 else if (valueIsInt)
498 {
499 queryBuilder.Append(" = ");
500 queryBuilder.Append(value);
501 }
502 else
503 {
504 queryBuilder.Append(" = ?");
505 this.whereParameters.Add(value);
506 }
507 return;
508
509 case ExpressionType.NotEqual:
510 if (value == null)
511 {
512 queryBuilder.Append(" IS NOT NULL");
513 }
514 else if (valueIsInt)
515 {
516 queryBuilder.Append(" <> ");
517 queryBuilder.Append(value);
518 }
519 else
520 {
521 queryBuilder.Append(" <> ?");
522 this.whereParameters.Add(value);
523 }
524 return;
525 }
526
527 if (value == null)
528 {
529 throw new InvalidOperationException(
530 "A null value was used in a greater-than/less-than operation.");
531 }
532
533 if (!valueIsInt)
534 {
535 throw new NotSupportedException(
536 "Greater-than/less-than operators not supported on strings.");
537 }
538
539 switch (expressionType)
540 {
541 case ExpressionType.LessThan:
542 queryBuilder.Append(" < ");
543 break;
544
545 case ExpressionType.LessThanOrEqual:
546 queryBuilder.Append(" <= ");
547 break;
548
549 case ExpressionType.GreaterThan:
550 queryBuilder.Append(" > ");
551 break;
552
553 case ExpressionType.GreaterThanOrEqual:
554 queryBuilder.Append(" >= ");
555 break;
556
557 default:
558 throw new NotSupportedException(
559 "Unsupported query expression type: " + expressionType);
560 }
561
562 queryBuilder.Append(value);
563 }
564
565 private string GetConditionColumn(
566 BinaryExpression binaryExpression, out bool swap)
567 {
568 MemberExpression memberExpression;
569 MethodCallExpression methodCallExpression;
570
571 if (((memberExpression = binaryExpression.Left as MemberExpression) != null) ||
572 ((binaryExpression.Left.NodeType == ExpressionType.Convert ||
573 binaryExpression.Left.NodeType == ExpressionType.ConvertChecked) &&
574 (memberExpression = ((UnaryExpression) binaryExpression.Left).Operand
575 as MemberExpression) != null))
576 {
577 string column = this.GetConditionColumn(memberExpression);
578 if (column != null)
579 {
580 swap = false;
581 return column;
582 }
583 }
584 else if (((memberExpression = binaryExpression.Right as MemberExpression) != null) ||
585 ((binaryExpression.Right.NodeType == ExpressionType.Convert ||
586 binaryExpression.Right.NodeType == ExpressionType.ConvertChecked) &&
587 (memberExpression = ((UnaryExpression) binaryExpression.Right).Operand
588 as MemberExpression) != null))
589 {
590 string column = this.GetConditionColumn(memberExpression);
591 if (column != null)
592 {
593 swap = true;
594 return column;
595 }
596 }
597 else if ((methodCallExpression = binaryExpression.Left as MethodCallExpression) != null)
598 {
599 string column = this.GetConditionColumn(methodCallExpression);
600 if (column != null)
601 {
602 swap = false;
603 return column;
604 }
605 }
606 else if ((methodCallExpression = binaryExpression.Right as MethodCallExpression) != null)
607 {
608 string column = this.GetConditionColumn(methodCallExpression);
609 if (column != null)
610 {
611 swap = true;
612 return column;
613 }
614 }
615
616 throw new NotSupportedException(
617 "Unsupported binary expression: " + binaryExpression);
618 }
619
620 private string GetConditionColumn(MemberExpression memberExpression)
621 {
622 string columnName = GetColumnName(memberExpression.Member);
623 string selectorName = GetConditionSelectorName(memberExpression.Expression);
624 string tableName = this.GetConditionTable(selectorName, columnName);
625 return this.FormatColumn(tableName, columnName);
626 }
627
628 private string GetConditionColumn(MethodCallExpression methodCallExpression)
629 {
630 LambdaExpression argumentExpression =
631 Expression.Lambda(methodCallExpression.Arguments[0]);
632 string columnName = (string) argumentExpression.Compile().DynamicInvoke();
633 string selectorName = GetConditionSelectorName(methodCallExpression.Object);
634 string tableName = this.GetConditionTable(selectorName, columnName);
635 return this.FormatColumn(tableName, columnName);
636 }
637
638 private static string GetConditionSelectorName(Expression expression)
639 {
640 ParameterExpression parameterExpression;
641 MemberExpression memberExpression;
642 if ((parameterExpression = expression as ParameterExpression) != null)
643 {
644 return parameterExpression.Name;
645 }
646 else if ((memberExpression = expression as MemberExpression) != null)
647 {
648 return memberExpression.Member.Name;
649 }
650 else
651 {
652 throw new NotSupportedException(
653 "Unsupported conditional selector expression: " + expression);
654 }
655 }
656
657 private string GetConditionTable(string selectorName, string columnName)
658 {
659 string tableName = null;
660
661 for (int i = 0; i < this.tables.Count; i++)
662 {
663 if (this.selectors[i] == selectorName)
664 {
665 tableName = this.tables[i].Name;
666 break;
667 }
668 }
669
670 if (tableName == null)
671 {
672 throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
673 "Conditional expression contains column {0}.{1} " +
674 "from a table that is not in the query.",
675 selectorName,
676 columnName));
677 }
678
679 return tableName;
680 }
681
682 private string FormatColumn(string tableName, string columnName)
683 {
684 if (tableName != null && this.tables.Count > 1)
685 {
686 return String.Format(CultureInfo.InvariantCulture, "`{0}`.`{1}`", tableName, columnName);
687 }
688 else
689 {
690 return String.Format(CultureInfo.InvariantCulture, "`{0}`", columnName);
691 }
692 }
693
694 private static string GetColumnName(MemberInfo memberInfo)
695 {
696 foreach (var attr in memberInfo.GetCustomAttributes(
697 typeof(DatabaseColumnAttribute), false))
698 {
699 return ((DatabaseColumnAttribute) attr).Column;
700 }
701
702 return memberInfo.Name;
703 }
704
705 internal void BuildProjection(TableInfo tableInfo, LambdaExpression expression)
706 {
707 if (tableInfo != null)
708 {
709 this.tables.Add(tableInfo);
710 this.recordTypes.Add(typeof(T));
711 this.selectors.Add(expression.Parameters[0].Name);
712 }
713
714 this.FindColumns(expression, this.selectColumns);
715 this.projectionDelegates.Add(expression.Compile());
716 }
717
718 internal void BuildSequence(TableInfo tableInfo, LambdaExpression expression)
719 {
720 if (tableInfo != null)
721 {
722 this.tables.Add(tableInfo);
723 this.recordTypes.Add(typeof(T));
724 this.selectors.Add(expression.Parameters[0].Name);
725 }
726
727 this.FindColumns(expression.Body, this.orderbyColumns);
728 }
729
730 private static void AddAllColumns(TableInfo tableInfo, IList<TableColumn> columnList)
731 {
732 foreach (ColumnInfo column in tableInfo.Columns)
733 {
734 columnList.Add(new TableColumn(tableInfo, column));
735 }
736 }
737
738 [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
739 private void FindColumns(Expression expression, IList<TableColumn> columnList)
740 {
741 if (expression is ParameterExpression)
742 {
743 ParameterExpression e = expression as ParameterExpression;
744 string selector = e.Name;
745 for (int i = 0; i < this.tables.Count; i++)
746 {
747 if (this.selectors[i] == selector)
748 {
749 AddAllColumns(this.tables[i], columnList);
750 break;
751 }
752 }
753 }
754 else if (expression.NodeType == ExpressionType.MemberAccess)
755 {
756 this.FindColumns(expression as MemberExpression, columnList);
757 }
758 else if (expression is MethodCallExpression)
759 {
760 this.FindColumns(expression as MethodCallExpression, columnList);
761 }
762 else if (expression is BinaryExpression)
763 {
764 BinaryExpression e = expression as BinaryExpression;
765 this.FindColumns(e.Left, columnList);
766 this.FindColumns(e.Right, columnList);
767 }
768 else if (expression is UnaryExpression)
769 {
770 UnaryExpression e = expression as UnaryExpression;
771 this.FindColumns(e.Operand, columnList);
772 }
773 else if (expression is ConditionalExpression)
774 {
775 ConditionalExpression e = expression as ConditionalExpression;
776 this.FindColumns(e.Test, columnList);
777 this.FindColumns(e.IfTrue, columnList);
778 this.FindColumns(e.IfFalse, columnList);
779 }
780 else if (expression is InvocationExpression)
781 {
782 InvocationExpression e = expression as InvocationExpression;
783 this.FindColumns(e.Expression, columnList);
784 this.FindColumns(e.Arguments, columnList);
785 }
786 else if (expression is LambdaExpression)
787 {
788 LambdaExpression e = expression as LambdaExpression;
789 this.FindColumns(e.Body, columnList);
790 }
791 else if (expression is ListInitExpression)
792 {
793 ListInitExpression e = expression as ListInitExpression;
794 this.FindColumns(e.NewExpression, columnList);
795 foreach (ElementInit ei in e.Initializers)
796 {
797 this.FindColumns(ei.Arguments, columnList);
798 }
799 }
800 else if (expression is MemberInitExpression)
801 {
802 MemberInitExpression e = expression as MemberInitExpression;
803 this.FindColumns(e.NewExpression, columnList);
804 foreach (MemberAssignment b in e.Bindings)
805 {
806 this.FindColumns(b.Expression, columnList);
807 }
808 }
809 else if (expression is NewExpression)
810 {
811 NewExpression e = expression as NewExpression;
812 this.FindColumns(e.Arguments, columnList);
813 }
814 else if (expression is NewArrayExpression)
815 {
816 NewArrayExpression e = expression as NewArrayExpression;
817 this.FindColumns(e.Expressions, columnList);
818 }
819 else if (expression is TypeBinaryExpression)
820 {
821 TypeBinaryExpression e = expression as TypeBinaryExpression;
822 this.FindColumns(e.Expression, columnList);
823 }
824 }
825
826 private void FindColumns(IEnumerable<Expression> expressions, IList<TableColumn> columnList)
827 {
828 foreach (Expression expression in expressions)
829 {
830 this.FindColumns(expression, columnList);
831 }
832 }
833
834 private void FindColumns(MemberExpression memberExpression, IList<TableColumn> columnList)
835 {
836 string selector = null;
837 MemberExpression objectMemberExpression;
838 ParameterExpression objectParameterExpression;
839 if ((objectParameterExpression = memberExpression.Expression as
840 ParameterExpression) != null)
841 {
842 selector = objectParameterExpression.Name;
843 }
844 else if ((objectMemberExpression = memberExpression.Expression as
845 MemberExpression) != null)
846 {
847 selector = objectMemberExpression.Member.Name;
848 }
849
850 if (selector != null)
851 {
852 for (int i = 0; i < this.tables.Count; i++)
853 {
854 if (this.selectors[i] == selector)
855 {
856 string columnName = GetColumnName(memberExpression.Member);
857 ColumnInfo column = this.tables[i].Columns[columnName];
858 columnList.Add(new TableColumn(this.tables[i], column));
859 break;
860 }
861 }
862 }
863
864 selector = memberExpression.Member.Name;
865 for (int i = 0; i < this.tables.Count; i++)
866 {
867 if (this.selectors[i] == selector)
868 {
869 AddAllColumns(this.tables[i], columnList);
870 break;
871 }
872 }
873 }
874
875 private void FindColumns(MethodCallExpression methodCallExpression, IList<TableColumn> columnList)
876 {
877 if (methodCallExpression.Method.Name == "get_Item" &&
878 methodCallExpression.Arguments.Count == 1 &&
879 methodCallExpression.Arguments[0].Type == typeof(string))
880 {
881 string selector = null;
882 MemberExpression objectMemberExpression;
883 ParameterExpression objectParameterExpression;
884 if ((objectParameterExpression = methodCallExpression.Object as ParameterExpression) != null)
885 {
886 selector = objectParameterExpression.Name;
887 }
888 else if ((objectMemberExpression = methodCallExpression.Object as MemberExpression) != null)
889 {
890 selector = objectMemberExpression.Member.Name;
891 }
892
893 if (selector != null)
894 {
895 for (int i = 0; i < this.tables.Count; i++)
896 {
897 if (this.selectors[i] == selector)
898 {
899 LambdaExpression argumentExpression =
900 Expression.Lambda(methodCallExpression.Arguments[0]);
901 string columnName = (string)
902 argumentExpression.Compile().DynamicInvoke();
903 ColumnInfo column = this.tables[i].Columns[columnName];
904 columnList.Add(new TableColumn(this.tables[i], column));
905 break;
906 }
907 }
908 }
909 }
910
911 if (methodCallExpression.Object != null && methodCallExpression.Object.NodeType != ExpressionType.Parameter)
912 {
913 this.FindColumns(methodCallExpression.Object, columnList);
914 }
915 }
916
917 internal void PerformJoin(
918 TableInfo tableInfo,
919 Type recordType,
920 IQueryable joinTable,
921 LambdaExpression outerKeySelector,
922 LambdaExpression innerKeySelector,
923 LambdaExpression resultSelector)
924 {
925 if (joinTable == null)
926 {
927 throw new ArgumentNullException("joinTable");
928 }
929
930 if (tableInfo != null)
931 {
932 this.tables.Add(tableInfo);
933 this.recordTypes.Add(recordType);
934 this.selectors.Add(outerKeySelector.Parameters[0].Name);
935 }
936
937 PropertyInfo tableInfoProp = joinTable.GetType().GetProperty("TableInfo");
938 if (tableInfoProp == null)
939 {
940 throw new NotSupportedException(
941 "Cannot join with object: " + joinTable.GetType().Name +
942 "; join is only supported on another QTable.");
943 }
944
945 TableInfo joinTableInfo = (TableInfo) tableInfoProp.GetValue(joinTable, null);
946 if (joinTableInfo == null)
947 {
948 throw new InvalidOperationException("Missing join table info.");
949 }
950
951 this.tables.Add(joinTableInfo);
952 this.recordTypes.Add(joinTable.ElementType);
953 this.selectors.Add(innerKeySelector.Parameters[0].Name);
954 this.projectionDelegates.Add(resultSelector.Compile());
955
956 int joinColumnCount = this.joinColumns.Count;
957 this.FindColumns(outerKeySelector.Body, this.joinColumns);
958 if (this.joinColumns.Count > joinColumnCount + 1)
959 {
960 throw new NotSupportedException("Join operations involving " +
961 "multiple columns are not supported.");
962 }
963 else if (this.joinColumns.Count != joinColumnCount + 1)
964 {
965 throw new InvalidOperationException("Bad outer key selector for join.");
966 }
967
968 this.FindColumns(innerKeySelector.Body, this.joinColumns);
969 if (this.joinColumns.Count > joinColumnCount + 2)
970 {
971 throw new NotSupportedException("Join operations involving " +
972 "multiple columns not are supported.");
973 }
974 if (this.joinColumns.Count != joinColumnCount + 2)
975 {
976 throw new InvalidOperationException("Bad inner key selector for join.");
977 }
978 }
979 }
980
981 internal class TableColumn
982 {
983 public TableColumn(TableInfo table, ColumnInfo column)
984 {
985 this.Table = table;
986 this.Column = column;
987 }
988
989 public TableInfo Table { get; set; }
990 public ColumnInfo Column { get; set; }
991 }
992}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/WixToolset.Dtf.WindowsInstaller.Linq.csproj b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/WixToolset.Dtf.WindowsInstaller.Linq.csproj
new file mode 100644
index 00000000..b4587071
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/WixToolset.Dtf.WindowsInstaller.Linq.csproj
@@ -0,0 +1,21 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <RootNamespace>WixToolset.Dtf.WindowsInstaller.Linq</RootNamespace>
7 <AssemblyName>WixToolset.Dtf.WindowsInstaller.Linq</AssemblyName>
8 <TargetFrameworks>netstandard2.0;net35</TargetFrameworks>
9 <Description>LINQ extensions for Windows Installer classes</Description>
10 <CreateDocumentationFile>true</CreateDocumentationFile>
11 </PropertyGroup>
12
13 <ItemGroup>
14 <ProjectReference Include="..\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj" />
15 </ItemGroup>
16
17 <ItemGroup>
18 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
19 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
20 </ItemGroup>
21</Project>
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller.Package/InstallPackage.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller.Package/InstallPackage.cs
new file mode 100644
index 00000000..276732b7
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller.Package/InstallPackage.cs
@@ -0,0 +1,1169 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller.Package
4{
5using System;
6using System.IO;
7using System.Text;
8using System.Collections;
9using System.Collections.Generic;
10using System.Diagnostics.CodeAnalysis;
11using System.Globalization;
12using System.Text.RegularExpressions;
13using WixToolset.Dtf.Compression;
14using WixToolset.Dtf.Compression.Cab;
15
16/// <summary>
17/// Handles status messages generated when operations are performed on an
18/// <see cref="InstallPackage"/> or <see cref="PatchPackage"/>.
19/// </summary>
20/// <example>
21/// <c>installPackage.Message += new InstallPackageMessageHandler(Console.WriteLine);</c>
22/// </example>
23public delegate void InstallPackageMessageHandler(string format, params object[] args);
24
25/// <summary>
26/// Provides access to powerful build, maintenance, and analysis operations on an
27/// installation package (.MSI or .MSM).
28/// </summary>
29public class InstallPackage : Database
30{
31 private string cabName;
32 private string cabMsg;
33
34 /// <summary>
35 /// Creates a new InstallPackage object. The file source directory and working
36 /// directory are the same as the location as the package file.
37 /// </summary>
38 /// <param name="packagePath">Path to the install package to be created or opened</param>
39 /// <param name="openMode">Open mode for the database</param>
40 public InstallPackage(string packagePath, DatabaseOpenMode openMode)
41 : this(packagePath, openMode, null, null)
42 {
43 }
44 /// <summary>
45 /// Creates a new InstallPackage object, specifying an alternate file source
46 /// directory and/or working directory.
47 /// </summary>
48 /// <param name="packagePath">Path to the install package to be created or opened</param>
49 /// <param name="openMode">Open mode for the database</param>
50 /// <param name="sourceDir">Location to obtain source files and cabinets when extracting
51 /// or updating files in the working directory. This is often the location of an original
52 /// copy of the package that is not meant to be modified. If this parameter is null, it
53 /// defaults to the directory of <paramref name="packagePath"/>.</param>
54 /// <param name="workingDir">Location where files will be extracted to/updated from. Also
55 /// the location where a temporary folder is created during some operations. If this
56 /// parameter is null, it defaults to the directory of <paramref name="packagePath"/>.</param>
57 /// <remarks>If the source location is different than the working directory, then
58 /// no files will be modified at the source location.
59 /// </remarks>
60 public InstallPackage(string packagePath, DatabaseOpenMode openMode,
61 string sourceDir, string workingDir) : base(packagePath, openMode)
62 {
63 this.sourceDir = (sourceDir != null ? sourceDir : Path.GetDirectoryName(packagePath));
64 this.workingDir = (workingDir != null ? workingDir : Path.GetDirectoryName(packagePath));
65 this.compressionLevel = CompressionLevel.Normal;
66
67 this.DeleteOnClose(this.TempDirectory);
68 }
69
70 /// <summary>
71 /// Handle this event to receive status messages when operations are performed
72 /// on the install package.
73 /// </summary>
74 /// <example>
75 /// <c>installPackage.Message += new InstallPackageMessageHandler(Console.WriteLine);</c>
76 /// </example>
77 public event InstallPackageMessageHandler Message;
78
79 /// <summary>
80 /// Sends a message to the <see cref="Message"/> event-handler.
81 /// </summary>
82 /// <param name="format">Message string, containing 0 or more format items</param>
83 /// <param name="args">Items to be formatted</param>
84 protected void LogMessage(string format, params object[] args)
85 {
86 if(this.Message != null)
87 {
88 this.Message(format, args);
89 }
90 }
91
92 /// <summary>
93 /// Gets or sets the location to obtain source files and cabinets when
94 /// extracting or updating files in the working directory. This is often
95 /// the location of an original copy of the package that is not meant
96 /// to be modified.
97 /// </summary>
98 public string SourceDirectory
99 {
100 get { return this.sourceDir; }
101 set { this.sourceDir = value; }
102 }
103 private string sourceDir;
104
105 /// <summary>
106 /// Gets or sets the location where files will be extracted to/updated from. Also
107 /// the location where a temporary folder is created during some operations.
108 /// </summary>
109 public string WorkingDirectory
110 {
111 get { return this.workingDir; }
112 set { this.workingDir = value; }
113 }
114 private string workingDir;
115
116 private const string TEMP_DIR_NAME = "WITEMP";
117
118 private string TempDirectory
119 {
120 get { return Path.Combine(this.WorkingDirectory, TEMP_DIR_NAME); }
121 }
122
123 /// <summary>
124 /// Gets the list of file keys that have the specified long file name.
125 /// </summary>
126 /// <param name="longFileName">File name to search for (case-insensitive)</param>
127 /// <returns>Array of file keys, or a 0-length array if none are found</returns>
128 [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
129 public string[] FindFiles(string longFileName)
130 {
131 longFileName = longFileName.ToLowerInvariant();
132 ArrayList fileList = new ArrayList();
133 foreach(KeyValuePair<string, InstallPath> entry in this.Files)
134 {
135 if(((InstallPath) entry.Value).TargetName.ToLowerInvariant()
136 == longFileName)
137 {
138 fileList.Add(entry.Key);
139 }
140 }
141 return (string[]) fileList.ToArray(typeof(string));
142 }
143
144 /// <summary>
145 /// Gets the list of file keys whose long file names match a specified
146 /// regular-expression search pattern.
147 /// </summary>
148 /// <param name="pattern">Regular expression search pattern</param>
149 /// <returns>Array of file keys, or a 0-length array if none are found</returns>
150 public string[] FindFiles(Regex pattern)
151 {
152 ArrayList fileList = new ArrayList();
153 foreach (KeyValuePair<string, InstallPath> entry in this.Files)
154 {
155 if(pattern.IsMatch(((InstallPath) entry.Value).TargetName))
156 {
157 fileList.Add(entry.Key);
158 }
159 }
160 return (string[]) fileList.ToArray(typeof(string));
161 }
162
163 /// <summary>
164 /// Extracts all files to the <see cref="WorkingDirectory"/>. The files are extracted
165 /// to the relative directory matching their <see cref="InstallPath.SourcePath"/>.
166 /// </summary>
167 /// <remarks>If any files have the uncompressed attribute, they will be copied
168 /// from the <see cref="SourceDirectory"/>.</remarks>
169 public void ExtractFiles()
170 {
171 this.ExtractFiles(null);
172 }
173 /// <summary>
174 /// Extracts a specified list of files to the <see cref="WorkingDirectory"/>. The files
175 /// are extracted to the relative directory matching their <see cref="InstallPath.SourcePath"/>.
176 /// </summary>
177 /// <param name="fileKeys">List of file key strings to extract</param>
178 /// <remarks>If any files have the uncompressed attribute, they will be copied
179 /// from the <see cref="SourceDirectory"/>.</remarks>
180 public void ExtractFiles(ICollection<string> fileKeys)
181 {
182 this.ProcessFilesByMediaDisk(fileKeys,
183 new ProcessFilesOnOneMediaDiskHandler(this.ExtractFilesOnOneMediaDisk));
184 }
185
186 private bool IsMergeModule()
187 {
188 return this.CountRows("Media", "`LastSequence` >= 0") == 0 &&
189 this.CountRows("_Streams", "`Name` = 'MergeModule.CABinet'") != 0;
190 }
191
192 private delegate void ProcessFilesOnOneMediaDiskHandler(string mediaCab,
193 InstallPathMap compressedFileMap, InstallPathMap uncompressedFileMap);
194
195 private void ProcessFilesByMediaDisk(ICollection<string> fileKeys,
196 ProcessFilesOnOneMediaDiskHandler diskHandler)
197 {
198 if(this.IsMergeModule())
199 {
200 InstallPathMap files = new InstallPathMap();
201 foreach(string fileKey in this.Files.Keys)
202 {
203 if(fileKeys == null || fileKeys.Contains(fileKey))
204 {
205 files[fileKey] = this.Files[fileKey];
206 }
207 }
208 diskHandler("#MergeModule.CABinet", files, new InstallPathMap());
209 }
210 else
211 {
212 bool defaultCompressed = ((this.SummaryInfo.WordCount & 0x2) != 0);
213
214 View fileView = null, mediaView = null;
215 Record fileRec = null;
216 try
217 {
218 fileView = this.OpenView("SELECT `File`, `Attributes`, `Sequence` " +
219 "FROM `File` ORDER BY `Sequence`");
220 mediaView = this.OpenView("SELECT `DiskId`, `LastSequence`, `Cabinet` " +
221 "FROM `Media` ORDER BY `DiskId`");
222 fileView.Execute();
223 mediaView.Execute();
224
225 int currentMediaDiskId = -1;
226 int currentMediaMaxSequence = -1;
227 string currentMediaCab = null;
228 InstallPathMap compressedFileMap = new InstallPathMap();
229 InstallPathMap uncompressedFileMap = new InstallPathMap();
230
231 while((fileRec = fileView.Fetch()) != null)
232 {
233 string fileKey = (string) fileRec[1];
234
235 if(fileKeys == null || fileKeys.Contains(fileKey))
236 {
237 int fileAttributes = fileRec.GetInteger(2);
238 int fileSequence = fileRec.GetInteger(3);
239
240 InstallPath fileInstallPath = this.Files[fileKey];
241 if(fileInstallPath == null)
242 {
243 this.LogMessage("Could not get install path for source file: {0}", fileKey);
244 throw new InstallerException("Could not get install path for source file: " + fileKey);
245 }
246
247 if(fileSequence > currentMediaMaxSequence)
248 {
249 if(currentMediaDiskId != -1)
250 {
251 diskHandler(currentMediaCab,
252 compressedFileMap, uncompressedFileMap);
253 compressedFileMap.Clear();
254 uncompressedFileMap.Clear();
255 }
256
257 while(fileSequence > currentMediaMaxSequence)
258 {
259 Record mediaRec = mediaView.Fetch();
260 if(mediaRec == null)
261 {
262 currentMediaDiskId = -1;
263 break;
264 }
265 using(mediaRec)
266 {
267 currentMediaDiskId = mediaRec.GetInteger(1);
268 currentMediaMaxSequence = mediaRec.GetInteger(2);
269 currentMediaCab = (string) mediaRec[3];
270 }
271 }
272 if(fileSequence > currentMediaMaxSequence) break;
273 }
274
275 if((fileAttributes & (int) WixToolset.Dtf.WindowsInstaller.FileAttributes.Compressed) != 0)
276 {
277 compressedFileMap[fileKey] = fileInstallPath;
278 }
279 else if ((fileAttributes & (int) WixToolset.Dtf.WindowsInstaller.FileAttributes.NonCompressed) != 0)
280 {
281 // Non-compressed files are located
282 // in the same directory as the MSI, without any path.
283 uncompressedFileMap[fileKey] = new InstallPath(fileInstallPath.SourceName);
284 }
285 else if(defaultCompressed)
286 {
287 compressedFileMap[fileKey] = fileInstallPath;
288 }
289 else
290 {
291 uncompressedFileMap[fileKey] = fileInstallPath;
292 }
293 }
294 fileRec.Close();
295 fileRec = null;
296 }
297 if(currentMediaDiskId != -1)
298 {
299 diskHandler(currentMediaCab,
300 compressedFileMap, uncompressedFileMap);
301 }
302 }
303 finally
304 {
305 if (fileRec != null) fileRec.Close();
306 if (fileView != null) fileView.Close();
307 if (mediaView != null) mediaView.Close();
308 }
309 }
310 }
311
312 [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
313 private void ExtractFilesOnOneMediaDisk(string mediaCab,
314 InstallPathMap compressedFileMap, InstallPathMap uncompressedFileMap)
315 {
316 if(compressedFileMap.Count > 0)
317 {
318 string cabFile = null;
319 if(mediaCab.StartsWith("#", StringComparison.Ordinal))
320 {
321 mediaCab = mediaCab.Substring(1);
322
323 using(View streamView = this.OpenView("SELECT `Name`, `Data` FROM `_Streams` " +
324 "WHERE `Name` = '{0}'", mediaCab))
325 {
326 streamView.Execute();
327 Record streamRec = streamView.Fetch();
328 if(streamRec == null)
329 {
330 this.LogMessage("Stream not found: {0}", mediaCab);
331 throw new InstallerException("Stream not found: " + mediaCab);
332 }
333 using(streamRec)
334 {
335 this.LogMessage("extract cab {0}", mediaCab);
336 Directory.CreateDirectory(this.TempDirectory);
337 cabFile = Path.Combine(this.TempDirectory,
338 Path.GetFileNameWithoutExtension(mediaCab) + ".cab");
339 streamRec.GetStream("Data", cabFile);
340 }
341 }
342 }
343 else
344 {
345 cabFile = Path.Combine(this.SourceDirectory, mediaCab);
346 }
347
348 this.cabName = mediaCab;
349 this.cabMsg = "extract {0}\\{1} {2}";
350 new CabInfo(cabFile).UnpackFileSet(compressedFileMap.SourcePaths, this.WorkingDirectory,
351 this.CabinetProgress);
352 ClearReadOnlyAttribute(this.WorkingDirectory, compressedFileMap.Values);
353 }
354 foreach(InstallPath fileInstallPath in uncompressedFileMap.Values)
355 {
356 string sourcePath = Path.Combine(this.SourceDirectory, fileInstallPath.SourcePath);
357 string extractPath = Path.Combine(this.WorkingDirectory, fileInstallPath.SourcePath);
358 if(Path.GetFullPath(sourcePath).ToLowerInvariant() !=
359 Path.GetFullPath(extractPath).ToLowerInvariant())
360 {
361 if(!File.Exists(sourcePath))
362 {
363 this.LogMessage("Error: Uncompressed file not found: {0}", sourcePath);
364 throw new FileNotFoundException("Uncompressed file not found.", sourcePath);
365 }
366 else
367 {
368 this.LogMessage("copy {0} {1}", sourcePath, extractPath);
369 Directory.CreateDirectory(Path.GetDirectoryName(extractPath));
370 File.Copy(sourcePath, extractPath, true);
371 }
372 }
373 else
374 {
375 if(!File.Exists(extractPath))
376 {
377 this.LogMessage("Error: Uncompressed file not found: {0}", extractPath);
378 throw new FileNotFoundException("Uncompressed file not found.", extractPath);
379 }
380 }
381 }
382 }
383
384 private void CabinetProgress(object sender, ArchiveProgressEventArgs e)
385 {
386 switch(e.ProgressType)
387 {
388 case ArchiveProgressType.StartFile:
389 {
390 string filePath = e.CurrentFileName;
391 if(this.filePathMap != null)
392 {
393 InstallPath fileInstallPath = this.Files[e.CurrentFileName];
394 if(fileInstallPath != null)
395 {
396 filePath = fileInstallPath.SourcePath;
397 }
398 }
399 this.LogMessage(this.cabMsg, this.cabName, e.CurrentFileName,
400 Path.Combine(this.WorkingDirectory, filePath));
401 }
402 break;
403 }
404 }
405
406 /// <summary>
407 /// Updates the install package with new files from the <see cref="WorkingDirectory"/>. The
408 /// files must be in the relative directory matching their <see cref="InstallPath.SourcePath"/>.
409 /// This method re-compresses and packages the files if necessary, and also updates the
410 /// following data: File.FileSize, File.Version, File.Language, MsiFileHash.HashPart*
411 /// </summary>
412 /// <remarks>
413 /// The cabinet compression level used during re-cabbing can be configured with the
414 /// <see cref="CompressionLevel"/> property.
415 /// </remarks>
416 public void UpdateFiles()
417 {
418 this.UpdateFiles(null);
419 }
420 /// <summary>
421 /// Updates the install package with new files from the <see cref="WorkingDirectory"/>. The
422 /// files must be in the relative directory matching their <see cref="InstallPath.SourcePath"/>.
423 /// This method re-compresses and packages the files if necessary, and also updates the
424 /// following data: File.FileSize, File.Version, File.Language, MsiFileHash.HashPart?.
425 /// </summary>
426 /// <param name="fileKeys">List of file key strings to update</param>
427 /// <remarks>
428 /// This method does not change the media structure of the package, so it may require extracting
429 /// and re-compressing a large cabinet just to update one file.
430 /// <p>The cabinet compression level used during re-cabbing can be configured with the
431 /// <see cref="CompressionLevel"/> property.</p>
432 /// </remarks>
433 public void UpdateFiles(ICollection<string> fileKeys)
434 {
435 this.ProcessFilesByMediaDisk(fileKeys,
436 new ProcessFilesOnOneMediaDiskHandler(this.UpdateFilesOnOneMediaDisk));
437 }
438
439 private void UpdateFilesOnOneMediaDisk(string mediaCab,
440 InstallPathMap compressedFileMap, InstallPathMap uncompressedFileMap)
441 {
442 if(compressedFileMap.Count > 0)
443 {
444 string cabFile = null;
445 bool cabFileIsTemp = false;
446 if(mediaCab.StartsWith("#", StringComparison.Ordinal))
447 {
448 cabFileIsTemp = true;
449 mediaCab = mediaCab.Substring(1);
450
451 using(View streamView = this.OpenView("SELECT `Name`, `Data` FROM `_Streams` " +
452 "WHERE `Name` = '{0}'", mediaCab))
453 {
454 streamView.Execute();
455 Record streamRec = streamView.Fetch();
456 if(streamRec == null)
457 {
458 this.LogMessage("Stream not found: {0}", mediaCab);
459 throw new InstallerException("Stream not found: " + mediaCab);
460 }
461 using(streamRec)
462 {
463 this.LogMessage("extract cab {0}", mediaCab);
464 Directory.CreateDirectory(this.TempDirectory);
465 cabFile = Path.Combine(this.TempDirectory,
466 Path.GetFileNameWithoutExtension(mediaCab) + ".cab");
467 streamRec.GetStream("Data", cabFile);
468 }
469 }
470 }
471 else
472 {
473 cabFile = Path.Combine(this.SourceDirectory, mediaCab);
474 }
475
476 CabInfo cab = new CabInfo(cabFile);
477 ArrayList fileKeyList = new ArrayList();
478 foreach (CabFileInfo fileInCab in cab.GetFiles())
479 {
480 string fileKey = fileInCab.Name;
481 if(this.Files[fileKey] != null)
482 {
483 fileKeyList.Add(fileKey);
484 }
485 }
486 string[] fileKeys = (string[]) fileKeyList.ToArray(typeof(string));
487
488 Directory.CreateDirectory(this.TempDirectory);
489
490 ArrayList remainingFileKeys = new ArrayList(fileKeys);
491 foreach(string fileKey in fileKeys)
492 {
493 InstallPath fileInstallPath = compressedFileMap[fileKey];
494 if(fileInstallPath != null)
495 {
496 UpdateFileStats(fileKey, fileInstallPath);
497
498 string filePath = Path.Combine(this.WorkingDirectory, fileInstallPath.SourcePath);
499 this.LogMessage("copy {0} {1}", filePath, fileKey);
500 File.Copy(filePath, Path.Combine(this.TempDirectory, fileKey), true);
501 remainingFileKeys.Remove(fileKey);
502 }
503 }
504
505 if(remainingFileKeys.Count > 0)
506 {
507 this.cabName = mediaCab;
508 this.cabMsg = "extract {0}\\{1}";
509 string[] remainingFileKeysArray = (string[]) remainingFileKeys.ToArray(typeof(string));
510 cab.UnpackFiles(remainingFileKeysArray, this.TempDirectory, remainingFileKeysArray,
511 this.CabinetProgress);
512 }
513
514 ClearReadOnlyAttribute(this.TempDirectory, fileKeys);
515
516 if(!cabFileIsTemp)
517 {
518 cab = new CabInfo(Path.Combine(this.WorkingDirectory, mediaCab));
519 }
520 this.cabName = mediaCab;
521 this.cabMsg = "compress {0}\\{1}";
522 cab.PackFiles(this.TempDirectory, fileKeys, fileKeys,
523 this.CompressionLevel, this.CabinetProgress);
524
525 if(cabFileIsTemp)
526 {
527 using (Record streamRec = new Record(1))
528 {
529 streamRec.SetStream(1, cabFile);
530 this.Execute(String.Format(
531 "UPDATE `_Streams` SET `Data` = ? WHERE `Name` = '{0}'", mediaCab),
532 streamRec);
533 }
534 }
535 }
536
537 foreach (KeyValuePair<string, InstallPath> entry in uncompressedFileMap)
538 {
539 UpdateFileStats((string) entry.Key, (InstallPath) entry.Value);
540 }
541 }
542
543 private void UpdateFileStats(string fileKey, InstallPath fileInstallPath)
544 {
545 string filePath = Path.Combine(this.WorkingDirectory, fileInstallPath.SourcePath);
546 if(!File.Exists(filePath))
547 {
548 this.LogMessage("Updated source file not found: {0}", filePath);
549 throw new FileNotFoundException("Updated source file not found: " + filePath);
550 }
551
552 this.LogMessage("updatestats {0}", fileKey);
553
554 string version = Installer.GetFileVersion(filePath);
555 string language = Installer.GetFileLanguage(filePath);
556 long size = new FileInfo(filePath).Length;
557
558 this.Execute("UPDATE `File` SET `Version` = '{0}', `Language` = '{1}', " +
559 "`FileSize` = {2} WHERE `File` = '{3}'", version, language, size, fileKey);
560
561 if ((version == null || version.Length == 0) && this.Tables.Contains("MsiFileHash"))
562 {
563 int[] hash = new int[4];
564 Installer.GetFileHash(filePath, hash);
565 this.Execute("DELETE FROM `MsiFileHash` WHERE `File_` = '{0}'", fileKey);
566 this.Execute("INSERT INTO `MsiFileHash` (`File_`, `Options`, `HashPart1`, `HashPart2`, " +
567 "`HashPart3`, `HashPart4`) VALUES ('" + fileKey + "', 0, {0}, {1}, {2}, {3})",
568 hash[0], hash[1], hash[2], hash[3]);
569 }
570 }
571
572 /// <summary>
573 /// Consolidates a package by combining and re-compressing all files into a single
574 /// internal or external cabinet.
575 /// </summary>
576 /// <param name="mediaCabinet"></param>
577 /// <remarks>If an installation package was built from many merge modules, this
578 /// method can somewhat decrease package size, complexity, and installation time.
579 /// <p>This method will also convert a package with all or mostly uncompressed
580 /// files into a package where all files are compressed.</p>
581 /// <p>If the package contains any not-yet-applied binary file patches (for
582 /// example, a package generated by a call to <see cref="ApplyPatch"/>) then
583 /// this method will apply the patches before compressing the updated files.</p>
584 /// <p>This method edits the database summary information and the File, Media
585 /// and Patch tables as necessary to maintain a valid installation package.</p>
586 /// <p>The cabinet compression level used during re-cabbing can be configured with the
587 /// <see cref="CompressionLevel"/> property.</p>
588 /// </remarks>
589 public void Consolidate(string mediaCabinet)
590 {
591 this.LogMessage("Consolidating package");
592
593 Directory.CreateDirectory(this.TempDirectory);
594
595 this.LogMessage("Extracting/preparing files");
596 this.ProcessFilesByMediaDisk(null,
597 new ProcessFilesOnOneMediaDiskHandler(this.PrepareOneMediaDiskForConsolidation));
598
599 this.LogMessage("Applying any file patches");
600 ApplyFilePatchesForConsolidation();
601
602 this.LogMessage("Clearing PatchPackage, Patch, MsiPatchHeaders tables");
603 if (this.Tables.Contains("PatchPackage"))
604 {
605 this.Execute("DELETE FROM `PatchPackage` WHERE `PatchId` <> ''");
606 }
607 if (this.Tables.Contains("Patch"))
608 {
609 this.Execute("DELETE FROM `Patch` WHERE `File_` <> ''");
610 }
611 if (this.Tables.Contains("MsiPatchHeaders"))
612 {
613 this.Execute("DELETE FROM `MsiPatchHeaders` WHERE `StreamRef` <> ''");
614 }
615
616 this.LogMessage("Resequencing files");
617 ArrayList files = new ArrayList();
618 using(View fileView = this.OpenView("SELECT `File`, `Attributes`, `Sequence` " +
619 "FROM `File` ORDER BY `Sequence`"))
620 {
621 fileView.Execute();
622
623 foreach (Record fileRec in fileView) using(fileRec)
624 {
625 files.Add(fileRec[1]);
626 int fileAttributes = fileRec.GetInteger(2);
627 fileAttributes &= ~(int) (WixToolset.Dtf.WindowsInstaller.FileAttributes.Compressed
628 | WixToolset.Dtf.WindowsInstaller.FileAttributes.NonCompressed | WixToolset.Dtf.WindowsInstaller.FileAttributes.PatchAdded);
629 fileRec[2] = fileAttributes;
630 fileRec[3] = files.Count;
631 fileView.Update(fileRec);
632 }
633 }
634
635 bool internalCab = false;
636 if(mediaCabinet.StartsWith("#", StringComparison.Ordinal))
637 {
638 internalCab = true;
639 mediaCabinet = mediaCabinet.Substring(1);
640 }
641
642 this.LogMessage("Cabbing files");
643 string[] fileKeys = (string[]) files.ToArray(typeof(string));
644 string cabPath = Path.Combine(internalCab ? this.TempDirectory
645 : this.WorkingDirectory, mediaCabinet);
646 this.cabName = mediaCabinet;
647 this.cabMsg = "compress {0}\\{1}";
648 new CabInfo(cabPath).PackFiles(this.TempDirectory, fileKeys,
649 fileKeys, this.CompressionLevel, this.CabinetProgress);
650
651 this.DeleteEmbeddedCabs();
652
653 if(internalCab)
654 {
655 this.LogMessage("Inserting cab stream into MSI");
656 Record cabRec = new Record(1);
657 cabRec.SetStream(1, cabPath);
658 this.Execute("INSERT INTO `_Streams` (`Name`, `Data`) VALUES ('" + mediaCabinet + "', ?)", cabRec);
659 }
660
661 this.LogMessage("Inserting cab media record into MSI");
662 this.Execute("DELETE FROM `Media` WHERE `DiskId` <> 0");
663 this.Execute("INSERT INTO `Media` (`DiskId`, `LastSequence`, `Cabinet`) " +
664 "VALUES (1, " + files.Count + ", '" + (internalCab ? "#" : "") + mediaCabinet + "')");
665
666
667 this.LogMessage("Setting compressed flag on package summary info");
668 this.SummaryInfo.WordCount = this.SummaryInfo.WordCount | 2;
669 this.SummaryInfo.Persist();
670 }
671
672 private void DeleteEmbeddedCabs()
673 {
674 using (View view = this.OpenView("SELECT `Cabinet` FROM `Media` WHERE `Cabinet` <> ''"))
675 {
676 view.Execute();
677
678 foreach (Record rec in view) using(rec)
679 {
680 string cab = rec.GetString(1);
681 if(cab.StartsWith("#", StringComparison.Ordinal))
682 {
683 cab = cab.Substring(1);
684 this.LogMessage("Deleting embedded cab stream: {0}", cab);
685 this.Execute("DELETE FROM `_Streams` WHERE `Name` = '{0}'", cab);
686 }
687 }
688 }
689 }
690
691 private void PrepareOneMediaDiskForConsolidation(string mediaCab,
692 InstallPathMap compressedFileMap, InstallPathMap uncompressedFileMap)
693 {
694 if(compressedFileMap.Count > 0)
695 {
696 string cabFile = null;
697 if(mediaCab.StartsWith("#", StringComparison.Ordinal))
698 {
699 mediaCab = mediaCab.Substring(1);
700
701 using (View streamView = this.OpenView("SELECT `Name`, `Data` FROM `_Streams` " +
702 "WHERE `Name` = '{0}'", mediaCab))
703 {
704 streamView.Execute();
705 Record streamRec = streamView.Fetch();
706 if(streamRec == null)
707 {
708 this.LogMessage("Stream not found: {0}", mediaCab);
709 throw new InstallerException("Stream not found: " + mediaCab);
710 }
711 using(streamRec)
712 {
713 this.LogMessage("extract cab {0}", mediaCab);
714 cabFile = Path.Combine(this.TempDirectory,
715 Path.GetFileNameWithoutExtension(mediaCab) + ".cab");
716 streamRec.GetStream("Data", cabFile);
717 }
718 }
719 }
720 else
721 {
722 cabFile = Path.Combine(this.SourceDirectory, mediaCab);
723 }
724 string[] fileKeys = new string[compressedFileMap.Keys.Count];
725 compressedFileMap.Keys.CopyTo(fileKeys, 0);
726 this.cabName = mediaCab;
727 this.cabMsg = "extract {0}\\{1}";
728 new CabInfo(cabFile).UnpackFiles(fileKeys, this.TempDirectory, fileKeys,
729 this.CabinetProgress);
730 ClearReadOnlyAttribute(this.TempDirectory, fileKeys);
731 }
732 foreach (KeyValuePair<string, InstallPath> entry in uncompressedFileMap)
733 {
734 string fileKey = (string) entry.Key;
735 InstallPath fileInstallPath = (InstallPath) entry.Value;
736
737 string filePath = Path.Combine(this.SourceDirectory, fileInstallPath.SourcePath);
738 this.LogMessage("copy {0} {1}", filePath, fileKey);
739 File.Copy(filePath, Path.Combine(this.TempDirectory, fileKey));
740 }
741 }
742
743 private void ClearReadOnlyAttribute(string baseDirectory, IEnumerable filePaths)
744 {
745 foreach(object filePath in filePaths)
746 {
747 string fullFilePath = Path.Combine(baseDirectory, filePath.ToString());
748 if (File.Exists(fullFilePath))
749 {
750 System.IO.FileAttributes fileAttributes = File.GetAttributes(fullFilePath);
751 if ((fileAttributes & System.IO.FileAttributes.ReadOnly) != 0)
752 {
753 fileAttributes &= ~System.IO.FileAttributes.ReadOnly;
754 File.SetAttributes(fullFilePath, fileAttributes);
755 }
756 }
757 }
758 }
759
760 private void ApplyFilePatchesForConsolidation()
761 {
762 if(this.Tables.Contains("Patch"))
763 {
764 using(View patchView = this.OpenView("SELECT `File_`, `Sequence` " +
765 "FROM `Patch` ORDER BY `Sequence`"))
766 {
767 patchView.Execute();
768 Hashtable extractedPatchCabs = new Hashtable();
769
770 foreach (Record patchRec in patchView) using(patchRec)
771 {
772 string fileKey = (string) patchRec[1];
773 int sequence = patchRec.GetInteger(2);
774 this.LogMessage("patch {0}", fileKey);
775
776 string tempPatchFile = Path.Combine(this.TempDirectory, fileKey + ".pat");
777 ExtractFilePatch(fileKey, sequence, tempPatchFile, extractedPatchCabs);
778 string filePath = Path.Combine(this.TempDirectory, fileKey);
779 string oldFilePath = filePath + ".old";
780 if(File.Exists(oldFilePath)) File.Delete(oldFilePath);
781 File.Move(filePath, oldFilePath);
782 Type.GetType("WixToolset.Dtf.WindowsInstaller.FilePatch")
783 .GetMethod("ApplyPatchToFile",
784 new Type[] { typeof(string), typeof(string), typeof(string) })
785 .Invoke(null, new object[] { tempPatchFile, oldFilePath, filePath });
786 }
787 }
788 }
789 }
790
791 private void ExtractFilePatch(string fileKey, int sequence, string extractPath,
792 IDictionary extractedCabs)
793 {
794 string mediaCab = null;
795 using(View mediaView = this.OpenView("SELECT `DiskId`, `LastSequence`, `Cabinet` " +
796 "FROM `Media` ORDER BY `DiskId`"))
797 {
798 mediaView.Execute();
799
800 foreach (Record mediaRec in mediaView) using(mediaRec)
801 {
802 int mediaMaxSequence = mediaRec.GetInteger(2);
803 if(mediaMaxSequence >= sequence)
804 {
805 mediaCab = mediaRec.GetString(3);
806 break;
807 }
808 }
809 }
810
811 if(mediaCab == null || mediaCab.Length == 0)
812 {
813 this.LogMessage("Could not find cabinet for file patch: {0}", fileKey);
814 throw new InstallerException("Could not find cabinet for file patch: " + fileKey);
815 }
816
817 if(!mediaCab.StartsWith("#", StringComparison.Ordinal))
818 {
819 this.LogMessage("Error: Patch cabinet {0} must be embedded", mediaCab);
820 throw new InstallerException("Patch cabinet " + mediaCab + " must be embedded.");
821 }
822 mediaCab = mediaCab.Substring(1);
823
824 string cabFile = (string) extractedCabs[mediaCab];
825 if(cabFile == null)
826 {
827 using(View streamView = this.OpenView("SELECT `Name`, `Data` FROM `_Streams` " +
828 "WHERE `Name` = '{0}'", mediaCab))
829 {
830 streamView.Execute();
831 Record streamRec = streamView.Fetch();
832 if(streamRec == null)
833 {
834 this.LogMessage("Stream not found: {0}", mediaCab);
835 throw new InstallerException("Stream not found: " + mediaCab);
836 }
837 using(streamRec)
838 {
839 this.LogMessage("extract cab {0}", mediaCab);
840 Directory.CreateDirectory(this.TempDirectory);
841 cabFile = Path.Combine(this.TempDirectory,
842 Path.GetFileNameWithoutExtension(mediaCab) + ".cab");
843 streamRec.GetStream("Data", cabFile);
844 }
845 }
846 extractedCabs[mediaCab] = cabFile;
847 }
848
849 this.LogMessage("extract patch {0}\\{1}", mediaCab, fileKey);
850 new CabInfo(cabFile).UnpackFile(fileKey, extractPath);
851 }
852
853 /// <summary>
854 /// Rebuilds the cached directory structure information accessed by the
855 /// <see cref="Directories"/> and <see cref="Files"/> properties. This
856 /// should be done after modifying the File, Component, or Directory
857 /// tables, or else the cached information may no longer be accurate.
858 /// </summary>
859 public void UpdateDirectories()
860 {
861 this.dirPathMap = null;
862 this.filePathMap = InstallPathMap.BuildFilePathMap(this,
863 InstallPathMap.BuildComponentPathMap(this, this.Directories), false);
864 }
865
866 /// <summary>
867 /// Gets a mapping from Directory keys to source/target paths.
868 /// </summary>
869 /// <remarks>
870 /// If the Directory table is modified, this mapping
871 /// will be outdated until you call <see cref="UpdateDirectories"/>.
872 /// </remarks>
873 public InstallPathMap Directories
874 {
875 get
876 {
877 if(this.dirPathMap == null)
878 {
879 this.dirPathMap = InstallPathMap.BuildDirectoryPathMap(this, false);
880 }
881 return this.dirPathMap;
882 }
883 }
884 private InstallPathMap dirPathMap;
885
886 /// <summary>
887 /// Gets a mapping from File keys to source/target paths.
888 /// </summary>
889 /// <remarks>
890 /// If the File, Component, or Directory tables are modified, this mapping
891 /// may be outdated until you call <see cref="UpdateDirectories"/>.
892 /// </remarks>
893 public InstallPathMap Files
894 {
895 get
896 {
897 if(this.filePathMap == null)
898 {
899 this.filePathMap = InstallPathMap.BuildFilePathMap(this,
900 InstallPathMap.BuildComponentPathMap(this, this.Directories), false);
901 }
902 return this.filePathMap;
903 }
904 }
905 private InstallPathMap filePathMap;
906
907 /// <summary>
908 /// Gets or sets the compression level used by <see cref="UpdateFiles()"/>
909 /// and <see cref="Consolidate"/>.
910 /// </summary>
911 /// <remarks>
912 /// If the Directory table is modified, this mapping will be outdated
913 /// until you close and reopen the install package.
914 /// </remarks>
915 public CompressionLevel CompressionLevel
916 {
917 get { return this.compressionLevel; }
918 set { this.compressionLevel = value; }
919 }
920 private CompressionLevel compressionLevel;
921
922 /// <summary>
923 /// Applies a patch package to the database, resulting in an installation package that
924 /// has the patch built-in.
925 /// </summary>
926 /// <param name="patchPackage">The patch package to be applied</param>
927 /// <param name="transform">Optional name of the specific transform to apply.
928 /// This parameter is usually left null, which causes the patch to be searched for
929 /// a transform that is valid to apply to this database.</param>
930 /// <remarks>
931 /// If the patch contains any binary file patches, they will not immediately be applied
932 /// to the target files, though they will at installation time.
933 /// <p>After calling this method you can use <see cref="Consolidate"/> to apply
934 /// the file patches immediately and also discard any outdated files from the package.</p>
935 /// </remarks>
936 public void ApplyPatch(PatchPackage patchPackage, string transform)
937 {
938 if(patchPackage == null) throw new ArgumentNullException("patchPackage");
939
940 this.LogMessage("Applying patch file {0} to database {1}",
941 patchPackage.FilePath, this.FilePath);
942
943 if(transform == null)
944 {
945 this.LogMessage("No transform specified; searching for valid patch transform");
946 string[] validTransforms = patchPackage.GetValidTransforms(this);
947 if(validTransforms.Length == 0)
948 {
949 this.LogMessage("No valid patch transform was found");
950 throw new InvalidOperationException("No valid patch transform was found.");
951 }
952 transform = validTransforms[0];
953 }
954 this.LogMessage("Patch transform = {0}", transform);
955
956 string patchPrefix = Path.GetFileNameWithoutExtension(patchPackage.FilePath) + "_";
957
958 string specialTransform = "#" + transform;
959 Directory.CreateDirectory(this.TempDirectory);
960 this.LogMessage("Extracting substorage {0}", transform);
961 string transformFile = Path.Combine(this.TempDirectory,
962 patchPrefix + Path.GetFileNameWithoutExtension(transform) + ".mst");
963 patchPackage.ExtractTransform(transform, transformFile);
964 this.LogMessage("Extracting substorage {0}", specialTransform);
965 string specialTransformFile = Path.Combine(this.TempDirectory,
966 patchPrefix + Path.GetFileNameWithoutExtension(specialTransform) + ".mst");
967 patchPackage.ExtractTransform(specialTransform, specialTransformFile);
968
969 if (this.Tables.Contains("Patch") && !this.Tables["Patch"].Columns.Contains("_StreamRef"))
970 {
971 if(this.CountRows("Patch") > 0)
972 {
973 this.LogMessage("Warning: non-empty Patch table exists without StreamRef_ column; " +
974 "patch transform may fail");
975 }
976 else
977 {
978 this.Execute("DROP TABLE `Patch`");
979 this.Execute("CREATE TABLE `Patch` (`File_` CHAR(72) NOT NULL, " +
980 "`Sequence` INTEGER NOT NULL, `PatchSize` LONG NOT NULL, " +
981 "`Attributes` INTEGER NOT NULL, `Header` OBJECT, `StreamRef_` CHAR(72) " +
982 "PRIMARY KEY `File_`, `Sequence`)");
983 }
984 }
985
986 this.LogMessage("Applying transform {0} to database", transform);
987 this.ApplyTransform(transformFile);
988 this.LogMessage("Applying transform {0} to database", specialTransform);
989 this.ApplyTransform(specialTransformFile);
990
991 if (this.Tables.Contains("MsiPatchHeaders") && this.CountRows("MsiPatchHeaders") > 0 &&
992 (!this.Tables.Contains("Patch") || this.CountRows("Patch", "`StreamRef_` <> ''") == 0))
993 {
994 this.LogMessage("Error: patch transform failed because of missing Patch.StreamRef_ column");
995 throw new InstallerException("Patch transform failed because of missing Patch.StreamRef_ column");
996 }
997
998 IList<int> mediaIds = this.ExecuteIntegerQuery("SELECT `Media_` FROM `PatchPackage` " +
999 "WHERE `PatchId` = '{0}'", patchPackage.PatchCode);
1000 if (mediaIds.Count == 0)
1001 {
1002 this.LogMessage("Warning: PatchPackage Media record not found -- " +
1003 "skipping inclusion of patch cabinet");
1004 }
1005 else
1006 {
1007 int patchMediaDiskId = mediaIds[0];
1008 IList<string> patchCabinets = this.ExecuteStringQuery("SELECT `Cabinet` FROM `Media` " +
1009 "WHERE `DiskId` = {0}", patchMediaDiskId);
1010 if(patchCabinets.Count == 0)
1011 {
1012 this.LogMessage("Patch cabinet record not found");
1013 throw new InstallerException("Patch cabinet record not found.");
1014 }
1015 string patchCabinet = patchCabinets[0];
1016 this.LogMessage("Patch cabinet = {0}", patchCabinet);
1017 if(!patchCabinet.StartsWith("#", StringComparison.Ordinal))
1018 {
1019 this.LogMessage("Error: Patch cabinet must be embedded");
1020 throw new InstallerException("Patch cabinet must be embedded.");
1021 }
1022 patchCabinet = patchCabinet.Substring(1);
1023
1024 string renamePatchCabinet = patchPrefix + patchCabinet;
1025
1026 const int HIGH_DISKID = 30000; // Must not collide with other patch media DiskIDs
1027 int renamePatchMediaDiskId = HIGH_DISKID;
1028 while (this.CountRows("Media", "`DiskId` = " + renamePatchMediaDiskId) > 0) renamePatchMediaDiskId++;
1029
1030 // Since the patch cab is now embedded in the MSI, it shouldn't have a separate disk prompt/source
1031 this.LogMessage("Renaming the patch media record");
1032 int lastSeq = Convert.ToInt32(this.ExecuteScalar("SELECT `LastSequence` FROM `Media` WHERE `DiskId` = {0}", patchMediaDiskId));
1033 this.Execute("DELETE FROM `Media` WHERE `DiskId` = {0}", patchMediaDiskId);
1034 this.Execute("INSERT INTO `Media` (`DiskId`, `LastSequence`, `Cabinet`) VALUES ({0}, '{1}', '#{2}')",
1035 renamePatchMediaDiskId, lastSeq, renamePatchCabinet);
1036 this.Execute("UPDATE `PatchPackage` SET `Media_` = {0} WHERE `PatchId` = '{1}'", renamePatchMediaDiskId, patchPackage.PatchCode);
1037
1038 this.LogMessage("Copying patch cabinet: {0}", patchCabinet);
1039 string patchCabFile = Path.Combine(this.TempDirectory,
1040 Path.GetFileNameWithoutExtension(patchCabinet) + ".cab");
1041 using(View streamView = patchPackage.OpenView("SELECT `Name`, `Data` FROM `_Streams` " +
1042 "WHERE `Name` = '{0}'", patchCabinet))
1043 {
1044 streamView.Execute();
1045 Record streamRec = streamView.Fetch();
1046 if(streamRec == null)
1047 {
1048 this.LogMessage("Error: Patch cabinet not found");
1049 throw new InstallerException("Patch cabinet not found.");
1050 }
1051 using(streamRec)
1052 {
1053 streamRec.GetStream(2, patchCabFile);
1054 }
1055 }
1056 using(Record patchCabRec = new Record(2))
1057 {
1058 patchCabRec[1] = patchCabinet;
1059 patchCabRec.SetStream(2, patchCabFile);
1060 this.Execute("INSERT INTO `_Streams` (`Name`, `Data`) VALUES (?, ?)", patchCabRec);
1061 }
1062
1063 this.LogMessage("Ensuring PatchFiles action exists in InstallExecuteSequence table");
1064 if (this.Tables.Contains("InstallExecuteSequence"))
1065 {
1066 if(this.CountRows("InstallExecuteSequence", "`Action` = 'PatchFiles'") == 0)
1067 {
1068 IList<int> installFilesSeqList = this.ExecuteIntegerQuery("SELECT `Sequence` " +
1069 "FROM `InstallExecuteSequence` WHERE `Action` = 'InstallFiles'");
1070 short installFilesSeq = (short) (installFilesSeqList.Count != 0 ?
1071 installFilesSeqList[0] : 0);
1072 this.Execute("INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) " +
1073 "VALUES ('PatchFiles', {0})", installFilesSeq + 1);
1074 }
1075 }
1076
1077 // Patch-added files need to be marked always-compressed
1078 this.LogMessage("Adjusting attributes of patch-added files");
1079 using(View fileView = this.OpenView("SELECT `File`, `Attributes`, `Sequence` " +
1080 "FROM `File` ORDER BY `Sequence`"))
1081 {
1082 fileView.Execute();
1083
1084 foreach (Record fileRec in fileView) using(fileRec)
1085 {
1086 int fileAttributes = fileRec.GetInteger(2);
1087 if ((fileAttributes & (int) WixToolset.Dtf.WindowsInstaller.FileAttributes.PatchAdded) != 0)
1088 {
1089 fileAttributes = (fileAttributes | (int) WixToolset.Dtf.WindowsInstaller.FileAttributes.Compressed)
1090 & ~(int) WixToolset.Dtf.WindowsInstaller.FileAttributes.NonCompressed
1091 & ~(int) WixToolset.Dtf.WindowsInstaller.FileAttributes.PatchAdded;
1092 fileRec[2] = fileAttributes;
1093 fileView.Update(fileRec);
1094 }
1095 }
1096 }
1097 }
1098
1099 this.LogMessage("Applying new summary info from patch package");
1100 this.SummaryInfo.RevisionNumber = this.Property["PATCHNEWPACKAGECODE"];
1101 this.SummaryInfo.Subject = this.Property["PATCHNEWSUMMARYSUBJECT"];
1102 this.SummaryInfo.Comments = this.Property["PATCHNEWSUMMARYCOMMENTS"];
1103 this.SummaryInfo.Persist();
1104 this.Property["PATCHNEWPACKAGECODE" ] = null;
1105 this.Property["PATCHNEWSUMMARYSUBJECT" ] = null;
1106 this.Property["PATCHNEWSUMMARYCOMMENTS"] = null;
1107
1108 this.LogMessage("Patch application finished");
1109 }
1110
1111 /// <summary>
1112 /// Accessor for getting and setting properties of the InstallPackage database.
1113 /// </summary>
1114 public InstallPackageProperties Property
1115 {
1116 get
1117 {
1118 if(this.properties == null)
1119 {
1120 this.properties = new InstallPackageProperties(this);
1121 }
1122 return this.properties;
1123 }
1124 }
1125 private InstallPackageProperties properties = null;
1126}
1127
1128/// <summary>
1129/// Accessor for getting and setting properties of the <see cref="InstallPackage"/> database.
1130/// </summary>
1131public class InstallPackageProperties
1132{
1133 internal InstallPackageProperties(InstallPackage installPackage)
1134 {
1135 this.installPackage = installPackage;
1136 }
1137 private InstallPackage installPackage;
1138
1139 /// <summary>
1140 /// Gets or sets a property in the database. When getting a property
1141 /// that does not exist in the database, an empty string is returned.
1142 /// To remove a property from the database, set it to an empty string.
1143 /// </summary>
1144 /// <remarks>
1145 /// This has the same results as direct SQL queries on the Property table; it's only
1146 /// meant to be a more convenient way of access.
1147 /// </remarks>
1148 public string this[string name]
1149 {
1150 get
1151 {
1152 IList<string> values = installPackage.ExecuteStringQuery(
1153 "SELECT `Value` FROM `Property` WHERE `Property` = '{0}'", name);
1154 return (values.Count != 0 ? values[0] : "");
1155 }
1156 set
1157 {
1158 Record propRec = new Record(name, (value != null ? value : ""));
1159 installPackage.Execute("DELETE FROM `Property` WHERE `Property` = ?", propRec);
1160 if(value != null && value.Length != 0)
1161 {
1162 installPackage.Execute("INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)",
1163 propRec);
1164 }
1165 }
1166 }
1167}
1168
1169}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller.Package/InstallPath.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller.Package/InstallPath.cs
new file mode 100644
index 00000000..e3ba81b5
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller.Package/InstallPath.cs
@@ -0,0 +1,1073 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller.Package
4{
5 using System;
6 using System.IO;
7 using System.Collections;
8 using System.Collections.Generic;
9 using System.Globalization;
10
11 /// <summary>
12 /// Represents the installation path of a file or directory from an installer product database.
13 /// </summary>
14 public class InstallPath
15 {
16 /// <summary>
17 /// Creates a new InstallPath, specifying a filename.
18 /// </summary>
19 /// <param name="name">The name of the file or directory. Not a full path.</param>
20 public InstallPath(string name) : this(name, false) { }
21
22 /// <summary>
23 /// Creates a new InstallPath, parsing out either the short or long filename.
24 /// </summary>
25 /// <param name="name">The name of the file or directory, in short|long syntax for a filename
26 /// or targetshort|targetlong:sourceshort|sourcelong syntax for a directory.</param>
27 /// <param name="useShortNames">true to parse the short part of the combined filename; false to parse the long part</param>
28 public InstallPath(string name, bool useShortNames)
29 {
30 if(name == null)
31 {
32 throw new ArgumentNullException();
33 }
34 this.parentPath = null;
35 ParseName(name, useShortNames);
36 }
37
38 private void ParseName(string name, bool useShortNames)
39 {
40 string[] parse = name.Split(new char[] { ':' }, 3);
41 if(parse.Length == 3)
42 {
43 // Syntax was targetshort:sourceshort|targetlong:sourcelong.
44 // Chnage it to targetshort|targetlong:sourceshort|sourcelong.
45 parse = name.Split(new char[] { ':', '|' }, 4);
46 if(parse.Length == 4)
47 parse = new string[] { parse[0] + '|' + parse[2], parse[1] + '|' + parse[3] };
48 else
49 parse = new string[] { parse[0] + '|' + parse[1], parse[1] + '|' + parse[2] };
50 }
51 string targetName = parse[0];
52 string sourceName = (parse.Length == 2 ? parse[1] : parse[0]);
53 parse = targetName.Split(new char[] { '|' }, 2);
54 if(parse.Length == 2) targetName = (useShortNames ? parse[0] : parse[1]);
55 parse = sourceName.Split(new char[] { '|' }, 2);
56 if(parse.Length == 2) sourceName = (useShortNames ? parse[0] : parse[1]);
57
58 this.SourceName = sourceName;
59 this.TargetName = targetName;
60 }
61
62 /// <summary>
63 /// Gets the path of the parent directory.
64 /// </summary>
65 public InstallPath ParentPath
66 {
67 get
68 {
69 return parentPath;
70 }
71 }
72 internal void SetParentPath(InstallPath value)
73 {
74 parentPath = value;
75 ResetSourcePath();
76 ResetTargetPath();
77 }
78 private InstallPath parentPath;
79
80 /// <summary>
81 /// Gets the set of child paths if this InstallPath object represents a a directory.
82 /// </summary>
83 public InstallPathCollection ChildPaths
84 {
85 get
86 {
87 if(childPaths == null)
88 {
89 childPaths = new InstallPathCollection(this);
90 }
91 return childPaths;
92 }
93 }
94 private InstallPathCollection childPaths;
95
96 /// <summary>
97 /// Gets or sets the source name of the InstallPath.
98 /// </summary>
99 public string SourceName
100 {
101 get
102 {
103 return sourceName;
104 }
105 set
106 {
107 if(value == null)
108 {
109 throw new ArgumentNullException();
110 }
111 sourceName = value;
112 ResetSourcePath();
113 }
114 }
115 private string sourceName;
116
117 /// <summary>
118 /// Gets or sets the target name of the install path.
119 /// </summary>
120 public string TargetName
121 {
122 get
123 {
124 return targetName;
125 }
126 set
127 {
128 if(value == null)
129 {
130 throw new ArgumentNullException();
131 }
132 targetName = value;
133 ResetTargetPath();
134 }
135 }
136 private string targetName;
137
138 /// <summary>
139 /// Gets the full source path.
140 /// </summary>
141 public string SourcePath
142 {
143 get
144 {
145 if(sourcePath == null)
146 {
147 if(parentPath != null)
148 {
149 sourcePath = (sourceName.Equals(".") ? parentPath.SourcePath
150 : Path.Combine(parentPath.SourcePath, sourceName));
151 }
152 else
153 {
154 sourcePath = sourceName;
155 }
156 }
157 return sourcePath;
158 }
159 set
160 {
161 ResetSourcePath();
162 sourcePath = value;
163 }
164 }
165 private string sourcePath;
166
167 /// <summary>
168 /// Gets the full target path.
169 /// </summary>
170 public string TargetPath
171 {
172 get
173 {
174 if(targetPath == null)
175 {
176 if(parentPath != null)
177 {
178 targetPath = (targetName.Equals(".") ? parentPath.TargetPath
179 : Path.Combine(parentPath.TargetPath, targetName));
180 }
181 else
182 {
183 targetPath = targetName;
184 }
185 }
186 return targetPath;
187 }
188 set
189 {
190 ResetTargetPath();
191 targetPath = value;
192 }
193 }
194 private string targetPath;
195
196 private void ResetSourcePath()
197 {
198 if(sourcePath != null)
199 {
200 sourcePath = null;
201 if(childPaths != null)
202 {
203 foreach(InstallPath ip in childPaths)
204 {
205 ip.ResetSourcePath();
206 }
207 }
208 }
209 }
210
211 private void ResetTargetPath()
212 {
213 if(targetPath != null)
214 {
215 targetPath = null;
216 if(childPaths != null)
217 {
218 foreach(InstallPath ip in childPaths)
219 {
220 ip.ResetTargetPath();
221 }
222 }
223 }
224 }
225
226 /// <summary>
227 /// Gets the full source path.
228 /// </summary>
229 /// <returns><see cref="SourcePath"/></returns>
230 public override String ToString()
231 {
232 return SourcePath;
233 }
234 }
235
236 /// <summary>
237 /// Represents a collection of InstallPaths that are the child paths of the same parent directory.
238 /// </summary>
239 public class InstallPathCollection : IList<InstallPath>
240 {
241 private InstallPath parentPath;
242 private List<InstallPath> items;
243
244 internal InstallPathCollection(InstallPath parentPath)
245 {
246 this.parentPath = parentPath;
247 this.items = new List<InstallPath>();
248 }
249
250 /// <summary>
251 /// Gets or sets the element at the specified index.
252 /// </summary>
253 public InstallPath this[int index]
254 {
255 get
256 {
257 return this.items[index];
258 }
259 set
260 {
261 this.OnSet(this.items[index], value);
262 this.items[index] = value;
263 }
264 }
265
266 /// <summary>
267 /// Adds a new child path to the collection.
268 /// </summary>
269 /// <param name="item">The InstallPath to add.</param>
270 public void Add(InstallPath item)
271 {
272 this.OnInsert(item);
273 this.items.Add(item);
274 }
275
276 /// <summary>
277 /// Removes a child path to the collection.
278 /// </summary>
279 /// <param name="item">The InstallPath to remove.</param>
280 public bool Remove(InstallPath item)
281 {
282 int index = this.items.IndexOf(item);
283 if (index >= 0)
284 {
285 this.OnRemove(item);
286 this.items.RemoveAt(index);
287 return true;
288 }
289 else
290 {
291 return false;
292 }
293 }
294
295 /// <summary>
296 /// Gets the index of a child path in the collection.
297 /// </summary>
298 /// <param name="item">The InstallPath to search for.</param>
299 /// <returns>The index of the item, or -1 if not found.</returns>
300 public int IndexOf(InstallPath item)
301 {
302 return this.items.IndexOf(item);
303 }
304
305 /// <summary>
306 /// Inserts a child path into the collection.
307 /// </summary>
308 /// <param name="index">The insertion index.</param>
309 /// <param name="item">The InstallPath to insert.</param>
310 public void Insert(int index, InstallPath item)
311 {
312 this.OnInsert(item);
313 this.items.Insert(index, item);
314 }
315
316 /// <summary>
317 /// Tests if the collection contains a child path.
318 /// </summary>
319 /// <param name="item">The InstallPath to search for.</param>
320 /// <returns>true if the item is found; false otherwise</returns>
321 public bool Contains(InstallPath item)
322 {
323 return this.items.Contains(item);
324 }
325
326 /// <summary>
327 /// Copies the collection into an array.
328 /// </summary>
329 /// <param name="array">The array to copy into.</param>
330 /// <param name="index">The starting index in the destination array.</param>
331 public void CopyTo(InstallPath[] array, int index)
332 {
333 this.items.CopyTo(array, index);
334 }
335
336 private void OnInsert(InstallPath item)
337 {
338 if (item.ParentPath != null)
339 {
340 item.ParentPath.ChildPaths.Remove(item);
341 }
342
343 item.SetParentPath(this.parentPath);
344 }
345
346 private void OnRemove(InstallPath item)
347 {
348 item.SetParentPath(null);
349 }
350
351 private void OnSet(InstallPath oldItem, InstallPath newItem)
352 {
353 this.OnRemove(oldItem);
354 this.OnInsert(newItem);
355 }
356
357 /// <summary>
358 /// Removes an item from the collection.
359 /// </summary>
360 /// <param name="index">The index of the item to remove.</param>
361 public void RemoveAt(int index)
362 {
363 this.OnRemove(this[index]);
364 this.items.RemoveAt(index);
365 }
366
367 /// <summary>
368 /// Removes all items from the collection.
369 /// </summary>
370 public void Clear()
371 {
372 foreach (InstallPath item in this)
373 {
374 this.OnRemove(item);
375 }
376
377 this.items.Clear();
378 }
379
380 /// <summary>
381 /// Gets the number of items in the collection.
382 /// </summary>
383 public int Count
384 {
385 get
386 {
387 return this.items.Count;
388 }
389 }
390
391 bool ICollection<InstallPath>.IsReadOnly
392 {
393 get
394 {
395 return false;
396 }
397 }
398
399 /// <summary>
400 /// Gets an enumerator over all items in the collection.
401 /// </summary>
402 /// <returns>An enumerator for the collection.</returns>
403 public IEnumerator<InstallPath> GetEnumerator()
404 {
405 return this.items.GetEnumerator();
406 }
407
408 IEnumerator IEnumerable.GetEnumerator()
409 {
410 return ((IEnumerable<InstallPath>) this).GetEnumerator();
411 }
412 }
413
414 /// <summary>
415 /// Represents a mapping of install paths for all directories, components, or files in
416 /// an installation database.
417 /// </summary>
418 public class InstallPathMap : IDictionary<string, InstallPath>
419 {
420 /// <summary>
421 /// Builds a mapping from File keys to installation paths.
422 /// </summary>
423 /// <param name="db">Installation database.</param>
424 /// <param name="componentPathMap">Component mapping returned by <see cref="BuildComponentPathMap"/>.</param>
425 /// <param name="useShortNames">true to use short file names; false to use long names</param>
426 /// <returns>An InstallPathMap with the described mapping.</returns>
427 public static InstallPathMap BuildFilePathMap(Database db, InstallPathMap componentPathMap,
428 bool useShortNames)
429 {
430 if(db == null)
431 {
432 throw new ArgumentNullException("db");
433 }
434
435 if(componentPathMap == null)
436 {
437 componentPathMap = BuildComponentPathMap(db, BuildDirectoryPathMap(db, useShortNames));
438 }
439
440 InstallPathMap filePathMap = new InstallPathMap();
441
442 using (View fileView = db.OpenView("SELECT `File`, `Component_`, `FileName` FROM `File`"))
443 {
444 fileView.Execute();
445
446 foreach (Record fileRec in fileView)
447 {
448 using (fileRec)
449 {
450 string file = (string) fileRec[1];
451 string comp = (string) fileRec[2];
452 string fileName = (string) fileRec[3];
453
454 InstallPath compPath = (InstallPath) componentPathMap[comp];
455 if(compPath != null)
456 {
457 InstallPath filePath = new InstallPath(fileName, useShortNames);
458 compPath.ChildPaths.Add(filePath);
459 filePathMap[file] = filePath;
460 }
461 }
462 }
463 }
464
465 return filePathMap;
466 }
467
468 /// <summary>
469 /// Builds a mapping from Component keys to installation paths.
470 /// </summary>
471 /// <param name="db">Installation database.</param>
472 /// <param name="directoryPathMap">Directory mapping returned by
473 /// <see cref="BuildDirectoryPathMap(Database,bool)"/>.</param>
474 /// <returns>An InstallPathMap with the described mapping.</returns>
475 public static InstallPathMap BuildComponentPathMap(Database db, InstallPathMap directoryPathMap)
476 {
477 if(db == null)
478 {
479 throw new ArgumentNullException("db");
480 }
481
482 InstallPathMap compPathMap = new InstallPathMap();
483
484 using (View compView = db.OpenView("SELECT `Component`, `Directory_` FROM `Component`"))
485 {
486 compView.Execute();
487
488 foreach (Record compRec in compView)
489 {
490 using (compRec)
491 {
492 string comp = (string) compRec[1];
493 InstallPath dirPath = (InstallPath) directoryPathMap[(string) compRec[2]];
494
495 if (dirPath != null)
496 {
497 compPathMap[comp] = dirPath;
498 }
499 }
500 }
501 }
502
503 return compPathMap;
504 }
505
506 /// <summary>
507 /// Builds a mapping from Directory keys to installation paths.
508 /// </summary>
509 /// <param name="db">Installation database.</param>
510 /// <param name="useShortNames">true to use short directory names; false to use long names</param>
511 /// <returns>An InstallPathMap with the described mapping.</returns>
512 public static InstallPathMap BuildDirectoryPathMap(Database db, bool useShortNames)
513 {
514 return BuildDirectoryPathMap(db, useShortNames, null, null);
515 }
516
517 /// <summary>
518 /// Builds a mapping of Directory keys to directory paths, specifying root directories
519 /// for the source and target paths.
520 /// </summary>
521 /// <param name="db">Database containing the Directory table.</param>
522 /// <param name="useShortNames">true to use short directory names; false to use long names</param>
523 /// <param name="sourceRootDir">The root directory path of all source paths, or null to leave them relative.</param>
524 /// <param name="targetRootDir">The root directory path of all source paths, or null to leave them relative.</param>
525 /// <returns>An InstallPathMap with the described mapping.</returns>
526 public static InstallPathMap BuildDirectoryPathMap(Database db, bool useShortNames,
527 string sourceRootDir, string targetRootDir)
528 {
529 if(db == null)
530 {
531 throw new ArgumentNullException("db");
532 }
533
534 if(sourceRootDir == null) sourceRootDir = "";
535 if(targetRootDir == null) targetRootDir = "";
536
537 InstallPathMap dirMap = new InstallPathMap();
538 IDictionary dirTreeMap = new Hashtable();
539
540 using (View dirView = db.OpenView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`"))
541 {
542 dirView.Execute();
543
544 foreach (Record dirRec in dirView) using (dirRec)
545 {
546 string key = (string) dirRec[1];
547 string parentKey = (string) dirRec[2];
548 InstallPath dir = new InstallPath((string) dirRec[3], useShortNames);
549
550 dirMap[key] = dir;
551
552 InstallPathMap siblingDirs = (InstallPathMap) dirTreeMap[parentKey];
553 if (siblingDirs == null)
554 {
555 siblingDirs = new InstallPathMap();
556 dirTreeMap[parentKey] = siblingDirs;
557 }
558 siblingDirs.Add(key, dir);
559 }
560 }
561
562 foreach (KeyValuePair<string, InstallPath> entry in (InstallPathMap) dirTreeMap[""])
563 {
564 string key = (string) entry.Key;
565 InstallPath dir = (InstallPath) entry.Value;
566 LinkSubdirectories(key, dir, dirTreeMap);
567 }
568
569 InstallPath targetDirPath = (InstallPath) dirMap["TARGETDIR"];
570 if(targetDirPath != null)
571 {
572 targetDirPath.SourcePath = sourceRootDir;
573 targetDirPath.TargetPath = targetRootDir;
574 }
575
576 return dirMap;
577 }
578
579 private static void LinkSubdirectories(string key, InstallPath dir, IDictionary dirTreeMap)
580 {
581 InstallPathMap subDirs = (InstallPathMap) dirTreeMap[key];
582 if(subDirs != null)
583 {
584 foreach (KeyValuePair<string, InstallPath> entry in subDirs)
585 {
586 string subKey = (string) entry.Key;
587 InstallPath subDir = (InstallPath) entry.Value;
588 dir.ChildPaths.Add(subDir);
589 LinkSubdirectories(subKey, subDir, dirTreeMap);
590 }
591 }
592 }
593
594 private Dictionary<string, InstallPath> items;
595
596 /// <summary>
597 /// Creates a new empty InstallPathMap.
598 /// </summary>
599 public InstallPathMap()
600 {
601 this.items = new Dictionary<string,InstallPath>(StringComparer.Ordinal);
602 }
603
604 /// <summary>
605 /// Gets a mapping from keys to source paths.
606 /// </summary>
607 public IDictionary<string, string> SourcePaths
608 {
609 get
610 {
611 return new SourcePathMap(this);
612 }
613 }
614
615 /// <summary>
616 /// Gets a mapping from keys to target paths.
617 /// </summary>
618 public IDictionary<string, string> TargetPaths
619 {
620 get
621 {
622 return new TargetPathMap(this);
623 }
624 }
625
626 /// <summary>
627 /// Gets or sets an install path for a direcotry, component, or file key.
628 /// </summary>
629 /// <param name="key">Depending on the type of InstallPathMap, this is the primary key from the
630 /// either the Directory, Component, or File table.</param>
631 /// <remarks>
632 /// Changing an install path does not modify the Database used to generate this InstallPathMap.
633 /// </remarks>
634 public InstallPath this[string key]
635 {
636 get
637 {
638 InstallPath value = null;
639 this.items.TryGetValue(key, out value);
640 return value;
641 }
642 set
643 {
644 this.items[key] = value;
645 }
646 }
647
648 /// <summary>
649 /// Gets the collection of keys in the InstallPathMap. Depending on the type of InstallPathMap,
650 /// they are all directory, component, or file key strings.
651 /// </summary>
652 public ICollection<string> Keys
653 {
654 get
655 {
656 return this.items.Keys;
657 }
658 }
659
660 /// <summary>
661 /// Gets the collection of InstallPath values in the InstallPathMap.
662 /// </summary>
663 public ICollection<InstallPath> Values
664 {
665 get
666 {
667 return this.items.Values;
668 }
669 }
670
671 /// <summary>
672 /// Sets an install path for a direcotry, component, or file key.
673 /// </summary>
674 /// <param name="key">Depending on the type of InstallPathMap, this is the primary key from the
675 /// either the Directory, Component, or File table.</param>
676 /// <param name="installPath">The install path of the key item.</param>
677 /// <remarks>
678 /// Changing an install path does not modify the Database used to generate this InstallPathMap.
679 /// </remarks>
680 public void Add(string key, InstallPath installPath)
681 {
682 this.items.Add(key, installPath);
683 }
684
685 /// <summary>
686 /// Removes an install path from the map.
687 /// </summary>
688 /// <param name="key">Depending on the type of InstallPathMap, this is the primary key from the
689 /// either the Directory, Component, or File table.</param>
690 /// <returns>true if the item was removed, false if it did not exist</returns>
691 /// <remarks>
692 /// Changing an install path does not modify the Database used to generate this InstallPathMap.
693 /// </remarks>
694 public bool Remove(string key)
695 {
696 return this.items.Remove(key);
697 }
698
699 /// <summary>
700 /// Tests whether a direcotry, component, or file key exists in the map.
701 /// </summary>
702 /// <param name="key">Depending on the type of InstallPathMap, this is the primary key from the
703 /// either the Directory, Component, or File table.</param>
704 /// <returns>true if the key is found; false otherwise</returns>
705 public bool ContainsKey(string key)
706 {
707 return this.items.ContainsKey(key);
708 }
709
710 /*
711 public override string ToString()
712 {
713 System.Text.StringBuilder buf = new System.Text.StringBuilder();
714 foreach(KeyValuePair<string, InstallPath> entry in this)
715 {
716 buf.AppendFormat("{0}={1}", entry.Key, entry.Value);
717 buf.Append("\n");
718 }
719 return buf.ToString();
720 }
721 */
722
723 /// <summary>
724 /// Attempts to get a value from the dictionary.
725 /// </summary>
726 /// <param name="key">The key to lookup.</param>
727 /// <param name="value">Receives the value, or null if they key was not found.</param>
728 /// <returns>True if the value was found, else false.</returns>
729 public bool TryGetValue(string key, out InstallPath value)
730 {
731 return this.items.TryGetValue(key, out value);
732 }
733
734 void ICollection<KeyValuePair<string, InstallPath>>.Add(KeyValuePair<string, InstallPath> item)
735 {
736 ((ICollection<KeyValuePair<string, InstallPath>>) this.items).Add(item);
737 }
738
739 /// <summary>
740 /// Removes all entries from the dictionary.
741 /// </summary>
742 public void Clear()
743 {
744 this.items.Clear();
745 }
746
747 bool ICollection<KeyValuePair<string, InstallPath>>.Contains(KeyValuePair<string, InstallPath> item)
748 {
749 return ((ICollection<KeyValuePair<string, InstallPath>>) this.items).Contains(item);
750 }
751
752 void ICollection<KeyValuePair<string, InstallPath>>.CopyTo(KeyValuePair<string, InstallPath>[] array, int arrayIndex)
753 {
754 ((ICollection<KeyValuePair<string, InstallPath>>) this.items).CopyTo(array, arrayIndex);
755 }
756
757 /// <summary>
758 /// Gets the number of entries in the dictionary.
759 /// </summary>
760 public int Count
761 {
762 get
763 {
764 return this.items.Count;
765 }
766 }
767
768 bool ICollection<KeyValuePair<string, InstallPath>>.IsReadOnly
769 {
770 get
771 {
772 return false;
773 }
774 }
775
776 bool ICollection<KeyValuePair<string, InstallPath>>.Remove(KeyValuePair<string, InstallPath> item)
777 {
778 return ((ICollection<KeyValuePair<string, InstallPath>>) this.items).Remove(item);
779 }
780
781 /// <summary>
782 /// Gets an enumerator over all entries in the dictionary.
783 /// </summary>
784 /// <returns>An enumerator for the dictionary.</returns>
785 public IEnumerator<KeyValuePair<string, InstallPath>> GetEnumerator()
786 {
787 return this.items.GetEnumerator();
788 }
789
790 IEnumerator IEnumerable.GetEnumerator()
791 {
792 return this.items.GetEnumerator();
793 }
794 }
795
796 internal class SourcePathMap : IDictionary<string, string>
797 {
798 private const string RO_MSG =
799 "The SourcePathMap collection is read-only. " +
800 "Modify the InstallPathMap instead.";
801
802 private InstallPathMap map;
803
804 internal SourcePathMap(InstallPathMap map)
805 {
806 this.map = map;
807 }
808
809 public void Add(string key, string value)
810 {
811 throw new InvalidOperationException(RO_MSG);
812 }
813
814 public bool ContainsKey(string key)
815 {
816 return this.map.ContainsKey(key);
817 }
818
819 public ICollection<string> Keys
820 {
821 get
822 {
823 return this.map.Keys;
824 }
825 }
826
827 public bool Remove(string key)
828 {
829 throw new InvalidOperationException(RO_MSG);
830 }
831
832 public bool TryGetValue(string key, out string value)
833 {
834 InstallPath installPath;
835 if (this.map.TryGetValue(key, out installPath))
836 {
837 value = installPath.SourcePath;
838 return true;
839 }
840 else
841 {
842 value = null;
843 return false;
844 }
845 }
846
847 public ICollection<string> Values
848 {
849 get
850 {
851 List<string> values = new List<string>(this.Count);
852 foreach (KeyValuePair<string, InstallPath> entry in this.map)
853 {
854 values.Add(entry.Value.SourcePath);
855 }
856 return values;
857 }
858 }
859
860 public string this[string key]
861 {
862 get
863 {
864 string value = null;
865 this.TryGetValue(key, out value);
866 return value;
867 }
868 set
869 {
870 throw new InvalidOperationException(RO_MSG);
871 }
872 }
873
874 public void Add(KeyValuePair<string, string> item)
875 {
876 throw new InvalidOperationException(RO_MSG);
877 }
878
879 public void Clear()
880 {
881 throw new InvalidOperationException(RO_MSG);
882 }
883
884 public bool Contains(KeyValuePair<string, string> item)
885 {
886 string value = this[item.Key];
887 return value == item.Value;
888 }
889
890 public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
891 {
892 foreach (KeyValuePair<string, string> entry in this)
893 {
894 array[arrayIndex] = entry;
895 arrayIndex++;
896 }
897 }
898
899 public int Count
900 {
901 get
902 {
903 return this.map.Count;
904 }
905 }
906
907 public bool IsReadOnly
908 {
909 get
910 {
911 return true;
912 }
913 }
914
915 public bool Remove(KeyValuePair<string, string> item)
916 {
917 throw new InvalidOperationException(RO_MSG);
918 }
919
920 public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
921 {
922 foreach (KeyValuePair<string, InstallPath> entry in this.map)
923 {
924 yield return new KeyValuePair<string, string>(
925 entry.Key, entry.Value.SourcePath);
926 }
927 }
928
929 IEnumerator IEnumerable.GetEnumerator()
930 {
931 return this.GetEnumerator();
932 }
933 }
934
935 internal class TargetPathMap : IDictionary<string, string>
936 {
937 private const string RO_MSG =
938 "The TargetPathMap collection is read-only. " +
939 "Modify the InstallPathMap instead.";
940
941 private InstallPathMap map;
942
943 internal TargetPathMap(InstallPathMap map)
944 {
945 this.map = map;
946 }
947
948 public void Add(string key, string value)
949 {
950 throw new InvalidOperationException(RO_MSG);
951 }
952
953 public bool ContainsKey(string key)
954 {
955 return this.map.ContainsKey(key);
956 }
957
958 public ICollection<string> Keys
959 {
960 get
961 {
962 return this.map.Keys;
963 }
964 }
965
966 public bool Remove(string key)
967 {
968 throw new InvalidOperationException(RO_MSG);
969 }
970
971 public bool TryGetValue(string key, out string value)
972 {
973 InstallPath installPath;
974 if (this.map.TryGetValue(key, out installPath))
975 {
976 value = installPath.TargetPath;
977 return true;
978 }
979 else
980 {
981 value = null;
982 return false;
983 }
984 }
985
986 public ICollection<string> Values
987 {
988 get
989 {
990 List<string> values = new List<string>(this.Count);
991 foreach (KeyValuePair<string, InstallPath> entry in this.map)
992 {
993 values.Add(entry.Value.TargetPath);
994 }
995 return values;
996 }
997 }
998
999 public string this[string key]
1000 {
1001 get
1002 {
1003 string value = null;
1004 this.TryGetValue(key, out value);
1005 return value;
1006 }
1007 set
1008 {
1009 throw new InvalidOperationException(RO_MSG);
1010 }
1011 }
1012
1013 public void Add(KeyValuePair<string, string> item)
1014 {
1015 throw new InvalidOperationException(RO_MSG);
1016 }
1017
1018 public void Clear()
1019 {
1020 throw new InvalidOperationException(RO_MSG);
1021 }
1022
1023 public bool Contains(KeyValuePair<string, string> item)
1024 {
1025 string value = this[item.Key];
1026 return value == item.Value;
1027 }
1028
1029 public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
1030 {
1031 foreach (KeyValuePair<string, string> entry in this)
1032 {
1033 array[arrayIndex] = entry;
1034 arrayIndex++;
1035 }
1036 }
1037
1038 public int Count
1039 {
1040 get
1041 {
1042 return this.map.Count;
1043 }
1044 }
1045
1046 public bool IsReadOnly
1047 {
1048 get
1049 {
1050 return true;
1051 }
1052 }
1053
1054 public bool Remove(KeyValuePair<string, string> item)
1055 {
1056 throw new InvalidOperationException(RO_MSG);
1057 }
1058
1059 public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
1060 {
1061 foreach (KeyValuePair<string, InstallPath> entry in this.map)
1062 {
1063 yield return new KeyValuePair<string, string>(
1064 entry.Key, entry.Value.TargetPath);
1065 }
1066 }
1067
1068 IEnumerator IEnumerable.GetEnumerator()
1069 {
1070 return this.GetEnumerator();
1071 }
1072 }
1073}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller.Package/PatchPackage.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller.Package/PatchPackage.cs
new file mode 100644
index 00000000..54bd2b93
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller.Package/PatchPackage.cs
@@ -0,0 +1,259 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller.Package
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Collections;
9 using System.Globalization;
10 using System.Runtime.InteropServices;
11
12 /// <summary>
13 /// Provides access to convenient properties and operations on a patch package (.MSP).
14 /// </summary>
15 public class PatchPackage : Database
16 {
17 /// <summary>
18 /// Creates a new patch package object; opening the patch database in read-only mode.
19 /// </summary>
20 /// <param name="packagePath">Path to the patch package (.MSP)</param>
21 /// <remarks>The PatchPackage object only opens the patch database in read-only mode, because
22 /// transforms (sub-storages) cannot be read if the database is open in read-write mode.</remarks>
23 public PatchPackage(string packagePath)
24 : base(packagePath, (DatabaseOpenMode) ((int) DatabaseOpenMode.ReadOnly | 32))
25 // TODO: figure out what to do about DatabaseOpenMode.Patch
26 {
27 }
28
29 /// <summary>
30 /// Handle this event to receive status messages when operations are performed on the patch package.
31 /// </summary>
32 /// <example>
33 /// <c>patchPackage.Message += new InstallPackageMessageHandler(Console.WriteLine);</c>
34 /// </example>
35 public event InstallPackageMessageHandler Message;
36
37 /// <summary>
38 /// Sends a message to the <see cref="Message"/> event-handler.
39 /// </summary>
40 /// <param name="format">Message string, containing 0 or more format items</param>
41 /// <param name="args">Items to be formatted</param>
42 protected void LogMessage(string format, params object[] args)
43 {
44 if(this.Message != null)
45 {
46 this.Message(format, args);
47 }
48 }
49
50 /// <summary>
51 /// Gets the patch code (GUID) of the patch package.
52 /// </summary>
53 /// <remarks>
54 /// The patch code is stored in the RevisionNumber field of the patch summary information.
55 /// </remarks>
56 public string PatchCode
57 {
58 get
59 {
60 string guids = this.SummaryInfo.RevisionNumber;
61 return guids.Substring(0, guids.IndexOf('}') + 1);
62 }
63 }
64
65 /// <summary>
66 /// Gets the list of patch codes that are replaced by this patch package.
67 /// </summary>
68 /// <returns>Array of replaced patch codes (GUIDs)</returns>
69 /// <remarks>
70 /// The list of replaced patch codes is stored in the RevisionNumber field of the patch summary information.
71 /// </remarks>
72 public string[] GetReplacedPatchCodes()
73 {
74 ArrayList patchCodeList = new ArrayList();
75 string guids = this.SummaryInfo.RevisionNumber;
76 int thisGuid = guids.IndexOf('}') + 1;
77 int nextGuid = guids.IndexOf('}', thisGuid) + 1;
78 while(nextGuid > 0)
79 {
80 patchCodeList.Add(guids.Substring(thisGuid, (nextGuid - thisGuid)));
81 thisGuid = nextGuid;
82 nextGuid = guids.IndexOf('}', thisGuid) + 1;
83 }
84 return (string[]) patchCodeList.ToArray(typeof(string));
85 }
86
87 /// <summary>
88 /// Gets the list of product codes of products targeted by this patch package.
89 /// </summary>
90 /// <returns>Array of product codes (GUIDs)</returns>
91 /// <remarks>
92 /// The list of target product codes is stored in the Template field of the patch summary information.
93 /// </remarks>
94 public string[] GetTargetProductCodes()
95 {
96 string productList = this.SummaryInfo.Template;
97 return productList.Split(';');
98 }
99
100 /// <summary>
101 /// Gets the names of the transforms included in the patch package.
102 /// </summary>
103 /// <returns>Array of transform names</returns>
104 /// <remarks>
105 /// The returned list does not include the &quot;patch special transforms&quot; that are prefixed with &quot;#&quot;
106 /// <p>The list of transform names is stored in the LastSavedBy field of the patch summary information.</p>
107 /// </remarks>
108 public string[] GetTransforms()
109 {
110 return this.GetTransforms(false);
111 }
112 /// <summary>
113 /// Gets the names of the transforms included in the patch package.
114 /// </summary>
115 /// <param name="includeSpecialTransforms">Specifies whether to include the
116 /// &quot;patch special transforms&quot; that are prefixed with &quot;#&quot;</param>
117 /// <returns>Array of transform names</returns>
118 /// <remarks>
119 /// The list of transform names is stored in the LastSavedBy field of the patch summary information.
120 /// </remarks>
121 public string[] GetTransforms(bool includeSpecialTransforms)
122 {
123 ArrayList transformArray = new ArrayList();
124 string transformList = this.SummaryInfo.LastSavedBy;
125 foreach(string transform in transformList.Split(';', ':'))
126 {
127 if(transform.Length != 0 && (includeSpecialTransforms || !transform.StartsWith("#", StringComparison.Ordinal)))
128 {
129 transformArray.Add(transform);
130 }
131 }
132 return (string[]) transformArray.ToArray(typeof(string));
133 }
134
135 /// <summary>
136 /// Gets information about the transforms included in the patch package.
137 /// </summary>
138 /// <returns>Array containing information about each transform</returns>
139 /// <remarks>
140 /// The returned info does not include the &quot;patch special transforms&quot; that are prefixed with &quot;#&quot;
141 /// </remarks>
142 public TransformInfo[] GetTransformsInfo()
143 {
144 return this.GetTransformsInfo(false);
145 }
146
147 /// <summary>
148 /// Gets information about the transforms included in the patch package.
149 /// </summary>
150 /// <param name="includeSpecialTransforms">Specifies whether to include the
151 /// &quot;patch special transforms&quot; that are prefixed with &quot;#&quot;</param>
152 /// <returns>Array containing information about each transform</returns>
153 public TransformInfo[] GetTransformsInfo(bool includeSpecialTransforms)
154 {
155 string[] transforms = this.GetTransforms(includeSpecialTransforms);
156 ArrayList transformInfoArray = new ArrayList(transforms.Length);
157 foreach(string transform in transforms)
158 {
159 transformInfoArray.Add(this.GetTransformInfo(transform));
160 }
161 return (TransformInfo[]) transformInfoArray.ToArray(typeof(TransformInfo));
162 }
163
164 /// <summary>
165 /// Gets information about a transforms included in the patch package.
166 /// </summary>
167 /// <param name="transform">Name of the transform to extract; this may optionally be a
168 /// special transform prefixed by &quot;#&quot;</param>
169 /// <returns>Information about the transform</returns>
170 public TransformInfo GetTransformInfo(string transform)
171 {
172 string tempTransformFile = null;
173 try
174 {
175 tempTransformFile = Path.GetTempFileName();
176 this.ExtractTransform(transform, tempTransformFile);
177 using(SummaryInfo transformSummInfo = new SummaryInfo(tempTransformFile, false))
178 {
179 return new TransformInfo(transform, transformSummInfo);
180 }
181 }
182 finally
183 {
184 if(tempTransformFile != null && File.Exists(tempTransformFile))
185 {
186 File.Delete(tempTransformFile);
187 }
188 }
189 }
190
191 /// <summary>
192 /// Analyzes the transforms included in the patch package to find the ones that
193 /// are applicable to an install package.
194 /// </summary>
195 /// <param name="installPackage">The install package to validate the transforms against</param>
196 /// <returns>Array of valid transform names</returns>
197 /// <remarks>
198 /// The returned list does not include the &quot;patch special transforms&quot; that
199 /// are prefixed with &quot;#&quot; If a transform is valid, then its corresponding
200 /// special transform is assumed to be valid as well.
201 /// </remarks>
202 public string[] GetValidTransforms(InstallPackage installPackage)
203 {
204 ArrayList transformArray = new ArrayList();
205 string transformList = this.SummaryInfo.LastSavedBy;
206 foreach(string transform in transformList.Split(';', ':'))
207 {
208 if(transform.Length != 0 && !transform.StartsWith("#", StringComparison.Ordinal))
209 {
210 this.LogMessage("Checking validity of transform {0}", transform);
211 string tempTransformFile = null;
212 try
213 {
214 tempTransformFile = Path.GetTempFileName();
215 this.ExtractTransform(transform, tempTransformFile);
216 if(installPackage.IsTransformValid(tempTransformFile))
217 {
218 this.LogMessage("Found valid transform: {0}", transform);
219 transformArray.Add(transform);
220 }
221 }
222 finally
223 {
224 if(tempTransformFile != null && File.Exists(tempTransformFile))
225 {
226 try { File.Delete(tempTransformFile); }
227 catch(IOException) { }
228 }
229 }
230 }
231 }
232 return (string[]) transformArray.ToArray(typeof(string));
233 }
234
235 /// <summary>
236 /// Extracts a transform (.MST) from a patch package.
237 /// </summary>
238 /// <param name="transform">Name of the transform to extract; this may optionally be a
239 /// special transform prefixed by &quot;#&quot;</param>
240 /// <param name="extractFile">Location where the transform will be extracted</param>
241 public void ExtractTransform(string transform, string extractFile)
242 {
243 using(View stgView = this.OpenView("SELECT `Name`, `Data` FROM `_Storages` WHERE `Name` = '{0}'", transform))
244 {
245 stgView.Execute();
246 Record stgRec = stgView.Fetch();
247 if(stgRec == null)
248 {
249 this.LogMessage("Transform not found: {0}", transform);
250 throw new InstallerException("Transform not found: " + transform);
251 }
252 using(stgRec)
253 {
254 stgRec.GetStream("Data", extractFile);
255 }
256 }
257 }
258 }
259}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller.Package/TransformInfo.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller.Package/TransformInfo.cs
new file mode 100644
index 00000000..0bf3d3f9
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller.Package/TransformInfo.cs
@@ -0,0 +1,154 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller.Package
4{
5 using System;
6 using System.IO;
7 using System.Globalization;
8
9 /// <summary>
10 /// Contains properties of a transform package (.MST).
11 /// </summary>
12 public class TransformInfo
13 {
14 /// <summary>
15 /// Reads transform information from a transform package.
16 /// </summary>
17 /// <param name="mstFile">Path to a transform package (.MST file).</param>
18 public TransformInfo(string mstFile)
19 {
20 this.name = Path.GetFileName(mstFile);
21 using (SummaryInfo transformSummInfo = new SummaryInfo(mstFile, false))
22 {
23 this.DecodeSummaryInfo(transformSummInfo);
24 }
25 }
26
27 /// <summary>
28 /// Reads transform information from the summary information of a transform package.
29 /// </summary>
30 /// <param name="name">Filename of the transform (optional).</param>
31 /// <param name="transformSummaryInfo">Handle to the summary information of a transform package (.MST file).</param>
32 public TransformInfo(string name, SummaryInfo transformSummaryInfo)
33 {
34 this.name = name;
35 this.DecodeSummaryInfo(transformSummaryInfo);
36 }
37
38 private void DecodeSummaryInfo(SummaryInfo transformSummaryInfo)
39 {
40 try
41 {
42 string[] rev = transformSummaryInfo.RevisionNumber.Split(new char[] { ';' }, 3);
43 this.targetProductCode = rev[0].Substring(0, 38);
44 this.targetProductVersion = rev[0].Substring(38);
45 this.upgradeProductCode = rev[1].Substring(0, 38);
46 this.upgradeProductVersion = rev[1].Substring(38);
47 this.upgradeCode = rev[2];
48
49 string[] templ = transformSummaryInfo.Template.Split(new Char[] { ';' }, 2);
50 this.targetPlatform = templ[0];
51 this.targetLanguage = 0;
52 if (templ.Length >= 2 && templ[1].Length > 0)
53 {
54 this.targetLanguage = Int32.Parse(templ[1], CultureInfo.InvariantCulture.NumberFormat);
55 }
56
57 this.validateFlags = (TransformValidations) transformSummaryInfo.CharacterCount;
58 }
59 catch (Exception ex)
60 {
61 throw new InstallerException("Invalid transform summary info", ex);
62 }
63 }
64
65 /// <summary>
66 /// Gets the filename of the transform.
67 /// </summary>
68 public string Name
69 {
70 get { return this.name; }
71 }
72 private string name;
73
74 /// <summary>
75 /// Gets the target product code of the transform.
76 /// </summary>
77 public string TargetProductCode
78 {
79 get { return this.targetProductCode; }
80 }
81 private string targetProductCode;
82
83 /// <summary>
84 /// Gets the target product version of the transform.
85 /// </summary>
86 public string TargetProductVersion
87 {
88 get { return this.targetProductVersion; }
89 }
90 private string targetProductVersion;
91
92 /// <summary>
93 /// Gets the upgrade product code of the transform.
94 /// </summary>
95 public string UpgradeProductCode
96 {
97 get { return this.upgradeProductCode; }
98 }
99 private string upgradeProductCode;
100
101 /// <summary>
102 /// Gets the upgrade product version of the transform.
103 /// </summary>
104 public string UpgradeProductVersion
105 {
106 get { return this.upgradeProductVersion; }
107 }
108 private string upgradeProductVersion;
109
110 /// <summary>
111 /// Gets the upgrade code of the transform.
112 /// </summary>
113 public string UpgradeCode
114 {
115 get { return this.upgradeCode; }
116 }
117 private string upgradeCode;
118
119 /// <summary>
120 /// Gets the target platform of the transform.
121 /// </summary>
122 public string TargetPlatform
123 {
124 get { return this.targetPlatform; }
125 }
126 private string targetPlatform;
127
128 /// <summary>
129 /// Gets the target language of the transform, or 0 if the transform is language-neutral.
130 /// </summary>
131 public int TargetLanguage
132 {
133 get { return this.targetLanguage; }
134 }
135 private int targetLanguage;
136
137 /// <summary>
138 /// Gets the validation flags specified when the transform was generated.
139 /// </summary>
140 public TransformValidations Validations
141 {
142 get { return this.validateFlags; }
143 }
144 private TransformValidations validateFlags;
145
146 /// <summary>
147 /// Returns the name of the transform.
148 /// </summary>
149 public override string ToString()
150 {
151 return (this.Name != null ? this.Name : "MST");
152 }
153 }
154}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller.Package/WixToolset.Dtf.WindowsInstaller.Package.csproj b/src/dtf/WixToolset.Dtf.WindowsInstaller.Package/WixToolset.Dtf.WindowsInstaller.Package.csproj
new file mode 100644
index 00000000..28ded687
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller.Package/WixToolset.Dtf.WindowsInstaller.Package.csproj
@@ -0,0 +1,23 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <RootNamespace>WixToolset.Dtf.WindowsInstaller</RootNamespace>
7 <AssemblyName>WixToolset.Dtf.WindowsInstaller.Package</AssemblyName>
8 <TargetFrameworks>netstandard2.0;net20</TargetFrameworks>
9 <Description>Extended managed libraries for Windows Installer</Description>
10 <CreateDocumentationFile>true</CreateDocumentationFile>
11 </PropertyGroup>
12
13 <ItemGroup>
14 <ProjectReference Include="..\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj" />
15 <ProjectReference Include="..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" />
16 <ProjectReference Include="..\WixToolset.Dtf.Compression.Cab\WixToolset.Dtf.Compression.Cab.csproj" />
17 </ItemGroup>
18
19 <ItemGroup>
20 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
21 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
22 </ItemGroup>
23</Project>
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnCollection.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnCollection.cs
new file mode 100644
index 00000000..9a452da1
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnCollection.cs
@@ -0,0 +1,333 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.IO;
7 using System.Collections;
8 using System.Collections.Generic;
9 using System.Text;
10 using System.Diagnostics.CodeAnalysis;
11
12 /// <summary>
13 /// Collection of column information related to a <see cref="TableInfo"/> or
14 /// <see cref="View"/>.
15 /// </summary>
16 public sealed class ColumnCollection : ICollection<ColumnInfo>
17 {
18 private IList<ColumnInfo> columns;
19 private string formatString;
20
21 /// <summary>
22 /// Creates a new ColumnCollection based on a specified list of columns.
23 /// </summary>
24 /// <param name="columns">columns to be added to the new collection</param>
25 public ColumnCollection(ICollection<ColumnInfo> columns)
26 {
27 if (columns == null)
28 {
29 throw new ArgumentNullException("columns");
30 }
31
32 this.columns = new List<ColumnInfo>(columns);
33 }
34
35 /// <summary>
36 /// Creates a new ColumnCollection that is associated with a database table.
37 /// </summary>
38 /// <param name="view">view that contains the columns</param>
39 internal ColumnCollection(View view)
40 {
41 if (view == null)
42 {
43 throw new ArgumentNullException("view");
44 }
45
46 this.columns = ColumnCollection.GetViewColumns(view);
47 }
48
49 /// <summary>
50 /// Gets the number of columns in the collection.
51 /// </summary>
52 /// <value>number of columns in the collection</value>
53 public int Count
54 {
55 get
56 {
57 return this.columns.Count;
58 }
59 }
60
61 /// <summary>
62 /// Gets a boolean value indicating whether the collection is read-only.
63 /// A ColumnCollection is read-only if it is associated with a <see cref="View"/>
64 /// or a read-only <see cref="Database"/>.
65 /// </summary>
66 /// <value>read-only status of the collection</value>
67 public bool IsReadOnly
68 {
69 get
70 {
71 return true;
72 }
73 }
74
75 /// <summary>
76 /// Gets information about a specific column in the collection.
77 /// </summary>
78 /// <param name="columnIndex">1-based index into the column collection</param>
79 /// <exception cref="ArgumentOutOfRangeException"><paramref name="columnIndex"/> is less
80 /// than 1 or greater than the number of columns in the collection</exception>
81 public ColumnInfo this[int columnIndex]
82 {
83 get
84 {
85 if (columnIndex >= 0 && columnIndex < this.columns.Count)
86 {
87 return this.columns[columnIndex];
88 }
89 else
90 {
91 throw new ArgumentOutOfRangeException("columnIndex");
92 }
93 }
94 }
95
96 /// <summary>
97 /// Gets information about a specific column in the collection.
98 /// </summary>
99 /// <param name="columnName">case-sensitive name of a column collection</param>
100 /// <exception cref="ArgumentOutOfRangeException"><paramref name="columnName"/> does
101 /// not exist in the collection</exception>
102 public ColumnInfo this[string columnName]
103 {
104 get
105 {
106 if (String.IsNullOrEmpty(columnName))
107 {
108 throw new ArgumentNullException("columnName");
109 }
110
111 foreach (ColumnInfo colInfo in this.columns)
112 {
113 if (colInfo.Name == columnName)
114 {
115 return colInfo;
116 }
117 }
118
119 throw new ArgumentOutOfRangeException("columnName");
120 }
121 }
122
123 /// <summary>
124 /// Not supported because the collection is read-only.
125 /// </summary>
126 /// <param name="item">information about the column being added</param>
127 /// <exception cref="InvalidOperationException">the collection is read-only</exception>
128 public void Add(ColumnInfo item)
129 {
130 throw new InvalidOperationException();
131 }
132
133 /// <summary>
134 /// Not supported because the collection is read-only.
135 /// </summary>
136 /// <exception cref="InvalidOperationException">the collection is read-only</exception>
137 public void Clear()
138 {
139 throw new InvalidOperationException();
140 }
141
142 /// <summary>
143 /// Checks if a column with a given name exists in the collection.
144 /// </summary>
145 /// <param name="columnName">case-sensitive name of the column to look for</param>
146 /// <returns>true if the column exists in the collection, false otherwise</returns>
147 public bool Contains(string columnName)
148 {
149 return this.IndexOf(columnName) >= 0;
150 }
151
152 /// <summary>
153 /// Checks if a column with a given name exists in the collection.
154 /// </summary>
155 /// <param name="column">column to look for, with case-sensitive name</param>
156 /// <returns>true if the column exists in the collection, false otherwise</returns>
157 bool ICollection<ColumnInfo>.Contains(ColumnInfo column)
158 {
159 return this.Contains(column.Name);
160 }
161
162 /// <summary>
163 /// Gets the index of a column within the collection.
164 /// </summary>
165 /// <param name="columnName">case-sensitive name of the column to look for</param>
166 /// <returns>0-based index of the column, or -1 if not found</returns>
167 public int IndexOf(string columnName)
168 {
169 if (String.IsNullOrEmpty(columnName))
170 {
171 throw new ArgumentNullException("columnName");
172 }
173
174 for (int index = 0; index < this.columns.Count; index++)
175 {
176 if (this.columns[index].Name == columnName)
177 {
178 return index;
179 }
180 }
181 return -1;
182 }
183
184 /// <summary>
185 /// Copies the columns from this collection into an array.
186 /// </summary>
187 /// <param name="array">destination array to be filed</param>
188 /// <param name="arrayIndex">offset into the destination array where copying begins</param>
189 public void CopyTo(ColumnInfo[] array, int arrayIndex)
190 {
191 if (array == null)
192 {
193 throw new ArgumentNullException("array");
194 }
195
196 this.columns.CopyTo(array, arrayIndex);
197 }
198
199 /// <summary>
200 /// Not supported because the collection is read-only.
201 /// </summary>
202 /// <param name="column">column to remove</param>
203 /// <returns>true if the column was removed, false if it was not found</returns>
204 /// <exception cref="InvalidOperationException">the collection is read-only</exception>
205 [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "column")]
206 [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
207 bool ICollection<ColumnInfo>.Remove(ColumnInfo column)
208 {
209 throw new InvalidOperationException();
210 }
211
212 /// <summary>
213 /// Gets an enumerator over the columns in the collection.
214 /// </summary>
215 /// <returns>An enumerator of ColumnInfo objects.</returns>
216 public IEnumerator<ColumnInfo> GetEnumerator()
217 {
218 return this.columns.GetEnumerator();
219 }
220
221 /// <summary>
222 /// Gets a string suitable for printing all the values of a record containing these columns.
223 /// </summary>
224 public string FormatString
225 {
226 get
227 {
228 if (this.formatString == null)
229 {
230 this.formatString = CreateFormatString(this.columns);
231 }
232 return this.formatString;
233 }
234 }
235
236 private static string CreateFormatString(IList<ColumnInfo> columns)
237 {
238 StringBuilder sb = new StringBuilder();
239 for (int i = 0; i < columns.Count; i++)
240 {
241 if (columns[i].Type == typeof(Stream))
242 {
243 sb.AppendFormat("{0} = [Binary Data]", columns[i].Name);
244 }
245 else
246 {
247 sb.AppendFormat("{0} = [{1}]", columns[i].Name, i + 1);
248 }
249
250 if (i < columns.Count - 1)
251 {
252 sb.Append(", ");
253 }
254 }
255 return sb.ToString();
256 }
257
258 /// <summary>
259 /// Gets an enumerator over the columns in the collection.
260 /// </summary>
261 /// <returns>An enumerator of ColumnInfo objects.</returns>
262 IEnumerator IEnumerable.GetEnumerator()
263 {
264 return this.GetEnumerator();
265 }
266
267 /// <summary>
268 /// Creates ColumnInfo objects for the associated view.
269 /// </summary>
270 /// <returns>dynamically-generated list of columns</returns>
271 private static IList<ColumnInfo> GetViewColumns(View view)
272 {
273 IList<string> columnNames = ColumnCollection.GetViewColumns(view, false);
274 IList<string> columnTypes = ColumnCollection.GetViewColumns(view, true);
275
276 int count = columnNames.Count;
277 if (columnTypes[count - 1] == "O0")
278 {
279 // Weird.. the "_Tables" table returns a second column with type "O0" -- ignore it.
280 count--;
281 }
282
283 IList<ColumnInfo> columnsList = new List<ColumnInfo>(count);
284 for (int i = 0; i < count; i++)
285 {
286 columnsList.Add(new ColumnInfo(columnNames[i], columnTypes[i]));
287 }
288
289 return columnsList;
290 }
291
292 /// <summary>
293 /// Gets a list of column names or column-definition-strings for the
294 /// associated view.
295 /// </summary>
296 /// <param name="view">the view to that defines the columns</param>
297 /// <param name="types">true to return types (column definition strings),
298 /// false to return names</param>
299 /// <returns>list of column names or types</returns>
300 private static IList<string> GetViewColumns(View view, bool types)
301 {
302 int recordHandle;
303 int typesFlag = types ? 1 : 0;
304 uint ret = RemotableNativeMethods.MsiViewGetColumnInfo(
305 (int) view.Handle, (uint) typesFlag, out recordHandle);
306 if (ret != 0)
307 {
308 throw InstallerException.ExceptionFromReturnCode(ret);
309 }
310
311 using (Record rec = new Record((IntPtr) recordHandle, true, null))
312 {
313 int count = rec.FieldCount;
314 IList<string> columnsList = new List<string>(count);
315
316 // Since we must be getting all strings of limited length,
317 // this code is faster than calling rec.GetString(field).
318 for (int field = 1; field <= count; field++)
319 {
320 uint bufSize = 256;
321 StringBuilder buf = new StringBuilder((int) bufSize);
322 ret = RemotableNativeMethods.MsiRecordGetString((int) rec.Handle, (uint) field, buf, ref bufSize);
323 if (ret != 0)
324 {
325 throw InstallerException.ExceptionFromReturnCode(ret);
326 }
327 columnsList.Add(buf.ToString());
328 }
329 return columnsList;
330 }
331 }
332 }
333}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnEnums.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnEnums.cs
new file mode 100644
index 00000000..ad0a945b
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnEnums.cs
@@ -0,0 +1,689 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Diagnostics.CodeAnalysis;
7
8 // Enumerations are in alphabetical order.
9
10 /// <summary>
11 /// Available values for the Attributes column of the Component table.
12 /// </summary>
13 [Flags]
14 public enum ComponentAttributes : int
15 {
16 /// <summary>
17 /// Local only - Component cannot be run from source.
18 /// </summary>
19 /// <remarks><p>
20 /// Set this value for all components belonging to a feature to prevent the feature from being run-from-network or
21 /// run-from-source. Note that if a feature has no components, the feature always shows run-from-source and
22 /// run-from-my-computer as valid options.
23 /// </p></remarks>
24 None = 0x0000,
25
26 /// <summary>
27 /// Component can only be run from source.
28 /// </summary>
29 /// <remarks><p>
30 /// Set this bit for all components belonging to a feature to prevent the feature from being run-from-my-computer.
31 /// Note that if a feature has no components, the feature always shows run-from-source and run-from-my-computer
32 /// as valid options.
33 /// </p></remarks>
34 SourceOnly = 0x0001,
35
36 /// <summary>
37 /// Component can run locally or from source.
38 /// </summary>
39 Optional = 0x0002,
40
41 /// <summary>
42 /// If this bit is set, the value in the KeyPath column is used as a key into the Registry table.
43 /// </summary>
44 /// <remarks><p>
45 /// If the Value field of the corresponding record in the Registry table is null, the Name field in that record
46 /// must not contain "+", "-", or "*". For more information, see the description of the Name field in Registry
47 /// table.
48 /// <p>Setting this bit is recommended for registry entries written to the HKCU hive. This ensures the installer
49 /// writes the necessary HKCU registry entries when there are multiple users on the same machine.</p>
50 /// </p></remarks>
51 RegistryKeyPath = 0x0004,
52
53 /// <summary>
54 /// If this bit is set, the installer increments the reference count in the shared DLL registry of the component's
55 /// key file. If this bit is not set, the installer increments the reference count only if the reference count
56 /// already exists.
57 /// </summary>
58 SharedDllRefCount = 0x0008,
59
60 /// <summary>
61 /// If this bit is set, the installer does not remove the component during an uninstall. The installer registers
62 /// an extra system client for the component in the Windows Installer registry settings.
63 /// </summary>
64 Permanent = 0x0010,
65
66 /// <summary>
67 /// If this bit is set, the value in the KeyPath column is a key into the ODBCDataSource table.
68 /// </summary>
69 OdbcDataSource = 0x0020,
70
71 /// <summary>
72 /// If this bit is set, the installer reevaluates the value of the statement in the Condition column upon a reinstall.
73 /// If the value was previously False and has changed to true, the installer installs the component. If the value
74 /// was previously true and has changed to false, the installer removes the component even if the component has
75 /// other products as clients.
76 /// </summary>
77 Transitive = 0x0040,
78
79 /// <summary>
80 /// If this bit is set, the installer does not install or reinstall the component if a key path file or a key path
81 /// registry entry for the component already exists. The application does register itself as a client of the component.
82 /// </summary>
83 /// <remarks><p>
84 /// Use this flag only for components that are being registered by the Registry table. Do not use this flag for
85 /// components registered by the AppId, Class, Extension, ProgId, MIME, and Verb tables.
86 /// </p></remarks>
87 NeverOverwrite = 0x0080,
88
89 /// <summary>
90 /// Set this bit to mark this as a 64-bit component. This attribute facilitates the installation of packages that
91 /// include both 32-bit and 64-bit components. If this bit is not set, the component is registered as a 32-bit component.
92 /// </summary>
93 /// <remarks><p>
94 /// If this is a 64-bit component replacing a 32-bit component, set this bit and assign a new GUID in the
95 /// ComponentId column.
96 /// </p></remarks>
97 SixtyFourBit = 0x0100,
98
99 /// <summary>
100 /// Set this bit to disable registry reflection on all existing and new registry keys affected by this component.
101 /// </summary>
102 /// <remarks><p>
103 /// If this bit is set, the Windows Installer calls the RegDisableReflectionKey on each key being accessed by the component.
104 /// This bit is available with Windows Installer version 4.0 and is ignored on 32-bit systems.
105 /// </p></remarks>
106 DisableRegistryReflection = 0x0200,
107
108 /// <summary>
109 /// [MSI 4.5] Set this bit for a component in a patch package to prevent leaving orphan components on the computer.
110 /// </summary>
111 /// <remarks><p>
112 /// If a subsequent patch is installed, marked with the SupersedeEarlier flag in its MsiPatchSequence
113 /// table to supersede the first patch, Windows Installer 4.5 can unregister and uninstall components marked with the
114 /// UninstallOnSupersedence value. If the component is not marked with this bit, installation of a superseding patch can leave
115 /// behind an unused component on the computer.
116 /// </p></remarks>
117 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Supersedence")]
118 UninstallOnSupersedence = 0x0400,
119
120 /// <summary>
121 /// [MSI 4.5] If a component is marked with this attribute value in at least one package installed on the system,
122 /// the installer treats the component as marked in all packages. If a package that shares the marked component
123 /// is uninstalled, Windows Installer 4.5 can continue to share the highest version of the component on the system,
124 /// even if that highest version was installed by the package that is being uninstalled.
125 /// </summary>
126 Shared = 0x0800,
127 }
128
129 /// <summary>
130 /// Defines flags for the Attributes column of the Control table.
131 /// </summary>
132 [Flags]
133 public enum ControlAttributes : int
134 {
135 /// <summary>If this bit is set, the control is visible on the dialog box.</summary>
136 Visible = 0x00000001,
137
138 /// <summary>specifies if the given control is enabled or disabled. Most controls appear gray when disabled.</summary>
139 Enabled = 0x00000002,
140
141 /// <summary>If this bit is set, the control is displayed with a sunken, three dimensional look.</summary>
142 Sunken = 0x00000004,
143
144 /// <summary>The Indirect control attribute specifies whether the value displayed or changed by this control is referenced indirectly.</summary>
145 Indirect = 0x00000008,
146
147 /// <summary>If this bit is set on a control, the associated property specified in the Property column of the Control table is an integer.</summary>
148 Integer = 0x00000010,
149
150 /// <summary>If this bit is set the text in the control is displayed in a right-to-left reading order.</summary>
151 RightToLeftReadingOrder = 0x00000020,
152
153 /// <summary>If this style bit is set, text in the control is aligned to the right.</summary>
154 RightAligned = 0x00000040,
155
156 /// <summary>If this bit is set, the scroll bar is located on the left side of the control, otherwise it is on the right.</summary>
157 LeftScroll = 0x00000080,
158
159 /// <summary>This is a combination of the RightToLeftReadingOrder, RightAligned, and LeftScroll attributes.</summary>
160 Bidirectional = RightToLeftReadingOrder | RightAligned | LeftScroll,
161
162 /// <summary>If this bit is set on a text control, the control is displayed transparently with the background showing through the control where there are no characters.</summary>
163 Transparent = 0x00010000,
164
165 /// <summary>If this bit is set on a text control, the occurrence of the character "&amp;" in a text string is displayed as itself.</summary>
166 NoPrefix = 0x00020000,
167
168 /// <summary>If this bit is set the text in the control is displayed on a single line.</summary>
169 NoWrap = 0x00040000,
170
171 /// <summary>If this bit is set for a text control, the control will automatically attempt to format the displayed text as a number representing a count of bytes.</summary>
172 FormatSize = 0x00080000,
173
174 /// <summary>If this bit is set, fonts are created using the user's default UI code page. Otherwise it is created using the database code page.</summary>
175 UsersLanguage = 0x00100000,
176
177 /// <summary>If this bit is set on an Edit control, the installer creates a multiple line edit control with a vertical scroll bar.</summary>
178 Multiline = 0x00010000,
179
180 /// <summary>This attribute creates an edit control for entering passwords. The control displays each character as an asterisk (*) as they are typed into the control.</summary>
181 PasswordInput = 0x00200000,
182
183 /// <summary>If this bit is set on a ProgressBar control, the bar is drawn as a series of small rectangles in Microsoft Windows 95-style. Otherwise it is drawn as a single continuous rectangle.</summary>
184 Progress95 = 0x00010000,
185
186 /// <summary>If this bit is set, the control shows removable volumes.</summary>
187 RemovableVolume = 0x00010000,
188
189 /// <summary>If this bit is set, the control shows fixed internal hard drives.</summary>
190 FixedVolume = 0x00020000,
191
192 /// <summary>If this bit is set, the control shows remote volumes.</summary>
193 RemoteVolume = 0x00040000,
194
195 /// <summary>If this bit is set, the control shows CD-ROM volumes.</summary>
196 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Cdrom")]
197 CdromVolume = 0x00080000,
198
199 /// <summary>If this bit is set, the control shows RAM disk volumes.</summary>
200 RamDiskVolume = 0x00100000,
201
202 /// <summary>If this bit is set, the control shows floppy volumes.</summary>
203 FloppyVolume = 0x00200000,
204
205 /// <summary>Specifies whether or not the rollback backup files are included in the costs displayed by the VolumeCostList control.</summary>
206 ShowRollbackCost = 0x00400000,
207
208 /// <summary>If this bit is set, the items listed in the control are displayed in a specified order. Otherwise, items are displayed in alphabetical order.</summary>
209 Sorted = 0x00010000,
210
211 /// <summary>If this bit is set on a combo box, the edit field is replaced by a static text field. This prevents a user from entering a new value and requires the user to choose only one of the predefined values.</summary>
212 ComboList = 0x00020000,
213
214 //ImageHandle = 0x00010000,
215
216 /// <summary>If this bit is set on a check box or a radio button group, the button is drawn with the appearance of a push button, but its logic stays the same.</summary>
217 PushLike = 0x00020000,
218
219 /// <summary>If this bit is set, the text in the control is replaced by a bitmap image. The Text column in the Control table is a foreign key into the Binary table.</summary>
220 Bitmap = 0x00040000,
221
222 /// <summary>If this bit is set, text is replaced by an icon image and the Text column in the Control table is a foreign key into the Binary table.</summary>
223 Icon = 0x00080000,
224
225 /// <summary>If this bit is set, the picture is cropped or centered in the control without changing its shape or size.</summary>
226 FixedSize = 0x00100000,
227
228 /// <summary>Specifies which size of the icon image to load. If none of the bits are set, the first image is loaded.</summary>
229 IconSize16 = 0x00200000,
230
231 /// <summary>Specifies which size of the icon image to load. If none of the bits are set, the first image is loaded.</summary>
232 IconSize32 = 0x00400000,
233
234 /// <summary>Specifies which size of the icon image to load. If none of the bits are set, the first image is loaded.</summary>
235 IconSize48 = 0x00600000,
236
237 /// <summary>If this bit is set, and the installation is not yet running with elevated privileges, the control is created with a UAC icon.</summary>
238 ElevationShield = 0x00800000,
239
240 /// <summary>If this bit is set, the RadioButtonGroup has text and a border displayed around it.</summary>
241 HasBorder = 0x01000000,
242 }
243
244 /// <summary>
245 /// Defines flags for the Type column of the CustomAction table.
246 /// </summary>
247 [SuppressMessage("Microsoft.Usage", "CA2217:DoNotMarkEnumsWithFlags")]
248 [Flags]
249 public enum CustomActionTypes : int
250 {
251 /// <summary>Unspecified custom action type.</summary>
252 None = 0x0000,
253
254 /// <summary>Target = entry point name</summary>
255 Dll = 0x0001,
256
257 /// <summary>Target = command line args</summary>
258 Exe = 0x0002,
259
260 /// <summary>Target = text string to be formatted and set into property</summary>
261 TextData = 0x0003,
262
263 /// <summary>Target = entry point name, null if none to call</summary>
264 JScript = 0x0005,
265
266 /// <summary>Target = entry point name, null if none to call</summary>
267 VBScript = 0x0006,
268
269 /// <summary>Target = property list for nested engine initialization</summary>
270 Install = 0x0007,
271
272 /// <summary>Source = File.File, file part of installation</summary>
273 SourceFile = 0x0010,
274
275 /// <summary>Source = Directory.Directory, folder containing existing file</summary>
276 Directory = 0x0020,
277
278 /// <summary>Source = Property.Property, full path to executable</summary>
279 Property = 0x0030,
280
281 /// <summary>Ignore action return status, continue running</summary>
282 Continue = 0x0040,
283
284 /// <summary>Run asynchronously</summary>
285 Async = 0x0080,
286
287 /// <summary>Skip if UI sequence already run</summary>
288 FirstSequence = 0x0100,
289
290 /// <summary>Skip if UI sequence already run in same process</summary>
291 OncePerProcess = 0x0200,
292
293 /// <summary>Run on client only if UI already run on client</summary>
294 ClientRepeat = 0x0300,
295
296 /// <summary>Queue for execution within script</summary>
297 InScript = 0x0400,
298
299 /// <summary>In conjunction with InScript: queue in Rollback script</summary>
300 Rollback = 0x0100,
301
302 /// <summary>In conjunction with InScript: run Commit ops from script on success</summary>
303 Commit = 0x0200,
304
305 /// <summary>No impersonation, run in system context</summary>
306 NoImpersonate = 0x0800,
307
308 /// <summary>Impersonate for per-machine installs on TS machines</summary>
309 TSAware = 0x4000,
310
311 /// <summary>Script requires 64bit process</summary>
312 SixtyFourBitScript = 0x1000,
313
314 /// <summary>Don't record the contents of the Target field in the log file</summary>
315 HideTarget = 0x2000,
316
317 /// <summary>The custom action runs only when a patch is being uninstalled</summary>
318 PatchUninstall = 0x8000,
319 }
320
321 /// <summary>
322 /// Defines flags for the Attributes column of the Dialog table.
323 /// </summary>
324 [Flags]
325 public enum DialogAttributes : int
326 {
327 /// <summary>If this bit is set, the dialog is originally created as visible, otherwise it is hidden.</summary>
328 Visible = 0x00000001,
329
330 /// <summary>If this bit is set, the dialog box is modal, other dialogs of the same application cannot be put on top of it, and the dialog keeps the control while it is running.</summary>
331 Modal = 0x00000002,
332
333 /// <summary>If this bit is set, the dialog box can be minimized. This bit is ignored for modal dialog boxes, which cannot be minimized.</summary>
334 Minimize = 0x00000004,
335
336 /// <summary>If this style bit is set, the dialog box will stop all other applications and no other applications can take the focus.</summary>
337 SysModal = 0x00000008,
338
339 /// <summary>If this bit is set, the other dialogs stay alive when this dialog box is created.</summary>
340 KeepModeless = 0x00000010,
341
342 /// <summary>If this bit is set, the dialog box periodically calls the installer. If the property changes, it notifies the controls on the dialog.</summary>
343 TrackDiskSpace = 0x00000020,
344
345 /// <summary>If this bit is set, the pictures on the dialog box are created with the custom palette (one per dialog received from the first control created).</summary>
346 UseCustomPalette = 0x00000040,
347
348 /// <summary>If this style bit is set the text in the dialog box is displayed in right-to-left-reading order.</summary>
349 RightToLeftReadingOrder = 0x00000080,
350
351 /// <summary>If this style bit is set, the text is aligned on the right side of the dialog box.</summary>
352 RightAligned = 0x00000100,
353
354 /// <summary>If this style bit is set, the scroll bar is located on the left side of the dialog box.</summary>
355 LeftScroll = 0x00000200,
356
357 /// <summary>This is a combination of the RightToLeftReadingOrder, RightAligned, and the LeftScroll dialog style bits.</summary>
358 Bidirectional = RightToLeftReadingOrder | RightAligned | LeftScroll,
359
360 /// <summary>If this bit is set, the dialog box is an error dialog.</summary>
361 Error = 0x00010000,
362 }
363
364 /// <summary>
365 /// Available values for the Attributes column of the Feature table.
366 /// </summary>
367 [Flags]
368 public enum FeatureAttributes : int
369 {
370 /// <summary>
371 /// Favor local - Components of this feature that are not marked for installation from source are installed locally.
372 /// </summary>
373 /// <remarks><p>
374 /// A component shared by two or more features, some of which are set to FavorLocal and some to FavorSource,
375 /// is installed locally. Components marked <see cref="ComponentAttributes.SourceOnly"/> in the Component
376 /// table are always run from the source CD/server. The bits FavorLocal and FavorSource work with features not
377 /// listed by the ADVERTISE property.
378 /// </p></remarks>
379 None = 0x0000,
380
381 /// <summary>
382 /// Components of this feature not marked for local installation are installed to run from the source
383 /// CD-ROM or server.
384 /// </summary>
385 /// <remarks><p>
386 /// A component shared by two or more features, some of which are set to FavorLocal and some to FavorSource,
387 /// is installed to run locally. Components marked <see cref="ComponentAttributes.None"/> (local-only) in the
388 /// Component table are always installed locally. The bits FavorLocal and FavorSource work with features
389 /// not listed by the ADVERTISE property.
390 /// </p></remarks>
391 FavorSource = 0x0001,
392
393 /// <summary>
394 /// Set this attribute and the state of the feature is the same as the state of the feature's parent.
395 /// You cannot use this option if the feature is located at the root of a feature tree.
396 /// </summary>
397 /// <remarks><p>
398 /// Omit this attribute and the feature state is determined according to DisallowAdvertise and
399 /// FavorLocal and FavorSource.
400 /// <p>To guarantee that the child feature's state always follows the state of its parent, even when the
401 /// child and parent are initially set to absent in the SelectionTree control, you must include both
402 /// FollowParent and UIDisallowAbsent in the attributes of the child feature.</p>
403 /// <p>Note that if you set FollowParent without setting UIDisallowAbsent, the installer cannot force
404 /// the child feature out of the absent state. In this case, the child feature matches the parent's
405 /// installation state only if the child is set to something other than absent.</p>
406 /// <p>Set FollowParent and UIDisallowAbsent to ensure a child feature follows the state of the parent feature.</p>
407 /// </p></remarks>
408 FollowParent = 0x0002,
409
410 /// <summary>
411 /// Set this attribute and the feature state is Advertise.
412 /// </summary>
413 /// <remarks><p>
414 /// If the feature is listed by the ADDDEFAULT property this bit is ignored and the feature state is determined
415 /// according to FavorLocal and FavorSource.
416 /// <p>Omit this attribute and the feature state is determined according to DisallowAdvertise and FavorLocal
417 /// and FavorSource.</p>
418 /// </p></remarks>
419 FavorAdvertise = 0x0004,
420
421 /// <summary>
422 /// Set this attribute to prevent the feature from being advertised.
423 /// </summary>
424 /// <remarks><p>
425 /// Note that this bit works only with features that are listed by the ADVERTISE property.
426 /// <p>Set this attribute and if the listed feature is not a parent or child, the feature is installed according to
427 /// FavorLocal and FavorSource.</p>
428 /// <p>Set this attribute for the parent of a listed feature and the parent is installed.</p>
429 /// <p>Set this attribute for the child of a listed feature and the state of the child is Absent.</p>
430 /// <p>Omit this attribute and if the listed feature is not a parent or child, the feature state is Advertise.</p>
431 /// <p>Omit this attribute and if the listed feature is a parent or child, the state of both features is Advertise.</p>
432 /// </p></remarks>
433 DisallowAdvertise = 0x0008,
434
435 /// <summary>
436 /// Set this attribute and the user interface does not display an option to change the feature state
437 /// to Absent. Setting this attribute forces the feature to the installation state, whether or not the
438 /// feature is visible in the UI.
439 /// </summary>
440 /// <remarks><p>
441 /// Omit this attribute and the user interface displays an option to change the feature state to Absent.
442 /// <p>Set FollowParent and UIDisallowAbsent to ensure a child feature follows the state of the parent feature.</p>
443 /// <p>Setting this attribute not only affects the UI, but also forces the feature to the install state whether
444 /// the feature is visible in the UI or not.</p>
445 /// </p></remarks>
446 UIDisallowAbsent = 0x0010,
447
448 /// <summary>
449 /// Set this attribute and advertising is disabled for the feature if the operating system shell does not
450 /// support Windows Installer descriptors.
451 /// </summary>
452 NoUnsupportedAdvertise = 0x0020,
453 }
454
455 /// <summary>
456 /// Available values for the Attributes column of the File table.
457 /// </summary>
458 [Flags]
459 public enum FileAttributes : int
460 {
461 /// <summary>No attributes.</summary>
462 None = 0x0000,
463
464 /// <summary>Read-only.</summary>
465 ReadOnly = 0x0001,
466
467 /// <summary>Hidden.</summary>
468 Hidden = 0x0002,
469
470 /// <summary>System.</summary>
471 System = 0x0004,
472
473 /// <summary>The file is vital for the proper operation of the component to which it belongs.</summary>
474 Vital = 0x0200,
475
476 /// <summary>The file contains a valid checksum. A checksum is required to repair a file that has become corrupted.</summary>
477 Checksum = 0x0400,
478
479 /// <summary>This bit must only be added by a patch and if the file is being added by the patch.</summary>
480 PatchAdded = 0x1000,
481
482 /// <summary>
483 /// The file's source type is uncompressed. If set, ignore the WordCount summary information property. If neither
484 /// Noncompressed nor Compressed are set, the compression state of the file is specified by the WordCount summary
485 /// information property. Do not set both Noncompressed and Compressed.
486 /// </summary>
487 NonCompressed = 0x2000,
488
489 /// <summary>
490 /// The file's source type is compressed. If set, ignore the WordCount summary information property. If neither
491 /// Noncompressed or Compressed are set, the compression state of the file is specified by the WordCount summary
492 /// information property. Do not set both Noncompressed and Compressed.
493 /// </summary>
494 Compressed = 0x4000,
495 }
496
497 /// <summary>
498 /// Defines values for the Action column of the IniFile and RemoveIniFile tables.
499 /// </summary>
500 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ini")]
501 public enum IniFileAction : int
502 {
503 /// <summary>Creates or updates a .ini entry.</summary>
504 AddLine = 0,
505
506 /// <summary>Creates a .ini entry only if the entry does not already exist.</summary>
507 CreateLine = 1,
508
509 /// <summary>Deletes .ini entry.</summary>
510 RemoveLine = 2,
511
512 /// <summary>Creates a new entry or appends a new comma-separated value to an existing entry.</summary>
513 AddTag = 3,
514
515 /// <summary>Deletes a tag from a .ini entry.</summary>
516 RemoveTag = 4,
517 }
518
519 /// <summary>
520 /// Defines values for the Type column of the CompLocator, IniLocator, and RegLocator tables.
521 /// </summary>
522 [Flags]
523 [SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue")]
524 public enum LocatorTypes : int
525 {
526 /// <summary>Key path is a directory.</summary>
527 Directory = 0x00000000,
528
529 /// <summary>Key path is a file name.</summary>
530 FileName = 0x00000001,
531
532 /// <summary>Key path is a registry value.</summary>
533 RawValue = 0x00000002,
534
535 /// <summary>Set this bit to have the installer search the 64-bit portion of the registry.</summary>
536 SixtyFourBit = 0x00000010,
537 }
538
539 /// <summary>
540 /// Defines values for the Root column of the Registry, RemoveRegistry, and RegLocator tables.
541 /// </summary>
542 public enum RegistryRoot : int
543 {
544 /// <summary>HKEY_CURRENT_USER for a per-user installation,
545 /// or HKEY_LOCAL_MACHINE for a per-machine installation.</summary>
546 UserOrMachine = -1,
547
548 /// <summary>HKEY_CLASSES_ROOT</summary>
549 ClassesRoot = 0,
550
551 /// <summary>HKEY_CURRENT_USER</summary>
552 CurrentUser = 1,
553
554 /// <summary>HKEY_LOCAL_MACHINE</summary>
555 LocalMachine = 2,
556
557 /// <summary>HKEY_USERS</summary>
558 Users = 3,
559 }
560
561 /// <summary>
562 /// Defines values for the InstallMode column of the RemoveFile table.
563 /// </summary>
564 [Flags]
565 public enum RemoveFileModes : int
566 {
567 /// <summary>Never remove.</summary>
568 None = 0,
569
570 /// <summary>Remove when the associated component is being installed (install state = local or source).</summary>
571 OnInstall = 1,
572
573 /// <summary>Remove when the associated component is being removed (install state = absent).</summary>
574 OnRemove = 2,
575 }
576
577 /// <summary>
578 /// Defines values for the ServiceType, StartType, and ErrorControl columns of the ServiceInstall table.
579 /// </summary>
580 [Flags]
581 public enum ServiceAttributes : int
582 {
583 /// <summary>No flags.</summary>
584 None = 0,
585
586 /// <summary>A Win32 service that runs its own process.</summary>
587 OwnProcess = 0x0010,
588
589 /// <summary>A Win32 service that shares a process.</summary>
590 ShareProcess = 0x0020,
591
592 /// <summary>A Win32 service that interacts with the desktop.
593 /// This value cannot be used alone and must be added to either
594 /// <see cref="OwnProcess"/> or <see cref="ShareProcess"/>.</summary>
595 Interactive = 0x0100,
596
597 /// <summary>Service starts during startup of the system.</summary>
598 AutoStart = 0x0002,
599
600 /// <summary>Service starts when the service control manager calls the StartService function.</summary>
601 DemandStart = 0x0003,
602
603 /// <summary>Specifies a service that can no longer be started.</summary>
604 Disabled = 0x0004,
605
606 /// <summary>Logs the error, displays a message box and continues the startup operation.</summary>
607 ErrorMessage = 0x0001,
608
609 /// <summary>Logs the error if it is possible and the system is restarted with the last configuration
610 /// known to be good. If the last-known-good configuration is being started, the startup operation fails.</summary>
611 ErrorCritical = 0x0003,
612
613 /// <summary>When combined with other error flags, specifies that the overall install should fail if
614 /// the service cannot be installed into the system.</summary>
615 ErrorControlVital = 0x8000,
616 }
617
618 /// <summary>
619 /// Defines values for the Event column of the ServiceControl table.
620 /// </summary>
621 [Flags]
622 public enum ServiceControlEvents : int
623 {
624 /// <summary>No control events.</summary>
625 None = 0x0000,
626
627 /// <summary>During an install, starts the service during the StartServices action.</summary>
628 Start = 0x0001,
629
630 /// <summary>During an install, stops the service during the StopServices action.</summary>
631 Stop = 0x0002,
632
633 /// <summary>During an install, deletes the service during the DeleteServices action.</summary>
634 Delete = 0x0008,
635
636 /// <summary>During an uninstall, starts the service during the StartServices action.</summary>
637 UninstallStart = 0x0010,
638
639 /// <summary>During an uninstall, stops the service during the StopServices action.</summary>
640 UninstallStop = 0x0020,
641
642 /// <summary>During an uninstall, deletes the service during the DeleteServices action.</summary>
643 UninstallDelete = 0x0080,
644 }
645
646 /// <summary>
647 /// Defines values for the StyleBits column of the TextStyle table.
648 /// </summary>
649 [Flags]
650 public enum TextStyles : int
651 {
652 /// <summary>Bold</summary>
653 Bold = 0x0001,
654
655 /// <summary>Italic</summary>
656 Italic = 0x0002,
657
658 /// <summary>Underline</summary>
659 Underline = 0x0004,
660
661 /// <summary>Strike out</summary>
662 Strike = 0x0008,
663 }
664
665 /// <summary>
666 /// Defines values for the Attributes column of the Upgrade table.
667 /// </summary>
668 [Flags]
669 public enum UpgradeAttributes : int
670 {
671 /// <summary>Migrates feature states by enabling the logic in the MigrateFeatureStates action.</summary>
672 MigrateFeatures = 0x0001,
673
674 /// <summary>Detects products and applications but does not remove.</summary>
675 OnlyDetect = 0x0002,
676
677 /// <summary>Continues installation upon failure to remove a product or application.</summary>
678 IgnoreRemoveFailure = 0x0004,
679
680 /// <summary>Detects the range of versions including the value in VersionMin.</summary>
681 VersionMinInclusive = 0x0100,
682
683 /// <summary>Dectects the range of versions including the value in VersionMax.</summary>
684 VersionMaxInclusive = 0x0200,
685
686 /// <summary>Detects all languages, excluding the languages listed in the Language column.</summary>
687 LanguagesExclusive = 0x0400,
688 }
689}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnInfo.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnInfo.cs
new file mode 100644
index 00000000..43363230
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnInfo.cs
@@ -0,0 +1,297 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Globalization;
9 using System.Diagnostics.CodeAnalysis;
10
11 /// <summary>
12 /// Defines a single column of a table in an installer database.
13 /// </summary>
14 /// <remarks>Once created, a ColumnInfo object is immutable.</remarks>
15 public class ColumnInfo
16 {
17 private string name;
18 private Type type;
19 private int size;
20 private bool isRequired;
21 private bool isTemporary;
22 private bool isLocalizable;
23
24 /// <summary>
25 /// Creates a new ColumnInfo object from a column definition.
26 /// </summary>
27 /// <param name="name">name of the column</param>
28 /// <param name="columnDefinition">column definition string</param>
29 /// <seealso cref="ColumnDefinitionString"/>
30 public ColumnInfo(string name, string columnDefinition)
31 : this(name, typeof(String), 0, false, false, false)
32 {
33 if (name == null)
34 {
35 throw new ArgumentNullException("name");
36 }
37
38 if (columnDefinition == null)
39 {
40 throw new ArgumentNullException("columnDefinition");
41 }
42
43 switch (Char.ToLower(columnDefinition[0], CultureInfo.InvariantCulture))
44 {
45 case 'i': this.type = typeof(Int32);
46 break;
47 case 'j': this.type = typeof(Int32); this.isTemporary = true;
48 break;
49 case 'g': this.type = typeof(String); this.isTemporary = true;
50 break;
51 case 'l': this.type = typeof(String); this.isLocalizable = true;
52 break;
53 case 'o': this.type = typeof(Stream); this.isTemporary = true;
54 break;
55 case 's': this.type = typeof(String);
56 break;
57 case 'v': this.type = typeof(Stream);
58 break;
59 default: throw new InstallerException();
60 }
61
62 this.isRequired = Char.IsLower(columnDefinition[0]);
63 this.size = Int32.Parse(
64 columnDefinition.Substring(1),
65 CultureInfo.InvariantCulture.NumberFormat);
66 if (this.type == typeof(Int32) && this.size <= 2)
67 {
68 this.type = typeof(Int16);
69 }
70 }
71
72 /// <summary>
73 /// Creates a new ColumnInfo object from a list of parameters.
74 /// </summary>
75 /// <param name="name">name of the column</param>
76 /// <param name="type">type of the column; must be one of the following:
77 /// Int16, Int32, String, or Stream</param>
78 /// <param name="size">the maximum number of characters for String columns;
79 /// ignored for other column types</param>
80 /// <param name="isRequired">true if the column is required to have a non-null value</param>
81 public ColumnInfo(string name, Type type, int size, bool isRequired)
82 : this(name, type, size, isRequired, false, false)
83 {
84 }
85
86 /// <summary>
87 /// Creates a new ColumnInfo object from a list of parameters.
88 /// </summary>
89 /// <param name="name">name of the column</param>
90 /// <param name="type">type of the column; must be one of the following:
91 /// Int16, Int32, String, or Stream</param>
92 /// <param name="size">the maximum number of characters for String columns;
93 /// ignored for other column types</param>
94 /// <param name="isRequired">true if the column is required to have a non-null value</param>
95 /// <param name="isTemporary">true to if the column is only in-memory and
96 /// not persisted with the database</param>
97 /// <param name="isLocalizable">for String columns, indicates the column
98 /// is localizable; ignored for other column types</param>
99 public ColumnInfo(string name, Type type, int size, bool isRequired, bool isTemporary, bool isLocalizable)
100 {
101 if (name == null)
102 {
103 throw new ArgumentNullException("name");
104 }
105
106 if (type == typeof(Int32))
107 {
108 size = 4;
109 isLocalizable = false;
110 }
111 else if (type == typeof(Int16))
112 {
113 size = 2;
114 isLocalizable = false;
115 }
116 else if (type == typeof(String))
117 {
118 }
119 else if (type == typeof(Stream))
120 {
121 isLocalizable = false;
122 }
123 else
124 {
125 throw new ArgumentOutOfRangeException("type");
126 }
127
128 this.name = name;
129 this.type = type;
130 this.size = size;
131 this.isRequired = isRequired;
132 this.isTemporary = isTemporary;
133 this.isLocalizable = isLocalizable;
134 }
135
136 /// <summary>
137 /// Gets the name of the column.
138 /// </summary>
139 /// <value>name of the column</value>
140 public string Name
141 {
142 get { return this.name; }
143 }
144
145 /// <summary>
146 /// Gets the type of the column as a System.Type. This is one of the following: Int16, Int32, String, or Stream
147 /// </summary>
148 /// <value>type of the column</value>
149 [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")]
150 public Type Type
151 {
152 get { return this.type; }
153 }
154
155 /// <summary>
156 /// Gets the type of the column as an integer that can be cast to a System.Data.DbType. This is one of the following: Int16, Int32, String, or Binary
157 /// </summary>
158 /// <value>equivalent DbType of the column as an integer</value>
159 public int DBType
160 {
161 get
162 {
163 if (this.type == typeof(Int16)) return 10;
164 else if (this.type == typeof(Int32)) return 11;
165 else if (this.type == typeof(Stream)) return 1;
166 else return 16;
167 }
168 }
169
170 /// <summary>
171 /// Gets the size of the column.
172 /// </summary>
173 /// <value>The size of integer columns this is either 2 or 4. For string columns this is the maximum
174 /// recommended length of the string, or 0 for unlimited length. For stream columns, 0 is returned.</value>
175 public int Size
176 {
177 get { return this.size; }
178 }
179
180 /// <summary>
181 /// Gets a value indicating whether the column must be non-null when inserting a record.
182 /// </summary>
183 /// <value>required status of the column</value>
184 public bool IsRequired
185 {
186 get { return this.isRequired; }
187 }
188
189 /// <summary>
190 /// Gets a value indicating whether the column is temporary. Temporary columns are not persisted
191 /// when the database is saved to disk.
192 /// </summary>
193 /// <value>temporary status of the column</value>
194 public bool IsTemporary
195 {
196 get { return this.isTemporary; }
197 }
198
199 /// <summary>
200 /// Gets a value indicating whether the column is a string column that is localizable.
201 /// </summary>
202 /// <value>localizable status of the column</value>
203 public bool IsLocalizable
204 {
205 get { return this.isLocalizable; }
206 }
207
208 /// <summary>
209 /// Gets an SQL fragment that can be used to create this column within a CREATE TABLE statement.
210 /// </summary>
211 /// <value>SQL fragment to be used for creating the column</value>
212 /// <remarks><p>
213 /// Examples:
214 /// <list type="bullet">
215 /// <item>LONG</item>
216 /// <item>SHORT TEMPORARY</item>
217 /// <item>CHAR(0) LOCALIZABLE</item>
218 /// <item>CHAR(72) NOT NULL LOCALIZABLE</item>
219 /// <item>OBJECT</item>
220 /// </list>
221 /// </p></remarks>
222 public string SqlCreateString
223 {
224 get
225 {
226 StringBuilder s = new StringBuilder();
227 s.AppendFormat("`{0}` ", this.name);
228 if (this.type == typeof(Int16)) s.Append("SHORT");
229 else if (this.type == typeof(Int32)) s.Append("LONG");
230 else if (this.type == typeof(String)) s.AppendFormat("CHAR({0})", this.size);
231 else s.Append("OBJECT");
232 if (this.isRequired) s.Append(" NOT NULL");
233 if (this.isTemporary) s.Append(" TEMPORARY");
234 if (this.isLocalizable) s.Append(" LOCALIZABLE");
235 return s.ToString();
236 }
237 }
238
239 /// <summary>
240 /// Gets a short string defining the type and size of the column.
241 /// </summary>
242 /// <value>
243 /// The definition string consists
244 /// of a single letter representing the data type followed by the width of the column (in characters
245 /// when applicable, bytes otherwise). A width of zero designates an unbounded width (for example,
246 /// long text fields and streams). An uppercase letter indicates that null values are allowed in
247 /// the column.
248 /// </value>
249 /// <remarks><p>
250 /// <list>
251 /// <item>s? - String, variable length (?=1-255)</item>
252 /// <item>s0 - String, variable length</item>
253 /// <item>i2 - Short integer</item>
254 /// <item>i4 - Long integer</item>
255 /// <item>v0 - Binary Stream</item>
256 /// <item>g? - Temporary string (?=0-255)</item>
257 /// <item>j? - Temporary integer (?=0,1,2,4)</item>
258 /// <item>O0 - Temporary object (stream)</item>
259 /// <item>l? - Localizable string, variable length (?=1-255)</item>
260 /// <item>l0 - Localizable string, variable length</item>
261 /// </list>
262 /// </p></remarks>
263 public string ColumnDefinitionString
264 {
265 get
266 {
267 char t;
268 if (this.type == typeof(Int16) || this.type == typeof(Int32))
269 {
270 t = (this.isTemporary ? 'j' : 'i');
271 }
272 else if (this.type == typeof(String))
273 {
274 t = (this.isTemporary ? 'g' : this.isLocalizable ? 'l' : 's');
275 }
276 else
277 {
278 t = (this.isTemporary ? 'O' : 'v');
279 }
280 return String.Format(
281 CultureInfo.InvariantCulture,
282 "{0}{1}",
283 (this.isRequired ? t : Char.ToUpper(t, CultureInfo.InvariantCulture)),
284 this.size);
285 }
286 }
287
288 /// <summary>
289 /// Gets the name of the column.
290 /// </summary>
291 /// <returns>Name of the column.</returns>
292 public override string ToString()
293 {
294 return this.Name;
295 }
296 }
297}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/ComponentInfo.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/ComponentInfo.cs
new file mode 100644
index 00000000..af27fd1d
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/ComponentInfo.cs
@@ -0,0 +1,276 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Text;
7 using System.Collections;
8 using System.Collections.Generic;
9
10 /// <summary>
11 /// Accessor for information about components within the context of an installation session.
12 /// </summary>
13 public sealed class ComponentInfoCollection : ICollection<ComponentInfo>
14 {
15 private Session session;
16
17 internal ComponentInfoCollection(Session session)
18 {
19 this.session = session;
20 }
21
22 /// <summary>
23 /// Gets information about a component within the context of an installation session.
24 /// </summary>
25 /// <param name="component">name of the component</param>
26 /// <returns>component object</returns>
27 public ComponentInfo this[string component]
28 {
29 get
30 {
31 return new ComponentInfo(this.session, component);
32 }
33 }
34
35 void ICollection<ComponentInfo>.Add(ComponentInfo item)
36 {
37 throw new InvalidOperationException();
38 }
39
40 void ICollection<ComponentInfo>.Clear()
41 {
42 throw new InvalidOperationException();
43 }
44
45 /// <summary>
46 /// Checks if the collection contains a component.
47 /// </summary>
48 /// <param name="component">name of the component</param>
49 /// <returns>true if the component is in the collection, else false</returns>
50 public bool Contains(string component)
51 {
52 return this.session.Database.CountRows(
53 "Component", "`Component` = '" + component + "'") == 1;
54 }
55
56 bool ICollection<ComponentInfo>.Contains(ComponentInfo item)
57 {
58 return item != null && this.Contains(item.Name);
59 }
60
61 /// <summary>
62 /// Copies the features into an array.
63 /// </summary>
64 /// <param name="array">array that receives the features</param>
65 /// <param name="arrayIndex">offset into the array</param>
66 public void CopyTo(ComponentInfo[] array, int arrayIndex)
67 {
68 foreach (ComponentInfo component in this)
69 {
70 array[arrayIndex++] = component;
71 }
72 }
73
74 /// <summary>
75 /// Gets the number of components defined for the product.
76 /// </summary>
77 public int Count
78 {
79 get
80 {
81 return this.session.Database.CountRows("Component");
82 }
83 }
84
85 bool ICollection<ComponentInfo>.IsReadOnly
86 {
87 get
88 {
89 return true;
90 }
91 }
92
93 bool ICollection<ComponentInfo>.Remove(ComponentInfo item)
94 {
95 throw new InvalidOperationException();
96 }
97
98 /// <summary>
99 /// Enumerates the components in the collection.
100 /// </summary>
101 /// <returns>an enumerator over all features in the collection</returns>
102 public IEnumerator<ComponentInfo> GetEnumerator()
103 {
104 using (View compView = this.session.Database.OpenView(
105 "SELECT `Component` FROM `Component`"))
106 {
107 compView.Execute();
108
109 foreach (Record compRec in compView) using (compRec)
110 {
111 string comp = compRec.GetString(1);
112 yield return new ComponentInfo(this.session, comp);
113 }
114 }
115 }
116
117 IEnumerator IEnumerable.GetEnumerator()
118 {
119 return this.GetEnumerator();
120 }
121 }
122
123 /// <summary>
124 /// Provides access to information about a component within the context of an installation session.
125 /// </summary>
126 public class ComponentInfo
127 {
128 private Session session;
129 private string name;
130
131 internal ComponentInfo(Session session, string name)
132 {
133 this.session = session;
134 this.name = name;
135 }
136
137 /// <summary>
138 /// Gets the name of the component (primary key in the Component table).
139 /// </summary>
140 public string Name
141 {
142 get
143 {
144 return this.name;
145 }
146 }
147
148 /// <summary>
149 /// Gets the current install state of the designated Component.
150 /// </summary>
151 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
152 /// <exception cref="ArgumentException">an unknown Component was requested</exception>
153 /// <remarks><p>
154 /// Win32 MSI API:
155 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetcomponentstate.asp">MsiGetComponentState</a>
156 /// </p></remarks>
157 public InstallState CurrentState
158 {
159 get
160 {
161 int installedState, actionState;
162 uint ret = RemotableNativeMethods.MsiGetComponentState((int) this.session.Handle, this.name, out installedState, out actionState);
163 if (ret != 0)
164 {
165 if (ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT)
166 {
167 throw InstallerException.ExceptionFromReturnCode(ret, this.name);
168 }
169 else
170 {
171 throw InstallerException.ExceptionFromReturnCode(ret);
172 }
173 }
174 return (InstallState) installedState;
175 }
176 }
177
178 /// <summary>
179 /// Gets or sets the action state of the designated Component.
180 /// </summary>
181 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
182 /// <exception cref="ArgumentException">an unknown Component was requested</exception>
183 /// <exception cref="InstallCanceledException">the user exited the installation</exception>
184 /// <remarks><p>
185 /// Win32 MSI APIs:
186 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetcomponentstate.asp">MsiGetComponentState</a>,
187 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetcomponentstate.asp">MsiSetComponentState</a>
188 /// </p></remarks>
189 public InstallState RequestState
190 {
191 get
192 {
193 int installedState, actionState;
194 uint ret = RemotableNativeMethods.MsiGetComponentState((int) this.session.Handle, this.name, out installedState, out actionState);
195 if (ret != 0)
196 {
197 if (ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT)
198 {
199 throw InstallerException.ExceptionFromReturnCode(ret, this.name);
200 }
201 else
202 {
203 throw InstallerException.ExceptionFromReturnCode(ret);
204 }
205 }
206 return (InstallState) actionState;
207 }
208
209 set
210 {
211 uint ret = RemotableNativeMethods.MsiSetComponentState((int) this.session.Handle, this.name, (int) value);
212 if (ret != 0)
213 {
214 if (ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT)
215 {
216 throw InstallerException.ExceptionFromReturnCode(ret, this.name);
217 }
218 else
219 {
220 throw InstallerException.ExceptionFromReturnCode(ret);
221 }
222 }
223 }
224 }
225
226 /// <summary>
227 /// Gets disk space per drive required to install a component.
228 /// </summary>
229 /// <param name="installState">Requested component state</param>
230 /// <returns>A list of InstallCost structures, specifying the cost for each drive for the component</returns>
231 /// <remarks><p>
232 /// Win32 MSI API:
233 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumcomponentcosts.asp">MsiEnumComponentCosts</a>
234 /// </p></remarks>
235 public IList<InstallCost> GetCost(InstallState installState)
236 {
237 IList<InstallCost> costs = new List<InstallCost>();
238 StringBuilder driveBuf = new StringBuilder(20);
239 for (uint i = 0; true; i++)
240 {
241 int cost, tempCost;
242 uint driveBufSize = (uint) driveBuf.Capacity;
243 uint ret = RemotableNativeMethods.MsiEnumComponentCosts(
244 (int) this.session.Handle,
245 this.name,
246 i,
247 (int) installState,
248 driveBuf,
249 ref driveBufSize,
250 out cost,
251 out tempCost);
252 if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break;
253 if (ret == (uint) NativeMethods.Error.MORE_DATA)
254 {
255 driveBuf.Capacity = (int) ++driveBufSize;
256 ret = RemotableNativeMethods.MsiEnumComponentCosts(
257 (int) this.session.Handle,
258 this.name,
259 i,
260 (int) installState,
261 driveBuf,
262 ref driveBufSize,
263 out cost,
264 out tempCost);
265 }
266
267 if (ret != 0)
268 {
269 throw InstallerException.ExceptionFromReturnCode(ret);
270 }
271 costs.Add(new InstallCost(driveBuf.ToString(), cost * 512L, tempCost * 512L));
272 }
273 return costs;
274 }
275 }
276}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/ComponentInstallation.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/ComponentInstallation.cs
new file mode 100644
index 00000000..6d368899
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/ComponentInstallation.cs
@@ -0,0 +1,382 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Text;
7 using System.Collections.Generic;
8 using System.Diagnostics.CodeAnalysis;
9
10 /// <summary>
11 /// Represents an instance of a registered component.
12 /// </summary>
13 public class ComponentInstallation : InstallationPart
14 {
15 /// <summary>
16 /// Gets the set of installed components for all products.
17 /// </summary>
18 /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
19 /// <remarks><p>
20 /// Win32 MSI API:
21 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumcomponents.asp">MsiEnumComponents</a>
22 /// </p></remarks>
23 public static IEnumerable<ComponentInstallation> AllComponents
24 {
25 get
26 {
27 StringBuilder buf = new StringBuilder(40);
28 for (uint i = 0; true; i++)
29 {
30 uint ret = NativeMethods.MsiEnumComponents(i, buf);
31 if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break;
32 if (ret != 0)
33 {
34 throw InstallerException.ExceptionFromReturnCode(ret);
35 }
36 yield return new ComponentInstallation(buf.ToString());
37 }
38 }
39 }
40
41 /// <summary>
42 /// Gets the set of installed components for products in the indicated context.
43 /// </summary>
44 /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
45 /// <remarks><p>
46 /// Win32 MSI API:
47 /// <a href="http://msdn.microsoft.com/library/dd407947.aspx">MsiEnumComponentsEx</a>
48 /// </p></remarks>
49 public static IEnumerable<ComponentInstallation> Components(string szUserSid, UserContexts dwContext)
50 {
51 uint pcchSid = 32;
52 StringBuilder szSid = new StringBuilder((int)pcchSid);
53 StringBuilder buf = new StringBuilder(40);
54 UserContexts installedContext;
55 for (uint i = 0; true; i++)
56 {
57 uint ret = NativeMethods.MsiEnumComponentsEx(szUserSid, dwContext, i, buf, out installedContext, szSid, ref pcchSid);
58 if (ret == (uint) NativeMethods.Error.MORE_DATA)
59 {
60 szSid.EnsureCapacity((int) ++pcchSid);
61 ret = NativeMethods.MsiEnumComponentsEx(szUserSid, dwContext, i, buf, out installedContext, szSid, ref pcchSid);
62 }
63 if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break;
64 if (ret != 0)
65 {
66 throw InstallerException.ExceptionFromReturnCode(ret);
67 }
68 yield return new ComponentInstallation(buf.ToString(), szSid.ToString(), installedContext);
69 }
70 }
71 private static string GetProductCode(string component)
72 {
73 StringBuilder buf = new StringBuilder(40);
74 uint ret = NativeMethods.MsiGetProductCode(component, buf);
75 if (ret != 0)
76 {
77 return null;
78 }
79
80 return buf.ToString();
81 }
82
83 private static string GetProductCode(string component, string szUserSid, UserContexts dwContext)
84 {
85 // TODO: We really need what would be MsiGetProductCodeEx, or at least a reasonable facimile thereof (something that restricts the search to the context defined by szUserSid & dwContext)
86 return GetProductCode(component);
87 }
88
89 /// <summary>
90 /// Creates a new ComponentInstallation, automatically detecting the
91 /// product that the component is a part of.
92 /// </summary>
93 /// <param name="componentCode">component GUID</param>
94 /// <remarks><p>
95 /// Win32 MSI API:
96 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproductcode.asp">MsiGetProductCode</a>
97 /// </p></remarks>
98 public ComponentInstallation(string componentCode)
99 : this(componentCode, ComponentInstallation.GetProductCode(componentCode))
100 {
101 }
102
103 /// <summary>
104 /// Creates a new ComponentInstallation, automatically detecting the
105 /// product that the component is a part of.
106 /// </summary>
107 /// <param name="componentCode">component GUID</param>
108 /// <param name="szUserSid">context user SID</param>
109 /// <param name="dwContext">user contexts</param>
110 public ComponentInstallation(string componentCode, string szUserSid, UserContexts dwContext)
111 : this(componentCode, ComponentInstallation.GetProductCode(componentCode, szUserSid, dwContext), szUserSid, dwContext)
112 {
113 }
114
115 /// <summary>
116 /// Creates a new ComponentInstallation for a component installed by a
117 /// specific product.
118 /// </summary>
119 /// <param name="componentCode">component GUID</param>
120 /// <param name="productCode">ProductCode GUID</param>
121 public ComponentInstallation(string componentCode, string productCode)
122 : this(componentCode, productCode, null, UserContexts.None)
123 {
124 }
125
126 /// <summary>
127 /// Creates a new ComponentInstallation for a component installed by a
128 /// specific product.
129 /// </summary>
130 /// <param name="componentCode">component GUID</param>
131 /// <param name="productCode">ProductCode GUID</param>
132 /// <param name="szUserSid">context user SID</param>
133 /// <param name="dwContext">user contexts</param>
134 public ComponentInstallation(string componentCode, string productCode, string szUserSid, UserContexts dwContext)
135 : base(componentCode, productCode, szUserSid, dwContext)
136 {
137 if (string.IsNullOrEmpty(componentCode))
138 {
139 throw new ArgumentNullException("componentCode");
140 }
141 }
142
143 /// <summary>
144 /// Gets the component code (GUID) of the component.
145 /// </summary>
146 public string ComponentCode
147 {
148 get
149 {
150 return this.Id;
151 }
152 }
153
154 /// <summary>
155 /// Gets all client products of a specified component.
156 /// </summary>
157 /// <returns>enumeration over all client products of the component</returns>
158 /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
159 /// <remarks><p>
160 /// Because clients are not ordered, any new component has an arbitrary index.
161 /// This means that the property may return clients in any order.
162 /// </p><p>
163 /// Win32 MSI API:
164 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumclients.asp">MsiEnumClients</a>,
165 /// <a href="http://msdn.microsoft.com/library/dd407946.aspx">MsiEnumClientsEx</a>
166 /// </p></remarks>
167 public IEnumerable<ProductInstallation> ClientProducts
168 {
169 get
170 {
171 StringBuilder buf = new StringBuilder(40);
172 for (uint i = 0; true; i++)
173 {
174 uint chSid = 0;
175 UserContexts installedContext;
176 uint ret = this.Context == UserContexts.None ?
177 NativeMethods.MsiEnumClients(this.ComponentCode, i, buf) :
178 NativeMethods.MsiEnumClientsEx(this.ComponentCode, this.UserSid, this.Context, i, buf, out installedContext, null, ref chSid);
179 if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break;
180 else if (ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT) break;
181 if (ret != 0)
182 {
183 throw InstallerException.ExceptionFromReturnCode(ret);
184 }
185 yield return new ProductInstallation(buf.ToString());
186 }
187 }
188 }
189
190 /// <summary>
191 /// Gets the installed state of a component.
192 /// </summary>
193 /// <returns>the installed state of the component, or InstallState.Unknown
194 /// if this component is not part of a product</returns>
195 /// <remarks><p>
196 /// Win32 MSI API:
197 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetcomponentpath.asp">MsiGetComponentPath</a>,
198 /// <a href="http://msdn.microsoft.com/library/dd408006.aspx">MsiGetComponentPathEx</a>
199 /// </p></remarks>
200 public override InstallState State
201 {
202 get
203 {
204 if (this.ProductCode != null)
205 {
206 uint bufSize = 0;
207 int installState = this.Context == UserContexts.None ?
208 NativeMethods.MsiGetComponentPath(
209 this.ProductCode, this.ComponentCode, null, ref bufSize) :
210 NativeMethods.MsiGetComponentPathEx(
211 this.ProductCode, this.ComponentCode, this.UserSid, this.Context, null, ref bufSize);
212 return (InstallState) installState;
213 }
214 else
215 {
216 return InstallState.Unknown;
217 }
218 }
219 }
220
221 /// <summary>
222 /// Gets the full path to an installed component. If the key path for the component is a
223 /// registry key then the registry key is returned.
224 /// </summary>
225 /// <returns>The file or registry keypath to the component, or null if the component is not available.</returns>
226 /// <exception cref="ArgumentException">An unknown product or component was specified</exception>
227 /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
228 /// <remarks><p>
229 /// If the component is a registry key, the registry roots are represented numerically.
230 /// For example, a registry path of "HKEY_CURRENT_USER\SOFTWARE\Microsoft" would be returned
231 /// as "01:\SOFTWARE\Microsoft". The registry roots returned are defined as follows:
232 /// HKEY_CLASSES_ROOT=00, HKEY_CURRENT_USER=01, HKEY_LOCAL_MACHINE=02, HKEY_USERS=03,
233 /// HKEY_PERFORMANCE_DATA=04
234 /// </p><p>
235 /// Win32 MSI APIs:
236 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetcomponentpath.asp">MsiGetComponentPath</a>,
237 /// <a href="http://msdn.microsoft.com/library/dd408006.aspx">MsiGetComponentPathEx</a>,
238 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msilocatecomponent.asp">MsiLocateComponent</a>
239 /// </p></remarks>
240 public string Path
241 {
242 get
243 {
244 StringBuilder buf = new StringBuilder(256);
245 uint bufSize = (uint) buf.Capacity;
246 InstallState installState;
247
248 if (this.ProductCode != null)
249 {
250 installState = (this.Context == UserContexts.None) ?
251 (InstallState) NativeMethods.MsiGetComponentPath(
252 this.ProductCode, this.ComponentCode, buf, ref bufSize) :
253 (InstallState) NativeMethods.MsiGetComponentPathEx(
254 this.ProductCode, this.ComponentCode, this.UserSid, this.Context, buf, ref bufSize);
255 if (installState == InstallState.MoreData)
256 {
257 buf.Capacity = (int) ++bufSize;
258 installState = (this.Context == UserContexts.None) ?
259 (InstallState) NativeMethods.MsiGetComponentPath(
260 this.ProductCode, this.ComponentCode, buf, ref bufSize) :
261 (InstallState) NativeMethods.MsiGetComponentPathEx(
262 this.ProductCode, this.ComponentCode, this.UserSid, this.Context, buf, ref bufSize);
263 }
264 }
265 else
266 {
267 installState = (InstallState) NativeMethods.MsiLocateComponent(
268 this.ComponentCode, buf, ref bufSize);
269 if (installState == InstallState.MoreData)
270 {
271 buf.Capacity = (int) ++bufSize;
272 installState = (InstallState) NativeMethods.MsiLocateComponent(
273 this.ComponentCode, buf, ref bufSize);
274 }
275 }
276
277 switch (installState)
278 {
279 case InstallState.Local:
280 case InstallState.Source:
281 case InstallState.SourceAbsent:
282 return buf.ToString();
283
284 default:
285 return null;
286 }
287 }
288 }
289
290 /// <summary>
291 /// Gets the set of registered qualifiers for the component.
292 /// </summary>
293 /// <returns>Enumeration of the qulifiers for the component.</returns>
294 /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
295 /// <remarks><p>
296 /// Because qualifiers are not ordered, any new qualifier has an arbitrary index,
297 /// meaning the function can return qualifiers in any order.
298 /// </p><p>
299 /// Win32 MSI API:
300 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumcomponentqualifiers.asp">MsiEnumComponentQualifiers</a>
301 /// </p></remarks>
302 public IEnumerable<ComponentInstallation.Qualifier> Qualifiers
303 {
304 get
305 {
306 StringBuilder qualBuf = new StringBuilder(255);
307 StringBuilder dataBuf = new StringBuilder(255);
308 for (uint i = 0; ; i++)
309 {
310 uint qualBufSize = (uint) qualBuf.Capacity;
311 uint dataBufSize = (uint) dataBuf.Capacity;
312 uint ret = NativeMethods.MsiEnumComponentQualifiers(
313 this.ComponentCode, i, qualBuf, ref qualBufSize, dataBuf, ref dataBufSize);
314 if (ret == (uint) NativeMethods.Error.MORE_DATA)
315 {
316 qualBuf.Capacity = (int) ++qualBufSize;
317 dataBuf.Capacity = (int) ++dataBufSize;
318 ret = NativeMethods.MsiEnumComponentQualifiers(
319 this.ComponentCode, i, qualBuf, ref qualBufSize, dataBuf, ref dataBufSize);
320 }
321
322 if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS ||
323 ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT)
324 {
325 break;
326 }
327
328 if (ret != 0)
329 {
330 throw InstallerException.ExceptionFromReturnCode(ret);
331 }
332
333 yield return new ComponentInstallation.Qualifier(
334 qualBuf.ToString(), dataBuf.ToString());
335 }
336 }
337 }
338
339 /// <summary>
340 /// Holds data about a component qualifier.
341 /// </summary>
342 /// <remarks><p>
343 /// Win32 MSI API:
344 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumcomponentqualifiers.asp">MsiEnumComponentQualifiers</a>
345 /// </p></remarks>
346 [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
347 [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
348 public struct Qualifier
349 {
350 private string qualifierCode;
351 private string data;
352
353 internal Qualifier(string qualifierCode, string data)
354 {
355 this.qualifierCode = qualifierCode;
356 this.data = data;
357 }
358
359 /// <summary>
360 /// Gets the qualifier code.
361 /// </summary>
362 public string QualifierCode
363 {
364 get
365 {
366 return this.qualifierCode;
367 }
368 }
369
370 /// <summary>
371 /// Gets the qualifier data.
372 /// </summary>
373 public string Data
374 {
375 get
376 {
377 return this.data;
378 }
379 }
380 }
381 }
382}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionAttribute.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionAttribute.cs
new file mode 100644
index 00000000..d9bdb71b
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionAttribute.cs
@@ -0,0 +1,55 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Reflection;
7
8 /// <summary>
9 /// Marks a method as a custom action entry point.
10 /// </summary>
11 /// <remarks><p>
12 /// A custom action method must be defined as public and static,
13 /// take a single <see cref="Session"/> object as a parameter,
14 /// and return an <see cref="ActionResult"/> enumeration value.
15 /// </p></remarks>
16 [Serializable, AttributeUsage(AttributeTargets.Method)]
17 public sealed class CustomActionAttribute : Attribute
18 {
19 /// <summary>
20 /// Name of the custom action entrypoint, or null if the same as the method name.
21 /// </summary>
22 private string name;
23
24 /// <summary>
25 /// Marks a method as a custom action entry point.
26 /// </summary>
27 public CustomActionAttribute()
28 : this(null)
29 {
30 }
31
32 /// <summary>
33 /// Marks a method as a custom action entry point.
34 /// </summary>
35 /// <param name="name">Name of the function to be exported,
36 /// defaults to the name of this method</param>
37 public CustomActionAttribute(string name)
38 {
39 this.name = name;
40 }
41
42 /// <summary>
43 /// Gets or sets the name of the custom action entrypoint. A null
44 /// value defaults to the name of the method.
45 /// </summary>
46 /// <value>name of the custom action entrypoint, or null if none was specified</value>
47 public string Name
48 {
49 get
50 {
51 return this.name;
52 }
53 }
54 }
55}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionProxy.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionProxy.cs
new file mode 100644
index 00000000..d3fd7d1b
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionProxy.cs
@@ -0,0 +1,321 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Security;
9 using System.Reflection;
10 using System.Collections;
11 using System.Configuration;
12 using System.Runtime.InteropServices;
13 using System.Diagnostics.CodeAnalysis;
14
15 /// <summary>
16 /// Managed-code portion of the custom action proxy.
17 /// </summary>
18 internal static class CustomActionProxy
19 {
20 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
21 public static int InvokeCustomAction32(int sessionHandle, string entryPoint,
22 int remotingDelegatePtr)
23 {
24 return CustomActionProxy.InvokeCustomAction(sessionHandle, entryPoint, new IntPtr(remotingDelegatePtr));
25 }
26
27 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
28 public static int InvokeCustomAction64(int sessionHandle, string entryPoint,
29 long remotingDelegatePtr)
30 {
31 return CustomActionProxy.InvokeCustomAction(sessionHandle, entryPoint, new IntPtr(remotingDelegatePtr));
32 }
33
34 /// <summary>
35 /// Invokes a managed custom action method.
36 /// </summary>
37 /// <param name="sessionHandle">Integer handle to the installer session.</param>
38 /// <param name="entryPoint">Name of the custom action entrypoint. This must
39 /// either map to an entrypoint definition in the <c>customActions</c>
40 /// config section, or be an explicit entrypoint of the form:
41 /// &quot;AssemblyName!Namespace.Class.Method&quot;</param>
42 /// <param name="remotingDelegatePtr">Pointer to a delegate used to
43 /// make remote API calls, if this custom action is running out-of-proc.</param>
44 /// <returns>The value returned by the custom action method,
45 /// or ERROR_INSTALL_FAILURE if the custom action could not be invoked.</returns>
46 [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
47 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
48 public static int InvokeCustomAction(int sessionHandle, string entryPoint,
49 IntPtr remotingDelegatePtr)
50 {
51 Session session = null;
52 string assemblyName, className, methodName;
53 MethodInfo method;
54
55 try
56 {
57 MsiRemoteInvoke remotingDelegate = (MsiRemoteInvoke)
58 Marshal.GetDelegateForFunctionPointer(
59 remotingDelegatePtr, typeof(MsiRemoteInvoke));
60 RemotableNativeMethods.RemotingDelegate = remotingDelegate;
61
62 sessionHandle = RemotableNativeMethods.MakeRemoteHandle(sessionHandle);
63 session = new Session((IntPtr) sessionHandle, false);
64 if (String.IsNullOrEmpty(entryPoint))
65 {
66 throw new ArgumentNullException("entryPoint");
67 }
68
69 if (!CustomActionProxy.FindEntryPoint(
70 session,
71 entryPoint,
72 out assemblyName,
73 out className,
74 out methodName))
75 {
76 return (int) ActionResult.Failure;
77 }
78 session.Log("Calling custom action {0}!{1}.{2}", assemblyName, className, methodName);
79
80 method = CustomActionProxy.GetCustomActionMethod(
81 session,
82 assemblyName,
83 className,
84 methodName);
85 if (method == null)
86 {
87 return (int) ActionResult.Failure;
88 }
89 }
90 catch (Exception ex)
91 {
92 if (session != null)
93 {
94 try
95 {
96 session.Log("Exception while loading custom action:");
97 session.Log(ex.ToString());
98 }
99 catch (Exception) { }
100 }
101 return (int) ActionResult.Failure;
102 }
103
104 try
105 {
106 // Set the current directory to the location of the extracted files.
107 Environment.CurrentDirectory =
108 AppDomain.CurrentDomain.BaseDirectory;
109
110 object[] args = new object[] { session };
111 if (DebugBreakEnabled(new string[] { entryPoint, methodName }))
112 {
113 string message = String.Format(
114 "To debug your custom action, attach to process ID {0} (0x{0:x}) and click OK; otherwise, click Cancel to fail the custom action.",
115 System.Diagnostics.Process.GetCurrentProcess().Id
116 );
117
118 MessageResult button = NativeMethods.MessageBox(
119 IntPtr.Zero,
120 message,
121 "Custom Action Breakpoint",
122 (int)MessageButtons.OKCancel | (int)MessageIcon.Asterisk | (int)(MessageBoxStyles.TopMost | MessageBoxStyles.ServiceNotification)
123 );
124
125 if (MessageResult.Cancel == button)
126 {
127 return (int)ActionResult.UserExit;
128 }
129 }
130
131 ActionResult result = (ActionResult) method.Invoke(null, args);
132 session.Close();
133 return (int) result;
134 }
135 catch (InstallCanceledException)
136 {
137 return (int) ActionResult.UserExit;
138 }
139 catch (Exception ex)
140 {
141 session.Log("Exception thrown by custom action:");
142 session.Log(ex.ToString());
143 return (int) ActionResult.Failure;
144 }
145 }
146
147 /// <summary>
148 /// Checks the "MMsiBreak" environment variable for any matching custom action names.
149 /// </summary>
150 /// <param name="names">List of names to search for in the environment
151 /// variable string.</param>
152 /// <returns>True if a match was found, else false.</returns>
153 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
154 internal static bool DebugBreakEnabled(string[] names)
155 {
156 string mmsibreak = Environment.GetEnvironmentVariable("MMsiBreak");
157 if (mmsibreak != null)
158 {
159 foreach (string breakName in mmsibreak.Split(',', ';'))
160 {
161 foreach (string name in names)
162 {
163 if (breakName == name)
164 {
165 return true;
166 }
167 }
168 }
169 }
170 return false;
171 }
172
173 /// <summary>
174 /// Locates and parses an entrypoint mapping in CustomAction.config.
175 /// </summary>
176 /// <param name="session">Installer session handle, just used for logging.</param>
177 /// <param name="entryPoint">Custom action entrypoint name: the key value
178 /// in an item in the <c>customActions</c> section of the config file.</param>
179 /// <param name="assemblyName">Returned display name of the assembly from
180 /// the entrypoint mapping.</param>
181 /// <param name="className">Returned class name of the entrypoint mapping.</param>
182 /// <param name="methodName">Returned method name of the entrypoint mapping.</param>
183 /// <returns>True if the entrypoint was found, false if not or if some error
184 /// occurred.</returns>
185 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
186 private static bool FindEntryPoint(
187 Session session,
188 string entryPoint,
189 out string assemblyName,
190 out string className,
191 out string methodName)
192 {
193 assemblyName = null;
194 className = null;
195 methodName = null;
196
197 string fullEntryPoint;
198 if (entryPoint.IndexOf('!') > 0)
199 {
200 fullEntryPoint = entryPoint;
201 }
202 else
203 {
204#if NET20
205 IDictionary config;
206 try
207 {
208 config = (IDictionary) ConfigurationManager.GetSection("customActions");
209 }
210 catch (ConfigurationException cex)
211 {
212 session.Log("Error: missing or invalid customActions config section.");
213 session.Log(cex.ToString());
214 return false;
215 }
216 fullEntryPoint = (string) config[entryPoint];
217 if (fullEntryPoint == null)
218 {
219 session.Log(
220 "Error: custom action entry point '{0}' not found " +
221 "in customActions config section.",
222 entryPoint);
223 return false;
224 }
225#else
226 throw new NotImplementedException();
227#endif
228 }
229
230 int assemblySplit = fullEntryPoint.IndexOf('!');
231 int methodSplit = fullEntryPoint.LastIndexOf('.');
232 if (assemblySplit < 0 || methodSplit < 0 || methodSplit < assemblySplit)
233 {
234 session.Log("Error: invalid custom action entry point:" + entryPoint);
235 return false;
236 }
237
238 assemblyName = fullEntryPoint.Substring(0, assemblySplit);
239 className = fullEntryPoint.Substring(assemblySplit + 1, methodSplit - assemblySplit - 1);
240 methodName = fullEntryPoint.Substring(methodSplit + 1);
241 return true;
242 }
243
244 /// <summary>
245 /// Uses reflection to load the assembly and class and find the method.
246 /// </summary>
247 /// <param name="session">Installer session handle, just used for logging.</param>
248 /// <param name="assemblyName">Display name of the assembly containing the
249 /// custom action method.</param>
250 /// <param name="className">Fully-qualified name of the class containing the
251 /// custom action method.</param>
252 /// <param name="methodName">Name of the custom action method.</param>
253 /// <returns>The method, or null if not found.</returns>
254 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
255 private static MethodInfo GetCustomActionMethod(
256 Session session,
257 string assemblyName,
258 string className,
259 string methodName)
260 {
261 Assembly customActionAssembly;
262 Type customActionClass = null;
263 Exception caughtEx = null;
264 try
265 {
266 customActionAssembly = AppDomain.CurrentDomain.Load(assemblyName);
267 customActionClass = customActionAssembly.GetType(className, true, true);
268 }
269 catch (IOException ex) { caughtEx = ex; }
270 catch (BadImageFormatException ex) { caughtEx = ex; }
271 catch (TypeLoadException ex) { caughtEx = ex; }
272 catch (ReflectionTypeLoadException ex) { caughtEx = ex; }
273 catch (SecurityException ex) { caughtEx = ex; }
274 if (caughtEx != null)
275 {
276 session.Log("Error: could not load custom action class " + className + " from assembly: " + assemblyName);
277 session.Log(caughtEx.ToString());
278 return null;
279 }
280
281 MethodInfo[] methods = customActionClass.GetMethods(
282 BindingFlags.Public | BindingFlags.Static);
283 foreach (MethodInfo method in methods)
284 {
285 if (method.Name == methodName &&
286 CustomActionProxy.MethodHasCustomActionSignature(method))
287 {
288 return method;
289 }
290 }
291 session.Log("Error: custom action method \"" + methodName +
292 "\" is missing or has the wrong signature.");
293 return null;
294 }
295
296 /// <summary>
297 /// Checks if a method has the right return and paramater types
298 /// for a custom action, and that it is marked by a CustomActionAttribute.
299 /// </summary>
300 /// <param name="method">Method to be checked.</param>
301 /// <returns>True if the method is a valid custom action, else false.</returns>
302 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
303 private static bool MethodHasCustomActionSignature(MethodInfo method)
304 {
305 if (method.ReturnType == typeof(ActionResult) &&
306 method.GetParameters().Length == 1 &&
307 method.GetParameters()[0].ParameterType == typeof(Session))
308 {
309 object[] methodAttribs = method.GetCustomAttributes(false);
310 foreach (object attrib in methodAttribs)
311 {
312 if (attrib is CustomActionAttribute)
313 {
314 return true;
315 }
316 }
317 }
318 return false;
319 }
320 }
321}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Database.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Database.cs
new file mode 100644
index 00000000..09627f4b
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Database.cs
@@ -0,0 +1,933 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.IO;
7 using System.Collections.Generic;
8 using System.Globalization;
9 using System.Diagnostics.CodeAnalysis;
10
11 /// <summary>
12 /// Accesses a Windows Installer database.
13 /// </summary>
14 /// <remarks><p>
15 /// The <see cref="Commit"/> method must be called before the Database is closed to write out all
16 /// persistent changes. If the Commit method is not called, the installer performs an implicit
17 /// rollback upon object destruction.
18 /// </p><p>
19 /// The client can use the following procedure for data access:<list type="number">
20 /// <item><description>Obtain a Database object using one of the Database constructors.</description></item>
21 /// <item><description>Initiate a query using a SQL string by calling the <see cref="OpenView"/>
22 /// method of the Database.</description></item>
23 /// <item><description>Set query parameters in a <see cref="Record"/> and execute the database
24 /// query by calling the <see cref="View.Execute(Record)"/> method of the <see cref="View"/>. This
25 /// produces a result that can be fetched or updated.</description></item>
26 /// <item><description>Call the <see cref="View.Fetch"/> method of the View repeatedly to return
27 /// Records.</description></item>
28 /// <item><description>Update database rows of a Record object obtained by the Fetch method using
29 /// one of the <see cref="View.Modify"/> methods of the View.</description></item>
30 /// <item><description>Release the query and any unfetched records by calling the <see cref="InstallerHandle.Close"/>
31 /// method of the View.</description></item>
32 /// <item><description>Persist any database updates by calling the Commit method of the Database.
33 /// </description></item>
34 /// </list>
35 /// </p></remarks>
36 public partial class Database : InstallerHandle
37 {
38 private string filePath;
39 private DatabaseOpenMode openMode;
40 private SummaryInfo summaryInfo;
41 private TableCollection tables;
42 private IList<string> deleteOnClose;
43
44 /// <summary>
45 /// Opens an existing database in read-only mode.
46 /// </summary>
47 /// <param name="filePath">Path to the database file.</param>
48 /// <exception cref="InstallerException">the database could not be created/opened</exception>
49 /// <remarks><p>
50 /// Because this constructor initiates database access, it cannot be used with a
51 /// running installation.
52 /// </p><p>
53 /// The Database object should be <see cref="InstallerHandle.Close"/>d after use.
54 /// It is best that the handle be closed manually as soon as it is no longer
55 /// needed, as leaving lots of unused handles open can degrade performance.
56 /// </p><p>
57 /// Win32 MSI API:
58 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopendatabase.asp">MsiOpenDatabase</a>
59 /// </p></remarks>
60 public Database(string filePath)
61 : this(filePath, DatabaseOpenMode.ReadOnly)
62 {
63 }
64
65 /// <summary>
66 /// Opens an existing database with another database as output.
67 /// </summary>
68 /// <param name="filePath">Path to the database to be read.</param>
69 /// <param name="outputPath">Open mode for the database</param>
70 /// <returns>Database object representing the created or opened database</returns>
71 /// <exception cref="InstallerException">the database could not be created/opened</exception>
72 /// <remarks><p>
73 /// When a database is opened as the output of another database, the summary information stream
74 /// of the output database is actually a read-only mirror of the original database and thus cannot
75 /// be changed. Additionally, it is not persisted with the database. To create or modify the
76 /// summary information for the output database it must be closed and re-opened.
77 /// </p><p>
78 /// The Database object should be <see cref="InstallerHandle.Close"/>d after use.
79 /// It is best that the handle be closed manually as soon as it is no longer
80 /// needed, as leaving lots of unused handles open can degrade performance.
81 /// </p><p>
82 /// The database is opened in <see cref="DatabaseOpenMode.CreateDirect" /> mode, and will be
83 /// automatically commited when it is closed.
84 /// </p><p>
85 /// Win32 MSI API:
86 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopendatabase.asp">MsiOpenDatabase</a>
87 /// </p></remarks>
88 public Database(string filePath, string outputPath)
89 : this((IntPtr) Database.Open(filePath, outputPath), true, outputPath, DatabaseOpenMode.CreateDirect)
90 {
91 }
92
93 /// <summary>
94 /// Opens an existing database or creates a new one.
95 /// </summary>
96 /// <param name="filePath">Path to the database file. If an empty string
97 /// is supplied, a temporary database is created that is not persisted.</param>
98 /// <param name="mode">Open mode for the database</param>
99 /// <exception cref="InstallerException">the database could not be created/opened</exception>
100 /// <remarks><p>
101 /// Because this constructor initiates database access, it cannot be used with a
102 /// running installation.
103 /// </p><p>
104 /// The database object should be <see cref="InstallerHandle.Close"/>d after use.
105 /// The finalizer will close the handle if it is still open, however due to the nondeterministic
106 /// nature of finalization it is best that the handle be closed manually as soon as it is no
107 /// longer needed, as leaving lots of unused handles open can degrade performance.
108 /// </p><p>
109 /// A database opened in <see cref="DatabaseOpenMode.CreateDirect" /> or
110 /// <see cref="DatabaseOpenMode.Direct" /> mode will be automatically commited when it is
111 /// closed. However a database opened in <see cref="DatabaseOpenMode.Create" /> or
112 /// <see cref="DatabaseOpenMode.Transact" /> mode must have the <see cref="Commit" /> method
113 /// called before it is closed, otherwise no changes will be persisted.
114 /// </p><p>
115 /// Win32 MSI API:
116 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopendatabase.asp">MsiOpenDatabase</a>
117 /// </p></remarks>
118 public Database(string filePath, DatabaseOpenMode mode)
119 : this((IntPtr) Database.Open(filePath, mode), true, filePath, mode)
120 {
121 }
122
123 /// <summary>
124 /// Creates a new database from an MSI handle.
125 /// </summary>
126 /// <param name="handle">Native MSI database handle.</param>
127 /// <param name="ownsHandle">True if the handle should be closed
128 /// when the database object is disposed</param>
129 /// <param name="filePath">Path of the database file, if known</param>
130 /// <param name="openMode">Mode the handle was originally opened in</param>
131 protected internal Database(
132 IntPtr handle, bool ownsHandle, string filePath, DatabaseOpenMode openMode)
133 : base(handle, ownsHandle)
134 {
135 this.filePath = filePath;
136 this.openMode = openMode;
137 }
138
139 /// <summary>
140 /// Gets the file path the Database was originally opened from, or null if not known.
141 /// </summary>
142 public String FilePath
143 {
144 get
145 {
146 return this.filePath;
147 }
148 }
149
150 /// <summary>
151 /// Gets the open mode for the database.
152 /// </summary>
153 public DatabaseOpenMode OpenMode
154 {
155 get
156 {
157 return this.openMode;
158 }
159 }
160
161 /// <summary>
162 /// Gets a boolean value indicating whether this database was opened in read-only mode.
163 /// </summary>
164 /// <remarks><p>
165 /// Win32 MSI API:
166 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetdatabasestate.asp">MsiGetDatabaseState</a>
167 /// </p></remarks>
168 public bool IsReadOnly
169 {
170 get
171 {
172 if (RemotableNativeMethods.RemotingEnabled)
173 {
174 return true;
175 }
176
177 int state = NativeMethods.MsiGetDatabaseState((int) this.Handle);
178 return state != 1;
179 }
180 }
181
182 /// <summary>
183 /// Gets the collection of tables in the Database.
184 /// </summary>
185 public TableCollection Tables
186 {
187 get
188 {
189 if (this.tables == null)
190 {
191 this.tables = new TableCollection(this);
192 }
193 return this.tables;
194 }
195 }
196
197 /// <summary>
198 /// Gets or sets the code page of the Database.
199 /// </summary>
200 /// <exception cref="IOException">error exporting/importing the codepage data</exception>
201 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
202 /// <remarks><p>
203 /// Getting or setting the code page is a slow operation because it involves an export or import
204 /// of the codepage data to/from a temporary file.
205 /// </p></remarks>
206 public int CodePage
207 {
208 get
209 {
210 string tempFile = Path.GetTempFileName();
211 StreamReader reader = null;
212 try
213 {
214 this.Export("_ForceCodepage", tempFile);
215 reader = File.OpenText(tempFile);
216 reader.ReadLine(); // Skip column name record.
217 reader.ReadLine(); // Skip column defn record.
218 string codePageLine = reader.ReadLine();
219 return Int32.Parse(codePageLine.Split('\t')[0], CultureInfo.InvariantCulture.NumberFormat);
220 }
221 finally
222 {
223 if (reader != null) reader.Close();
224 File.Delete(tempFile);
225 }
226 }
227
228 set
229 {
230 string tempFile = Path.GetTempFileName();
231 StreamWriter writer = null;
232 try
233 {
234 writer = File.AppendText(tempFile);
235 writer.WriteLine("");
236 writer.WriteLine("");
237 writer.WriteLine("{0}\t_ForceCodepage", value);
238 writer.Close();
239 writer = null;
240 this.Import(tempFile);
241 }
242 finally
243 {
244 if (writer != null) writer.Close();
245 File.Delete(tempFile);
246 }
247 }
248 }
249
250 /// <summary>
251 /// Gets the SummaryInfo object for this database that can be used to examine and modify properties
252 /// to the summary information stream.
253 /// </summary>
254 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
255 /// <remarks><p>
256 /// The object returned from this property does not need to be explicitly persisted or closed.
257 /// Any modifications will be automatically saved when the database is committed.
258 /// </p><p>
259 /// Win32 MSI API:
260 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetsummaryinformation.asp">MsiGetSummaryInformation</a>
261 /// </p></remarks>
262 public SummaryInfo SummaryInfo
263 {
264 get
265 {
266 if (this.summaryInfo == null || this.summaryInfo.IsClosed)
267 {
268 lock (this.Sync)
269 {
270 if (this.summaryInfo == null || this.summaryInfo.IsClosed)
271 {
272 int summaryInfoHandle;
273 int maxProperties = this.IsReadOnly ? 0 : SummaryInfo.MAX_PROPERTIES;
274 uint ret = RemotableNativeMethods.MsiGetSummaryInformation((int) this.Handle, null, (uint) maxProperties, out summaryInfoHandle);
275 if (ret != 0)
276 {
277 throw InstallerException.ExceptionFromReturnCode(ret);
278 }
279 this.summaryInfo = new SummaryInfo((IntPtr) summaryInfoHandle, true);
280 }
281 }
282 }
283 return this.summaryInfo;
284 }
285 }
286
287 /// <summary>
288 /// Creates a new Database object from an integer database handle.
289 /// </summary>
290 /// <remarks><p>
291 /// This method is only provided for interop purposes. A Database object
292 /// should normally be obtained from <see cref="Session.Database"/> or
293 /// a public Database constructor.
294 /// </p></remarks>
295 /// <param name="handle">Integer database handle</param>
296 /// <param name="ownsHandle">true to close the handle when this object is disposed</param>
297 public static Database FromHandle(IntPtr handle, bool ownsHandle)
298 {
299 return new Database(
300 handle,
301 ownsHandle,
302 null,
303 NativeMethods.MsiGetDatabaseState((int) handle) == 1 ? DatabaseOpenMode.Direct : DatabaseOpenMode.ReadOnly);
304 }
305
306 /// <summary>
307 /// Schedules a file or directory for deletion after the database handle is closed.
308 /// </summary>
309 /// <param name="path">File or directory path to be deleted. All files and subdirectories
310 /// under a directory are deleted.</param>
311 /// <remarks><p>
312 /// Once an item is scheduled, it cannot be unscheduled.
313 /// </p><p>
314 /// The items cannot be deleted if the Database object is auto-disposed by the
315 /// garbage collector; the handle must be explicitly closed.
316 /// </p><p>
317 /// Files which are read-only or otherwise locked cannot be deleted,
318 /// but they will not cause an exception to be thrown.
319 /// </p></remarks>
320 public void DeleteOnClose(string path)
321 {
322 if (this.deleteOnClose == null)
323 {
324 this.deleteOnClose = new List<string>();
325 }
326 this.deleteOnClose.Add(path);
327 }
328
329 /// <summary>
330 /// Merges another database with this database.
331 /// </summary>
332 /// <param name="otherDatabase">The database to be merged into this database</param>
333 /// <param name="errorTable">Optional name of table to contain the names of the tables containing
334 /// merge conflicts, the number of conflicting rows within the table, and a reference to the table
335 /// with the merge conflict.</param>
336 /// <exception cref="MergeException">merge failed due to a schema difference or data conflict</exception>
337 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
338 /// <remarks><p>
339 /// Merge does not copy over embedded cabinet files or embedded transforms from the
340 /// reference database into the target database. Embedded data streams that are listed in the
341 /// Binary table or Icon table are copied from the reference database to the target database.
342 /// Storage embedded in the reference database are not copied to the target database.
343 /// </p><p>
344 /// The Merge method merges the data of two databases. These databases must have the same
345 /// codepage. The merge fails if any tables or rows in the databases conflict. A conflict exists
346 /// if the data in any row in the first database differs from the data in the corresponding row
347 /// of the second database. Corresponding rows are in the same table of both databases and have
348 /// the same primary key in both databases. The tables of non-conflicting databases must have
349 /// the same number of primary keys, same number of columns, same column types, same column names,
350 /// and the same data in rows with identical primary keys. Temporary columns however don't matter
351 /// in the column count and corresponding tables can have a different number of temporary columns
352 /// without creating conflict as long as the persistent columns match.
353 /// </p><p>
354 /// If the number, type, or name of columns in corresponding tables are different, the
355 /// schema of the two databases are incompatible and the installer will stop processing tables
356 /// and the merge fails. The installer checks that the two databases have the same schema before
357 /// checking for row merge conflicts. If the schemas are incompatible, the databases have be
358 /// modified.
359 /// </p><p>
360 /// If the data in particular rows differ, this is a row merge conflict, the merge fails
361 /// and creates a new table with the specified name. The first column of this table is the name
362 /// of the table having the conflict. The second column gives the number of rows in the table
363 /// having the conflict.
364 /// </p><p>
365 /// Win32 MSI API:
366 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabasemerge.asp">MsiDatabaseMerge</a>
367 /// </p></remarks>
368 [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
369 public void Merge(Database otherDatabase, string errorTable)
370 {
371 if (otherDatabase == null)
372 {
373 throw new ArgumentNullException("otherDatabase");
374 }
375
376 uint ret = NativeMethods.MsiDatabaseMerge((int) this.Handle, (int) otherDatabase.Handle, errorTable);
377 if (ret != 0)
378 {
379 if (ret == (uint) NativeMethods.Error.FUNCTION_FAILED)
380 {
381 throw new MergeException(this, errorTable);
382 }
383 else if (ret == (uint) NativeMethods.Error.DATATYPE_MISMATCH)
384 {
385 throw new MergeException("Schema difference between the two databases.");
386 }
387 else
388 {
389 throw InstallerException.ExceptionFromReturnCode(ret);
390 }
391 }
392 }
393
394 /// <summary>
395 /// Merges another database with this database.
396 /// </summary>
397 /// <param name="otherDatabase">The database to be merged into this database</param>
398 /// <exception cref="MergeException">merge failed due to a schema difference or data conflict</exception>
399 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
400 /// <remarks><p>
401 /// MsiDatabaseMerge does not copy over embedded cabinet files or embedded transforms from
402 /// the reference database into the target database. Embedded data streams that are listed in
403 /// the Binary table or Icon table are copied from the reference database to the target database.
404 /// Storage embedded in the reference database are not copied to the target database.
405 /// </p><p>
406 /// The Merge method merges the data of two databases. These databases must have the same
407 /// codepage. The merge fails if any tables or rows in the databases conflict. A conflict exists
408 /// if the data in any row in the first database differs from the data in the corresponding row
409 /// of the second database. Corresponding rows are in the same table of both databases and have
410 /// the same primary key in both databases. The tables of non-conflicting databases must have
411 /// the same number of primary keys, same number of columns, same column types, same column names,
412 /// and the same data in rows with identical primary keys. Temporary columns however don't matter
413 /// in the column count and corresponding tables can have a different number of temporary columns
414 /// without creating conflict as long as the persistent columns match.
415 /// </p><p>
416 /// If the number, type, or name of columns in corresponding tables are different, the
417 /// schema of the two databases are incompatible and the installer will stop processing tables
418 /// and the merge fails. The installer checks that the two databases have the same schema before
419 /// checking for row merge conflicts. If the schemas are incompatible, the databases have be
420 /// modified.
421 /// </p><p>
422 /// Win32 MSI API:
423 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabasemerge.asp">MsiDatabaseMerge</a>
424 /// </p></remarks>
425 public void Merge(Database otherDatabase) { this.Merge(otherDatabase, null); }
426
427 /// <summary>
428 /// Checks whether a table exists and is persistent in the database.
429 /// </summary>
430 /// <param name="table">The table to the checked</param>
431 /// <returns>true if the table exists and is persistent in the database; false otherwise</returns>
432 /// <exception cref="ArgumentException">the table is unknown</exception>
433 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
434 /// <remarks><p>
435 /// To check whether a table exists regardless of persistence,
436 /// use <see cref="TableCollection.Contains"/>.
437 /// </p><p>
438 /// Win32 MSI API:
439 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseistablepersistent.asp">MsiDatabaseIsTablePersistent</a>
440 /// </p></remarks>
441 public bool IsTablePersistent(string table)
442 {
443 if (String.IsNullOrEmpty(table))
444 {
445 throw new ArgumentNullException("table");
446 }
447 uint ret = RemotableNativeMethods.MsiDatabaseIsTablePersistent((int) this.Handle, table);
448 if (ret == 3) // MSICONDITION_ERROR
449 {
450 throw new InstallerException();
451 }
452 return ret == 1;
453 }
454
455 /// <summary>
456 /// Checks whether a table contains a persistent column with a given name.
457 /// </summary>
458 /// <param name="table">The table to the checked</param>
459 /// <param name="column">The name of the column to be checked</param>
460 /// <returns>true if the column exists in the table; false if the column is temporary or does not exist.</returns>
461 /// <exception cref="InstallerException">the View could not be executed</exception>
462 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
463 /// <remarks><p>
464 /// To check whether a column exists regardless of persistence,
465 /// use <see cref="ColumnCollection.Contains"/>.
466 /// </p></remarks>
467 public bool IsColumnPersistent(string table, string column)
468 {
469 if (String.IsNullOrEmpty(table))
470 {
471 throw new ArgumentNullException("table");
472 }
473 if (String.IsNullOrEmpty(column))
474 {
475 throw new ArgumentNullException("column");
476 }
477 using (View view = this.OpenView(
478 "SELECT `Number` FROM `_Columns` WHERE `Table` = '{0}' AND `Name` = '{1}'", table, column))
479 {
480 view.Execute();
481 using (Record rec = view.Fetch())
482 {
483 return (rec != null);
484 }
485 }
486 }
487
488 /// <summary>
489 /// Gets the count of all rows in the table.
490 /// </summary>
491 /// <param name="table">Name of the table whose rows are to be counted</param>
492 /// <returns>The count of all rows in the table</returns>
493 /// <exception cref="InstallerException">the View could not be executed</exception>
494 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
495 public int CountRows(string table)
496 {
497 return this.CountRows(table, null);
498 }
499
500 /// <summary>
501 /// Gets the count of all rows in the table that satisfy a given condition.
502 /// </summary>
503 /// <param name="table">Name of the table whose rows are to be counted</param>
504 /// <param name="where">Conditional expression, such as could be placed on the end of a SQL WHERE clause</param>
505 /// <returns>The count of all rows in the table satisfying the condition</returns>
506 /// <exception cref="BadQuerySyntaxException">the SQL WHERE syntax is invalid</exception>
507 /// <exception cref="InstallerException">the View could not be executed</exception>
508 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
509 public int CountRows(string table, string where)
510 {
511 if (String.IsNullOrEmpty(table))
512 {
513 throw new ArgumentNullException("table");
514 }
515
516 // to support temporary tables like _Streams, run the query even if the table isn't persistent
517 TableInfo tableInfo = this.Tables[table];
518 string primaryKeys = tableInfo == null ? "*" : String.Concat("`", tableInfo.PrimaryKeys[0], "`");
519 int count;
520
521 try
522 {
523 using (View view = this.OpenView(
524 "SELECT {0} FROM `{1}`{2}",
525 primaryKeys,
526 table,
527 (where != null && where.Length != 0 ? " WHERE " + where : "")))
528 {
529 view.Execute();
530 for (count = 0; ; count++)
531 {
532 // Avoid creating unnecessary Record objects by not calling View.Fetch().
533 int recordHandle;
534 uint ret = RemotableNativeMethods.MsiViewFetch((int)view.Handle, out recordHandle);
535 if (ret == (uint)NativeMethods.Error.NO_MORE_ITEMS)
536 {
537 break;
538 }
539
540 if (ret != 0)
541 {
542 throw InstallerException.ExceptionFromReturnCode(ret);
543 }
544
545 RemotableNativeMethods.MsiCloseHandle(recordHandle);
546 }
547 }
548 }
549 catch (BadQuerySyntaxException)
550 {
551 // table was missing
552 count = 0;
553 }
554
555 return count;
556 }
557
558 /// <summary>
559 /// Finalizes the persistent form of the database. All persistent data is written
560 /// to the writeable database, and no temporary columns or rows are written.
561 /// </summary>
562 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
563 /// <remarks><p>
564 /// For a database open in <see cref="DatabaseOpenMode.ReadOnly"/> mode, this method has no effect.
565 /// </p><p>
566 /// For a database open in <see cref="DatabaseOpenMode.CreateDirect" /> or <see cref="DatabaseOpenMode.Direct" />
567 /// mode, it is not necessary to call this method because the database will be automatically committed
568 /// when it is closed. However this method may be called at any time to persist the current state of tables
569 /// loaded into memory.
570 /// </p><p>
571 /// For a database open in <see cref="DatabaseOpenMode.Create" /> or <see cref="DatabaseOpenMode.Transact" />
572 /// mode, no changes will be persisted until this method is called. If the database object is closed without
573 /// calling this method, the database file remains unmodified.
574 /// </p><p>
575 /// Win32 MSI API:
576 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabasecommit.asp">MsiDatabaseCommit</a>
577 /// </p></remarks>
578 public void Commit()
579 {
580 if (this.summaryInfo != null && !this.summaryInfo.IsClosed)
581 {
582 this.summaryInfo.Persist();
583 this.summaryInfo.Close();
584 this.summaryInfo = null;
585 }
586 uint ret = NativeMethods.MsiDatabaseCommit((int) this.Handle);
587 if (ret != 0)
588 {
589 throw InstallerException.ExceptionFromReturnCode(ret);
590 }
591 }
592
593 /// <summary>
594 /// Copies the structure and data from a specified table to a text archive file.
595 /// </summary>
596 /// <param name="table">Name of the table to be exported</param>
597 /// <param name="exportFilePath">Path to the file to be created</param>
598 /// <exception cref="FileNotFoundException">the file path is invalid</exception>
599 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
600 /// <remarks><p>
601 /// Win32 MSI API:
602 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseexport.asp">MsiDatabaseExport</a>
603 /// </p></remarks>
604 public void Export(string table, string exportFilePath)
605 {
606 if (table == null)
607 {
608 throw new ArgumentNullException("table");
609 }
610
611 FileInfo file = new FileInfo(exportFilePath);
612 uint ret = NativeMethods.MsiDatabaseExport((int) this.Handle, table, file.DirectoryName, file.Name);
613 if (ret != 0)
614 {
615 if (ret == (uint) NativeMethods.Error.BAD_PATHNAME)
616 {
617 throw new FileNotFoundException(null, exportFilePath);
618 }
619 else
620 {
621 throw InstallerException.ExceptionFromReturnCode(ret);
622 }
623 }
624 }
625
626 /// <summary>
627 /// Imports a database table from a text archive file, dropping any existing table.
628 /// </summary>
629 /// <param name="importFilePath">Path to the file to be imported.
630 /// The table name is specified within the file.</param>
631 /// <exception cref="FileNotFoundException">the file path is invalid</exception>
632 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
633 /// <remarks><p>
634 /// Win32 MSI API:
635 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseimport.asp">MsiDatabaseImport</a>
636 /// </p></remarks>
637 public void Import(string importFilePath)
638 {
639 if (String.IsNullOrEmpty(importFilePath))
640 {
641 throw new ArgumentNullException("importFilePath");
642 }
643
644 FileInfo file = new FileInfo(importFilePath);
645 uint ret = NativeMethods.MsiDatabaseImport((int) this.Handle, file.DirectoryName, file.Name);
646 if (ret != 0)
647 {
648 if (ret == (uint) NativeMethods.Error.BAD_PATHNAME)
649 {
650 throw new FileNotFoundException(null, importFilePath);
651 }
652 else
653 {
654 throw InstallerException.ExceptionFromReturnCode(ret);
655 }
656 }
657 }
658
659 /// <summary>
660 /// Exports all database tables, streams, and summary information to archive files.
661 /// </summary>
662 /// <param name="directoryPath">Path to the directory where archive files will be created</param>
663 /// <exception cref="FileNotFoundException">the directory path is invalid</exception>
664 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
665 /// <remarks><p>
666 /// The directory will be created if it does not already exist.
667 /// </p><p>
668 /// Win32 MSI API:
669 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseexport.asp">MsiDatabaseExport</a>
670 /// </p></remarks>
671 public void ExportAll(string directoryPath)
672 {
673 if (String.IsNullOrEmpty(directoryPath))
674 {
675 throw new ArgumentNullException("directoryPath");
676 }
677
678 if (!Directory.Exists(directoryPath))
679 {
680 Directory.CreateDirectory(directoryPath);
681 }
682
683 this.Export("_SummaryInformation", Path.Combine(directoryPath, "_SummaryInformation.idt"));
684
685 using (View view = this.OpenView("SELECT `Name` FROM `_Tables`"))
686 {
687 view.Execute();
688
689 foreach (Record rec in view) using (rec)
690 {
691 string table = (string) rec[1];
692
693 this.Export(table, Path.Combine(directoryPath, table + ".idt"));
694 }
695 }
696
697 if (!Directory.Exists(Path.Combine(directoryPath, "_Streams")))
698 {
699 Directory.CreateDirectory(Path.Combine(directoryPath, "_Streams"));
700 }
701
702 using (View view = this.OpenView("SELECT `Name`, `Data` FROM `_Streams`"))
703 {
704 view.Execute();
705
706 foreach (Record rec in view) using (rec)
707 {
708 string stream = (string) rec[1];
709 if (stream.EndsWith("SummaryInformation", StringComparison.Ordinal)) continue;
710
711 int i = stream.IndexOf('.');
712 if (i >= 0)
713 {
714 if (File.Exists(Path.Combine(
715 directoryPath,
716 Path.Combine(stream.Substring(0, i), stream.Substring(i + 1) + ".ibd"))))
717 {
718 continue;
719 }
720 }
721 rec.GetStream(2, Path.Combine(directoryPath, Path.Combine("_Streams", stream)));
722 }
723 }
724 }
725
726 /// <summary>
727 /// Imports all database tables, streams, and summary information from archive files.
728 /// </summary>
729 /// <param name="directoryPath">Path to the directory from which archive files will be imported</param>
730 /// <exception cref="FileNotFoundException">the directory path is invalid</exception>
731 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
732 /// <remarks><p>
733 /// Win32 MSI API:
734 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseimport.asp">MsiDatabaseImport</a>
735 /// </p></remarks>
736 public void ImportAll(string directoryPath)
737 {
738 if (String.IsNullOrEmpty(directoryPath))
739 {
740 throw new ArgumentNullException("directoryPath");
741 }
742
743 if (File.Exists(Path.Combine(directoryPath, "_SummaryInformation.idt")))
744 {
745 this.Import(Path.Combine(directoryPath, "_SummaryInformation.idt"));
746 }
747
748 string[] idtFiles = Directory.GetFiles(directoryPath, "*.idt");
749 foreach (string file in idtFiles)
750 {
751 if (Path.GetFileName(file) != "_SummaryInformation.idt")
752 {
753 this.Import(file);
754 }
755 }
756
757 if (Directory.Exists(Path.Combine(directoryPath, "_Streams")))
758 {
759 View view = this.OpenView("SELECT `Name`, `Data` FROM `_Streams`");
760 Record rec = null;
761 try
762 {
763 view.Execute();
764 string[] streamFiles = Directory.GetFiles(Path.Combine(directoryPath, "_Streams"));
765 foreach (string file in streamFiles)
766 {
767 rec = this.CreateRecord(2);
768 rec[1] = Path.GetFileName(file);
769 rec.SetStream(2, file);
770 view.Insert(rec);
771 rec.Close();
772 rec = null;
773 }
774 }
775 finally
776 {
777 if (rec != null) rec.Close();
778 view.Close();
779 }
780 }
781 }
782
783 /// <summary>
784 /// Creates a new record object with the requested number of fields.
785 /// </summary>
786 /// <param name="fieldCount">Required number of fields, which may be 0.
787 /// The maximum number of fields in a record is limited to 65535.</param>
788 /// <returns>A new record object that can be used with the database.</returns>
789 /// <remarks><p>
790 /// This method is equivalent to directly calling the <see cref="Record" />
791 /// constructor in all cases outside of a custom action context. When in a
792 /// custom action session, this method allows creation of a record that can
793 /// work with a database other than the session database.
794 /// </p><p>
795 /// The Record object should be <see cref="InstallerHandle.Close"/>d after use.
796 /// It is best that the handle be closed manually as soon as it is no longer
797 /// needed, as leaving lots of unused handles open can degrade performance.
798 /// </p><p>
799 /// Win32 MSI API:
800 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msicreaterecord.asp">MsiCreateRecord</a>
801 /// </p></remarks>
802 public Record CreateRecord(int fieldCount)
803 {
804 int hRecord = RemotableNativeMethods.MsiCreateRecord((uint) fieldCount, (int) this.Handle);
805 return new Record((IntPtr) hRecord, true, (View) null);
806 }
807
808 /// <summary>
809 /// Returns the file path of this database, or the handle value if a file path was not specified.
810 /// </summary>
811 public override string ToString()
812 {
813 if (this.FilePath != null)
814 {
815 return this.FilePath;
816 }
817 else
818 {
819 return "#" + ((int) this.Handle).ToString(CultureInfo.InvariantCulture);
820 }
821 }
822
823 /// <summary>
824 /// Closes the database handle. After closing a handle, further method calls may throw <see cref="InvalidHandleException"/>.
825 /// </summary>
826 /// <param name="disposing">If true, the method has been called directly or
827 /// indirectly by a user's code, so managed and unmanaged resources will be
828 /// disposed. If false, only unmanaged resources will be disposed.</param>
829 protected override void Dispose(bool disposing)
830 {
831 if (!this.IsClosed &&
832 (this.OpenMode == DatabaseOpenMode.CreateDirect ||
833 this.OpenMode == DatabaseOpenMode.Direct))
834 {
835 // Always commit a direct-opened database before closing.
836 // This avoids unexpected corruption of the database.
837 this.Commit();
838 }
839
840 base.Dispose(disposing);
841
842 if (disposing)
843 {
844 if (this.summaryInfo != null)
845 {
846 this.summaryInfo.Close();
847 this.summaryInfo = null;
848 }
849
850 if (this.deleteOnClose != null)
851 {
852 foreach (string path in this.deleteOnClose)
853 {
854 try
855 {
856 if (Directory.Exists(path))
857 {
858 Directory.Delete(path, true);
859 }
860 else
861 {
862 if (File.Exists(path)) File.Delete(path);
863 }
864 }
865 catch (IOException)
866 {
867 }
868 catch (UnauthorizedAccessException)
869 {
870 }
871 }
872 this.deleteOnClose = null;
873 }
874 }
875 }
876
877 private static int Open(string filePath, string outputPath)
878 {
879 if (String.IsNullOrEmpty(filePath))
880 {
881 throw new ArgumentNullException("filePath");
882 }
883
884 if (String.IsNullOrEmpty(outputPath))
885 {
886 throw new ArgumentNullException("outputPath");
887 }
888
889 int hDb;
890 uint ret = NativeMethods.MsiOpenDatabase(filePath, outputPath, out hDb);
891 if (ret != 0)
892 {
893 throw InstallerException.ExceptionFromReturnCode(ret);
894 }
895 return hDb;
896 }
897
898 private static int Open(string filePath, DatabaseOpenMode mode)
899 {
900 if (String.IsNullOrEmpty(filePath))
901 {
902 throw new ArgumentNullException("filePath");
903 }
904
905 if (Path.GetExtension(filePath).Equals(".msp", StringComparison.Ordinal))
906 {
907 const int DATABASEOPENMODE_PATCH = 32;
908 int patchMode = (int) mode | DATABASEOPENMODE_PATCH;
909 mode = (DatabaseOpenMode) patchMode;
910 }
911
912 int hDb;
913 uint ret = NativeMethods.MsiOpenDatabase(filePath, (IntPtr) mode, out hDb);
914 if (ret != 0)
915 {
916 throw InstallerException.ExceptionFromReturnCode(
917 ret,
918 String.Format(CultureInfo.InvariantCulture, "Database=\"{0}\"", filePath));
919 }
920 return hDb;
921 }
922
923 /// <summary>
924 /// Returns the value of the specified property.
925 /// </summary>
926 /// <param name="property">Name of the property to retrieve.</param>
927 public string ExecutePropertyQuery(string property)
928 {
929 IList<string> values = this.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = '{0}'", property);
930 return (values.Count > 0 ? values[0] : null);
931 }
932 }
933}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/DatabaseQuery.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/DatabaseQuery.cs
new file mode 100644
index 00000000..7c9e011e
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/DatabaseQuery.cs
@@ -0,0 +1,412 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Globalization;
9 using System.Diagnostics.CodeAnalysis;
10
11 public partial class Database
12 {
13 /// <summary>
14 /// Gets a View object representing the query specified by a SQL string.
15 /// </summary>
16 /// <param name="sqlFormat">SQL query string, which may contain format items</param>
17 /// <param name="args">Zero or more objects to format</param>
18 /// <returns>A View object representing the query specified by a SQL string</returns>
19 /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
20 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
21 /// <remarks><p>
22 /// The <paramref name="sqlFormat"/> parameter is formatted using <see cref="String.Format(string,object[])"/>.
23 /// </p><p>
24 /// The View object should be <see cref="InstallerHandle.Close"/>d after use.
25 /// It is best that the handle be closed manually as soon as it is no longer
26 /// needed, as leaving lots of unused handles open can degrade performance.
27 /// </p><p>
28 /// Win32 MSI API:
29 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>
30 /// </p></remarks>
31 public View OpenView(string sqlFormat, params object[] args)
32 {
33 if (String.IsNullOrEmpty(sqlFormat))
34 {
35 throw new ArgumentNullException("sqlFormat");
36 }
37
38 string sql = (args == null || args.Length == 0 ? sqlFormat :
39 String.Format(CultureInfo.InvariantCulture, sqlFormat, args));
40 int viewHandle;
41 uint ret = RemotableNativeMethods.MsiDatabaseOpenView((int) this.Handle, sql, out viewHandle);
42 if (ret != 0)
43 {
44 throw InstallerException.ExceptionFromReturnCode(ret);
45 }
46
47 return new View((IntPtr) viewHandle, sql, this);
48 }
49
50 /// <summary>
51 /// Executes the query specified by a SQL string. The query may not be a SELECT statement.
52 /// </summary>
53 /// <param name="sqlFormat">SQL query string, which may contain format items</param>
54 /// <param name="args">Zero or more objects to format</param>
55 /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
56 /// <exception cref="InstallerException">the View could not be executed</exception>
57 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
58 /// <remarks><p>
59 /// The <paramref name="sqlFormat"/> parameter is formatted using
60 /// <see cref="String.Format(string,object[])"/>.
61 /// </p><p>
62 /// Win32 MSI APIs:
63 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
64 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>
65 /// </p></remarks>
66 public void Execute(string sqlFormat, params object[] args)
67 {
68 if (String.IsNullOrEmpty(sqlFormat))
69 {
70 throw new ArgumentNullException("sqlFormat");
71 }
72
73 this.Execute(
74 args == null || args.Length == 0 ?
75 sqlFormat : String.Format(CultureInfo.InvariantCulture, sqlFormat, args),
76 (Record) null);
77 }
78
79 /// <summary>
80 /// Executes the query specified by a SQL string. The query may not be a SELECT statement.
81 /// </summary>
82 /// <param name="sql">SQL query string</param>
83 /// <param name="record">Optional Record object containing the values that replace
84 /// the parameter tokens (?) in the SQL query.</param>
85 /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
86 /// <exception cref="InstallerException">the View could not be executed</exception>
87 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
88 /// <remarks><p>
89 /// Win32 MSI APIs:
90 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
91 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>
92 /// </p></remarks>
93 public void Execute(string sql, Record record)
94 {
95 if (String.IsNullOrEmpty(sql))
96 {
97 throw new ArgumentNullException("sql");
98 }
99
100 using (View view = this.OpenView(sql))
101 {
102 view.Execute(record);
103 }
104 }
105
106 /// <summary>
107 /// Executes the specified SQL SELECT query and returns all results.
108 /// </summary>
109 /// <param name="sqlFormat">SQL query string, which may contain format items</param>
110 /// <param name="args">Zero or more objects to format</param>
111 /// <returns>All results combined into an array</returns>
112 /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
113 /// <exception cref="InstallerException">the View could not be executed</exception>
114 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
115 /// <remarks><p>
116 /// The <paramref name="sqlFormat"/> parameter is formatted using
117 /// <see cref="String.Format(string,object[])"/>.
118 /// </p><p>
119 /// Multiple rows columns will be collapsed into a single one-dimensional list.
120 /// </p><p>
121 /// Win32 MSI APIs:
122 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
123 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>,
124 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
125 /// </p></remarks>
126 public IList ExecuteQuery(string sqlFormat, params object[] args)
127 {
128 if (String.IsNullOrEmpty(sqlFormat))
129 {
130 throw new ArgumentNullException("sqlFormat");
131 }
132
133 return this.ExecuteQuery(
134 args == null || args.Length == 0 ?
135 sqlFormat : String.Format(CultureInfo.InvariantCulture, sqlFormat, args),
136 (Record) null);
137 }
138
139 /// <summary>
140 /// Executes the specified SQL SELECT query and returns all results.
141 /// </summary>
142 /// <param name="sql">SQL SELECT query string</param>
143 /// <param name="record">Optional Record object containing the values that replace
144 /// the parameter tokens (?) in the SQL query.</param>
145 /// <returns>All results combined into an array</returns>
146 /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
147 /// <exception cref="InstallerException">the View could not be executed</exception>
148 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
149 /// <remarks><p>
150 /// Multiple rows columns will be collapsed into a single one-dimensional list.
151 /// </p><p>
152 /// Win32 MSI APIs:
153 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
154 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>,
155 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
156 /// </p></remarks>
157 public IList ExecuteQuery(string sql, Record record)
158 {
159 if (String.IsNullOrEmpty(sql))
160 {
161 throw new ArgumentNullException("sql");
162 }
163
164 using (View view = this.OpenView(sql))
165 {
166 view.Execute(record);
167 IList results = new ArrayList();
168 int fieldCount = 0;
169
170 foreach (Record rec in view) using (rec)
171 {
172 if (fieldCount == 0) fieldCount = rec.FieldCount;
173 for (int i = 1; i <= fieldCount; i++)
174 {
175 results.Add(rec[i]);
176 }
177 }
178
179 return results;
180 }
181 }
182
183 /// <summary>
184 /// Executes the specified SQL SELECT query and returns all results as integers.
185 /// </summary>
186 /// <param name="sqlFormat">SQL query string, which may contain format items</param>
187 /// <param name="args">Zero or more objects to format</param>
188 /// <returns>All results combined into an array</returns>
189 /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
190 /// <exception cref="InstallerException">the View could not be executed</exception>
191 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
192 /// <remarks><p>
193 /// The <paramref name="sqlFormat"/> parameter is formatted using
194 /// <see cref="String.Format(string,object[])"/>.
195 /// </p><p>
196 /// Multiple rows columns will be collapsed into a single one-dimensional list.
197 /// </p><p>
198 /// Win32 MSI APIs:
199 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
200 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>,
201 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
202 /// </p></remarks>
203 public IList<int> ExecuteIntegerQuery(string sqlFormat, params object[] args)
204 {
205 if (String.IsNullOrEmpty(sqlFormat))
206 {
207 throw new ArgumentNullException("sqlFormat");
208 }
209
210 return this.ExecuteIntegerQuery(
211 args == null || args.Length == 0 ?
212 sqlFormat : String.Format(CultureInfo.InvariantCulture, sqlFormat, args),
213 (Record) null);
214 }
215
216 /// <summary>
217 /// Executes the specified SQL SELECT query and returns all results as integers.
218 /// </summary>
219 /// <param name="sql">SQL SELECT query string</param>
220 /// <param name="record">Optional Record object containing the values that replace
221 /// the parameter tokens (?) in the SQL query.</param>
222 /// <returns>All results combined into an array</returns>
223 /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
224 /// <exception cref="InstallerException">the View could not be executed</exception>
225 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
226 /// <remarks><p>
227 /// Multiple rows columns will be collapsed into a single one-dimensional list.
228 /// </p><p>
229 /// Win32 MSI APIs:
230 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
231 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>,
232 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
233 /// </p></remarks>
234 public IList<int> ExecuteIntegerQuery(string sql, Record record)
235 {
236 if (String.IsNullOrEmpty(sql))
237 {
238 throw new ArgumentNullException("sql");
239 }
240
241 using (View view = this.OpenView(sql))
242 {
243 view.Execute(record);
244 IList<int> results = new List<int>();
245 int fieldCount = 0;
246
247 foreach (Record rec in view) using (rec)
248 {
249 if (fieldCount == 0) fieldCount = rec.FieldCount;
250 for (int i = 1; i <= fieldCount; i++)
251 {
252 results.Add(rec.GetInteger(i));
253 }
254 }
255
256 return results;
257 }
258 }
259
260 /// <summary>
261 /// Executes the specified SQL SELECT query and returns all results as strings.
262 /// </summary>
263 /// <param name="sqlFormat">SQL query string, which may contain format items</param>
264 /// <param name="args">Zero or more objects to format</param>
265 /// <returns>All results combined into an array</returns>
266 /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
267 /// <exception cref="InstallerException">the View could not be executed</exception>
268 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
269 /// <remarks><p>
270 /// The <paramref name="sqlFormat"/> parameter is formatted using
271 /// <see cref="String.Format(string,object[])"/>.
272 /// </p><p>
273 /// Multiple rows columns will be collapsed into a single on-dimensional list.
274 /// </p><p>
275 /// Win32 MSI APIs:
276 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
277 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>,
278 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
279 /// </p></remarks>
280 public IList<string> ExecuteStringQuery(string sqlFormat, params object[] args)
281 {
282 if (String.IsNullOrEmpty(sqlFormat))
283 {
284 throw new ArgumentNullException("sqlFormat");
285 }
286
287 return this.ExecuteStringQuery(
288 args == null || args.Length == 0 ?
289 sqlFormat : String.Format(CultureInfo.InvariantCulture, sqlFormat, args),
290 (Record) null);
291 }
292
293 /// <summary>
294 /// Executes the specified SQL SELECT query and returns all results as strings.
295 /// </summary>
296 /// <param name="sql">SQL SELECT query string</param>
297 /// <param name="record">Optional Record object containing the values that replace
298 /// the parameter tokens (?) in the SQL query.</param>
299 /// <returns>All results combined into an array</returns>
300 /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
301 /// <exception cref="InstallerException">the View could not be executed</exception>
302 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
303 /// <remarks><p>
304 /// Multiple rows columns will be collapsed into a single on-dimensional list.
305 /// </p><p>
306 /// Win32 MSI APIs:
307 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
308 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>,
309 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
310 /// </p></remarks>
311 public IList<string> ExecuteStringQuery(string sql, Record record)
312 {
313 if (String.IsNullOrEmpty(sql))
314 {
315 throw new ArgumentNullException("sql");
316 }
317
318 using (View view = this.OpenView(sql))
319 {
320 view.Execute(record);
321 IList<string> results = new List<string>();
322 int fieldCount = 0;
323
324 foreach (Record rec in view) using (rec)
325 {
326 if (fieldCount == 0) fieldCount = rec.FieldCount;
327 for (int i = 1; i <= fieldCount; i++)
328 {
329 results.Add(rec.GetString(i));
330 }
331 }
332
333 return results;
334 }
335 }
336
337 /// <summary>
338 /// Executes the specified SQL SELECT query and returns a single result.
339 /// </summary>
340 /// <param name="sqlFormat">SQL query string, which may contain format items</param>
341 /// <param name="args">Zero or more objects to format</param>
342 /// <returns>First field of the first result</returns>
343 /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
344 /// <exception cref="InstallerException">the View could not be executed
345 /// or the query returned 0 results</exception>
346 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
347 /// <remarks><p>
348 /// The <paramref name="sqlFormat"/> parameter is formatted using
349 /// <see cref="String.Format(string,object[])"/>.
350 /// </p><p>
351 /// Win32 MSI APIs:
352 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
353 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>,
354 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
355 /// </p></remarks>
356 public object ExecuteScalar(string sqlFormat, params object[] args)
357 {
358 if (String.IsNullOrEmpty(sqlFormat))
359 {
360 throw new ArgumentNullException("sqlFormat");
361 }
362
363 return this.ExecuteScalar(
364 args == null || args.Length == 0 ?
365 sqlFormat : String.Format(CultureInfo.InvariantCulture, sqlFormat, args),
366 (Record) null);
367 }
368
369 /// <summary>
370 /// Executes the specified SQL SELECT query and returns a single result.
371 /// </summary>
372 /// <param name="sql">SQL SELECT query string</param>
373 /// <param name="record">Optional Record object containing the values that replace
374 /// the parameter tokens (?) in the SQL query.</param>
375 /// <returns>First field of the first result</returns>
376 /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
377 /// <exception cref="InstallerException">the View could not be executed
378 /// or the query returned 0 results</exception>
379 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
380 /// <remarks><p>
381 /// Win32 MSI APIs:
382 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
383 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>,
384 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
385 /// </p></remarks>
386 public object ExecuteScalar(string sql, Record record)
387 {
388 if (String.IsNullOrEmpty(sql))
389 {
390 throw new ArgumentNullException("sql");
391 }
392
393 View view = this.OpenView(sql);
394 Record rec = null;
395 try
396 {
397 view.Execute(record);
398 rec = view.Fetch();
399 if (rec == null)
400 {
401 throw InstallerException.ExceptionFromReturnCode((uint) NativeMethods.Error.NO_MORE_ITEMS);
402 }
403 return rec[1];
404 }
405 finally
406 {
407 if (rec != null) rec.Close();
408 view.Close();
409 }
410 }
411 }
412}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/DatabaseTransform.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/DatabaseTransform.cs
new file mode 100644
index 00000000..fa843012
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/DatabaseTransform.cs
@@ -0,0 +1,278 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.IO;
7 using System.Globalization;
8 using System.Diagnostics.CodeAnalysis;
9
10 public partial class Database
11 {
12 /// <summary>
13 /// Creates a transform that, when applied to the object database, results in the reference database.
14 /// </summary>
15 /// <param name="referenceDatabase">Database that does not include the changes</param>
16 /// <param name="transformFile">Name of the generated transform file, or null to only
17 /// check whether or not the two database are identical</param>
18 /// <returns>true if a transform is generated, or false if a transform is not generated
19 /// because there are no differences between the two databases.</returns>
20 /// <exception cref="InstallerException">the transform could not be generated</exception>
21 /// <exception cref="InvalidHandleException">a Database handle is invalid</exception>
22 /// <remarks><p>
23 /// A transform can add non-primary key columns to the end of a table. A transform cannot
24 /// be created that adds primary key columns to a table. A transform cannot be created that
25 /// changes the order, names, or definitions of columns.
26 /// </p><p>
27 /// If the transform is to be applied during an installation you must use the
28 /// <see cref="Database.CreateTransformSummaryInfo"/> method to populate the
29 /// summary information stream.
30 /// </p><p>
31 /// Win32 MSI API:
32 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabasegeneratetransform.asp">MsiDatabaseGenerateTransform</a>
33 /// </p></remarks>
34 [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
35 public bool GenerateTransform(Database referenceDatabase, string transformFile)
36 {
37 if (referenceDatabase == null)
38 {
39 throw new ArgumentNullException("referenceDatabase");
40 }
41
42 if (String.IsNullOrEmpty(transformFile))
43 {
44 throw new ArgumentNullException("transformFile");
45 }
46
47 uint ret = NativeMethods.MsiDatabaseGenerateTransform((int) this.Handle, (int) referenceDatabase.Handle, transformFile, 0, 0);
48 if (ret == (uint) NativeMethods.Error.NO_DATA)
49 {
50 return false;
51 }
52 else if (ret != 0)
53 {
54 throw InstallerException.ExceptionFromReturnCode(ret);
55 }
56 return true;
57 }
58
59 /// <summary>
60 /// Creates and populates the summary information stream of an existing transform file, and
61 /// fills in the properties with the base and reference ProductCode and ProductVersion.
62 /// </summary>
63 /// <param name="referenceDatabase">Database that does not include the changes</param>
64 /// <param name="transformFile">Name of the generated transform file</param>
65 /// <param name="errors">Error conditions that should be suppressed
66 /// when the transform is applied</param>
67 /// <param name="validations">Defines which properties should be validated
68 /// to verify that this transform can be applied to a database.</param>
69 /// <exception cref="InstallerException">the transform summary info could not be
70 /// generated</exception>
71 /// <exception cref="InvalidHandleException">a Database handle is invalid</exception>
72 /// <remarks><p>
73 /// Win32 MSI API:
74 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msicreatetransformsummaryinfo.asp">MsiCreateTransformSummaryInfo</a>
75 /// </p></remarks>
76 [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
77 public void CreateTransformSummaryInfo(
78 Database referenceDatabase,
79 string transformFile,
80 TransformErrors errors,
81 TransformValidations validations)
82 {
83 if (referenceDatabase == null)
84 {
85 throw new ArgumentNullException("referenceDatabase");
86 }
87
88 if (String.IsNullOrEmpty(transformFile))
89 {
90 throw new ArgumentNullException("transformFile");
91 }
92
93 uint ret = NativeMethods.MsiCreateTransformSummaryInfo(
94 (int) this.Handle,
95 (int) referenceDatabase.Handle,
96 transformFile,
97 (int) errors,
98 (int) validations);
99 if (ret != 0)
100 {
101 throw InstallerException.ExceptionFromReturnCode(ret);
102 }
103 }
104
105 /// <summary>
106 /// Apply a transform to the database, recording the changes in the "_TransformView" table.
107 /// </summary>
108 /// <param name="transformFile">Path to the transform file</param>
109 /// <exception cref="InstallerException">the transform could not be applied</exception>
110 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
111 /// <remarks><p>
112 /// Win32 MSI API:
113 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseapplytransform.asp">MsiDatabaseApplyTransform</a>
114 /// </p></remarks>
115 public void ViewTransform(string transformFile)
116 {
117 TransformErrors transformErrors =
118 TransformErrors.AddExistingRow |
119 TransformErrors.DelMissingRow |
120 TransformErrors.AddExistingTable |
121 TransformErrors.DelMissingTable |
122 TransformErrors.UpdateMissingRow |
123 TransformErrors.ChangeCodePage |
124 TransformErrors.ViewTransform;
125 this.ApplyTransform(transformFile, transformErrors);
126 }
127
128 /// <summary>
129 /// Apply a transform to the database, suppressing any error conditions
130 /// specified by the transform's summary information.
131 /// </summary>
132 /// <param name="transformFile">Path to the transform file</param>
133 /// <exception cref="InstallerException">the transform could not be applied</exception>
134 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
135 /// <remarks><p>
136 /// Win32 MSI API:
137 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseapplytransform.asp">MsiDatabaseApplyTransform</a>
138 /// </p></remarks>
139 public void ApplyTransform(string transformFile)
140 {
141 if (String.IsNullOrEmpty(transformFile))
142 {
143 throw new ArgumentNullException("transformFile");
144 }
145
146 TransformErrors errorConditionsToSuppress;
147 using (SummaryInfo transformSummInfo = new SummaryInfo(transformFile, false))
148 {
149 int errorConditions = transformSummInfo.CharacterCount & 0xFFFF;
150 errorConditionsToSuppress = (TransformErrors) errorConditions;
151 }
152 this.ApplyTransform(transformFile, errorConditionsToSuppress);
153 }
154
155 /// <summary>
156 /// Apply a transform to the database, specifying error conditions to suppress.
157 /// </summary>
158 /// <param name="transformFile">Path to the transform file</param>
159 /// <param name="errorConditionsToSuppress">Error conditions that are to be suppressed</param>
160 /// <exception cref="InstallerException">the transform could not be applied</exception>
161 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
162 /// <remarks><p>
163 /// Win32 MSI API:
164 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseapplytransform.asp">MsiDatabaseApplyTransform</a>
165 /// </p></remarks>
166 public void ApplyTransform(string transformFile, TransformErrors errorConditionsToSuppress)
167 {
168 if (String.IsNullOrEmpty(transformFile))
169 {
170 throw new ArgumentNullException("transformFile");
171 }
172
173 uint ret = NativeMethods.MsiDatabaseApplyTransform((int) this.Handle, transformFile, (int) errorConditionsToSuppress);
174 if (ret != 0)
175 {
176 throw InstallerException.ExceptionFromReturnCode(ret);
177 }
178 }
179
180 /// <summary>
181 /// Checks whether a transform is valid for this Database, according to its validation data and flags.
182 /// </summary>
183 /// <param name="transformFile">Path to the transform file</param>
184 /// <returns>true if the transform can be validly applied to this Database; false otherwise</returns>
185 /// <exception cref="InstallerException">the transform could not be applied</exception>
186 /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
187 public bool IsTransformValid(string transformFile)
188 {
189 if (String.IsNullOrEmpty(transformFile))
190 {
191 throw new ArgumentNullException("transformFile");
192 }
193
194 using (SummaryInfo transformSummInfo = new SummaryInfo(transformFile, false))
195 {
196 return this.IsTransformValid(transformSummInfo);
197 }
198 }
199
200 /// <summary>
201 /// Checks whether a transform is valid for this Database, according to its SummaryInfo data.
202 /// </summary>
203 /// <param name="transformSummaryInfo">SummaryInfo data of a transform file</param>
204 /// <returns>true if the transform can be validly applied to this Database; false otherwise</returns>
205 /// <exception cref="InstallerException">error processing summary info</exception>
206 /// <exception cref="InvalidHandleException">the Database or SummaryInfo handle is invalid</exception>
207 public bool IsTransformValid(SummaryInfo transformSummaryInfo)
208 {
209 if (transformSummaryInfo == null)
210 {
211 throw new ArgumentNullException("transformSummaryInfo");
212 }
213
214 string[] rev = transformSummaryInfo.RevisionNumber.Split(new char[] { ';' }, 3);
215 string targetProductCode = rev[0].Substring(0, 38);
216 string targetProductVersion = rev[0].Substring(38);
217 string upgradeCode = rev[2];
218
219 string[] templ = transformSummaryInfo.Template.Split(new char[] { ';' }, 2);
220 int targetProductLanguage = 0;
221 if (templ.Length >= 2 && templ[1].Length > 0)
222 {
223 targetProductLanguage = Int32.Parse(templ[1], CultureInfo.InvariantCulture.NumberFormat);
224 }
225
226 int flags = transformSummaryInfo.CharacterCount;
227 int validateFlags = flags >> 16;
228
229 string thisProductCode = this.ExecutePropertyQuery("ProductCode");
230 string thisProductVersion = this.ExecutePropertyQuery("ProductVersion");
231 string thisUpgradeCode = this.ExecutePropertyQuery("UpgradeCode");
232 string thisProductLang = this.ExecutePropertyQuery("ProductLanguage");
233 int thisProductLanguage = 0;
234 if (!String.IsNullOrEmpty(thisProductLang))
235 {
236 thisProductLanguage = Int32.Parse(thisProductLang, CultureInfo.InvariantCulture.NumberFormat);
237 }
238
239 if ((validateFlags & (int) TransformValidations.Product) != 0 &&
240 thisProductCode != targetProductCode)
241 {
242 return false;
243 }
244
245 if ((validateFlags & (int) TransformValidations.UpgradeCode) != 0 &&
246 thisUpgradeCode != upgradeCode)
247 {
248 return false;
249 }
250
251 if ((validateFlags & (int) TransformValidations.Language) != 0 &&
252 targetProductLanguage != 0 && thisProductLanguage != targetProductLanguage)
253 {
254 return false;
255 }
256
257 Version thisProductVer = new Version(thisProductVersion);
258 Version targetProductVer = new Version(targetProductVersion);
259 if ((validateFlags & (int) TransformValidations.UpdateVersion) != 0)
260 {
261 if (thisProductVer.Major != targetProductVer.Major) return false;
262 if (thisProductVer.Minor != targetProductVer.Minor) return false;
263 if (thisProductVer.Build != targetProductVer.Build) return false;
264 }
265 else if ((validateFlags & (int) TransformValidations.MinorVersion) != 0)
266 {
267 if (thisProductVer.Major != targetProductVer.Major) return false;
268 if (thisProductVer.Minor != targetProductVer.Minor) return false;
269 }
270 else if ((validateFlags & (int) TransformValidations.MajorVersion) != 0)
271 {
272 if (thisProductVer.Major != targetProductVer.Major) return false;
273 }
274
275 return true;
276 }
277 }
278}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/EmbeddedUIProxy.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/EmbeddedUIProxy.cs
new file mode 100644
index 00000000..05e910d4
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/EmbeddedUIProxy.cs
@@ -0,0 +1,231 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Collections;
7 using System.Configuration;
8 using System.Diagnostics.CodeAnalysis;
9 using System.IO;
10 using System.Reflection;
11 using System.Runtime.InteropServices;
12 using System.Security;
13 using System.Text;
14
15 /// <summary>
16 /// Managed-code portion of the embedded UI proxy.
17 /// </summary>
18 internal static class EmbeddedUIProxy
19 {
20 private static IEmbeddedUI uiInstance;
21 private static string uiClass;
22
23 private static bool DebugBreakEnabled(string method)
24 {
25 return CustomActionProxy.DebugBreakEnabled(new string[] { method, EmbeddedUIProxy.uiClass + "." + method } );
26 }
27
28 /// <summary>
29 /// Initializes managed embedded UI by loading the UI class and invoking its Initialize method.
30 /// </summary>
31 /// <param name="sessionHandle">Integer handle to the installer session.</param>
32 /// <param name="uiClass">Name of the class that implements the embedded UI. This must
33 /// be of the form: &quot;AssemblyName!Namespace.Class&quot;</param>
34 /// <param name="internalUILevel">On entry, contains the current UI level for the installation. After this
35 /// method returns, the installer resets the UI level to the returned value of this parameter.</param>
36 /// <returns>0 if the embedded UI was successfully loaded and initialized,
37 /// ERROR_INSTALL_USEREXIT if the user canceled the installation during initialization,
38 /// or ERROR_INSTALL_FAILURE if the embedded UI could not be initialized.</returns>
39 /// <remarks>
40 /// Due to interop limitations, the successful resulting UILevel is actually returned
41 /// as the high-word of the return value instead of via a ref parameter.
42 /// </remarks>
43 [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
44 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
45 public static int Initialize(int sessionHandle, string uiClass, int internalUILevel)
46 {
47 Session session = null;
48
49 try
50 {
51 session = new Session((IntPtr) sessionHandle, false);
52
53 if (String.IsNullOrEmpty(uiClass))
54 {
55 throw new ArgumentNullException("uiClass");
56 }
57
58 EmbeddedUIProxy.uiInstance = EmbeddedUIProxy.InstantiateUI(session, uiClass);
59 }
60 catch (Exception ex)
61 {
62 if (session != null)
63 {
64 try
65 {
66 session.Log("Exception while loading embedded UI:");
67 session.Log(ex.ToString());
68 }
69 catch (Exception)
70 {
71 }
72 }
73 }
74
75 if (EmbeddedUIProxy.uiInstance == null)
76 {
77 return (int) ActionResult.Failure;
78 }
79
80 try
81 {
82 string resourcePath = Path.GetDirectoryName(EmbeddedUIProxy.uiInstance.GetType().Assembly.Location);
83 InstallUIOptions uiOptions = (InstallUIOptions) internalUILevel;
84 if (EmbeddedUIProxy.DebugBreakEnabled("Initialize"))
85 {
86 System.Diagnostics.Debugger.Launch();
87 }
88
89 if (EmbeddedUIProxy.uiInstance.Initialize(session, resourcePath, ref uiOptions))
90 {
91 // The embedded UI initialized and the installation should continue
92 // with internal UI reset according to options.
93 return ((int) uiOptions) << 16;
94 }
95 else
96 {
97 // The embedded UI did not initialize but the installation should still continue
98 // with internal UI reset according to options.
99 return (int) uiOptions;
100 }
101 }
102 catch (InstallCanceledException)
103 {
104 // The installation was canceled by the user.
105 return (int) ActionResult.UserExit;
106 }
107 catch (Exception ex)
108 {
109 // An unhandled exception causes the installation to fail immediately.
110 session.Log("Exception thrown by embedded UI initialization:");
111 session.Log(ex.ToString());
112 return (int) ActionResult.Failure;
113 }
114 }
115
116 /// <summary>
117 /// Passes a progress message to the UI class.
118 /// </summary>
119 /// <param name="messageType">Installer message type and message box options.</param>
120 /// <param name="recordHandle">Handle to a record containing message data.</param>
121 /// <returns>Return value returned by the UI class.</returns>
122 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
123 [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
124 public static int ProcessMessage(int messageType, int recordHandle)
125 {
126 if (EmbeddedUIProxy.uiInstance != null)
127 {
128 try
129 {
130 int msgType = messageType & 0x7F000000;
131 int buttons = messageType & 0x0000000F;
132 int icon = messageType & 0x000000F0;
133 int defButton = messageType & 0x00000F00;
134
135 Record msgRec = (recordHandle != 0 ? Record.FromHandle((IntPtr) recordHandle, false) : null);
136 using (msgRec)
137 {
138 if (EmbeddedUIProxy.DebugBreakEnabled("ProcessMessage"))
139 {
140 System.Diagnostics.Debugger.Launch();
141 }
142
143 return (int) EmbeddedUIProxy.uiInstance.ProcessMessage(
144 (InstallMessage) msgType,
145 msgRec,
146 (MessageButtons) buttons,
147 (MessageIcon) icon,
148 (MessageDefaultButton) defButton);
149 }
150 }
151 catch (Exception)
152 {
153 // Ignore it... just hope future messages will not throw exceptions.
154 }
155 }
156
157 return 0;
158 }
159
160 /// <summary>
161 /// Passes a shutdown message to the UI class.
162 /// </summary>
163 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
164 [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
165 public static void Shutdown()
166 {
167 if (EmbeddedUIProxy.uiInstance != null)
168 {
169 try
170 {
171 if (EmbeddedUIProxy.DebugBreakEnabled("Shutdown"))
172 {
173 System.Diagnostics.Debugger.Launch();
174 }
175
176 EmbeddedUIProxy.uiInstance.Shutdown();
177 }
178 catch (Exception)
179 {
180 // Nothing to do at this point... the installation is done anyway.
181 }
182
183 EmbeddedUIProxy.uiInstance = null;
184 }
185 }
186
187 /// <summary>
188 /// Instantiates a UI class from a given assembly and class name.
189 /// </summary>
190 /// <param name="session">Installer session, for logging.</param>
191 /// <param name="uiClass">Name of the class that implements the embedded UI. This must
192 /// be of the form: &quot;AssemblyName!Namespace.Class&quot;</param>
193 /// <returns>Interface on the UI class for handling UI messages.</returns>
194 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
195 [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
196 private static IEmbeddedUI InstantiateUI(Session session, string uiClass)
197 {
198 int assemblySplit = uiClass.IndexOf('!');
199 if (assemblySplit < 0)
200 {
201 session.Log("Error: invalid embedded UI assembly and class:" + uiClass);
202 return null;
203 }
204
205 string assemblyName = uiClass.Substring(0, assemblySplit);
206 EmbeddedUIProxy.uiClass = uiClass.Substring(assemblySplit + 1);
207
208 Assembly uiAssembly;
209 try
210 {
211 uiAssembly = AppDomain.CurrentDomain.Load(assemblyName);
212
213 // This calls out to CustomActionProxy.DebugBreakEnabled() directly instead
214 // of calling EmbeddedUIProxy.DebugBreakEnabled() because we don't compose a
215 // class.method name for this breakpoint.
216 if (CustomActionProxy.DebugBreakEnabled(new string[] { "EmbeddedUI" }))
217 {
218 System.Diagnostics.Debugger.Launch();
219 }
220
221 return (IEmbeddedUI) uiAssembly.CreateInstance(EmbeddedUIProxy.uiClass);
222 }
223 catch (Exception ex)
224 {
225 session.Log("Error: could not load embedded UI class " + EmbeddedUIProxy.uiClass + " from assembly: " + assemblyName);
226 session.Log(ex.ToString());
227 return null;
228 }
229 }
230 }
231}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Enums.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Enums.cs
new file mode 100644
index 00000000..64ed0e7f
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Enums.cs
@@ -0,0 +1,909 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Diagnostics.CodeAnalysis;
7
8 // Enumerations are in alphabetical order.
9
10 /// <summary>
11 /// Specifies a return status value for custom actions.
12 /// </summary>
13 public enum ActionResult : int
14 {
15 /// <summary>Action completed successfully.</summary>
16 Success = 0,
17
18 /// <summary>Skip remaining actions, not an error.</summary>
19 SkipRemainingActions = 259,
20
21 /// <summary>User terminated prematurely.</summary>
22 UserExit = 1602,
23
24 /// <summary>Unrecoverable error or unhandled exception occurred.</summary>
25 Failure = 1603,
26
27 /// <summary>Action not executed.</summary>
28 NotExecuted = 1626,
29 }
30
31 /// <summary>
32 /// Specifies the open mode for a <see cref="Database"/>.
33 /// </summary>
34 public enum DatabaseOpenMode : int
35 {
36 /// <summary>Open a database read-only, no persistent changes.</summary>
37 ReadOnly = 0,
38
39 /// <summary>Open a database read/write in transaction mode.</summary>
40 Transact = 1,
41
42 /// <summary>Open a database direct read/write without transaction.</summary>
43 Direct = 2,
44
45 /// <summary>Create a new database, transact mode read/write.</summary>
46 Create = 3,
47
48 /// <summary>Create a new database, direct mode read/write.</summary>
49 CreateDirect = 4,
50 }
51
52 /// <summary>
53 /// Log modes available for <see cref="Installer.EnableLog(InstallLogModes,string)"/>
54 /// and <see cref="Installer.SetExternalUI(ExternalUIHandler,InstallLogModes)"/>.
55 /// </summary>
56 [Flags]
57 public enum InstallLogModes : int
58 {
59 /// <summary>Disable logging.</summary>
60 None = 0,
61
62 /// <summary>Log out of memory or fatal exit information.</summary>
63 FatalExit = (1 << ((int) InstallMessage.FatalExit >> 24)),
64
65 /// <summary>Log error messages.</summary>
66 Error = (1 << ((int) InstallMessage.Error >> 24)),
67
68 /// <summary>Log warning messages.</summary>
69 Warning = (1 << ((int) InstallMessage.Warning >> 24)),
70
71 /// <summary>Log user requests.</summary>
72 User = (1 << ((int) InstallMessage.User >> 24)),
73
74 /// <summary>Log status messages that are not displayed.</summary>
75 Info = (1 << ((int) InstallMessage.Info >> 24)),
76
77 /// <summary>Log request to determine a valid source location.</summary>
78 ResolveSource = (1 << ((int) InstallMessage.ResolveSource >> 24)),
79
80 /// <summary>Log insufficient disk space error.</summary>
81 OutOfDiskSpace = (1 << ((int) InstallMessage.OutOfDiskSpace >> 24)),
82
83 /// <summary>Log the start of installation actions.</summary>
84 ActionStart = (1 << ((int) InstallMessage.ActionStart >> 24)),
85
86 /// <summary>Log the data record for installation actions.</summary>
87 ActionData = (1 << ((int) InstallMessage.ActionData >> 24)),
88
89 /// <summary>Log parameters for user-interface initialization.</summary>
90 CommonData = (1 << ((int) InstallMessage.CommonData >> 24)),
91
92 /// <summary>Log the property values at termination.</summary>
93 PropertyDump = (1 << ((int) InstallMessage.Progress >> 24)), // log only
94
95 /// <summary>
96 /// Sends large amounts of information to log file not generally useful to users.
97 /// May be used for support.
98 /// </summary>
99 Verbose = (1 << ((int) InstallMessage.Initialize >> 24)), // log only
100
101 /// <summary>
102 /// Log extra debugging information.
103 /// </summary>
104 ExtraDebug = (1 << ((int) InstallMessage.Terminate >> 24)), // log only
105
106 /// <summary>
107 /// Log only on error.
108 /// </summary>
109 LogOnlyOnError = (1 << ((int) InstallMessage.ShowDialog >> 24)), // log only
110
111 /// <summary>
112 /// Log progress bar information. This message includes information on units so far and total number
113 /// of units. See <see cref="Session.Message"/> for an explanation of the message format. This message
114 /// is only sent to an external user interface and is not logged.
115 /// </summary>
116 Progress = (1 << ((int) InstallMessage.Progress >> 24)), // external handler only
117
118 /// <summary>
119 /// If this is not a quiet installation, then the basic UI has been initialized. If this is a full
120 /// UI installation, the Full UI is not yet initialized. This message is only sent to an external
121 /// user interface and is not logged.
122 /// </summary>
123 Initialize = (1 << ((int) InstallMessage.Initialize >> 24)), // external handler only
124
125 /// <summary>
126 /// If a full UI is being used, the full UI has ended. If this is not a quiet installation, the basic
127 /// UI has not yet ended. This message is only sent to an external user interface and is not logged.
128 /// </summary>
129 Terminate = (1 << ((int) InstallMessage.Terminate >> 24)), // external handler only
130
131 /// <summary>
132 /// Sent prior to display of the Full UI dialog. This message is only sent to an external user
133 /// interface and is not logged.
134 /// </summary>
135 ShowDialog = (1 << ((int) InstallMessage.ShowDialog >> 24)), // external handler only
136
137 /// <summary>
138 /// List of files in use that need to be replaced.
139 /// </summary>
140 FilesInUse = (1 << ((int) InstallMessage.FilesInUse >> 24)), // external handler only
141
142 /// <summary>
143 /// [MSI 4.0] List of apps that the user can request Restart Manager to shut down and restart.
144 /// </summary>
145 RMFilesInUse = (1 << ((int) InstallMessage.RMFilesInUse >> 24)), // external handler only
146 }
147
148 /// <summary>
149 /// Type of message to be processed by <see cref="Session.Message"/>,
150 /// <see cref="ExternalUIHandler"/>, or <see cref="ExternalUIRecordHandler"/>.
151 /// </summary>
152 [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")]
153 public enum InstallMessage : int
154 {
155 /// <summary>Premature termination, possibly fatal OOM.</summary>
156 FatalExit = 0x00000000,
157
158 /// <summary>Formatted error message.</summary>
159 Error = 0x01000000,
160
161 /// <summary>Formatted warning message.</summary>
162 Warning = 0x02000000,
163
164 /// <summary>User request message.</summary>
165 User = 0x03000000,
166
167 /// <summary>Informative message for log.</summary>
168 Info = 0x04000000,
169
170 /// <summary>List of files in use that need to be replaced.</summary>
171 FilesInUse = 0x05000000,
172
173 /// <summary>Request to determine a valid source location.</summary>
174 ResolveSource = 0x06000000,
175
176 /// <summary>Insufficient disk space message.</summary>
177 OutOfDiskSpace = 0x07000000,
178
179 /// <summary>Start of action: action name &amp; description.</summary>
180 ActionStart = 0x08000000,
181
182 /// <summary>Formatted data associated with individual action item.</summary>
183 ActionData = 0x09000000,
184
185 /// <summary>Progress gauge info: units so far, total.</summary>
186 Progress = 0x0A000000,
187
188 /// <summary>Product info for dialog: language Id, dialog caption.</summary>
189 CommonData = 0x0B000000,
190
191 /// <summary>Sent prior to UI initialization, no string data.</summary>
192 Initialize = 0x0C000000,
193
194 /// <summary>Sent after UI termination, no string data.</summary>
195 Terminate = 0x0D000000,
196
197 /// <summary>Sent prior to display or authored dialog or wizard.</summary>
198 ShowDialog = 0x0E000000,
199
200 /// <summary>[MSI 4.0] List of apps that the user can request Restart Manager to shut down and restart.</summary>
201 RMFilesInUse = 0x19000000,
202
203 /// <summary>[MSI 4.5] Sent prior to install of a product.</summary>
204 InstallStart = 0x1A000000,
205
206 /// <summary>[MSI 4.5] Sent after install of a product.</summary>
207 InstallEnd = 0x1B000000,
208 }
209
210 /// <summary>
211 /// Specifies the install mode for <see cref="Installer.ProvideComponent"/> or <see cref="Installer.ProvideQualifiedComponent"/>.
212 /// </summary>
213 public enum InstallMode : int
214 {
215 /// <summary>Provide the component only if the feature's installation state is <see cref="InstallState.Local"/>.</summary>
216 NoSourceResolution = -3,
217
218 /// <summary>Only check that the component is registered, without verifying that the key file of the component exists.</summary>
219 NoDetection = -2,
220
221 /// <summary>Provide the component only if the feature exists.</summary>
222 Existing = -1,
223
224 /// <summary>Provide the component and perform any installation necessary to provide the component.</summary>
225 Default = 0,
226 }
227
228 /// <summary>
229 /// Specifies the run mode for <see cref="Session.GetMode"/>.
230 /// </summary>
231 [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")]
232 public enum InstallRunMode : int
233 {
234 /// <summary>The administrative mode is installing, or the product is installing.</summary>
235 Admin = 0,
236
237 /// <summary>The advertisements are installing or the product is installing or updating.</summary>
238 Advertise = 1,
239
240 /// <summary>An existing installation is being modified or there is a new installation.</summary>
241 Maintenance = 2,
242
243 /// <summary>Rollback is enabled.</summary>
244 RollbackEnabled = 3,
245
246 /// <summary>The log file is active. It was enabled prior to the installation session.</summary>
247 LogEnabled = 4,
248
249 /// <summary>Execute operations are spooling or they are in the determination phase.</summary>
250 Operations = 5,
251
252 /// <summary>A reboot is necessary after a successful installation (settable).</summary>
253 RebootAtEnd = 6,
254
255 /// <summary>A reboot is necessary to continue the installation (settable).</summary>
256 RebootNow = 7,
257
258 /// <summary>Files from cabinets and Media table files are installing.</summary>
259 Cabinet = 8,
260
261 /// <summary>The source LongFileNames is suppressed through the PID_MSISOURCE summary property.</summary>
262 SourceShortNames = 9,
263
264 /// <summary>The target LongFileNames is suppressed through the SHORTFILENAMES property.</summary>
265 TargetShortNames = 10,
266
267 // <summary>Reserved for future use.</summary>
268 //Reserved11 = 11,
269
270 /// <summary>The operating system is Windows 95, Windows 98, or Windows ME.</summary>
271 [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x")]
272 Windows9x = 12,
273
274 /// <summary>The operating system supports demand installation.</summary>
275 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Zaw")]
276 ZawEnabled = 13,
277
278 // <summary>Reserved for future use.</summary>
279 //Reserved14 = 14,
280
281 // <summary>Reserved for future use.</summary>
282 //Reserved15 = 15,
283
284 /// <summary>A custom action called from install script execution.</summary>
285 Scheduled = 16,
286
287 /// <summary>A custom action called from rollback execution script.</summary>
288 Rollback = 17,
289
290 /// <summary>A custom action called from commit execution script.</summary>
291 Commit = 18,
292 }
293
294 /// <summary>
295 /// Installed state of a Component or Feature.
296 /// </summary>
297 public enum InstallState : int
298 {
299 /// <summary>The component is disabled.</summary>
300 NotUsed = -7,
301
302 /// <summary>The installation configuration data is corrupt.</summary>
303 BadConfig = -6,
304
305 /// <summary>The installation is suspended or in progress.</summary>
306 Incomplete = -5,
307
308 /// <summary>Component is set to run from source, but source is unavailable.</summary>
309 SourceAbsent = -4,
310
311 /// <summary>The buffer overflow is returned.</summary>
312 MoreData = -3,
313
314 /// <summary>An invalid parameter was passed to the function.</summary>
315 InvalidArgument = -2,
316
317 /// <summary>An unrecognized product or feature name was passed to the function.</summary>
318 Unknown = -1,
319
320 /// <summary>The component is broken.</summary>
321 Broken = 0,
322
323 /// <summary>The feature is advertised.</summary>
324 Advertised = 1,
325
326 /// <summary>The component is being removed. In action state and not settable.</summary>
327 Removed = 1,
328
329 /// <summary>The component is not installed, or action state is absent but clients remain.</summary>
330 Absent = 2,
331
332 /// <summary>The component is installed on the local drive.</summary>
333 Local = 3,
334
335 /// <summary>The component will run from the source, CD, or network.</summary>
336 Source = 4,
337
338 /// <summary>The component will be installed in the default location: local or source.</summary>
339 Default = 5,
340 }
341
342 /// <summary>
343 /// Specifies the type of installation for <see cref="Installer.ApplyPatch(string,string,InstallType,string)"/>.
344 /// </summary>
345 public enum InstallType : int
346 {
347 /// <summary>Searches system for products to patch.</summary>
348 Default = 0,
349
350 /// <summary>Indicates a administrative installation.</summary>
351 NetworkImage = 1,
352
353 /// <summary>Indicates a particular instance.</summary>
354 SingleInstance = 2,
355 }
356
357 /// <summary>
358 /// Level of the installation user interface, specified with
359 /// <see cref="Installer.SetInternalUI(InstallUIOptions)"/>.
360 /// </summary>
361 [SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue")]
362 [Flags]
363 public enum InstallUIOptions : int
364 {
365 /// <summary>Does not change UI level.</summary>
366 NoChange = 0,
367
368 /// <summary>Uses Default UI level.</summary>
369 Default = 1,
370
371 /// <summary>Silent installation.</summary>
372 Silent = 2,
373
374 /// <summary>Simple progress and error handling.</summary>
375 Basic = 3,
376
377 /// <summary>Authored UI, wizard dialogs suppressed.</summary>
378 Reduced = 4,
379
380 /// <summary>Authored UI with wizards, progress, and errors.</summary>
381 Full = 5,
382
383 /// <summary>
384 /// When combined with the <see cref="Basic"/> value, the installer does not display
385 /// the cancel button in the progress dialog.
386 /// </summary>
387 HideCancel = 0x20,
388
389 /// <summary>
390 /// When combined with the <see cref="Basic"/> value, the installer displays progress
391 /// dialog boxes but does not display any modal dialog boxes or error dialog boxes.
392 /// </summary>
393 ProgressOnly = 0x40,
394
395 /// <summary>
396 /// When combined with another value, the installer displays a modal dialog
397 /// box at the end of a successful installation or if there has been an error.
398 /// No dialog box is displayed if the user cancels.
399 /// </summary>
400 EndDialog = 0x80,
401
402 /// <summary>
403 /// Forces display of the source resolution dialog even if the UI is otherwise silent.
404 /// </summary>
405 SourceResolutionOnly = 0x100,
406
407 /// <summary>
408 /// [MSI 5.0] Forces display of the UAC dialog even if the UI is otherwise silent.
409 /// </summary>
410 UacOnly = 0x200,
411 }
412
413 /// <summary>
414 /// Specifies a return status value for message handlers. These values are returned by
415 /// <see cref="Session.Message"/>, <see cref="ExternalUIHandler"/>, and <see cref="IEmbeddedUI.ProcessMessage"/>.
416 /// </summary>
417 public enum MessageResult : int
418 {
419 /// <summary>An error was found in the message handler.</summary>
420 Error = -1,
421
422 /// <summary>No action was taken.</summary>
423 None = 0,
424
425 /// <summary>IDOK</summary>
426 [SuppressMessage("Microsoft.Naming", "CA1706:ShortAcronymsShouldBeUppercase")]
427 OK = 1,
428
429 /// <summary>IDCANCEL</summary>
430 Cancel = 2,
431
432 /// <summary>IDABORT</summary>
433 Abort = 3,
434
435 /// <summary>IDRETRY</summary>
436 Retry = 4,
437
438 /// <summary>IDIGNORE</summary>
439 Ignore = 5,
440
441 /// <summary>IDYES</summary>
442 Yes = 6,
443
444 /// <summary>IDNO</summary>
445 No = 7,
446 }
447
448 /// <summary>
449 /// Specifies constants defining which buttons to display for a message. This can be cast to
450 /// the MessageBoxButtons enum in System.Windows.Forms and System.Windows.
451 /// </summary>
452 public enum MessageButtons
453 {
454 /// <summary>
455 /// The message contains an OK button.
456 /// </summary>
457 OK = 0,
458
459 /// <summary>
460 /// The message contains OK and Cancel buttons.
461 /// </summary>
462 OKCancel = 1,
463
464 /// <summary>
465 /// The message contains Abort, Retry, and Ignore buttons.
466 /// </summary>
467 AbortRetryIgnore = 2,
468
469 /// <summary>
470 /// The message contains Yes, No, and Cancel buttons.
471 /// </summary>
472 YesNoCancel = 3,
473
474 /// <summary>
475 /// The message contains Yes and No buttons.
476 /// </summary>
477 YesNo = 4,
478
479 /// <summary>
480 /// The message contains Retry and Cancel buttons.
481 /// </summary>
482 RetryCancel = 5,
483 }
484
485 /// <summary>
486 /// Specifies constants defining which information to display. This can be cast to
487 /// the MessageBoxIcon enum in System.Windows.Forms and System.Windows.
488 /// </summary>
489 public enum MessageIcon
490 {
491 /// <summary>
492 /// The message contain no symbols.
493 /// </summary>
494 None = 0,
495
496 /// <summary>
497 /// The message contains a symbol consisting of white X in a circle with a red background.
498 /// </summary>
499 Error = 16,
500
501 /// <summary>
502 /// The message contains a symbol consisting of a white X in a circle with a red background.
503 /// </summary>
504 Hand = 16,
505
506 /// <summary>
507 /// The message contains a symbol consisting of white X in a circle with a red background.
508 /// </summary>
509 Stop = 16,
510
511 /// <summary>
512 /// The message contains a symbol consisting of a question mark in a circle.
513 /// </summary>
514 Question = 32,
515
516 /// <summary>
517 /// The message contains a symbol consisting of an exclamation point in a triangle with a yellow background.
518 /// </summary>
519 Exclamation = 48,
520
521 /// <summary>
522 /// The message contains a symbol consisting of an exclamation point in a triangle with a yellow background.
523 /// </summary>
524 Warning = 48,
525
526 /// <summary>
527 /// The message contains a symbol consisting of a lowercase letter i in a circle.
528 /// </summary>
529 Information = 64,
530
531 /// <summary>
532 /// The message contains a symbol consisting of a lowercase letter i in a circle.
533 /// </summary>
534 Asterisk = 64,
535 }
536
537 /// <summary>
538 /// Specifies constants defining the default button on a message. This can be cast to
539 /// the MessageBoxDefaultButton enum in System.Windows.Forms and System.Windows.
540 /// </summary>
541 public enum MessageDefaultButton
542 {
543 /// <summary>
544 /// The first button on the message is the default button.
545 /// </summary>
546 Button1 = 0,
547
548 /// <summary>
549 /// The second button on the message is the default button.
550 /// </summary>
551 Button2 = 256,
552
553 /// <summary>
554 /// The third button on the message is the default button.
555 /// </summary>
556 Button3 = 512,
557 }
558
559 /// <summary>
560 /// Additional styles for use with message boxes.
561 /// </summary>
562 [Flags]
563 internal enum MessageBoxStyles
564 {
565 /// <summary>
566 /// The message box is created with the WS_EX_TOPMOST window style.
567 /// </summary>
568 TopMost = 0x00040000,
569
570 /// <summary>
571 /// The caller is a service notifying the user of an event.
572 /// The function displays a message box on the current active desktop, even if there is no user logged on to the computer.
573 /// </summary>
574 ServiceNotification = 0x00200000,
575 }
576
577 /// <summary>
578 /// Specifies the different patch states for <see cref="PatchInstallation.GetPatches(string, string, string, UserContexts, PatchStates)"/>.
579 /// </summary>
580 [Flags]
581 public enum PatchStates : int
582 {
583 /// <summary>Invalid value.</summary>
584 None = 0,
585
586 /// <summary>Patches applied to a product.</summary>
587 Applied = 1,
588
589 /// <summary>Patches that are superseded by other patches.</summary>
590 Superseded = 2,
591
592 /// <summary>Patches that are obsolesced by other patches.</summary>
593 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Obsoleted")]
594 Obsoleted = 4,
595
596 /// <summary>Patches that are registered to a product but not applied.</summary>
597 Registered = 8,
598
599 /// <summary>All valid patch states.</summary>
600 All = (Applied | Superseded | Obsoleted | Registered)
601 }
602
603 /// <summary>
604 /// Specifies the reinstall mode for <see cref="Installer.ReinstallFeature"/> or <see cref="Installer.ReinstallProduct"/>.
605 /// </summary>
606 [Flags]
607 public enum ReinstallModes : int
608 {
609 /// <summary>Reinstall only if file is missing.</summary>
610 FileMissing = 0x00000002,
611
612 /// <summary>Reinstall if file is missing, or older version.</summary>
613 FileOlderVersion = 0x00000004,
614
615 /// <summary>Reinstall if file is missing, or equal or older version.</summary>
616 FileEqualVersion = 0x00000008,
617
618 /// <summary>Reinstall if file is missing, or not exact version.</summary>
619 FileExact = 0x00000010,
620
621 /// <summary>Checksum executables, reinstall if missing or corrupt.</summary>
622 FileVerify = 0x00000020,
623
624 /// <summary>Reinstall all files, regardless of version.</summary>
625 FileReplace = 0x00000040,
626
627 /// <summary>Insure required machine reg entries.</summary>
628 MachineData = 0x00000080,
629
630 /// <summary>Insure required user reg entries.</summary>
631 UserData = 0x00000100,
632
633 /// <summary>Validate shortcuts items.</summary>
634 Shortcut = 0x00000200,
635
636 /// <summary>Use re-cache source install package.</summary>
637 Package = 0x00000400,
638 }
639
640 /// <summary>
641 /// Attributes for <see cref="Transaction"/> methods.
642 /// </summary>
643 [Flags]
644 public enum TransactionAttributes : int
645 {
646 /// <summary>No attributes.</summary>
647 None = 0x00000000,
648
649 /// <summary>Request that the Windows Installer not shutdown the embedded UI until the transaction is complete.</summary>
650 ChainEmbeddedUI = 0x00000001,
651
652 /// <summary>Request that the Windows Installer transfer the embedded UI from the original installation.</summary>
653 JoinExistingEmbeddedUI = 0x00000002,
654 }
655
656 /// <summary>
657 /// Transform error conditions available for <see cref="Database.CreateTransformSummaryInfo"/> or
658 /// <see cref="Database.ApplyTransform(string,TransformErrors)"/>.
659 /// </summary>
660 [Flags]
661 public enum TransformErrors : int
662 {
663 /// <summary>No error conditions.</summary>
664 None = 0x0000,
665
666 /// <summary>Adding a row that already exists.</summary>
667 AddExistingRow = 0x0001,
668
669 /// <summary>Deleting a row that doesn't exist.</summary>
670 DelMissingRow = 0x0002,
671
672 /// <summary>Adding a table that already exists.</summary>
673 AddExistingTable = 0x0004,
674
675 /// <summary>Deleting a table that doesn't exist.</summary>
676 DelMissingTable = 0x0008,
677
678 /// <summary>Updating a row that doesn't exist.</summary>
679 UpdateMissingRow = 0x0010,
680
681 /// <summary>Transform and database code pages do not match and neither code page is neutral.</summary>
682 ChangeCodePage = 0x0020,
683
684 /// <summary>Create the temporary _TransformView table when applying the transform.</summary>
685 ViewTransform = 0x0100,
686 }
687
688 /// <summary>
689 /// Transform validation flags available for <see cref="Database.CreateTransformSummaryInfo"/>.
690 /// </summary>
691 [Flags]
692 public enum TransformValidations : int
693 {
694 /// <summary>Validate no properties.</summary>
695 None = 0x0000,
696
697 /// <summary>Default language must match base database.</summary>
698 Language = 0x0001,
699
700 /// <summary>Product must match base database.</summary>
701 Product = 0x0002,
702
703 /// <summary>Check major version only.</summary>
704 MajorVersion = 0x0008,
705
706 /// <summary>Check major and minor versions only.</summary>
707 MinorVersion = 0x0010,
708
709 /// <summary>Check major, minor, and update versions.</summary>
710 UpdateVersion = 0x0020,
711
712 /// <summary>Installed version &lt; base version.</summary>
713 NewLessBaseVersion = 0x0040,
714
715 /// <summary>Installed version &lt;= base version.</summary>
716 NewLessEqualBaseVersion = 0x0080,
717
718 /// <summary>Installed version = base version.</summary>
719 NewEqualBaseVersion = 0x0100,
720
721 /// <summary>Installed version &gt;= base version.</summary>
722 NewGreaterEqualBaseVersion = 0x0200,
723
724 /// <summary>Installed version &gt; base version.</summary>
725 NewGreaterBaseVersion = 0x0400,
726
727 /// <summary>UpgradeCode must match base database.</summary>
728 UpgradeCode = 0x0800,
729 }
730
731 /// <summary>
732 /// Specifies the installation context for <see cref="ProductInstallation"/>s,
733 /// <see cref="PatchInstallation"/>es, and
734 /// <see cref="Installer.DetermineApplicablePatches(string,string[],InapplicablePatchHandler,string,UserContexts)"/>
735 /// </summary>
736 [Flags]
737 public enum UserContexts : int
738 {
739 /// <summary>Not installed.</summary>
740 None = 0,
741
742 /// <summary>User managed install context.</summary>
743 UserManaged = 1,
744
745 /// <summary>User non-managed context.</summary>
746 UserUnmanaged = 2,
747
748 /// <summary>Per-machine context.</summary>
749 Machine = 4,
750
751 /// <summary>All contexts, or all valid values.</summary>
752 All = (UserManaged | UserUnmanaged | Machine),
753
754 /// <summary>All user-managed contexts.</summary>
755 AllUserManaged = 8,
756 }
757
758 /// <summary>
759 /// Defines the type of error encountered by the <see cref="View.Validate"/>, <see cref="View.ValidateNew"/>,
760 /// or <see cref="View.ValidateFields"/> methods of the <see cref="View"/> class.
761 /// </summary>
762 public enum ValidationError : int
763 {
764 /*
765 InvalidArg = -3,
766 MoreData = -2,
767 FunctionError = -1,
768 */
769
770 /// <summary>No error.</summary>
771 None = 0,
772
773 /// <summary>The new record duplicates primary keys of the existing record in a table.</summary>
774 DuplicateKey = 1,
775
776 /// <summary>There are no null values allowed, or the column is about to be deleted but is referenced by another row.</summary>
777 Required = 2,
778
779 /// <summary>The corresponding record in a foreign table was not found.</summary>
780 BadLink = 3,
781
782 /// <summary>The data is greater than the maximum value allowed.</summary>
783 Overflow = 4,
784
785 /// <summary>The data is less than the minimum value allowed.</summary>
786 Underflow = 5,
787
788 /// <summary>The data is not a member of the values permitted in the set.</summary>
789 NotInSet = 6,
790
791 /// <summary>An invalid version string was supplied.</summary>
792 BadVersion = 7,
793
794 /// <summary>The case was invalid. The case must be all uppercase or all lowercase.</summary>
795 BadCase = 8,
796
797 /// <summary>An invalid GUID was supplied.</summary>
798 BadGuid = 9,
799
800 /// <summary>An invalid wildcard file name was supplied, or the use of wildcards was invalid.</summary>
801 BadWildcard = 10,
802
803 /// <summary>An invalid identifier was supplied.</summary>
804 BadIdentifier = 11,
805
806 /// <summary>Invalid language IDs were supplied.</summary>
807 BadLanguage = 12,
808
809 /// <summary>An invalid file name was supplied.</summary>
810 BadFileName = 13,
811
812 /// <summary>An invalid path was supplied.</summary>
813 BadPath = 14,
814
815 /// <summary>An invalid conditional statement was supplied.</summary>
816 BadCondition = 15,
817
818 /// <summary>An invalid format string was supplied.</summary>
819 BadFormatted = 16,
820
821 /// <summary>An invalid template string was supplied.</summary>
822 BadTemplate = 17,
823
824 /// <summary>An invalid string was supplied in the DefaultDir column of the Directory table.</summary>
825 BadDefaultDir = 18,
826
827 /// <summary>An invalid registry path string was supplied.</summary>
828 BadRegPath = 19,
829
830 /// <summary>An invalid string was supplied in the CustomSource column of the CustomAction table.</summary>
831 BadCustomSource = 20,
832
833 /// <summary>An invalid property string was supplied.</summary>
834 BadProperty = 21,
835
836 /// <summary>The _Validation table is missing a reference to a column.</summary>
837 MissingData = 22,
838
839 /// <summary>The category column of the _Validation table for the column is invalid.</summary>
840 BadCategory = 23,
841
842 /// <summary>The table in the Keytable column of the _Validation table was not found or loaded.</summary>
843 BadKeyTable = 24,
844
845 /// <summary>The value in the MaxValue column of the _Validation table is less than the value in the MinValue column.</summary>
846 BadMaxMinValues = 25,
847
848 /// <summary>An invalid cabinet name was supplied.</summary>
849 BadCabinet = 26,
850
851 /// <summary>An invalid shortcut target name was supplied.</summary>
852 BadShortcut = 27,
853
854 /// <summary>The string is too long for the length specified by the column definition.</summary>
855 StringOverflow = 28,
856
857 /// <summary>An invalid localization attribute was supplied. (Primary keys cannot be localized.)</summary>
858 BadLocalizeAttrib = 29
859 }
860
861 /// <summary>
862 /// Specifies the modify mode for <see cref="View.Modify"/>.
863 /// </summary>
864 public enum ViewModifyMode : int
865 {
866 /// <summary>
867 /// Refreshes the information in the supplied record without changing the position
868 /// in the result set and without affecting subsequent fetch operations.
869 /// </summary>
870 Seek = -1,
871
872 /// <summary>Refreshes the data in a Record.</summary>
873 Refresh = 0,
874
875 /// <summary>Inserts a Record into the view.</summary>
876 Insert = 1,
877
878 /// <summary>Updates the View with new data from the Record.</summary>
879 Update = 2,
880
881 /// <summary>Updates or inserts a Record into the View.</summary>
882 Assign = 3,
883
884 /// <summary>Updates or deletes and inserts a Record into the View.</summary>
885 Replace = 4,
886
887 /// <summary>Inserts or validates a record.</summary>
888 Merge = 5,
889
890 /// <summary>Deletes a Record from the View.</summary>
891 Delete = 6,
892
893 /// <summary>Inserts a Record into the View. The inserted data is not persistent.</summary>
894 InsertTemporary = 7,
895
896 /// <summary>Validates a record.</summary>
897 Validate = 8,
898
899 /// <summary>Validates a new record.</summary>
900 [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
901 ValidateNew = 9,
902
903 /// <summary>Validates fields of a fetched or new record. Can validate one or more fields of an incomplete record.</summary>
904 ValidateField = 10,
905
906 /// <summary>Validates a record that will be deleted later.</summary>
907 ValidateDelete = 11,
908 }
909}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Errors.resources b/src/dtf/WixToolset.Dtf.WindowsInstaller/Errors.resources
new file mode 100644
index 00000000..5564e88a
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Errors.resources
Binary files differ
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Errors.txt b/src/dtf/WixToolset.Dtf.WindowsInstaller/Errors.txt
new file mode 100644
index 00000000..ec7c97b1
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Errors.txt
@@ -0,0 +1,404 @@
1; Windows Installer Internal Error Messages
2;
3; Error numbers greater than 2000 are "internal errors" and do not have
4; localized strings in msimsg.dll. The messages are provided here
5; (in English only) because they can be informative and time-saving
6; during developement.
7;
8
92101=Shortcuts not supported by the OS.
102102=Invalid .INI action: [2]
112103=Could not resolve path for shell folder [2].
122104=Writing .INI file: [3]: System error: [2]
132105=Shortcut Creation [3] Failed. System error: [2]
142106=Shortcut Deletion [3] Failed. System error: [2]
152107=Error [3] registering type library [2].
162108=Error [3] unregistering type library [2].
172109=Section missing for INI action.
182110=Key missing for INI action.
192111=Detection of running apps failed, could not get performance data. Reg operation returned : [2].
202112=Detection of running apps failed, could not get performance index. Reg operation returned : [2].
212113=Detection of running apps failed.
222200=Database: [2]. Database object creation failed, mode = [3].
232201=Database: [2]. Initialization failed, out of memory.
242202=Database: [2]. Data access failed, out of memory.
252203=Database: [2]. Cannot open database file. System error [3].
262204=Database: [2]. Table already exists: [3]
272205=Database: [2]. Table does not exist: [3]
282206=Database: [2]. Table could not be dropped: [3]
292207=Database: [2]. Intent violation.
302208=Database: [2]. Insufficient parameters for Execute.
312209=Database: [2]. Cursor in invalid state.
322210=Database: [2]. Invalid update data type in column [3]
332211=Database: [2]. Could not create database table [3]
342212=Database: [2]. Database not in writable state.
352213=Database: [2]. Error saving database tables.
362214=Database: [2]. Error writing export file: [3]
372215=Database: [2]. Cannot open import file: [3]
382216=Database: [2]. Import file format error: [3], Line [4]
392217=Database: [2]. Wrong state to CreateOutputDatabase [3].
402218=Database: [2]. Table name not supplied.
412219=Database: [2]. Invalid Installer database format.
422220=Database: [2]. Invalid row/field data.
432221=Database: [2]. Code page conflict in import file: [3].
442222=Database: [2]. Transform or merge code page [3] differs from database code page [4].
452223=Database: [2]. Databases are the same. No transform generated.
462224=Database: [2]. GenerateTransform: Database corrupt. Table: [3]
472225=Database: [2]. Transform: Cannot transform a temporary table. Table: [3]
482226=Database: [2]. Transform failed.
492227=Database: [2]. Invalid identifier '[3]' in SQL query: [4]
502228=Database: [2]. Unknown table '[3]' in SQL query: [4]
512229=Database: [2]. Could not load table '[3]' in SQL query: [4]
522230=Database: [2]. Repeated table '[3]' in SQL query: [4]
532231=Database: [2]. Missing ')' in SQL query: [3]
542232=Database: [2]. Unexpected token '[3]' in SQL query: [4]
552233=Database: [2]. No columns in SELECT clause in SQL query: [3]
562234=Database: [2]. No columns in ORDER BY clause in SQL query: [3]
572235=Database: [2]. Column '[3]' not present or ambiguous in SQL query: [4]
582236=Database: [2]. Invalid operator '[3]' in SQL query: [4]
592237=Database: [2]. Invalid or missing query string: [3]
602238=Database: [2]. Missing FROM clause in SQL query: [3]
612239=Database: [2]. Insufficient values in INSERT SQL stmt.
622240=Database: [2]. Missing update columns in UPDATE SQL stmt.
632241=Database: [2]. Missing insert columns in INSERT SQL stmt.
642242=Database: [2]. Column '[3]' repeated
652243=Database: [2]. No primary columns defined for table creation.
662244=Database: [2]. Invalid type specifier '[3]' in SQL query [4].
672245=IStorage::Stat failed with error [3]
682246=Database: [2]. Invalid Installer transform format.
692247=Database: [2] Transform stream read/write failure.
702248=Database: [2] GenerateTransform/Merge: Column type in base table doesn't match reference table. Table: [3] Col #: [4]
712249=Database: [2] GenerateTransform: More columns in base table than in reference table. Table: [3]
722250=Database: [2] Transform: Cannot add existing row. Table: [3]
732251=Database: [2] Transform: Cannot delete row that doesn't exist. Table: [3]
742252=Database: [2] Transform: Cannot add existing table. Table: [3]
752253=Database: [2] Transform: Cannot delete table that doesn't exist. Table: [3]
762254=Database: [2] Transform: Cannot update row that doesn't exist. Table: [3]
772255=Database: [2] Transform: Column with this name already exists. Table: [3] Col: [4]
782256=Database: [2] GenerateTransform/Merge: Number of primary keys in base table doesn't match reference table. Table: [3]
792257=Database: [2]. Intent to modify read only table: [3]
802258=Database: [2]. Type mismatch in parameter: [3]
812259=Database: [2] Table(s) Update failed.
822260=Storage CopyTo failed. System error: [3]
832261=Could not remove stream [2]. System error: [3]
842262=Stream does not exist: [2]. System error: [3]
852263=Could not open stream [2]. System error: [3]
862264=Could not remove stream [2]. System error: [3]
872265=Could not commit storage. System error: [3]
882266=Could not rollback storage. System error: [3]
892267=Could not delete storage [2]. System error: [3]
902268=Database: [2]. Merge: There were merge conflicts reported in [3] tables.
912269=Database: [2]. Merge: The column count differed in the '[3]' table of the two databases.
922270=Database: [2]. GenerateTransform/Merge: Column name in base table doesn't match reference table. Table: [3] Col #: [4]
932271=SummaryInformation write for transform failed.
942272=Database: [2]. MergeDatabase will not write any changes because the database is open read-only.
952273=Database: [2]. MergeDatabase: A reference to the base database was passed as the reference database.
962274=Database: [2]. MergeDatabase: Unable to write errors to Error table. Could be due to a non-nullable column in a predefined Error table.
972275=Database: [2]. Specified Modify [3] operation invalid for table joins.
982276=Database: [2]. Code page [3] not supported by the system.
992277=Database: [2]. Failed to save table [3].
1002278=Database: [2]. Exceeded number of expressions limit of 32 in WHERE clause of SQL query: [3].
1012279=Database: [2] Transform: Too many columns in base table [3]
1022280=Database: [2]. Could not create column [3] for table [4]
1032281=Could not rename stream [2]. System error: [3]
1042282=Stream name invalid [2].
1052302=Patch notify: [2] bytes patched to far.
1062303=Error getting volume info. GetLastError: [2]
1072304=Error getting disk free space. GetLastError: [2]. Volume: [3]
1082305=Error waiting for patch thread. GetLastError: [2].
1092306=Could not create thread for patch application. GetLastError: [2].
1102307=Source file key name is null.
1112308=Destination File Name is Null
1122309=Attempting to patch file [2] when patch already in progress.
1132310=Attempting to continue patch when no patch is in progress.
1142315=Missing Path Separator: [2]
1152318=File does not exist: [2]
1162319=Error setting file attribute: [3] GetLastError: [2]
1172320=File not writable: [2]
1182321=Error creating file: [2]
1192322=User canceled
1202323=Invalid File Attribute
1212324=Could not open file: [3] GetLastError: [2]
1222325=Could not get file time for file: [3] GetLastError: [2]
1232326=Error in FileToDosDateTime.
1242327=Could not remove directory: [3] GetLastError: [2]
1252328=Error getting file version info for file: [2]
1262329=Error deleting file: [3]. GetLastError: [2]
1272330=Error getting file attributes: [3]. GetLastError: [2]
1282331=Error loading library [2] or finding entry point [3]
1292332=Error getting file attributes. GetLastError: [2]
1302333=Error setting file attributes. GetLastError: [2]
1312334=Error converting file time to local time for file: [3]. GetLastError: [2]
1322335=Path: [2] is not a parent of [3]
1332336=Error creating temp file on path: [3]. GetLastError: [2]
1342337=Could not close file: [3] GetLastError: [2]
1352338=Could not update resource for file: [3] GetLastError: [2]
1362339=Could not set file time for file: [3] GetLastError: [2]
1372340=Could not update resource for file: [3], Missing Resource
1382341=Could not update resource for file: [3], Resource too large
1392342=Could not update resource for file: [3] GetLastError: [2]
1402343=Specified path is empty.
1412344=Could not find required file IMAGEHLP.DLL to validate file:[2]
1422345=[2]: File does not contain a valid checksum value.
1432347=User ignore
1442348=Error attempting to read from cabinet stream.
1452349=Copy Resumed With Different Info
1462350=FDI Server Error
1472351=File key '[2]' not found in cabinet '[3]'. The installation cannot continue.
1482352=Couldn't initialize cabinet file server. The required file 'Cabinet.dll' may be missing.
1492353=Not a cabinet
1502354=Cannot handle cabinet
1512355=Corrupt cabinet
1522356=Couldn't locate cabinet in stream: [2].
1532357=Cannot set attributes
1542358=Error determining whether file is in-use: [3]. GetLastError: [2]
1552359=Unable to create the target file - file may be in use.
1562360=progress tick.
1572361=Need next cabinet.
1582362=Folder not found: [2]
1592363=Could not enumerate subfolders for folder: [2]
1602364=Bad enumeration constant in CreateCopier call.
1612365=Could not BindImage exe file [2]
1622366=User Failure
1632367=User Abort.
1642368=Failed to get network resource information. Error [2], network path [3]. Extended error: network provider [5], error code [4], error description [6].
1652370=Invalid CRC checksum value for [2] file.{ Its header says [3] for checksum, its computed value is [4].}
1662371=Could not apply patch to file [2]. GetLastError: [3]
1672372=Patch file [2] is corrupt or of an invalid format. Attempting to patch file [3]. GetLastError: [4]
1682373=File [2] is not a valid patch file.
1692374=File [2] is not a valid destination file for patch file [3].
1702375=Unknown patching error: [2].
1712376=Cabinet not found.
1722379=Error opening file for read: [3] GetLastError: [2]
1732380=Error opening file for write: [3] GetLastError: [2]
1742381=Directory does not exist: [2]
1752382=Drive not ready: [2]
1762401=64-bit registry operation attempted on 32-bit operating system for key [2].
1772402=Out of memory.
1782501=Could not create rollback script enumerator
1792502=Called InstallFinalize when no install in progress.
1802503=Called RunScript when not marked in progress.
1812601=Invalid value for property [2]: '[3]'
1822602=The [2] table entry '[3]' has no associated entry in the Media table.
1832603=Duplicate Table Name [2]
1842604=[2] property undefined.
1852605=Could not find server [2] in [3] or [4].
1862606=Value of property [2] is not a valid full path: '[3]'.
1872607=Media table not found or empty (required for installation of files).
1882608=Could not create security descriptor for object. Error: '[2]'.
1892609=Attempt to migrate product settings before initialization.
1902611=The file [2] is marked as compressed, but the associated media entry does not specify a cabinet.
1912612=Stream not found in '[2]' column. Primary key: '[3]'.
1922613=RemoveExistingProducts action sequenced incorrectly.
1932614=Could not access IStorage object from installation package.
1942615=Skipped unregistration of Module [2] due to source resolution failure.
1952616=Companion file [2] parent missing.
1962617=Shared component [2] not found in Component table.
1972618=Isolated application component [2] not found in Component table.
1982619=Isolated components [2], [3] not part of same feature.
1992620=Key file of isolated application component [2] not in File table.
2002621=Resource DLL or Resource ID information for shortcut [2] set incorrectly.
2012701=The Component Table exceeds the acceptable tree depth of [2] levels.
2022702=A Feature Table record ([2]) references a non-existent parent in the Attributes field.
2032703=Property name for root source path not defined: [2]
2042704=Root directory property undefined: [2]
2052705=Invalid table: [2]; Could not be linked as tree.
2062706=Source paths not created. No path exists for entry [2] in Directory Table
2072707=Target paths not created. No path exists for entry [2] in Directory Table
2082708=No entries found in the file table.
2092709=The specified Component name ('[2]') not found in Component Table.
2102710=The requested 'Select' state is illegal for this Component.
2112711=The specified Feature name ('[2]') not found in Feature Table.
2122712=Invalid return from modeless dialog: [3], in action [2].
2132713=Null value in a non-nullable column ('[2]' in '[3]' column of the '[4]' table.
2142714=Invalid value for default folder name: [2].
2152715=The specified File key ('[2]') not found in the File Table.
2162716=Couldn't create a random subcomponent name for component '[2]'.
2172717=Bad action condition or error calling custom action '[2]'.
2182718=Missing package name for product code '[2]'.
2192719=Neither UNC nor drive letter path found in source '[2]'.
2202720=Error opening source list key. Error: '[2]'
2212721=Custom action [2] not found in Binary table stream
2222722=Custom action [2] not found in File table
2232723=Custom action [2] specifies unsupported type
2242724=The volume label '[2]' on the media you're running from doesn't match the label '[3]' given in the Media table. This is allowed only if you have only 1 entry in your Media table.
2252725=Invalid database tables
2262726=Action not found: [2]
2272727=The directory entry '[2]' does not exist in the Directory table
2282728=Table definition error: [2]
2292729=Install engine not initialized.
2302730=Bad value in database. Table: '[2]'; Primary key: '[3]'; Column: '[4]'
2312731=Selection Manager not initialized.
2322732=Directory Manager not initialized.
2332733=Bad foreign key ('[2]') in '[3]' column of the '[4]' table.
2342734=Invalid Reinstall mode character.
2352735=Custom action '[2]' has caused an unhandled exception and has been stopped. This may be the result of an internal error in the custom action, such as an access violation.
2362736=Generation of custom action temp file failed: [2]
2372737=Could not access custom action [2], entry [3], library [4]
2382738=Could not access VBScript runtime for custom action [2]
2392739=Could not access JavaScript runtime for custom action [2]
2402740=Custom action [2] script error [3], [4]: [5] Line [6], Column [7], [8]
2412741=Configuration information for product [2] is corrupt. Invalid info: [2]
2422742=Marshaling to Server failed: [2]
2432743=Could not execute custom action [2], location: [3], command: [4]
2442744=EXE failed called by custom action [2], location: [3], command: [4]
2452745=Transform [2] invalid for package [3]. Expected language [4], found language [5].
2462746=Transform [2] invalid for package [3]. Expected product [4], found product [5].
2472747=Transform [2] invalid for package [3]. Expected product version < [4], found product version [5].
2482748=Transform [2] invalid for package [3]. Expected product version <= [4], found product version [5].
2492749=Transform [2] invalid for package [3]. Expected product version == [4], found product version [5].
2502750=Transform [2] invalid for package [3]. Expected product version >= [4], found product version [5].
2512751=Transform [2] invalid for package [3]. Expected product version > [4], found product version [5].
2522752=Could not open transform [2] stored as child storage of package [4].
2532753=The File '[2]' is not marked for installation.
2542754=The File '[2]' is not a valid patch file.
2552755=Server returned unexpected error [2] attempting to install package [3].
2562756=The property '[2]' was used as a directory property in one or more tables, but no value was ever assigned.
2572757=Could not create summary info for transform [2].
2582758=Transform [2] doesn't contain a MSI version.
2592759=Transform [2] version [3] incompatible with engine; Min: [4], Max: [5].
2602760=Transform [2] invalid for package [3]. Expected upgrade code [4], found [5].
2612761=Cannot begin transaction. Global mutex not properly initialized.
2622762=Cannot write script record. Transaction not started.
2632763=Cannot run script. Transaction not started.
2642765=Assembly name missing from AssemblyName table : Component: [4].
2652766=The file [2] is an invalid MSI storage file.
2662767=No more data{ while enumerating [2]}.
2672768=Transform in patch package is invalid.
2682769=Custom Action [2] did not close [3] handles.
2692770=Cached folder [2] not defined in internal cache folder table.
2702771=Upgrade of feature [2] has a missing component.
2712772=New upgrade feature [2] must be a leaf feature.
2722801=Unknown Message -- Type [2]. No action is taken.
2732802=No publisher is found for the event [2].
2742803=Dialog View did not find a record for the dialog [2].
2752804=On activation of the control [3] on dialog [2], failed to evaluate the condition [3].
2762805=
2772806=The dialog [2] failed to evaluate the condition [3].
2782807=The action [2] is not recognized.
2792808=Default button is ill-defined on dialog [2].
2802809=On the dialog [2] the next control pointers do not form a cycle. There is a pointer from [3] to [4], but there is no further pointer.
2812810=On the dialog [2] the next control pointers do not form a cycle. There is a pointer from both [3] and [5] to [4].
2822811=On dialog [2] control [3] has to take focus, but it is unable to do so.
2832812=The event [2] is not recognized.
2842813=The EndDialog event was called with the argument [2], but the dialog has a parent.
2852814=On the dialog [2] the control [3] names a non-existent control [4] as the next control.
2862815=ControlCondition table has a row without condition for the dialog [2].
2872816=The EventMapping table refers to an invalid control [4] on dialog [2] for the event [3].
2882817=The event [2] failed to set the attribute for the control [4] on dialog [3].
2892818=In the ControlEvent table EndDialog has an unrecognized argument [2].
2902819=Control [3] on dialog [2] needs a property linked to it.
2912820=Attempted to initialize an already initialized handler.
2922821=Attempted to initialize an already initialized dialog: [2].
2932822=No other method can be called on dialog [2] until all the controls are added.
2942823=Attempted to initialize an already initialized control: [3] on dialog [2].
2952824=The dialog attribute [3] needs a record of at least [2] field(s).
2962825=The control attribute [3] needs a record of at least [2] field(s).
2972826=Control [3] on dialog [2] extends beyond the boundaries of the dialog [4] by [5] pixels.
2982827=The button [4] on the radio button group [3] on dialog [2] extends beyond the boundaries of the group [5] by [6] pixels.
2992828=Tried to remove control [3] from dialog [2], but the control is not part of the dialog.
3002829=Attempt to use an uninitialized dialog.
3012830=Attempt to use an uninitialized control on dialog [2].
3022831=The control [3] on dialog [2] does not support [5] the attribute [4].
3032832=The dialog [2] does not support the attribute [3].
3042833=Control [4] on dialog [3] ignored the message [2].
3052834=The next pointers on the dialog [2] do not form a single loop.
3062835=The control [2] was not found on dialog [3].
3072836=The control [3] on the dialog [2] cannot take focus.
3082837=The control [3] on dialog [2] wants the win proc to return [4].
3092838=The item [2] in the selection table has itself as a parent.
3102839=Setting the property [2] failed.
3112840=Error dialog name mismatch.
3122841=No OK button was found on the error dialog
3132842=No text field was found on the error dialog.
3142843=The ErrorString attribute is not supported for standard dialogs.
3152844=Cannot execute an error dialog if the error string is not set.
3162845=The total width of the buttons exceeds the size of the error dialog.
3172846=SetFocus did not find the required control on the error dialog.
3182847=The control [3] on dialog [2] has both the icon and the bitmap style set.
3192848=Tried to set control [3] as the default button on dialog [2], but the control does not exist.
3202849=The control [3] on dialog [2] is of a type, that cannot be integer valued.
3212850=Unrecognized volume type.
3222851=The data for the icon [2] is not valid.
3232852=At least one control has to be added to dialog [2] before it is used.
3242853=Dialog [2] is a modeless dialog. The execute method should not be called on it.
3252854=On the dialog [2] the control [3] is designated as first active control, but there is no such control.
3262855=The radio button group [3] on dialog [2] has fewer than 2 buttons.
3272856=Creating a second copy of the dialog [2].
3282857=The directory [2] is mentioned in the selection table but not found.
3292858=The data for the bitmap [2] is not valid.
3302859=Test error message.
3312860=Cancel button is ill-defined on dialog [2].
3322861=The next pointers for the radio buttons on dialog [2] control [3] do not form a cycle.
3332862=The attributes for the control [3] on dialog [2] do not define a valid icon size. Setting the size to 16.
3342863=The control [3] on dialog [2] needs the icon [4] in size [5]x[5], but that size is not available. Loading the first available size.
3352864=The control [3] on dialog [2] received a browse event, but there is no configurable directory for the present selection. Likely cause: browse button is not authored correctly.
3362865=Control [3] on billboard [2] extends beyond the boundaries of the billboard [4] by [5] pixels.
3372866=The dialog [2] is not allowed to return the argument [3].
3382867=The error dialog property is not set.
3392868=The error dialog [2] does not have the error style bit set.
3402869=The dialog [2] has the error style bit set, but is not an error dialog.
3412870=The help string [4] for control [3] on dialog [2] does not contain the separator character.
3422871=The [2] table is out of date: [3]
3432872=The argument of the CheckPath control event on dialog [2] is invalid.
3442873=On the dialog [2] the control [3] has an invalid string length limit: [4]
3452874=Changing the text font to [2] failed.
3462875=Changing the text color to [2] failed.
3472876=The control [3] on dialog [2] had to truncate the string: [4]
3482877=The binary data [2] was not found.
3492878=On the dialog [2] the control [3] has a possible value: [4]. This is an invalid or duplicate value.
3502879=The control [3] on dialog [2] cannot parse the mask string: [4]
3512880=Do not perform the remaining control events.
3522881=Initialization failed.
3532882=Dialog window class registration failed.
3542883=CreateNewDialog failed for the dialog [2].
3552884=Failed to create a window for the dialog [2]!
3562885=Failed to create the control [3] on the dialog [2].
3572886=Creating the [2] table failed.
3582887=Creating a cursor to the [2] table failed.
3592888=Executing the [2] view failed.
3602889=Creating the window for the control [3] on dialog [2] failed.
3612890=The handler failed in creating an initialized dialog.
3622891=Failed to destroy window for dialog [2].
3632892=[2] is an integer only control, [3] is not a valid integer value.
3642893=The control [3] on dialog [2] can accept property values that are at most [5] characters long. The value [4] exceeds this limit, and has been truncated.
3652894=Loading RichEd20.dll failed. GetLastError() returned: [2]
3662895=Freeing RichEd20.dll failed. GetLastError() returned: [2]
3672896=Executing action [2] failed.
3682897=Failed to create any [2] font on this system.
3692898=For [2] text style, the system created a '[3]' font, in [4] character set.
3702899=Failed to create [2] text style. GetLastError() returned: [3].
3712901=Invalid parameter to operation [2]: Parameter [3]
3722902=Operation [2] called out of sequence.
3732903=The file [2] is missing.
3742904=Could not BindImage file [2].
3752905=Could not read record from script file [2].
3762906=Missing header in script file [2].
3772907=Could not create secure security descriptor. Error: [2]
3782908=Could not register component [2].
3792909=Could not unregister component [2].
3802910=Could not determine user's security id.
3812911=Could not remove the folder [2].
3822912=Could not schedule file [2] for removal on reboot.
3832919=No cabinet specified for compressed file: [2]
3842920=Source directory not specified for file [2].
3852924=Script [2] version unsupported. Script version: [3], minimum version: [4], maximum version: [5].
3862927=ShellFolder id [2] is invalid.
3872928=Exceeded maximum number of sources. Skipping source '[2]'.
3882929=Could not determine publishing root. Error: [2]
3892932=Could not create file [2] from script data. Error: [3]
3902933=Could not initialize rollback script [2].
3912934=Could not secure transform [2]. Error [3]
3922935=Could not un-secure transform [2]. Error [3]
3932936=Could not find transform [2].
3942937=The Windows Installer cannot install a system file protection catalog. Catalog: [2], Error: [3]
3952938=The Windows Installer cannot retrieve a system file protection catalog from the cache. Catalog: [2], Error: [3]
3962939=The Windows Installer cannot delete a system file protection catalog from the cache. Catalog: [2], Error: [3]
3972940=Directory Manager not supplied for source resolution.
3982941=Unable to compute the CRC for file [2].
3992942=BindImage action has not been executed on [2] file.
4002943=This version of Windows does not support deploying 64-bit packages. The script [2] is for a 64-bit package.
4012944=GetProductAssignmentType failed.
4022945=Installation of ComPlus App [2] failed with error [3].
4033001=The patches in this list contain incorrect sequencing information: [2][3][4][5][6][7][8][9][10][11][12][13][14][15][16]. 3.0
4043002=Patch [2] contains invalid sequencing information.
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Exceptions.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Exceptions.cs
new file mode 100644
index 00000000..4954cda0
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Exceptions.cs
@@ -0,0 +1,573 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Collections.Generic;
9 using System.Globalization;
10 using System.Runtime.Serialization;
11 using System.Diagnostics.CodeAnalysis;
12
13 /// <summary>
14 /// Base class for Windows Installer exceptions.
15 /// </summary>
16 [Serializable]
17 public class InstallerException : SystemException
18 {
19 private int errorCode;
20 private object[] errorData;
21
22 /// <summary>
23 /// Creates a new InstallerException with a specified error message and a reference to the
24 /// inner exception that is the cause of this exception.
25 /// </summary>
26 /// <param name="msg">The message that describes the error.</param>
27 /// <param name="innerException">The exception that is the cause of the current exception. If the
28 /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
29 /// is raised in a catch block that handles the inner exception.</param>
30 public InstallerException(string msg, Exception innerException)
31 : this(0, msg, innerException)
32 {
33 }
34
35 /// <summary>
36 /// Creates a new InstallerException with a specified error message.
37 /// </summary>
38 /// <param name="msg">The message that describes the error.</param>
39 public InstallerException(string msg)
40 : this(0, msg)
41 {
42 }
43
44 /// <summary>
45 /// Creates a new InstallerException.
46 /// </summary>
47 public InstallerException()
48 : this(0, null)
49 {
50 }
51
52 internal InstallerException(int errorCode, string msg, Exception innerException)
53 : base(msg, innerException)
54 {
55 this.errorCode = errorCode;
56 this.SaveErrorRecord();
57 }
58
59 internal InstallerException(int errorCode, string msg)
60 : this(errorCode, msg, null)
61 {
62 }
63
64 /// <summary>
65 /// Initializes a new instance of the InstallerException class with serialized data.
66 /// </summary>
67 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
68 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
69 protected InstallerException(SerializationInfo info, StreamingContext context) : base(info, context)
70 {
71 if (info == null)
72 {
73 throw new ArgumentNullException("info");
74 }
75
76 this.errorCode = info.GetInt32("msiErrorCode");
77 }
78
79 /// <summary>
80 /// Gets the system error code that resulted in this exception, or 0 if not applicable.
81 /// </summary>
82 public int ErrorCode
83 {
84 get
85 {
86 return this.errorCode;
87 }
88 }
89
90 /// <summary>
91 /// Gets a message that describes the exception. This message may contain detailed
92 /// formatted error data if it was available.
93 /// </summary>
94 public override String Message
95 {
96 get
97 {
98 string msg = base.Message;
99 using (Record errorRec = this.GetErrorRecord())
100 {
101 if (errorRec != null)
102 {
103 string errorMsg = Installer.GetErrorMessage(errorRec, CultureInfo.InvariantCulture);
104 msg = Combine(msg, errorMsg);
105 }
106 }
107 return msg;
108 }
109 }
110
111 /// <summary>
112 /// Sets the SerializationInfo with information about the exception.
113 /// </summary>
114 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
115 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
116 public override void GetObjectData(SerializationInfo info, StreamingContext context)
117 {
118 if (info == null)
119 {
120 throw new ArgumentNullException("info");
121 }
122
123 info.AddValue("msiErrorCode", this.errorCode);
124 base.GetObjectData(info, context);
125 }
126
127 /// <summary>
128 /// Gets extended information about the error, or null if no further information
129 /// is available.
130 /// </summary>
131 /// <returns>A Record object. Field 1 of the Record contains the installer
132 /// message code. Other fields contain data specific to the particular error.</returns>
133 /// <remarks><p>
134 /// If the record is passed to <see cref="Session.Message"/>, it is formatted
135 /// by looking up the string in the current database. If there is no installation
136 /// session, the formatted error message may be obtained by a query on the Error table using
137 /// the error code, followed by a call to <see cref="Record.ToString()"/>.
138 /// Alternatively, the standard MSI message can by retrieved by calling the
139 /// <see cref="Installer.GetErrorMessage(Record,CultureInfo)"/> method.
140 /// </p><p>
141 /// The following methods and properties may report extended error data:
142 /// <list type="bullet">
143 /// <item><see cref="Database"/> (constructor)</item>
144 /// <item><see cref="Database"/>.<see cref="Database.ApplyTransform(string,TransformErrors)"/></item>
145 /// <item><see cref="Database"/>.<see cref="Database.Commit"/></item>
146 /// <item><see cref="Database"/>.<see cref="Database.Execute(string,object[])"/></item>
147 /// <item><see cref="Database"/>.<see cref="Database.ExecuteQuery(string,object[])"/></item>
148 /// <item><see cref="Database"/>.<see cref="Database.ExecuteIntegerQuery(string,object[])"/></item>
149 /// <item><see cref="Database"/>.<see cref="Database.ExecuteStringQuery(string,object[])"/></item>
150 /// <item><see cref="Database"/>.<see cref="Database.Export"/></item>
151 /// <item><see cref="Database"/>.<see cref="Database.ExportAll"/></item>
152 /// <item><see cref="Database"/>.<see cref="Database.GenerateTransform"/></item>
153 /// <item><see cref="Database"/>.<see cref="Database.Import"/></item>
154 /// <item><see cref="Database"/>.<see cref="Database.ImportAll"/></item>
155 /// <item><see cref="Database"/>.<see cref="Database.Merge(Database,string)"/></item>
156 /// <item><see cref="Database"/>.<see cref="Database.OpenView"/></item>
157 /// <item><see cref="Database"/>.<see cref="Database.SummaryInfo"/></item>
158 /// <item><see cref="Database"/>.<see cref="Database.ViewTransform"/></item>
159 /// <item><see cref="View"/>.<see cref="View.Assign"/></item>
160 /// <item><see cref="View"/>.<see cref="View.Delete"/></item>
161 /// <item><see cref="View"/>.<see cref="View.Execute(Record)"/></item>
162 /// <item><see cref="View"/>.<see cref="View.Insert"/></item>
163 /// <item><see cref="View"/>.<see cref="View.InsertTemporary"/></item>
164 /// <item><see cref="View"/>.<see cref="View.Merge"/></item>
165 /// <item><see cref="View"/>.<see cref="View.Modify"/></item>
166 /// <item><see cref="View"/>.<see cref="View.Refresh"/></item>
167 /// <item><see cref="View"/>.<see cref="View.Replace"/></item>
168 /// <item><see cref="View"/>.<see cref="View.Seek"/></item>
169 /// <item><see cref="View"/>.<see cref="View.Update"/></item>
170 /// <item><see cref="View"/>.<see cref="View.Validate"/></item>
171 /// <item><see cref="View"/>.<see cref="View.ValidateFields"/></item>
172 /// <item><see cref="View"/>.<see cref="View.ValidateDelete"/></item>
173 /// <item><see cref="View"/>.<see cref="View.ValidateNew"/></item>
174 /// <item><see cref="SummaryInfo"/> (constructor)</item>
175 /// <item><see cref="Record"/>.<see cref="Record.SetStream(int,string)"/></item>
176 /// <item><see cref="Session"/>.<see cref="Session.SetInstallLevel"/></item>
177 /// <item><see cref="Session"/>.<see cref="Session.GetSourcePath"/></item>
178 /// <item><see cref="Session"/>.<see cref="Session.GetTargetPath"/></item>
179 /// <item><see cref="Session"/>.<see cref="Session.SetTargetPath"/></item>
180 /// <item><see cref="ComponentInfo"/>.<see cref="ComponentInfo.CurrentState"/></item>
181 /// <item><see cref="FeatureInfo"/>.<see cref="FeatureInfo.CurrentState"/></item>
182 /// <item><see cref="FeatureInfo"/>.<see cref="FeatureInfo.ValidStates"/></item>
183 /// <item><see cref="FeatureInfo"/>.<see cref="FeatureInfo.GetCost"/></item>
184 /// </list>
185 /// </p><p>
186 /// The Record object should be <see cref="InstallerHandle.Close"/>d after use.
187 /// It is best that the handle be closed manually as soon as it is no longer
188 /// needed, as leaving lots of unused handles open can degrade performance.
189 /// </p><p>
190 /// Win32 MSI API:
191 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetlasterrorrecord.asp">MsiGetLastErrorRecord</a>
192 /// </p></remarks>
193 [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
194 public Record GetErrorRecord()
195 {
196 return this.errorData != null ? new Record(this.errorData) : null;
197 }
198
199 internal static Exception ExceptionFromReturnCode(uint errorCode)
200 {
201 return ExceptionFromReturnCode(errorCode, null);
202 }
203
204 internal static Exception ExceptionFromReturnCode(uint errorCode, string msg)
205 {
206 msg = Combine(GetSystemMessage(errorCode), msg);
207 switch (errorCode)
208 {
209 case (uint) NativeMethods.Error.FILE_NOT_FOUND:
210 case (uint) NativeMethods.Error.PATH_NOT_FOUND: return new FileNotFoundException(msg);
211
212 case (uint) NativeMethods.Error.INVALID_PARAMETER:
213 case (uint) NativeMethods.Error.DIRECTORY:
214 case (uint) NativeMethods.Error.UNKNOWN_PROPERTY:
215 case (uint) NativeMethods.Error.UNKNOWN_PRODUCT:
216 case (uint) NativeMethods.Error.UNKNOWN_FEATURE:
217 case (uint) NativeMethods.Error.UNKNOWN_COMPONENT: return new ArgumentException(msg);
218
219 case (uint) NativeMethods.Error.BAD_QUERY_SYNTAX: return new BadQuerySyntaxException(msg);
220
221 case (uint) NativeMethods.Error.INVALID_HANDLE_STATE:
222 case (uint) NativeMethods.Error.INVALID_HANDLE:
223 InvalidHandleException ihex = new InvalidHandleException(msg);
224 ihex.errorCode = (int) errorCode;
225 return ihex;
226
227 case (uint) NativeMethods.Error.INSTALL_USEREXIT: return new InstallCanceledException(msg);
228
229 case (uint) NativeMethods.Error.CALL_NOT_IMPLEMENTED: return new NotImplementedException(msg);
230
231 default: return new InstallerException((int) errorCode, msg);
232 }
233 }
234
235 internal static string GetSystemMessage(uint errorCode)
236 {
237 const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
238 const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
239
240 StringBuilder buf = new StringBuilder(1024);
241 uint formatCount = NativeMethods.FormatMessage(
242 FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
243 IntPtr.Zero,
244 (uint) errorCode,
245 0,
246 buf,
247 (uint) buf.Capacity,
248 IntPtr.Zero);
249
250 if (formatCount != 0)
251 {
252 return buf.ToString().Trim();
253 }
254 else
255 {
256 return null;
257 }
258 }
259
260 internal void SaveErrorRecord()
261 {
262 // TODO: pass an affinity handle here?
263 int recordHandle = RemotableNativeMethods.MsiGetLastErrorRecord(0);
264 if (recordHandle != 0)
265 {
266 using (Record errorRec = new Record((IntPtr) recordHandle, true, null))
267 {
268 this.errorData = new object[errorRec.FieldCount];
269 for (int i = 0; i < this.errorData.Length; i++)
270 {
271 this.errorData[i] = errorRec[i + 1];
272 }
273 }
274 }
275 else
276 {
277 this.errorData = null;
278 }
279 }
280
281 private static string Combine(string msg1, string msg2)
282 {
283 if (msg1 == null) return msg2;
284 if (msg2 == null) return msg1;
285 return msg1 + " " + msg2;
286 }
287 }
288
289 /// <summary>
290 /// User Canceled the installation.
291 /// </summary>
292 [Serializable]
293 public class InstallCanceledException : InstallerException
294 {
295 /// <summary>
296 /// Creates a new InstallCanceledException with a specified error message and a reference to the
297 /// inner exception that is the cause of this exception.
298 /// </summary>
299 /// <param name="msg">The message that describes the error.</param>
300 /// <param name="innerException">The exception that is the cause of the current exception. If the
301 /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
302 /// is raised in a catch block that handles the inner exception.</param>
303 public InstallCanceledException(string msg, Exception innerException)
304 : base((int) NativeMethods.Error.INSTALL_USEREXIT, msg, innerException)
305 {
306 }
307
308 /// <summary>
309 /// Creates a new InstallCanceledException with a specified error message.
310 /// </summary>
311 /// <param name="msg">The message that describes the error.</param>
312 public InstallCanceledException(string msg)
313 : this(msg, null)
314 {
315 }
316
317 /// <summary>
318 /// Creates a new InstallCanceledException.
319 /// </summary>
320 public InstallCanceledException()
321 : this(null, null)
322 {
323 }
324
325 /// <summary>
326 /// Initializes a new instance of the InstallCanceledException class with serialized data.
327 /// </summary>
328 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
329 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
330 protected InstallCanceledException(SerializationInfo info, StreamingContext context)
331 : base(info, context)
332 {
333 }
334 }
335
336 /// <summary>
337 /// A bad SQL query string was passed to <see cref="Database.OpenView"/> or <see cref="Database.Execute(string,object[])"/>.
338 /// </summary>
339 [Serializable]
340 public class BadQuerySyntaxException : InstallerException
341 {
342 /// <summary>
343 /// Creates a new BadQuerySyntaxException with a specified error message and a reference to the
344 /// inner exception that is the cause of this exception.
345 /// </summary>
346 /// <param name="msg">The message that describes the error.</param>
347 /// <param name="innerException">The exception that is the cause of the current exception. If the
348 /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
349 /// is raised in a catch block that handles the inner exception.</param>
350 public BadQuerySyntaxException(string msg, Exception innerException)
351 : base((int) NativeMethods.Error.BAD_QUERY_SYNTAX, msg, innerException)
352 {
353 }
354
355 /// <summary>
356 /// Creates a new BadQuerySyntaxException with a specified error message.
357 /// </summary>
358 /// <param name="msg">The message that describes the error.</param>
359 public BadQuerySyntaxException(string msg)
360 : this(msg, null)
361 {
362 }
363
364 /// <summary>
365 /// Creates a new BadQuerySyntaxException.
366 /// </summary>
367 public BadQuerySyntaxException()
368 : this(null, null)
369 {
370 }
371
372 /// <summary>
373 /// Initializes a new instance of the BadQuerySyntaxException class with serialized data.
374 /// </summary>
375 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
376 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
377 protected BadQuerySyntaxException(SerializationInfo info, StreamingContext context)
378 : base(info, context)
379 {
380 }
381 }
382
383 /// <summary>
384 /// A method was called on an invalid installer handle. The handle may have been already closed.
385 /// </summary>
386 [Serializable]
387 public class InvalidHandleException : InstallerException
388 {
389 /// <summary>
390 /// Creates a new InvalidHandleException with a specified error message and a reference to the
391 /// inner exception that is the cause of this exception.
392 /// </summary>
393 /// <param name="msg">The message that describes the error.</param>
394 /// <param name="innerException">The exception that is the cause of the current exception. If the
395 /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
396 /// is raised in a catch block that handles the inner exception.</param>
397 public InvalidHandleException(string msg, Exception innerException)
398 : base((int) NativeMethods.Error.INVALID_HANDLE, msg, innerException)
399 {
400 }
401
402 /// <summary>
403 /// Creates a new InvalidHandleException with a specified error message.
404 /// </summary>
405 /// <param name="msg">The message that describes the error.</param>
406 public InvalidHandleException(string msg)
407 : this(msg, null)
408 {
409 }
410
411 /// <summary>
412 /// Creates a new InvalidHandleException.
413 /// </summary>
414 public InvalidHandleException()
415 : this(null, null)
416 {
417 }
418
419 /// <summary>
420 /// Initializes a new instance of the InvalidHandleException class with serialized data.
421 /// </summary>
422 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
423 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
424 protected InvalidHandleException(SerializationInfo info, StreamingContext context)
425 : base(info, context)
426 {
427 }
428 }
429
430 /// <summary>
431 /// A failure occurred when executing <see cref="Database.Merge(Database,string)"/>. The exception may contain
432 /// details about the merge conflict.
433 /// </summary>
434 [Serializable]
435 public class MergeException : InstallerException
436 {
437 private IList<string> conflictTables;
438 private IList<int> conflictCounts;
439
440 /// <summary>
441 /// Creates a new MergeException with a specified error message and a reference to the
442 /// inner exception that is the cause of this exception.
443 /// </summary>
444 /// <param name="msg">The message that describes the error.</param>
445 /// <param name="innerException">The exception that is the cause of the current exception. If the
446 /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
447 /// is raised in a catch block that handles the inner exception.</param>
448 public MergeException(string msg, Exception innerException)
449 : base(msg, innerException)
450 {
451 }
452
453 /// <summary>
454 /// Creates a new MergeException with a specified error message.
455 /// </summary>
456 /// <param name="msg">The message that describes the error.</param>
457 public MergeException(string msg)
458 : base(msg)
459 {
460 }
461
462 /// <summary>
463 /// Creates a new MergeException.
464 /// </summary>
465 public MergeException()
466 : base()
467 {
468 }
469
470 internal MergeException(Database db, string conflictsTableName)
471 : base("Merge failed.")
472 {
473 if (conflictsTableName != null)
474 {
475 IList<string> conflictTableList = new List<string>();
476 IList<int> conflictCountList = new List<int>();
477
478 using (View view = db.OpenView("SELECT `Table`, `NumRowMergeConflicts` FROM `" + conflictsTableName + "`"))
479 {
480 view.Execute();
481
482 foreach (Record rec in view) using (rec)
483 {
484 conflictTableList.Add(rec.GetString(1));
485 conflictCountList.Add((int) rec.GetInteger(2));
486 }
487 }
488
489 this.conflictTables = conflictTableList;
490 this.conflictCounts = conflictCountList;
491 }
492 }
493
494 /// <summary>
495 /// Initializes a new instance of the MergeException class with serialized data.
496 /// </summary>
497 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
498 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
499 protected MergeException(SerializationInfo info, StreamingContext context) : base(info, context)
500 {
501 if (info == null)
502 {
503 throw new ArgumentNullException("info");
504 }
505
506 this.conflictTables = (string[]) info.GetValue("mergeConflictTables", typeof(string[]));
507 this.conflictCounts = (int[]) info.GetValue("mergeConflictCounts", typeof(int[]));
508 }
509
510 /// <summary>
511 /// Gets the number of merge conflicts in each table, corresponding to the tables returned by
512 /// <see cref="ConflictTables"/>.
513 /// </summary>
514 public IList<int> ConflictCounts
515 {
516 get
517 {
518 return new List<int>(this.conflictCounts);
519 }
520 }
521
522 /// <summary>
523 /// Gets the list of tables containing merge conflicts.
524 /// </summary>
525 public IList<string> ConflictTables
526 {
527 get
528 {
529 return new List<string>(this.conflictTables);
530 }
531 }
532
533 /// <summary>
534 /// Gets a message that describes the merge conflits.
535 /// </summary>
536 public override String Message
537 {
538 get
539 {
540 StringBuilder msg = new StringBuilder(base.Message);
541 if (this.conflictTables != null)
542 {
543 for (int i = 0; i < this.conflictTables.Count; i++)
544 {
545 msg.Append(i == 0 ? " Conflicts: " : ", ");
546 msg.Append(this.conflictTables[i]);
547 msg.Append('(');
548 msg.Append(this.conflictCounts[i]);
549 msg.Append(')');
550 }
551 }
552 return msg.ToString();
553 }
554 }
555
556 /// <summary>
557 /// Sets the SerializationInfo with information about the exception.
558 /// </summary>
559 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
560 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
561 public override void GetObjectData(SerializationInfo info, StreamingContext context)
562 {
563 if (info == null)
564 {
565 throw new ArgumentNullException("info");
566 }
567
568 info.AddValue("mergeConflictTables", this.conflictTables);
569 info.AddValue("mergeConflictCounts", this.conflictCounts);
570 base.GetObjectData(info, context);
571 }
572 }
573}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/ExternalUIHandler.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/ExternalUIHandler.cs
new file mode 100644
index 00000000..08f00867
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/ExternalUIHandler.cs
@@ -0,0 +1,223 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Collections;
7 using System.Runtime.InteropServices;
8 using System.Diagnostics.CodeAnalysis;
9
10 /// <summary>
11 /// Defines a callback function that the installer calls for progress notification and error messages.
12 /// </summary>
13 public delegate MessageResult ExternalUIHandler(
14 InstallMessage messageType,
15 string message,
16 MessageButtons buttons,
17 MessageIcon icon,
18 MessageDefaultButton defaultButton);
19
20 /// <summary>
21 /// [MSI 3.1] Defines a callback function that the installer calls for record-based progress notification and error messages.
22 /// </summary>
23 public delegate MessageResult ExternalUIRecordHandler(
24 InstallMessage messageType,
25 Record messageRecord,
26 MessageButtons buttons,
27 MessageIcon icon,
28 MessageDefaultButton defaultButton);
29
30 internal delegate int NativeExternalUIHandler(IntPtr context, int messageType, [MarshalAs(UnmanagedType.LPWStr)] string message);
31
32 internal delegate int NativeExternalUIRecordHandler(IntPtr context, int messageType, int recordHandle);
33
34 internal class ExternalUIProxy
35 {
36 private ExternalUIHandler handler;
37
38 internal ExternalUIProxy(ExternalUIHandler handler)
39 {
40 this.handler = handler;
41 }
42
43 public ExternalUIHandler Handler
44 {
45 get { return this.handler; }
46 }
47
48 [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
49 public int ProxyHandler(IntPtr contextPtr, int messageType, [MarshalAs(UnmanagedType.LPWStr)] string message)
50 {
51 try
52 {
53 int msgType = messageType & 0x7F000000;
54 int buttons = messageType & 0x0000000F;
55 int icon = messageType & 0x000000F0;
56 int defButton = messageType & 0x00000F00;
57
58 return (int) this.handler(
59 (InstallMessage) msgType,
60 message,
61 (MessageButtons) buttons,
62 (MessageIcon) icon,
63 (MessageDefaultButton) defButton);
64 }
65 catch
66 {
67 return (int) MessageResult.Error;
68 }
69 }
70 }
71
72 internal class ExternalUIRecordProxy
73 {
74 private ExternalUIRecordHandler handler;
75
76 internal ExternalUIRecordProxy(ExternalUIRecordHandler handler)
77 {
78 this.handler = handler;
79 }
80
81 public ExternalUIRecordHandler Handler
82 {
83 get { return this.handler; }
84 }
85
86 [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
87 public int ProxyHandler(IntPtr contextPtr, int messageType, int recordHandle)
88 {
89 try
90 {
91 int msgType = messageType & 0x7F000000;
92 int buttons = messageType & 0x0000000F;
93 int icon = messageType & 0x000000F0;
94 int defButton = messageType & 0x00000F00;
95
96 Record msgRec = (recordHandle != 0 ? Record.FromHandle((IntPtr) recordHandle, false) : null);
97 using (msgRec)
98 {
99 return (int) this.handler(
100 (InstallMessage) msgType,
101 msgRec,
102 (MessageButtons) buttons,
103 (MessageIcon) icon,
104 (MessageDefaultButton) defButton);
105 }
106 }
107 catch
108 {
109 return (int) MessageResult.Error;
110 }
111 }
112 }
113
114 public static partial class Installer
115 {
116 private static IList externalUIHandlers = ArrayList.Synchronized(new ArrayList());
117
118 /// <summary>
119 /// Enables an external user-interface handler. This external UI handler is called before the
120 /// normal internal user-interface handler. The external UI handler has the option to suppress
121 /// the internal UI by returning a non-zero value to indicate that it has handled the messages.
122 /// </summary>
123 /// <param name="uiHandler">A callback delegate that handles the UI messages</param>
124 /// <param name="messageFilter">Specifies which messages to handle using the external message handler.
125 /// If the external handler returns a non-zero result, then that message will not be sent to the UI,
126 /// instead the message will be logged if logging has been enabled.</param>
127 /// <returns>The previously set external handler, or null if there was no previously set handler</returns>
128 /// <remarks><p>
129 /// To restore the previous UI handler, a second call is made to SetExternalUI using the
130 /// ExternalUIHandler returned by the first call to SetExternalUI and specifying
131 /// <see cref="InstallLogModes.None"/> as the message filter.
132 /// </p><p>
133 /// The external user interface handler does not have full control over the external user
134 /// interface unless <see cref="SetInternalUI(InstallUIOptions)"/> is called with the uiLevel parameter set to
135 /// <see cref="InstallUIOptions.Silent"/>. If SetInternalUI is not called, the internal user
136 /// interface level defaults to <see cref="InstallUIOptions.Basic"/>. As a result, any message not
137 /// handled by the external user interface handler is handled by Windows Installer. The initial
138 /// "Preparing to install..." dialog always appears even if the external user interface
139 /// handler handles all messages.
140 /// </p><p>
141 /// SetExternalUI should only be called from a bootstrapping application. You cannot call
142 /// it from a custom action
143 /// </p><p>
144 /// Win32 MSI API:
145 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetexternalui.asp">MsiSetExternalUI</a>
146 /// </p></remarks>
147 public static ExternalUIHandler SetExternalUI(ExternalUIHandler uiHandler, InstallLogModes messageFilter)
148 {
149 NativeExternalUIHandler nativeHandler = null;
150 if (uiHandler != null)
151 {
152 nativeHandler = new ExternalUIProxy(uiHandler).ProxyHandler;
153 Installer.externalUIHandlers.Add(nativeHandler);
154 }
155 NativeExternalUIHandler oldNativeHandler = NativeMethods.MsiSetExternalUI(nativeHandler, (uint) messageFilter, IntPtr.Zero);
156 if (oldNativeHandler != null && oldNativeHandler.Target is ExternalUIProxy)
157 {
158 Installer.externalUIHandlers.Remove(oldNativeHandler);
159 return ((ExternalUIProxy) oldNativeHandler.Target).Handler;
160 }
161 else
162 {
163 return null;
164 }
165 }
166
167 /// <summary>
168 /// [MSI 3.1] Enables a record-based external user-interface handler. This external UI handler is called
169 /// before the normal internal user-interface handler. The external UI handler has the option to suppress
170 /// the internal UI by returning a non-zero value to indicate that it has handled the messages.
171 /// </summary>
172 /// <param name="uiHandler">A callback delegate that handles the UI messages</param>
173 /// <param name="messageFilter">Specifies which messages to handle using the external message handler.
174 /// If the external handler returns a non-zero result, then that message will not be sent to the UI,
175 /// instead the message will be logged if logging has been enabled.</param>
176 /// <returns>The previously set external handler, or null if there was no previously set handler</returns>
177 /// <remarks><p>
178 /// To restore the previous UI handler, a second call is made to SetExternalUI using the
179 /// ExternalUIHandler returned by the first call to SetExternalUI and specifying
180 /// <see cref="InstallLogModes.None"/> as the message filter.
181 /// </p><p>
182 /// The external user interface handler does not have full control over the external user
183 /// interface unless <see cref="SetInternalUI(InstallUIOptions)"/> is called with the uiLevel parameter set to
184 /// <see cref="InstallUIOptions.Silent"/>. If SetInternalUI is not called, the internal user
185 /// interface level defaults to <see cref="InstallUIOptions.Basic"/>. As a result, any message not
186 /// handled by the external user interface handler is handled by Windows Installer. The initial
187 /// "Preparing to install..." dialog always appears even if the external user interface
188 /// handler handles all messages.
189 /// </p><p>
190 /// SetExternalUI should only be called from a bootstrapping application. You cannot call
191 /// it from a custom action
192 /// </p><p>
193 /// Win32 MSI API:
194 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetexternaluirecord.asp">MsiSetExternalUIRecord</a>
195 /// </p></remarks>
196 public static ExternalUIRecordHandler SetExternalUI(ExternalUIRecordHandler uiHandler, InstallLogModes messageFilter)
197 {
198 NativeExternalUIRecordHandler nativeHandler = null;
199 if (uiHandler != null)
200 {
201 nativeHandler = new ExternalUIRecordProxy(uiHandler).ProxyHandler;
202 Installer.externalUIHandlers.Add(nativeHandler);
203 }
204 NativeExternalUIRecordHandler oldNativeHandler;
205 uint ret = NativeMethods.MsiSetExternalUIRecord(nativeHandler, (uint) messageFilter, IntPtr.Zero, out oldNativeHandler);
206 if (ret != 0)
207 {
208 Installer.externalUIHandlers.Remove(nativeHandler);
209 throw InstallerException.ExceptionFromReturnCode(ret);
210 }
211
212 if (oldNativeHandler != null && oldNativeHandler.Target is ExternalUIRecordProxy)
213 {
214 Installer.externalUIHandlers.Remove(oldNativeHandler);
215 return ((ExternalUIRecordProxy) oldNativeHandler.Target).Handler;
216 }
217 else
218 {
219 return null;
220 }
221 }
222 }
223}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/FeatureInfo.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/FeatureInfo.cs
new file mode 100644
index 00000000..9a1a859a
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/FeatureInfo.cs
@@ -0,0 +1,497 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Text;
7 using System.Collections;
8 using System.Collections.Generic;
9
10 /// <summary>
11 /// Accessor for information about features within the context of an installation session.
12 /// </summary>
13 public sealed class FeatureInfoCollection : ICollection<FeatureInfo>
14 {
15 private Session session;
16
17 internal FeatureInfoCollection(Session session)
18 {
19 this.session = session;
20 }
21
22 /// <summary>
23 /// Gets information about a feature within the context of an installation session.
24 /// </summary>
25 /// <param name="feature">name of the feature</param>
26 /// <returns>feature object</returns>
27 public FeatureInfo this[string feature]
28 {
29 get
30 {
31 return new FeatureInfo(this.session, feature);
32 }
33 }
34
35 void ICollection<FeatureInfo>.Add(FeatureInfo item)
36 {
37 throw new InvalidOperationException();
38 }
39
40 void ICollection<FeatureInfo>.Clear()
41 {
42 throw new InvalidOperationException();
43 }
44
45 /// <summary>
46 /// Checks if the collection contains a feature.
47 /// </summary>
48 /// <param name="feature">name of the feature</param>
49 /// <returns>true if the feature is in the collection, else false</returns>
50 public bool Contains(string feature)
51 {
52 return this.session.Database.CountRows(
53 "Feature", "`Feature` = '" + feature + "'") == 1;
54 }
55
56 bool ICollection<FeatureInfo>.Contains(FeatureInfo item)
57 {
58 return item != null && this.Contains(item.Name);
59 }
60
61 /// <summary>
62 /// Copies the features into an array.
63 /// </summary>
64 /// <param name="array">array that receives the features</param>
65 /// <param name="arrayIndex">offset into the array</param>
66 public void CopyTo(FeatureInfo[] array, int arrayIndex)
67 {
68 foreach (FeatureInfo feature in this)
69 {
70 array[arrayIndex++] = feature;
71 }
72 }
73
74 /// <summary>
75 /// Gets the number of features defined for the product.
76 /// </summary>
77 public int Count
78 {
79 get
80 {
81 return this.session.Database.CountRows("Feature");
82 }
83 }
84
85 bool ICollection<FeatureInfo>.IsReadOnly
86 {
87 get
88 {
89 return true;
90 }
91 }
92
93 bool ICollection<FeatureInfo>.Remove(FeatureInfo item)
94 {
95 throw new InvalidOperationException();
96 }
97
98 /// <summary>
99 /// Enumerates the features in the collection.
100 /// </summary>
101 /// <returns>an enumerator over all features in the collection</returns>
102 public IEnumerator<FeatureInfo> GetEnumerator()
103 {
104 using (View featureView = this.session.Database.OpenView(
105 "SELECT `Feature` FROM `Feature`"))
106 {
107 featureView.Execute();
108
109 foreach (Record featureRec in featureView) using (featureRec)
110 {
111 string feature = featureRec.GetString(1);
112 yield return new FeatureInfo(this.session, feature);
113 }
114 }
115 }
116
117 IEnumerator IEnumerable.GetEnumerator()
118 {
119 return this.GetEnumerator();
120 }
121 }
122
123 /// <summary>
124 /// Provides access to information about a feature within the context of an installation session.
125 /// </summary>
126 public class FeatureInfo
127 {
128 private Session session;
129 private string name;
130
131 internal FeatureInfo(Session session, string name)
132 {
133 this.session = session;
134 this.name = name;
135 }
136
137 /// <summary>
138 /// Gets the name of the feature (primary key in the Feature table).
139 /// </summary>
140 public string Name
141 {
142 get
143 {
144 return this.name;
145 }
146 }
147
148 /// <summary>
149 /// Gets the current install state of the feature.
150 /// </summary>
151 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
152 /// <exception cref="ArgumentException">an unknown feature was requested</exception>
153 /// <remarks><p>
154 /// Win32 MSI API:
155 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeaturestate.asp">MsiGetFeatureState</a>
156 /// </p></remarks>
157 public InstallState CurrentState
158 {
159 get
160 {
161 int installState, actionState;
162 uint ret = RemotableNativeMethods.MsiGetFeatureState((int) this.session.Handle, this.name, out installState, out actionState);
163 if (ret != 0)
164 {
165 if (ret == (uint) NativeMethods.Error.UNKNOWN_FEATURE)
166 {
167 throw InstallerException.ExceptionFromReturnCode(ret, this.name);
168 }
169 else
170 {
171 throw InstallerException.ExceptionFromReturnCode(ret);
172 }
173 }
174
175 if (installState == (int) InstallState.Advertised)
176 {
177 return InstallState.Advertised;
178 }
179 return (InstallState) installState;
180 }
181 }
182
183 /// <summary>
184 /// Gets or sets the action state of the feature.
185 /// </summary>
186 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
187 /// <exception cref="ArgumentException">an unknown feature was requested</exception>
188 /// <remarks><p>
189 /// When changing the feature action, the action state of all the Components linked to the changed
190 /// Feature records are also updated appropriately, based on the new feature Select state.
191 /// All Features can be configured at once by specifying the keyword ALL instead of a specific feature name.
192 /// </p><p>
193 /// Win32 MSI APIs:
194 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeaturestate.asp">MsiGetFeatureState</a>,
195 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetfeaturestate.asp">MsiSetFeatureState</a>
196 /// </p></remarks>
197 public InstallState RequestState
198 {
199 get
200 {
201 int installState, actionState;
202 uint ret = RemotableNativeMethods.MsiGetFeatureState((int) this.session.Handle, this.name, out installState, out actionState);
203 if (ret != 0)
204 {
205 if (ret == (uint) NativeMethods.Error.UNKNOWN_FEATURE)
206 {
207 throw InstallerException.ExceptionFromReturnCode(ret, this.name);
208 }
209 else
210 {
211 throw InstallerException.ExceptionFromReturnCode(ret);
212 }
213 }
214 return (InstallState) actionState;
215 }
216
217 set
218 {
219 uint ret = RemotableNativeMethods.MsiSetFeatureState((int) this.session.Handle, this.name, (int) value);
220 if (ret != 0)
221 {
222 if (ret == (uint) NativeMethods.Error.UNKNOWN_FEATURE)
223 {
224 throw InstallerException.ExceptionFromReturnCode(ret, this.name);
225 }
226 else
227 {
228 throw InstallerException.ExceptionFromReturnCode(ret);
229 }
230 }
231 }
232 }
233
234 /// <summary>
235 /// Gets a list of valid installation states for the feature.
236 /// </summary>
237 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
238 /// <exception cref="ArgumentException">an unknown feature was requested</exception>
239 /// <remarks><p>
240 /// Win32 MSI API:
241 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeaturevalidstates.asp">MsiGetFeatureValidStates</a>
242 /// </p></remarks>
243 public ICollection<InstallState> ValidStates
244 {
245 get
246 {
247 List<InstallState> states = new List<InstallState>();
248 uint installState;
249 uint ret = RemotableNativeMethods.MsiGetFeatureValidStates((int) this.session.Handle, this.name, out installState);
250 if (ret != 0)
251 {
252 if (ret == (uint) NativeMethods.Error.UNKNOWN_FEATURE)
253 {
254 throw InstallerException.ExceptionFromReturnCode(ret, this.name);
255 }
256 else
257 {
258 throw InstallerException.ExceptionFromReturnCode(ret);
259 }
260 }
261
262 for (int i = 1; i <= (int) InstallState.Default; i++)
263 {
264 if (((int) installState & (1 << i)) != 0)
265 {
266 states.Add((InstallState) i);
267 }
268 }
269 return states.AsReadOnly();
270 }
271 }
272
273 /// <summary>
274 /// Gets or sets the attributes of the feature.
275 /// </summary>
276 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
277 /// <exception cref="ArgumentException">an unknown feature was requested</exception>
278 /// <remarks><p>
279 /// Win32 MSI APIs:
280 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeatureinfo.asp">MsiGetFeatureInfo</a>,
281 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetfeatureattributes.asp">MsiSetFeatureAttributes</a>
282 /// </p><p>
283 /// Since the lpAttributes paramter of
284 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeatureinfo.asp">MsiGetFeatureInfo</a>
285 /// does not contain an equivalent flag for <see cref="FeatureAttributes.UIDisallowAbsent"/>, this flag will
286 /// not be retrieved.
287 /// </p><p>
288 /// Since the dwAttributes parameter of
289 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetfeatureattributes.asp">MsiSetFeatureAttributes</a>
290 /// does not contain an equivalent flag for <see cref="FeatureAttributes.UIDisallowAbsent"/>, the presence
291 /// of this flag will be ignored.
292 /// </p></remarks>
293 public FeatureAttributes Attributes
294 {
295 get
296 {
297 FeatureAttributes attributes;
298 uint titleBufSize = 0;
299 uint descBufSize = 0;
300 uint attr;
301 uint ret = NativeMethods.MsiGetFeatureInfo(
302 (int) this.session.Handle,
303 this.name,
304 out attr,
305 null,
306 ref titleBufSize,
307 null,
308 ref descBufSize);
309
310 if (ret != 0)
311 {
312 throw InstallerException.ExceptionFromReturnCode(ret);
313 }
314
315 // Values for attributes that MsiGetFeatureInfo returns are
316 // double the values in the Attributes column of the Feature Table.
317 attributes = (FeatureAttributes) (attr >> 1);
318
319 // MsiGetFeatureInfo MSDN documentation indicates
320 // NOUNSUPPORTEDADVERTISE is 32. Conversion above changes this to 16
321 // which is UIDisallowAbsent. MsiGetFeatureInfo isn't documented to
322 // return an attribute for 'UIDisallowAbsent', so if UIDisallowAbsent
323 // is set, change it to NoUnsupportedAdvertise which then maps correctly
324 // to NOUNSUPPORTEDADVERTISE.
325 if ((attributes & FeatureAttributes.UIDisallowAbsent) == FeatureAttributes.UIDisallowAbsent)
326 {
327 attributes &= ~FeatureAttributes.UIDisallowAbsent;
328 attributes |= FeatureAttributes.NoUnsupportedAdvertise;
329 }
330
331 return attributes;
332 }
333
334 set
335 {
336 // MsiSetFeatureAttributes doesn't indicate UIDisallowAbsent is valid
337 // so remove it.
338 FeatureAttributes attributes = value;
339 attributes &= ~FeatureAttributes.UIDisallowAbsent;
340
341 // Values for attributes that MsiSetFeatureAttributes uses are
342 // double the values in the Attributes column of the Feature Table.
343 uint attr = ((uint) attributes) << 1;
344
345 // MsiSetFeatureAttributes MSDN documentation indicates
346 // NOUNSUPPORTEDADVERTISE is 32. Conversion above changes this to 64
347 // which is undefined. Change this back to 32.
348 uint noUnsupportedAdvertiseDbl = ((uint)FeatureAttributes.NoUnsupportedAdvertise) << 1;
349 if ((attr & noUnsupportedAdvertiseDbl) == noUnsupportedAdvertiseDbl)
350 {
351 attr &= ~noUnsupportedAdvertiseDbl;
352 attr |= (uint) FeatureAttributes.NoUnsupportedAdvertise;
353 }
354
355 uint ret = RemotableNativeMethods.MsiSetFeatureAttributes((int) this.session.Handle, this.name, attr);
356
357 if (ret != (uint)NativeMethods.Error.SUCCESS)
358 {
359 if (ret == (uint)NativeMethods.Error.UNKNOWN_FEATURE)
360 {
361 throw InstallerException.ExceptionFromReturnCode(ret, this.name);
362 }
363 else
364 {
365 throw InstallerException.ExceptionFromReturnCode(ret);
366 }
367 }
368 }
369 }
370
371 /// <summary>
372 /// Gets the title of the feature.
373 /// </summary>
374 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
375 /// <exception cref="ArgumentException">an unknown feature was requested</exception>
376 /// <remarks><p>
377 /// Win32 MSI API:
378 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeatureinfo.asp">MsiGetFeatureInfo</a>
379 /// </p></remarks>
380 public string Title
381 {
382 get
383 {
384 StringBuilder titleBuf = new StringBuilder(80);
385 uint titleBufSize = (uint) titleBuf.Capacity;
386 uint descBufSize = 0;
387 uint attr;
388 uint ret = NativeMethods.MsiGetFeatureInfo(
389 (int) this.session.Handle,
390 this.name,
391 out attr,
392 titleBuf,
393 ref titleBufSize,
394 null,
395 ref descBufSize);
396
397 if (ret == (uint) NativeMethods.Error.MORE_DATA)
398 {
399 titleBuf.Capacity = (int) ++titleBufSize;
400 ret = NativeMethods.MsiGetFeatureInfo(
401 (int) this.session.Handle,
402 this.name,
403 out attr,
404 titleBuf,
405 ref titleBufSize,
406 null,
407 ref descBufSize);
408 }
409
410 if (ret != 0)
411 {
412 throw InstallerException.ExceptionFromReturnCode(ret);
413 }
414
415 return titleBuf.ToString();
416 }
417 }
418
419 /// <summary>
420 /// Gets the description of the feature.
421 /// </summary>
422 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
423 /// <exception cref="ArgumentException">an unknown feature was requested</exception>
424 /// <remarks><p>
425 /// Win32 MSI API:
426 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeatureinfo.asp">MsiGetFeatureInfo</a>
427 /// </p></remarks>
428 public string Description
429 {
430 get
431 {
432 StringBuilder descBuf = new StringBuilder(256);
433 uint titleBufSize = 0;
434 uint descBufSize = (uint) descBuf.Capacity;
435 uint attr;
436 uint ret = NativeMethods.MsiGetFeatureInfo(
437 (int) this.session.Handle,
438 this.name,
439 out attr,
440 null,
441 ref titleBufSize,
442 descBuf,
443 ref descBufSize);
444
445 if (ret == (uint) NativeMethods.Error.MORE_DATA)
446 {
447 descBuf.Capacity = (int) ++descBufSize;
448 ret = NativeMethods.MsiGetFeatureInfo(
449 (int) this.session.Handle,
450 this.name,
451 out attr,
452 null,
453 ref titleBufSize,
454 descBuf,
455 ref descBufSize);
456 }
457
458 if (ret != 0)
459 {
460 throw InstallerException.ExceptionFromReturnCode(ret);
461 }
462
463 return descBuf.ToString();
464 }
465 }
466
467 /// <summary>
468 /// Calculates the disk space required by the feature and its selected children and parent features.
469 /// </summary>
470 /// <param name="includeParents">If true, the parent features are included in the cost.</param>
471 /// <param name="includeChildren">If true, the child features are included in the cost.</param>
472 /// <param name="installState">Specifies the installation state.</param>
473 /// <returns>The disk space requirement in bytes.</returns>
474 /// <remarks><p>
475 /// Win32 MSI API:
476 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeaturecost.asp">MsiGetFeatureCost</a>
477 /// </p></remarks>
478 public long GetCost(bool includeParents, bool includeChildren, InstallState installState)
479 {
480 const int MSICOSTTREE_CHILDREN = 1;
481 const int MSICOSTTREE_PARENTS = 2;
482
483 int cost;
484 uint ret = RemotableNativeMethods.MsiGetFeatureCost(
485 (int) this.session.Handle,
486 this.name,
487 (includeParents ? MSICOSTTREE_PARENTS : 0) | (includeChildren ? MSICOSTTREE_CHILDREN : 0),
488 (int) installState,
489 out cost);
490 if (ret != 0)
491 {
492 throw InstallerException.ExceptionFromReturnCode(ret);
493 }
494 return cost * 512L;
495 }
496 }
497}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/FeatureInstallation.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/FeatureInstallation.cs
new file mode 100644
index 00000000..aa8ffe34
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/FeatureInstallation.cs
@@ -0,0 +1,174 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Text;
7 using System.Collections.Generic;
8 using System.Diagnostics.CodeAnalysis;
9
10 /// <summary>
11 /// Represents an instance of a feature of an installed product.
12 /// </summary>
13 public class FeatureInstallation : InstallationPart
14 {
15 /// <summary>
16 /// Creates a new FeatureInstallation instance for a feature of a product.
17 /// </summary>
18 /// <param name="featureName">feature name</param>
19 /// <param name="productCode">ProductCode GUID</param>
20 public FeatureInstallation(string featureName, string productCode)
21 : base(featureName, productCode)
22 {
23 if (String.IsNullOrEmpty(featureName))
24 {
25 throw new ArgumentNullException("featureName");
26 }
27 }
28
29 /// <summary>
30 /// Gets the name of the feature.
31 /// </summary>
32 public string FeatureName
33 {
34 get
35 {
36 return this.Id;
37 }
38 }
39
40 /// <summary>
41 /// Gets the installed state of the feature.
42 /// </summary>
43 /// <remarks><p>
44 /// Win32 MSI API:
45 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiqueryfeaturestate.asp">MsiQueryFeatureState</a>
46 /// </p></remarks>
47 public override InstallState State
48 {
49 get
50 {
51 int installState = NativeMethods.MsiQueryFeatureState(
52 this.ProductCode, this.FeatureName);
53 return (InstallState) installState;
54 }
55 }
56
57 /// <summary>
58 /// Gets the parent of the feature, or null if the feature has no parent (it is a root feature).
59 /// </summary>
60 /// <remarks>
61 /// Invocation of this property may be slightly costly for products with many features,
62 /// because it involves an enumeration of all the features in the product.
63 /// </remarks>
64 public FeatureInstallation Parent
65 {
66 get
67 {
68 StringBuilder featureBuf = new StringBuilder(256);
69 StringBuilder parentBuf = new StringBuilder(256);
70 for (uint i = 0; ; i++)
71 {
72 uint ret = NativeMethods.MsiEnumFeatures(this.ProductCode, i, featureBuf, parentBuf);
73
74 if (ret != 0)
75 {
76 break;
77 }
78
79 if (featureBuf.ToString() == this.FeatureName)
80 {
81 if (parentBuf.Length > 0)
82 {
83 return new FeatureInstallation(parentBuf.ToString(), this.ProductCode);
84 }
85 else
86 {
87 return null;
88 }
89 }
90 }
91
92 return null;
93 }
94 }
95
96 /// <summary>
97 /// Gets the usage metrics for the feature.
98 /// </summary>
99 /// <remarks><p>
100 /// If no usage metrics are recorded, the <see cref="UsageData.UseCount" /> value is 0.
101 /// </p><p>
102 /// Win32 MSI API:
103 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeatureusage.asp">MsiGetFeatureUsage</a>
104 /// </p></remarks>
105 public FeatureInstallation.UsageData Usage
106 {
107 get
108 {
109 uint useCount;
110 ushort useDate;
111 uint ret = NativeMethods.MsiGetFeatureUsage(
112 this.ProductCode, this.FeatureName, out useCount, out useDate);
113 if (ret != 0)
114 {
115 throw InstallerException.ExceptionFromReturnCode(ret);
116 }
117
118 DateTime lastUsedDate;
119 if (useCount == 0)
120 {
121 lastUsedDate = DateTime.MinValue;
122 }
123 else
124 {
125 lastUsedDate = new DateTime(
126 1980 + (useDate >> 9),
127 (useDate & 0x01FF) >> 5,
128 (useDate & 0x001F));
129 }
130
131 return new UsageData((int) useCount, lastUsedDate);
132 }
133 }
134
135 /// <summary>
136 /// Holds data about the usage of a feature.
137 /// </summary>
138 [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
139 [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
140 public struct UsageData
141 {
142 private int useCount;
143 private DateTime lastUsedDate;
144
145 internal UsageData(int useCount, DateTime lastUsedDate)
146 {
147 this.useCount = useCount;
148 this.lastUsedDate = lastUsedDate;
149 }
150
151 /// <summary>
152 /// Gets count of the number of times the feature has been used.
153 /// </summary>
154 public int UseCount
155 {
156 get
157 {
158 return this.useCount;
159 }
160 }
161
162 /// <summary>
163 /// Gets the date the feature was last used.
164 /// </summary>
165 public DateTime LastUsedDate
166 {
167 get
168 {
169 return this.lastUsedDate;
170 }
171 }
172 }
173 }
174}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Handle.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Handle.cs
new file mode 100644
index 00000000..c3d3625f
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Handle.cs
@@ -0,0 +1,154 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.ComponentModel;
7 using System.Diagnostics.CodeAnalysis;
8
9 /// <summary>
10 /// Base class for Windows Installer handle types (Database, View, Record, SummaryInfo).
11 /// </summary>
12 /// <remarks><p>
13 /// These classes implement the <see cref="IDisposable"/> interface, because they
14 /// hold unmanaged resources (MSI handles) that should be properly disposed
15 /// when no longer needed.
16 /// </p></remarks>
17 public abstract class InstallerHandle : MarshalByRefObject, IDisposable
18 {
19 private NativeMethods.MsiHandle handle;
20
21 /// <summary>
22 /// Constructs a handle object from a native integer handle.
23 /// </summary>
24 /// <param name="handle">Native integer handle.</param>
25 /// <param name="ownsHandle">true to close the handle when this object is disposed or finalized</param>
26 protected InstallerHandle(IntPtr handle, bool ownsHandle)
27 {
28 if (handle == IntPtr.Zero)
29 {
30 throw new InvalidHandleException();
31 }
32
33 this.handle = new NativeMethods.MsiHandle(handle, ownsHandle);
34 }
35
36 /// <summary>
37 /// Gets the native integer handle.
38 /// </summary>
39 [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")]
40 public IntPtr Handle
41 {
42 get
43 {
44 if (this.IsClosed)
45 {
46 throw new InvalidHandleException();
47 }
48 return this.handle;
49 }
50 }
51
52 /// <summary>
53 /// Checks if the handle is closed. When closed, method calls on the handle object may throw an <see cref="InvalidHandleException"/>.
54 /// </summary>
55 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
56 public bool IsClosed
57 {
58 get
59 {
60 return this.handle.IsClosed;
61 }
62 }
63
64 /// <summary>
65 /// Closes the handle. After closing a handle, further method calls may throw an <see cref="InvalidHandleException"/>.
66 /// </summary>
67 /// <remarks><p>
68 /// The finalizer of this class will NOT close the handle if it is still open,
69 /// because finalization can run on a separate thread from the application,
70 /// resulting in potential problems if handles are closed from that thread.
71 /// It is best that the handle be closed manually as soon as it is no longer needed,
72 /// as leaving lots of unused handles open can degrade performance.
73 /// </p><p>
74 /// Win32 MSI API:
75 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiclosehandle.asp">MsiCloseHandle</a>
76 /// </p></remarks>
77 /// <seealso cref="Close"/>
78 public void Dispose()
79 {
80 this.Dispose(true);
81 GC.SuppressFinalize(this);
82 }
83
84 /// <summary>
85 /// Closes the handle. After closing a handle, further method calls may throw an <see cref="InvalidHandleException"/>.
86 /// </summary>
87 /// <remarks><p>
88 /// The finalizer of this class will NOT close the handle if it is still open,
89 /// because finalization can run on a separate thread from the application,
90 /// resulting in potential problems if handles are closed from that thread.
91 /// It is best that the handle be closed manually as soon as it is no longer needed,
92 /// as leaving lots of unused handles open can degrade performance.
93 /// </p><p>
94 /// This method is merely an alias for the <see cref="Dispose()"/> method.
95 /// </p><p>
96 /// Win32 MSI API:
97 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiclosehandle.asp">MsiCloseHandle</a>
98 /// </p></remarks>
99 public void Close()
100 {
101 this.Dispose();
102 }
103
104 /// <summary>
105 /// Tests whether this handle object is equal to another handle object. Two handle objects are equal
106 /// if their types are the same and their native integer handles are the same.
107 /// </summary>
108 /// <param name="obj">The handle object to compare with the current handle object.</param>
109 /// <returns>true if the specified handle object is equal to the current handle object; otherwise false</returns>
110 public override bool Equals(object obj)
111 {
112 return (obj != null && this.GetType() == obj.GetType() &&
113 this.Handle == ((InstallerHandle) obj).Handle);
114 }
115
116 /// <summary>
117 /// Gets a hash value for the handle object.
118 /// </summary>
119 /// <returns>A hash code for the handle object.</returns>
120 /// <remarks><p>
121 /// The hash code is derived from the native integer handle.
122 /// </p></remarks>
123 public override int GetHashCode()
124 {
125 return this.Handle.GetHashCode();
126 }
127
128 /// <summary>
129 /// Gets an object that can be used internally for safe syncronization.
130 /// </summary>
131 internal object Sync
132 {
133 get
134 {
135 return this.handle;
136 }
137 }
138
139 /// <summary>
140 /// Closes the handle. After closing a handle, further method calls may throw an <see cref="InvalidHandleException"/>.
141 /// </summary>
142 /// <param name="disposing">If true, the method has been called directly or indirectly by a user's code,
143 /// so managed and unmanaged resources will be disposed. If false, the method has been called by the
144 /// runtime from inside the finalizer, and only unmanaged resources will be disposed.</param>
145 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
146 protected virtual void Dispose(bool disposing)
147 {
148 if (disposing)
149 {
150 this.handle.Dispose();
151 }
152 }
153 }
154}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/IEmbeddedUI.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/IEmbeddedUI.cs
new file mode 100644
index 00000000..d77c82a9
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/IEmbeddedUI.cs
@@ -0,0 +1,67 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System.Diagnostics.CodeAnalysis;
6
7 /// <summary>
8 /// [MSI 4.5] Interface for an embedded external user interface for an installation.
9 /// </summary>
10 /// <remarks>
11 /// Classes which implement this interface must have a public constructor that takes no parameters.
12 /// </remarks>
13 public interface IEmbeddedUI
14 {
15 /// <summary>
16 /// Initializes the embedded UI.
17 /// </summary>
18 /// <param name="session">Handle to the installer which can be used to get and set properties.
19 /// The handle is only valid for the duration of this method call.</param>
20 /// <param name="resourcePath">Path to the directory that contains all the files from the MsiEmbeddedUI table.</param>
21 /// <param name="internalUILevel">On entry, contains the current UI level for the installation. After this
22 /// method returns, the installer resets the UI level to the returned value of this parameter.</param>
23 /// <returns>True if the embedded UI was successfully initialized; false if the installation
24 /// should continue without the embedded UI.</returns>
25 /// <exception cref="InstallCanceledException">The installation was canceled by the user.</exception>
26 /// <exception cref="InstallerException">The embedded UI failed to initialize and
27 /// causes the installation to fail.</exception>
28 /// <remarks><p>
29 /// Win32 MSI API:
30 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/initializeembeddedui.asp">InitializeEmbeddedUI</a>
31 /// </p></remarks>
32 [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
33 bool Initialize(Session session, string resourcePath, ref InstallUIOptions internalUILevel);
34
35 /// <summary>
36 /// Processes information and progress messages sent to the user interface.
37 /// </summary>
38 /// <param name="messageType">Message type.</param>
39 /// <param name="messageRecord">Record that contains message data.</param>
40 /// <param name="buttons">Message buttons.</param>
41 /// <param name="icon">Message box icon.</param>
42 /// <param name="defaultButton">Message box default button.</param>
43 /// <returns>Result of processing the message.</returns>
44 /// <remarks><p>
45 /// Win32 MSI API:
46 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/embeddeduihandler.asp">EmbeddedUIHandler</a>
47 /// </p></remarks>
48 MessageResult ProcessMessage(
49 InstallMessage messageType,
50 Record messageRecord,
51 MessageButtons buttons,
52 MessageIcon icon,
53 MessageDefaultButton defaultButton);
54
55 /// <summary>
56 /// Shuts down the embedded UI at the end of the installation.
57 /// </summary>
58 /// <remarks>
59 /// If the installation was canceled during initialization, this method will not be called.
60 /// If the installation was canceled or failed at any later point, this method will be called at the end.
61 /// <p>
62 /// Win32 MSI API:
63 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/shutdownembeddedui.asp">ShutdownEmbeddedUI</a>
64 /// </p></remarks>
65 void Shutdown();
66 }
67}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallCost.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallCost.cs
new file mode 100644
index 00000000..f29612d6
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallCost.cs
@@ -0,0 +1,67 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System.Diagnostics.CodeAnalysis;
6
7 /// <summary>
8 /// Represents a per-drive disk space cost for an installation.
9 /// </summary>
10 [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
11 public struct InstallCost
12 {
13 private string driveName;
14 private long cost;
15 private long tempCost;
16
17 /// <summary>
18 /// Creates a new InstallCost object.
19 /// </summary>
20 /// <param name="driveName">name of the drive this cost data applies to</param>
21 /// <param name="cost">installation cost on this drive, as a number of bytes</param>
22 /// <param name="tempCost">temporary disk space required on this drive, as a number of bytes</param>
23 internal InstallCost(string driveName, long cost, long tempCost)
24 {
25 this.driveName = driveName;
26 this.cost = cost;
27 this.tempCost = tempCost;
28 }
29
30 /// <summary>
31 /// The name of the drive this cost data applies to.
32 /// </summary>
33 public string DriveName
34 {
35 get
36 {
37 return this.driveName;
38 }
39 }
40
41 /// <summary>
42 /// The installation cost on this drive, as a number of bytes.
43 /// </summary>
44 public long Cost
45 {
46 get
47 {
48 return this.cost;
49 }
50 }
51
52 /// <summary>
53 /// The temporary disk space required on this drive, as a number of bytes.
54 /// </summary>
55 /// <remarks><p>
56 /// This temporary space requirement is space needed only for the duration
57 /// of the installation, over the final footprint on disk.
58 /// </p></remarks>
59 public long TempCost
60 {
61 get
62 {
63 return this.tempCost;
64 }
65 }
66 }
67}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Installation.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Installation.cs
new file mode 100644
index 00000000..47ca00a1
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Installation.cs
@@ -0,0 +1,100 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Text;
7 using System.Globalization;
8
9 /// <summary>
10 /// Subclasses of this abstract class represent a unique instance of a
11 /// registered product or patch installation.
12 /// </summary>
13 public abstract class Installation
14 {
15 private string installationCode;
16 private string userSid;
17 private UserContexts context;
18 private SourceList sourceList;
19
20 internal Installation(string installationCode, string userSid, UserContexts context)
21 {
22 if (context == UserContexts.Machine)
23 {
24 userSid = null;
25 }
26 this.installationCode = installationCode;
27 this.userSid = userSid;
28 this.context = context;
29 }
30
31 /// <summary>
32 /// Gets the user security identifier (SID) under which this product or patch
33 /// installation is available.
34 /// </summary>
35 public string UserSid
36 {
37 get
38 {
39 return this.userSid;
40 }
41 }
42
43 /// <summary>
44 /// Gets the user context of this product or patch installation.
45 /// </summary>
46 public UserContexts Context
47 {
48 get
49 {
50 return this.context;
51 }
52 }
53
54 /// <summary>
55 /// Gets the source list of this product or patch installation.
56 /// </summary>
57 public virtual SourceList SourceList
58 {
59 get
60 {
61 if (this.sourceList == null)
62 {
63 this.sourceList = new SourceList(this);
64 }
65 return this.sourceList;
66 }
67 }
68
69 /// <summary>
70 /// Gets a value indicating whether this product or patch is installed on the current system.
71 /// </summary>
72 public abstract bool IsInstalled
73 {
74 get;
75 }
76
77 internal string InstallationCode
78 {
79 get
80 {
81 return this.installationCode;
82 }
83 }
84
85 internal abstract int InstallationType
86 {
87 get;
88 }
89
90 /// <summary>
91 /// Gets a property about the product or patch installation.
92 /// </summary>
93 /// <param name="propertyName">Name of the property being retrieved.</param>
94 /// <returns></returns>
95 public abstract string this[string propertyName]
96 {
97 get;
98 }
99 }
100}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallationPart.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallationPart.cs
new file mode 100644
index 00000000..ce5a6a94
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallationPart.cs
@@ -0,0 +1,82 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 /// <summary>
6 /// Subclasses of this abstract class represent an instance
7 /// of a registered feature or component.
8 /// </summary>
9 public abstract class InstallationPart
10 {
11 private string id;
12 private string productCode;
13 private string userSid;
14 private UserContexts context;
15
16 internal InstallationPart(string id, string productCode)
17 : this(id, productCode, null, UserContexts.None)
18 {
19 }
20
21 internal InstallationPart(string id, string productCode, string userSid, UserContexts context)
22 {
23 this.id = id;
24 this.productCode = productCode;
25 this.userSid = userSid;
26 this.context = context;
27 }
28
29 internal string Id
30 {
31 get
32 {
33 return this.id;
34 }
35 }
36
37 internal string ProductCode
38 {
39 get
40 {
41 return this.productCode;
42 }
43 }
44
45 internal string UserSid
46 {
47 get
48 {
49 return this.userSid;
50 }
51 }
52
53 internal UserContexts Context
54 {
55 get
56 {
57 return this.context;
58 }
59 }
60
61 /// <summary>
62 /// Gets the product that this item is a part of.
63 /// </summary>
64 public ProductInstallation Product
65 {
66 get
67 {
68 return this.productCode != null ?
69 new ProductInstallation(this.productCode, userSid, context) : null;
70 }
71 }
72
73 /// <summary>
74 /// Gets the current installation state of the item.
75 /// </summary>
76 public abstract InstallState State
77 {
78 get;
79 }
80 }
81
82}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Installer.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Installer.cs
new file mode 100644
index 00000000..8df0aed9
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Installer.cs
@@ -0,0 +1,890 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Resources;
9 using System.Reflection;
10 using System.Collections.Generic;
11 using System.Globalization;
12 using System.Runtime.InteropServices;
13 using System.Diagnostics.CodeAnalysis;
14
15/// <summary>
16/// Receives an exception from
17/// <see cref="Installer.DetermineApplicablePatches(string,string[],InapplicablePatchHandler,string,UserContexts)"/>
18/// indicating the reason a particular patch is not applicable to a product.
19/// </summary>
20/// <param name="patch">MSP file path, XML file path, or XML blob that was passed to
21/// <see cref="Installer.DetermineApplicablePatches(string,string[],InapplicablePatchHandler,string,UserContexts)"/></param>
22/// <param name="exception">exception indicating the reason the patch is not applicable</param>
23/// <remarks><p>
24/// If <paramref name="exception"/> is an <see cref="InstallerException"/> or subclass, then
25/// its <see cref="InstallerException.ErrorCode"/> and <see cref="InstallerException.Message"/>
26/// properties will indicate a more specific reason the patch was not applicable.
27/// </p><p>
28/// The <paramref name="exception"/> could also be a FileNotFoundException if the
29/// patch string was a file path.
30/// </p></remarks>
31public delegate void InapplicablePatchHandler(string patch, Exception exception);
32
33/// <summary>
34/// Provides static methods for installing and configuring products and patches.
35/// </summary>
36public static partial class Installer
37{
38 private static bool rebootRequired;
39 private static bool rebootInitiated;
40 private static ResourceManager errorResources;
41
42 /// <summary>
43 /// Indicates whether a system reboot is required after running an installation or configuration operation.
44 /// </summary>
45 public static bool RebootRequired
46 {
47 get
48 {
49 return Installer.rebootRequired;
50 }
51 }
52
53 /// <summary>
54 /// Indicates whether a system reboot has been initiated after running an installation or configuration operation.
55 /// </summary>
56 public static bool RebootInitiated
57 {
58 get
59 {
60 return Installer.rebootInitiated;
61 }
62 }
63
64 /// <summary>
65 /// Enables the installer's internal user interface. Then this user interface is used
66 /// for all subsequent calls to user-interface-generating installer functions in this process.
67 /// </summary>
68 /// <param name="uiOptions">Specifies the level of complexity of the user interface</param>
69 /// <param name="windowHandle">Handle to a window, which becomes the owner of any user interface created.
70 /// A pointer to the previous owner of the user interface is returned.</param>
71 /// <returns>The previous user interface level</returns>
72 /// <remarks><p>
73 /// Win32 MSI API:
74 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetinternalui.asp">MsiSetInternalUI</a>
75 /// </p></remarks>
76 [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
77 public static InstallUIOptions SetInternalUI(InstallUIOptions uiOptions, ref IntPtr windowHandle)
78 {
79 return (InstallUIOptions) NativeMethods.MsiSetInternalUI((uint) uiOptions, ref windowHandle);
80 }
81
82 /// <summary>
83 /// Enables the installer's internal user interface. Then this user interface is used
84 /// for all subsequent calls to user-interface-generating installer functions in this process.
85 /// The owner of the user interface does not change.
86 /// </summary>
87 /// <param name="uiOptions">Specifies the level of complexity of the user interface</param>
88 /// <returns>The previous user interface level</returns>
89 /// <remarks><p>
90 /// Win32 MSI API:
91 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetinternalui.asp">MsiSetInternalUI</a>
92 /// </p></remarks>
93 public static InstallUIOptions SetInternalUI(InstallUIOptions uiOptions)
94 {
95 return (InstallUIOptions) NativeMethods.MsiSetInternalUI((uint) uiOptions, IntPtr.Zero);
96 }
97
98 /// <summary>
99 /// Enables logging of the selected message type for all subsequent install sessions in
100 /// the current process space.
101 /// </summary>
102 /// <param name="logModes">One or more mode flags specifying the type of messages to log</param>
103 /// <param name="logFile">Full path to the log file. A null path disables logging,
104 /// in which case the logModes paraneter is ignored.</param>
105 /// <exception cref="ArgumentException">an invalid log mode was specified</exception>
106 /// <remarks>This method takes effect on any new installation processes. Calling this
107 /// method from within a custom action will not start logging for that installation.</remarks>
108 public static void EnableLog(InstallLogModes logModes, string logFile)
109 {
110 Installer.EnableLog(logModes, logFile, false, true);
111 }
112
113 /// <summary>
114 /// Enables logging of the selected message type for all subsequent install sessions in
115 /// the current process space.
116 /// </summary>
117 /// <param name="logModes">One or more mode flags specifying the type of messages to log</param>
118 /// <param name="logFile">Full path to the log file. A null path disables logging,
119 /// in which case the logModes paraneter is ignored.</param>
120 /// <param name="append">If true, the log lines will be appended to any existing file content.
121 /// If false, the log file will be truncated if it exists. The default is false.</param>
122 /// <param name="flushEveryLine">If true, the log will be flushed after every line.
123 /// If false, the log will be flushed every 20 lines. The default is true.</param>
124 /// <exception cref="ArgumentException">an invalid log mode was specified</exception>
125 /// <remarks><p>
126 /// This method takes effect on any new installation processes. Calling this
127 /// method from within a custom action will not start logging for that installation.
128 /// </p><p>
129 /// Win32 MSI API:
130 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienablelog.asp">MsiEnableLog</a>
131 /// </p></remarks>
132 public static void EnableLog(InstallLogModes logModes, string logFile, bool append, bool flushEveryLine)
133 {
134 uint ret = NativeMethods.MsiEnableLog((uint) logModes, logFile, (append ? (uint) 1 : 0) + (flushEveryLine ? (uint) 2 : 0));
135 if (ret != 0 && ret != (uint) NativeMethods.Error.FILE_INVALID)
136 {
137 throw InstallerException.ExceptionFromReturnCode(ret);
138 }
139 }
140
141 /// <summary>
142 /// increments the usage count for a particular feature and returns the installation state for
143 /// that feature. This method should be used to indicate an application's intent to use a feature.
144 /// </summary>
145 /// <param name="productCode">The product code of the product.</param>
146 /// <param name="feature">The feature to be used.</param>
147 /// <param name="installMode">Must have the value <see cref="InstallMode.NoDetection"/>.</param>
148 /// <returns>The installed state of the feature.</returns>
149 /// <remarks><p>
150 /// The UseFeature method should only be used on features known to be published. The application
151 /// should determine the status of the feature by calling either the FeatureState method or
152 /// Features method.
153 /// </p><p>
154 /// Win32 MSI APIs:
155 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiusefeature.asp">MsiUseFeature</a>,
156 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiusefeatureex.asp">MsiUseFeatureEx</a>
157 /// </p></remarks>
158 public static InstallState UseFeature(string productCode, string feature, InstallMode installMode)
159 {
160 int installState = NativeMethods.MsiUseFeatureEx(productCode, feature, unchecked ((uint) installMode), 0);
161 return (InstallState) installState;
162 }
163
164 /// <summary>
165 /// Opens an installer package for use with functions that access the product database and install engine,
166 /// returning an Session object.
167 /// </summary>
168 /// <param name="packagePath">Path to the package</param>
169 /// <param name="ignoreMachineState">Specifies whether or not the create a Session object that ignores the
170 /// computer state and that is incapable of changing the current computer state. A value of false yields
171 /// the normal behavior. A value of true creates a "safe" Session object that cannot change of the current
172 /// machine state.</param>
173 /// <returns>A Session object allowing access to the product database and install engine</returns>
174 /// <exception cref="InstallerException">The product could not be opened</exception>
175 /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
176 /// <remarks><p>
177 /// Note that only one Session object can be opened by a single process. OpenPackage cannot be used in a
178 /// custom action because the active installation is the only session allowed.
179 /// </p><p>
180 /// A "safe" Session object ignores the current computer state when opening the package and prevents
181 /// changes to the current computer state.
182 /// </p><p>
183 /// The Session object should be <see cref="InstallerHandle.Close"/>d after use.
184 /// It is best that the handle be closed manually as soon as it is no longer
185 /// needed, as leaving lots of unused handles open can degrade performance.
186 /// </p><p>
187 /// Win32 MSI APIs:
188 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopenpackage.asp">MsiOpenPackage</a>,
189 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopenpackageex.asp">MsiOpenPackageEx</a>
190 /// </p></remarks>
191 public static Session OpenPackage(string packagePath, bool ignoreMachineState)
192 {
193 int sessionHandle;
194 uint ret = NativeMethods.MsiOpenPackageEx(packagePath, ignoreMachineState ? (uint) 1 : 0, out sessionHandle);
195 if (ret != 0)
196 {
197 throw InstallerException.ExceptionFromReturnCode(ret);
198 }
199 return new Session((IntPtr) sessionHandle, true);
200 }
201
202 /// <summary>
203 /// Opens an installer package for use with functions that access the product database and install engine,
204 /// returning an Session object.
205 /// </summary>
206 /// <param name="database">Database used to create the session</param>
207 /// <param name="ignoreMachineState">Specifies whether or not the create a Session object that ignores the
208 /// computer state and that is incapable of changing the current computer state. A value of false yields
209 /// the normal behavior. A value of true creates a "safe" Session object that cannot change of the current
210 /// machine state.</param>
211 /// <returns>A Session object allowing access to the product database and install engine</returns>
212 /// <exception cref="InstallerException">The product could not be opened</exception>
213 /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
214 /// <remarks><p>
215 /// Note that only one Session object can be opened by a single process. OpenPackage cannot be used in a
216 /// custom action because the active installation is the only session allowed.
217 /// </p><p>
218 /// A "safe" Session object ignores the current computer state when opening the package and prevents
219 /// changes to the current computer state.
220 /// </p><p>
221 /// The Session object should be <see cref="InstallerHandle.Close"/>d after use.
222 /// It is best that the handle be closed manually as soon as it is no longer
223 /// needed, as leaving lots of unused handles open can degrade performance.
224 /// </p><p>
225 /// Win32 MSI APIs:
226 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopenpackage.asp">MsiOpenPackage</a>,
227 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopenpackageex.asp">MsiOpenPackageEx</a>
228 /// </p></remarks>
229 [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
230 public static Session OpenPackage(Database database, bool ignoreMachineState)
231 {
232 if (database == null)
233 {
234 throw new ArgumentNullException("database");
235 }
236
237 return Installer.OpenPackage(
238 String.Format(CultureInfo.InvariantCulture, "#{0}", database.Handle),
239 ignoreMachineState);
240 }
241
242 /// <summary>
243 /// Opens an installer package for an installed product using the product code.
244 /// </summary>
245 /// <param name="productCode">Product code of the installed product</param>
246 /// <returns>A Session object allowing access to the product database and install engine,
247 /// or null if the specified product is not installed.</returns>
248 /// <exception cref="ArgumentException">An unknown product was requested</exception>
249 /// <exception cref="InstallerException">The product could not be opened</exception>
250 /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
251 /// <remarks><p>
252 /// Note that only one Session object can be opened by a single process. OpenProduct cannot be
253 /// used in a custom action because the active installation is the only session allowed.
254 /// </p><p>
255 /// The Session object should be <see cref="InstallerHandle.Close"/>d after use.
256 /// It is best that the handle be closed manually as soon as it is no longer
257 /// needed, as leaving lots of unused handles open can degrade performance.
258 /// </p><p>
259 /// Win32 MSI API:
260 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopenproduct.asp">MsiOpenProduct</a>
261 /// </p></remarks>
262 public static Session OpenProduct(string productCode)
263 {
264 int sessionHandle;
265 uint ret = NativeMethods.MsiOpenProduct(productCode, out sessionHandle);
266 if (ret != 0)
267 {
268 if (ret == (uint) NativeMethods.Error.UNKNOWN_PRODUCT)
269 {
270 return null;
271 }
272 else
273 {
274 throw InstallerException.ExceptionFromReturnCode(ret);
275 }
276 }
277 return new Session((IntPtr) sessionHandle, true);
278 }
279
280 /// <summary>
281 /// Gets the full component path, performing any necessary installation. This method prompts for source if
282 /// necessary and increments the usage count for the feature.
283 /// </summary>
284 /// <param name="product">Product code for the product that contains the feature with the necessary component</param>
285 /// <param name="feature">Feature ID of the feature with the necessary component</param>
286 /// <param name="component">Component code of the necessary component</param>
287 /// <param name="installMode">Installation mode; this can also include bits from <see cref="ReinstallModes"/></param>
288 /// <returns>Path to the component</returns>
289 /// <remarks><p>
290 /// Win32 MSI API:
291 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprovidecomponent.asp">MsiProvideComponent</a>
292 /// </p></remarks>
293 public static string ProvideComponent(string product, string feature, string component, InstallMode installMode)
294 {
295 StringBuilder pathBuf = new StringBuilder(512);
296 uint pathBufSize = (uint) pathBuf.Capacity;
297 uint ret = NativeMethods.MsiProvideComponent(product, feature, component, unchecked((uint)installMode), pathBuf, ref pathBufSize);
298 if (ret == (uint) NativeMethods.Error.MORE_DATA)
299 {
300 pathBuf.Capacity = (int) ++pathBufSize;
301 ret = NativeMethods.MsiProvideComponent(product, feature, component, unchecked((uint)installMode), pathBuf, ref pathBufSize);
302 }
303
304 if (ret != 0)
305 {
306 throw InstallerException.ExceptionFromReturnCode(ret);
307 }
308 return pathBuf.ToString();
309 }
310
311 /// <summary>
312 /// Gets the full component path for a qualified component that is published by a product and
313 /// performs any necessary installation. This method prompts for source if necessary and increments
314 /// the usage count for the feature.
315 /// </summary>
316 /// <param name="component">Specifies the component ID for the requested component. This may not be the
317 /// GUID for the component itself but rather a server that provides the correct functionality, as in the
318 /// ComponentId column of the PublishComponent table.</param>
319 /// <param name="qualifier">Specifies a qualifier into a list of advertising components (from PublishComponent Table).</param>
320 /// <param name="installMode">Installation mode; this can also include bits from <see cref="ReinstallModes"/></param>
321 /// <param name="product">Optional; specifies the product to match that has published the qualified component.</param>
322 /// <returns>Path to the component</returns>
323 /// <remarks><p>
324 /// Win32 MSI APIs:
325 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprovidequalifiedcomponent.asp">MsiProvideQualifiedComponent</a>
326 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprovidequalifiedcomponentex.asp">MsiProvideQualifiedComponentEx</a>
327 /// </p></remarks>
328 public static string ProvideQualifiedComponent(string component, string qualifier, InstallMode installMode, string product)
329 {
330 StringBuilder pathBuf = new StringBuilder(512);
331 uint pathBufSize = (uint) pathBuf.Capacity;
332 uint ret = NativeMethods.MsiProvideQualifiedComponentEx(component, qualifier, unchecked((uint)installMode), product, 0, 0, pathBuf, ref pathBufSize);
333 if (ret == (uint) NativeMethods.Error.MORE_DATA)
334 {
335 pathBuf.Capacity = (int) ++pathBufSize;
336 ret = NativeMethods.MsiProvideQualifiedComponentEx(component, qualifier, unchecked((uint)installMode), product, 0, 0, pathBuf, ref pathBufSize);
337 }
338
339 if (ret != 0)
340 {
341 throw InstallerException.ExceptionFromReturnCode(ret);
342 }
343 return pathBuf.ToString();
344 }
345
346 /// <summary>
347 /// Gets the full path to a Windows Installer component containing an assembly. This method prompts for a source and
348 /// increments the usage count for the feature.
349 /// </summary>
350 /// <param name="assemblyName">Assembly name</param>
351 /// <param name="appContext">Set to null for global assemblies. For private assemblies, set to the full path of the
352 /// application configuration file (.cfg file) or executable file (.exe) of the application to which the assembly
353 /// has been made private.</param>
354 /// <param name="installMode">Installation mode; this can also include bits from <see cref="ReinstallModes"/></param>
355 /// <param name="isWin32Assembly">True if this is a Win32 assembly, false if it is a .NET assembly</param>
356 /// <returns>Path to the assembly</returns>
357 /// <remarks><p>
358 /// Win32 MSI API:
359 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprovideassembly.asp">MsiProvideAssembly</a>
360 /// </p></remarks>
361 public static string ProvideAssembly(string assemblyName, string appContext, InstallMode installMode, bool isWin32Assembly)
362 {
363 StringBuilder pathBuf = new StringBuilder(512);
364 uint pathBufSize = (uint) pathBuf.Capacity;
365 uint ret = NativeMethods.MsiProvideAssembly(assemblyName, appContext, unchecked ((uint) installMode), (isWin32Assembly ? (uint) 1 : 0), pathBuf, ref pathBufSize);
366 if (ret == (uint) NativeMethods.Error.MORE_DATA)
367 {
368 pathBuf.Capacity = (int) ++pathBufSize;
369 ret = NativeMethods.MsiProvideAssembly(assemblyName, appContext, unchecked ((uint) installMode), (isWin32Assembly ? (uint) 1 : 0), pathBuf, ref pathBufSize);
370 }
371
372 if (ret != 0)
373 {
374 throw InstallerException.ExceptionFromReturnCode(ret);
375 }
376 return pathBuf.ToString();
377 }
378
379 /// <summary>
380 /// Installs files that are unexpectedly missing.
381 /// </summary>
382 /// <param name="product">Product code for the product that owns the component to be installed</param>
383 /// <param name="component">Component to be installed</param>
384 /// <param name="installState">Specifies the way the component should be installed.</param>
385 /// <exception cref="InstallCanceledException">the user exited the installation</exception>
386 /// <remarks><p>
387 /// Win32 MSI API:
388 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiinstallmissingcomponent.asp">MsiInstallMissingComponent</a>
389 /// </p></remarks>
390 public static void InstallMissingComponent(string product, string component, InstallState installState)
391 {
392 uint ret = NativeMethods.MsiInstallMissingComponent(product, component, (int) installState);
393 if (ret != 0)
394 {
395 throw InstallerException.ExceptionFromReturnCode(ret);
396 }
397 }
398
399 /// <summary>
400 /// Installs files that are unexpectedly missing.
401 /// </summary>
402 /// <param name="product">Product code for the product that owns the file to be installed</param>
403 /// <param name="file">File to be installed</param>
404 /// <exception cref="InstallCanceledException">the user exited the installation</exception>
405 /// <remarks><p>
406 /// Win32 MSI API:
407 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiinstallmissingfile.asp">MsiInstallMissingFile</a>
408 /// </p></remarks>
409 public static void InstallMissingFile(string product, string file)
410 {
411 uint ret = NativeMethods.MsiInstallMissingFile(product, file);
412 if (ret != 0)
413 {
414 throw InstallerException.ExceptionFromReturnCode(ret);
415 }
416 }
417
418 /// <summary>
419 /// Reinstalls a feature.
420 /// </summary>
421 /// <param name="product">Product code for the product containing the feature to be reinstalled</param>
422 /// <param name="feature">Feature to be reinstalled</param>
423 /// <param name="reinstallModes">Reinstall modes</param>
424 /// <exception cref="InstallCanceledException">the user exited the installation</exception>
425 /// <remarks><p>
426 /// Win32 MSI API:
427 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msireinstallfeature.asp">MsiReinstallFeature</a>
428 /// </p></remarks>
429 public static void ReinstallFeature(string product, string feature, ReinstallModes reinstallModes)
430 {
431 uint ret = NativeMethods.MsiReinstallFeature(product, feature, (uint) reinstallModes);
432 if (ret != 0)
433 {
434 throw InstallerException.ExceptionFromReturnCode(ret);
435 }
436 }
437
438 /// <summary>
439 /// Reinstalls a product.
440 /// </summary>
441 /// <param name="product">Product code for the product to be reinstalled</param>
442 /// <param name="reinstallModes">Reinstall modes</param>
443 /// <exception cref="InstallCanceledException">the user exited the installation</exception>
444 /// <remarks><p>
445 /// Win32 MSI API:
446 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msireinstallproduct.asp">MsiReinstallProduct</a>
447 /// </p></remarks>
448 public static void ReinstallProduct(string product, ReinstallModes reinstallModes)
449 {
450 uint ret = NativeMethods.MsiReinstallProduct(product, (uint) reinstallModes);
451 if (ret != 0)
452 {
453 throw InstallerException.ExceptionFromReturnCode(ret);
454 }
455 }
456
457 /// <summary>
458 /// Opens an installer package and initializes an install session.
459 /// </summary>
460 /// <param name="packagePath">path to the patch package</param>
461 /// <param name="commandLine">command line property settings</param>
462 /// <exception cref="InstallerException">There was an error installing the product</exception>
463 /// <remarks><p>
464 /// To completely remove a product, set REMOVE=ALL in <paramRef name="commandLine"/>.
465 /// </p><p>
466 /// This method displays the user interface with the current settings and
467 /// log mode. You can change user interface settings with the <see cref="SetInternalUI(InstallUIOptions)"/>
468 /// and <see cref="SetExternalUI(ExternalUIHandler,InstallLogModes)"/> functions. You can set the log mode with the
469 /// <see cref="EnableLog(InstallLogModes,string)"/> function.
470 /// </p><p>
471 /// The <see cref="RebootRequired"/> and <see cref="RebootInitiated"/> properties should be
472 /// tested after calling this method.
473 /// </p><p>
474 /// Win32 MSI API:
475 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiinstallproduct.asp">MsiInstallProduct</a>
476 /// </p></remarks>
477 public static void InstallProduct(string packagePath, string commandLine)
478 {
479 uint ret = NativeMethods.MsiInstallProduct(packagePath, commandLine);
480 Installer.CheckInstallResult(ret);
481 }
482
483 /// <summary>
484 /// Installs or uninstalls a product.
485 /// </summary>
486 /// <param name="productCode">Product code of the product to be configured.</param>
487 /// <param name="installLevel">Specifies the default installation configuration of the
488 /// product. The <paramref name="installLevel"/> parameter is ignored and all features
489 /// are installed if the <paramref name="installState"/> parameter is set to any other
490 /// value than <see cref="InstallState.Default"/>. This parameter must be either 0
491 /// (install using authored feature levels), 65535 (install all features), or a value
492 /// between 0 and 65535 to install a subset of available features. </param>
493 /// <param name="installState">Specifies the installation state for the product.</param>
494 /// <param name="commandLine">Specifies the command line property settings. This should
495 /// be a list of the format Property=Setting Property=Setting.</param>
496 /// <exception cref="InstallerException">There was an error configuring the product</exception>
497 /// <remarks><p>
498 /// This method displays the user interface with the current settings and
499 /// log mode. You can change user interface settings with the <see cref="SetInternalUI(InstallUIOptions)"/>
500 /// and <see cref="SetExternalUI(ExternalUIHandler,InstallLogModes)"/> functions. You can set the log mode with the
501 /// <see cref="EnableLog(InstallLogModes,string)"/> function.
502 /// </p><p>
503 /// The <see cref="RebootRequired"/> and <see cref="RebootInitiated"/> properties should be
504 /// tested after calling this method.
505 /// </p><p>
506 /// Win32 MSI APIs:
507 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiconfigureproduct.asp">MsiConfigureProduct</a>,
508 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiconfigureproductex.asp">MsiConfigureProductEx</a>
509 /// </p></remarks>
510 public static void ConfigureProduct(string productCode, int installLevel, InstallState installState, string commandLine)
511 {
512 uint ret = NativeMethods.MsiConfigureProductEx(productCode, installLevel, (int) installState, commandLine);
513 Installer.CheckInstallResult(ret);
514 }
515
516 /// <summary>
517 /// Configures the installed state for a product feature.
518 /// </summary>
519 /// <param name="productCode">Product code of the product to be configured.</param>
520 /// <param name="feature">Specifies the feature ID for the feature to be configured.</param>
521 /// <param name="installState">Specifies the installation state for the feature.</param>
522 /// <exception cref="InstallerException">There was an error configuring the feature</exception>
523 /// <remarks><p>
524 /// The <see cref="RebootRequired"/> and <see cref="RebootInitiated"/> properties should be
525 /// tested after calling this method.
526 /// </p><p>
527 /// Win32 MSI API:
528 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiconfigurefeature.asp">MsiConfigureFeature</a>
529 /// </p></remarks>
530 public static void ConfigureFeature(string productCode, string feature, InstallState installState)
531 {
532 uint ret = NativeMethods.MsiConfigureFeature(productCode, feature, (int) installState);
533 Installer.CheckInstallResult(ret);
534 }
535
536 /// <summary>
537 /// For each product listed by the patch package as eligible to receive the patch, ApplyPatch invokes
538 /// an installation and sets the PATCH property to the path of the patch package.
539 /// </summary>
540 /// <param name="patchPackage">path to the patch package</param>
541 /// <param name="commandLine">optional command line property settings</param>
542 /// <exception cref="InstallerException">There was an error applying the patch</exception>
543 /// <remarks><p>
544 /// The <see cref="RebootRequired"/> and <see cref="RebootInitiated"/> properties should be
545 /// tested after calling this method.
546 /// </p><p>
547 /// Win32 MSI API:
548 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiapplypatch.asp">MsiApplyPatch</a>
549 /// </p></remarks>
550 public static void ApplyPatch(string patchPackage, string commandLine)
551 {
552 Installer.ApplyPatch(patchPackage, null, InstallType.Default, commandLine);
553 }
554
555 /// <summary>
556 /// For each product listed by the patch package as eligible to receive the patch, ApplyPatch invokes
557 /// an installation and sets the PATCH property to the path of the patch package.
558 /// </summary>
559 /// <param name="patchPackage">path to the patch package</param>
560 /// <param name="installPackage">path to the product to be patched, if installType
561 /// is set to <see cref="InstallType.NetworkImage"/></param>
562 /// <param name="installType">type of installation to patch</param>
563 /// <param name="commandLine">optional command line property settings</param>
564 /// <exception cref="InstallerException">There was an error applying the patch</exception>
565 /// <remarks><p>
566 /// The <see cref="RebootRequired"/> and <see cref="RebootInitiated"/> properties should be
567 /// tested after calling this method.
568 /// </p><p>
569 /// Win32 MSI API:
570 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiapplypatch.asp">MsiApplyPatch</a>
571 /// </p></remarks>
572 public static void ApplyPatch(string patchPackage, string installPackage, InstallType installType, string commandLine)
573 {
574 uint ret = NativeMethods.MsiApplyPatch(patchPackage, installPackage, (int) installType, commandLine);
575 Installer.CheckInstallResult(ret);
576 }
577
578 /// <summary>
579 /// Removes one or more patches from a single product. To remove a patch from
580 /// multiple products, RemovePatches must be called for each product.
581 /// </summary>
582 /// <param name="patches">List of patches to remove. Each patch can be specified by the GUID
583 /// of the patch or the full path to the patch package.</param>
584 /// <param name="productCode">The ProductCode (GUID) of the product from which the patches
585 /// are removed. This parameter cannot be null.</param>
586 /// <param name="commandLine">optional command line property settings</param>
587 /// <exception cref="InstallerException">There was an error removing the patches</exception>
588 /// <remarks><p>
589 /// The <see cref="RebootRequired"/> and <see cref="RebootInitiated"/> properties should be
590 /// tested after calling this method.
591 /// </p><p>
592 /// Win32 MSI API:
593 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiremovepatches.asp">MsiRemovePatches</a>
594 /// </p></remarks>
595 public static void RemovePatches(IList<string> patches, string productCode, string commandLine)
596 {
597 if (patches == null || patches.Count == 0)
598 {
599 throw new ArgumentNullException("patches");
600 }
601
602 if (productCode == null)
603 {
604 throw new ArgumentNullException("productCode");
605 }
606
607 StringBuilder patchList = new StringBuilder();
608 foreach (string patch in patches)
609 {
610 if (patch != null)
611 {
612 if (patchList.Length != 0)
613 {
614 patchList.Append(';');
615 }
616
617 patchList.Append(patch);
618 }
619 }
620
621 if (patchList.Length == 0)
622 {
623 throw new ArgumentNullException("patches");
624 }
625
626 uint ret = NativeMethods.MsiRemovePatches(patchList.ToString(), productCode, (int) InstallType.SingleInstance, commandLine);
627 Installer.CheckInstallResult(ret);
628 }
629
630 /// <summary>
631 /// Determines which patches apply to a specified product MSI and in what sequence.
632 /// </summary>
633 /// <param name="productPackage">Full path to an MSI file that is the target product
634 /// for the set of patches.</param>
635 /// <param name="patches">An array of strings specifying the patches to be checked. Each item
636 /// may be the path to an MSP file, the path an XML file, or just an XML blob.</param>
637 /// <param name="errorHandler">Callback to be invoked for each inapplicable patch, reporting the
638 /// reason the patch is not applicable. This value may be left null if that information is not
639 /// desired.</param>
640 /// <returns>An array of selected patch strings from <paramref name="patches"/>, indicating
641 /// the set of applicable patches. The items are re-ordered to be in the best sequence.</returns>
642 /// <remarks><p>
643 /// If an item in <paramref name="patches"/> is a file path but does not end in .MSP or .XML,
644 /// it is assumed to be an MSP file.
645 /// </p><p>
646 /// As this overload uses InstallContext.None, it does not consider the current state of
647 /// the system.
648 /// </p><p>
649 /// Win32 MSI API:
650 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidetermineapplicablepatches.asp">MsiDetermineApplicablePatches</a>
651 /// </p></remarks>
652 public static IList<string> DetermineApplicablePatches(
653 string productPackage,
654 string[] patches,
655 InapplicablePatchHandler errorHandler)
656 {
657 return DetermineApplicablePatches(productPackage, patches, errorHandler, null, UserContexts.None);
658 }
659
660 /// <summary>
661 /// Determines which patches apply to a specified product and in what sequence. If
662 /// the product is installed, this method accounts for patches that have already been applied to
663 /// the product and accounts for obsolete and superceded patches.
664 /// </summary>
665 /// <param name="product">The product that is the target for the set of patches. This may be
666 /// either a ProductCode (GUID) of a product that is currently installed, or the path to a an
667 /// MSI file.</param>
668 /// <param name="patches">An array of strings specifying the patches to be checked. Each item
669 /// may be the path to an MSP file, the path an XML file, or just an XML blob.</param>
670 /// <param name="errorHandler">Callback to be invoked for each inapplicable patch, reporting the
671 /// reason the patch is not applicable. This value may be left null if that information is not
672 /// desired.</param>
673 /// <param name="userSid">Specifies a security identifier (SID) of a user. This parameter restricts
674 /// the context of enumeration for this user account. This parameter cannot be the special SID
675 /// strings s-1-1-0 (everyone) or s-1-5-18 (local system). If <paramref name="context"/> is set to
676 /// <see cref="UserContexts.None"/> or <see cref="UserContexts.Machine"/>, then
677 /// <paramref name="userSid"/> must be null. For the current user context, <paramref name="userSid"/>
678 /// can be null and <paramref name="context"/> can be set to <see cref="UserContexts.UserManaged"/>
679 /// or <see cref="UserContexts.UserUnmanaged"/>.</param>
680 /// <param name="context">Restricts the enumeration to per-user-unmanaged, per-user-managed,
681 /// or per-machine context, or (if referring to an MSI) to no system context at all. This
682 /// parameter can be <see cref="UserContexts.Machine"/>, <see cref="UserContexts.UserManaged"/>,
683 /// <see cref="UserContexts.UserUnmanaged"/>, or <see cref="UserContexts.None"/>.</param>
684 /// <returns>An array of selected patch strings from <paramref name="patches"/>, indicating
685 /// the set of applicable patches. The items are re-ordered to be in the best sequence.</returns>
686 /// <remarks><p>
687 /// If an item in <paramref name="patches"/> is a file path but does not end in .MSP or .XML,
688 /// it is assumed to be an MSP file.
689 /// </p><p>
690 /// Passing an InstallContext of None only analyzes the MSI file; it does not consider the
691 /// current state of the system. You cannot use InstallContext.None with a ProductCode GUID.
692 /// </p><p>
693 /// Win32 MSI APIs:
694 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidetermineapplicablepatches.asp">MsiDetermineApplicablePatches</a>
695 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msideterminepatchsequence.asp">MsiDeterminePatchSequence</a>
696 /// </p></remarks>
697 public static IList<string> DetermineApplicablePatches(
698 string product,
699 string[] patches,
700 InapplicablePatchHandler errorHandler,
701 string userSid,
702 UserContexts context)
703 {
704 if (String.IsNullOrEmpty(product))
705 {
706 throw new ArgumentNullException("product");
707 }
708
709 if (patches == null)
710 {
711 throw new ArgumentNullException("patches");
712 }
713
714 NativeMethods.MsiPatchSequenceData[] sequenceData = new NativeMethods.MsiPatchSequenceData[patches.Length];
715 for (int i = 0; i < patches.Length; i++)
716 {
717 if (String.IsNullOrEmpty(patches[i]))
718 {
719 throw new ArgumentNullException("patches[" + i + "]");
720 }
721
722 sequenceData[i].szPatchData = patches[i];
723 sequenceData[i].ePatchDataType = GetPatchStringDataType(patches[i]);
724 sequenceData[i].dwOrder = -1;
725 sequenceData[i].dwStatus = 0;
726 }
727
728 uint ret;
729 if (context == UserContexts.None)
730 {
731 ret = NativeMethods.MsiDetermineApplicablePatches(product, (uint) sequenceData.Length, sequenceData);
732 }
733 else
734 {
735 ret = NativeMethods.MsiDeterminePatchSequence(product, userSid, context, (uint) sequenceData.Length, sequenceData);
736 }
737
738 if (errorHandler != null)
739 {
740 for (int i = 0; i < sequenceData.Length; i++)
741 {
742 if (sequenceData[i].dwOrder < 0 && sequenceData[i].dwStatus != 0)
743 {
744 errorHandler(sequenceData[i].szPatchData, InstallerException.ExceptionFromReturnCode(sequenceData[i].dwStatus));
745 }
746 }
747 }
748
749 if (ret != 0)
750 {
751 throw InstallerException.ExceptionFromReturnCode(ret);
752 }
753
754 IList<string> patchSeq = new List<string>(patches.Length);
755 for (int i = 0; i < sequenceData.Length; i++)
756 {
757 for (int j = 0; j < sequenceData.Length; j++)
758 {
759 if (sequenceData[j].dwOrder == i)
760 {
761 patchSeq.Add(sequenceData[j].szPatchData);
762 }
763 }
764 }
765 return patchSeq;
766 }
767
768 /// <summary>
769 /// Applies one or more patches to products that are eligible to receive the patch.
770 /// For each product listed by the patch package as eligible to receive the patch, ApplyPatch invokes
771 /// an installation and sets the PATCH property to the path of the patch package.
772 /// </summary>
773 /// <param name="patchPackages">The set of patch packages to be applied.
774 /// Each item is the full path to an MSP file.</param>
775 /// <param name="productCode">Provides the ProductCode of the product being patched. If this parameter
776 /// is null, the patches are applied to all products that are eligible to receive these patches.</param>
777 /// <param name="commandLine">optional command line property settings</param>
778 /// <remarks><p>
779 /// Win32 MSI API:
780 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiapplymultiplepatches.asp">MsiApplyMultiplePatches</a>
781 /// </p></remarks>
782 public static void ApplyMultiplePatches(
783 IList<string> patchPackages, string productCode, string commandLine)
784 {
785 if (patchPackages == null || patchPackages.Count == 0)
786 {
787 throw new ArgumentNullException("patchPackages");
788 }
789
790 StringBuilder patchList = new StringBuilder();
791 foreach (string patch in patchPackages)
792 {
793 if (patch != null)
794 {
795 if (patchList.Length != 0)
796 {
797 patchList.Append(';');
798 }
799
800 patchList.Append(patch);
801 }
802 }
803
804 if (patchList.Length == 0)
805 {
806 throw new ArgumentNullException("patchPackages");
807 }
808
809 uint ret = NativeMethods.MsiApplyMultiplePatches(patchList.ToString(), productCode, commandLine);
810 Installer.CheckInstallResult(ret);
811 }
812
813 /// <summary>
814 /// Extracts information from a patch that can be used to determine whether the patch
815 /// applies on a target system. The method returns an XML string that can be provided to
816 /// <see cref="DetermineApplicablePatches(string,string[],InapplicablePatchHandler,string,UserContexts)"/>
817 /// instead of the full patch file.
818 /// </summary>
819 /// <param name="patchPath">Full path to the patch being queried.</param>
820 /// <returns>XML string containing patch data.</returns>
821 /// <remarks><p>
822 /// Win32 MSI API:
823 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiextractpatchxmldata.asp">MsiExtractPatchXMLData</a>
824 /// </p></remarks>
825 public static string ExtractPatchXmlData(string patchPath)
826 {
827 StringBuilder buf = new StringBuilder("");
828 uint bufSize = 0;
829 uint ret = NativeMethods.MsiExtractPatchXMLData(patchPath, 0, buf, ref bufSize);
830 if (ret == (uint) NativeMethods.Error.MORE_DATA)
831 {
832 buf.Capacity = (int) ++bufSize;
833 ret = NativeMethods.MsiExtractPatchXMLData(patchPath, 0, buf, ref bufSize);
834 }
835
836 if (ret != 0)
837 {
838 throw InstallerException.ExceptionFromReturnCode(ret);
839 }
840 return buf.ToString();
841 }
842
843 /// <summary>
844 /// [MSI 3.1] Migrates a user's application configuration data to a new SID.
845 /// </summary>
846 /// <param name="oldSid">Previous user SID that data is to be migrated from</param>
847 /// <param name="newSid">New user SID that data is to be migrated to</param>
848 /// <remarks><p>
849 /// Win32 MSI API:
850 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msinotifysidchange.asp">MsiNotifySidChange</a>
851 /// </p></remarks>
852 public static void NotifySidChange(string oldSid, string newSid)
853 {
854 uint ret = NativeMethods.MsiNotifySidChange(oldSid, newSid);
855 if (ret != 0)
856 {
857 throw InstallerException.ExceptionFromReturnCode(ret);
858 }
859 }
860
861 private static void CheckInstallResult(uint ret)
862 {
863 switch (ret)
864 {
865 case (uint) NativeMethods.Error.SUCCESS: break;
866 case (uint) NativeMethods.Error.SUCCESS_REBOOT_REQUIRED: Installer.rebootRequired = true; break;
867 case (uint) NativeMethods.Error.SUCCESS_REBOOT_INITIATED: Installer.rebootInitiated = true; break;
868 default: throw InstallerException.ExceptionFromReturnCode(ret);
869 }
870 }
871
872 private static int GetPatchStringDataType(string patchData)
873 {
874 if (patchData.IndexOf("<", StringComparison.Ordinal) >= 0 &&
875 patchData.IndexOf(">", StringComparison.Ordinal) >= 0)
876 {
877 return 2; // XML blob
878 }
879 else if (String.Compare(Path.GetExtension(patchData), ".xml",
880 StringComparison.OrdinalIgnoreCase) == 0)
881 {
882 return 1; // XML file path
883 }
884 else
885 {
886 return 0; // MSP file path
887 }
888 }
889}
890}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallerAdvertise.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallerAdvertise.cs
new file mode 100644
index 00000000..9da593d9
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallerAdvertise.cs
@@ -0,0 +1,270 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Reflection;
9 using System.Globalization;
10 using System.Collections.Generic;
11 using System.Diagnostics.CodeAnalysis;
12
13 public static partial class Installer
14 {
15 /// <summary>
16 /// Advertises a product to the local computer.
17 /// </summary>
18 /// <param name="packagePath">Path to the package of the product being advertised</param>
19 /// <param name="perUser">True if the product is user-assigned; false if it is machine-assigned.</param>
20 /// <param name="transforms">Semi-colon delimited list of transforms to be applied. This parameter may be null.</param>
21 /// <param name="locale">The language to use if the source supports multiple languages</param>
22 /// <exception cref="FileNotFoundException">the specified package file does not exist</exception>
23 /// <seealso cref="GenerateAdvertiseScript(string,string,string,int,ProcessorArchitecture,bool)"/>
24 /// <remarks><p>
25 /// Win32 MSI APIs:
26 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiadvertiseproduct.asp">MsiAdvertiseProduct</a>,
27 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiadvertiseproductex.asp">MsiAdvertiseProductEx</a>
28 /// </p></remarks>
29 public static void AdvertiseProduct(string packagePath, bool perUser, string transforms, int locale)
30 {
31 if (String.IsNullOrEmpty(packagePath))
32 {
33 throw new ArgumentNullException("packagePath");
34 }
35
36 if (!File.Exists(packagePath))
37 {
38 throw new FileNotFoundException(null, packagePath);
39 }
40
41 uint ret = NativeMethods.MsiAdvertiseProduct(packagePath, new IntPtr(perUser ? 1 : 0), transforms, (ushort) locale);
42 if (ret != 0)
43 {
44 throw InstallerException.ExceptionFromReturnCode(ret);
45 }
46 }
47
48 /// <summary>
49 /// Generates an advertise script. The method enables the installer to write to a
50 /// script the registry and shortcut information used to assign or publish a product.
51 /// </summary>
52 /// <param name="packagePath">Path to the package of the product being advertised</param>
53 /// <param name="scriptFilePath">path to script file to be created with the advertise information</param>
54 /// <param name="transforms">Semi-colon delimited list of transforms to be applied. This parameter may be null.</param>
55 /// <param name="locale">The language to use if the source supports multiple languages</param>
56 /// <exception cref="FileNotFoundException">the specified package file does not exist</exception>
57 /// <seealso cref="AdvertiseProduct"/>
58 /// <remarks><p>
59 /// Win32 MSI APIs:
60 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiadvertiseproduct.asp">MsiAdvertiseProduct</a>,
61 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiadvertiseproductex.asp">MsiAdvertiseProductEx</a>
62 /// </p></remarks>
63 public static void GenerateAdvertiseScript(string packagePath, string scriptFilePath, string transforms, int locale)
64 {
65 if (String.IsNullOrEmpty(packagePath))
66 {
67 throw new ArgumentNullException("packagePath");
68 }
69
70 if (!File.Exists(packagePath))
71 {
72 throw new FileNotFoundException(null, packagePath);
73 }
74
75 uint ret = NativeMethods.MsiAdvertiseProduct(packagePath, scriptFilePath, transforms, (ushort) locale);
76 if (ret != 0)
77 {
78 throw InstallerException.ExceptionFromReturnCode(ret);
79 }
80 }
81
82 /// <summary>
83 /// Generates an advertise script. The method enables the installer to write to a
84 /// script the registry and shortcut information used to assign or publish a product.
85 /// </summary>
86 /// <param name="packagePath">Path to the package of the product being advertised</param>
87 /// <param name="scriptFilePath">path to script file to be created with the advertise information</param>
88 /// <param name="transforms">Semi-colon delimited list of transforms to be applied. This parameter may be null.</param>
89 /// <param name="locale">The language to use if the source supports multiple languages</param>
90 /// <param name="processor">Targeted processor architecture.</param>
91 /// <param name="instance">True to install multiple instances through product code changing transform.
92 /// Advertises a new instance of the product. Requires that the <paramref name="transforms"/> parameter
93 /// includes the instance transform that changes the product code.</param>
94 /// <seealso cref="AdvertiseProduct"/>
95 /// <remarks><p>
96 /// Win32 MSI APIs:
97 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiadvertiseproduct.asp">MsiAdvertiseProduct</a>,
98 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiadvertiseproductex.asp">MsiAdvertiseProductEx</a>
99 /// </p></remarks>
100 public static void GenerateAdvertiseScript(
101 string packagePath,
102 string scriptFilePath,
103 string transforms,
104 int locale,
105 ProcessorArchitecture processor,
106 bool instance)
107 {
108 if (String.IsNullOrEmpty(packagePath))
109 {
110 throw new ArgumentNullException("packagePath");
111 }
112
113 if (String.IsNullOrEmpty(scriptFilePath))
114 {
115 throw new ArgumentNullException("scriptFilePath");
116 }
117
118 if (!File.Exists(packagePath))
119 {
120 throw new FileNotFoundException(null, packagePath);
121 }
122
123 uint platform = 0;
124 switch (processor)
125 {
126 case ProcessorArchitecture.X86: platform = (uint) 1; break;
127 case ProcessorArchitecture.IA64: platform = (uint) 2; break;
128 case ProcessorArchitecture.Amd64: platform = (uint) 4; break;
129 }
130
131 uint ret = NativeMethods.MsiAdvertiseProductEx(
132 packagePath,
133 scriptFilePath,
134 transforms,
135 (ushort) locale,
136 platform,
137 instance ? (uint) 1 : 0);
138 if (ret != 0)
139 {
140 throw InstallerException.ExceptionFromReturnCode(ret);
141 }
142 }
143
144 /// <summary>
145 /// Copies an advertise script file to the local computer.
146 /// </summary>
147 /// <param name="scriptFile">Path to a script file generated by
148 /// <see cref="GenerateAdvertiseScript(string,string,string,int,ProcessorArchitecture,bool)"/></param>
149 /// <param name="flags">Flags controlling advertisement</param>
150 /// <param name="removeItems">True if specified items are to be removed instead of being created</param>
151 /// <remarks><p>
152 /// The process calling this function must be running under the LocalSystem account. To advertise an
153 /// application for per-user installation to a targeted user, the thread that calls this function must
154 /// impersonate the targeted user. If the thread calling this function is not impersonating a targeted
155 /// user, the application is advertised to all users for installation with elevated privileges.
156 /// </p></remarks>
157 [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "flags")]
158 public static void AdvertiseScript(string scriptFile, int flags, bool removeItems)
159 {
160 uint ret = NativeMethods.MsiAdvertiseScript(scriptFile, (uint) flags, IntPtr.Zero, removeItems);
161 if (ret != 0)
162 {
163 throw InstallerException.ExceptionFromReturnCode(ret);
164 }
165 }
166
167 /// <summary>
168 /// Processes an advertise script file into the specified locations.
169 /// </summary>
170 /// <param name="scriptFile">Path to a script file generated by
171 /// <see cref="GenerateAdvertiseScript(string,string,string,int,ProcessorArchitecture,bool)"/></param>
172 /// <param name="iconFolder">An optional path to a folder in which advertised icon files and transform
173 /// files are located. If this parameter is null, no icon or transform files are written.</param>
174 /// <param name="shortcuts">True if shortcuts should be created</param>
175 /// <param name="removeItems">True if specified items are to be removed instead of created</param>
176 /// <remarks><p>
177 /// The process calling this function must be running under the LocalSystem account. To advertise an
178 /// application for per-user installation to a targeted user, the thread that calls this function must
179 /// impersonate the targeted user. If the thread calling this function is not impersonating a targeted
180 /// user, the application is advertised to all users for installation with elevated privileges.
181 /// </p><p>
182 /// Win32 MSI API:
183 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprocessadvertisescript.asp">MsiProcessAdvertiseScript</a>
184 /// </p></remarks>
185 public static void ProcessAdvertiseScript(string scriptFile, string iconFolder, bool shortcuts, bool removeItems)
186 {
187 uint ret = NativeMethods.MsiProcessAdvertiseScript(scriptFile, iconFolder, IntPtr.Zero, shortcuts, removeItems);
188 if (ret != 0)
189 {
190 throw InstallerException.ExceptionFromReturnCode(ret);
191 }
192 }
193
194 /// <summary>
195 /// Gets product information for an installer script file.
196 /// </summary>
197 /// <param name="scriptFile">Path to a script file generated by
198 /// <see cref="GenerateAdvertiseScript(string,string,string,int,ProcessorArchitecture,bool)"/></param>
199 /// <returns>ProductInstallation stub with advertise-related properties filled in.</returns>
200 /// <exception cref="ArgumentOutOfRangeException">An invalid product property was requested</exception>
201 /// <remarks><p>
202 /// Only the following properties will be filled in in the returned object:<ul>
203 /// <li><see cref="ProductInstallation.ProductCode"/></li>
204 /// <li><see cref="ProductInstallation.AdvertisedLanguage"/></li>
205 /// <li><see cref="ProductInstallation.AdvertisedVersion"/></li>
206 /// <li><see cref="ProductInstallation.AdvertisedProductName"/></li>
207 /// <li><see cref="ProductInstallation.AdvertisedPackageName"/></li>
208 /// </ul>Other properties will be null.
209 /// </p><p>
210 /// Win32 MSI API:
211 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproductinfofromscript.asp">MsiGetProductInfoFromScript</a>
212 /// </p></remarks>
213 public static ProductInstallation GetProductInfoFromScript(string scriptFile)
214 {
215 if (String.IsNullOrEmpty(scriptFile))
216 {
217 throw new ArgumentNullException("scriptFile");
218 }
219 StringBuilder productCodeBuf = new StringBuilder(40);
220 ushort lang;
221 uint ver;
222 StringBuilder productNameBuf = new StringBuilder(100);
223 StringBuilder packageNameBuf = new StringBuilder(40);
224 uint productCodeBufSize = (uint) productCodeBuf.Capacity;
225 uint productNameBufSize = (uint) productNameBuf.Capacity;
226 uint packageNameBufSize = (uint) packageNameBuf.Capacity;
227 uint ret = NativeMethods.MsiGetProductInfoFromScript(
228 scriptFile,
229 productCodeBuf,
230 out lang,
231 out ver,
232 productNameBuf,
233 ref productNameBufSize,
234 packageNameBuf,
235 ref packageNameBufSize);
236 if (ret == (uint) NativeMethods.Error.MORE_DATA)
237 {
238 productCodeBuf.Capacity = (int) ++productCodeBufSize;
239 productNameBuf.Capacity = (int) ++productNameBufSize;
240 packageNameBuf.Capacity = (int) ++packageNameBufSize;
241 ret = NativeMethods.MsiGetProductInfoFromScript(
242 scriptFile,
243 productCodeBuf,
244 out lang,
245 out ver,
246 productNameBuf,
247 ref productNameBufSize,
248 packageNameBuf,
249 ref packageNameBufSize);
250 }
251
252 if (ret != 0)
253 {
254 throw InstallerException.ExceptionFromReturnCode(ret);
255 }
256 uint verPart1 = ver >> 24;
257 uint verPart2 = (ver & 0x00FFFFFF) >> 16;
258 uint verPart3 = ver & 0x0000FFFF;
259 Version version = new Version((int) verPart1, (int) verPart2, (int) verPart3);
260
261 IDictionary<string, string> props = new Dictionary<string, string>();
262 props["ProductCode"] = productCodeBuf.ToString();
263 props["Language"] = lang.ToString(CultureInfo.InvariantCulture);
264 props["Version"] = version.ToString();
265 props["ProductName"] = productNameBuf.ToString();
266 props["PackageName"] = packageNameBuf.ToString();
267 return new ProductInstallation(props);
268 }
269 }
270}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallerUtils.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallerUtils.cs
new file mode 100644
index 00000000..8d9cf0a1
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/InstallerUtils.cs
@@ -0,0 +1,472 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Resources;
9 using System.Reflection;
10 using System.Collections.Generic;
11 using System.Globalization;
12 using System.Runtime.InteropServices;
13 using System.Diagnostics.CodeAnalysis;
14
15 public static partial class Installer
16 {
17 /// <summary>
18 /// Gets the current version of the installer.
19 /// </summary>
20 public static Version Version
21 {
22 get
23 {
24 // TODO: Use the extended form of version info to get the 4th component of the verison.
25 uint[] dllVersionInfo = new uint[5];
26 dllVersionInfo[0] = 20;
27 int hr = NativeMethods.DllGetVersion(dllVersionInfo);
28 if (hr != 0)
29 {
30 Marshal.ThrowExceptionForHR(hr);
31 }
32
33 return new Version((int) dllVersionInfo[1], (int) dllVersionInfo[2], (int) dllVersionInfo[3]);
34 }
35 }
36
37 internal static ResourceManager ErrorResources
38 {
39 get
40 {
41 if (errorResources == null)
42 {
43 errorResources = new ResourceManager(typeof(Installer).Namespace + ".Errors", typeof(Installer).Assembly);
44 }
45 return errorResources;
46 }
47 }
48
49 /// <summary>
50 /// Gets a Windows Installer error message in the system default language.
51 /// </summary>
52 /// <param name="errorNumber">The error number.</param>
53 /// <returns>The message string, or null if the error message is not found.</returns>
54 /// <remarks><p>
55 /// The returned string may have tokens such as [2] and [3] that are meant to be substituted
56 /// with context-specific values.
57 /// </p><p>
58 /// Error numbers greater than 2000 refer to MSI "internal" errors, and are always
59 /// returned in English.
60 /// </p></remarks>
61 public static string GetErrorMessage(int errorNumber)
62 {
63 return Installer.GetErrorMessage(errorNumber, null);
64 }
65
66 /// <summary>
67 /// Gets a Windows Installer error message in a specified language.
68 /// </summary>
69 /// <param name="errorNumber">The error number.</param>
70 /// <param name="culture">The locale for the message.</param>
71 /// <returns>The message string, or null if the error message or locale is not found.</returns>
72 /// <remarks><p>
73 /// The returned string may have tokens such as [2] and [3] that are meant to be substituted
74 /// with context-specific values.
75 /// </p><p>
76 /// Error numbers greater than 2000 refer to MSI "internal" errors, and are always
77 /// returned in English.
78 /// </p></remarks>
79 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
80 public static string GetErrorMessage(int errorNumber, CultureInfo culture)
81 {
82 if (culture == null)
83 {
84 culture = CultureInfo.CurrentCulture;
85 }
86
87 string msg = Installer.ErrorResources.GetString(
88 errorNumber.ToString(CultureInfo.InvariantCulture.NumberFormat),
89 culture);
90 if (msg == null)
91 {
92 string msiMsgModule = Path.Combine(
93 Environment.SystemDirectory, "msimsg.dll");
94 msg = Installer.GetMessageFromModule(
95 msiMsgModule, errorNumber, culture);
96 }
97 return msg;
98 }
99
100 private static string GetMessageFromModule(
101 string modulePath, int errorNumber, CultureInfo culture)
102 {
103 const uint LOAD_LIBRARY_AS_DATAFILE = 2;
104 const int RT_RCDATA = 10;
105
106 IntPtr msgModule = NativeMethods.LoadLibraryEx(
107 modulePath, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE);
108 if (msgModule == IntPtr.Zero)
109 {
110 return null;
111 }
112
113 try
114 {
115 // On pre-Vista systems, the messages are stored as RCDATA resources.
116
117 int lcid = (culture == CultureInfo.InvariantCulture) ?
118 0 : culture.LCID;
119 IntPtr resourceInfo = NativeMethods.FindResourceEx(
120 msgModule,
121 new IntPtr(RT_RCDATA),
122 new IntPtr(errorNumber),
123 (ushort) lcid);
124 if (resourceInfo != IntPtr.Zero)
125 {
126 IntPtr resourceData = NativeMethods.LoadResource(
127 msgModule, resourceInfo);
128 IntPtr resourcePtr = NativeMethods.LockResource(resourceData);
129
130 if (lcid == 0)
131 {
132 string msg = Marshal.PtrToStringAnsi(resourcePtr);
133 return msg;
134 }
135 else
136 {
137 int len = 0;
138 while (Marshal.ReadByte(resourcePtr, len) != 0)
139 {
140 len++;
141 }
142 byte[] msgBytes = new byte[len + 1];
143 Marshal.Copy(resourcePtr, msgBytes, 0, msgBytes.Length);
144 Encoding encoding = Encoding.GetEncoding(
145 culture.TextInfo.ANSICodePage);
146 string msg = encoding.GetString(msgBytes);
147 return msg;
148 }
149 }
150 else
151 {
152 // On Vista (and above?), the messages are stored in the module message table.
153 // They're actually in MUI files, and the redirection happens automatically here.
154
155 const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
156 const uint FORMAT_MESSAGE_FROM_HMODULE = 0x00000800;
157 const uint MESSAGE_OFFSET = 20000; // Not documented, but observed on Vista
158
159 StringBuilder buf = new StringBuilder(1024);
160 uint formatCount = NativeMethods.FormatMessage(
161 FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS,
162 msgModule,
163 (uint) errorNumber + MESSAGE_OFFSET,
164 (ushort) lcid,
165 buf,
166 (uint) buf.Capacity,
167 IntPtr.Zero);
168
169 return formatCount != 0 ? buf.ToString().Trim() : null;
170 }
171 }
172 finally
173 {
174 NativeMethods.FreeLibrary(msgModule);
175 }
176 }
177
178 /// <summary>
179 /// Gets a formatted Windows Installer error message in the system default language.
180 /// </summary>
181 /// <param name="errorRecord">Error record containing the error number in the first field, and
182 /// error-specific parameters in the other fields.</param>
183 /// <returns>The message string, or null if the error message is not found.</returns>
184 /// <remarks><p>
185 /// Error numbers greater than 2000 refer to MSI "internal" errors, and are always
186 /// returned in English.
187 /// </p></remarks>
188 public static string GetErrorMessage(Record errorRecord) { return Installer.GetErrorMessage(errorRecord, null); }
189
190 /// <summary>
191 /// Gets a formatted Windows Installer error message in a specified language.
192 /// </summary>
193 /// <param name="errorRecord">Error record containing the error number in the first field, and
194 /// error-specific parameters in the other fields.</param>
195 /// <param name="culture">The locale for the message.</param>
196 /// <returns>The message string, or null if the error message or locale is not found.</returns>
197 /// <remarks><p>
198 /// Error numbers greater than 2000 refer to MSI "internal" errors, and are always
199 /// returned in English.
200 /// </p></remarks>
201 public static string GetErrorMessage(Record errorRecord, CultureInfo culture)
202 {
203 if (errorRecord == null)
204 {
205 throw new ArgumentNullException("errorRecord");
206 }
207 int errorNumber;
208 if (errorRecord.FieldCount < 1 || (errorNumber = (int) errorRecord.GetInteger(1)) == 0)
209 {
210 throw new ArgumentOutOfRangeException("errorRecord");
211 }
212
213 string msg = Installer.GetErrorMessage(errorNumber, culture);
214 if (msg != null)
215 {
216 errorRecord.FormatString = msg;
217 msg = errorRecord.ToString((IFormatProvider)null);
218 }
219 return msg;
220 }
221
222 /// <summary>
223 /// Gets the version string of the path specified using the format that the installer
224 /// expects to find it in in the database.
225 /// </summary>
226 /// <param name="path">Path to the file</param>
227 /// <returns>Version string in the "#.#.#.#" format, or an empty string if the file
228 /// does not contain version information</returns>
229 /// <exception cref="FileNotFoundException">the file does not exist or could not be read</exception>
230 /// <remarks><p>
231 /// Win32 MSI API:
232 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfileversion.asp">MsiGetFileVersion</a>
233 /// </p></remarks>
234 public static string GetFileVersion(string path)
235 {
236 StringBuilder version = new StringBuilder(20);
237 uint verBufSize = 0, langBufSize = 0;
238 uint ret = NativeMethods.MsiGetFileVersion(path, version, ref verBufSize, null, ref langBufSize);
239 if (ret == (uint) NativeMethods.Error.MORE_DATA)
240 {
241 version.Capacity = (int) ++verBufSize;
242 ret = NativeMethods.MsiGetFileVersion(path, version, ref verBufSize, null, ref langBufSize);
243 }
244
245 if (ret != 0 && ret != (uint) NativeMethods.Error.FILE_INVALID)
246 {
247 if (ret == (uint) NativeMethods.Error.FILE_NOT_FOUND ||
248 ret == (uint) NativeMethods.Error.ACCESS_DENIED)
249 {
250 throw new FileNotFoundException(null, path);
251 }
252 else
253 {
254 throw InstallerException.ExceptionFromReturnCode(ret);
255 }
256 }
257 return version.ToString();
258 }
259
260 /// <summary>
261 /// Gets the language string of the path specified using the format that the installer
262 /// expects to find them in in the database.
263 /// </summary>
264 /// <param name="path">Path to the file</param>
265 /// <returns>Language string in the form of a decimal language ID, or an empty string if the file
266 /// does not contain a language ID</returns>
267 /// <exception cref="FileNotFoundException">the file does not exist or could not be read</exception>
268 /// <remarks><p>
269 /// Win32 MSI API:
270 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfileversion.asp">MsiGetFileVersion</a>
271 /// </p></remarks>
272 public static string GetFileLanguage(string path)
273 {
274 StringBuilder language = new StringBuilder("", 10);
275 uint verBufSize = 0, langBufSize = 0;
276 uint ret = NativeMethods.MsiGetFileVersion(path, null, ref verBufSize, language, ref langBufSize);
277 if (ret == (uint) NativeMethods.Error.MORE_DATA)
278 {
279 language.Capacity = (int) ++langBufSize;
280 ret = NativeMethods.MsiGetFileVersion(path, null, ref verBufSize, language, ref langBufSize);
281 }
282
283 if (ret != 0 && ret != (uint) NativeMethods.Error.FILE_INVALID)
284 {
285 if (ret == (uint) NativeMethods.Error.FILE_NOT_FOUND ||
286 ret == (uint) NativeMethods.Error.ACCESS_DENIED)
287 {
288 throw new FileNotFoundException(null, path);
289 }
290 else
291 {
292 throw InstallerException.ExceptionFromReturnCode(ret);
293 }
294 }
295 return language.ToString();
296 }
297
298 /// <summary>
299 /// Gets a 128-bit hash of the specified file.
300 /// </summary>
301 /// <param name="path">Path to the file</param>
302 /// <param name="hash">Integer array of length 4 which receives the
303 /// four 32-bit parts of the hash value.</param>
304 /// <exception cref="FileNotFoundException">the file does not exist or
305 /// could not be read</exception>
306 /// <remarks><p>
307 /// Win32 MSI API:
308 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfilehash.asp">MsiGetFileHash</a>
309 /// </p></remarks>
310 public static void GetFileHash(string path, int[] hash)
311 {
312 if (hash == null)
313 {
314 throw new ArgumentNullException("hash");
315 }
316
317 uint[] tempHash = new uint[5];
318 tempHash[0] = 20;
319 uint ret = NativeMethods.MsiGetFileHash(path, 0, tempHash);
320 if (ret != 0)
321 {
322 if (ret == (uint) NativeMethods.Error.FILE_NOT_FOUND ||
323 ret == (uint) NativeMethods.Error.ACCESS_DENIED)
324 {
325 throw new FileNotFoundException(null, path);
326 }
327 else
328 {
329 throw InstallerException.ExceptionFromReturnCode(ret);
330 }
331 }
332
333 for (int i = 0; i < 4; i++)
334 {
335 hash[i] = unchecked ((int) tempHash[i + 1]);
336 }
337 }
338
339 /// <summary>
340 /// Examines a shortcut and returns its product, feature name, and component if available.
341 /// </summary>
342 /// <param name="shortcut">Full path to a shortcut</param>
343 /// <returns>ShortcutTarget structure containing target product code, feature, and component code</returns>
344 /// <remarks><p>
345 /// Win32 MSI API:
346 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetshortcuttarget.asp">MsiGetShortcutTarget</a>
347 /// </p></remarks>
348 public static ShortcutTarget GetShortcutTarget(string shortcut)
349 {
350 StringBuilder productBuf = new StringBuilder(40);
351 StringBuilder featureBuf = new StringBuilder(40);
352 StringBuilder componentBuf = new StringBuilder(40);
353
354 uint ret = NativeMethods.MsiGetShortcutTarget(shortcut, productBuf, featureBuf, componentBuf);
355 if (ret != 0)
356 {
357 throw InstallerException.ExceptionFromReturnCode(ret);
358 }
359 return new ShortcutTarget(
360 productBuf.Length > 0 ? productBuf.ToString() : null,
361 featureBuf.Length > 0 ? featureBuf.ToString() : null,
362 componentBuf.Length > 0 ? componentBuf.ToString() : null);
363 }
364
365 /// <summary>
366 /// Verifies that the given file is an installation package.
367 /// </summary>
368 /// <param name="packagePath">Path to the package</param>
369 /// <returns>True if the file is an installation package; false otherwise.</returns>
370 /// <exception cref="FileNotFoundException">the specified package file does not exist</exception>
371 /// <exception cref="InstallerException">the package file could not be opened</exception>
372 /// <remarks><p>
373 /// Win32 MSI API:
374 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiverifypackage.asp">MsiVerifyPackage</a>
375 /// </p></remarks>
376 public static bool VerifyPackage(string packagePath)
377 {
378 if (String.IsNullOrEmpty(packagePath))
379 {
380 throw new ArgumentNullException("packagePath");
381 }
382
383 if (!File.Exists(packagePath))
384 {
385 throw new FileNotFoundException(null, packagePath);
386 }
387
388 uint ret = NativeMethods.MsiVerifyPackage(packagePath);
389 if (ret == (uint) NativeMethods.Error.INSTALL_PACKAGE_INVALID)
390 {
391 return false;
392 }
393 else if (ret != 0)
394 {
395 throw InstallerException.ExceptionFromReturnCode(ret);
396 }
397 return true;
398 }
399
400 /// <summary>
401 /// [MSI 4.0] Gets the list of files that can be updated by one or more patches.
402 /// </summary>
403 /// <param name="productCode">ProductCode (GUID) of the product which is
404 /// the target of the patches</param>
405 /// <param name="patches">list of file paths of one or more patches to be
406 /// analyzed</param>
407 /// <returns>List of absolute paths of files that can be updated when the
408 /// patches are applied on this system.</returns>
409 /// <remarks><p>
410 /// Win32 MSI API:
411 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetpatchfilelist.asp">MsiGetPatchFileList</a>
412 /// </p></remarks>
413 public static IList<string> GetPatchFileList(string productCode, IList<string> patches)
414 {
415 if (String.IsNullOrEmpty(productCode))
416 {
417 throw new ArgumentNullException("productCode");
418 }
419
420 if (patches == null || patches.Count == 0)
421 {
422 throw new ArgumentNullException("patches");
423 }
424
425 StringBuilder patchList = new StringBuilder();
426 foreach (string patch in patches)
427 {
428 if (patch != null)
429 {
430 if (patchList.Length != 0)
431 {
432 patchList.Append(';');
433 }
434
435 patchList.Append(patch);
436 }
437 }
438
439 if (patchList.Length == 0)
440 {
441 throw new ArgumentNullException("patches");
442 }
443
444 IntPtr phFileRecords;
445 uint cFiles;
446
447 uint ret = NativeMethods.MsiGetPatchFileList(
448 productCode,
449 patchList.ToString(),
450 out cFiles,
451 out phFileRecords);
452 if (ret != 0)
453 {
454 throw InstallerException.ExceptionFromReturnCode(ret);
455 }
456
457 List<string> files = new List<string>();
458
459 for (uint i = 0; i < cFiles; i++)
460 {
461 int hFileRec = Marshal.ReadInt32(phFileRecords, (int) i);
462
463 using (Record fileRec = new Record(hFileRec, true, null))
464 {
465 files.Add(fileRec.GetString(1));
466 }
467 }
468
469 return files;
470 }
471 }
472}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/MediaDisk.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/MediaDisk.cs
new file mode 100644
index 00000000..7b196b3e
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/MediaDisk.cs
@@ -0,0 +1,58 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Diagnostics.CodeAnalysis;
7
8 /// <summary>
9 /// Represents a media disk source of a product or a patch.
10 /// </summary>
11 [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
12 public struct MediaDisk
13 {
14 private int diskId;
15 private string volumeLabel;
16 private string diskPrompt;
17
18 /// <summary>
19 /// Creates a new media disk.
20 /// </summary>
21 /// <param name="diskId"></param>
22 /// <param name="volumeLabel"></param>
23 /// <param name="diskPrompt"></param>
24 public MediaDisk(int diskId, string volumeLabel, string diskPrompt)
25 {
26 this.diskId = diskId;
27 this.volumeLabel = volumeLabel;
28 this.diskPrompt = diskPrompt;
29 }
30
31 /// <summary>
32 /// Gets or sets the disk id of the media disk.
33 /// </summary>
34 public int DiskId
35 {
36 get { return this.diskId; }
37 set { this.diskId = value; }
38 }
39
40 /// <summary>
41 /// Gets or sets the volume label of the media disk.
42 /// </summary>
43 public string VolumeLabel
44 {
45 get { return this.volumeLabel; }
46 set { this.volumeLabel = value; }
47 }
48
49 /// <summary>
50 /// Gets or sets the disk prompt of the media disk.
51 /// </summary>
52 public string DiskPrompt
53 {
54 get { return this.diskPrompt; }
55 set { this.diskPrompt = value; }
56 }
57 }
58}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/NativeMethods.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/NativeMethods.cs
new file mode 100644
index 00000000..a438b640
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/NativeMethods.cs
@@ -0,0 +1,309 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5using System;
6using System.Text;
7using System.Runtime.InteropServices;
8using System.Diagnostics.CodeAnalysis;
9
10using IStream = System.Runtime.InteropServices.ComTypes.IStream;
11using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
12using STATSTG = System.Runtime.InteropServices.ComTypes.STATSTG;
13
14[Guid("0000000b-0000-0000-C000-000000000046")]
15[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
16internal interface IStorage
17{
18 [return: MarshalAs(UnmanagedType.Interface)]
19 IStream CreateStream([MarshalAs(UnmanagedType.LPWStr)] string wcsName, uint grfMode, uint reserved1, uint reserved2);
20 [return: MarshalAs(UnmanagedType.Interface)]
21 IStream OpenStream([MarshalAs(UnmanagedType.LPWStr)] string wcsName, IntPtr reserved1, uint grfMode, uint reserved2);
22 [return: MarshalAs(UnmanagedType.Interface)]
23 IStorage CreateStorage([MarshalAs(UnmanagedType.LPWStr)] string wcsName, uint grfMode, uint reserved1, uint reserved2);
24 [return: MarshalAs(UnmanagedType.Interface)]
25 IStorage OpenStorage([MarshalAs(UnmanagedType.LPWStr)] string wcsName, IntPtr stgPriority, uint grfMode, IntPtr snbExclude, uint reserved);
26 void CopyTo(uint ciidExclude, IntPtr rgiidExclude, IntPtr snbExclude, [MarshalAs(UnmanagedType.Interface)] IStorage stgDest);
27 void MoveElementTo([MarshalAs(UnmanagedType.LPWStr)] string wcsName, [MarshalAs(UnmanagedType.Interface)] IStorage stgDest, [MarshalAs(UnmanagedType.LPWStr)] string wcsNewName, uint grfFlags);
28 void Commit(uint grfCommitFlags);
29 void Revert();
30 IntPtr EnumElements(uint reserved1, IntPtr reserved2, uint reserved3);
31 void DestroyElement([MarshalAs(UnmanagedType.LPWStr)] string wcsName);
32 void RenameElement([MarshalAs(UnmanagedType.LPWStr)] string wcsOldName, [MarshalAs(UnmanagedType.LPWStr)] string wcsNewName);
33 void SetElementTimes([MarshalAs(UnmanagedType.LPWStr)] string wcsName, ref FILETIME ctime, ref FILETIME atime, ref FILETIME mtime);
34 void SetClass(ref Guid clsid);
35 void SetStateBits(uint grfStateBits, uint grfMask);
36 void Stat(ref STATSTG statstg, uint grfStatFlag);
37}
38
39internal static class NativeMethods
40{
41 internal enum Error : uint
42 {
43 SUCCESS = 0,
44 FILE_NOT_FOUND = 2,
45 PATH_NOT_FOUND = 3,
46 ACCESS_DENIED = 5,
47 INVALID_HANDLE = 6,
48 INVALID_DATA = 13,
49 INVALID_PARAMETER = 87,
50 OPEN_FAILED = 110,
51 DISK_FULL = 112,
52 CALL_NOT_IMPLEMENTED = 120,
53 BAD_PATHNAME = 161,
54 NO_DATA = 232,
55 MORE_DATA = 234,
56 NO_MORE_ITEMS = 259,
57 DIRECTORY = 267,
58 INSTALL_USEREXIT = 1602,
59 INSTALL_FAILURE = 1603,
60 FILE_INVALID = 1006,
61 UNKNOWN_PRODUCT = 1605,
62 UNKNOWN_FEATURE = 1606,
63 UNKNOWN_COMPONENT = 1607,
64 UNKNOWN_PROPERTY = 1608,
65 INVALID_HANDLE_STATE = 1609,
66 INSTALL_SOURCE_ABSENT = 1612,
67 BAD_QUERY_SYNTAX = 1615,
68 INSTALL_PACKAGE_INVALID = 1620,
69 FUNCTION_FAILED = 1627,
70 INVALID_TABLE = 1628,
71 DATATYPE_MISMATCH = 1629,
72 CREATE_FAILED = 1631,
73 SUCCESS_REBOOT_INITIATED = 1641,
74 SUCCESS_REBOOT_REQUIRED = 3010,
75 }
76
77 internal enum SourceType : int
78 {
79 Unknown = 0,
80 Network = 1,
81 Url = 2,
82 Media = 3,
83 }
84
85 [Flags]
86 internal enum STGM : uint
87 {
88 DIRECT = 0x00000000,
89 TRANSACTED = 0x00010000,
90 SIMPLE = 0x08000000,
91
92 READ = 0x00000000,
93 WRITE = 0x00000001,
94 READWRITE = 0x00000002,
95
96 SHARE_DENY_NONE = 0x00000040,
97 SHARE_DENY_READ = 0x00000030,
98 SHARE_DENY_WRITE = 0x00000020,
99 SHARE_EXCLUSIVE = 0x00000010,
100
101 PRIORITY = 0x00040000,
102 DELETEONRELEASE = 0x04000000,
103 NOSCRATCH = 0x00100000,
104
105 CREATE = 0x00001000,
106 CONVERT = 0x00020000,
107 FAILIFTHERE = 0x00000000,
108
109 NOSNAPSHOT = 0x00200000,
110 DIRECT_SWMR = 0x00400000,
111 }
112
113[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int DllGetVersion(uint[] dvi);
114
115[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetInternalUI(uint dwUILevel, ref IntPtr phWnd);
116[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetInternalUI(uint dwUILevel, IntPtr phWnd);
117[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern NativeExternalUIHandler MsiSetExternalUI([MarshalAs(UnmanagedType.FunctionPtr)] NativeExternalUIHandler puiHandler, uint dwMessageFilter, IntPtr pvContext);
118[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnableLog(uint dwLogMode, string szLogFile, uint dwLogAttributes);
119//[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumProducts(uint iProductIndex, StringBuilder lpProductBuf);
120[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetProductInfo(string szProduct, string szProperty, StringBuilder lpValueBuf, ref uint pcchValueBuf);
121//[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumPatches(string szProduct, uint iPatchIndex, StringBuilder lpPatchBuf, StringBuilder lpTransformsBuf, ref uint pcchTransformsBuf);
122[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetPatchInfo(string szPatch, string szAttribute, StringBuilder lpValueBuf, ref uint pcchValueBuf);
123[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumFeatures(string szProduct, uint iFeatureIndex, StringBuilder lpFeatureBuf, StringBuilder lpParentBuf);
124[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiQueryFeatureState(string szProduct, string szFeature);
125[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiUseFeatureEx(string szProduct, string szFeature, uint dwInstallMode, uint dwReserved);
126[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiQueryProductState(string szProduct);
127[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetShortcutTarget(string szShortcut, StringBuilder szProductCode, StringBuilder szFeatureId, StringBuilder szComponentCode);
128[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiProvideComponent(string szProduct, string szFeature, string szComponent, uint dwInstallMode, StringBuilder lpPathBuf, ref uint cchPathBuf);
129[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiProvideQualifiedComponentEx(string szComponent, string szQualifier, uint dwInstallMode, string szProduct, uint dwUnused1, uint dwUnused2, StringBuilder lpPathBuf, ref uint cchPathBuf);
130[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiReinstallFeature(string szFeature, string szProduct, uint dwReinstallMode);
131[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiReinstallProduct(string szProduct, uint dwReinstallMode);
132//[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListAddSource(string szProduct, string szUserName, uint dwReserved, string szSource);
133//[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListClearAll(string szProduct, string szUserName, uint dwReserved);
134//[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListForceResolution(string szProduct, string szUserName, uint dwReserved);
135[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiCollectUserInfo(string szProduct);
136//[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiGetUserInfo(string szProduct, StringBuilder lpUserNameBuf, ref uint cchUserNameBuf, StringBuilder lpOrgNameBuf, ref uint cchOrgNameBuf, StringBuilder lpSerialBuf, ref uint cchSerialBuf);
137[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiOpenPackageEx(string szPackagePath, uint dwOptions, out int hProduct);
138[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiOpenProduct(string szProduct, out int hProduct);
139[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiInstallProduct(string szPackagePath, string szCommandLine);
140[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiConfigureProductEx(string szProduct, int iInstallLevel, int eInstallState, string szCommandLine);
141[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiConfigureFeature(string szProduct, string szFeature, int eInstallState);
142[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiApplyPatch(string szPatchPackage, string szInstallPackage, int eInstallType, string szCommandLine);
143[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiOpenDatabase(string szDatabasePath, IntPtr uiOpenMode, out int hDatabase);
144[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiOpenDatabase(string szDatabasePath, string szPersist, out int hDatabase);
145[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiGetDatabaseState(int hDatabase);
146[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseOpenView(int hDatabase, string szQuery, out int hView);
147[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseMerge(int hDatabase, int hDatabaseMerge, string szTableName);
148[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseCommit(int hDatabase);
149[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseGetPrimaryKeys(int hDatabase, string szTableName, out int hRecord);
150[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseIsTablePersistent(int hDatabase, string szTableName);
151[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseExport(int hDatabase, string szTableName, string szFolderPath, string szFileName);
152[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseImport(int hDatabase, string szFolderPath, string szFileName);
153[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseGenerateTransform(int hDatabase, int hDatabaseReference, string szTransformFile, int iReserved1, int iReserved2);
154[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiCreateTransformSummaryInfo(int hDatabase, int hDatabaseReference, string szTransformFile, int iErrorConditions, int iValidation);
155[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseApplyTransform(int hDatabase, string szTransformFile, int iErrorConditions);
156[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiViewExecute(int hView, int hRecord);
157[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiViewFetch(int hView, out int hRecord);
158[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiViewModify(int hView, int iModifyMode, int hRecord);
159[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiViewGetError(int hView, StringBuilder szColumnNameBuffer, ref uint cchBuf);
160[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiViewGetColumnInfo(int hView, uint eColumnInfo, out int hRecord);
161[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiCreateRecord(uint cParams);
162[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiFormatRecord(int hInstall, int hRecord, StringBuilder szResultBuf, ref uint cchResultBuf);
163[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordClearData(int hRecord);
164[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordGetFieldCount(int hRecord);
165[DllImport("msi.dll", CharSet=CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool MsiRecordIsNull(int hRecord, uint iField);
166[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiRecordGetInteger(int hRecord, uint iField);
167[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordGetString(int hRecord, uint iField, StringBuilder szValueBuf, ref uint cchValueBuf);
168[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordSetInteger(int hRecord, uint iField, int iValue);
169[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordSetString(int hRecord, uint iField, string szValue);
170[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordDataSize(int hRecord, uint iField);
171[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordReadStream(int hRecord, uint iField, byte[] szDataBuf, ref uint cbDataBuf);
172[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordSetStream(int hRecord, uint iField, string szFilePath);
173[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetSummaryInformation(int hDatabase, string szDatabasePath, uint uiUpdateCount, out int hSummaryInfo);
174//[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSummaryInfoGetPropertyCount(int hSummaryInfo, out uint uiPropertyCount);
175[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSummaryInfoGetProperty(int hSummaryInfo, uint uiProperty, out uint uiDataType, out int iValue, ref long ftValue, StringBuilder szValueBuf, ref uint cchValueBuf);
176[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSummaryInfoSetProperty(int hSummaryInfo, uint uiProperty, uint uiDataType, int iValue, ref long ftValue, string szValue);
177[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSummaryInfoPersist(int hSummaryInfo);
178[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiCloseHandle(int hAny);
179[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFileVersion(string szFilePath, StringBuilder szVersionBuf, ref uint cchVersionBuf, StringBuilder szLangBuf, ref uint cchLangBuf);
180[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFileHash(string szFilePath, uint dwOptions, uint[] hash);
181[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiGetActiveDatabase(int hInstall);
182[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetProperty(int hInstall, string szName, StringBuilder szValueBuf, ref uint cchValueBuf);
183[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetProperty(int hInstall, string szName, string szValue);
184[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiProcessMessage(int hInstall, uint eMessageType, int hRecord);
185[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEvaluateCondition(int hInstall, string szCondition);
186[DllImport("msi.dll", CharSet=CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool MsiGetMode(int hInstall, uint iRunMode);
187[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetMode(int hInstall, uint iRunMode, [MarshalAs(UnmanagedType.Bool)] bool fState);
188[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDoAction(int hInstall, string szAction);
189[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSequence(int hInstall, string szTable, int iSequenceMode);
190[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetSourcePath(int hInstall, string szFolder, StringBuilder szPathBuf, ref uint cchPathBuf);
191[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetTargetPath(int hInstall, string szFolder, StringBuilder szPathBuf, ref uint cchPathBuf);
192[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetTargetPath(int hInstall, string szFolder, string szFolderPath);
193[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetComponentState(int hInstall, string szComponent, out int iInstalled, out int iAction);
194[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetComponentState(int hInstall, string szComponent, int iState);
195[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFeatureState(int hInstall, string szFeature, out int iInstalled, out int iAction);
196[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetFeatureState(int hInstall, string szFeature, int iState);
197[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFeatureValidStates(int hInstall, string szFeature, out uint dwInstallState);
198[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetInstallLevel(int hInstall, int iInstallLevel);
199[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern ushort MsiGetLanguage(int hInstall);
200[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumComponents(uint iComponentIndex, StringBuilder lpComponentBuf);
201[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumComponentsEx(string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwIndex, StringBuilder szInstalledProductCode, [MarshalAs(UnmanagedType.I4)] out UserContexts pdwInstalledContext, StringBuilder szSid, ref uint pcchSid);
202[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumClients(string szComponent, uint iProductIndex, StringBuilder lpProductBuf);
203[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumClientsEx(string szComponent, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint iProductIndex, StringBuilder lpProductBuf, [MarshalAs(UnmanagedType.I4)] out UserContexts pdwInstalledContext, StringBuilder szSid, ref uint pcchSid);
204[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiGetComponentPath(string szProduct, string szComponent, StringBuilder lpPathBuf, ref uint pcchBuf);
205[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiGetComponentPathEx(string szProduct, string szComponent, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, StringBuilder lpPathBuf, ref uint pcchBuf);
206[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumComponentQualifiers(string szComponent, uint iIndex, StringBuilder lpQualifierBuf, ref uint pcchQualifierBuf, StringBuilder lpApplicationDataBuf, ref uint pcchApplicationDataBuf);
207[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiGetLastErrorRecord();
208[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumRelatedProducts(string upgradeCode, uint dwReserved, uint iProductIndex, StringBuilder lpProductBuf);
209[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetProductCode(string szComponent, StringBuilder lpProductBuf);
210[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFeatureUsage(string szProduct, string szFeature, out uint dwUseCount, out ushort dwDateUsed);
211[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFeatureCost(int hInstall, string szFeature, int iCostTree, int iState, out int iCost);
212[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiVerifyPackage(string szPackagePath);
213[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiIsProductElevated(string szProductCode, [MarshalAs(UnmanagedType.Bool)] out bool fElevated);
214[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiAdvertiseProduct(string szPackagePath, IntPtr szScriptFilePath, string szTransforms, ushort lgidLanguage);
215[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiAdvertiseProduct(string szPackagePath, string szScriptFilePath, string szTransforms, ushort lgidLanguage);
216[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiAdvertiseProductEx(string szPackagePath, string szScriptFilePath, string szTransforms, ushort lgidLanguage, uint dwPlatform, uint dwReserved);
217[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiAdvertiseScript(string szScriptFile, uint dwFlags, IntPtr phRegData, [MarshalAs(UnmanagedType.Bool)] bool fRemoveItems);
218[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiProcessAdvertiseScript(string szScriptFile, string szIconFolder, IntPtr hRegData, [MarshalAs(UnmanagedType.Bool)] bool fShortcuts, [MarshalAs(UnmanagedType.Bool)] bool fRemoveItems);
219[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetProductInfoFromScript(string szScriptFile, StringBuilder lpProductBuf39, out ushort plgidLanguage, out uint pdwVersion, StringBuilder lpNameBuf, ref uint cchNameBuf, StringBuilder lpPackageBuf, ref uint cchPackageBuf);
220[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiProvideAssembly(string szAssemblyName, string szAppContext, uint dwInstallMode, uint dwAssemblyInfo, StringBuilder lpPathBuf, ref uint cchPathBuf);
221[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiInstallMissingComponent(string szProduct, string szComponent, int eInstallState);
222[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiInstallMissingFile(string szProduct, string szFile);
223[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiLocateComponent(string szComponent, StringBuilder lpPathBuf, ref uint cchBuf);
224[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetProductProperty(int hProduct, string szProperty, StringBuilder lpValueBuf, ref uint cchValueBuf);
225[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFeatureInfo(int hProduct, string szFeature, out uint lpAttributes, StringBuilder lpTitleBuf, ref uint cchTitleBuf, StringBuilder lpHelpBuf, ref uint cchHelpBuf);
226[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiVerifyDiskSpace(int hInstall);
227[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumComponentCosts(int hInstall, string szComponent, uint dwIndex, int iState, StringBuilder lpDriveBuf, ref uint cchDriveBuf, out int iCost, out int iTempCost);
228[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetFeatureAttributes(int hInstall, string szFeature, uint dwAttributes);
229
230[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRemovePatches(string szPatchList, string szProductCode, int eUninstallType, string szPropertyList);
231[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDetermineApplicablePatches(string szProductPackagePath, uint cPatchInfo, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1), In, Out] MsiPatchSequenceData[] pPatchInfo);
232[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDeterminePatchSequence(string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint cPatchInfo, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=3), In, Out] MsiPatchSequenceData[] pPatchInfo);
233[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiApplyMultiplePatches(string szPatchPackages, string szProductCode, string szPropertiesList);
234[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumPatchesEx(string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwFilter, uint dwIndex, StringBuilder szPatchCode, StringBuilder szTargetProductCode, [MarshalAs(UnmanagedType.I4)] out UserContexts pdwTargetProductContext, StringBuilder szTargetUserSid, ref uint pcchTargetUserSid);
235[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetPatchInfoEx(string szPatchCode, string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, string szProperty, StringBuilder lpValue, ref uint pcchValue);
236[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumProductsEx(string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwIndex, StringBuilder szInstalledProductCode, [MarshalAs(UnmanagedType.I4)] out UserContexts pdwInstalledContext, StringBuilder szSid, ref uint pcchSid);
237[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetProductInfoEx(string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, string szProperty, StringBuilder lpValue, ref uint pcchValue);
238[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiQueryFeatureStateEx(string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, string szFeature, out int pdwState);
239[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiQueryComponentState(string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, string szComponent, out int pdwState);
240[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiExtractPatchXMLData(string szPatchPath, uint dwReserved, StringBuilder szXMLData, ref uint pcchXMLData);
241[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListEnumSources(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, uint dwIndex, StringBuilder szSource, ref uint pcchSource);
242[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListAddSourceEx(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, string szSource, uint dwIndex);
243[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListClearSource(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, string szSource);
244[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListClearAllEx(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions);
245[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListForceResolutionEx(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions);
246[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListGetInfo(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, string szProperty, StringBuilder szValue, ref uint pcchValue);
247[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListSetInfo(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, string szProperty, string szValue);
248[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListEnumMediaDisks(string szProductCodeOrPatchCode, string szUserSID, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, uint dwIndex, out uint pdwDiskId, StringBuilder szVolumeLabel, ref uint pcchVolumeLabel, StringBuilder szDiskPrompt, ref uint pcchDiskPrompt);
249[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListAddMediaDisk(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, uint dwDiskId, string szVolumeLabel, string szDiskPrompt);
250[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListClearMediaDisk(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, uint dwDiskID);
251[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiNotifySidChange(string szOldSid, string szNewSid);
252[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetExternalUIRecord([MarshalAs(UnmanagedType.FunctionPtr)] NativeExternalUIRecordHandler puiHandler, uint dwMessageFilter, IntPtr pvContext, out NativeExternalUIRecordHandler ppuiPrevHandler);
253
254[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetPatchFileList(string szProductCode, string szPatchList, out uint cFiles, out IntPtr phFileRecords);
255
256[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiBeginTransaction(string szTransactionName, int dwTransactionAttributes, out int hTransaction, out IntPtr phChangeOfOwnerEvent);
257[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEndTransaction(int dwTransactionState);
258[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiJoinTransaction(int hTransaction, int dwTransactionAttributes, out IntPtr phChangeOfOwnerEvent);
259
260[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode, EntryPoint="LoadLibraryExW")] internal static extern IntPtr LoadLibraryEx(string fileName, IntPtr hFile, uint flags);
261[DllImport("kernel32.dll", SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool FreeLibrary(IntPtr hModule);
262[DllImport("kernel32.dll", SetLastError=true)] internal static extern IntPtr FindResourceEx(IntPtr hModule, IntPtr type, IntPtr name, ushort langId);
263[DllImport("kernel32.dll", SetLastError=true)] internal static extern IntPtr LoadResource(IntPtr hModule, IntPtr lpResourceInfo);
264[DllImport("kernel32.dll", SetLastError=true)] internal static extern IntPtr LockResource(IntPtr resourceData);
265[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode, EntryPoint="FormatMessageW")] internal static extern uint FormatMessage(uint dwFlags, IntPtr lpSource, uint dwMessageId, uint dwLanguageId, StringBuilder lpBuffer, uint nSize, IntPtr Arguments);
266[DllImport("kernel32.dll", SetLastError=true)] internal static extern int WaitForSingleObject(IntPtr handle, int milliseconds);
267
268[DllImport("ole32.dll")] internal static extern int StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)] string wcsName, IntPtr stgPriority, uint grfMode, IntPtr snbExclude, uint reserved, [MarshalAs(UnmanagedType.Interface)] out IStorage stgOpen);
269[DllImport("ole32.dll")] internal static extern int StgCreateDocfile([MarshalAs(UnmanagedType.LPWStr)] string wcsName, uint grfMode, uint reserved, [MarshalAs(UnmanagedType.Interface)] out IStorage stgOpen);
270
271[DllImport("user32.dll", CharSet=CharSet.Unicode, EntryPoint="MessageBoxW")] internal static extern MessageResult MessageBox(IntPtr hWnd, string lpText, string lpCaption, [MarshalAs(UnmanagedType.U4)] int uType);
272
273 [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
274 internal struct MsiPatchSequenceData
275 {
276 public string szPatchData;
277 public int ePatchDataType;
278 public int dwOrder;
279 public uint dwStatus;
280 }
281
282 internal class MsiHandle : SafeHandle
283 {
284 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
285 public MsiHandle(IntPtr handle, bool ownsHandle)
286 : base(handle, ownsHandle)
287 {
288 }
289
290 public override bool IsInvalid
291 {
292 get
293 {
294 return this.handle == IntPtr.Zero;
295 }
296 }
297
298 public static implicit operator IntPtr(MsiHandle msiHandle)
299 {
300 return msiHandle.handle;
301 }
302
303 protected override bool ReleaseHandle()
304 {
305 return RemotableNativeMethods.MsiCloseHandle((int) this.handle) == 0;
306 }
307 }
308}
309}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/PatchInstallation.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/PatchInstallation.cs
new file mode 100644
index 00000000..defbf64a
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/PatchInstallation.cs
@@ -0,0 +1,413 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Text;
7 using System.Collections.Generic;
8 using System.Globalization;
9 using System.Diagnostics.CodeAnalysis;
10
11 /// <summary>
12 /// The Patch object represents a unique instance of a patch that has been
13 /// registered or applied.
14 /// </summary>
15 public class PatchInstallation : Installation
16 {
17 /// <summary>
18 /// Enumerates all patch installations on the system.
19 /// </summary>
20 /// <returns>Enumeration of patch objects.</returns>
21 /// <remarks><p>
22 /// Win32 MSI API:
23 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumpatches.asp">MsiEnumPatches</a>
24 /// </p></remarks>
25 public static IEnumerable<PatchInstallation> AllPatches
26 {
27 get
28 {
29 return PatchInstallation.GetPatches(null, null, null, UserContexts.All, PatchStates.All);
30 }
31 }
32
33 /// <summary>
34 /// Enumerates patch installations based on certain criteria.
35 /// </summary>
36 /// <param name="patchCode">PatchCode (GUID) of the patch to be enumerated. Only
37 /// instances of patches within the scope of the context specified by the
38 /// <paramref name="userSid"/> and <paramref name="context"/> parameters will be
39 /// enumerated. This parameter may be set to null to enumerate all patches in the specified
40 /// context.</param>
41 /// <param name="targetProductCode">ProductCode (GUID) product whose patches are to be
42 /// enumerated. If non-null, patch enumeration is restricted to instances of this product
43 /// within the specified context. If null, the patches for all products under the specified
44 /// context are enumerated.</param>
45 /// <param name="userSid">Specifies a security identifier (SID) that restricts the context
46 /// of enumeration. A SID value other than s-1-1-0 is considered a user SID and restricts
47 /// enumeration to the current user or any user in the system. The special SID string
48 /// s-1-1-0 (Everyone) specifies enumeration across all users in the system. This parameter
49 /// can be set to null to restrict the enumeration scope to the current user. When
50 /// <paramref name="userSid"/> must be null.</param>
51 /// <param name="context">Specifies the user context.</param>
52 /// <param name="states">The <see cref="PatchStates"/> of patches to return.</param>
53 /// <remarks><p>
54 /// Win32 MSI APIs:
55 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumpatchesex.asp">MsiEnumPatchesEx</a>
56 /// </p></remarks>
57 public static IEnumerable<PatchInstallation> GetPatches(
58 string patchCode,
59 string targetProductCode,
60 string userSid,
61 UserContexts context,
62 PatchStates states)
63 {
64 StringBuilder buf = new StringBuilder(40);
65 StringBuilder targetProductBuf = new StringBuilder(40);
66 UserContexts targetContext;
67 StringBuilder targetSidBuf = new StringBuilder(40);
68 for (uint i = 0; ; i++)
69 {
70 uint targetSidBufSize = (uint) targetSidBuf.Capacity;
71 uint ret = NativeMethods.MsiEnumPatchesEx(
72 targetProductCode,
73 userSid,
74 context,
75 (uint) states,
76 i,
77 buf,
78 targetProductBuf,
79 out targetContext,
80 targetSidBuf,
81 ref targetSidBufSize);
82 if (ret == (uint) NativeMethods.Error.MORE_DATA)
83 {
84 targetSidBuf.Capacity = (int) ++targetSidBufSize;
85 ret = NativeMethods.MsiEnumPatchesEx(
86 targetProductCode,
87 userSid,
88 context,
89 (uint) states,
90 i,
91 buf,
92 targetProductBuf,
93 out targetContext,
94 targetSidBuf,
95 ref targetSidBufSize);
96 }
97
98 if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS)
99 {
100 break;
101 }
102
103 if (ret != 0)
104 {
105 throw InstallerException.ExceptionFromReturnCode(ret);
106 }
107
108 string thisPatchCode = buf.ToString();
109 if (patchCode == null || patchCode == thisPatchCode)
110 {
111 yield return new PatchInstallation(
112 buf.ToString(),
113 targetProductBuf.ToString(),
114 targetSidBuf.ToString(),
115 targetContext);
116 }
117 }
118 }
119
120 private string productCode;
121
122 /// <summary>
123 /// Creates a new object for accessing information about a patch installation on the current system.
124 /// </summary>
125 /// <param name="patchCode">Patch code (GUID) of the patch.</param>
126 /// <param name="productCode">ProductCode (GUID) the patch has been applied to.
127 /// This parameter may be null for patches that are registered only and not yet
128 /// applied to any product.</param>
129 /// <remarks><p>
130 /// All available user contexts will be queried.
131 /// </p></remarks>
132 public PatchInstallation(string patchCode, string productCode)
133 : this(patchCode, productCode, null, UserContexts.All)
134 {
135 }
136
137 /// <summary>
138 /// Creates a new object for accessing information about a patch installation on the current system.
139 /// </summary>
140 /// <param name="patchCode">Registered patch code (GUID) of the patch.</param>
141 /// <param name="productCode">ProductCode (GUID) the patch has been applied to.
142 /// This parameter may be null for patches that are registered only and not yet
143 /// applied to any product.</param>
144 /// <param name="userSid">The specific user, when working in a user context. This
145 /// parameter may be null to indicate the current user. The parameter must be null
146 /// when working in a machine context.</param>
147 /// <param name="context">The user context. The calling process must have administrative
148 /// privileges to get information for a product installed for a user other than the
149 /// current user.</param>
150 /// <remarks><p>
151 /// If the <paramref name="productCode"/> is null, the Patch object may
152 /// only be used to read and update the patch's SourceList information.
153 /// </p></remarks>
154 public PatchInstallation(string patchCode, string productCode, string userSid, UserContexts context)
155 : base(patchCode, userSid, context)
156 {
157 if (String.IsNullOrEmpty(patchCode))
158 {
159 throw new ArgumentNullException("patchCode");
160 }
161
162 this.productCode = productCode;
163 }
164
165 /// <summary>
166 /// Gets the patch code (GUID) of the patch.
167 /// </summary>
168 public string PatchCode
169 {
170 get
171 {
172 return this.InstallationCode;
173 }
174 }
175
176 /// <summary>
177 /// Gets the ProductCode (GUID) of the product.
178 /// </summary>
179 public string ProductCode
180 {
181 get
182 {
183 return this.productCode;
184 }
185 }
186
187 /// <summary>
188 /// Gets a value indicating whether this patch is currently installed.
189 /// </summary>
190 public override bool IsInstalled
191 {
192 get
193 {
194 return (this.State & PatchStates.Applied) != 0;
195 }
196 }
197
198 /// <summary>
199 /// Gets a value indicating whether this patch is marked as obsolte.
200 /// </summary>
201 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Obsoleted")]
202 public bool IsObsoleted
203 {
204 get
205 {
206 return (this.State & PatchStates.Obsoleted) != 0;
207 }
208 }
209
210 /// <summary>
211 /// Gets a value indicating whether this patch is present but has been
212 /// superseded by a more recent installed patch.
213 /// </summary>
214 public bool IsSuperseded
215 {
216 get
217 {
218 return (this.State & PatchStates.Superseded) != 0;
219 }
220 }
221
222 internal override int InstallationType
223 {
224 get
225 {
226 const int MSICODE_PATCH = 0x40000000;
227 return MSICODE_PATCH;
228 }
229 }
230
231 /// <summary>
232 /// Gets the installation state of this instance of the patch.
233 /// </summary>
234 /// <exception cref="ArgumentException">An unknown patch was requested</exception>
235 /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
236 public PatchStates State
237 {
238 get
239 {
240 string stateString = this["State"];
241 return (PatchStates) Int32.Parse(stateString, CultureInfo.InvariantCulture.NumberFormat);
242 }
243 }
244
245 /// <summary>
246 /// Gets the cached patch file that the product uses.
247 /// </summary>
248 public string LocalPackage
249 {
250 get
251 {
252 return this["LocalPackage"];
253 }
254 }
255
256 /// <summary>
257 /// Gets the set of patch transforms that the last patch
258 /// installation applied to the product.
259 /// </summary>
260 /// <remarks><p>
261 /// This value may not be available for per-user, non-managed applications
262 /// if the user is not logged on.
263 /// </p></remarks>
264 public string Transforms
265 {
266 get
267 {
268 // TODO: convert to IList<string>?
269 return this["Transforms"];
270 }
271 }
272
273 /// <summary>
274 /// Gets the date and time when the patch is applied to the product.
275 /// </summary>
276 public DateTime InstallDate
277 {
278 get
279 {
280 try
281 {
282 return DateTime.ParseExact(
283 this["InstallDate"], "yyyyMMdd", CultureInfo.InvariantCulture);
284 }
285 catch (FormatException)
286 {
287 return DateTime.MinValue;
288 }
289 }
290 }
291
292 /// <summary>
293 /// True patch is marked as possible to uninstall from the product.
294 /// </summary>
295 /// <remarks><p>
296 /// Even if this property is true, the installer can still block the
297 /// uninstallation if this patch is required by another patch that
298 /// cannot be uninstalled.
299 /// </p></remarks>
300 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Uninstallable")]
301 public bool Uninstallable
302 {
303 get
304 {
305 return this["Uninstallable"] == "1";
306 }
307 }
308
309 /// <summary>
310 /// Get the registered display name for the patch.
311 /// </summary>
312 public string DisplayName
313 {
314 get
315 {
316 return this["DisplayName"];
317 }
318 }
319
320 /// <summary>
321 /// Gets the registered support information URL for the patch.
322 /// </summary>
323 public Uri MoreInfoUrl
324 {
325 get
326 {
327 string value = this["MoreInfoURL"];
328 if (!String.IsNullOrEmpty(value))
329 {
330 try
331 {
332 return new Uri(value);
333 }
334 catch (UriFormatException) { }
335 }
336
337 return null;
338 }
339 }
340
341 /// <summary>
342 /// Gets information about a specific patch installation.
343 /// </summary>
344 /// <param name="propertyName">The property being retrieved; see remarks for valid properties.</param>
345 /// <returns>The property value, or an empty string if the property is not set for the patch.</returns>
346 /// <exception cref="ArgumentOutOfRangeException">An unknown patch or property was requested</exception>
347 /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
348 /// <remarks><p>
349 /// Win32 MSI APIs:
350 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetpatchinfo.asp">MsiGetPatchInfo</a>,
351 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetpatchinfoex.asp">MsiGetPatchInfoEx</a>
352 /// </p></remarks>
353 public override string this[string propertyName]
354 {
355 get
356 {
357 StringBuilder buf = new StringBuilder("");
358 uint bufSize = 0;
359 uint ret;
360
361 if (this.Context == UserContexts.UserManaged ||
362 this.Context == UserContexts.UserUnmanaged ||
363 this.Context == UserContexts.Machine)
364 {
365 ret = NativeMethods.MsiGetPatchInfoEx(
366 this.PatchCode,
367 this.ProductCode,
368 this.UserSid,
369 this.Context,
370 propertyName,
371 buf,
372 ref bufSize);
373 if (ret == (uint) NativeMethods.Error.MORE_DATA)
374 {
375 buf.Capacity = (int) ++bufSize;
376 ret = NativeMethods.MsiGetPatchInfoEx(
377 this.PatchCode,
378 this.ProductCode,
379 this.UserSid,
380 this.Context,
381 propertyName,
382 buf,
383 ref bufSize);
384 }
385 }
386 else
387 {
388 ret = NativeMethods.MsiGetPatchInfo(
389 this.PatchCode,
390 propertyName,
391 buf,
392 ref bufSize);
393 if (ret == (uint) NativeMethods.Error.MORE_DATA)
394 {
395 buf.Capacity = (int) ++bufSize;
396 ret = NativeMethods.MsiGetPatchInfo(
397 this.PatchCode,
398 propertyName,
399 buf,
400 ref bufSize);
401 }
402 }
403
404 if (ret != 0)
405 {
406 return null;
407 }
408
409 return buf.ToString();
410 }
411 }
412 }
413}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/ProductInstallation.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/ProductInstallation.cs
new file mode 100644
index 00000000..27739e17
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/ProductInstallation.cs
@@ -0,0 +1,801 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Text;
7 using System.Collections.Generic;
8 using System.Globalization;
9 using System.Diagnostics.CodeAnalysis;
10
11 /// <summary>
12 /// Represents a unique instance of a product that
13 /// is either advertised, installed or unknown.
14 /// </summary>
15 public class ProductInstallation : Installation
16 {
17 /// <summary>
18 /// Gets the set of all products with a specified upgrade code. This method lists the
19 /// currently installed and advertised products that have the specified UpgradeCode
20 /// property in their Property table.
21 /// </summary>
22 /// <param name="upgradeCode">Upgrade code of related products</param>
23 /// <returns>Enumeration of product codes</returns>
24 /// <remarks><p>
25 /// Win32 MSI API:
26 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumrelatedproducts.asp">MsiEnumRelatedProducts</a>
27 /// </p></remarks>
28 public static IEnumerable<ProductInstallation> GetRelatedProducts(string upgradeCode)
29 {
30 StringBuilder buf = new StringBuilder(40);
31 for (uint i = 0; true; i++)
32 {
33 uint ret = NativeMethods.MsiEnumRelatedProducts(upgradeCode, 0, i, buf);
34 if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break;
35 if (ret != 0)
36 {
37 throw InstallerException.ExceptionFromReturnCode(ret);
38 }
39 yield return new ProductInstallation(buf.ToString());
40 }
41 }
42
43 /// <summary>
44 /// Enumerates all product installations on the system.
45 /// </summary>
46 /// <returns>An enumeration of product objects.</returns>
47 /// <remarks><p>
48 /// Win32 MSI API:
49 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumproducts.asp">MsiEnumProducts</a>,
50 /// </p></remarks>
51 public static IEnumerable<ProductInstallation> AllProducts
52 {
53 get
54 {
55 return GetProducts(null, null, UserContexts.All);
56 }
57 }
58
59 /// <summary>
60 /// Enumerates product installations based on certain criteria.
61 /// </summary>
62 /// <param name="productCode">ProductCode (GUID) of the product instances to be enumerated. Only
63 /// instances of products within the scope of the context specified by the
64 /// <paramref name="userSid"/> and <paramref name="context"/> parameters will be
65 /// enumerated. This parameter may be set to null to enumerate all products in the specified
66 /// context.</param>
67 /// <param name="userSid">Specifies a security identifier (SID) that restricts the context
68 /// of enumeration. A SID value other than s-1-1-0 is considered a user SID and restricts
69 /// enumeration to the current user or any user in the system. The special SID string
70 /// s-1-1-0 (Everyone) specifies enumeration across all users in the system. This parameter
71 /// can be set to null to restrict the enumeration scope to the current user. When
72 /// <paramref name="context"/> is set to the machine context only,
73 /// <paramref name="userSid"/> must be null.</param>
74 /// <param name="context">Specifies the user context.</param>
75 /// <returns>An enumeration of product objects for enumerated product instances.</returns>
76 /// <remarks><p>
77 /// Win32 MSI API:
78 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumproductsex.asp">MsiEnumProductsEx</a>
79 /// </p></remarks>
80 public static IEnumerable<ProductInstallation> GetProducts(
81 string productCode, string userSid, UserContexts context)
82 {
83 StringBuilder buf = new StringBuilder(40);
84 UserContexts targetContext;
85 StringBuilder targetSidBuf = new StringBuilder(40);
86 for (uint i = 0; ; i++)
87 {
88 uint targetSidBufSize = (uint) targetSidBuf.Capacity;
89 uint ret = NativeMethods.MsiEnumProductsEx(
90 productCode,
91 userSid,
92 context,
93 i,
94 buf,
95 out targetContext,
96 targetSidBuf,
97 ref targetSidBufSize);
98 if (ret == (uint) NativeMethods.Error.MORE_DATA)
99 {
100 targetSidBuf.Capacity = (int) ++targetSidBufSize;
101 ret = NativeMethods.MsiEnumProductsEx(
102 productCode,
103 userSid,
104 context,
105 i,
106 buf,
107 out targetContext,
108 targetSidBuf,
109 ref targetSidBufSize);
110 }
111
112 if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS)
113 {
114 break;
115 }
116
117 if (ret != 0)
118 {
119 throw InstallerException.ExceptionFromReturnCode(ret);
120 }
121
122 yield return new ProductInstallation(
123 buf.ToString(),
124 targetSidBuf.ToString(),
125 targetContext);
126 }
127 }
128
129 private IDictionary<string, string> properties;
130
131 /// <summary>
132 /// Creates a new object for accessing information about a product installation on the current system.
133 /// </summary>
134 /// <param name="productCode">ProductCode (GUID) of the product.</param>
135 /// <remarks><p>
136 /// All available user contexts will be queried.
137 /// </p></remarks>
138 public ProductInstallation(string productCode)
139 : this(productCode, null, UserContexts.All)
140 {
141 }
142
143 /// <summary>
144 /// Creates a new object for accessing information about a product installation on the current system.
145 /// </summary>
146 /// <param name="productCode">ProductCode (GUID) of the product.</param>
147 /// <param name="userSid">The specific user, when working in a user context. This
148 /// parameter may be null to indicate the current user. The parameter must be null
149 /// when working in a machine context.</param>
150 /// <param name="context">The user context. The calling process must have administrative
151 /// privileges to get information for a product installed for a user other than the
152 /// current user.</param>
153 public ProductInstallation(string productCode, string userSid, UserContexts context)
154 : base(productCode, userSid, context)
155 {
156 if (String.IsNullOrEmpty(productCode))
157 {
158 throw new ArgumentNullException("productCode");
159 }
160 }
161
162 internal ProductInstallation(IDictionary<string, string> properties)
163 : base(properties["ProductCode"], null, UserContexts.None)
164 {
165 this.properties = properties;
166 }
167
168 /// <summary>
169 /// Gets the set of published features for the product.
170 /// </summary>
171 /// <returns>Enumeration of published features for the product.</returns>
172 /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
173 /// <remarks><p>
174 /// Because features are not ordered, any new feature has an arbitrary index, meaning
175 /// this property can return features in any order.
176 /// </p><p>
177 /// Win32 MSI API:
178 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumfeatures.asp">MsiEnumFeatures</a>
179 /// </p></remarks>
180 public IEnumerable<FeatureInstallation> Features
181 {
182 get
183 {
184 StringBuilder buf = new StringBuilder(256);
185 for (uint i = 0; ; i++)
186 {
187 uint ret = NativeMethods.MsiEnumFeatures(this.ProductCode, i, buf, null);
188
189 if (ret != 0)
190 {
191 break;
192 }
193
194 yield return new FeatureInstallation(buf.ToString(), this.ProductCode);
195 }
196 }
197 }
198
199 /// <summary>
200 /// Gets the ProductCode (GUID) of the product.
201 /// </summary>
202 public string ProductCode
203 {
204 get { return this.InstallationCode; }
205 }
206
207 /// <summary>
208 /// Gets a value indicating whether this product is installed on the current system.
209 /// </summary>
210 public override bool IsInstalled
211 {
212 get
213 {
214 return (this.State == InstallState.Default);
215 }
216 }
217
218 /// <summary>
219 /// Gets a value indicating whether this product is advertised on the current system.
220 /// </summary>
221 public bool IsAdvertised
222 {
223 get
224 {
225 return (this.State == InstallState.Advertised);
226 }
227 }
228
229 /// <summary>
230 /// Checks whether the product is installed with elevated privileges. An application is called
231 /// a "managed application" if elevated (system) privileges are used to install the application.
232 /// </summary>
233 /// <returns>True if the product is elevated; false otherwise</returns>
234 /// <remarks><p>
235 /// Note that this property does not take into account policies such as AlwaysInstallElevated,
236 /// but verifies that the local system owns the product's registry data.
237 /// </p></remarks>
238 public bool IsElevated
239 {
240 get
241 {
242 bool isElevated;
243 uint ret = NativeMethods.MsiIsProductElevated(this.ProductCode, out isElevated);
244 if (ret != 0)
245 {
246 throw InstallerException.ExceptionFromReturnCode(ret);
247 }
248 return isElevated;
249 }
250 }
251
252 /// <summary>
253 /// Gets the source list of this product installation.
254 /// </summary>
255 public override SourceList SourceList
256 {
257 get
258 {
259 return this.properties == null ? base.SourceList : null;
260 }
261 }
262
263 internal InstallState State
264 {
265 get
266 {
267 if (this.properties != null)
268 {
269 return InstallState.Unknown;
270 }
271 else
272 {
273 int installState = NativeMethods.MsiQueryProductState(this.ProductCode);
274 return (InstallState) installState;
275 }
276 }
277 }
278
279 internal override int InstallationType
280 {
281 get
282 {
283 const int MSICODE_PRODUCT = 0x00000000;
284 return MSICODE_PRODUCT;
285 }
286 }
287
288 /// <summary>
289 /// The support link.
290 /// </summary>
291 public string HelpLink
292 {
293 get
294 {
295 return this["HelpLink"];
296 }
297 }
298
299 /// <summary>
300 /// The support telephone.
301 /// </summary>
302 public string HelpTelephone
303 {
304 get
305 {
306 return this["HelpTelephone"];
307 }
308 }
309
310 /// <summary>
311 /// Date and time the product was installed.
312 /// </summary>
313 public DateTime InstallDate
314 {
315 get
316 {
317 try
318 {
319 return DateTime.ParseExact(
320 this["InstallDate"], "yyyyMMdd", CultureInfo.InvariantCulture);
321 }
322 catch (FormatException)
323 {
324 return DateTime.MinValue;
325 }
326 }
327 }
328
329 /// <summary>
330 /// The installed product name.
331 /// </summary>
332 public string ProductName
333 {
334 get
335 {
336 return this["InstalledProductName"];
337 }
338 }
339
340 /// <summary>
341 /// The installation location.
342 /// </summary>
343 public string InstallLocation
344 {
345 get
346 {
347 return this["InstallLocation"];
348 }
349 }
350
351 /// <summary>
352 /// The installation source.
353 /// </summary>
354 public string InstallSource
355 {
356 get
357 {
358 return this["InstallSource"];
359 }
360 }
361
362 /// <summary>
363 /// The local cached package.
364 /// </summary>
365 public string LocalPackage
366 {
367 get
368 {
369 return this["LocalPackage"];
370 }
371 }
372
373 /// <summary>
374 /// The publisher.
375 /// </summary>
376 public string Publisher
377 {
378 get
379 {
380 return this["Publisher"];
381 }
382 }
383
384 /// <summary>
385 /// URL about information.
386 /// </summary>
387 public Uri UrlInfoAbout
388 {
389 get
390 {
391 string value = this["URLInfoAbout"];
392 if (!String.IsNullOrEmpty(value))
393 {
394 try
395 {
396 return new Uri(value);
397 }
398 catch (UriFormatException) { }
399 }
400
401 return null;
402 }
403 }
404
405 /// <summary>
406 /// The URL update information.
407 /// </summary>
408 public Uri UrlUpdateInfo
409 {
410 get
411 {
412 string value = this["URLUpdateInfo"];
413 if (!String.IsNullOrEmpty(value))
414 {
415 try
416 {
417 return new Uri(value);
418 }
419 catch (UriFormatException) { }
420 }
421
422 return null;
423 }
424 }
425
426 /// <summary>
427 /// The product version.
428 /// </summary>
429 public Version ProductVersion
430 {
431 get
432 {
433 string ver = this["VersionString"];
434 return ProductInstallation.ParseVersion(ver);
435 }
436 }
437
438 /// <summary>
439 /// The product identifier.
440 /// </summary>
441 /// <remarks><p>
442 /// For more information, see
443 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/productid.asp">ProductID</a>
444 /// </p></remarks>
445 public string ProductId
446 {
447 get
448 {
449 return this["ProductID"];
450 }
451 }
452
453 /// <summary>
454 /// The company that is registered to use the product.
455 /// </summary>
456 public string RegCompany
457 {
458 get
459 {
460 return this["RegCompany"];
461 }
462 }
463
464 /// <summary>
465 /// The owner who is registered to use the product.
466 /// </summary>
467 public string RegOwner
468 {
469 get
470 {
471 return this["RegOwner"];
472 }
473 }
474
475 /// <summary>
476 /// Transforms.
477 /// </summary>
478 public string AdvertisedTransforms
479 {
480 get
481 {
482 return this["Transforms"];
483 }
484 }
485
486 /// <summary>
487 /// Product language.
488 /// </summary>
489 public string AdvertisedLanguage
490 {
491 get
492 {
493 return this["Language"];
494 }
495 }
496
497 /// <summary>
498 /// Human readable product name.
499 /// </summary>
500 public string AdvertisedProductName
501 {
502 get
503 {
504 return this["ProductName"];
505 }
506 }
507
508 /// <summary>
509 /// True if the product is advertised per-machine;
510 /// false if it is per-user or not advertised.
511 /// </summary>
512 public bool AdvertisedPerMachine
513 {
514 get
515 {
516 return this["AssignmentType"] == "1";
517 }
518 }
519
520 /// <summary>
521 /// Identifier of the package that a product is installed from.
522 /// </summary>
523 public string AdvertisedPackageCode
524 {
525 get
526 {
527 return this["PackageCode"];
528 }
529 }
530
531 /// <summary>
532 /// Version of the advertised product.
533 /// </summary>
534 public Version AdvertisedVersion
535 {
536 get
537 {
538 string ver = this["Version"];
539 return ProductInstallation.ParseVersion(ver);
540 }
541 }
542
543 /// <summary>
544 /// Primary icon for the package.
545 /// </summary>
546 public string AdvertisedProductIcon
547 {
548 get
549 {
550 return this["ProductIcon"];
551 }
552 }
553
554 /// <summary>
555 /// Name of the installation package for the advertised product.
556 /// </summary>
557 public string AdvertisedPackageName
558 {
559 get
560 {
561 return this["PackageName"];
562 }
563 }
564
565 /// <summary>
566 /// True if the advertised product can be serviced by
567 /// non-administrators without elevation.
568 /// </summary>
569 public bool PrivilegedPatchingAuthorized
570 {
571 get
572 {
573 return this["AuthorizedLUAApp"] == "1";
574 }
575 }
576
577 /// <summary>
578 /// Gets information about an installation of a product.
579 /// </summary>
580 /// <param name="propertyName">Name of the property being retrieved.</param>
581 /// <exception cref="ArgumentOutOfRangeException">An unknown product or property was requested</exception>
582 /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
583 /// <remarks><p>
584 /// Win32 MSI APIs:
585 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproductinfo.asp">MsiGetProductInfo</a>,
586 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproductinfoex.asp">MsiGetProductInfoEx</a>
587 /// </p></remarks>
588 public override string this[string propertyName]
589 {
590 get
591 {
592 if (this.properties != null)
593 {
594 string value = null;
595 this.properties.TryGetValue(propertyName, out value);
596 return value;
597 }
598 else
599 {
600 StringBuilder buf = new StringBuilder(40);
601 uint bufSize = (uint) buf.Capacity;
602 uint ret;
603
604 if (this.Context == UserContexts.UserManaged ||
605 this.Context == UserContexts.UserUnmanaged ||
606 this.Context == UserContexts.Machine)
607 {
608 ret = NativeMethods.MsiGetProductInfoEx(
609 this.ProductCode,
610 this.UserSid,
611 this.Context,
612 propertyName,
613 buf,
614 ref bufSize);
615 if (ret == (uint) NativeMethods.Error.MORE_DATA)
616 {
617 buf.Capacity = (int) ++bufSize;
618 ret = NativeMethods.MsiGetProductInfoEx(
619 this.ProductCode,
620 this.UserSid,
621 this.Context,
622 propertyName,
623 buf,
624 ref bufSize);
625 }
626 }
627 else
628 {
629 ret = NativeMethods.MsiGetProductInfo(
630 this.ProductCode,
631 propertyName,
632 buf,
633 ref bufSize);
634 if (ret == (uint) NativeMethods.Error.MORE_DATA)
635 {
636 buf.Capacity = (int) ++bufSize;
637 ret = NativeMethods.MsiGetProductInfo(
638 this.ProductCode,
639 propertyName,
640 buf,
641 ref bufSize);
642 }
643 }
644
645 if (ret != 0)
646 {
647 return null;
648 }
649
650 return buf.ToString();
651 }
652 }
653 }
654
655 /// <summary>
656 /// Gets the installed state for a product feature.
657 /// </summary>
658 /// <param name="feature">The feature being queried; identifier from the
659 /// Feature table</param>
660 /// <returns>Installation state of the feature for the product instance: either
661 /// <see cref="InstallState.Local"/>, <see cref="InstallState.Source"/>,
662 /// or <see cref="InstallState.Advertised"/>.</returns>
663 /// <remarks><p>
664 /// Win32 MSI APIs:
665 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiqueryfeaturestate.asp">MsiQueryFeatureState</a>,
666 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiqueryfeaturestateex.asp">MsiQueryFeatureStateEx</a>
667 /// </p></remarks>
668 public InstallState GetFeatureState(string feature)
669 {
670 if (this.properties != null)
671 {
672 return InstallState.Unknown;
673 }
674 else
675 {
676 int installState;
677 uint ret = NativeMethods.MsiQueryFeatureStateEx(
678 this.ProductCode,
679 this.UserSid,
680 this.Context,
681 feature,
682 out installState);
683 if (ret != 0)
684 {
685 throw InstallerException.ExceptionFromReturnCode(ret);
686 }
687 return (InstallState) installState;
688 }
689 }
690
691 /// <summary>
692 /// Gets the installed state for a product component.
693 /// </summary>
694 /// <param name="component">The component being queried; GUID of the component
695 /// as found in the ComponentId column of the Component table.</param>
696 /// <returns>Installation state of the component for the product instance: either
697 /// <see cref="InstallState.Local"/> or <see cref="InstallState.Source"/>.</returns>
698 /// <remarks><p>
699 /// Win32 MSI API:
700 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiquerycomponnetstate.asp">MsiQueryComponentState</a>
701 /// </p></remarks>
702 public InstallState GetComponentState(string component)
703 {
704 if (this.properties != null)
705 {
706 return InstallState.Unknown;
707 }
708 else
709 {
710 int installState;
711 uint ret = NativeMethods.MsiQueryComponentState(
712 this.ProductCode,
713 this.UserSid,
714 this.Context,
715 component,
716 out installState);
717 if (ret != 0)
718 {
719 throw InstallerException.ExceptionFromReturnCode(ret);
720 }
721 return (InstallState) installState;
722 }
723 }
724
725 /// <summary>
726 /// Obtains and stores the user information and product ID from an installation wizard.
727 /// </summary>
728 /// <remarks><p>
729 /// This method is typically called by an application during the first run of the application. The application
730 /// first gets the <see cref="ProductInstallation.ProductId"/> or <see cref="ProductInstallation.RegOwner"/>.
731 /// If those properties are missing, the application calls CollectUserInfo.
732 /// CollectUserInfo opens the product's installation package and invokes a wizard sequence that collects
733 /// user information. Upon completion of the sequence, user information is registered. Since this API requires
734 /// an authored user interface, the user interface level should be set to full by calling
735 /// <see cref="Installer.SetInternalUI(InstallUIOptions)"/> as <see cref="InstallUIOptions.Full"/>.
736 /// </p><p>
737 /// The CollectUserInfo method invokes a FirstRun dialog from the product installation database.
738 /// </p><p>
739 /// Win32 MSI API:
740 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msicollectuserinfo.asp">MsiCollectUserInfo</a>
741 /// </p></remarks>
742 public void CollectUserInfo()
743 {
744 if (this.properties == null)
745 {
746 uint ret = NativeMethods.MsiCollectUserInfo(this.InstallationCode);
747 if (ret != 0)
748 {
749 throw InstallerException.ExceptionFromReturnCode(ret);
750 }
751 }
752 }
753
754 /// <summary>
755 /// Some products might write some invalid/nonstandard version strings to the registry.
756 /// This method tries to get the best data it can.
757 /// </summary>
758 /// <param name="ver">Version string retrieved from the registry.</param>
759 /// <returns>Version object, or null if the version string is completely invalid.</returns>
760 private static Version ParseVersion(string ver)
761 {
762 if (ver != null)
763 {
764 int dotCount = 0;
765 for (int i = 0; i < ver.Length; i++)
766 {
767 char c = ver[i];
768 if (c == '.') dotCount++;
769 else if (!Char.IsDigit(c))
770 {
771 ver = ver.Substring(0, i);
772 break;
773 }
774 }
775
776 if (ver.Length > 0)
777 {
778 if (dotCount == 0)
779 {
780 ver = ver + ".0";
781 }
782 else if (dotCount > 3)
783 {
784 string[] verSplit = ver.Split('.');
785 ver = String.Join(".", verSplit, 0, 4);
786 }
787
788 try
789 {
790 return new Version(ver);
791 }
792 catch (ArgumentException)
793 {
794 }
795 }
796 }
797
798 return null;
799 }
800 }
801}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Record.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Record.cs
new file mode 100644
index 00000000..2d31fa75
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Record.cs
@@ -0,0 +1,1048 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Collections.Generic;
9 using System.Runtime.InteropServices;
10 using System.Diagnostics.CodeAnalysis;
11
12 /// <summary>
13 /// The Record object is a container for holding and transferring a variable number of values.
14 /// Fields within the record are numerically indexed and can contain strings, integers, streams,
15 /// and null values. Record fields are indexed starting with 1. Field 0 is a special format field.
16 /// </summary>
17 /// <remarks><p>
18 /// Most methods on the Record class have overloads that allow using either a number
19 /// or a name to designate a field. However note that field names only exist when the
20 /// Record is directly returned from a query on a database. For other records, attempting
21 /// to access a field by name will result in an InvalidOperationException.
22 /// </p></remarks>
23 public class Record : InstallerHandle
24 {
25 private View view;
26 private bool isFormatStringInvalid;
27
28 /// <summary>
29 /// IsFormatStringInvalid is set from several View methods that invalidate the FormatString
30 /// and used to determine behavior during Record.ToString().
31 /// </summary>
32 internal protected bool IsFormatStringInvalid
33 {
34 set { this.isFormatStringInvalid = value; }
35
36 get { return this.isFormatStringInvalid; }
37 }
38
39 /// <summary>
40 /// Creates a new record object with the requested number of fields.
41 /// </summary>
42 /// <param name="fieldCount">Required number of fields, which may be 0.
43 /// The maximum number of fields in a record is limited to 65535.</param>
44 /// <remarks><p>
45 /// The Record object should be <see cref="InstallerHandle.Close"/>d after use.
46 /// It is best that the handle be closed manually as soon as it is no longer
47 /// needed, as leaving lots of unused handles open can degrade performance.
48 /// </p><p>
49 /// Win32 MSI API:
50 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msicreaterecord.asp">MsiCreateRecord</a>
51 /// </p></remarks>
52 public Record(int fieldCount)
53 : this((IntPtr) RemotableNativeMethods.MsiCreateRecord((uint) fieldCount, 0), true, (View) null)
54 {
55 }
56
57 /// <summary>
58 /// Creates a new record object, providing values for an arbitrary number of fields.
59 /// </summary>
60 /// <param name="fields">The values of the record fields. The parameters should be of type Int16, Int32 or String</param>
61 /// <remarks><p>
62 /// The Record object should be <see cref="InstallerHandle.Close"/>d after use.
63 /// It is best that the handle be closed manually as soon as it is no longer
64 /// needed, as leaving lots of unused handles open can degrade performance.
65 /// </p><p>
66 /// Win32 MSI API:
67 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msicreaterecord.asp">MsiCreateRecord</a>
68 /// </p></remarks>
69 public Record(params object[] fields)
70 : this(fields.Length)
71 {
72 for (int i = 0; i < fields.Length; i++)
73 {
74 this[i + 1] = fields[i];
75 }
76 }
77
78 internal Record(IntPtr handle, bool ownsHandle, View view)
79 : base(handle, ownsHandle)
80 {
81 this.view = view;
82 }
83
84 /// <summary>
85 /// Gets the number of fields in a record.
86 /// </summary>
87 /// <remarks><p>
88 /// Win32 MSI API:
89 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetfieldcount.asp">MsiRecordGetFieldCount</a>
90 /// </p></remarks>
91 public int FieldCount
92 {
93 get
94 {
95 return (int) RemotableNativeMethods.MsiRecordGetFieldCount((int) this.Handle);
96 }
97 }
98
99 /// <summary>
100 /// Gets or sets field 0 of the Record, which is the format string.
101 /// </summary>
102 public string FormatString
103 {
104 get { return this.GetString(0); }
105 set { this.SetString(0, value); }
106 }
107
108 /// <summary>
109 /// Gets or sets a record field value.
110 /// </summary>
111 /// <param name="fieldName">Specifies the name of the field of the Record to get or set.</param>
112 /// <exception cref="ArgumentOutOfRangeException">The name does not match any known field of the Record.</exception>
113 /// <remarks><p>
114 /// When getting a field, the type of the object returned depends on the type of the Record field.
115 /// The object will be one of: Int16, Int32, String, Stream, or null.
116 /// </p><p>
117 /// When setting a field, the type of the object provided will be converted to match the View
118 /// query that returned the record, or if Record was not returned from a view then the type of
119 /// the object provided will determine the type of the Record field. The object should be one of:
120 /// Int16, Int32, String, Stream, or null.
121 /// </p></remarks>
122 public object this[string fieldName]
123 {
124 get
125 {
126 int field = this.FindColumn(fieldName);
127 return this[field];
128 }
129
130 set
131 {
132 int field = this.FindColumn(fieldName);
133 this[field] = value;
134 }
135 }
136
137 /// <summary>
138 /// Gets or sets a record field value.
139 /// </summary>
140 /// <param name="field">Specifies the field of the Record to get or set.</param>
141 /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
142 /// number of fields in the Record.</exception>
143 /// <remarks><p>
144 /// Record fields are indexed starting with 1. Field 0 is a special format field.
145 /// </p><p>
146 /// When getting a field, the type of the object returned depends on the type of the Record field.
147 /// The object will be one of: Int16, Int32, String, Stream, or null. If the Record was returned
148 /// from a View, the type will match that of the field from the View query. Otherwise, the type
149 /// will match the type of the last value set for the field.
150 /// </p><p>
151 /// When setting a field, the type of the object provided will be converted to match the View
152 /// query that returned the Record, or if Record was not returned from a View then the type of
153 /// the object provided will determine the type of the Record field. The object should be one of:
154 /// Int16, Int32, String, Stream, or null.
155 /// </p><p>
156 /// The type-specific getters and setters are slightly more efficient than this property, since
157 /// they don't have to do the extra work to infer the value's type every time.
158 /// </p><p>
159 /// Win32 MSI APIs:
160 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetinteger.asp">MsiRecordGetInteger</a>,
161 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetstring.asp">MsiRecordGetString</a>,
162 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetinteger.asp">MsiRecordSetInteger</a>,
163 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetstring.asp">MsiRecordSetString</a>
164 /// </p></remarks>
165 public object this[int field]
166 {
167 get
168 {
169 if (field == 0)
170 {
171 return this.GetString(0);
172 }
173 else
174 {
175 Type valueType = null;
176 if (this.view != null)
177 {
178 this.CheckRange(field);
179
180 valueType = this.view.Columns[field - 1].Type;
181 }
182
183 if (valueType == null || valueType == typeof(String))
184 {
185 return this.GetString(field);
186 }
187 else if (valueType == typeof(Stream))
188 {
189 return this.IsNull(field) ? null : new RecordStream(this, field);
190 }
191 else
192 {
193 int? value = this.GetNullableInteger(field);
194 return value.HasValue ? (object) value.Value : null;
195 }
196 }
197 }
198
199 set
200 {
201 if (field == 0)
202 {
203 if (value == null)
204 {
205 value = String.Empty;
206 }
207
208 this.SetString(0, value.ToString());
209 }
210 else if (value == null)
211 {
212 this.SetNullableInteger(field, null);
213 }
214 else
215 {
216 Type valueType = value.GetType();
217 if (valueType == typeof(Int32) || valueType == typeof(Int16))
218 {
219 this.SetInteger(field, (int) value);
220 }
221 else if (valueType.IsSubclassOf(typeof(Stream)))
222 {
223 this.SetStream(field, (Stream) value);
224 }
225 else
226 {
227 this.SetString(field, value.ToString());
228 }
229 }
230 }
231 }
232
233 /// <summary>
234 /// Creates a new Record object from an integer record handle.
235 /// </summary>
236 /// <remarks><p>
237 /// This method is only provided for interop purposes. A Record object
238 /// should normally be obtained by calling <see cref="WixToolset.Dtf.WindowsInstaller.View.Fetch"/>
239 /// other methods.
240 /// <p>The handle will be closed when this object is disposed or finalized.</p>
241 /// </p></remarks>
242 /// <param name="handle">Integer record handle</param>
243 /// <param name="ownsHandle">true to close the handle when this object is disposed or finalized</param>
244 public static Record FromHandle(IntPtr handle, bool ownsHandle)
245 {
246 return new Record(handle, ownsHandle, (View) null);
247 }
248
249 /// <summary>
250 /// Sets all fields in a record to null.
251 /// </summary>
252 /// <remarks><p>
253 /// Win32 MSI API:
254 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordcleardata.asp">MsiRecordClearData</a>
255 /// </p></remarks>
256 public void Clear()
257 {
258 uint ret = RemotableNativeMethods.MsiRecordClearData((int) this.Handle);
259 if (ret != 0)
260 {
261 throw InstallerException.ExceptionFromReturnCode(ret);
262 }
263 }
264
265 /// <summary>
266 /// Reports whether a record field is null.
267 /// </summary>
268 /// <param name="field">Specifies the field to check.</param>
269 /// <returns>True if the field is null, false otherwise.</returns>
270 /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
271 /// number of fields in the Record.</exception>
272 /// <remarks><p>
273 /// Win32 MSI API:
274 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordisnull.asp">MsiRecordIsNull</a>
275 /// </p></remarks>
276 public bool IsNull(int field)
277 {
278 this.CheckRange(field);
279 return RemotableNativeMethods.MsiRecordIsNull((int) this.Handle, (uint) field);
280 }
281
282 /// <summary>
283 /// Reports whether a record field is null.
284 /// </summary>
285 /// <param name="fieldName">Specifies the field to check.</param>
286 /// <returns>True if the field is null, false otherwise.</returns>
287 /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
288 /// of the named fields in the Record.</exception>
289 public bool IsNull(string fieldName)
290 {
291 int field = this.FindColumn(fieldName);
292 return this.IsNull(field);
293 }
294
295 /// <summary>
296 /// Gets the length of a record field. The count does not include the terminating null.
297 /// </summary>
298 /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
299 /// number of fields in the Record.</exception>
300 /// <remarks><p>
301 /// The returned data size is 0 if the field is null, non-existent,
302 /// or an internal object pointer. The method also returns 0 if the handle is not a valid
303 /// Record handle.
304 /// </p><p>
305 /// If the data is in integer format, the property returns 2 or 4.
306 /// </p><p>
307 /// If the data is in string format, the property returns the character count
308 /// (not including the NULL terminator).
309 /// </p><p>
310 /// If the data is in stream format, the property returns the byte count.
311 /// </p><p>
312 /// Win32 MSI API:
313 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecorddatasize.asp">MsiRecordDataSize</a>
314 /// </p></remarks>
315 public int GetDataSize(int field)
316 {
317 this.CheckRange(field);
318 return (int) RemotableNativeMethods.MsiRecordDataSize((int) this.Handle, (uint) field);
319 }
320
321 /// <summary>
322 /// Gets the length of a record field. The count does not include the terminating null.
323 /// </summary>
324 /// <param name="fieldName">Specifies the field to check.</param>
325 /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
326 /// of the named fields in the Record.</exception>
327 /// <remarks><p>The returned data size is 0 if the field is null, non-existent,
328 /// or an internal object pointer. The method also returns 0 if the handle is not a valid
329 /// Record handle.
330 /// </p><p>
331 /// If the data is in integer format, the property returns 2 or 4.
332 /// </p><p>
333 /// If the data is in string format, the property returns the character count
334 /// (not including the NULL terminator).
335 /// </p><p>
336 /// If the data is in stream format, the property returns the byte count.
337 /// </p></remarks>
338 public int GetDataSize(string fieldName)
339 {
340 int field = this.FindColumn(fieldName);
341 return this.GetDataSize(field);
342 }
343
344 /// <summary>
345 /// Gets a field value as an integer.
346 /// </summary>
347 /// <param name="field">Specifies the field to retrieve.</param>
348 /// <returns>Integer value of the field, or 0 if the field is null.</returns>
349 /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
350 /// number of fields in the Record.</exception>
351 /// <remarks><p>
352 /// Win32 MSI API:
353 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetinteger.asp">MsiRecordGetInteger</a>
354 /// </p></remarks>
355 /// <seealso cref="GetNullableInteger(int)"/>
356 [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "integer")]
357 public int GetInteger(int field)
358 {
359 this.CheckRange(field);
360
361 int value = RemotableNativeMethods.MsiRecordGetInteger((int) this.Handle, (uint) field);
362 if (value == Int32.MinValue) // MSI_NULL_INTEGER
363 {
364 return 0;
365 }
366 return value;
367 }
368
369 /// <summary>
370 /// Gets a field value as an integer.
371 /// </summary>
372 /// <param name="fieldName">Specifies the field to retrieve.</param>
373 /// <returns>Integer value of the field, or 0 if the field is null.</returns>
374 /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
375 /// of the named fields in the Record.</exception>
376 /// <seealso cref="GetNullableInteger(string)"/>
377 [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "integer")]
378 public int GetInteger(string fieldName)
379 {
380 int field = this.FindColumn(fieldName);
381 return this.GetInteger(field);
382 }
383
384 /// <summary>
385 /// Gets a field value as an integer.
386 /// </summary>
387 /// <param name="field">Specifies the field to retrieve.</param>
388 /// <returns>Integer value of the field, or null if the field is null.</returns>
389 /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
390 /// number of fields in the Record.</exception>
391 /// <remarks><p>
392 /// Win32 MSI API:
393 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetinteger.asp">MsiRecordGetInteger</a>
394 /// </p></remarks>
395 /// <seealso cref="GetInteger(int)"/>
396 public int? GetNullableInteger(int field)
397 {
398 this.CheckRange(field);
399
400 int value = RemotableNativeMethods.MsiRecordGetInteger((int) this.Handle, (uint) field);
401 if (value == Int32.MinValue) // MSI_NULL_INTEGER
402 {
403 return null;
404 }
405 return value;
406 }
407
408 /// <summary>
409 /// Gets a field value as an integer.
410 /// </summary>
411 /// <param name="fieldName">Specifies the field to retrieve.</param>
412 /// <returns>Integer value of the field, or null if the field is null.</returns>
413 /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
414 /// of the named fields in the Record.</exception>
415 /// <seealso cref="GetInteger(string)"/>
416 public int? GetNullableInteger(string fieldName)
417 {
418 int field = this.FindColumn(fieldName);
419 return this.GetInteger(field);
420 }
421
422 /// <summary>
423 /// Sets the value of a field to an integer.
424 /// </summary>
425 /// <param name="field">Specifies the field to set.</param>
426 /// <param name="value">new value of the field</param>
427 /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
428 /// number of fields in the Record.</exception>
429 /// <remarks><p>
430 /// Win32 MSI API:
431 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetinteger.asp">MsiRecordSetInteger</a>
432 /// </p></remarks>
433 /// <seealso cref="SetNullableInteger(int,int?)"/>
434 public void SetInteger(int field, int value)
435 {
436 this.CheckRange(field);
437
438 uint ret = RemotableNativeMethods.MsiRecordSetInteger((int) this.Handle, (uint) field, value);
439 if (ret != 0)
440 {
441 throw InstallerException.ExceptionFromReturnCode(ret);
442 }
443 }
444
445 /// <summary>
446 /// Sets the value of a field to an integer.
447 /// </summary>
448 /// <param name="fieldName">Specifies the field to set.</param>
449 /// <param name="value">new value of the field</param>
450 /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
451 /// of the named fields in the Record.</exception>
452 /// <seealso cref="SetNullableInteger(string,int?)"/>
453 public void SetInteger(string fieldName, int value)
454 {
455 int field = this.FindColumn(fieldName);
456 this.SetInteger(field, value);
457 }
458
459 /// <summary>
460 /// Sets the value of a field to a nullable integer.
461 /// </summary>
462 /// <param name="field">Specifies the field to set.</param>
463 /// <param name="value">new value of the field</param>
464 /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
465 /// number of fields in the Record.</exception>
466 /// <remarks><p>
467 /// Win32 MSI API:
468 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetinteger.asp">MsiRecordSetInteger</a>
469 /// </p></remarks>
470 /// <seealso cref="SetInteger(int,int)"/>
471 public void SetNullableInteger(int field, int? value)
472 {
473 this.CheckRange(field);
474
475 uint ret = RemotableNativeMethods.MsiRecordSetInteger(
476 (int) this.Handle,
477 (uint) field,
478 value.HasValue ? (int) value : Int32.MinValue);
479 if (ret != 0)
480 {
481 throw InstallerException.ExceptionFromReturnCode(ret);
482 }
483 }
484
485 /// <summary>
486 /// Sets the value of a field to a nullable integer.
487 /// </summary>
488 /// <param name="fieldName">Specifies the field to set.</param>
489 /// <param name="value">new value of the field</param>
490 /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
491 /// of the named fields in the Record.</exception>
492 /// <seealso cref="SetInteger(string,int)"/>
493 public void SetNullableInteger(string fieldName, int? value)
494 {
495 int field = this.FindColumn(fieldName);
496 this.SetNullableInteger(field, value);
497 }
498
499 /// <summary>
500 /// Gets a field value as a string.
501 /// </summary>
502 /// <param name="field">Specifies the field to retrieve.</param>
503 /// <returns>String value of the field, or an empty string if the field is null.</returns>
504 /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
505 /// number of fields in the Record.</exception>
506 /// <remarks><p>
507 /// Win32 MSI API:
508 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetstring.asp">MsiRecordGetString</a>
509 /// </p></remarks>
510 public string GetString(int field)
511 {
512 this.CheckRange(field);
513
514 StringBuilder buf = new StringBuilder(String.Empty);
515 uint bufSize = 0;
516 uint ret = RemotableNativeMethods.MsiRecordGetString((int) this.Handle, (uint) field, buf, ref bufSize);
517 if (ret == (uint) NativeMethods.Error.MORE_DATA)
518 {
519 buf.Capacity = (int) ++bufSize;
520 ret = RemotableNativeMethods.MsiRecordGetString((int) this.Handle, (uint) field, buf, ref bufSize);
521 }
522 if (ret != 0)
523 {
524 throw InstallerException.ExceptionFromReturnCode(ret);
525 }
526 return buf.ToString();
527 }
528
529 /// <summary>
530 /// Gets a field value as a string.
531 /// </summary>
532 /// <param name="fieldName">Specifies the field to retrieve.</param>
533 /// <returns>String value of the field, or an empty string if the field is null.</returns>
534 /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
535 /// of the named fields in the Record.</exception>
536 public string GetString(string fieldName)
537 {
538 int field = this.FindColumn(fieldName);
539 return this.GetString(field);
540 }
541
542 /// <summary>
543 /// Sets the value of a field to a string.
544 /// </summary>
545 /// <param name="field">Specifies the field to set.</param>
546 /// <param name="value">new value of the field</param>
547 /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
548 /// number of fields in the Record.</exception>
549 /// <remarks><p>
550 /// Win32 MSI API:
551 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetstring.asp">MsiRecordSetString</a>
552 /// </p></remarks>
553 public void SetString(int field, string value)
554 {
555 this.CheckRange(field);
556
557 if (value == null)
558 {
559 value = String.Empty;
560 }
561
562 uint ret = RemotableNativeMethods.MsiRecordSetString((int) this.Handle, (uint) field, value);
563 if (ret != 0)
564 {
565 throw InstallerException.ExceptionFromReturnCode(ret);
566 }
567
568 // If we set the FormatString manually, then it should be valid again
569 if (field == 0)
570 {
571 this.IsFormatStringInvalid = false;
572 }
573 }
574
575 /// <summary>
576 /// Sets the value of a field to a string.
577 /// </summary>
578 /// <param name="fieldName">Specifies the field to set.</param>
579 /// <param name="value">new value of the field</param>
580 /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
581 /// of the named fields in the Record.</exception>
582 public void SetString(string fieldName, string value)
583 {
584 int field = this.FindColumn(fieldName);
585 this.SetString(field, value);
586 }
587
588 /// <summary>
589 /// Reads a record stream field into a file.
590 /// </summary>
591 /// <param name="field">Specifies the field of the Record to get.</param>
592 /// <param name="filePath">Specifies the path to the file to contain the stream.</param>
593 /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
594 /// number of fields in the Record.</exception>
595 /// <exception cref="NotSupportedException">Attempt to extract a storage from a database open
596 /// in read-write mode, or from a database without an associated file path</exception>
597 /// <remarks><p>
598 /// This method is capable of directly extracting substorages. To do so, first select both the
599 /// `Name` and `Data` column of the `_Storages` table, then get the stream of the `Data` field.
600 /// However, substorages may only be extracted from a database that is open in read-only mode.
601 /// </p><p>
602 /// Win32 MSI API:
603 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordreadstream.asp">MsiRecordReadStream</a>
604 /// </p></remarks>
605 public void GetStream(int field, string filePath)
606 {
607 if (String.IsNullOrEmpty(filePath))
608 {
609 throw new ArgumentNullException("filePath");
610 }
611
612 IList<TableInfo> tables = (this.view != null ? this.view.Tables : null);
613 if (tables != null && tables.Count == 1 && tables[0].Name == "_Storages" && field == this.FindColumn("Data"))
614 {
615 if (!this.view.Database.IsReadOnly)
616 {
617 throw new NotSupportedException("Database must be opened read-only to support substorage extraction.");
618 }
619 else if (this.view.Database.FilePath == null)
620 {
621 throw new NotSupportedException("Database must have an associated file path to support substorage extraction.");
622 }
623 else if (this.FindColumn("Name") <= 0)
624 {
625 throw new NotSupportedException("Name column must be part of the Record in order to extract substorage.");
626 }
627 else
628 {
629 Record.ExtractSubStorage(this.view.Database.FilePath, this.GetString("Name"), filePath);
630 }
631 }
632 else
633 {
634 if (!this.IsNull(field))
635 {
636 Stream readStream = null, writeStream = null;
637 try
638 {
639 readStream = new RecordStream(this, field);
640 writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write);
641 int count = 512;
642 byte[] buf = new byte[count];
643 while (count == buf.Length)
644 {
645 if ((count = readStream.Read(buf, 0, buf.Length)) > 0)
646 {
647 writeStream.Write(buf, 0, count);
648 }
649 }
650 }
651 finally
652 {
653 if (readStream != null) readStream.Close();
654 if (writeStream != null) writeStream.Close();
655 }
656 }
657 }
658 }
659
660 /// <summary>
661 /// Reads a record stream field into a file.
662 /// </summary>
663 /// <param name="fieldName">Specifies the field of the Record to get.</param>
664 /// <param name="filePath">Specifies the path to the file to contain the stream.</param>
665 /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
666 /// of the named fields in the Record.</exception>
667 /// <exception cref="NotSupportedException">Attempt to extract a storage from a database open
668 /// in read-write mode, or from a database without an associated file path</exception>
669 /// <remarks><p>
670 /// This method is capable of directly extracting substorages. To do so, first select both the
671 /// `Name` and `Data` column of the `_Storages` table, then get the stream of the `Data` field.
672 /// However, substorages may only be extracted from a database that is open in read-only mode.
673 /// </p></remarks>
674 public void GetStream(string fieldName, string filePath)
675 {
676 int field = this.FindColumn(fieldName);
677 this.GetStream(field, filePath);
678 }
679
680 /// <summary>
681 /// Gets a record stream field.
682 /// </summary>
683 /// <param name="field">Specifies the field of the Record to get.</param>
684 /// <returns>A Stream that reads the field data.</returns>
685 /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
686 /// number of fields in the Record.</exception>
687 /// <remarks><p>
688 /// This method is not capable of reading substorages. To extract a substorage,
689 /// use <see cref="GetStream(int,string)"/>.
690 /// </p><p>
691 /// Win32 MSI API:
692 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordreadstream.asp">MsiRecordReadStream</a>
693 /// </p></remarks>
694 public Stream GetStream(int field)
695 {
696 this.CheckRange(field);
697
698 return this.IsNull(field) ? null : new RecordStream(this, field);
699 }
700
701 /// <summary>
702 /// Gets a record stream field.
703 /// </summary>
704 /// <param name="fieldName">Specifies the field of the Record to get.</param>
705 /// <returns>A Stream that reads the field data.</returns>
706 /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
707 /// of the named fields in the Record.</exception>
708 /// <remarks><p>
709 /// This method is not capable of reading substorages. To extract a substorage,
710 /// use <see cref="GetStream(string,string)"/>.
711 /// </p></remarks>
712 public Stream GetStream(string fieldName)
713 {
714 int field = this.FindColumn(fieldName);
715 return this.GetStream(field);
716 }
717
718 /// <summary>
719 /// Sets a record stream field from a file. Stream data cannot be inserted into temporary fields.
720 /// </summary>
721 /// <param name="field">Specifies the field of the Record to set.</param>
722 /// <param name="filePath">Specifies the path to the file containing the stream.</param>
723 /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
724 /// number of fields in the Record.</exception>
725 /// <remarks><p>
726 /// The contents of the specified file are read into a stream object. The stream persists if
727 /// the Record is inserted into the Database and the Database is committed.
728 /// </p><p>
729 /// To reset the stream to its beginning you must pass in null for filePath.
730 /// Do not pass an empty string, "", to reset the stream.
731 /// </p><p>
732 /// Setting a stream with this method is more efficient than setting a field to a
733 /// FileStream object.
734 /// </p><p>
735 /// Win32 MSI API:
736 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetstream.asp">MsiRecordsetStream</a>
737 /// </p></remarks>
738 public void SetStream(int field, string filePath)
739 {
740 this.CheckRange(field);
741
742 if (String.IsNullOrEmpty(filePath))
743 {
744 throw new ArgumentNullException("filePath");
745 }
746
747 uint ret = RemotableNativeMethods.MsiRecordSetStream((int) this.Handle, (uint) field, filePath);
748 if (ret != 0)
749 {
750 throw InstallerException.ExceptionFromReturnCode(ret);
751 }
752 }
753
754 /// <summary>
755 /// Sets a record stream field from a file. Stream data cannot be inserted into temporary fields.
756 /// </summary>
757 /// <param name="fieldName">Specifies the field name of the Record to set.</param>
758 /// <param name="filePath">Specifies the path to the file containing the stream.</param>
759 /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
760 /// of the named fields in the Record.</exception>
761 /// <remarks><p>
762 /// The contents of the specified file are read into a stream object. The stream persists if
763 /// the Record is inserted into the Database and the Database is committed.
764 /// To reset the stream to its beginning you must pass in null for filePath.
765 /// Do not pass an empty string, "", to reset the stream.
766 /// </p><p>
767 /// Setting a stream with this method is more efficient than setting a field to a
768 /// FileStream object.
769 /// </p></remarks>
770 public void SetStream(string fieldName, string filePath)
771 {
772 int field = this.FindColumn(fieldName);
773 this.SetStream(field, filePath);
774 }
775
776 /// <summary>
777 /// Sets a record stream field from a Stream object. Stream data cannot be inserted into temporary fields.
778 /// </summary>
779 /// <param name="field">Specifies the field of the Record to set.</param>
780 /// <param name="stream">Specifies the stream data.</param>
781 /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
782 /// number of fields in the Record.</exception>
783 /// <remarks><p>
784 /// The stream persists if the Record is inserted into the Database and the Database is committed.
785 /// </p><p>
786 /// Win32 MSI API:
787 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetstream.asp">MsiRecordsetStream</a>
788 /// </p></remarks>
789 public void SetStream(int field, Stream stream)
790 {
791 this.CheckRange(field);
792
793 if (stream == null)
794 {
795 uint ret = RemotableNativeMethods.MsiRecordSetStream((int) this.Handle, (uint) field, null);
796 if (ret != 0)
797 {
798 throw InstallerException.ExceptionFromReturnCode(ret);
799 }
800 }
801 else
802 {
803 Stream writeStream = null;
804 string tempPath = Path.GetTempFileName();
805 try
806 {
807 writeStream = new FileStream(tempPath, FileMode.Truncate, FileAccess.Write);
808 byte[] buf = new byte[512];
809 int count;
810 while ((count = stream.Read(buf, 0, buf.Length)) > 0)
811 {
812 writeStream.Write(buf, 0, count);
813 }
814 writeStream.Close();
815 writeStream = null;
816
817 uint ret = RemotableNativeMethods.MsiRecordSetStream((int) this.Handle, (uint) field, tempPath);
818 if (ret != 0)
819 {
820 throw InstallerException.ExceptionFromReturnCode(ret);
821 }
822 }
823 finally
824 {
825 if (writeStream != null) writeStream.Close();
826 if (File.Exists(tempPath))
827 {
828 try
829 {
830 File.Delete(tempPath);
831 }
832 catch (IOException)
833 {
834 if (this.view != null)
835 {
836 this.view.Database.DeleteOnClose(tempPath);
837 }
838 }
839 }
840 }
841 }
842 }
843
844 /// <summary>
845 /// Sets a record stream field from a Stream object. Stream data cannot be inserted into temporary fields.
846 /// </summary>
847 /// <param name="fieldName">Specifies the field name of the Record to set.</param>
848 /// <param name="stream">Specifies the stream data.</param>
849 /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
850 /// of the named fields in the Record.</exception>
851 /// <remarks><p>
852 /// The stream persists if the Record is inserted into the Database and the Database is committed.
853 /// </p></remarks>
854 public void SetStream(string fieldName, Stream stream)
855 {
856 int field = this.FindColumn(fieldName);
857 this.SetStream(field, stream);
858 }
859
860 /// <summary>
861 /// Gets a formatted string representation of the Record.
862 /// </summary>
863 /// <returns>A formatted string representation of the Record.</returns>
864 /// <remarks><p>
865 /// If field 0 of the Record is set to a nonempty string, it is used to format the data in the Record.
866 /// </p><p>
867 /// Win32 MSI API:
868 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a>
869 /// </p></remarks>
870 /// <seealso cref="FormatString"/>
871 /// <seealso cref="Session.FormatRecord(Record)"/>
872 public override string ToString()
873 {
874 return this.ToString((IFormatProvider) null);
875 }
876
877 /// <summary>
878 /// Gets a formatted string representation of the Record, optionally using a Session to format properties.
879 /// </summary>
880 /// <param name="provider">an optional Session instance that will be used to lookup any
881 /// properties in the Record's format string</param>
882 /// <returns>A formatted string representation of the Record.</returns>
883 /// <remarks><p>
884 /// If field 0 of the Record is set to a nonempty string, it is used to format the data in the Record.
885 /// </p><p>
886 /// Win32 MSI API:
887 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a>
888 /// </p></remarks>
889 /// <seealso cref="FormatString"/>
890 /// <seealso cref="Session.FormatRecord(Record)"/>
891 public string ToString(IFormatProvider provider)
892 {
893 if (this.IsFormatStringInvalid) // Format string is invalid
894 {
895 // TODO: return all values by default?
896 return String.Empty;
897 }
898
899 InstallerHandle session = provider as InstallerHandle;
900 int sessionHandle = session != null ? (int) session.Handle : 0;
901 StringBuilder buf = new StringBuilder(String.Empty);
902 uint bufSize = 1;
903 uint ret = RemotableNativeMethods.MsiFormatRecord(sessionHandle, (int) this.Handle, buf, ref bufSize);
904 if (ret == (uint) NativeMethods.Error.MORE_DATA)
905 {
906 bufSize++;
907 buf = new StringBuilder((int) bufSize);
908 ret = RemotableNativeMethods.MsiFormatRecord(sessionHandle, (int) this.Handle, buf, ref bufSize);
909 }
910 if (ret != 0)
911 {
912 throw InstallerException.ExceptionFromReturnCode(ret);
913 }
914 return buf.ToString();
915 }
916
917 /// <summary>
918 /// Gets a formatted string representation of the Record.
919 /// </summary>
920 /// <param name="format">String to be used to format the data in the Record,
921 /// instead of the Record's format string.</param>
922 /// <returns>A formatted string representation of the Record.</returns>
923 /// <remarks><p>
924 /// Win32 MSI API:
925 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a>
926 /// </p></remarks>
927 [Obsolete("This method is obsolete because it has undesirable side-effects. As an alternative, set the FormatString " +
928 "property separately before calling the ToString() override that takes no parameters.")]
929 public string ToString(string format)
930 {
931 return this.ToString(format, null);
932 }
933
934 /// <summary>
935 /// Gets a formatted string representation of the Record, optionally using a Session to format properties.
936 /// </summary>
937 /// <param name="format">String to be used to format the data in the Record,
938 /// instead of the Record's format string.</param>
939 /// <param name="provider">an optional Session instance that will be used to lookup any
940 /// properties in the Record's format string</param>
941 /// <returns>A formatted string representation of the Record.</returns>
942 /// <remarks><p>
943 /// Win32 MSI API:
944 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a>
945 /// </p></remarks>
946 /// <seealso cref="FormatString"/>
947 /// <seealso cref="Session.FormatRecord(Record)"/>
948 [Obsolete("This method is obsolete because it has undesirable side-effects. As an alternative, set the FormatString " +
949 "property separately before calling the ToString() override that takes just a format provider.")]
950 public string ToString(string format, IFormatProvider provider)
951 {
952 if (format == null)
953 {
954 return this.ToString(provider);
955 }
956 else if (format.Length == 0)
957 {
958 return String.Empty;
959 }
960 else
961 {
962 string savedFormatString = (string) this[0];
963 try
964 {
965 this.FormatString = format;
966 return this.ToString(provider);
967 }
968 finally
969 {
970 this.FormatString = savedFormatString;
971 }
972 }
973 }
974
975 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
976 private static void ExtractSubStorage(string databaseFile, string storageName, string extractFile)
977 {
978 IStorage storage;
979 NativeMethods.STGM openMode = NativeMethods.STGM.READ | NativeMethods.STGM.SHARE_DENY_WRITE;
980 int hr = NativeMethods.StgOpenStorage(databaseFile, IntPtr.Zero, (uint) openMode, IntPtr.Zero, 0, out storage);
981 if (hr != 0)
982 {
983 Marshal.ThrowExceptionForHR(hr);
984 }
985
986 try
987 {
988 openMode = NativeMethods.STGM.READ | NativeMethods.STGM.SHARE_EXCLUSIVE;
989 IStorage subStorage = storage.OpenStorage(storageName, IntPtr.Zero, (uint) openMode, IntPtr.Zero, 0);
990
991 try
992 {
993 IStorage newStorage;
994 openMode = NativeMethods.STGM.CREATE | NativeMethods.STGM.READWRITE | NativeMethods.STGM.SHARE_EXCLUSIVE;
995 hr = NativeMethods.StgCreateDocfile(extractFile, (uint) openMode, 0, out newStorage);
996 if (hr != 0)
997 {
998 Marshal.ThrowExceptionForHR(hr);
999 }
1000
1001 try
1002 {
1003 subStorage.CopyTo(0, IntPtr.Zero, IntPtr.Zero, newStorage);
1004
1005 newStorage.Commit(0);
1006 }
1007 finally
1008 {
1009 Marshal.ReleaseComObject(newStorage);
1010 }
1011 }
1012 finally
1013 {
1014 Marshal.ReleaseComObject(subStorage);
1015 }
1016 }
1017 finally
1018 {
1019 Marshal.ReleaseComObject(storage);
1020 }
1021 }
1022
1023 private int FindColumn(string fieldName)
1024 {
1025 if (this.view == null)
1026 {
1027 throw new InvalidOperationException();
1028 }
1029 ColumnCollection columns = this.view.Columns;
1030 for (int i = 0; i < columns.Count; i++)
1031 {
1032 if (columns[i].Name == fieldName)
1033 {
1034 return i + 1;
1035 }
1036 }
1037 throw new ArgumentOutOfRangeException("fieldName");
1038 }
1039
1040 private void CheckRange(int field)
1041 {
1042 if (field < 0 || field > this.FieldCount)
1043 {
1044 throw new ArgumentOutOfRangeException("field");
1045 }
1046 }
1047 }
1048}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/RecordStream.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/RecordStream.cs
new file mode 100644
index 00000000..82e8fb46
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/RecordStream.cs
@@ -0,0 +1,92 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.IO;
7
8 internal class RecordStream : Stream
9 {
10 private Record record;
11 private int field;
12 private long position;
13
14 internal RecordStream(Record record, int field)
15 : base()
16 {
17 this.record = record;
18 this.field = field;
19 }
20
21 public override bool CanRead { get { return true; } }
22 public override bool CanWrite { get { return false; } }
23 public override bool CanSeek { get { return false; } }
24
25 public override long Length
26 {
27 get
28 {
29 return this.record.GetDataSize(this.field);
30 }
31 }
32
33 public override long Position
34 {
35 get
36 {
37 return this.position;
38 }
39
40 set
41 {
42 throw new NotSupportedException();
43 }
44 }
45
46 public override long Seek(long offset, SeekOrigin origin)
47 {
48 throw new NotSupportedException();
49 }
50
51 public override void SetLength(long value)
52 {
53 throw new NotSupportedException();
54 }
55
56 public override void Flush()
57 {
58 throw new NotSupportedException();
59 }
60
61 public override int Read(byte[] buffer, int offset, int count)
62 {
63 if (count > 0)
64 {
65 byte[] readBuffer = (offset == 0 ? buffer : new byte[count]);
66 uint ucount = (uint) count;
67 uint ret = RemotableNativeMethods.MsiRecordReadStream((int) this.record.Handle, (uint) this.field, buffer, ref ucount);
68 if (ret != 0)
69 {
70 throw InstallerException.ExceptionFromReturnCode(ret);
71 }
72 count = (int) ucount;
73 if (offset > 0)
74 {
75 Array.Copy(readBuffer, 0, buffer, offset, count);
76 }
77 this.position += count;
78 }
79 return count;
80 }
81
82 public override void Write(byte[] array, int offset, int count)
83 {
84 throw new NotSupportedException();
85 }
86
87 public override string ToString()
88 {
89 return "[Binary data]";
90 }
91 }
92}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/RemotableNativeMethods.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/RemotableNativeMethods.cs
new file mode 100644
index 00000000..960fd15f
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/RemotableNativeMethods.cs
@@ -0,0 +1,1171 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Text;
7 using System.Runtime.InteropServices;
8 using System.Diagnostics.CodeAnalysis;
9
10 /// <summary>
11 /// Assigns ID numbers to the MSI APIs that are remotable.
12 /// </summary>
13 /// <remarks><p>
14 /// This enumeration MUST stay in sync with the
15 /// unmanaged equivalent in RemoteMsiSession.h!
16 /// </p></remarks>
17 internal enum RemoteMsiFunctionId
18 {
19 EndSession = 0,
20 MsiCloseHandle,
21 MsiCreateRecord,
22 MsiDatabaseGetPrimaryKeys,
23 MsiDatabaseIsTablePersistent,
24 MsiDatabaseOpenView,
25 MsiDoAction,
26 MsiEnumComponentCosts,
27 MsiEvaluateCondition,
28 MsiFormatRecord,
29 MsiGetActiveDatabase,
30 MsiGetComponentState,
31 MsiGetFeatureCost,
32 MsiGetFeatureState,
33 MsiGetFeatureValidStates,
34 MsiGetLanguage,
35 MsiGetLastErrorRecord,
36 MsiGetMode,
37 MsiGetProperty,
38 MsiGetSourcePath,
39 MsiGetSummaryInformation,
40 MsiGetTargetPath,
41 MsiProcessMessage,
42 MsiRecordClearData,
43 MsiRecordDataSize,
44 MsiRecordGetFieldCount,
45 MsiRecordGetInteger,
46 MsiRecordGetString,
47 MsiRecordIsNull,
48 MsiRecordReadStream,
49 MsiRecordSetInteger,
50 MsiRecordSetStream,
51 MsiRecordSetString,
52 MsiSequence,
53 MsiSetComponentState,
54 MsiSetFeatureAttributes,
55 MsiSetFeatureState,
56 MsiSetInstallLevel,
57 MsiSetMode,
58 MsiSetProperty,
59 MsiSetTargetPath,
60 MsiSummaryInfoGetProperty,
61 MsiVerifyDiskSpace,
62 MsiViewExecute,
63 MsiViewFetch,
64 MsiViewGetError,
65 MsiViewGetColumnInfo,
66 MsiViewModify,
67 }
68
69 /// <summary>
70 /// Defines the signature of the native function
71 /// in SfxCA.dll that implements the remoting call.
72 /// </summary>
73 internal delegate void MsiRemoteInvoke(
74 RemoteMsiFunctionId id,
75 [MarshalAs(UnmanagedType.SysInt)]
76 IntPtr request,
77 [MarshalAs(UnmanagedType.SysInt)]
78 out IntPtr response);
79
80 /// <summary>
81 /// Redirects native API calls to either the normal NativeMethods class
82 /// or to out-of-proc calls via the remoting channel.
83 /// </summary>
84 internal static class RemotableNativeMethods
85 {
86 private const int MAX_REQUEST_FIELDS = 4;
87 private static int requestFieldDataOffset;
88 private static int requestFieldSize;
89
90 [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
91 private static IntPtr requestBuf;
92
93 private static MsiRemoteInvoke remotingDelegate;
94
95 /// <summary>
96 /// Checks if the current process is using remoting to access the
97 /// MSI session and database APIs.
98 /// </summary>
99 internal static bool RemotingEnabled
100 {
101 get
102 {
103 return RemotableNativeMethods.remotingDelegate != null;
104 }
105 }
106
107 /// <summary>
108 /// Sets a delegate that is used to make remote API calls.
109 /// </summary>
110 /// <remarks><p>
111 /// The implementation of this delegate is provided by the
112 /// custom action host DLL.
113 /// </p></remarks>
114 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
115 internal static MsiRemoteInvoke RemotingDelegate
116 {
117 set
118 {
119 RemotableNativeMethods.remotingDelegate = value;
120
121 if (value != null && requestBuf == IntPtr.Zero)
122 {
123 requestFieldDataOffset = Marshal.SizeOf(typeof(IntPtr));
124 requestFieldSize = 2 * Marshal.SizeOf(typeof(IntPtr));
125 RemotableNativeMethods.requestBuf = Marshal.AllocHGlobal(
126 requestFieldSize * MAX_REQUEST_FIELDS);
127 }
128 }
129 }
130
131 internal static bool IsRemoteHandle(int handle)
132 {
133 return (handle & Int32.MinValue) != 0;
134 }
135
136 internal static int MakeRemoteHandle(int handle)
137 {
138 if (handle == 0)
139 {
140 return handle;
141 }
142
143 if (RemotableNativeMethods.IsRemoteHandle(handle))
144 {
145 throw new InvalidOperationException("Handle already has the remote bit set.");
146 }
147
148 return handle ^ Int32.MinValue;
149 }
150
151 internal static int GetRemoteHandle(int handle)
152 {
153 if (handle == 0)
154 {
155 return handle;
156 }
157
158 if (!RemotableNativeMethods.IsRemoteHandle(handle))
159 {
160 throw new InvalidOperationException("Handle does not have the remote bit set.");
161 }
162
163 return handle ^ Int32.MinValue;
164 }
165
166 private static void ClearData(IntPtr buf)
167 {
168 for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
169 {
170 Marshal.WriteInt32(buf, (i * requestFieldSize), (int) VarEnum.VT_NULL);
171 Marshal.WriteIntPtr(buf, (i * requestFieldSize) + requestFieldDataOffset, IntPtr.Zero);
172 }
173 }
174
175 private static void WriteInt(IntPtr buf, int field, int value)
176 {
177 Marshal.WriteInt32(buf, (field * requestFieldSize), (int) VarEnum.VT_I4);
178 Marshal.WriteInt32(buf, (field * requestFieldSize) + requestFieldDataOffset, value);
179 }
180
181 private static void WriteString(IntPtr buf, int field, string value)
182 {
183 if (value == null)
184 {
185 Marshal.WriteInt32(buf, (field * requestFieldSize), (int) VarEnum.VT_NULL);
186 Marshal.WriteIntPtr(buf, (field * requestFieldSize) + requestFieldDataOffset, IntPtr.Zero);
187 }
188 else
189 {
190 IntPtr stringPtr = Marshal.StringToHGlobalUni(value);
191 Marshal.WriteInt32(buf, (field * requestFieldSize), (int) VarEnum.VT_LPWSTR);
192 Marshal.WriteIntPtr(buf, (field * requestFieldSize) + requestFieldDataOffset, stringPtr);
193 }
194 }
195
196 private static int ReadInt(IntPtr buf, int field)
197 {
198 VarEnum vt = (VarEnum) Marshal.ReadInt32(buf, (field * requestFieldSize));
199 if (vt == VarEnum.VT_EMPTY)
200 {
201 return 0;
202 }
203 else if (vt != VarEnum.VT_I4 && vt != VarEnum.VT_UI4)
204 {
205 throw new InstallerException("Invalid data received from remote MSI function invocation.");
206 }
207 return Marshal.ReadInt32(buf, (field * requestFieldSize) + requestFieldDataOffset);
208 }
209
210 private static void ReadString(IntPtr buf, int field, StringBuilder szBuf, ref uint cchBuf)
211 {
212 VarEnum vt = (VarEnum) Marshal.ReadInt32(buf, (field * requestFieldSize));
213 if (vt == VarEnum.VT_NULL)
214 {
215 szBuf.Remove(0, szBuf.Length);
216 return;
217 }
218 else if (vt != VarEnum.VT_LPWSTR)
219 {
220 throw new InstallerException("Invalid data received from remote MSI function invocation.");
221 }
222
223 szBuf.Remove(0, szBuf.Length);
224 IntPtr strPtr = Marshal.ReadIntPtr(buf, (field * requestFieldSize) + requestFieldDataOffset);
225 string str = Marshal.PtrToStringUni(strPtr);
226 if (str != null)
227 {
228 szBuf.Append(str);
229 }
230 cchBuf = (uint) szBuf.Length;
231 }
232
233 private static void FreeString(IntPtr buf, int field)
234 {
235 IntPtr stringPtr = Marshal.ReadIntPtr(buf, (field * requestFieldSize) + requestFieldDataOffset);
236 if (stringPtr != IntPtr.Zero)
237 {
238 Marshal.FreeHGlobal(stringPtr);
239 }
240 }
241
242 private static void ReadStream(IntPtr buf, int field, byte[] sBuf, int count)
243 {
244 VarEnum vt = (VarEnum) Marshal.ReadInt32(buf, (field * requestFieldSize));
245 if (vt != VarEnum.VT_STREAM)
246 {
247 throw new InstallerException("Invalid data received from remote MSI function invocation.");
248 }
249
250 IntPtr sPtr = Marshal.ReadIntPtr(buf, (field * requestFieldSize) + requestFieldDataOffset);
251 Marshal.Copy(sPtr, sBuf, 0, count);
252 }
253
254 private static uint MsiFunc_III(RemoteMsiFunctionId id, int in1, int in2, int in3)
255 {
256 lock (RemotableNativeMethods.remotingDelegate)
257 {
258 ClearData(requestBuf);
259 WriteInt(requestBuf, 0, in1);
260 WriteInt(requestBuf, 1, in2);
261 WriteInt(requestBuf, 2, in3);
262 IntPtr resp;
263 remotingDelegate(id, requestBuf, out resp);
264 return unchecked ((uint) ReadInt(resp, 0));
265 }
266 }
267
268 private static uint MsiFunc_IIS(RemoteMsiFunctionId id, int in1, int in2, string in3)
269 {
270 lock (RemotableNativeMethods.remotingDelegate)
271 {
272 ClearData(requestBuf);
273 WriteInt(requestBuf, 0, in1);
274 WriteInt(requestBuf, 1, in2);
275 WriteString(requestBuf, 2, in3);
276 IntPtr resp;
277 remotingDelegate(id, requestBuf, out resp);
278 FreeString(requestBuf, 2);
279 return unchecked ((uint) ReadInt(resp, 0));
280 }
281 }
282
283 private static uint MsiFunc_ISI(RemoteMsiFunctionId id, int in1, string in2, int in3)
284 {
285 lock (RemotableNativeMethods.remotingDelegate)
286 {
287 ClearData(requestBuf);
288 WriteInt(requestBuf, 0, in1);
289 WriteString(requestBuf, 1, in2);
290 WriteInt(requestBuf, 2, in3);
291 IntPtr resp;
292 remotingDelegate(id, requestBuf, out resp);
293 FreeString(requestBuf, 2);
294 return unchecked ((uint) ReadInt(resp, 0));
295 }
296 }
297
298 private static uint MsiFunc_ISS(RemoteMsiFunctionId id, int in1, string in2, string in3)
299 {
300 lock (RemotableNativeMethods.remotingDelegate)
301 {
302 ClearData(requestBuf);
303 WriteInt(requestBuf, 0, in1);
304 WriteString(requestBuf, 1, in2);
305 WriteString(requestBuf, 2, in3);
306 IntPtr resp;
307 remotingDelegate(id, requestBuf, out resp);
308 FreeString(requestBuf, 1);
309 FreeString(requestBuf, 2);
310 return unchecked ((uint) ReadInt(resp, 0));
311 }
312 }
313
314 private static uint MsiFunc_II_I(RemoteMsiFunctionId id, int in1, int in2, out int out1)
315 {
316 lock (RemotableNativeMethods.remotingDelegate)
317 {
318 ClearData(requestBuf);
319 WriteInt(requestBuf, 0, in1);
320 WriteInt(requestBuf, 1, in2);
321 IntPtr resp;
322 remotingDelegate(id, requestBuf, out resp);
323 uint ret = unchecked ((uint) ReadInt(resp, 0));
324 out1 = ReadInt(resp, 1);
325 return ret;
326 }
327 }
328
329 private static uint MsiFunc_ISII_I(RemoteMsiFunctionId id, int in1, string in2, int in3, int in4, out int out1)
330 {
331 lock (RemotableNativeMethods.remotingDelegate)
332 {
333 ClearData(requestBuf);
334 WriteInt(requestBuf, 0, in1);
335 WriteString(requestBuf, 1, in2);
336 WriteInt(requestBuf, 2, in3);
337 WriteInt(requestBuf, 3, in4);
338 IntPtr resp;
339 remotingDelegate(id, requestBuf, out resp);
340 FreeString(requestBuf, 1);
341 uint ret = unchecked ((uint) ReadInt(resp, 0));
342 out1 = ReadInt(resp, 1);
343 return ret;
344 }
345 }
346
347 private static uint MsiFunc_IS_II(RemoteMsiFunctionId id, int in1, string in2, out int out1, out int out2)
348 {
349 lock (RemotableNativeMethods.remotingDelegate)
350 {
351 ClearData(requestBuf);
352 WriteInt(requestBuf, 0, in1);
353 WriteString(requestBuf, 1, in2);
354 IntPtr resp;
355 remotingDelegate(id, requestBuf, out resp);
356 FreeString(requestBuf, 1);
357 uint ret = unchecked ((uint) ReadInt(resp, 0));
358 out1 = ReadInt(resp, 1);
359 out2 = ReadInt(resp, 2);
360 return ret;
361 }
362 }
363
364 private static uint MsiFunc_II_S(RemoteMsiFunctionId id, int in1, int in2, StringBuilder out1, ref uint cchOut1)
365 {
366 lock (RemotableNativeMethods.remotingDelegate)
367 {
368 ClearData(requestBuf);
369 WriteInt(requestBuf, 0, in1);
370 WriteInt(requestBuf, 1, in2);
371 IntPtr resp;
372 remotingDelegate(id, requestBuf, out resp);
373 uint ret = unchecked ((uint) ReadInt(resp, 0));
374 if (ret == 0) ReadString(resp, 1, out1, ref cchOut1);
375 return ret;
376 }
377 }
378
379 private static uint MsiFunc_IS_S(RemoteMsiFunctionId id, int in1, string in2, StringBuilder out1, ref uint cchOut1)
380 {
381 lock (RemotableNativeMethods.remotingDelegate)
382 {
383 ClearData(requestBuf);
384 WriteInt(requestBuf, 0, in1);
385 WriteString(requestBuf, 1, in2);
386 IntPtr resp;
387 remotingDelegate(id, requestBuf, out resp);
388 FreeString(requestBuf, 1);
389 uint ret = unchecked ((uint) ReadInt(resp, 0));
390 if (ret == 0) ReadString(resp, 1, out1, ref cchOut1);
391 return ret;
392 }
393 }
394
395 private static uint MsiFunc_ISII_SII(RemoteMsiFunctionId id, int in1, string in2, int in3, int in4, StringBuilder out1, ref uint cchOut1, out int out2, out int out3)
396 {
397 lock (RemotableNativeMethods.remotingDelegate)
398 {
399 ClearData(requestBuf);
400 WriteInt(requestBuf, 0, in1);
401 WriteString(requestBuf, 1, in2);
402 WriteInt(requestBuf, 2, in3);
403 WriteInt(requestBuf, 3, in4);
404 IntPtr resp;
405 remotingDelegate(id, requestBuf, out resp);
406 FreeString(requestBuf, 1);
407 uint ret = unchecked ((uint) ReadInt(resp, 0));
408 if (ret == 0) ReadString(resp, 1, out1, ref cchOut1);
409 out2 = ReadInt(resp, 2);
410 out3 = ReadInt(resp, 3);
411 return ret;
412 }
413 }
414
415 internal static int MsiProcessMessage(int hInstall, uint eMessageType, int hRecord)
416 {
417 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
418 {
419 return NativeMethods.MsiProcessMessage(hInstall, eMessageType, hRecord);
420 }
421 else lock (remotingDelegate)
422 {
423 // I don't understand why, but this particular function doesn't work
424 // when using the static requestBuf -- some data doesn't make it through.
425 // But it works when a fresh buffer is allocated here every call.
426 IntPtr buf = Marshal.AllocHGlobal(
427 requestFieldSize * MAX_REQUEST_FIELDS);
428 ClearData(buf);
429 WriteInt(buf, 0, RemotableNativeMethods.GetRemoteHandle(hInstall));
430 WriteInt(buf, 1, unchecked ((int) eMessageType));
431 WriteInt(buf, 2, RemotableNativeMethods.GetRemoteHandle(hRecord));
432 IntPtr resp;
433 remotingDelegate(RemoteMsiFunctionId.MsiProcessMessage, buf, out resp);
434 Marshal.FreeHGlobal(buf);
435 return ReadInt(resp, 0);
436 }
437 }
438
439 internal static uint MsiCloseHandle(int hAny)
440 {
441 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hAny))
442 return NativeMethods.MsiCloseHandle(hAny);
443 else
444 return RemotableNativeMethods.MsiFunc_III(
445 RemoteMsiFunctionId.MsiCloseHandle, RemotableNativeMethods.GetRemoteHandle(hAny), 0, 0);
446 }
447
448 internal static uint MsiGetProperty(int hInstall, string szName, StringBuilder szValueBuf, ref uint cchValueBuf)
449 {
450 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
451 return NativeMethods.MsiGetProperty(hInstall, szName, szValueBuf, ref cchValueBuf);
452 else
453 {
454 return RemotableNativeMethods.MsiFunc_IS_S(
455 RemoteMsiFunctionId.MsiGetProperty,
456 RemotableNativeMethods.GetRemoteHandle(hInstall),
457 szName,
458 szValueBuf,
459 ref cchValueBuf);
460 }
461 }
462
463 internal static uint MsiSetProperty(int hInstall, string szName, string szValue)
464 {
465 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
466 return NativeMethods.MsiSetProperty(hInstall, szName, szValue);
467 else
468 {
469 return RemotableNativeMethods.MsiFunc_ISS(
470 RemoteMsiFunctionId.MsiSetProperty,
471 RemotableNativeMethods.GetRemoteHandle(hInstall),
472 szName,
473 szValue);
474 }
475 }
476
477 internal static int MsiCreateRecord(uint cParams, int hAny)
478 {
479 // When remoting is enabled, we might need to create either a local or
480 // remote record, depending on the handle it is to have an affinity with.
481 // If no affinity handle is specified, create a remote record (the 99% case).
482 if (!RemotingEnabled ||
483 (hAny != 0 && !RemotableNativeMethods.IsRemoteHandle(hAny)))
484 {
485 return NativeMethods.MsiCreateRecord(cParams);
486 }
487 else
488 {
489 int hRecord = unchecked((int)RemotableNativeMethods.MsiFunc_III(
490 RemoteMsiFunctionId.MsiCreateRecord, (int) cParams, 0, 0));
491 return RemotableNativeMethods.MakeRemoteHandle(hRecord);
492 }
493 }
494
495 internal static uint MsiRecordGetFieldCount(int hRecord)
496 {
497 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
498 return NativeMethods.MsiRecordGetFieldCount(hRecord);
499 else
500 {
501 return RemotableNativeMethods.MsiFunc_III(
502 RemoteMsiFunctionId.MsiRecordGetFieldCount,
503 RemotableNativeMethods.GetRemoteHandle(hRecord),
504 0,
505 0);
506 }
507 }
508
509 internal static int MsiRecordGetInteger(int hRecord, uint iField)
510 {
511 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
512 return NativeMethods.MsiRecordGetInteger(hRecord, iField);
513 else
514 {
515 return unchecked ((int) RemotableNativeMethods.MsiFunc_III(
516 RemoteMsiFunctionId.MsiRecordGetInteger,
517 RemotableNativeMethods.GetRemoteHandle(hRecord),
518 (int) iField,
519 0));
520 }
521 }
522
523 internal static uint MsiRecordGetString(int hRecord, uint iField, StringBuilder szValueBuf, ref uint cchValueBuf)
524 {
525 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
526 {
527 return NativeMethods.MsiRecordGetString(hRecord, iField, szValueBuf, ref cchValueBuf);
528 }
529 else
530 {
531 return RemotableNativeMethods.MsiFunc_II_S(
532 RemoteMsiFunctionId.MsiRecordGetString,
533 RemotableNativeMethods.GetRemoteHandle(hRecord),
534 (int) iField,
535 szValueBuf,
536 ref cchValueBuf);
537 }
538 }
539
540 internal static uint MsiRecordSetInteger(int hRecord, uint iField, int iValue)
541 {
542 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
543 return NativeMethods.MsiRecordSetInteger(hRecord, iField, iValue);
544 else
545 {
546 return RemotableNativeMethods.MsiFunc_III(
547 RemoteMsiFunctionId.MsiRecordSetInteger,
548 RemotableNativeMethods.GetRemoteHandle(hRecord),
549 (int) iField,
550 iValue);
551 }
552 }
553
554 internal static uint MsiRecordSetString(int hRecord, uint iField, string szValue)
555 {
556 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
557 return NativeMethods.MsiRecordSetString(hRecord, iField, szValue);
558 else
559 {
560 return RemotableNativeMethods.MsiFunc_IIS(
561 RemoteMsiFunctionId.MsiRecordSetString,
562 RemotableNativeMethods.GetRemoteHandle(hRecord),
563 (int) iField,
564 szValue);
565 }
566 }
567
568 internal static int MsiGetActiveDatabase(int hInstall)
569 {
570 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
571 return NativeMethods.MsiGetActiveDatabase(hInstall);
572 else
573 {
574 int hDatabase = (int)RemotableNativeMethods.MsiFunc_III(
575 RemoteMsiFunctionId.MsiGetActiveDatabase,
576 RemotableNativeMethods.GetRemoteHandle(hInstall),
577 0,
578 0);
579 return RemotableNativeMethods.MakeRemoteHandle(hDatabase);
580 }
581 }
582
583 internal static uint MsiDatabaseOpenView(int hDatabase, string szQuery, out int hView)
584 {
585 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hDatabase))
586 return NativeMethods.MsiDatabaseOpenView(hDatabase, szQuery, out hView);
587 else
588 {
589 uint err = RemotableNativeMethods.MsiFunc_ISII_I(
590 RemoteMsiFunctionId.MsiDatabaseOpenView,
591 RemotableNativeMethods.GetRemoteHandle(hDatabase),
592 szQuery,
593 0,
594 0,
595 out hView);
596 hView = RemotableNativeMethods.MakeRemoteHandle(hView);
597 return err;
598 }
599 }
600
601 internal static uint MsiViewExecute(int hView, int hRecord)
602 {
603 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hView))
604 return NativeMethods.MsiViewExecute(hView, hRecord);
605 else
606 {
607 return RemotableNativeMethods.MsiFunc_III(
608 RemoteMsiFunctionId.MsiViewExecute,
609 RemotableNativeMethods.GetRemoteHandle(hView),
610 RemotableNativeMethods.GetRemoteHandle(hRecord),
611 0);
612 }
613 }
614
615 internal static uint MsiViewFetch(int hView, out int hRecord)
616 {
617 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hView))
618 return NativeMethods.MsiViewFetch(hView, out hRecord);
619 else
620 {
621 uint err = RemotableNativeMethods.MsiFunc_II_I(
622 RemoteMsiFunctionId.MsiViewFetch,
623 RemotableNativeMethods.GetRemoteHandle(hView),
624 0,
625 out hRecord);
626 hRecord = RemotableNativeMethods.MakeRemoteHandle(hRecord);
627 return err;
628 }
629 }
630
631 internal static uint MsiViewModify(int hView, int iModifyMode, int hRecord)
632 {
633 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hView))
634 return NativeMethods.MsiViewModify(hView, iModifyMode, hRecord);
635 else
636 {
637 return RemotableNativeMethods.MsiFunc_III(
638 RemoteMsiFunctionId.MsiViewModify,
639 RemotableNativeMethods.GetRemoteHandle(hView),
640 iModifyMode,
641 RemotableNativeMethods.GetRemoteHandle(hRecord));
642 }
643 }
644
645 internal static int MsiViewGetError(int hView, StringBuilder szColumnNameBuffer, ref uint cchBuf)
646 {
647 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hView))
648 return NativeMethods.MsiViewGetError(hView, szColumnNameBuffer, ref cchBuf);
649 else
650 {
651 return unchecked ((int) RemotableNativeMethods.MsiFunc_II_S(
652 RemoteMsiFunctionId.MsiViewGetError,
653 RemotableNativeMethods.GetRemoteHandle(hView),
654 0,
655 szColumnNameBuffer,
656 ref cchBuf));
657 }
658 }
659
660 internal static uint MsiViewGetColumnInfo(int hView, uint eColumnInfo, out int hRecord)
661 {
662 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hView))
663 return NativeMethods.MsiViewGetColumnInfo(hView, eColumnInfo, out hRecord);
664 else
665 {
666 uint err = RemotableNativeMethods.MsiFunc_II_I(
667 RemoteMsiFunctionId.MsiViewGetColumnInfo,
668 RemotableNativeMethods.GetRemoteHandle(hView),
669 (int) eColumnInfo,
670 out hRecord);
671 hRecord = RemotableNativeMethods.MakeRemoteHandle(hRecord);
672 return err;
673 }
674 }
675
676 internal static uint MsiFormatRecord(int hInstall, int hRecord, StringBuilder szResultBuf, ref uint cchResultBuf)
677 {
678 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
679 return NativeMethods.MsiFormatRecord(hInstall, hRecord, szResultBuf, ref cchResultBuf);
680 else
681 {
682 return RemotableNativeMethods.MsiFunc_II_S(
683 RemoteMsiFunctionId.MsiFormatRecord,
684 RemotableNativeMethods.GetRemoteHandle(hInstall),
685 RemotableNativeMethods.GetRemoteHandle(hRecord),
686 szResultBuf,
687 ref cchResultBuf);
688 }
689 }
690
691 internal static uint MsiRecordClearData(int hRecord)
692 {
693 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
694 return NativeMethods.MsiRecordClearData(hRecord);
695 else
696 {
697 return RemotableNativeMethods.MsiFunc_III(
698 RemoteMsiFunctionId.MsiRecordClearData,
699 RemotableNativeMethods.GetRemoteHandle(hRecord),
700 0,
701 0);
702 }
703 }
704
705 internal static bool MsiRecordIsNull(int hRecord, uint iField)
706 {
707 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
708 return NativeMethods.MsiRecordIsNull(hRecord, iField);
709 else
710 {
711 return 0 != RemotableNativeMethods.MsiFunc_III(
712 RemoteMsiFunctionId.MsiRecordIsNull,
713 RemotableNativeMethods.GetRemoteHandle(hRecord),
714 (int) iField,
715 0);
716 }
717 }
718
719 internal static uint MsiDatabaseGetPrimaryKeys(int hDatabase, string szTableName, out int hRecord)
720 {
721 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hDatabase))
722 return NativeMethods.MsiDatabaseGetPrimaryKeys(hDatabase, szTableName, out hRecord);
723 else
724 {
725 uint err = RemotableNativeMethods.MsiFunc_ISII_I(
726 RemoteMsiFunctionId.MsiDatabaseGetPrimaryKeys,
727 RemotableNativeMethods.GetRemoteHandle(hDatabase),
728 szTableName,
729 0,
730 0,
731 out hRecord);
732 hRecord = RemotableNativeMethods.MakeRemoteHandle(hRecord);
733 return err;
734 }
735 }
736
737 internal static uint MsiDatabaseIsTablePersistent(int hDatabase, string szTableName)
738 {
739 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hDatabase))
740 return NativeMethods.MsiDatabaseIsTablePersistent(hDatabase, szTableName);
741 else
742 {
743 return RemotableNativeMethods.MsiFunc_ISI(
744 RemoteMsiFunctionId.MsiDatabaseIsTablePersistent,
745 RemotableNativeMethods.GetRemoteHandle(hDatabase),
746 szTableName,
747 0);
748 }
749 }
750
751 internal static uint MsiDoAction(int hInstall, string szAction)
752 {
753 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
754 return NativeMethods.MsiDoAction(hInstall, szAction);
755 else
756 {
757 return RemotableNativeMethods.MsiFunc_ISI(
758 RemoteMsiFunctionId.MsiDoAction,
759 RemotableNativeMethods.GetRemoteHandle(hInstall),
760 szAction,
761 0);
762 }
763 }
764
765 internal static uint MsiEnumComponentCosts(int hInstall, string szComponent, uint dwIndex, int iState, StringBuilder lpDriveBuf, ref uint cchDriveBuf, out int iCost, out int iTempCost)
766 {
767 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
768 return NativeMethods.MsiEnumComponentCosts(hInstall, szComponent, dwIndex, iState, lpDriveBuf, ref cchDriveBuf, out iCost, out iTempCost);
769 else
770 {
771 return RemotableNativeMethods.MsiFunc_ISII_SII(
772 RemoteMsiFunctionId.MsiEvaluateCondition,
773 RemotableNativeMethods.GetRemoteHandle(hInstall),
774 szComponent, (int) dwIndex, iState, lpDriveBuf, ref cchDriveBuf, out iCost, out iTempCost);
775 }
776 }
777
778 internal static uint MsiEvaluateCondition(int hInstall, string szCondition)
779 {
780 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
781 return NativeMethods.MsiEvaluateCondition(hInstall, szCondition);
782 else
783 {
784 return RemotableNativeMethods.MsiFunc_ISI(
785 RemoteMsiFunctionId.MsiEvaluateCondition,
786 RemotableNativeMethods.GetRemoteHandle(hInstall),
787 szCondition,
788 0);
789 }
790 }
791
792 internal static uint MsiGetComponentState(int hInstall, string szComponent, out int iInstalled, out int iAction)
793 {
794 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
795 return NativeMethods.MsiGetComponentState(hInstall, szComponent, out iInstalled, out iAction);
796 else
797 {
798 return RemotableNativeMethods.MsiFunc_IS_II(
799 RemoteMsiFunctionId.MsiGetComponentState,
800 RemotableNativeMethods.GetRemoteHandle(hInstall),
801 szComponent,
802 out iInstalled,
803 out iAction);
804 }
805 }
806
807 internal static uint MsiGetFeatureCost(int hInstall, string szFeature, int iCostTree, int iState, out int iCost)
808 {
809 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
810 return NativeMethods.MsiGetFeatureCost(hInstall, szFeature, iCostTree, iState, out iCost);
811 else
812 {
813 return RemotableNativeMethods.MsiFunc_ISII_I(
814 RemoteMsiFunctionId.MsiGetFeatureCost,
815 RemotableNativeMethods.GetRemoteHandle(hInstall),
816 szFeature,
817 iCostTree,
818 iState,
819 out iCost);
820 }
821 }
822
823 internal static uint MsiGetFeatureState(int hInstall, string szFeature, out int iInstalled, out int iAction)
824 {
825 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
826 return NativeMethods.MsiGetFeatureState(hInstall, szFeature, out iInstalled, out iAction);
827 else
828 {
829 return RemotableNativeMethods.MsiFunc_IS_II(
830 RemoteMsiFunctionId.MsiGetFeatureState,
831 RemotableNativeMethods.GetRemoteHandle(hInstall),
832 szFeature,
833 out iInstalled,
834 out iAction);
835 }
836 }
837
838 internal static uint MsiGetFeatureValidStates(int hInstall, string szFeature, out uint dwInstalledState)
839 {
840 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
841 return NativeMethods.MsiGetFeatureValidStates(hInstall, szFeature, out dwInstalledState);
842 else
843 {
844 int iTemp;
845 uint ret = RemotableNativeMethods.MsiFunc_ISII_I(
846 RemoteMsiFunctionId.MsiGetFeatureValidStates,
847 RemotableNativeMethods.GetRemoteHandle(hInstall),
848 szFeature,
849 0,
850 0,
851 out iTemp);
852 dwInstalledState = (uint) iTemp;
853 return ret;
854 }
855 }
856
857 internal static int MsiGetLanguage(int hInstall)
858 {
859 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
860 return NativeMethods.MsiGetLanguage(hInstall);
861 else
862 {
863 return unchecked((int)RemotableNativeMethods.MsiFunc_III(
864 RemoteMsiFunctionId.MsiGetLanguage,
865 RemotableNativeMethods.GetRemoteHandle(hInstall),
866 0,
867 0));
868 }
869 }
870
871 internal static int MsiGetLastErrorRecord(int hAny)
872 {
873 // When remoting is enabled, we might need to create either a local or
874 // remote record, depending on the handle it is to have an affinity with.
875 // If no affinity handle is specified, create a remote record (the 99% case).
876 if (!RemotingEnabled ||
877 (hAny != 0 && !RemotableNativeMethods.IsRemoteHandle(hAny)))
878 {
879 return NativeMethods.MsiGetLastErrorRecord();
880 }
881 else
882 {
883 int hRecord = unchecked((int) RemotableNativeMethods.MsiFunc_III(
884 RemoteMsiFunctionId.MsiGetLastErrorRecord, 0, 0, 0));
885 return RemotableNativeMethods.MakeRemoteHandle(hRecord);
886 }
887 }
888
889 internal static bool MsiGetMode(int hInstall, uint iRunMode)
890 {
891 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
892 return NativeMethods.MsiGetMode(hInstall, iRunMode);
893 else
894 {
895 return 0 != RemotableNativeMethods.MsiFunc_III(
896 RemoteMsiFunctionId.MsiGetMode,
897 RemotableNativeMethods.GetRemoteHandle(hInstall),
898 (int) iRunMode,
899 0);
900 }
901 }
902
903 internal static uint MsiGetSourcePath(int hInstall, string szFolder, StringBuilder szPathBuf, ref uint cchPathBuf)
904 {
905 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
906 return NativeMethods.MsiGetSourcePath(hInstall, szFolder, szPathBuf, ref cchPathBuf);
907 else
908 {
909 return RemotableNativeMethods.MsiFunc_IS_S(
910 RemoteMsiFunctionId.MsiGetSourcePath,
911 RemotableNativeMethods.GetRemoteHandle(hInstall),
912 szFolder,
913 szPathBuf,
914 ref cchPathBuf);
915 }
916 }
917
918 internal static uint MsiGetSummaryInformation(int hDatabase, string szDatabasePath, uint uiUpdateCount, out int hSummaryInfo)
919 {
920 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hDatabase))
921 return NativeMethods.MsiGetSummaryInformation(hDatabase, szDatabasePath, uiUpdateCount, out hSummaryInfo);
922 else
923 {
924 uint err = RemotableNativeMethods.MsiFunc_ISII_I(
925 RemoteMsiFunctionId.MsiGetSummaryInformation,
926 RemotableNativeMethods.GetRemoteHandle(hDatabase),
927 szDatabasePath,
928 (int)uiUpdateCount,
929 0,
930 out hSummaryInfo);
931 hSummaryInfo = RemotableNativeMethods.MakeRemoteHandle(hSummaryInfo);
932 return err;
933 }
934 }
935
936 internal static uint MsiGetTargetPath(int hInstall, string szFolder, StringBuilder szPathBuf, ref uint cchPathBuf)
937 {
938 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
939 return NativeMethods.MsiGetTargetPath(hInstall, szFolder, szPathBuf, ref cchPathBuf);
940 else
941 {
942 return RemotableNativeMethods.MsiFunc_IS_S(
943 RemoteMsiFunctionId.MsiGetTargetPath,
944 RemotableNativeMethods.GetRemoteHandle(hInstall),
945 szFolder,
946 szPathBuf,
947 ref cchPathBuf);
948 }
949 }
950
951 internal static uint MsiRecordDataSize(int hRecord, uint iField)
952 {
953 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
954 return NativeMethods.MsiRecordDataSize(hRecord, iField);
955 else
956 {
957 return RemotableNativeMethods.MsiFunc_III(
958 RemoteMsiFunctionId.MsiRecordDataSize,
959 RemotableNativeMethods.GetRemoteHandle(hRecord),
960 (int) iField, 0);
961 }
962 }
963
964 internal static uint MsiRecordReadStream(int hRecord, uint iField, byte[] szDataBuf, ref uint cbDataBuf)
965 {
966 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
967 {
968 return NativeMethods.MsiRecordReadStream(hRecord, iField, szDataBuf, ref cbDataBuf);
969 }
970 else lock (RemotableNativeMethods.remotingDelegate)
971 {
972 ClearData(requestBuf);
973 unchecked
974 {
975 WriteInt(requestBuf, 0, RemotableNativeMethods.GetRemoteHandle(hRecord));
976 WriteInt(requestBuf, 1, (int) iField);
977 WriteInt(requestBuf, 2, (int) cbDataBuf);
978 IntPtr resp;
979 remotingDelegate(RemoteMsiFunctionId.MsiRecordReadStream, requestBuf, out resp);
980 uint ret = (uint) ReadInt(resp, 0);
981 if (ret == 0)
982 {
983 cbDataBuf = (uint) ReadInt(resp, 2);
984 if (cbDataBuf > 0)
985 {
986 RemotableNativeMethods.ReadStream(resp, 1, szDataBuf, (int) cbDataBuf);
987 }
988 }
989 return ret;
990 }
991 }
992 }
993
994 internal static uint MsiRecordSetStream(int hRecord, uint iField, string szFilePath)
995 {
996 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
997 return NativeMethods.MsiRecordSetStream(hRecord, iField, szFilePath);
998 else
999 {
1000 return RemotableNativeMethods.MsiFunc_IIS(
1001 RemoteMsiFunctionId.MsiRecordSetStream,
1002 RemotableNativeMethods.GetRemoteHandle(hRecord),
1003 (int) iField,
1004 szFilePath);
1005 }
1006 }
1007
1008 internal static uint MsiSequence(int hInstall, string szTable, int iSequenceMode)
1009 {
1010 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
1011 return NativeMethods.MsiSequence(hInstall, szTable, iSequenceMode);
1012 else
1013 {
1014 return RemotableNativeMethods.MsiFunc_ISI(
1015 RemoteMsiFunctionId.MsiSequence,
1016 RemotableNativeMethods.GetRemoteHandle(hInstall),
1017 szTable,
1018 iSequenceMode);
1019 }
1020 }
1021
1022 internal static uint MsiSetComponentState(int hInstall, string szComponent, int iState)
1023 {
1024 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
1025 return NativeMethods.MsiSetComponentState(hInstall, szComponent, iState);
1026 else
1027 {
1028 return RemotableNativeMethods.MsiFunc_ISI(
1029 RemoteMsiFunctionId.MsiSetComponentState,
1030 RemotableNativeMethods.GetRemoteHandle(hInstall),
1031 szComponent,
1032 iState);
1033 }
1034 }
1035
1036 internal static uint MsiSetFeatureAttributes(int hInstall, string szFeature, uint dwAttributes)
1037 {
1038 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
1039 return NativeMethods.MsiSetFeatureAttributes(hInstall, szFeature, dwAttributes);
1040 else
1041 {
1042 return RemotableNativeMethods.MsiFunc_ISI(
1043 RemoteMsiFunctionId.MsiSetFeatureAttributes,
1044 RemotableNativeMethods.GetRemoteHandle(hInstall),
1045 szFeature,
1046 (int) dwAttributes);
1047 }
1048 }
1049
1050 internal static uint MsiSetFeatureState(int hInstall, string szFeature, int iState)
1051 {
1052 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
1053 return NativeMethods.MsiSetFeatureState(hInstall, szFeature, iState);
1054 else
1055 {
1056 return RemotableNativeMethods.MsiFunc_ISI(
1057 RemoteMsiFunctionId.MsiSetFeatureState,
1058 RemotableNativeMethods.GetRemoteHandle(hInstall), szFeature, iState);
1059 }
1060 }
1061
1062 internal static uint MsiSetInstallLevel(int hInstall, int iInstallLevel)
1063 {
1064 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
1065 return NativeMethods.MsiSetInstallLevel(hInstall, iInstallLevel);
1066 else
1067 {
1068 return RemotableNativeMethods.MsiFunc_III(
1069 RemoteMsiFunctionId.MsiSetInstallLevel,
1070 RemotableNativeMethods.GetRemoteHandle(hInstall),
1071 iInstallLevel,
1072 0);
1073 }
1074 }
1075
1076 internal static uint MsiSetMode(int hInstall, uint iRunMode, bool fState)
1077 {
1078 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
1079 return NativeMethods.MsiSetMode(hInstall, iRunMode, fState);
1080 else
1081 {
1082 return RemotableNativeMethods.MsiFunc_III(
1083 RemoteMsiFunctionId.MsiSetMode,
1084 RemotableNativeMethods.GetRemoteHandle(hInstall),
1085 (int) iRunMode,
1086 fState ? 1 : 0);
1087 }
1088 }
1089
1090 internal static uint MsiSetTargetPath(int hInstall, string szFolder, string szFolderPath)
1091 {
1092 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
1093 return NativeMethods.MsiSetTargetPath(hInstall, szFolder, szFolderPath);
1094 else
1095 {
1096 return RemotableNativeMethods.MsiFunc_ISS(
1097 RemoteMsiFunctionId.MsiSetTargetPath,
1098 RemotableNativeMethods.GetRemoteHandle(hInstall),
1099 szFolder,
1100 szFolderPath);
1101 }
1102 }
1103
1104 internal static uint MsiSummaryInfoGetProperty(int hSummaryInfo, uint uiProperty, out uint uiDataType, out int iValue, ref long ftValue, StringBuilder szValueBuf, ref uint cchValueBuf)
1105 {
1106 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hSummaryInfo))
1107 {
1108 return NativeMethods.MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, out uiDataType, out iValue, ref ftValue, szValueBuf, ref cchValueBuf);
1109 }
1110 else lock (RemotableNativeMethods.remotingDelegate)
1111 {
1112 ClearData(requestBuf);
1113 WriteInt(requestBuf, 0, RemotableNativeMethods.GetRemoteHandle(hSummaryInfo));
1114 WriteInt(requestBuf, 1, (int) uiProperty);
1115 IntPtr resp;
1116 remotingDelegate(RemoteMsiFunctionId.MsiSummaryInfoGetProperty, requestBuf, out resp);
1117 unchecked
1118 {
1119 uint ret = (uint) ReadInt(resp, 0);
1120 if (ret == 0)
1121 {
1122 uiDataType = (uint) ReadInt(resp, 1);
1123 switch ((VarEnum) uiDataType)
1124 {
1125 case VarEnum.VT_I2:
1126 case VarEnum.VT_I4:
1127 iValue = ReadInt(resp, 2);
1128 break;
1129
1130 case VarEnum.VT_FILETIME:
1131 uint ftHigh = (uint) ReadInt(resp, 2);
1132 uint ftLow = (uint) ReadInt(resp, 3);
1133 ftValue = ((long) ftHigh) << 32 | ((long) ftLow);
1134 iValue = 0;
1135 break;
1136
1137 case VarEnum.VT_LPSTR:
1138 ReadString(resp, 2, szValueBuf, ref cchValueBuf);
1139 iValue = 0;
1140 break;
1141
1142 default:
1143 iValue = 0;
1144 break;
1145 }
1146 }
1147 else
1148 {
1149 uiDataType = 0;
1150 iValue = 0;
1151 }
1152 return ret;
1153 }
1154 }
1155 }
1156
1157 internal static uint MsiVerifyDiskSpace(int hInstall)
1158 {
1159 if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
1160 return NativeMethods.MsiVerifyDiskSpace(hInstall);
1161 else
1162 {
1163 return RemotableNativeMethods.MsiFunc_III(
1164 RemoteMsiFunctionId.MsiVerifyDiskSpace,
1165 RemotableNativeMethods.GetRemoteHandle(hInstall),
1166 0,
1167 0);
1168 }
1169 }
1170 }
1171}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Session.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Session.cs
new file mode 100644
index 00000000..875e49a6
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Session.cs
@@ -0,0 +1,946 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Text;
7 using System.Collections.Generic;
8 using System.Globalization;
9 using System.Runtime.InteropServices;
10 using System.Diagnostics.CodeAnalysis;
11
12 /// <summary>
13 /// The Session object controls the installation process. It opens the
14 /// install database, which contains the installation tables and data.
15 /// </summary>
16 /// <remarks><p>
17 /// This object is associated with a standard set of action functions,
18 /// each performing particular operations on data from one or more tables. Additional
19 /// custom actions may be added for particular product installations. The basic engine
20 /// function is a sequencer that fetches sequential records from a designated sequence
21 /// table, evaluates any specified condition expression, and executes the designated
22 /// action. Actions not recognized by the engine are deferred to the UI handler object
23 /// for processing, usually dialog box sequences.
24 /// </p><p>
25 /// Note that only one Session object can be opened by a single process.
26 /// </p></remarks>
27 public sealed class Session : InstallerHandle, IFormatProvider
28 {
29 private Database database;
30 private CustomActionData customActionData;
31 private bool sessionAccessValidated = false;
32
33 internal Session(IntPtr handle, bool ownsHandle)
34 : base(handle, ownsHandle)
35 {
36 }
37
38 /// <summary>
39 /// Gets the Database for the install session.
40 /// </summary>
41 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
42 /// <exception cref="InstallerException">the Database cannot be accessed</exception>
43 /// <remarks><p>
44 /// Normally there is no need to close this Database object. The same object can be
45 /// used throughout the lifetime of the Session, and it will be closed when the Session
46 /// is closed.
47 /// </p><p>
48 /// Win32 MSI API:
49 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetactivedatabase.asp">MsiGetActiveDatabase</a>
50 /// </p></remarks>
51 [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")]
52 public Database Database
53 {
54 get
55 {
56 if (this.database == null || this.database.IsClosed)
57 {
58 lock (this.Sync)
59 {
60 if (this.database == null || this.database.IsClosed)
61 {
62 this.ValidateSessionAccess();
63
64 int hDb = RemotableNativeMethods.MsiGetActiveDatabase((int) this.Handle);
65 if (hDb == 0)
66 {
67 throw new InstallerException();
68 }
69 this.database = new Database((IntPtr) hDb, true, "", DatabaseOpenMode.ReadOnly);
70 }
71 }
72 }
73 return this.database;
74 }
75 }
76
77 /// <summary>
78 /// Gets the numeric language ID used by the current install session.
79 /// </summary>
80 /// <remarks><p>
81 /// Win32 MSI API:
82 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetlanguage.asp">MsiGetLanguage</a>
83 /// </p></remarks>
84 public int Language
85 {
86 get
87 {
88 return (int) RemotableNativeMethods.MsiGetLanguage((int) this.Handle);
89 }
90 }
91
92 /// <summary>
93 /// Gets or sets the string value of a named installer property, as maintained by the
94 /// Session object in the in-memory Property table, or, if it is prefixed with a percent
95 /// sign (%), the value of a system environment variable for the current process.
96 /// </summary>
97 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
98 /// <remarks><p>
99 /// Win32 MSI APIs:
100 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproperty.asp">MsiGetProperty</a>,
101 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetproperty.asp">MsiSetProperty</a>
102 /// </p></remarks>
103 public string this[string property]
104 {
105 get
106 {
107 if (String.IsNullOrEmpty(property))
108 {
109 throw new ArgumentNullException("property");
110 }
111
112 if (!this.sessionAccessValidated &&
113 !Session.NonImmediatePropertyNames.Contains(property))
114 {
115 this.ValidateSessionAccess();
116 }
117
118 StringBuilder buf = new StringBuilder();
119 uint bufSize = 0;
120 uint ret = RemotableNativeMethods.MsiGetProperty((int) this.Handle, property, buf, ref bufSize);
121 if (ret == (uint) NativeMethods.Error.MORE_DATA)
122 {
123 buf.Capacity = (int) ++bufSize;
124 ret = RemotableNativeMethods.MsiGetProperty((int) this.Handle, property, buf, ref bufSize);
125 }
126
127 if (ret != 0)
128 {
129 throw InstallerException.ExceptionFromReturnCode(ret);
130 }
131 return buf.ToString();
132 }
133
134 set
135 {
136 if (String.IsNullOrEmpty(property))
137 {
138 throw new ArgumentNullException("property");
139 }
140
141 this.ValidateSessionAccess();
142
143 if (value == null)
144 {
145 value = String.Empty;
146 }
147
148 uint ret = RemotableNativeMethods.MsiSetProperty((int) this.Handle, property, value);
149 if (ret != 0)
150 {
151 throw InstallerException.ExceptionFromReturnCode(ret);
152 }
153 }
154 }
155
156 /// <summary>
157 /// Creates a new Session object from an integer session handle.
158 /// </summary>
159 /// <param name="handle">Integer session handle</param>
160 /// <param name="ownsHandle">true to close the handle when this object is disposed or finalized</param>
161 /// <remarks><p>
162 /// This method is only provided for interop purposes. A Session object
163 /// should normally be obtained by calling <see cref="Installer.OpenPackage(Database,bool)"/>
164 /// or <see cref="Installer.OpenProduct"/>.
165 /// </p></remarks>
166 public static Session FromHandle(IntPtr handle, bool ownsHandle)
167 {
168 return new Session(handle, ownsHandle);
169 }
170
171 /// <summary>
172 /// Performs any enabled logging operations and defers execution to the UI handler
173 /// object associated with the engine.
174 /// </summary>
175 /// <param name="messageType">Type of message to be processed</param>
176 /// <param name="record">Contains message-specific fields</param>
177 /// <returns>A message-dependent return value</returns>
178 /// <exception cref="InvalidHandleException">the Session or Record handle is invalid</exception>
179 /// <exception cref="ArgumentOutOfRangeException">an invalid message kind is specified</exception>
180 /// <exception cref="InstallCanceledException">the user exited the installation</exception>
181 /// <exception cref="InstallerException">the message-handler failed for an unknown reason</exception>
182 /// <remarks><p>
183 /// Logging may be selectively enabled for the various message types.
184 /// See the <see cref="Installer.EnableLog(InstallLogModes,string)"/> method.
185 /// </p><p>
186 /// If record field 0 contains a formatting string, it is used to format the data in
187 /// the other fields. Else if the message is an error, warning, or user message, an attempt
188 /// is made to find a message template in the Error table for the current database using the
189 /// error number found in field 1 of the record for message types and return values.
190 /// </p><p>
191 /// The <paramref name="messageType"/> parameter may also include message-box flags from
192 /// the following enumerations: System.Windows.Forms.MessageBoxButtons,
193 /// System.Windows.Forms.MessageBoxDefaultButton, System.Windows.Forms.MessageBoxIcon. These
194 /// flags can be combined with the InstallMessage with a bitwise OR.
195 /// </p><p>
196 /// Note, this method never returns Cancel or Error values. Instead, appropriate
197 /// exceptions are thrown in those cases.
198 /// </p><p>
199 /// Win32 MSI API:
200 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprocessmessage.asp">MsiProcessMessage</a>
201 /// </p></remarks>
202 [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
203 public MessageResult Message(InstallMessage messageType, Record record)
204 {
205 if (record == null)
206 {
207 throw new ArgumentNullException("record");
208 }
209
210 int ret = RemotableNativeMethods.MsiProcessMessage((int) this.Handle, (uint) messageType, (int) record.Handle);
211 if (ret < 0)
212 {
213 throw new InstallerException();
214 }
215 else if (ret == (int) MessageResult.Cancel)
216 {
217 throw new InstallCanceledException();
218 }
219 return (MessageResult) ret;
220 }
221
222 /// <summary>
223 /// Writes a message to the log, if logging is enabled.
224 /// </summary>
225 /// <param name="msg">The line to be written to the log</param>
226 /// <remarks><p>
227 /// Win32 MSI API:
228 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprocessmessage.asp">MsiProcessMessage</a>
229 /// </p></remarks>
230 public void Log(string msg)
231 {
232 if (msg == null)
233 {
234 throw new ArgumentNullException("msg");
235 }
236
237 using (Record rec = new Record(0))
238 {
239 rec.FormatString = msg;
240 this.Message(InstallMessage.Info, rec);
241 }
242 }
243
244 /// <summary>
245 /// Writes a formatted message to the log, if logging is enabled.
246 /// </summary>
247 /// <param name="format">The line to be written to the log, containing 0 or more format specifiers</param>
248 /// <param name="args">An array containing 0 or more objects to be formatted</param>
249 /// <remarks><p>
250 /// Win32 MSI API:
251 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprocessmessage.asp">MsiProcessMessage</a>
252 /// </p></remarks>
253 public void Log(string format, params object[] args)
254 {
255 this.Log(String.Format(CultureInfo.InvariantCulture, format, args));
256 }
257
258 /// <summary>
259 /// Evaluates a logical expression containing symbols and values.
260 /// </summary>
261 /// <param name="condition">conditional expression</param>
262 /// <returns>The result of the condition evaluation</returns>
263 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
264 /// <exception cref="ArgumentNullException">the condition is null or empty</exception>
265 /// <exception cref="InvalidOperationException">the conditional expression is invalid</exception>
266 /// <remarks><p>
267 /// Win32 MSI API:
268 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msievaluatecondition.asp">MsiEvaluateCondition</a>
269 /// </p></remarks>
270 public bool EvaluateCondition(string condition)
271 {
272 if (String.IsNullOrEmpty(condition))
273 {
274 throw new ArgumentNullException("condition");
275 }
276
277 uint value = RemotableNativeMethods.MsiEvaluateCondition((int) this.Handle, condition);
278 if (value == 0)
279 {
280 return false;
281 }
282 else if (value == 1)
283 {
284 return true;
285 }
286 else
287 {
288 throw new InvalidOperationException();
289 }
290 }
291
292 /// <summary>
293 /// Evaluates a logical expression containing symbols and values, specifying a default
294 /// value to be returned in case the condition is empty.
295 /// </summary>
296 /// <param name="condition">conditional expression</param>
297 /// <param name="defaultValue">value to return if the condition is empty</param>
298 /// <returns>The result of the condition evaluation</returns>
299 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
300 /// <exception cref="InvalidOperationException">the conditional expression is invalid</exception>
301 /// <remarks><p>
302 /// Win32 MSI API:
303 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msievaluatecondition.asp">MsiEvaluateCondition</a>
304 /// </p></remarks>
305 public bool EvaluateCondition(string condition, bool defaultValue)
306 {
307 if (condition == null)
308 {
309 throw new ArgumentNullException("condition");
310 }
311 else if (condition.Length == 0)
312 {
313 return defaultValue;
314 }
315 else
316 {
317 this.ValidateSessionAccess();
318 return this.EvaluateCondition(condition);
319 }
320 }
321
322 /// <summary>
323 /// Formats a string containing installer properties.
324 /// </summary>
325 /// <param name="format">A format string containing property tokens</param>
326 /// <returns>A formatted string containing property data</returns>
327 /// <exception cref="InvalidHandleException">the Record handle is invalid</exception>
328 /// <remarks><p>
329 /// Win32 MSI API:
330 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a>
331 /// </p></remarks>
332 [SuppressMessage("Microsoft.Naming", "CA1719:ParameterNamesShouldNotMatchMemberNames")]
333 public string Format(string format)
334 {
335 if (format == null)
336 {
337 throw new ArgumentNullException("format");
338 }
339
340 using (Record formatRec = new Record(0))
341 {
342 formatRec.FormatString = format;
343 return formatRec.ToString(this);
344 }
345 }
346
347 /// <summary>
348 /// Returns a formatted string from record data.
349 /// </summary>
350 /// <param name="record">Record object containing a template and data to be formatted.
351 /// The template string must be set in field 0 followed by any referenced data parameters.</param>
352 /// <returns>A formatted string containing the record data</returns>
353 /// <exception cref="InvalidHandleException">the Record handle is invalid</exception>
354 /// <remarks><p>
355 /// Win32 MSI API:
356 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a>
357 /// </p></remarks>
358 public string FormatRecord(Record record)
359 {
360 if (record == null)
361 {
362 throw new ArgumentNullException("record");
363 }
364
365 return record.ToString(this);
366 }
367
368 /// <summary>
369 /// Returns a formatted string from record data using a specified format.
370 /// </summary>
371 /// <param name="record">Record object containing a template and data to be formatted</param>
372 /// <param name="format">Format string to be used instead of field 0 of the Record</param>
373 /// <returns>A formatted string containing the record data</returns>
374 /// <exception cref="InvalidHandleException">the Record handle is invalid</exception>
375 /// <remarks><p>
376 /// Win32 MSI API:
377 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a>
378 /// </p></remarks>
379 [Obsolete("This method is obsolete because it has undesirable side-effects. As an alternative, set the Record's " +
380 "FormatString property separately before calling the FormatRecord() override that takes only the Record parameter.")]
381 public string FormatRecord(Record record, string format)
382 {
383 if (record == null)
384 {
385 throw new ArgumentNullException("record");
386 }
387
388 return record.ToString(format, this);
389 }
390
391 /// <summary>
392 /// Retrieves product properties (not session properties) from the product database.
393 /// </summary>
394 /// <returns>Value of the property, or an empty string if the property is not set.</returns>
395 /// <remarks><p>
396 /// Note this is not the correct method for getting ordinary session properties. For that,
397 /// see the indexer on the Session class.
398 /// </p><p>
399 /// Win32 MSI API:
400 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproductproperty.asp">MsiGetProductProperty</a>
401 /// </p></remarks>
402 public string GetProductProperty(string property)
403 {
404 if (String.IsNullOrEmpty(property))
405 {
406 throw new ArgumentNullException("property");
407 }
408
409 this.ValidateSessionAccess();
410
411 StringBuilder buf = new StringBuilder();
412 uint bufSize = (uint) buf.Capacity;
413 uint ret = NativeMethods.MsiGetProductProperty((int) this.Handle, property, buf, ref bufSize);
414
415 if (ret == (uint) NativeMethods.Error.MORE_DATA)
416 {
417 buf.Capacity = (int) ++bufSize;
418 ret = NativeMethods.MsiGetProductProperty((int) this.Handle, property, buf, ref bufSize);
419 }
420
421 if (ret != 0)
422 {
423 throw InstallerException.ExceptionFromReturnCode(ret);
424 }
425 return buf.ToString();
426 }
427
428 /// <summary>
429 /// Gets an accessor for components in the current session.
430 /// </summary>
431 public ComponentInfoCollection Components
432 {
433 get
434 {
435 this.ValidateSessionAccess();
436 return new ComponentInfoCollection(this);
437 }
438 }
439
440 /// <summary>
441 /// Gets an accessor for features in the current session.
442 /// </summary>
443 public FeatureInfoCollection Features
444 {
445 get
446 {
447 this.ValidateSessionAccess();
448 return new FeatureInfoCollection(this);
449 }
450 }
451
452 /// <summary>
453 /// Checks to see if sufficient disk space is present for the current installation.
454 /// </summary>
455 /// <returns>True if there is sufficient disk space; false otherwise.</returns>
456 /// <remarks><p>
457 /// Win32 MSI API:
458 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiverifydiskspace.asp">MsiVerifyDiskSpace</a>
459 /// </p></remarks>
460 public bool VerifyDiskSpace()
461 {
462 this.ValidateSessionAccess();
463
464 uint ret = RemotableNativeMethods.MsiVerifyDiskSpace((int)this.Handle);
465 if (ret == (uint) NativeMethods.Error.DISK_FULL)
466 {
467 return false;
468 }
469 else if (ret != 0)
470 {
471 throw InstallerException.ExceptionFromReturnCode(ret);
472 }
473 return true;
474 }
475
476 /// <summary>
477 /// Gets the total disk space per drive required for the installation.
478 /// </summary>
479 /// <returns>A list of InstallCost structures, specifying the cost for each drive</returns>
480 /// <remarks><p>
481 /// Win32 MSI API:
482 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumcomponentcosts.asp">MsiEnumComponentCosts</a>
483 /// </p></remarks>
484 [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
485 public IList<InstallCost> GetTotalCost()
486 {
487 this.ValidateSessionAccess();
488
489 IList<InstallCost> costs = new List<InstallCost>();
490 StringBuilder driveBuf = new StringBuilder(20);
491 for (uint i = 0; true; i++)
492 {
493 int cost, tempCost;
494 uint driveBufSize = (uint) driveBuf.Capacity;
495 uint ret = RemotableNativeMethods.MsiEnumComponentCosts(
496 (int) this.Handle,
497 null,
498 i,
499 (int) InstallState.Default,
500 driveBuf,
501 ref driveBufSize,
502 out cost,
503 out tempCost);
504 if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break;
505 if (ret == (uint) NativeMethods.Error.MORE_DATA)
506 {
507 driveBuf.Capacity = (int) ++driveBufSize;
508 ret = RemotableNativeMethods.MsiEnumComponentCosts(
509 (int) this.Handle,
510 null,
511 i,
512 (int) InstallState.Default,
513 driveBuf,
514 ref driveBufSize,
515 out cost,
516 out tempCost);
517 }
518
519 if (ret != 0)
520 {
521 throw InstallerException.ExceptionFromReturnCode(ret);
522 }
523 costs.Add(new InstallCost(driveBuf.ToString(), cost * 512L, tempCost * 512L));
524 }
525 return costs;
526 }
527
528 /// <summary>
529 /// Gets the designated mode flag for the current install session.
530 /// </summary>
531 /// <param name="mode">The type of mode to be checked.</param>
532 /// <returns>The value of the designated mode flag.</returns>
533 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
534 /// <exception cref="ArgumentOutOfRangeException">an invalid mode flag was specified</exception>
535 /// <remarks><p>
536 /// Note that only the following run modes are available to read from
537 /// a deferred custom action:<list type="bullet">
538 /// <item><description><see cref="InstallRunMode.Scheduled"/></description></item>
539 /// <item><description><see cref="InstallRunMode.Rollback"/></description></item>
540 /// <item><description><see cref="InstallRunMode.Commit"/></description></item>
541 /// </list>
542 /// </p><p>
543 /// Win32 MSI API:
544 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetmode.asp">MsiGetMode</a>
545 /// </p></remarks>
546 public bool GetMode(InstallRunMode mode)
547 {
548 return RemotableNativeMethods.MsiGetMode((int) this.Handle, (uint) mode);
549 }
550
551 /// <summary>
552 /// Sets the designated mode flag for the current install session.
553 /// </summary>
554 /// <param name="mode">The type of mode to be set.</param>
555 /// <param name="value">The desired value of the mode.</param>
556 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
557 /// <exception cref="ArgumentOutOfRangeException">an invalid mode flag was specified</exception>
558 /// <exception cref="InvalidOperationException">the mode cannot not be set</exception>
559 /// <remarks><p>
560 /// Win32 MSI API:
561 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetmode.asp">MsiSetMode</a>
562 /// </p></remarks>
563 public void SetMode(InstallRunMode mode, bool value)
564 {
565 this.ValidateSessionAccess();
566
567 uint ret = RemotableNativeMethods.MsiSetMode((int) this.Handle, (uint) mode, value);
568 if (ret != 0)
569 {
570 if (ret == (uint) NativeMethods.Error.ACCESS_DENIED)
571 {
572 throw new InvalidOperationException();
573 }
574 else
575 {
576 throw InstallerException.ExceptionFromReturnCode(ret);
577 }
578 }
579 }
580
581 /// <summary>
582 /// Gets the full path to the designated folder on the source media or server image.
583 /// </summary>
584 /// <exception cref="ArgumentException">the folder was not found in the Directory table</exception>
585 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
586 /// <remarks><p>
587 /// Win32 MSI API:
588 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetsourcepath.asp">MsiGetSourcePath</a>
589 /// </p></remarks>
590 public string GetSourcePath(string directory)
591 {
592 if (String.IsNullOrEmpty(directory))
593 {
594 throw new ArgumentNullException("directory");
595 }
596
597 this.ValidateSessionAccess();
598
599 StringBuilder buf = new StringBuilder();
600 uint bufSize = 0;
601 uint ret = RemotableNativeMethods.MsiGetSourcePath((int) this.Handle, directory, buf, ref bufSize);
602 if (ret == (uint) NativeMethods.Error.MORE_DATA)
603 {
604 buf.Capacity = (int) ++bufSize;
605 ret = ret = RemotableNativeMethods.MsiGetSourcePath((int) this.Handle, directory, buf, ref bufSize);
606 }
607
608 if (ret != 0)
609 {
610 if (ret == (uint) NativeMethods.Error.DIRECTORY)
611 {
612 throw InstallerException.ExceptionFromReturnCode(ret, directory);
613 }
614 else
615 {
616 throw InstallerException.ExceptionFromReturnCode(ret);
617 }
618 }
619 return buf.ToString();
620 }
621
622 /// <summary>
623 /// Gets the full path to the designated folder on the installation target drive.
624 /// </summary>
625 /// <exception cref="ArgumentException">the folder was not found in the Directory table</exception>
626 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
627 /// <remarks><p>
628 /// Win32 MSI API:
629 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigettargetpath.asp">MsiGetTargetPath</a>
630 /// </p></remarks>
631 public string GetTargetPath(string directory)
632 {
633 if (String.IsNullOrEmpty(directory))
634 {
635 throw new ArgumentNullException("directory");
636 }
637
638 this.ValidateSessionAccess();
639
640 StringBuilder buf = new StringBuilder();
641 uint bufSize = 0;
642 uint ret = RemotableNativeMethods.MsiGetTargetPath((int) this.Handle, directory, buf, ref bufSize);
643 if (ret == (uint) NativeMethods.Error.MORE_DATA)
644 {
645 buf.Capacity = (int) ++bufSize;
646 ret = ret = RemotableNativeMethods.MsiGetTargetPath((int) this.Handle, directory, buf, ref bufSize);
647 }
648
649 if (ret != 0)
650 {
651 if (ret == (uint) NativeMethods.Error.DIRECTORY)
652 {
653 throw InstallerException.ExceptionFromReturnCode(ret, directory);
654 }
655 else
656 {
657 throw InstallerException.ExceptionFromReturnCode(ret);
658 }
659 }
660 return buf.ToString();
661 }
662
663 /// <summary>
664 /// Sets the full path to the designated folder on the installation target drive.
665 /// </summary>
666 /// <exception cref="ArgumentException">the folder was not found in the Directory table</exception>
667 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
668 /// <remarks><p>
669 /// Setting the target path of a directory changes the path specification for the directory
670 /// in the in-memory Directory table. Also, the path specifications of all other path objects
671 /// in the table that are either subordinate or equivalent to the changed path are updated
672 /// to reflect the change. The properties for each affected path are also updated.
673 /// </p><p>
674 /// If an error occurs in this function, all updated paths and properties revert to
675 /// their previous values. Therefore, it is safe to treat errors returned by this function
676 /// as non-fatal.
677 /// </p><p>
678 /// Do not attempt to configure the target path if the components using those paths
679 /// are already installed for the current user or for a different user. Check the
680 /// ProductState property before setting the target path to determine if the product
681 /// containing this component is installed.
682 /// </p><p>
683 /// Win32 MSI API:
684 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisettargetpath.asp">MsiSetTargetPath</a>
685 /// </p></remarks>
686 public void SetTargetPath(string directory, string value)
687 {
688 if (String.IsNullOrEmpty(directory))
689 {
690 throw new ArgumentNullException("directory");
691 }
692
693 if (value == null)
694 {
695 throw new ArgumentNullException("value");
696 }
697
698 this.ValidateSessionAccess();
699
700 uint ret = RemotableNativeMethods.MsiSetTargetPath((int) this.Handle, directory, value);
701 if (ret != 0)
702 {
703 if (ret == (uint) NativeMethods.Error.DIRECTORY)
704 {
705 throw InstallerException.ExceptionFromReturnCode(ret, directory);
706 }
707 else
708 {
709 throw InstallerException.ExceptionFromReturnCode(ret);
710 }
711 }
712 }
713
714 /// <summary>
715 /// Sets the install level for the current installation to a specified value and
716 /// recalculates the Select and Installed states for all features in the Feature
717 /// table. Also sets the Action state of each component in the Component table based
718 /// on the new level.
719 /// </summary>
720 /// <param name="installLevel">New install level</param>
721 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
722 /// <remarks><p>
723 /// The SetInstallLevel method sets the following:<list type="bullet">
724 /// <item><description>The installation level for the current installation to a specified value</description></item>
725 /// <item><description>The Select and Installed states for all features in the Feature table</description></item>
726 /// <item><description>The Action state of each component in the Component table, based on the new level</description></item>
727 /// </list>
728 /// If 0 or a negative number is passed in the ilnstallLevel parameter,
729 /// the current installation level does not change, but all features are still
730 /// updated based on the current installation level.
731 /// </p><p>
732 /// Win32 MSI API:
733 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetinstalllevel.asp">MsiSetInstallLevel</a>
734 /// </p></remarks>
735 public void SetInstallLevel(int installLevel)
736 {
737 this.ValidateSessionAccess();
738
739 uint ret = RemotableNativeMethods.MsiSetInstallLevel((int) this.Handle, installLevel);
740 if (ret != 0)
741 {
742 throw InstallerException.ExceptionFromReturnCode(ret);
743 }
744 }
745
746 /// <summary>
747 /// Executes a built-in action, custom action, or user-interface wizard action.
748 /// </summary>
749 /// <param name="action">Name of the action to execute. Case-sensitive.</param>
750 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
751 /// <exception cref="InstallCanceledException">the user exited the installation</exception>
752 /// <remarks><p>
753 /// The DoAction method executes the action that corresponds to the name supplied. If the
754 /// name is not recognized by the installer as a built-in action or as a custom action in
755 /// the CustomAction table, the name is passed to the user-interface handler object, which
756 /// can invoke a function or a dialog box. If a null action name is supplied, the installer
757 /// uses the upper-case value of the ACTION property as the action to perform. If no property
758 /// value is defined, the default action is performed, defined as "INSTALL".
759 /// </p><p>
760 /// Actions that update the system, such as the InstallFiles and WriteRegistryValues
761 /// actions, cannot be run by calling MsiDoAction. The exception to this rule is if DoAction
762 /// is called from a custom action that is scheduled in the InstallExecuteSequence table
763 /// between the InstallInitialize and InstallFinalize actions. Actions that do not update the
764 /// system, such as AppSearch or CostInitialize, can be called.
765 /// </p><p>
766 /// Win32 MSI API:
767 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidoaction.asp">MsiDoAction</a>
768 /// </p></remarks>
769 public void DoAction(string action)
770 {
771 this.DoAction(action, null);
772 }
773
774 /// <summary>
775 /// Executes a built-in action, custom action, or user-interface wizard action.
776 /// </summary>
777 /// <param name="action">Name of the action to execute. Case-sensitive.</param>
778 /// <param name="actionData">Optional data to be passed to a deferred custom action.</param>
779 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
780 /// <exception cref="InstallCanceledException">the user exited the installation</exception>
781 /// <remarks><p>
782 /// The DoAction method executes the action that corresponds to the name supplied. If the
783 /// name is not recognized by the installer as a built-in action or as a custom action in
784 /// the CustomAction table, the name is passed to the user-interface handler object, which
785 /// can invoke a function or a dialog box. If a null action name is supplied, the installer
786 /// uses the upper-case value of the ACTION property as the action to perform. If no property
787 /// value is defined, the default action is performed, defined as "INSTALL".
788 /// </p><p>
789 /// Actions that update the system, such as the InstallFiles and WriteRegistryValues
790 /// actions, cannot be run by calling MsiDoAction. The exception to this rule is if DoAction
791 /// is called from a custom action that is scheduled in the InstallExecuteSequence table
792 /// between the InstallInitialize and InstallFinalize actions. Actions that do not update the
793 /// system, such as AppSearch or CostInitialize, can be called.
794 /// </p><p>
795 /// If the called action is a deferred, rollback, or commit custom action, then the supplied
796 /// <paramref name="actionData"/> will be available via the <see cref="CustomActionData"/>
797 /// property of that custom action's session.
798 /// </p><p>
799 /// Win32 MSI API:
800 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidoaction.asp">MsiDoAction</a>
801 /// </p></remarks>
802 public void DoAction(string action, CustomActionData actionData)
803 {
804 if (String.IsNullOrEmpty(action))
805 {
806 throw new ArgumentNullException("action");
807 }
808
809 this.ValidateSessionAccess();
810
811 if (actionData != null)
812 {
813 this[action] = actionData.ToString();
814 }
815
816 uint ret = RemotableNativeMethods.MsiDoAction((int) this.Handle, action);
817 if (ret != 0)
818 {
819 throw InstallerException.ExceptionFromReturnCode(ret);
820 }
821 }
822
823 /// <summary>
824 /// Executes an action sequence described in the specified table.
825 /// </summary>
826 /// <param name="sequenceTable">Name of the table containing the action sequence.</param>
827 /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
828 /// <exception cref="InstallCanceledException">the user exited the installation</exception>
829 /// <remarks><p>
830 /// This method queries the specified table, ordering the actions by the numbers in the Sequence column.
831 /// For each row retrieved, an action is executed, provided that any supplied condition expression does
832 /// not evaluate to FALSE.
833 /// </p><p>
834 /// An action sequence containing any actions that update the system, such as the InstallFiles and
835 /// WriteRegistryValues actions, cannot be run by calling DoActionSequence. The exception to this rule is if
836 /// DoActionSequence is called from a custom action that is scheduled in the InstallExecuteSequence table
837 /// between the InstallInitialize and InstallFinalize actions. Actions that do not update the system, such
838 /// as AppSearch or CostInitialize, can be called.
839 /// </p><p>
840 /// Win32 MSI API:
841 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisequence.asp">MsiSequence</a>
842 /// </p></remarks>
843 public void DoActionSequence(string sequenceTable)
844 {
845 if (String.IsNullOrEmpty(sequenceTable))
846 {
847 throw new ArgumentNullException("sequenceTable");
848 }
849
850 this.ValidateSessionAccess();
851
852 uint ret = RemotableNativeMethods.MsiSequence((int) this.Handle, sequenceTable, 0);
853 if (ret != 0)
854 {
855 throw InstallerException.ExceptionFromReturnCode(ret);
856 }
857 }
858
859 /// <summary>
860 /// Gets custom action data for the session that was supplied by the caller.
861 /// </summary>
862 /// <seealso cref="DoAction(string,CustomActionData)"/>
863 public CustomActionData CustomActionData
864 {
865 get
866 {
867 if (this.customActionData == null)
868 {
869 this.customActionData = new CustomActionData(this[CustomActionData.PropertyName]);
870 }
871
872 return this.customActionData;
873 }
874 }
875
876 /// <summary>
877 /// Implements formatting for <see cref="Record" /> data.
878 /// </summary>
879 /// <param name="formatType">Type of format object to get.</param>
880 /// <returns>The the current instance, if <paramref name="formatType"/> is the same type
881 /// as the current instance; otherwise, null.</returns>
882 object IFormatProvider.GetFormat(Type formatType)
883 {
884 return formatType == typeof(Session) ? this : null;
885 }
886
887 /// <summary>
888 /// Closes the session handle. Also closes the active database handle, if it is open.
889 /// After closing a handle, further method calls may throw an <see cref="InvalidHandleException"/>.
890 /// </summary>
891 /// <param name="disposing">If true, the method has been called directly
892 /// or indirectly by a user's code, so managed and unmanaged resources will
893 /// be disposed. If false, only unmanaged resources will be disposed.</param>
894 protected override void Dispose(bool disposing)
895 {
896 try
897 {
898 if (disposing)
899 {
900 if (this.database != null)
901 {
902 this.database.Dispose();
903 this.database = null;
904 }
905 }
906 }
907 finally
908 {
909 base.Dispose(disposing);
910 }
911 }
912
913 /// <summary>
914 /// Gets the (short) list of properties that are available from non-immediate custom actions.
915 /// </summary>
916 private static IList<string> NonImmediatePropertyNames
917 {
918 get
919 {
920 return new string[] {
921 CustomActionData.PropertyName,
922 "ProductCode",
923 "UserSID"
924 };
925 }
926 }
927
928 /// <summary>
929 /// Throws an exception if the custom action is not able to access immedate session details.
930 /// </summary>
931 private void ValidateSessionAccess()
932 {
933 if (!this.sessionAccessValidated)
934 {
935 if (this.GetMode(InstallRunMode.Scheduled) ||
936 this.GetMode(InstallRunMode.Rollback) ||
937 this.GetMode(InstallRunMode.Commit))
938 {
939 throw new InstallerException("Cannot access session details from a non-immediate custom action");
940 }
941
942 this.sessionAccessValidated = true;
943 }
944 }
945 }
946}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/ShortcutTarget.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/ShortcutTarget.cs
new file mode 100644
index 00000000..4c043bf2
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/ShortcutTarget.cs
@@ -0,0 +1,104 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 /// <summary>
6 /// Holds information about the target of a shortcut file.
7 /// </summary>
8 public struct ShortcutTarget
9 {
10 private string productCode;
11 private string feature;
12 private string componentCode;
13
14 internal ShortcutTarget(string productCode, string feature, string componentCode)
15 {
16 this.productCode = productCode;
17 this.feature = feature;
18 this.componentCode = componentCode;
19 }
20
21 /// <summary>
22 /// Gets the target product code of the shortcut, or null if not available.
23 /// </summary>
24 public string ProductCode
25 {
26 get
27 {
28 return this.productCode;
29 }
30 }
31
32 /// <summary>
33 /// Gets the name of the target feature of the shortcut, or null if not available.
34 /// </summary>
35 public string Feature
36 {
37 get
38 {
39 return this.feature;
40 }
41 }
42
43 /// <summary>
44 /// Gets the target component code of the shortcut, or null if not available.
45 /// </summary>
46 public string ComponentCode
47 {
48 get
49 {
50 return this.componentCode;
51 }
52 }
53
54 /// <summary>
55 /// Tests whether two shortcut targets have the same product code, feature, and/or component code.
56 /// </summary>
57 /// <param name="st1">The first shortcut target to compare.</param>
58 /// <param name="st2">The second shortcut target to compare.</param>
59 /// <returns>True if all parts of the targets are the same, else false.</returns>
60 public static bool operator ==(ShortcutTarget st1, ShortcutTarget st2)
61 {
62 return st1.Equals(st2);
63 }
64
65 /// <summary>
66 /// Tests whether two shortcut targets have the same product code, feature, and/or component code.
67 /// </summary>
68 /// <param name="st1">The first shortcut target to compare.</param>
69 /// <param name="st2">The second shortcut target to compare.</param>
70 /// <returns>True if any parts of the targets are different, else false.</returns>
71 public static bool operator !=(ShortcutTarget st1, ShortcutTarget st2)
72 {
73 return !st1.Equals(st2);
74 }
75
76 /// <summary>
77 /// Tests whether two shortcut targets have the same product code, feature, and/or component code.
78 /// </summary>
79 /// <param name="obj">The shortcut target to compare to the current object.</param>
80 /// <returns>True if <paramref name="obj"/> is a shortcut target and all parts of the targets are the same, else false.</returns>
81 public override bool Equals(object obj)
82 {
83 if (obj == null || obj.GetType() != typeof(ShortcutTarget))
84 {
85 return false;
86 }
87 ShortcutTarget st = (ShortcutTarget) obj;
88 return this.productCode == st.productCode
89 && this.feature == st.feature
90 && this.componentCode == st.componentCode;
91 }
92
93 /// <summary>
94 /// Generates a hash code using all parts of the shortcut target.
95 /// </summary>
96 /// <returns>An integer suitable for hashing the shortcut target.</returns>
97 public override int GetHashCode()
98 {
99 return (this.productCode != null ? this.productCode.GetHashCode() : 0)
100 ^ (this.feature != null ? this.feature.GetHashCode() : 0)
101 ^ (this.componentCode != null ? this.componentCode.GetHashCode() : 0);
102 }
103 }
104}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/SourceList.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/SourceList.cs
new file mode 100644
index 00000000..16ec22e8
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/SourceList.cs
@@ -0,0 +1,525 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Text;
7 using System.Collections.Generic;
8 using System.Globalization;
9 using System.Diagnostics.CodeAnalysis;
10
11 /// <summary>
12 /// A list of sources for an installed product or patch.
13 /// </summary>
14 [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
15 public class SourceList : ICollection<string>
16 {
17 private Installation installation;
18 private SourceMediaList mediaList;
19
20 internal SourceList(Installation installation)
21 {
22 this.installation = installation;
23 }
24
25 /// <summary>
26 /// Gets the list of disks registered for the media source of
27 /// the patch or product installation.
28 /// </summary>
29 public SourceMediaList MediaList
30 {
31 get
32 {
33 if (this.mediaList == null)
34 {
35 this.mediaList = new SourceMediaList(this.installation);
36 }
37 return this.mediaList;
38 }
39 }
40
41 /// <summary>
42 /// Gets the number of network and URL sources in the list.
43 /// </summary>
44 public int Count
45 {
46 get
47 {
48 int count = 0;
49 IEnumerator<string> e = this.GetEnumerator();
50 while (e.MoveNext())
51 {
52 count++;
53 }
54 return count;
55 }
56 }
57
58 /// <summary>
59 /// Gets a boolean value indicating whether the list is read-only.
60 /// A SourceList is never read-only.
61 /// </summary>
62 /// <value>read-only status of the list</value>
63 public bool IsReadOnly
64 {
65 get { return false; }
66 }
67
68 /// <summary>
69 /// Adds a network or URL source to the source list of the installed product.
70 /// </summary>
71 /// <param name="item">Path to the source to be added. This parameter is
72 /// expected to contain only the path without the filename.</param>
73 /// <remarks><p>
74 /// If this method is called with a new source, the installer adds the source
75 /// to the end of the source list.
76 /// </p><p>
77 /// If this method is called with a source already existing in the source
78 /// list, it has no effect.
79 /// </p><p>
80 /// Win32 MSI APIs:
81 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistaddsource.asp">MsiSourceListAddSource</a>,
82 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistaddsourceex.asp">MsiSourceListAddSourceEx</a>
83 /// </p></remarks>
84 /// <seealso cref="Insert"/>
85 public void Add(string item)
86 {
87 if (!this.Contains(item))
88 {
89 this.Insert(item, 0);
90 }
91 }
92
93 /// <summary>
94 /// Adds or reorders a network or URL source for the product or patch.
95 /// </summary>
96 /// <param name="item">Path to the source to be added. This parameter is
97 /// expected to contain only the path without the filename.</param>
98 /// <param name="index">Specifies the priority order in which the source
99 /// will be inserted</param>
100 /// <remarks><p>
101 /// If this method is called with a new source and <paramref name="index"/>
102 /// is set to 0, the installer adds the source to the end of the source list.
103 /// </p><p>
104 /// If this method is called with a source already existing in the source
105 /// list and <paramref name="index"/> is set to 0, the installer retains the
106 /// source's existing index.
107 /// </p><p>
108 /// If the method is called with an existing source in the source list
109 /// and <paramref name="index"/> is set to a non-zero value, the source is
110 /// removed from its current location in the list and inserted at the position
111 /// specified by Index, before any source that already exists at that position.
112 /// </p><p>
113 /// If the method is called with a new source and Index is set to a
114 /// non-zero value, the source is inserted at the position specified by
115 /// <paramref name="index"/>, before any source that already exists at
116 /// that position. The index value for all sources in the list after the
117 /// index specified by Index are updated to ensure unique index values and
118 /// the pre-existing order is guaranteed to remain unchanged.
119 /// </p><p>
120 /// If <paramref name="index"/> is greater than the number of sources
121 /// in the list, the source is placed at the end of the list with an index
122 /// value one larger than any existing source.
123 /// </p><p>
124 /// Win32 MSI API:
125 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistaddsourceex.asp">MsiSourceListAddSourceEx</a>
126 /// </p></remarks>
127 public void Insert(string item, int index)
128 {
129 if (item == null)
130 {
131 throw new ArgumentNullException("item");
132 }
133
134 NativeMethods.SourceType type = item.Contains("://") ? NativeMethods.SourceType.Url : NativeMethods.SourceType.Network;
135
136 uint ret = NativeMethods.MsiSourceListAddSourceEx(
137 this.installation.InstallationCode,
138 this.installation.UserSid,
139 this.installation.Context,
140 (uint) type | (uint) this.installation.InstallationType,
141 item,
142 (uint) index);
143 if (ret != 0)
144 {
145 throw InstallerException.ExceptionFromReturnCode(ret);
146 }
147 }
148
149 /// <summary>
150 /// Clears sources of all types: network, url, and media.
151 /// </summary>
152 /// <remarks><p>
153 /// Win32 MSI API:
154 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistclearall.asp">MsiSourceListClearAll</a>
155 /// </p></remarks>
156 public void Clear()
157 {
158 this.ClearSourceType(NativeMethods.SourceType.Url);
159 this.ClearSourceType(NativeMethods.SourceType.Network);
160 this.MediaList.Clear();
161 }
162
163 /// <summary>
164 /// Removes all network sources from the list. URL sources are not affected.
165 /// </summary>
166 /// <remarks><p>
167 /// Win32 MSI API:
168 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistclearallex.asp">MsiSourceListClearAllEx</a>
169 /// </p></remarks>
170 public void ClearNetworkSources()
171 {
172 this.ClearSourceType(NativeMethods.SourceType.Network);
173 }
174
175 /// <summary>
176 /// Removes all URL sources from the list. Network sources are not affected.
177 /// </summary>
178 /// <remarks><p>
179 /// Win32 MSI API:
180 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistclearallex.asp">MsiSourceListClearAllEx</a>
181 /// </p></remarks>
182 public void ClearUrlSources()
183 {
184 this.ClearSourceType(NativeMethods.SourceType.Url);
185 }
186
187 /// <summary>
188 /// Checks if the specified source exists in the list.
189 /// </summary>
190 /// <param name="item">case-insensitive source to look for</param>
191 /// <returns>true if the source exists in the list, false otherwise</returns>
192 public bool Contains(string item)
193 {
194 if (String.IsNullOrEmpty(item))
195 {
196 throw new ArgumentNullException("item");
197 }
198
199 foreach (string s in this)
200 {
201 if (s.Equals(item, StringComparison.OrdinalIgnoreCase))
202 {
203 return true;
204 }
205 }
206 return false;
207 }
208
209 /// <summary>
210 /// Copies the network and URL sources from this list into an array.
211 /// </summary>
212 /// <param name="array">destination array to be filed</param>
213 /// <param name="arrayIndex">offset into the destination array where copying begins</param>
214 public void CopyTo(string[] array, int arrayIndex)
215 {
216 foreach (string source in this)
217 {
218 array[arrayIndex++] = source;
219 }
220 }
221
222 /// <summary>
223 /// Removes a network or URL source.
224 /// </summary>
225 /// <remarks><p>
226 /// Win32 MSI API:
227 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistclearsource.asp">MsiSourceListClearSource</a>
228 /// </p></remarks>
229 public bool Remove(string item)
230 {
231 if (String.IsNullOrEmpty(item))
232 {
233 throw new ArgumentNullException("item");
234 }
235
236 NativeMethods.SourceType type = item.Contains("://") ? NativeMethods.SourceType.Url : NativeMethods.SourceType.Network;
237
238 uint ret = NativeMethods.MsiSourceListClearSource(
239 this.installation.InstallationCode,
240 this.installation.UserSid,
241 this.installation.Context,
242 (uint) type | (uint) this.installation.InstallationType,
243 item);
244 if (ret != 0)
245 {
246 // TODO: Figure out when to return false.
247 throw InstallerException.ExceptionFromReturnCode(ret);
248 }
249 return true;
250 }
251
252 /// <summary>
253 /// Enumerates the network and URL sources in the source list of the patch or product installation.
254 /// </summary>
255 /// <remarks><p>
256 /// Win32 MSI API:
257 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistenumsources.asp">MsiSourceListEnumSources</a>
258 /// </p></remarks>
259 public IEnumerator<string> GetEnumerator()
260 {
261 StringBuilder sourceBuf = new StringBuilder(256);
262 uint sourceBufSize = (uint) sourceBuf.Capacity;
263 for (uint i = 0; true; i++)
264 {
265 uint ret = this.EnumSources(sourceBuf, i, NativeMethods.SourceType.Network);
266 if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS)
267 {
268 break;
269 }
270 else if (ret != 0)
271 {
272 throw InstallerException.ExceptionFromReturnCode(ret);
273 }
274 else
275 {
276 yield return sourceBuf.ToString();
277 }
278 }
279
280 for (uint i = 0; true; i++)
281 {
282 uint ret = this.EnumSources(sourceBuf, i, NativeMethods.SourceType.Url);
283 if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS)
284 {
285 break;
286 }
287 else if (ret != 0)
288 {
289 throw InstallerException.ExceptionFromReturnCode(ret);
290 }
291 else
292 {
293 yield return sourceBuf.ToString();
294 }
295 }
296 }
297
298 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
299 {
300 return this.GetEnumerator();
301 }
302
303 /// <summary>
304 /// Forces the installer to search the source list for a valid
305 /// source the next time a source is required. For example, when the
306 /// installer performs an installation or reinstallation, or when it
307 /// requires the path for a component that is set to run from source.
308 /// </summary>
309 /// <remarks><p>
310 /// Win32 MSI APIs:
311 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistforceresolution.asp">MsiSourceListForceResolution</a>,
312 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistforceresolutionex.asp">MsiSourceListForceResolutionEx</a>
313 /// </p></remarks>
314 public void ForceResolution()
315 {
316 uint ret = NativeMethods.MsiSourceListForceResolutionEx(
317 this.installation.InstallationCode,
318 this.installation.UserSid,
319 this.installation.Context,
320 (uint) this.installation.InstallationType);
321 if (ret != 0)
322 {
323 throw InstallerException.ExceptionFromReturnCode(ret);
324 }
325 }
326
327 /// <summary>
328 /// Gets or sets the path relative to the root of the installation media.
329 /// </summary>
330 public string MediaPackagePath
331 {
332 get
333 {
334 return this["MediaPackagePath"];
335 }
336 set
337 {
338 this["MediaPackagePath"] = value;
339 }
340 }
341
342 /// <summary>
343 /// Gets or sets the prompt template that is used when prompting the user
344 /// for installation media.
345 /// </summary>
346 public string DiskPrompt
347 {
348 get
349 {
350 return this["DiskPrompt"];
351 }
352 set
353 {
354 this["DiskPrompt"] = value;
355 }
356 }
357
358 /// <summary>
359 /// Gets or sets the most recently used source location for the product.
360 /// </summary>
361 public string LastUsedSource
362 {
363 get
364 {
365 return this["LastUsedSource"];
366 }
367 set
368 {
369 this["LastUsedSource"] = value;
370 }
371 }
372
373 /// <summary>
374 /// Gets or sets the name of the Windows Installer package or patch package
375 /// on the source.
376 /// </summary>
377 public string PackageName
378 {
379 get
380 {
381 return this["PackageName"];
382 }
383 set
384 {
385 this["PackageName"] = value;
386 }
387 }
388
389 /// <summary>
390 /// Gets the type of the last-used source.
391 /// </summary>
392 /// <remarks><p>
393 /// <ul>
394 /// <li>&quot;n&quot; = network location</li>
395 /// <li>&quot;u&quot; = URL location</li>
396 /// <li>&quot;m&quot; = media location</li>
397 /// <li>(empty string) = no last used source</li>
398 /// </ul>
399 /// </p></remarks>
400 public string LastUsedType
401 {
402 get
403 {
404 return this["LastUsedType"];
405 }
406 }
407
408 /// <summary>
409 /// Gets or sets source list information properties of a product or patch installation.
410 /// </summary>
411 /// <param name="property">The source list information property name.</param>
412 /// <exception cref="ArgumentOutOfRangeException">An unknown product, patch, or property was requested</exception>
413 /// <remarks><p>
414 /// Win32 MSI API:
415 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistgetinfo.asp">MsiSourceListGetInfo</a>
416 /// </p></remarks>
417 public string this[string property]
418 {
419 get
420 {
421 StringBuilder buf = new StringBuilder("");
422 uint bufSize = 0;
423 uint ret = NativeMethods.MsiSourceListGetInfo(
424 this.installation.InstallationCode,
425 this.installation.UserSid,
426 this.installation.Context,
427 (uint) this.installation.InstallationType,
428 property,
429 buf,
430 ref bufSize);
431 if (ret != 0)
432 {
433 if (ret == (uint) NativeMethods.Error.MORE_DATA)
434 {
435 buf.Capacity = (int) ++bufSize;
436 ret = NativeMethods.MsiSourceListGetInfo(
437 this.installation.InstallationCode,
438 this.installation.UserSid,
439 this.installation.Context,
440 (uint) this.installation.InstallationType,
441 property,
442 buf,
443 ref bufSize);
444 }
445
446 if (ret != 0)
447 {
448 if (ret == (uint) NativeMethods.Error.UNKNOWN_PRODUCT ||
449 ret == (uint) NativeMethods.Error.UNKNOWN_PROPERTY)
450 {
451 throw new ArgumentOutOfRangeException("property");
452 }
453 else
454 {
455 throw InstallerException.ExceptionFromReturnCode(ret);
456 }
457 }
458 }
459 return buf.ToString();
460 }
461 set
462 {
463 uint ret = NativeMethods.MsiSourceListSetInfo(
464 this.installation.InstallationCode,
465 this.installation.UserSid,
466 this.installation.Context,
467 (uint) this.installation.InstallationType,
468 property,
469 value);
470 if (ret != 0)
471 {
472 if (ret == (uint) NativeMethods.Error.UNKNOWN_PRODUCT ||
473 ret == (uint) NativeMethods.Error.UNKNOWN_PROPERTY)
474 {
475 throw new ArgumentOutOfRangeException("property");
476 }
477 else
478 {
479 throw InstallerException.ExceptionFromReturnCode(ret);
480 }
481 }
482 }
483 }
484
485 private void ClearSourceType(NativeMethods.SourceType type)
486 {
487 uint ret = NativeMethods.MsiSourceListClearAllEx(
488 this.installation.InstallationCode,
489 this.installation.UserSid,
490 this.installation.Context,
491 (uint) type | (uint) this.installation.InstallationType);
492 if (ret != 0)
493 {
494 throw InstallerException.ExceptionFromReturnCode(ret);
495 }
496 }
497
498 private uint EnumSources(StringBuilder sourceBuf, uint i, NativeMethods.SourceType sourceType)
499 {
500 int enumType = (this.installation.InstallationType | (int) sourceType);
501 uint sourceBufSize = (uint) sourceBuf.Capacity;
502 uint ret = NativeMethods.MsiSourceListEnumSources(
503 this.installation.InstallationCode,
504 this.installation.UserSid,
505 this.installation.Context,
506 (uint) enumType,
507 i,
508 sourceBuf,
509 ref sourceBufSize);
510 if (ret == (uint) NativeMethods.Error.MORE_DATA)
511 {
512 sourceBuf.Capacity = (int) ++sourceBufSize;
513 ret = NativeMethods.MsiSourceListEnumSources(
514 this.installation.InstallationCode,
515 this.installation.UserSid,
516 this.installation.Context,
517 (uint) enumType,
518 i,
519 sourceBuf,
520 ref sourceBufSize);
521 }
522 return ret;
523 }
524 }
525}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/SourceMediaList.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/SourceMediaList.cs
new file mode 100644
index 00000000..cf7b7ec5
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/SourceMediaList.cs
@@ -0,0 +1,229 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Text;
8 using System.Globalization;
9 using System.Diagnostics.CodeAnalysis;
10
11 /// <summary>
12 /// A list of source media for an installed product or patch.
13 /// </summary>
14 [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
15 public class SourceMediaList : ICollection<MediaDisk>
16 {
17 private Installation installation;
18
19 internal SourceMediaList(Installation installation)
20 {
21 this.installation = installation;
22 }
23
24 /// <summary>
25 /// Gets the number of source media in the list.
26 /// </summary>
27 public int Count
28 {
29 get
30 {
31 int count = 0;
32 IEnumerator<MediaDisk> e = this.GetEnumerator();
33 while (e.MoveNext())
34 {
35 count++;
36 }
37 return count;
38 }
39 }
40
41 /// <summary>
42 /// Gets a boolean value indicating whether the list is read-only.
43 /// A SourceMediaList is never read-only.
44 /// </summary>
45 /// <value>read-only status of the list</value>
46 public bool IsReadOnly
47 {
48 get
49 {
50 return false;
51 }
52 }
53
54 /// <summary>
55 /// Adds or updates a disk of the media source for the product or patch.
56 /// </summary>
57 /// <remarks><p>
58 /// Win32 MSI API:
59 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistaddmediadisk.asp">MsiSourceListAddMediaDisk</a>
60 /// </p></remarks>
61 public void Add(MediaDisk item)
62 {
63 uint ret = NativeMethods.MsiSourceListAddMediaDisk(
64 this.installation.InstallationCode,
65 this.installation.UserSid,
66 this.installation.Context,
67 (uint) this.installation.InstallationType,
68 (uint) item.DiskId,
69 item.VolumeLabel,
70 item.DiskPrompt);
71
72 if (ret != 0)
73 {
74 throw InstallerException.ExceptionFromReturnCode(ret);
75 }
76 }
77
78 /// <summary>
79 /// Removes all source media from the list.
80 /// </summary>
81 /// <remarks><p>
82 /// Win32 MSI API:
83 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistclearallex.asp">MsiSourceListClearAllEx</a>
84 /// </p></remarks>
85 public void Clear()
86 {
87 uint ret = NativeMethods.MsiSourceListClearAllEx(
88 this.installation.InstallationCode,
89 this.installation.UserSid,
90 this.installation.Context,
91 (uint) NativeMethods.SourceType.Media | (uint) this.installation.InstallationType);
92
93 if (ret != 0)
94 {
95 throw InstallerException.ExceptionFromReturnCode(ret);
96 }
97 }
98
99 /// <summary>
100 /// Checks if the specified media disk id exists in the list.
101 /// </summary>
102 /// <param name="diskId">disk id of the media to look for</param>
103 /// <returns>true if the media exists in the list, false otherwise</returns>
104 public bool Contains(int diskId)
105 {
106 foreach (MediaDisk mediaDisk in this)
107 {
108 if (mediaDisk.DiskId == diskId)
109 {
110 return true;
111 }
112 }
113 return false;
114 }
115
116 bool ICollection<MediaDisk>.Contains(MediaDisk mediaDisk)
117 {
118 return this.Contains(mediaDisk.DiskId);
119 }
120
121 /// <summary>
122 /// Copies the source media info from this list into an array.
123 /// </summary>
124 /// <param name="array">destination array to be filed</param>
125 /// <param name="arrayIndex">offset into the destination array where copying begins</param>
126 public void CopyTo(MediaDisk[] array, int arrayIndex)
127 {
128 foreach (MediaDisk mediaDisk in this)
129 {
130 array[arrayIndex++] = mediaDisk;
131 }
132 }
133
134 /// <summary>
135 /// Removes a specified disk from the set of registered disks.
136 /// </summary>
137 /// <param name="diskId">ID of the disk to remove</param>
138 /// <remarks><p>
139 /// Win32 MSI API:
140 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistclearmediadisk.asp">MsiSourceListClearMediaDisk</a>
141 /// </p></remarks>
142 public bool Remove(int diskId)
143 {
144 uint ret = NativeMethods.MsiSourceListClearMediaDisk(
145 this.installation.InstallationCode,
146 this.installation.UserSid,
147 this.installation.Context,
148 (uint) this.installation.InstallationType,
149 (uint) diskId);
150
151 if (ret != 0)
152 {
153 // TODO: Figure out when to return false.
154 throw InstallerException.ExceptionFromReturnCode(ret);
155 }
156 return true;
157 }
158
159 bool ICollection<MediaDisk>.Remove(MediaDisk mediaDisk)
160 {
161 return this.Remove(mediaDisk.DiskId);
162 }
163
164 /// <summary>
165 /// Enumerates the source media in the source list of the patch or product installation.
166 /// </summary>
167 /// <remarks><p>
168 /// Win32 MSI API:
169 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistenummediadisks.asp">MsiSourceListEnumMediaDisks</a>
170 /// </p></remarks>
171 public IEnumerator<MediaDisk> GetEnumerator()
172 {
173 uint diskId;
174 StringBuilder volumeBuf = new StringBuilder(40);
175 uint volumeBufSize = (uint) volumeBuf.Capacity;
176 StringBuilder promptBuf = new StringBuilder(80);
177 uint promptBufSize = (uint) promptBuf.Capacity;
178 for (uint i = 0; true; i++)
179 {
180 uint ret = NativeMethods.MsiSourceListEnumMediaDisks(
181 this.installation.InstallationCode,
182 this.installation.UserSid,
183 this.installation.Context,
184 (uint) this.installation.InstallationType,
185 i,
186 out diskId,
187 volumeBuf,
188 ref volumeBufSize,
189 promptBuf,
190 ref promptBufSize);
191
192 if (ret == (uint) NativeMethods.Error.MORE_DATA)
193 {
194 volumeBuf.Capacity = (int) ++volumeBufSize;
195 promptBuf.Capacity = (int) ++promptBufSize;
196
197 ret = NativeMethods.MsiSourceListEnumMediaDisks(
198 this.installation.InstallationCode,
199 this.installation.UserSid,
200 this.installation.Context,
201 (uint) this.installation.InstallationType,
202 i,
203 out diskId,
204 volumeBuf,
205 ref volumeBufSize,
206 promptBuf,
207 ref promptBufSize);
208 }
209
210 if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS)
211 {
212 break;
213 }
214
215 if (ret != 0)
216 {
217 throw InstallerException.ExceptionFromReturnCode(ret);
218 }
219
220 yield return new MediaDisk((int) diskId, volumeBuf.ToString(), promptBuf.ToString());
221 }
222 }
223
224 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
225 {
226 return this.GetEnumerator();
227 }
228 }
229}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/SummaryInfo.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/SummaryInfo.cs
new file mode 100644
index 00000000..4dbff93f
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/SummaryInfo.cs
@@ -0,0 +1,612 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Globalization;
9 using System.Runtime.InteropServices;
10
11 /// <summary>
12 /// Provides access to summary information of a Windows Installer database.
13 /// </summary>
14 public class SummaryInfo : InstallerHandle
15 {
16 internal const int MAX_PROPERTIES = 20;
17
18 /// <summary>
19 /// Gets a SummaryInfo object that can be used to examine, update, and add
20 /// properties to the summary information stream of a package or transform.
21 /// </summary>
22 /// <param name="packagePath">Path to the package (database) or transform</param>
23 /// <param name="enableWrite">True to reserve resources for writing summary information properties.</param>
24 /// <exception cref="FileNotFoundException">the package does not exist or could not be read</exception>
25 /// <exception cref="InstallerException">the package is an invalid format</exception>
26 /// <remarks><p>
27 /// The SummaryInfo object should be <see cref="InstallerHandle.Close"/>d after use.
28 /// It is best that the handle be closed manually as soon as it is no longer
29 /// needed, as leaving lots of unused handles open can degrade performance.
30 /// </p><p>
31 /// Win32 MSI API:
32 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetsummaryinformation.asp">MsiGetSummaryInformation</a>
33 /// </p></remarks>
34 public SummaryInfo(string packagePath, bool enableWrite)
35 : base((IntPtr) SummaryInfo.OpenSummaryInfo(packagePath, enableWrite), true)
36 {
37 }
38
39 internal SummaryInfo(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle)
40 {
41 }
42
43 /// <summary>Gets or sets the Title summary information property.</summary>
44 /// <remarks><p>
45 /// The Title summary information property briefly describes the type of installer package. Phrases
46 /// such as "Installation Database" or "Transform" or "Patch" may be used for this property.
47 /// </p><p>
48 /// Win32 MSI APIs:
49 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
50 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
51 /// </p></remarks>
52 public string Title
53 {
54 get { return this[2]; }
55 set { this[2] = value; }
56 }
57
58 /// <summary>Gets or sets the Subject summary information property.</summary>
59 /// <remarks><p>
60 /// The Subject summary information property conveys to a file browser the product that can be installed using
61 /// the logic and data in this installer database. For example, the value of the summary property for
62 /// Microsoft Office 97 would be "Microsoft Office 97 Professional". This value is typically set from the
63 /// installer property ProductName.
64 /// </p><p>
65 /// Win32 MSI APIs:
66 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
67 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
68 /// </p></remarks>
69 public string Subject
70 {
71 get { return this[3]; }
72 set { this[3] = value; }
73 }
74
75 /// <summary>Gets or sets the Author summary information property.</summary>
76 /// <remarks><p>
77 /// The Author summary information property conveys to a file browser the manufacturer of the installation
78 /// database. This value is typically set from the installer property Manufacturer.
79 /// </p><p>
80 /// Win32 MSI APIs:
81 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
82 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
83 /// </p></remarks>
84 public string Author
85 {
86 get { return this[4]; }
87 set { this[4] = value; }
88 }
89
90 /// <summary>Gets or sets the Keywords summary information property.</summary>
91 /// <remarks><p>
92 /// The Keywords summary information property is used by file browsers to hold keywords that permit the
93 /// database file to be found in a keyword search. The set of keywords typically includes "Installer" as
94 /// well as product-specific keywords, and may be localized.
95 /// </p><p>
96 /// Win32 MSI APIs:
97 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
98 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
99 /// </p></remarks>
100 public string Keywords
101 {
102 get { return this[5]; }
103 set { this[5] = value; }
104 }
105
106 /// <summary>Gets or sets the Comments summary information property.</summary>
107 /// <remarks><p>
108 /// The Comments summary information property conveys the general purpose of the installer database. By convention,
109 /// the value for this summary property is set to the following:
110 /// </p><p>
111 /// "This installer database contains the logic and data required to install &lt;product name&gt;."
112 /// </p><p>
113 /// where &lt;product name&gt; is the name of the product being installed. In general the value for this summary
114 /// property only changes in the product name, nothing else.
115 /// </p><p>
116 /// Win32 MSI APIs:
117 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
118 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
119 /// </p></remarks>
120 public string Comments
121 {
122 get { return this[6]; }
123 set { this[6] = value; }
124 }
125
126 /// <summary>Gets or sets the Template summary information property.</summary>
127 /// <remarks><p>
128 /// The Template summary information propery indicates the platform and language versions supported by the database.
129 /// </p><p>
130 /// The syntax of the Template Summary property information is:
131 /// [platform property][,platform property][,...];[language id][,language id][,...]
132 /// </p><p>
133 /// For example, the following are all valid values for the Template Summary property:
134 /// <list type="bullet">
135 /// <item>Intel;1033</item>
136 /// <item>Intel64;1033</item>
137 /// <item>;1033</item>
138 /// <item>;</item>
139 /// <item>Intel ;1033,2046</item>
140 /// <item>Intel64;1033,2046</item>
141 /// <item>Intel;0</item>
142 /// </list>
143 /// </p><p>
144 /// If this is a 64-bit Windows Installer, enter Intel64 in the Template summary information property. Note that an
145 /// installation package cannot have both the Intel and Intel64 properties set.
146 /// </p><p>
147 /// If the current platform does not match one of the platforms specified then the installer will not process the
148 /// package. Not specifying a platform implies that the package is platform-independent.
149 /// </p><p>
150 /// Entering 0 in the language ID field of the Template summary information property, or leaving this field empty,
151 /// indicates that the package is language neutral.
152 /// </p><p>
153 /// There are variations of this property depending on whether it is in a source installer database or a transform.
154 /// </p><p>
155 /// Source Installer Database - Only one language can be specified in a source installer database. Merge Modules are
156 /// the only packages that may have multiple languages. For more information, see Multiple Language Merge Modules.
157 /// </p><p>
158 /// Transform - In a transform file, only one language may be specified. The specified platform and language determine
159 /// whether a transform can be applied to a particular database. The platform property and the language property can
160 /// be left blank if no transform restriction relies on them to validate the transform.
161 /// </p><p>
162 /// This summary property is REQUIRED.
163 /// </p><p>
164 /// Win32 MSI APIs:
165 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
166 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
167 /// </p></remarks>
168 public string Template
169 {
170 get { return this[7]; }
171 set { this[7] = value; }
172 }
173
174 /// <summary>Gets or sets the LastSavedBy summary information property.</summary>
175 /// <remarks><p>
176 /// The installer sets the Last Saved By summary information property to the value of the LogonUser property during
177 /// an administrative installation. The installer never uses this property and a user never needs to modify it.
178 /// Developers of a database editing tool may use this property to track the last person to modify the database.
179 /// This property should be left set to null in a final shipping database.
180 /// </p><p>
181 /// In a transform, this summary property contains the platform and language ID(s) that a database should have
182 /// after it has been transformed. The property specifies to what the Template should be set in the new database.
183 /// </p><p>
184 /// Win32 MSI APIs:
185 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
186 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
187 /// </p></remarks>
188 public string LastSavedBy
189 {
190 get { return this[8]; }
191 set { this[8] = value; }
192 }
193
194 /// <summary>Gets or sets the RevisionNumber summary information property.</summary>
195 /// <remarks><p>
196 /// The Revision Number summary information property contains the package code for the installer package. The
197 /// package code is a unique identifier of the installer package.
198 /// </p><p>
199 /// The Revision Number summary information property of a patch package specifies the GUID patch code for
200 /// the patch. This is followed by a list of patch code GUIDs for obsolete patches that are removed when this
201 /// patch is applied. The patch codes are concatenated with no delimiters separating GUIDs in the list.
202 /// </p><p>
203 /// The Revision Number summary information property of a transform package lists the product code GUIDs
204 /// and version of the new and original products and the upgrade code GUID. The list is separated with
205 /// semicolons as follows.
206 /// </p><p>
207 /// Original-Product-Code Original-Product-Version ; New-Product Code New-Product-Version; Upgrade-Code
208 /// </p><p>
209 /// This summary property is REQUIRED.
210 /// </p><p>
211 /// Win32 MSI APIs:
212 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
213 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
214 /// </p></remarks>
215 public string RevisionNumber
216 {
217 get { return this[9]; }
218 set { this[9] = value; }
219 }
220
221 /// <summary>Gets or sets the CreatingApp summary information property.</summary>
222 /// <remarks><p>
223 /// The CreatingApp summary information property conveys which application created the installer database.
224 /// In general the value for this summary property is the name of the software used to author this database.
225 /// </p><p>
226 /// Win32 MSI APIs:
227 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
228 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
229 /// </p></remarks>
230 public string CreatingApp
231 {
232 get { return this[18]; }
233 set { this[18] = value; }
234 }
235
236 /// <summary>Gets or sets the LastPrintTime summary information property.</summary>
237 /// <remarks><p>
238 /// The LastPrintTime summary information property can be set to the date and time during an administrative
239 /// installation to record when the administrative image was created. For non-administrative installations
240 /// this property is the same as the CreateTime summary information property.
241 /// </p><p>
242 /// Win32 MSI APIs:
243 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
244 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
245 /// </p></remarks>
246 public DateTime LastPrintTime
247 {
248 get { return (DateTime) this[11, typeof(DateTime)]; }
249 set { this[11, typeof(DateTime)] = value; }
250 }
251
252 /// <summary>Gets or sets the CreateTime summary information property.</summary>
253 /// <remarks><p>
254 /// The CreateTime summary information property conveys when the installer database was created.
255 /// </p><p>
256 /// Win32 MSI APIs:
257 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
258 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
259 /// </p></remarks>
260 public DateTime CreateTime
261 {
262 get { return (DateTime) this[12, typeof(DateTime)]; }
263 set { this[12, typeof(DateTime)] = value; }
264 }
265
266 /// <summary>Gets or sets the LastSaveTime summary information property.</summary>
267 /// <remarks><p>
268 /// The LastSaveTime summary information property conveys when the last time the installer database was
269 /// modified. Each time a user changes an installation the value for this summary property is updated to
270 /// the current system time/date at the time the installer database was saved. Initially the value for
271 /// this summary property is set to null to indicate that no changes have yet been made.
272 /// </p><p>
273 /// Win32 MSI APIs:
274 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
275 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
276 /// </p></remarks>
277 public DateTime LastSaveTime
278 {
279 get { return (DateTime) this[13, typeof(DateTime)]; }
280 set { this[13, typeof(DateTime)] = value; }
281 }
282
283 /// <summary>Gets or sets the CodePage summary information property.</summary>
284 /// <remarks><p>
285 /// The Codepage summary information property is the numeric value of the ANSI code page used for any
286 /// strings that are stored in the summary information. Note that this is not the same code page for
287 /// strings in the installation database. The Codepage summary information property is used to translate
288 /// the strings in the summary information into Unicode when calling the Unicode API functions. The
289 /// Codepage summary information property must be set before any string properties are set in the
290 /// summary information.
291 /// </p><p>
292 /// Win32 MSI APIs:
293 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
294 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
295 /// </p></remarks>
296 public short CodePage
297 {
298 get { return (short) this[1, typeof(short)]; }
299 set { this[1, typeof(short)] = value; }
300 }
301
302 /// <summary>Gets or sets the PageCount summary information property.</summary>
303 /// <remarks><p>
304 /// For an installation package, the PageCount summary information property contains the minimum
305 /// installer version required. For Windows Installer version 1.0, this property must be set to the
306 /// integer 100. For 64-bit Windows Installer Packages, this property must be set to the integer 200.
307 /// </p><p>
308 /// For a transform package, the PageCount summary information property contains minimum installer
309 /// version required to process the transform. Set to the greater of the two PageCount summary information
310 /// property values belonging to the databases used to generate the transform.
311 /// </p><p>
312 /// The PageCount summary information property is set to null in patch packages.
313 /// </p><p>
314 /// This summary property is REQUIRED.
315 /// </p><p>
316 /// Win32 MSI APIs:
317 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
318 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
319 /// </p></remarks>
320 public int PageCount
321 {
322 get { return (int) this[14, typeof(int)]; }
323 set { this[14, typeof(int)] = value; }
324 }
325
326 /// <summary>Gets or sets the WordCount summary information property.</summary>
327 /// <remarks><p>
328 /// The WordCount summary information property indicates the type of source file image. If this property is
329 /// not present, it defaults to 0. Note that this property is stored in place of the standard Count property.
330 /// </p><p>
331 /// This property is a bit field. New bits may be added in the future. At present the following bits are
332 /// available:
333 /// <list type="bullet">
334 /// <item>Bit 0: 0 = long file names, 1 = short file names</item>
335 /// <item>Bit 1: 0 = source is uncompressed, 1 = source is compressed</item>
336 /// <item>Bit 2: 0 = source is original media, 1 = source is administrative installation</item>
337 /// <item>[MSI 4.0] Bit 3: 0 = elevated privileges can be required to install, 1 = elevated privileges are not required to install</item>
338 /// </list>
339 /// </p><p>
340 /// These are combined to give the WordCount summary information property one of the following values
341 /// indicating a type of source file image:
342 /// <list type="bullet">
343 /// <item>0 - Original source using long file names. Matches tree in Directory table.</item>
344 /// <item>1 - Original source using short file names. Matches tree in Directory table.</item>
345 /// <item>2 - Compressed source files using long file names. Matches cabinets and files in the Media table.</item>
346 /// <item>3 - Compressed source files using short file names. Matches cabinets and files in the Media table.</item>
347 /// <item>4 - Administrative image using long file names. Matches tree in Directory table.</item>
348 /// <item>5 - Administrative image using short file names. Matches tree in Directory table.</item>
349 /// </list>
350 /// </p><p>
351 /// Note that if the package is marked as compressed (bit 1 is set), the installer only installs files
352 /// located at the root of the source. In this case, even files marked as uncompressed in the File table must
353 /// be located at the root to be installed. To specify a source image that has both a cabinet file (compressed
354 /// files) and uncompressed files that match the tree in the Directory table, mark the package as uncompressed
355 /// by leaving bit 1 unset (value=0) in the WordCount summary information property and set
356 /// <see cref="FileAttributes.Compressed"/> (value=16384) in the Attributes column of the File table
357 /// for each file in the cabinet.
358 /// </p><p>
359 /// For a patch package, the WordCount summary information property specifies the patch engine that was used
360 /// to create the patch files. The default value is 1 and indicates that MSPATCH was used to create the patch
361 /// A value of "2" means that the patch is using smaller, optimized, files available only with Windows Installer
362 /// version 1.2 or later. A patch with a WordCount of "2" fails immediately if used with a Windows Installer
363 /// version earlier than 1.2. A patch with a WordCount of "3" fails immediately if used with a Windows Installer
364 /// version earlier than 2.0.
365 /// </p><p>
366 /// This summary property is REQUIRED.
367 /// </p><p>
368 /// Win32 MSI APIs:
369 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
370 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
371 /// </p></remarks>
372 public int WordCount
373 {
374 get { return (int) this[15, typeof(int)]; }
375 set { this[15, typeof(int)] = value; }
376 }
377
378 /// <summary>Gets or sets the CharacterCount summary information property.</summary>
379 /// <remarks><p>
380 /// The CharacterCount summary information property is only used in transforms. This part of the summary
381 /// information stream is divided into two 16-bit words. The upper word contains the transform validation
382 /// flags. The lower word contains the transform error condition flags.
383 /// </p><p>
384 /// Win32 MSI APIs:
385 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
386 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
387 /// </p></remarks>
388 public int CharacterCount
389 {
390 get { return (int) this[16, typeof(int)]; }
391 set { this[16, typeof(int)] = value; }
392 }
393
394 /// <summary>Gets or sets the Security summary information property.</summary>
395 /// <remarks><p>
396 /// The Security summary information property conveys whether the package should be opened as read-only. The database
397 /// editing tool should not modify a read-only enforced database and should issue a warning at attempts to modify a
398 /// read-only recommended database. The following values of this property are applicable to Windows Installer files:
399 /// <list type="bullet">
400 /// <item>0 - no restriction</item>
401 /// <item>2 - read only recommended</item>
402 /// <item>4 - read only enforced</item>
403 /// </list>
404 /// </p><p>
405 /// This property should be set to read-only recommended (2) for an installation database and to read-only
406 /// enforced (4) for a transform or patch.
407 /// </p><p>
408 /// Win32 MSI APIs:
409 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
410 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
411 /// </p></remarks>
412 public int Security
413 {
414 get { return (int) this[19, typeof(int)]; }
415 set { this[19, typeof(int)] = value; }
416 }
417
418 private object this[uint property, Type type]
419 {
420 get
421 {
422 uint dataType;
423 StringBuilder stringValue = new StringBuilder("");
424 uint bufSize = 0;
425 int intValue;
426 long timeValue = 0;
427
428 uint ret = RemotableNativeMethods.MsiSummaryInfoGetProperty(
429 (int) this.Handle,
430 property,
431 out dataType,
432 out intValue,
433 ref timeValue,
434 stringValue,
435 ref bufSize);
436 if (ret != 0 && dataType != (uint) VarEnum.VT_LPSTR)
437 {
438 throw InstallerException.ExceptionFromReturnCode(ret);
439 }
440
441 switch ((VarEnum) dataType)
442 {
443 case VarEnum.VT_EMPTY:
444 {
445 if (type == typeof(DateTime))
446 {
447 return DateTime.MinValue;
448 }
449 else if (type == typeof(string))
450 {
451 return String.Empty;
452 }
453 else if (type == typeof(short))
454 {
455 return (short) 0;
456 }
457 else
458 {
459 return (int) 0;
460 }
461 }
462
463 case VarEnum.VT_LPSTR:
464 {
465 if (ret == (uint) NativeMethods.Error.MORE_DATA)
466 {
467 stringValue.Capacity = (int) ++bufSize;
468 ret = RemotableNativeMethods.MsiSummaryInfoGetProperty(
469 (int) this.Handle,
470 property,
471 out dataType,
472 out intValue,
473 ref timeValue,
474 stringValue,
475 ref bufSize);
476 }
477 if (ret != 0)
478 {
479 throw InstallerException.ExceptionFromReturnCode(ret);
480 }
481 return stringValue.ToString();
482 }
483
484 case VarEnum.VT_I2:
485 case VarEnum.VT_I4:
486 {
487 if (type == typeof(string))
488 {
489 return intValue.ToString(CultureInfo.InvariantCulture);
490 }
491 else if (type == typeof(short))
492 {
493 return (short) intValue;
494 }
495 else
496 {
497 return intValue;
498 }
499 }
500
501 case VarEnum.VT_FILETIME:
502 {
503 if (type == typeof(string))
504 {
505 return DateTime.FromFileTime(timeValue).ToString(CultureInfo.InvariantCulture);
506 }
507 else
508 {
509 return DateTime.FromFileTime(timeValue);
510 }
511 }
512
513 default:
514 {
515 throw new InstallerException();
516 }
517 }
518 }
519
520 set
521 {
522 uint dataType = (uint) VarEnum.VT_NULL;
523 string stringValue = "";
524 int intValue = 0;
525 long timeValue = 0;
526
527 if (type == typeof(short))
528 {
529 dataType = (uint) VarEnum.VT_I2;
530 intValue = (int)(short) value; // Double cast because value is a *boxed* short.
531 }
532 else if (type == typeof(int))
533 {
534 dataType = (uint) VarEnum.VT_I4;
535 intValue = (int) value;
536 }
537 else if (type == typeof(string))
538 {
539 dataType = (uint) VarEnum.VT_LPSTR;
540 stringValue = (string) value;
541 }
542 else // (type == typeof(DateTime))
543 {
544 dataType = (uint) VarEnum.VT_FILETIME;
545 timeValue = ((DateTime) value).ToFileTime();
546 }
547
548 uint ret = NativeMethods.MsiSummaryInfoSetProperty(
549 (int) this.Handle,
550 property,
551 dataType,
552 intValue,
553 ref timeValue,
554 stringValue);
555 if (ret != 0)
556 {
557 throw InstallerException.ExceptionFromReturnCode(ret);
558 }
559 }
560 }
561
562 private string this[uint property]
563 {
564 get { return (string) this[property, typeof(string)]; }
565 set { this[property, typeof(string)] = value; }
566 }
567
568 /// <summary>
569 /// Formats and writes the previously stored properties into the standard summary information stream.
570 /// </summary>
571 /// <exception cref="InstallerException">The stream cannot be successfully written.</exception>
572 /// <remarks><p>
573 /// This method may only be called once after all the property values have been set. Properties may
574 /// still be read after the stream is written.
575 /// </p><p>
576 /// Win32 MSI API:
577 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfopersist.asp">MsiSummaryInfoPersist</a>
578 /// </p></remarks>
579 public void Persist()
580 {
581 uint ret = NativeMethods.MsiSummaryInfoPersist((int) this.Handle);
582 if (ret != 0)
583 {
584 throw InstallerException.ExceptionFromReturnCode(ret);
585 }
586 }
587
588 private static int OpenSummaryInfo(string packagePath, bool enableWrite)
589 {
590 int summaryInfoHandle;
591 int maxProperties = !enableWrite ? 0 : SummaryInfo.MAX_PROPERTIES;
592 uint ret = RemotableNativeMethods.MsiGetSummaryInformation(
593 0,
594 packagePath,
595 (uint) maxProperties,
596 out summaryInfoHandle);
597 if (ret != 0)
598 {
599 if (ret == (uint) NativeMethods.Error.FILE_NOT_FOUND ||
600 ret == (uint) NativeMethods.Error.ACCESS_DENIED)
601 {
602 throw new FileNotFoundException(null, packagePath);
603 }
604 else
605 {
606 throw InstallerException.ExceptionFromReturnCode(ret);
607 }
608 }
609 return summaryInfoHandle;
610 }
611 }
612}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/TableCollection.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/TableCollection.cs
new file mode 100644
index 00000000..95176ea0
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/TableCollection.cs
@@ -0,0 +1,192 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Text;
8
9 /// <summary>
10 /// Contains information about all the tables in a Windows Installer database.
11 /// </summary>
12 public class TableCollection : ICollection<TableInfo>
13 {
14 private Database db;
15
16 internal TableCollection(Database db)
17 {
18 this.db = db;
19 }
20
21 /// <summary>
22 /// Gets the number of tables in the database.
23 /// </summary>
24 public int Count
25 {
26 get
27 {
28 return this.GetTables().Count;
29 }
30 }
31
32 /// <summary>
33 /// Gets a boolean value indicating whether the collection is read-only.
34 /// A TableCollection is read-only when the database is read-only.
35 /// </summary>
36 /// <value>read-only status of the collection</value>
37 public bool IsReadOnly
38 {
39 get
40 {
41 return this.db.IsReadOnly;
42 }
43 }
44
45 /// <summary>
46 /// Gets information about a given table.
47 /// </summary>
48 /// <param name="table">case-sensitive name of the table</param>
49 /// <returns>information about the requested table, or null if the table does not exist in the database</returns>
50 public TableInfo this[string table]
51 {
52 get
53 {
54 if (String.IsNullOrEmpty(table))
55 {
56 throw new ArgumentNullException("table");
57 }
58
59 if (!this.Contains(table))
60 {
61 return null;
62 }
63
64 return new TableInfo(this.db, table);
65 }
66 }
67
68 /// <summary>
69 /// Adds a new table to the database.
70 /// </summary>
71 /// <param name="item">information about the table to be added</param>
72 /// <exception cref="InvalidOperationException">a table with the same name already exists in the database</exception>
73 public void Add(TableInfo item)
74 {
75 if (item == null)
76 {
77 throw new ArgumentNullException("item");
78 }
79
80 if (this.Contains(item.Name))
81 {
82 throw new InvalidOperationException();
83 }
84
85 this.db.Execute(item.SqlCreateString);
86 }
87
88 /// <summary>
89 /// Removes all tables (and all data) from the database.
90 /// </summary>
91 public void Clear()
92 {
93 foreach (string table in this.GetTables())
94 {
95 this.Remove(table);
96 }
97 }
98
99 /// <summary>
100 /// Checks if the database contains a table with the given name.
101 /// </summary>
102 /// <param name="item">case-sensitive name of the table to search for</param>
103 /// <returns>True if the table exists, false otherwise.</returns>
104 public bool Contains(string item)
105 {
106 if (String.IsNullOrEmpty(item))
107 {
108 throw new ArgumentNullException("item");
109 }
110 uint ret = RemotableNativeMethods.MsiDatabaseIsTablePersistent((int) this.db.Handle, item);
111 if (ret == 3) // MSICONDITION_ERROR
112 {
113 throw new InstallerException();
114 }
115 return ret != 2; // MSICONDITION_NONE
116 }
117
118 bool ICollection<TableInfo>.Contains(TableInfo item)
119 {
120 return this.Contains(item.Name);
121 }
122
123 /// <summary>
124 /// Copies the table information from this collection into an array.
125 /// </summary>
126 /// <param name="array">destination array to be filed</param>
127 /// <param name="arrayIndex">offset into the destination array where copying begins</param>
128 public void CopyTo(TableInfo[] array, int arrayIndex)
129 {
130 if (array == null)
131 {
132 throw new ArgumentNullException("array");
133 }
134
135 foreach (string table in this.GetTables())
136 {
137 array[arrayIndex++] = new TableInfo(this.db, table);
138 }
139 }
140
141 /// <summary>
142 /// Removes a table from the database.
143 /// </summary>
144 /// <param name="item">case-sensitive name of the table to be removed</param>
145 /// <returns>true if the table was removed, false if the table did not exist</returns>
146 public bool Remove(string item)
147 {
148 if (String.IsNullOrEmpty(item))
149 {
150 throw new ArgumentNullException("item");
151 }
152
153 if (!this.Contains(item))
154 {
155 return false;
156 }
157 this.db.Execute("DROP TABLE `{0}`", item);
158 return true;
159 }
160
161 bool ICollection<TableInfo>.Remove(TableInfo item)
162 {
163 if (item == null)
164 {
165 throw new ArgumentNullException("item");
166 }
167
168 return this.Remove(item.Name);
169 }
170
171 /// <summary>
172 /// Enumerates the tables in the database.
173 /// </summary>
174 public IEnumerator<TableInfo> GetEnumerator()
175 {
176 foreach (string table in this.GetTables())
177 {
178 yield return new TableInfo(this.db, table);
179 }
180 }
181
182 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
183 {
184 return this.GetEnumerator();
185 }
186
187 private IList<string> GetTables()
188 {
189 return this.db.ExecuteStringQuery("SELECT `Name` FROM `_Tables`");
190 }
191 }
192}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/TableInfo.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/TableInfo.cs
new file mode 100644
index 00000000..e5a850b0
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/TableInfo.cs
@@ -0,0 +1,264 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Text;
7 using System.Collections.Generic;
8 using System.Collections.ObjectModel;
9
10 /// <summary>
11 /// Defines a table in an installation database.
12 /// </summary>
13 public class TableInfo
14 {
15 private string name;
16 private ColumnCollection columns;
17 private ReadOnlyCollection<string> primaryKeys;
18
19 /// <summary>
20 /// Creates a table definition.
21 /// </summary>
22 /// <param name="name">Name of the table.</param>
23 /// <param name="columns">Columns in the table.</param>
24 /// <param name="primaryKeys">The primary keys of the table.</param>
25 public TableInfo(string name, ICollection<ColumnInfo> columns, IList<string> primaryKeys)
26 {
27 if (String.IsNullOrEmpty(name))
28 {
29 throw new ArgumentNullException("name");
30 }
31
32 if (columns == null || columns.Count == 0)
33 {
34 throw new ArgumentNullException("columns");
35 }
36
37 if (primaryKeys == null || primaryKeys.Count == 0)
38 {
39 throw new ArgumentNullException("primaryKeys");
40 }
41
42 this.name = name;
43 this.columns = new ColumnCollection(columns);
44 this.primaryKeys = new List<string>(primaryKeys).AsReadOnly();
45 foreach (string primaryKey in this.primaryKeys)
46 {
47 if (!this.columns.Contains(primaryKey))
48 {
49 throw new ArgumentOutOfRangeException("primaryKeys");
50 }
51 }
52 }
53
54 internal TableInfo(Database db, string name)
55 {
56 if (db == null)
57 {
58 throw new ArgumentNullException("db");
59 }
60
61 if (String.IsNullOrEmpty(name))
62 {
63 throw new ArgumentNullException("name");
64 }
65
66 this.name = name;
67
68 using (View columnsView = db.OpenView("SELECT * FROM `{0}`", name))
69 {
70 this.columns = new ColumnCollection(columnsView);
71 }
72
73 this.primaryKeys = new ReadOnlyCollection<string>(
74 TableInfo.GetTablePrimaryKeys(db, name));
75 }
76
77 /// <summary>
78 /// Gets the name of the table.
79 /// </summary>
80 public string Name
81 {
82 get
83 {
84 return this.name;
85 }
86 }
87
88 /// <summary>
89 /// Gets information about the columns in this table.
90 /// </summary>
91 /// <remarks><p>
92 /// This property queries the database every time it is called,
93 /// to ensure the returned values are up-to-date. For best performance,
94 /// hold onto the returned collection if using it more than once.
95 /// </p></remarks>
96 public ColumnCollection Columns
97 {
98 get
99 {
100 return this.columns;
101 }
102 }
103
104 /// <summary>
105 /// Gets the names of the columns that are primary keys of the table.
106 /// </summary>
107 public IList<string> PrimaryKeys
108 {
109 get
110 {
111 return this.primaryKeys;
112 }
113 }
114
115 /// <summary>
116 /// Gets an SQL CREATE string that can be used to create the table.
117 /// </summary>
118 public string SqlCreateString
119 {
120 get
121 {
122 StringBuilder s = new StringBuilder("CREATE TABLE `");
123 s.Append(this.name);
124 s.Append("` (");
125 int count = 0;
126 foreach (ColumnInfo col in this.Columns)
127 {
128 if (count > 0)
129 {
130 s.Append(", ");
131 }
132 s.Append(col.SqlCreateString);
133 count++;
134 }
135 s.Append(" PRIMARY KEY ");
136 count = 0;
137 foreach (string key in this.PrimaryKeys)
138 {
139 if (count > 0)
140 {
141 s.Append(", ");
142 }
143 s.AppendFormat("`{0}`", key);
144 count++;
145 }
146 s.Append(')');
147 return s.ToString();
148 }
149 }
150
151 /// <summary>
152 /// Gets an SQL INSERT string that can be used insert a new record into the table.
153 /// </summary>
154 /// <remarks><p>
155 /// The values are expressed as question-mark tokens, to be supplied by the record.
156 /// </p></remarks>
157 public string SqlInsertString
158 {
159 get
160 {
161 StringBuilder s = new StringBuilder("INSERT INTO `");
162 s.Append(this.name);
163 s.Append("` (");
164 int count = 0;
165 foreach (ColumnInfo col in this.Columns)
166 {
167 if (count > 0)
168 {
169 s.Append(", ");
170 }
171
172 s.AppendFormat("`{0}`", col.Name);
173 count++;
174 }
175 s.Append(") VALUES (");
176 while (count > 0)
177 {
178 count--;
179 s.Append("?");
180
181 if (count > 0)
182 {
183 s.Append(", ");
184 }
185 }
186 s.Append(')');
187 return s.ToString();
188 }
189 }
190
191 /// <summary>
192 /// Gets an SQL SELECT string that can be used to select all columns of the table.
193 /// </summary>
194 /// <remarks><p>
195 /// The columns are listed explicitly in the SELECT string, as opposed to using "SELECT *".
196 /// </p></remarks>
197 public string SqlSelectString
198 {
199 get
200 {
201 StringBuilder s = new StringBuilder("SELECT ");
202 int count = 0;
203 foreach (ColumnInfo col in this.Columns)
204 {
205 if (count > 0) s.Append(", ");
206 s.AppendFormat("`{0}`", col.Name);
207 count++;
208 }
209 s.AppendFormat(" FROM `{0}`", this.Name);
210 return s.ToString();
211 }
212 }
213
214 /// <summary>
215 /// Gets a string representation of the table.
216 /// </summary>
217 /// <returns>The name of the table.</returns>
218 public override string ToString()
219 {
220 return this.name;
221 }
222
223 private static IList<string> GetTablePrimaryKeys(Database db, string table)
224 {
225 if (table == "_Tables")
226 {
227 return new string[] { "Name" };
228 }
229 else if (table == "_Columns")
230 {
231 return new string[] { "Table", "Number" };
232 }
233 else if (table == "_Storages")
234 {
235 return new string[] { "Name" };
236 }
237 else if (table == "_Streams")
238 {
239 return new string[] { "Name" };
240 }
241 else
242 {
243 int hrec;
244 uint ret = RemotableNativeMethods.MsiDatabaseGetPrimaryKeys(
245 (int) db.Handle, table, out hrec);
246 if (ret != 0)
247 {
248 throw InstallerException.ExceptionFromReturnCode(ret);
249 }
250
251 using (Record rec = new Record((IntPtr) hrec, true, null))
252 {
253 string[] keys = new string[rec.FieldCount];
254 for (int i = 0; i < keys.Length; i++)
255 {
256 keys[i] = rec.GetString(i + 1);
257 }
258
259 return keys;
260 }
261 }
262 }
263 }
264}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Transaction.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Transaction.cs
new file mode 100644
index 00000000..798dc79e
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Transaction.cs
@@ -0,0 +1,201 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics.CodeAnalysis;
8 using System.Runtime.InteropServices;
9 using System.Threading;
10
11 /// <summary>
12 /// [MSI 4.5] Handle to a multi-session install transaction.
13 /// </summary>
14 /// <remarks><p>
15 /// Win32 MSI APIs:
16 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msibegintransaction.asp">MsiBeginTransaction</a>
17 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msijointransaction.asp">MsiJoinTransaction</a>
18 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiendtransaction.asp">MsiEndTransaction</a>
19 /// </p></remarks>
20 public class Transaction : InstallerHandle
21 {
22 private string name;
23 private IntPtr ownerChangeEvent;
24 private IList<EventHandler<EventArgs>> ownerChangeListeners;
25
26 /// <summary>
27 /// [MSI 4.5] Begins transaction processing of a multi-package installation.
28 /// </summary>
29 /// <param name="name">Name of the multi-package installation.</param>
30 /// <param name="attributes">Select optional behavior when beginning the transaction.</param>
31 /// <exception cref="InstallerException">The transaction could not be initialized.</exception>
32 /// <remarks><p>
33 /// Win32 MSI API:
34 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msibegintransaction.asp">MsiBeginTransaction</a>
35 /// </p></remarks>
36 public Transaction(string name, TransactionAttributes attributes)
37 : this(name, Transaction.Begin(name, attributes), true)
38 {
39 }
40
41 /// <summary>
42 /// Internal constructor.
43 /// </summary>
44 /// <remarks>
45 /// The second parameter is an array in order to receive multiple values from the initialization method.
46 /// </remarks>
47 private Transaction(string name, IntPtr[] handles, bool ownsHandle)
48 : base(handles[0], ownsHandle)
49 {
50 this.name = name;
51 this.ownerChangeEvent = handles[1];
52 this.ownerChangeListeners = new List<EventHandler<EventArgs>>();
53 }
54
55 /// <summary>
56 /// Creates a new Transaction object from an integer handle.
57 /// </summary>
58 /// <param name="handle">Integer transaction handle</param>
59 /// <param name="ownsHandle">true to close the handle when this object is disposed</param>
60 public static Transaction FromHandle(IntPtr handle, bool ownsHandle)
61 {
62 return new Transaction(handle.ToString(), new IntPtr[] { handle, IntPtr.Zero }, ownsHandle);
63 }
64
65 /// <summary>
66 /// Gets the name of the transaction.
67 /// </summary>
68 public string Name
69 {
70 get
71 {
72 return name;
73 }
74 }
75
76 /// <summary>
77 /// Notifies listeners when the process that owns the transaction changed.
78 /// </summary>
79 public event EventHandler<EventArgs> OwnerChanged
80 {
81 add
82 {
83 this.ownerChangeListeners.Add(value);
84
85 if (this.ownerChangeEvent != IntPtr.Zero && this.ownerChangeListeners.Count == 1)
86 {
87 new Thread(this.WaitForOwnerChange).Start();
88 }
89 }
90 remove
91 {
92 this.ownerChangeListeners.Remove(value);
93 }
94 }
95
96 private void OnOwnerChanged()
97 {
98 EventArgs e = new EventArgs();
99 foreach (EventHandler<EventArgs> handler in this.ownerChangeListeners)
100 {
101 handler(this, e);
102 }
103 }
104
105 private void WaitForOwnerChange()
106 {
107 int ret = NativeMethods.WaitForSingleObject(this.ownerChangeEvent, -1);
108 if (ret == 0)
109 {
110 this.OnOwnerChanged();
111 }
112 else
113 {
114 throw new InstallerException();
115 }
116 }
117
118 /// <summary>
119 /// Makes the current process the owner of the multi-package installation transaction.
120 /// </summary>
121 /// <param name="attributes">Select optional behavior when joining the transaction.</param>
122 /// <exception cref="InvalidHandleException">The transaction handle is not valid.</exception>
123 /// <exception cref="InstallerException">The transaction could not be joined.</exception>
124 /// <remarks><p>
125 /// Win32 MSI API:
126 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msijointransaction.asp">MsiJoinTransaction</a>
127 /// </p></remarks>
128 public void Join(TransactionAttributes attributes)
129 {
130 IntPtr hChangeOfOwnerEvent;
131 uint ret = NativeMethods.MsiJoinTransaction((int) this.Handle, (int) attributes, out hChangeOfOwnerEvent);
132 if (ret != 0)
133 {
134 throw InstallerException.ExceptionFromReturnCode(ret);
135 }
136
137 this.ownerChangeEvent = hChangeOfOwnerEvent;
138 if (this.ownerChangeEvent != IntPtr.Zero && this.ownerChangeListeners.Count >= 1)
139 {
140 new Thread(this.WaitForOwnerChange).Start();
141 }
142 }
143
144 /// <summary>
145 /// Ends the install transaction and commits all changes to the system belonging to the transaction.
146 /// </summary>
147 /// <exception cref="InstallerException">The transaction could not be committed.</exception>
148 /// <remarks><p>
149 /// Runs any Commit Custom Actions and commits to the system any changes to Win32 or common language
150 /// runtime assemblies. Deletes the rollback script, and after using this option, the transaction's
151 /// changes can no longer be undone with a Rollback Installation.
152 /// </p><p>
153 /// This method can only be called by the current owner of the transaction.
154 /// </p><p>
155 /// Win32 MSI API:
156 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiendtransaction.asp">MsiEndTransaction</a>
157 /// </p></remarks>
158 public void Commit()
159 {
160 this.End(true);
161 }
162
163 /// <summary>
164 /// Ends the install transaction and undoes changes to the system belonging to the transaction.
165 /// </summary>
166 /// <exception cref="InstallerException">The transaction could not be rolled back.</exception>
167 /// <remarks><p>
168 /// This method can only be called by the current owner of the transaction.
169 /// </p><p>
170 /// Win32 MSI API:
171 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiendtransaction.asp">MsiEndTransaction</a>
172 /// </p></remarks>
173 public void Rollback()
174 {
175 this.End(false);
176 }
177
178 private static IntPtr[] Begin(string transactionName, TransactionAttributes attributes)
179 {
180 int hTransaction;
181 IntPtr hChangeOfOwnerEvent;
182 uint ret = NativeMethods.MsiBeginTransaction(transactionName, (int) attributes, out hTransaction, out hChangeOfOwnerEvent);
183 if (ret != 0)
184 {
185 throw InstallerException.ExceptionFromReturnCode(ret);
186 }
187
188 return new IntPtr[] { (IntPtr) hTransaction, hChangeOfOwnerEvent };
189 }
190
191 [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
192 private void End(bool commit)
193 {
194 uint ret = NativeMethods.MsiEndTransaction(commit ? 1 : 0);
195 if (ret != 0)
196 {
197 throw InstallerException.ExceptionFromReturnCode(ret);
198 }
199 }
200 }
201}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/ValidationErrorInfo.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/ValidationErrorInfo.cs
new file mode 100644
index 00000000..3f75326e
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/ValidationErrorInfo.cs
@@ -0,0 +1,46 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System.Diagnostics.CodeAnalysis;
6
7 /// <summary>
8 /// Contains specific information about an error encountered by the <see cref="View.Validate"/>,
9 /// <see cref="View.ValidateNew"/>, or <see cref="View.ValidateFields"/> methods of the
10 /// <see cref="View"/> class.
11 /// </summary>
12 [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
13 public struct ValidationErrorInfo
14 {
15 private ValidationError error;
16 private string column;
17
18 internal ValidationErrorInfo(ValidationError error, string column)
19 {
20 this.error = error;
21 this.column = column;
22 }
23
24 /// <summary>
25 /// Gets the type of validation error encountered.
26 /// </summary>
27 public ValidationError Error
28 {
29 get
30 {
31 return this.error;
32 }
33 }
34
35 /// <summary>
36 /// Gets the column containing the error, or null if the error applies to the whole row.
37 /// </summary>
38 public string Column
39 {
40 get
41 {
42 return this.column;
43 }
44 }
45 }
46}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/View.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/View.cs
new file mode 100644
index 00000000..21e8543e
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/View.cs
@@ -0,0 +1,625 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Text;
7 using System.Collections.Generic;
8 using System.Globalization;
9 using System.Diagnostics.CodeAnalysis;
10
11 /// <summary>
12 /// A View represents a result set obtained when processing a query using the
13 /// <see cref="WixToolset.Dtf.WindowsInstaller.Database.OpenView"/> method of a
14 /// <see cref="Database"/>. Before any data can be transferred,
15 /// the query must be executed using the <see cref="Execute(Record)"/> method, passing to
16 /// it all replaceable parameters designated within the SQL query string.
17 /// </summary>
18 [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
19 public class View : InstallerHandle, IEnumerable<Record>
20 {
21 private Database database;
22 private string sql;
23 private IList<TableInfo> tables;
24 private ColumnCollection columns;
25
26 internal View(IntPtr handle, string sql, Database database)
27 : base(handle, true)
28 {
29 this.sql = sql;
30 this.database = database;
31 }
32
33 /// <summary>
34 /// Gets the Database on which this View was opened.
35 /// </summary>
36 public Database Database
37 {
38 get { return this.database; }
39 }
40
41 /// <summary>
42 /// Gets the SQL query string used to open this View.
43 /// </summary>
44 public string QueryString
45 {
46 get { return this.sql; }
47 }
48
49 /// <summary>
50 /// Gets the set of tables that were included in the SQL query for this View.
51 /// </summary>
52 public IList<TableInfo> Tables
53 {
54 get
55 {
56 if (this.tables == null)
57 {
58 if (this.sql == null)
59 {
60 return null;
61 }
62
63 // Parse the table names out of the SQL query string by looking
64 // for tokens that can come before or after the list of tables.
65
66 string parseSql = this.sql.Replace('\t', ' ').Replace('\r', ' ').Replace('\n', ' ');
67 string upperSql = parseSql.ToUpper(CultureInfo.InvariantCulture);
68
69 string[] prefixes = new string[] { " FROM ", " INTO ", " TABLE " };
70 string[] suffixes = new string[] { " WHERE ", " ORDER ", " SET ", " (", " ADD " };
71
72 int index;
73
74 for (int i = 0; i < prefixes.Length; i++)
75 {
76 if ((index = upperSql.IndexOf(prefixes[i], StringComparison.Ordinal)) > 0)
77 {
78 parseSql = parseSql.Substring(index + prefixes[i].Length);
79 upperSql = upperSql.Substring(index + prefixes[i].Length);
80 }
81 }
82
83 if (upperSql.StartsWith("UPDATE ", StringComparison.Ordinal))
84 {
85 parseSql = parseSql.Substring(7);
86 upperSql = upperSql.Substring(7);
87 }
88
89 for (int i = 0; i < suffixes.Length; i++)
90 {
91 if ((index = upperSql.IndexOf(suffixes[i], StringComparison.Ordinal)) > 0)
92 {
93 parseSql = parseSql.Substring(0, index);
94 upperSql = upperSql.Substring(0, index);
95 }
96 }
97
98 if (upperSql.EndsWith(" HOLD", StringComparison.Ordinal) ||
99 upperSql.EndsWith(" FREE", StringComparison.Ordinal))
100 {
101 parseSql = parseSql.Substring(0, parseSql.Length - 5);
102 upperSql = upperSql.Substring(0, upperSql.Length - 5);
103 }
104
105 // At this point we should be left with a comma-separated list of table names,
106 // optionally quoted with grave accent marks (`).
107
108 string[] tableNames = parseSql.Split(',');
109 IList<TableInfo> tableList = new List<TableInfo>(tableNames.Length);
110 for (int i = 0; i < tableNames.Length; i++)
111 {
112 string tableName = tableNames[i].Trim(' ', '`');
113 tableList.Add(new TableInfo(this.database, tableName));
114 }
115 this.tables = tableList;
116 }
117 return new List<TableInfo>(this.tables);
118 }
119 }
120
121 /// <summary>
122 /// Gets the set of columns that were included in the query for this View,
123 /// or null if this view is not a SELECT query.
124 /// </summary>
125 /// <exception cref="InstallerException">the View is not in an active state</exception>
126 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
127 /// <remarks><p>
128 /// Win32 MSI API:
129 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgetcolumninfo.asp">MsiViewGetColumnInfo</a>
130 /// </p></remarks>
131 public ColumnCollection Columns
132 {
133 get
134 {
135 if (this.columns == null)
136 {
137 this.columns = new ColumnCollection(this);
138 }
139 return this.columns;
140 }
141 }
142
143 /// <summary>
144 /// Executes a SQL View query and supplies any required parameters. The query uses the
145 /// question mark token to represent parameters as described in SQL Syntax. The values of
146 /// these parameters are passed in as the corresponding fields of a parameter record.
147 /// </summary>
148 /// <param name="executeParams">Optional Record that supplies the parameters. This
149 /// Record contains values to replace the parameter tokens in the SQL query.</param>
150 /// <exception cref="InstallerException">the View could not be executed</exception>
151 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
152 /// <remarks><p>
153 /// Win32 MSI API:
154 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>
155 /// </p></remarks>
156 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Params"), SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
157 public void Execute(Record executeParams)
158 {
159 uint ret = RemotableNativeMethods.MsiViewExecute(
160 (int) this.Handle,
161 (executeParams != null ? (int) executeParams.Handle : 0));
162 if (ret == (uint) NativeMethods.Error.BAD_QUERY_SYNTAX)
163 {
164 throw new BadQuerySyntaxException(this.sql);
165 }
166 else if (ret != 0)
167 {
168 throw InstallerException.ExceptionFromReturnCode(ret);
169 }
170 }
171
172 /// <summary>
173 /// Executes a SQL View query.
174 /// </summary>
175 /// <exception cref="InstallerException">the View could not be executed</exception>
176 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
177 /// <remarks><p>
178 /// Win32 MSI API:
179 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>
180 /// </p></remarks>
181 public void Execute() { this.Execute(null); }
182
183 /// <summary>
184 /// Fetches the next sequential record from the view, or null if there are no more records.
185 /// </summary>
186 /// <exception cref="InstallerException">the View is not in an active state</exception>
187 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
188 /// <remarks><p>
189 /// The Record object should be <see cref="InstallerHandle.Close"/>d after use.
190 /// It is best that the handle be closed manually as soon as it is no longer
191 /// needed, as leaving lots of unused handles open can degrade performance.
192 /// </p><p>
193 /// Win32 MSI API:
194 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
195 /// </p></remarks>
196 public Record Fetch()
197 {
198 int recordHandle;
199 uint ret = RemotableNativeMethods.MsiViewFetch((int) this.Handle, out recordHandle);
200 if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS)
201 {
202 return null;
203 }
204 else if (ret != 0)
205 {
206 throw InstallerException.ExceptionFromReturnCode(ret);
207 }
208
209 Record r = new Record((IntPtr) recordHandle, true, this);
210 r.IsFormatStringInvalid = true;
211 return r;
212 }
213
214 /// <summary>
215 /// Updates a fetched Record.
216 /// </summary>
217 /// <param name="mode">specifies the modify mode</param>
218 /// <param name="record">the Record to modify</param>
219 /// <exception cref="InstallerException">the modification failed,
220 /// or a validation was requested and the data did not pass</exception>
221 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
222 /// <remarks><p>
223 /// You can update or delete a record immediately after inserting, or seeking provided you
224 /// have NOT modified the 0th field of the inserted or sought record.
225 /// </p><p>
226 /// To execute any SQL statement, a View must be created. However, a View that does not
227 /// create a result set, such as CREATE TABLE, or INSERT INTO, cannot be used with any of
228 /// the Modify methods to update tables though the view.
229 /// </p><p>
230 /// You cannot fetch a record containing binary data from one database and then use
231 /// that record to insert the data into another database. To move binary data from one database
232 /// to another, you should export the data to a file and then import it into the new database
233 /// using a query and the <see cref="Record.SetStream(int,string)"/>. This ensures that each database has
234 /// its own copy of the binary data.
235 /// </p><p>
236 /// Note that custom actions can only add, modify, or remove temporary rows, columns,
237 /// or tables from a database. Custom actions cannot modify persistent data in a database,
238 /// such as data that is a part of the database stored on disk.
239 /// </p><p>
240 /// Win32 MSI API:
241 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
242 /// </p></remarks>
243 /// <seealso cref="Refresh"/>
244 /// <seealso cref="Insert"/>
245 /// <seealso cref="Update"/>
246 /// <seealso cref="Assign"/>
247 /// <seealso cref="Replace"/>
248 /// <seealso cref="Delete"/>
249 /// <seealso cref="InsertTemporary"/>
250 /// <seealso cref="Seek"/>
251 /// <seealso cref="Merge"/>
252 /// <seealso cref="Validate"/>
253 /// <seealso cref="ValidateNew"/>
254 /// <seealso cref="ValidateFields"/>
255 /// <seealso cref="ValidateDelete"/>
256 [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
257 public void Modify(ViewModifyMode mode, Record record)
258 {
259 if (record == null)
260 {
261 throw new ArgumentNullException("record");
262 }
263
264 uint ret = RemotableNativeMethods.MsiViewModify((int) this.Handle, (int) mode, (int) record.Handle);
265 if (mode == ViewModifyMode.Insert || mode == ViewModifyMode.InsertTemporary)
266 {
267 record.IsFormatStringInvalid = true;
268 }
269 if (ret != 0)
270 {
271 throw InstallerException.ExceptionFromReturnCode(ret);
272 }
273 }
274
275 /// <summary>
276 /// Refreshes the data in a Record.
277 /// </summary>
278 /// <param name="record">the Record to be refreshed</param>
279 /// <exception cref="InstallerException">the refresh failed</exception>
280 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
281 /// <remarks><p>
282 /// The Record must have been obtained by calling <see cref="Fetch"/>. Fails with
283 /// a deleted Record. Works only with read-write Records.
284 /// </p><p>
285 /// See <see cref="Modify"/> for more remarks.
286 /// </p><p>
287 /// Win32 MSI API:
288 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
289 /// </p></remarks>
290 public void Refresh(Record record) { this.Modify(ViewModifyMode.Refresh, record); }
291
292 /// <summary>
293 /// Inserts a Record into the view.
294 /// </summary>
295 /// <param name="record">the Record to be inserted</param>
296 /// <exception cref="InstallerException">the insertion failed</exception>
297 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
298 /// <remarks><p>
299 /// Fails if a row with the same primary keys exists. Fails with a read-only database.
300 /// This method cannot be used with a View containing joins.
301 /// </p><p>
302 /// See <see cref="Modify"/> for more remarks.
303 /// </p><p>
304 /// Win32 MSI API:
305 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
306 /// </p></remarks>
307 public void Insert(Record record) { this.Modify(ViewModifyMode.Insert, record); }
308
309 /// <summary>
310 /// Updates the View with new data from the Record.
311 /// </summary>
312 /// <param name="record">the new data</param>
313 /// <exception cref="InstallerException">the update failed</exception>
314 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
315 /// <remarks><p>
316 /// Only non-primary keys can be updated. The Record must have been obtained by calling
317 /// <see cref="Fetch"/>. Fails with a deleted Record. Works only with read-write Records.
318 /// </p><p>
319 /// See <see cref="Modify"/> for more remarks.
320 /// </p><p>
321 /// Win32 MSI API:
322 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
323 /// </p></remarks>
324 public void Update(Record record) { this.Modify(ViewModifyMode.Update, record); }
325
326 /// <summary>
327 /// Updates or inserts a Record into the View.
328 /// </summary>
329 /// <param name="record">the Record to be assigned</param>
330 /// <exception cref="InstallerException">the assignment failed</exception>
331 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
332 /// <remarks><p>
333 /// Updates record if the primary keys match an existing row and inserts if they do not match.
334 /// Fails with a read-only database. This method cannot be used with a View containing joins.
335 /// </p><p>
336 /// See <see cref="Modify"/> for more remarks.
337 /// </p><p>
338 /// Win32 MSI API:
339 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
340 /// </p></remarks>
341 public void Assign(Record record) { this.Modify(ViewModifyMode.Assign, record); }
342
343 /// <summary>
344 /// Updates or deletes and inserts a Record into the View.
345 /// </summary>
346 /// <param name="record">the Record to be replaced</param>
347 /// <exception cref="InstallerException">the replacement failed</exception>
348 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
349 /// <remarks><p>
350 /// The Record must have been obtained by calling <see cref="Fetch"/>. Updates record if the
351 /// primary keys are unchanged. Deletes old row and inserts new if primary keys have changed.
352 /// Fails with a read-only database. This method cannot be used with a View containing joins.
353 /// </p><p>
354 /// See <see cref="Modify"/> for more remarks.
355 /// </p><p>
356 /// Win32 MSI API:
357 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
358 /// </p></remarks>
359 public void Replace(Record record) { this.Modify(ViewModifyMode.Replace, record); }
360
361 /// <summary>
362 /// Deletes a Record from the View.
363 /// </summary>
364 /// <param name="record">the Record to be deleted</param>
365 /// <exception cref="InstallerException">the deletion failed</exception>
366 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
367 /// <remarks><p>
368 /// The Record must have been obtained by calling <see cref="Fetch"/>. Fails if the row has been
369 /// deleted. Works only with read-write records. This method cannot be used with a View containing joins.
370 /// </p><p>
371 /// See <see cref="Modify"/> for more remarks.
372 /// </p><p>
373 /// Win32 MSI API:
374 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
375 /// </p></remarks>
376 public void Delete(Record record) { this.Modify(ViewModifyMode.Delete, record); }
377
378 /// <summary>
379 /// Inserts a Record into the View. The inserted data is not persistent.
380 /// </summary>
381 /// <param name="record">the Record to be inserted</param>
382 /// <exception cref="InstallerException">the insertion failed</exception>
383 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
384 /// <remarks><p>
385 /// Fails if a row with the same primary key exists. Works only with read-write records.
386 /// This method cannot be used with a View containing joins.
387 /// </p><p>
388 /// See <see cref="Modify"/> for more remarks.
389 /// </p><p>
390 /// Win32 MSI API:
391 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
392 /// </p></remarks>
393 public void InsertTemporary(Record record) { this.Modify(ViewModifyMode.InsertTemporary, record); }
394
395 /// <summary>
396 /// Refreshes the information in the supplied record without changing the position
397 /// in the result set and without affecting subsequent fetch operations.
398 /// </summary>
399 /// <param name="record">the Record to be filled with the result of the seek</param>
400 /// <exception cref="InstallerException">the seek failed</exception>
401 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
402 /// <remarks><p>
403 /// After seeking, the Record may then be used for subsequent Update, Delete, and Refresh
404 /// operations. All primary key columns of the table must be in the query and the Record must
405 /// have at least as many fields as the query. Seek cannot be used with multi-table queries.
406 /// This method cannot be used with a View containing joins.
407 /// </p><p>
408 /// See <see cref="Modify"/> for more remarks.
409 /// </p><p>
410 /// Win32 MSI API:
411 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
412 /// </p></remarks>
413 [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
414 public bool Seek(Record record)
415 {
416 if (record == null)
417 {
418 throw new ArgumentNullException("record");
419 }
420
421 uint ret = RemotableNativeMethods.MsiViewModify((int) this.Handle, (int) ViewModifyMode.Seek, (int) record.Handle);
422 record.IsFormatStringInvalid = true;
423 if (ret == (uint) NativeMethods.Error.FUNCTION_FAILED)
424 {
425 return false;
426 }
427 else if (ret != 0)
428 {
429 throw InstallerException.ExceptionFromReturnCode(ret);
430 }
431
432 return true;
433 }
434
435 /// <summary>
436 /// Inserts or validates a record.
437 /// </summary>
438 /// <param name="record">the Record to be merged</param>
439 /// <returns>true if the record was inserted or validated, false if there is an existing
440 /// record with the same primary keys that is not identical</returns>
441 /// <exception cref="InstallerException">the merge failed (for a reason other than invalid data)</exception>
442 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
443 /// <remarks><p>
444 /// Works only with read-write records. This method cannot be used with a
445 /// View containing joins.
446 /// </p><p>
447 /// See <see cref="Modify"/> for more remarks.
448 /// </p><p>
449 /// Win32 MSI API:
450 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
451 /// </p></remarks>
452 [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
453 public bool Merge(Record record)
454 {
455 if (record == null)
456 {
457 throw new ArgumentNullException("record");
458 }
459
460 uint ret = RemotableNativeMethods.MsiViewModify((int) this.Handle, (int) ViewModifyMode.Merge, (int) record.Handle);
461 if (ret == (uint) NativeMethods.Error.FUNCTION_FAILED)
462 {
463 return false;
464 }
465 else if (ret != 0)
466 {
467 throw InstallerException.ExceptionFromReturnCode(ret);
468 }
469 return true;
470 }
471
472 /// <summary>
473 /// Validates a record, returning information about any errors.
474 /// </summary>
475 /// <param name="record">the Record to be validated</param>
476 /// <returns>null if the record was validated; if there is an existing record with
477 /// the same primary keys that has conflicting data then error information is returned</returns>
478 /// <exception cref="InstallerException">the validation failed (for a reason other than invalid data)</exception>
479 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
480 /// <remarks><p>
481 /// The Record must have been obtained by calling <see cref="Fetch"/>.
482 /// Works with read-write and read-only records. This method cannot be used
483 /// with a View containing joins.
484 /// </p><p>
485 /// See <see cref="Modify"/> for more remarks.
486 /// </p><p>
487 /// Win32 MSI APIs:
488 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>,
489 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgeterror.asp">MsiViewGetError</a>
490 /// </p></remarks>
491 public ICollection<ValidationErrorInfo> Validate(Record record) { return this.InternalValidate(ViewModifyMode.Validate, record); }
492
493 /// <summary>
494 /// Validates a new record, returning information about any errors.
495 /// </summary>
496 /// <param name="record">the Record to be validated</param>
497 /// <returns>null if the record was validated; if there is an existing
498 /// record with the same primary keys then error information is returned</returns>
499 /// <exception cref="InstallerException">the validation failed (for a reason other than invalid data)</exception>
500 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
501 /// <remarks><p>
502 /// Checks for duplicate keys. The Record must have been obtained by
503 /// calling <see cref="Fetch"/>. Works with read-write and read-only records.
504 /// This method cannot be used with a View containing joins.
505 /// </p><p>
506 /// See <see cref="Modify"/> for more remarks.
507 /// </p><p>
508 /// Win32 MSI APIs:
509 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>,
510 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgeterror.asp">MsiViewGetError</a>
511 /// </p></remarks>
512 [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
513 public ICollection<ValidationErrorInfo> ValidateNew(Record record) { return this.InternalValidate(ViewModifyMode.ValidateNew, record); }
514
515 /// <summary>
516 /// Validates fields of a fetched or new record, returning information about any errors.
517 /// Can validate one or more fields of an incomplete record.
518 /// </summary>
519 /// <param name="record">the Record to be validated</param>
520 /// <returns>null if the record was validated; if there is an existing record with
521 /// the same primary keys that has conflicting data then error information is returned</returns>
522 /// <exception cref="InstallerException">the validation failed (for a reason other than invalid data)</exception>
523 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
524 /// <remarks><p>
525 /// Works with read-write and read-only records. This method cannot be used with
526 /// a View containing joins.
527 /// </p><p>
528 /// See <see cref="Modify"/> for more remarks.
529 /// </p><p>
530 /// Win32 MSI APIs:
531 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>,
532 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgeterror.asp">MsiViewGetError</a>
533 /// </p></remarks>
534 public ICollection<ValidationErrorInfo> ValidateFields(Record record) { return this.InternalValidate(ViewModifyMode.ValidateField, record); }
535
536 /// <summary>
537 /// Validates a record that will be deleted later, returning information about any errors.
538 /// </summary>
539 /// <param name="record">the Record to be validated</param>
540 /// <returns>null if the record is safe to delete; if another row refers to
541 /// the primary keys of this row then error information is returned</returns>
542 /// <exception cref="InstallerException">the validation failed (for a reason other than invalid data)</exception>
543 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
544 /// <remarks><p>
545 /// Validation does not check for the existence of the primary keys of this row in properties
546 /// or strings. Does not check if a column is a foreign key to multiple tables. Works with
547 /// read-write and read-only records. This method cannot be used with a View containing joins.
548 /// </p><p>
549 /// See <see cref="Modify"/> for more remarks.
550 /// </p><p>
551 /// Win32 MSI APIs:
552 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>,
553 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgeterror.asp">MsiViewGetError</a>
554 /// </p></remarks>
555 public ICollection<ValidationErrorInfo> ValidateDelete(Record record) { return this.InternalValidate(ViewModifyMode.ValidateDelete, record); }
556
557 /// <summary>
558 /// Enumerates over the Records retrieved by the View.
559 /// </summary>
560 /// <returns>An enumerator of Record objects.</returns>
561 /// <exception cref="InstallerException">The View was not <see cref="Execute(Record)"/>d before attempting the enumeration.</exception>
562 /// <remarks><p>
563 /// Each Record object should be <see cref="InstallerHandle.Close"/>d after use.
564 /// It is best that the handle be closed manually as soon as it is no longer
565 /// needed, as leaving lots of unused handles open can degrade performance.
566 /// However, note that it is not necessary to complete the enumeration just
567 /// for the purpose of closing handles, because Records are fetched lazily
568 /// on each step of the enumeration.
569 /// </p><p>
570 /// Win32 MSI API:
571 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
572 /// </p></remarks>
573 public IEnumerator<Record> GetEnumerator()
574 {
575 Record rec;
576 while ((rec = this.Fetch()) != null)
577 {
578 yield return rec;
579 }
580 }
581
582 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
583 {
584 return this.GetEnumerator();
585 }
586
587 private ICollection<ValidationErrorInfo> InternalValidate(ViewModifyMode mode, Record record)
588 {
589 uint ret = RemotableNativeMethods.MsiViewModify((int) this.Handle, (int) mode, (int) record.Handle);
590 if (ret == (uint) NativeMethods.Error.INVALID_DATA)
591 {
592 ICollection<ValidationErrorInfo> errorInfo = new List<ValidationErrorInfo>();
593 while (true)
594 {
595 uint bufSize = 40;
596 StringBuilder column = new StringBuilder("", (int) bufSize);
597 int error = RemotableNativeMethods.MsiViewGetError((int) this.Handle, column, ref bufSize);
598 if (error == -2 /*MSIDBERROR_MOREDATA*/)
599 {
600 column.Capacity = (int) ++bufSize;
601 error = RemotableNativeMethods.MsiViewGetError((int) this.Handle, column, ref bufSize);
602 }
603
604 if (error == -3 /*MSIDBERROR_INVALIDARG*/)
605 {
606 throw InstallerException.ExceptionFromReturnCode((uint) NativeMethods.Error.INVALID_PARAMETER);
607 }
608 else if (error == 0 /*MSIDBERROR_NOERROR*/)
609 {
610 break;
611 }
612
613 errorInfo.Add(new ValidationErrorInfo((ValidationError) error, column.ToString()));
614 }
615
616 return errorInfo;
617 }
618 else if (ret != 0)
619 {
620 throw InstallerException.ExceptionFromReturnCode(ret);
621 }
622 return null;
623 }
624 }
625}
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/WindowsInstaller.cd b/src/dtf/WixToolset.Dtf.WindowsInstaller/WindowsInstaller.cd
new file mode 100644
index 00000000..01b68153
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/WindowsInstaller.cd
@@ -0,0 +1,943 @@
1<?xml version="1.0" encoding="utf-8"?>
2<ClassDiagram MajorVersion="1" MinorVersion="1">
3 <Comment CommentText="Database classes">
4 <Position X="3.433" Y="7.717" Height="0.26" Width="1.159" />
5 </Comment>
6 <Comment CommentText="Custom action classes">
7 <Position X="5.791" Y="1.774" Height="0.312" Width="1.465" />
8 </Comment>
9 <Comment CommentText="Inventory classes">
10 <Position X="12.323" Y="2.386" Height="0.292" Width="1.183" />
11 </Comment>
12 <Comment CommentText="Exceptions">
13 <Position X="11.75" Y="9.25" Height="0.285" Width="0.798" />
14 </Comment>
15 <Comment CommentText="Callback delegates">
16 <Position X="17.25" Y="8" Height="0.281" Width="1.25" />
17 </Comment>
18 <Comment CommentText="API enums">
19 <Position X="21.992" Y="0.5" Height="0.283" Width="2.008" />
20 </Comment>
21 <Comment CommentText="Database column enums">
22 <Position X="24.25" Y="0.5" Height="0.26" Width="1.983" />
23 </Comment>
24 <Class Name="WixToolset.Dtf.WindowsInstaller.Database">
25 <Position X="0.5" Y="5" Width="2.25" />
26 <Members>
27 <Method Name="Database" Hidden="true" />
28 <Field Name="deleteOnClose" Hidden="true" />
29 <Method Name="Dispose" Hidden="true" />
30 <Method Name="ExecutePropertyQuery" Hidden="true" />
31 <Field Name="filePath" Hidden="true" />
32 <Method Name="Open" Hidden="true" />
33 <Field Name="openMode" Hidden="true" />
34 <Field Name="summaryInfo" Hidden="true" />
35 <Field Name="tables" Hidden="true" />
36 </Members>
37 <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerHandle" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
38 <Path>
39 <Point X="3.312" Y="2.119" />
40 <Point X="3.312" Y="2.689" />
41 <Point X="2.699" Y="2.689" />
42 <Point X="2.699" Y="5" />
43 </Path>
44 </InheritanceLine>
45 <AssociationLine Name="Tables" Type="WixToolset.Dtf.WindowsInstaller.TableInfo" FixedFromPoint="true" FixedToPoint="true">
46 <Path>
47 <Point X="2.75" Y="8.125" />
48 <Point X="3.75" Y="8.125" />
49 <Point X="3.75" Y="8.5" />
50 </Path>
51 <MemberNameLabel ManuallyPlaced="true">
52 <Position X="0.293" Y="0.152" />
53 </MemberNameLabel>
54 </AssociationLine>
55 <AssociationLine Name="SummaryInfo" Type="WixToolset.Dtf.WindowsInstaller.SummaryInfo" FixedFromPoint="true">
56 <Path>
57 <Point X="1.318" Y="5" />
58 <Point X="1.318" Y="4.535" />
59 </Path>
60 <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
61 <Position X="-1.155" Y="0.279" Height="0.16" Width="1.095" />
62 </MemberNameLabel>
63 </AssociationLine>
64 <TypeIdentifier>
65 <HashCode>QABACUgAACAAIAVUIAgQREACAAIAk0ABAIhmoEAAAAA=</HashCode>
66 <FileName>Database.cs</FileName>
67 </TypeIdentifier>
68 <ShowAsAssociation>
69 <Property Name="SummaryInfo" />
70 </ShowAsAssociation>
71 <ShowAsCollectionAssociation>
72 <Property Name="Tables" />
73 </ShowAsCollectionAssociation>
74 </Class>
75 <Class Name="WixToolset.Dtf.WindowsInstaller.InstallerHandle">
76 <Position X="3" Y="0.5" Width="2" />
77 <Members>
78 <Method Name="Dispose" Hidden="true" />
79 <Method Name="Equals" Hidden="true" />
80 <Method Name="GetHashCode" Hidden="true" />
81 <Field Name="handle" Hidden="true" />
82 <Method Name="InstallerHandle" Hidden="true" />
83 <Property Name="Sync" Hidden="true" />
84 </Members>
85 <TypeIdentifier>
86 <HashCode>AAAAAAAAACAAAACAgAAAAAAAAAAAAMIAAAACAACAAAA=</HashCode>
87 <FileName>Handle.cs</FileName>
88 </TypeIdentifier>
89 <Lollipop Position="0.2" />
90 </Class>
91 <Class Name="WixToolset.Dtf.WindowsInstaller.Installer">
92 <Position X="19.25" Y="0.5" Width="2.5" />
93 <Members>
94 <Method Name="CheckInstallResult" Hidden="true" />
95 <Field Name="errorResources" Hidden="true" />
96 <Property Name="ErrorResources" Hidden="true" />
97 <Field Name="externalUIHandlers" Hidden="true" />
98 <Method Name="GetMessageFromModule" Hidden="true" />
99 <Method Name="GetPatchStringDataType" Hidden="true" />
100 <Field Name="rebootInitiated" Hidden="true" />
101 <Field Name="rebootRequired" Hidden="true" />
102 </Members>
103 <TypeIdentifier>
104 <HashCode>IAQBiIgAgAKAAAAELEAAPBgJAUAAYAAAAOQDagAgAQE=</HashCode>
105 <FileName>ExternalUIHandler.cs</FileName>
106 </TypeIdentifier>
107 </Class>
108 <Class Name="WixToolset.Dtf.WindowsInstaller.Record">
109 <Position X="5.25" Y="3" Width="2" />
110 <Members>
111 <Method Name="CheckRange" Hidden="true" />
112 <Method Name="ExtractSubStorage" Hidden="true" />
113 <Method Name="FindColumn" Hidden="true" />
114 <Method Name="Record" Hidden="true" />
115 <Field Name="view" Hidden="true" />
116 </Members>
117 <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerHandle" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
118 <Path>
119 <Point X="3.688" Y="2.119" />
120 <Point X="3.688" Y="2.672" />
121 <Point X="5.4" Y="2.672" />
122 <Point X="5.4" Y="3" />
123 </Path>
124 </InheritanceLine>
125 <TypeIdentifier>
126 <HashCode>AIAAAAAAAAAAAIAEiEIAJAAAAQgIEIAIIQCAAABIAAA=</HashCode>
127 <FileName>Record.cs</FileName>
128 </TypeIdentifier>
129 </Class>
130 <Class Name="WixToolset.Dtf.WindowsInstaller.Session">
131 <Position X="7.5" Y="0.5" Width="2.25" />
132 <Members>
133 <Field Name="database" Hidden="true" />
134 <Method Name="Dispose" Hidden="true" />
135 <Method Name="IFormatProvider.GetFormat" Hidden="true" />
136 <Method Name="Session" Hidden="true" />
137 </Members>
138 <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerHandle" FixedFromPoint="true">
139 <Path>
140 <Point X="3.875" Y="2.119" />
141 <Point X="3.875" Y="2.395" />
142 <Point X="7.5" Y="2.395" />
143 </Path>
144 </InheritanceLine>
145 <AssociationLine Name="Database" Type="WixToolset.Dtf.WindowsInstaller.Database" FixedFromPoint="true" FixedToPoint="true">
146 <Path>
147 <Point X="7.562" Y="4.696" />
148 <Point X="7.562" Y="10.375" />
149 <Point X="2.75" Y="10.375" />
150 </Path>
151 <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
152 <Position X="4.869" Y="5.46" Height="0.16" Width="0.806" />
153 </MemberNameLabel>
154 </AssociationLine>
155 <AssociationLine Name="Components" Type="WixToolset.Dtf.WindowsInstaller.ComponentInfo" FixedFromPoint="true">
156 <Path>
157 <Point X="8.562" Y="4.696" />
158 <Point X="8.562" Y="5.5" />
159 </Path>
160 <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
161 <Position X="-1.021" Y="0.317" Height="0.16" Width="0.986" />
162 </MemberNameLabel>
163 </AssociationLine>
164 <AssociationLine Name="Features" Type="WixToolset.Dtf.WindowsInstaller.FeatureInfo">
165 <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
166 <Position X="0.016" Y="2.568" Height="0.16" Width="0.785" />
167 </MemberNameLabel>
168 </AssociationLine>
169 <TypeIdentifier>
170 <HashCode>AAAAEAAAKGAAAoDADIBBAAAAAAgIEABBEIAAIAAACAA=</HashCode>
171 <FileName>Session.cs</FileName>
172 </TypeIdentifier>
173 <ShowAsAssociation>
174 <Property Name="Database" />
175 </ShowAsAssociation>
176 <ShowAsCollectionAssociation>
177 <Property Name="Components" />
178 <Property Name="Features" />
179 </ShowAsCollectionAssociation>
180 <Lollipop Position="0.2" />
181 </Class>
182 <Class Name="WixToolset.Dtf.WindowsInstaller.SummaryInfo">
183 <Position X="0.5" Y="0.5" Width="2" />
184 <Members>
185 <Field Name="MAX_PROPERTIES" Hidden="true" />
186 <Method Name="OpenSummaryInfo" Hidden="true" />
187 <Method Name="SummaryInfo" Hidden="true" />
188 <Property Name="this" Hidden="true" />
189 </Members>
190 <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerHandle" FixedFromPoint="true">
191 <Path>
192 <Point X="3.125" Y="2.119" />
193 <Point X="3.125" Y="2.404" />
194 <Point X="2.5" Y="2.404" />
195 </Path>
196 </InheritanceLine>
197 <TypeIdentifier>
198 <HashCode>AQAAAAAAAEAAAEAAjMgAAJIAAAEAAAAAAAAEhAAAoCI=</HashCode>
199 <FileName>SummaryInfo.cs</FileName>
200 </TypeIdentifier>
201 </Class>
202 <Class Name="WixToolset.Dtf.WindowsInstaller.View">
203 <Position X="3" Y="3" Width="2" />
204 <Members>
205 <Field Name="columns" Hidden="true" />
206 <Field Name="database" Hidden="true" />
207 <Method Name="InternalValidate" Hidden="true" />
208 <Field Name="sql" Hidden="true" />
209 <Method Name="System.Collections.IEnumerable.GetEnumerator" Hidden="true" />
210 <Field Name="tables" Hidden="true" />
211 <Method Name="View" Hidden="true" />
212 </Members>
213 <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerHandle" FixedFromPoint="true" FixedToPoint="true">
214 <Path>
215 <Point X="3.5" Y="2.119" />
216 <Point X="3.5" Y="3" />
217 </Path>
218 </InheritanceLine>
219 <AssociationLine Name="Database" Type="WixToolset.Dtf.WindowsInstaller.Database" FixedFromPoint="true" FixedToPoint="true">
220 <Path>
221 <Point X="3.75" Y="7.196" />
222 <Point X="3.75" Y="7.562" />
223 <Point X="2.75" Y="7.562" />
224 </Path>
225 <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
226 <Position X="0.168" Y="0.16" Height="0.16" Width="0.831" />
227 </MemberNameLabel>
228 </AssociationLine>
229 <AssociationLine Name="Columns" Type="WixToolset.Dtf.WindowsInstaller.ColumnInfo" FixedFromPoint="true" FixedToPoint="true">
230 <Path>
231 <Point X="4.25" Y="7.196" />
232 <Point X="4.25" Y="7.562" />
233 <Point X="5.25" Y="7.562" />
234 </Path>
235 <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
236 <Position X="0.168" Y="0.152" Height="0.16" Width="0.787" />
237 </MemberNameLabel>
238 </AssociationLine>
239 <TypeIdentifier>
240 <HashCode>ICQAIEQAAEDgABRACQEAQECBAAEAAQAACAAAAAgAACA=</HashCode>
241 <FileName>View.cs</FileName>
242 </TypeIdentifier>
243 <ShowAsAssociation>
244 <Property Name="Database" />
245 </ShowAsAssociation>
246 <ShowAsCollectionAssociation>
247 <Property Name="Columns" />
248 </ShowAsCollectionAssociation>
249 <Lollipop Position="0.638" />
250 </Class>
251 <Class Name="WixToolset.Dtf.WindowsInstaller.TableInfo">
252 <Position X="3" Y="8.5" Width="2" />
253 <Members>
254 <Field Name="columns" Hidden="true" />
255 <Method Name="GetTablePrimaryKeys" Hidden="true" />
256 <Field Name="name" Hidden="true" />
257 <Field Name="primaryKeys" Hidden="true" />
258 <Method Name="TableInfo" Hidden="true" />
259 <Method Name="ToString" Hidden="true" />
260 </Members>
261 <AssociationLine Name="Columns" Type="WixToolset.Dtf.WindowsInstaller.ColumnInfo" FixedFromPoint="true" FixedToPoint="true">
262 <Path>
263 <Point X="4.25" Y="8.5" />
264 <Point X="4.25" Y="8.125" />
265 <Point X="5.25" Y="8.125" />
266 </Path>
267 <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
268 <Position X="0.143" Y="-0.338" Height="0.16" Width="0.787" />
269 </MemberNameLabel>
270 </AssociationLine>
271 <TypeIdentifier>
272 <HashCode>AAAAAAAAAEAAAAAEACAAAAQAQAAEAAAASAAAAAgAAAA=</HashCode>
273 <FileName>TableInfo.cs</FileName>
274 </TypeIdentifier>
275 <ShowAsCollectionAssociation>
276 <Property Name="Columns" />
277 </ShowAsCollectionAssociation>
278 </Class>
279 <Class Name="WixToolset.Dtf.WindowsInstaller.ColumnInfo">
280 <Position X="5.25" Y="7.25" Width="2" />
281 <Members>
282 <Method Name="ColumnInfo" Hidden="true" />
283 <Field Name="isLocalizable" Hidden="true" />
284 <Field Name="isRequired" Hidden="true" />
285 <Field Name="isTemporary" Hidden="true" />
286 <Field Name="name" Hidden="true" />
287 <Field Name="size" Hidden="true" />
288 <Method Name="ToString" Hidden="true" />
289 <Field Name="type" Hidden="true" />
290 </Members>
291 <TypeIdentifier>
292 <HashCode>AgAAAAAAAAAAgCAEEIAAABQgQAAFIAAAAQAAAAIAEAA=</HashCode>
293 <FileName>ColumnInfo.cs</FileName>
294 </TypeIdentifier>
295 </Class>
296 <Class Name="WixToolset.Dtf.WindowsInstaller.CustomActionAttribute">
297 <Position X="5.25" Y="0.5" Width="2" />
298 <Members>
299 <Method Name="CustomActionAttribute" Hidden="true" />
300 <Field Name="name" Hidden="true" />
301 </Members>
302 <TypeIdentifier>
303 <HashCode>AAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAA=</HashCode>
304 <FileName>CustomActionAttribute.cs</FileName>
305 </TypeIdentifier>
306 </Class>
307 <Class Name="WixToolset.Dtf.WindowsInstaller.Installation">
308 <Position X="12.5" Y="0.5" Width="2" />
309 <Members>
310 <Field Name="context" Hidden="true" />
311 <Method Name="Installation" Hidden="true" />
312 <Field Name="installationCode" Hidden="true" />
313 <Property Name="InstallationCode" Hidden="true" />
314 <Property Name="InstallationType" Hidden="true" />
315 <Field Name="sourceList" Hidden="true" />
316 <Field Name="userSid" Hidden="true" />
317 </Members>
318 <AssociationLine Name="SourceList" Type="WixToolset.Dtf.WindowsInstaller.SourceList" FixedFromPoint="true" FixedToPoint="true">
319 <Path>
320 <Point X="13.75" Y="1.95" />
321 <Point X="13.75" Y="2.298" />
322 <Point X="14.75" Y="2.298" />
323 </Path>
324 <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
325 <Position X="0.076" Y="0.114" Height="0.16" Width="0.868" />
326 </MemberNameLabel>
327 </AssociationLine>
328 <TypeIdentifier>
329 <HashCode>AAACIhAAAoAQAACACAAAAABAAAAAAAAAAAAEAAAAAAA=</HashCode>
330 <FileName>Installation.cs</FileName>
331 </TypeIdentifier>
332 <ShowAsAssociation>
333 <Property Name="SourceList" />
334 </ShowAsAssociation>
335 </Class>
336 <Class Name="WixToolset.Dtf.WindowsInstaller.InstallationPart">
337 <Position X="17" Y="3" Width="2" />
338 <Members>
339 <Field Name="id" Hidden="true" />
340 <Property Name="Id" Hidden="true" />
341 <Method Name="InstallationPart" Hidden="true" />
342 <Field Name="productCode" Hidden="true" />
343 <Property Name="ProductCode" Hidden="true" />
344 </Members>
345 <AssociationLine Name="Product" Type="WixToolset.Dtf.WindowsInstaller.ProductInstallation" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
346 <Path>
347 <Point X="17.611" Y="3.967" />
348 <Point X="17.611" Y="4.519" />
349 <Point X="14.76" Y="4.519" />
350 <Point X="14.76" Y="6.438" />
351 <Point X="12.25" Y="6.438" />
352 </Path>
353 <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
354 <Position X="4.65" Y="2.262" Height="0.16" Width="0.703" />
355 </MemberNameLabel>
356 </AssociationLine>
357 <TypeIdentifier>
358 <HashCode>AAAiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAIgA=</HashCode>
359 <FileName>InstallationPart.cs</FileName>
360 </TypeIdentifier>
361 <ShowAsAssociation>
362 <Property Name="Product" />
363 </ShowAsAssociation>
364 </Class>
365 <Class Name="WixToolset.Dtf.WindowsInstaller.ProductInstallation">
366 <Position X="10" Y="0.5" Width="2.25" />
367 <Members>
368 <Method Name="EnumFeatures" Hidden="true" />
369 <Method Name="EnumProducts" Hidden="true" />
370 <Method Name="EnumRelatedProducts" Hidden="true" />
371 <Property Name="InstallationType" Hidden="true" />
372 <Field Name="properties" Hidden="true" />
373 <Property Name="State" Hidden="true" />
374 </Members>
375 <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.Installation" FixedFromPoint="true" FixedToPoint="true">
376 <Path>
377 <Point X="13.312" Y="1.95" />
378 <Point X="13.312" Y="2.312" />
379 <Point X="12.25" Y="2.312" />
380 </Path>
381 </InheritanceLine>
382 <AssociationLine Name="AllProducts" Type="WixToolset.Dtf.WindowsInstaller.ProductInstallation" FixedFromPoint="true" FixedToPoint="true">
383 <Path>
384 <Point X="10.5" Y="7.273" />
385 <Point X="10.5" Y="7.523" />
386 <Point X="11.562" Y="7.523" />
387 <Point X="11.562" Y="7.273" />
388 </Path>
389 <MemberNameLabel ManuallyPlaced="true">
390 <Position X="0.068" Y="0.052" />
391 </MemberNameLabel>
392 </AssociationLine>
393 <AssociationLine Name="Features" Type="WixToolset.Dtf.WindowsInstaller.FeatureInstallation" FixedToPoint="true">
394 <Path>
395 <Point X="12.25" Y="6.813" />
396 <Point X="15" Y="6.813" />
397 </Path>
398 <MemberNameLabel ManuallyPlaced="true">
399 <Position X="1.922" Y="0.05" />
400 </MemberNameLabel>
401 </AssociationLine>
402 <TypeIdentifier>
403 <HashCode>UAIEYrAGAAAACQBAKEABAAoASA0ARQAASUIGBAAIIAA=</HashCode>
404 <FileName>ProductInstallation.cs</FileName>
405 </TypeIdentifier>
406 <ShowAsCollectionAssociation>
407 <Property Name="AllProducts" />
408 <Property Name="Features" />
409 </ShowAsCollectionAssociation>
410 </Class>
411 <Class Name="WixToolset.Dtf.WindowsInstaller.PatchInstallation">
412 <Position X="12.5" Y="2.75" Width="2" />
413 <Members>
414 <Method Name="EnumPatches" Hidden="true" />
415 <Property Name="InstallationType" Hidden="true" />
416 <Method Name="PatchInstallation" Hidden="true" />
417 <Field Name="productCode" Hidden="true" />
418 <Property Name="State" Hidden="true" />
419 </Members>
420 <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.Installation" FixedFromPoint="true" FixedToPoint="true">
421 <Path>
422 <Point X="13.562" Y="1.95" />
423 <Point X="13.562" Y="2.75" />
424 </Path>
425 </InheritanceLine>
426 <AssociationLine Name="AllPatches" Type="WixToolset.Dtf.WindowsInstaller.PatchInstallation" FixedFromPoint="true" FixedToPoint="true">
427 <Path>
428 <Point X="12.938" Y="5.98" />
429 <Point X="12.938" Y="6.23" />
430 <Point X="14" Y="6.23" />
431 <Point X="14" Y="5.98" />
432 </Path>
433 <MemberNameLabel ManuallyPlaced="true">
434 <Position X="0.093" Y="0.06" />
435 </MemberNameLabel>
436 </AssociationLine>
437 <TypeIdentifier>
438 <HashCode>AAAgsgCACAAAAAAAKEAAAgAAAgAAAAAAAgIAFAAAIAA=</HashCode>
439 <FileName>PatchInstallation.cs</FileName>
440 </TypeIdentifier>
441 <ShowAsCollectionAssociation>
442 <Property Name="AllPatches" />
443 </ShowAsCollectionAssociation>
444 </Class>
445 <Class Name="WixToolset.Dtf.WindowsInstaller.ComponentInstallation">
446 <Position X="17" Y="5" Width="2" />
447 <Members>
448 <Method Name="ComponentInstallation" Hidden="true" />
449 <Method Name="EnumClients" Hidden="true" />
450 <Method Name="EnumComponents" Hidden="true" />
451 <Method Name="EnumQualifiers" Hidden="true" />
452 <Method Name="GetProductCode" Hidden="true" />
453 </Members>
454 <Compartments>
455 <Compartment Name="Nested Types" Collapsed="false" />
456 </Compartments>
457 <NestedTypes>
458 <Struct Name="WixToolset.Dtf.WindowsInstaller.ComponentInstallation.Qualifier" Collapsed="true">
459 <TypeIdentifier>
460 <NewMemberFileName>ComponentInstallation.cs</NewMemberFileName>
461 </TypeIdentifier>
462 </Struct>
463 </NestedTypes>
464 <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallationPart" FixedFromPoint="true" FixedToPoint="true">
465 <Path>
466 <Point X="18.111" Y="3.967" />
467 <Point X="18.111" Y="5" />
468 </Path>
469 </InheritanceLine>
470 <AssociationLine Name="AllComponents" Type="WixToolset.Dtf.WindowsInstaller.ComponentInstallation" FixedFromPoint="true" FixedToPoint="true">
471 <Path>
472 <Point X="17.375" Y="7.443" />
473 <Point X="17.375" Y="7.693" />
474 <Point X="18.625" Y="7.693" />
475 <Point X="18.625" Y="7.443" />
476 </Path>
477 <MemberNameLabel ManuallyPlaced="true">
478 <Position X="0.068" Y="0.052" />
479 </MemberNameLabel>
480 </AssociationLine>
481 <AssociationLine Name="ClientProducts" Type="WixToolset.Dtf.WindowsInstaller.ProductInstallation" FixedFromPoint="true">
482 <Path>
483 <Point X="17" Y="7.188" />
484 <Point X="12.25" Y="7.188" />
485 </Path>
486 <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
487 <Position X="3.658" Y="-0.195" Height="0.16" Width="1.087" />
488 </MemberNameLabel>
489 </AssociationLine>
490 <TypeIdentifier>
491 <HashCode>AUAAAAACAAAAAAIAEAgQAAQAAAAAAAAEAAAABAAAAAA=</HashCode>
492 <FileName>ComponentInstallation.cs</FileName>
493 </TypeIdentifier>
494 <ShowAsCollectionAssociation>
495 <Property Name="AllComponents" />
496 <Property Name="ClientProducts" />
497 </ShowAsCollectionAssociation>
498 </Class>
499 <Class Name="WixToolset.Dtf.WindowsInstaller.FeatureInstallation">
500 <Position X="15" Y="4.75" Width="1.75" />
501 <Members>
502 <Method Name="FeatureInstallation" Hidden="true" />
503 </Members>
504 <Compartments>
505 <Compartment Name="Nested Types" Collapsed="false" />
506 </Compartments>
507 <NestedTypes>
508 <Struct Name="WixToolset.Dtf.WindowsInstaller.FeatureInstallation.UsageData" Collapsed="true">
509 <TypeIdentifier>
510 <NewMemberFileName>FeatureInstallation.cs</NewMemberFileName>
511 </TypeIdentifier>
512 </Struct>
513 </NestedTypes>
514 <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallationPart" FixedFromPoint="true" FixedToPoint="true">
515 <Path>
516 <Point X="17.889" Y="3.967" />
517 <Point X="17.889" Y="4.804" />
518 <Point X="16.75" Y="4.804" />
519 </Path>
520 </InheritanceLine>
521 <TypeIdentifier>
522 <HashCode>AAAAAAAAAAAAAAAAAAAAAAAEAAAAACAAAAAABAAAAAA=</HashCode>
523 <FileName>FeatureInstallation.cs</FileName>
524 </TypeIdentifier>
525 </Class>
526 <Class Name="WixToolset.Dtf.WindowsInstaller.SourceList">
527 <Position X="14.75" Y="0.5" Width="2" />
528 <Members>
529 <Method Name="ClearSourceType" Hidden="true" />
530 <Method Name="Contains" Hidden="true" />
531 <Method Name="CopyTo" Hidden="true" />
532 <Method Name="EnumSources" Hidden="true" />
533 <Field Name="installation" Hidden="true" />
534 <Field Name="mediaList" Hidden="true" />
535 <Method Name="SourceList" Hidden="true" />
536 <Method Name="System.Collections.IEnumerable.GetEnumerator" Hidden="true" />
537 </Members>
538 <AssociationLine Name="MediaList" Type="WixToolset.Dtf.WindowsInstaller.MediaDisk" FixedFromPoint="true" FixedToPoint="true">
539 <Path>
540 <Point X="16.75" Y="2.375" />
541 <Point X="17.667" Y="2.375" />
542 <Point X="17.667" Y="1.789" />
543 </Path>
544 <MemberNameLabel ManuallyPlaced="true">
545 <Position X="0.029" Y="0.377" />
546 </MemberNameLabel>
547 </AssociationLine>
548 <TypeIdentifier>
549 <HashCode>BCIAAEAAEABAABgQCAAABIQAAFAQAAAAAAAABARwIgA=</HashCode>
550 <FileName>SourceList.cs</FileName>
551 </TypeIdentifier>
552 <ShowAsCollectionAssociation>
553 <Property Name="MediaList" />
554 </ShowAsCollectionAssociation>
555 <Lollipop Position="0.2" />
556 </Class>
557 <Class Name="WixToolset.Dtf.WindowsInstaller.ComponentInfo">
558 <Position X="8" Y="5.5" Width="1.5" />
559 <Members>
560 <Method Name="ComponentInfo" Hidden="true" />
561 <Field Name="name" Hidden="true" />
562 <Field Name="session" Hidden="true" />
563 </Members>
564 <TypeIdentifier>
565 <HashCode>AQAAAAIAAAAAAAAAAAEAACQAAAAEAAAAAAAAAAAAAAA=</HashCode>
566 <FileName>ComponentInfo.cs</FileName>
567 </TypeIdentifier>
568 </Class>
569 <Class Name="WixToolset.Dtf.WindowsInstaller.FeatureInfo">
570 <Position X="8" Y="7.5" Width="1.75" />
571 <Members>
572 <Method Name="FeatureInfo" Hidden="true" />
573 <Field Name="name" Hidden="true" />
574 <Field Name="session" Hidden="true" />
575 </Members>
576 <TypeIdentifier>
577 <HashCode>AQAAAAIAAAggAEAAAAEAACQQAAAEAAAAAAAAAAAAAAA=</HashCode>
578 <FileName>FeatureInfo.cs</FileName>
579 </TypeIdentifier>
580 </Class>
581 <Class Name="WixToolset.Dtf.WindowsInstaller.InstallerException">
582 <Position X="11.75" Y="7.5" Width="2.25" />
583 <Members>
584 <Method Name="Combine" Hidden="true" />
585 <Field Name="errorCode" Hidden="true" />
586 <Field Name="errorData" Hidden="true" />
587 <Method Name="ExceptionFromReturnCode" Hidden="true" />
588 <Method Name="GetObjectData" Hidden="true" />
589 <Method Name="GetSystemMessage" Hidden="true" />
590 <Method Name="InstallerException" Hidden="true" />
591 <Method Name="SaveErrorRecord" Hidden="true" />
592 </Members>
593 <TypeIdentifier>
594 <HashCode>hAAAAAAAAAAAAAQAAAAAAAAgAAgIAAAIAAABIAAAQAA=</HashCode>
595 <FileName>Exceptions.cs</FileName>
596 </TypeIdentifier>
597 </Class>
598 <Class Name="WixToolset.Dtf.WindowsInstaller.BadQuerySyntaxException" Collapsed="true">
599 <Position X="11.75" Y="9.75" Width="2.25" />
600 <Members>
601 <Method Name="BadQuerySyntaxException" Hidden="true" />
602 </Members>
603 <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerException" FixedFromPoint="true" FixedToPoint="true">
604 <Path>
605 <Point X="12.938" Y="9.119" />
606 <Point X="12.938" Y="9.75" />
607 </Path>
608 </InheritanceLine>
609 <TypeIdentifier>
610 <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
611 <FileName>Exceptions.cs</FileName>
612 </TypeIdentifier>
613 </Class>
614 <Class Name="WixToolset.Dtf.WindowsInstaller.InstallCanceledException" Collapsed="true">
615 <Position X="14.25" Y="9.75" Width="2.25" />
616 <Members>
617 <Method Name="InstallCanceledException" Hidden="true" />
618 </Members>
619 <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerException" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
620 <Path>
621 <Point X="13.375" Y="9.119" />
622 <Point X="13.375" Y="9.631" />
623 <Point X="14.25" Y="9.631" />
624 <Point X="14.25" Y="9.813" />
625 </Path>
626 </InheritanceLine>
627 <TypeIdentifier>
628 <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
629 <FileName>Exceptions.cs</FileName>
630 </TypeIdentifier>
631 </Class>
632 <Class Name="WixToolset.Dtf.WindowsInstaller.InvalidHandleException" Collapsed="true">
633 <Position X="14.25" Y="9" Width="2.25" />
634 <Members>
635 <Method Name="InvalidHandleException" Hidden="true" />
636 </Members>
637 <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerException" FixedFromPoint="true" FixedToPoint="true">
638 <Path>
639 <Point X="13.812" Y="9.119" />
640 <Point X="13.812" Y="9.375" />
641 <Point X="14.25" Y="9.375" />
642 </Path>
643 </InheritanceLine>
644 <TypeIdentifier>
645 <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
646 <FileName>Exceptions.cs</FileName>
647 </TypeIdentifier>
648 </Class>
649 <Class Name="WixToolset.Dtf.WindowsInstaller.MergeException">
650 <Position X="14.25" Y="7.5" Width="2.25" />
651 <Members>
652 <Field Name="conflictCounts" Hidden="true" />
653 <Field Name="conflictTables" Hidden="true" />
654 <Method Name="GetObjectData" Hidden="true" />
655 <Method Name="MergeException" Hidden="true" />
656 </Members>
657 <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerException" FixedFromPoint="true">
658 <Path>
659 <Point X="14" Y="8.813" />
660 <Point X="14.25" Y="8.813" />
661 </Path>
662 </InheritanceLine>
663 <TypeIdentifier>
664 <HashCode>AAAAAAAAAAAAAEAAEABAABAgAAAAAAAAAAAAIAAAAAA=</HashCode>
665 <FileName>Exceptions.cs</FileName>
666 </TypeIdentifier>
667 </Class>
668 <Struct Name="WixToolset.Dtf.WindowsInstaller.MediaDisk">
669 <Position X="17" Y="0.5" Width="2" />
670 <Members>
671 <Field Name="diskId" Hidden="true" />
672 <Field Name="diskPrompt" Hidden="true" />
673 <Method Name="MediaDisk" Hidden="true" />
674 <Field Name="volumeLabel" Hidden="true" />
675 </Members>
676 <TypeIdentifier>
677 <HashCode>AgAAAAAAAAAAAAAAAgAAIAAAACAAAAAEAAAABAAAAAA=</HashCode>
678 <FileName>MediaDisk.cs</FileName>
679 </TypeIdentifier>
680 </Struct>
681 <Struct Name="WixToolset.Dtf.WindowsInstaller.InstallCost">
682 <Position X="10" Y="8" Width="1.25" />
683 <Members>
684 <Field Name="cost" Hidden="true" />
685 <Field Name="driveName" Hidden="true" />
686 <Method Name="InstallCost" Hidden="true" />
687 <Field Name="tempCost" Hidden="true" />
688 </Members>
689 <TypeIdentifier>
690 <HashCode>AAAAAAAAAgAAAAIAAAAACBAAAAgQAAAAAAAAAAAAAAA=</HashCode>
691 <FileName>InstallCost.cs</FileName>
692 </TypeIdentifier>
693 </Struct>
694 <Struct Name="WixToolset.Dtf.WindowsInstaller.ShortcutTarget">
695 <Position X="19.5" Y="7.75" Width="1.5" />
696 <Members>
697 <Field Name="componentCode" Hidden="true" />
698 <Method Name="Equals" Hidden="true" />
699 <Field Name="feature" Hidden="true" />
700 <Method Name="GetHashCode" Hidden="true" />
701 <Method Name="operator !=" Hidden="true" />
702 <Method Name="operator ==" Hidden="true" />
703 <Field Name="productCode" Hidden="true" />
704 <Method Name="ShortcutTarget" Hidden="true" />
705 </Members>
706 <TypeIdentifier>
707 <HashCode>AAAgAAACAAAAAAAAgAAAAAAAAAAAAIAIAAIACAAAIiA=</HashCode>
708 <FileName>ShortcutTarget.cs</FileName>
709 </TypeIdentifier>
710 </Struct>
711 <Enum Name="WixToolset.Dtf.WindowsInstaller.ActionResult" Collapsed="true">
712 <Position X="22" Y="1" Width="2" />
713 <TypeIdentifier>
714 <HashCode>AAAAAAAAAAAAAAQgAAAAAAAAAgAAAACAAAAAAAACAAA=</HashCode>
715 <FileName>Enums.cs</FileName>
716 </TypeIdentifier>
717 </Enum>
718 <Enum Name="WixToolset.Dtf.WindowsInstaller.ControlAttributes" Collapsed="true">
719 <Position X="24.25" Y="1" Width="2" />
720 <TypeIdentifier>
721 <HashCode>AADAAlQAAAEgAoNFAAgAACAgAQAAAooMAAAAgCkCAkA=</HashCode>
722 <FileName>ColumnEnums.cs</FileName>
723 </TypeIdentifier>
724 </Enum>
725 <Enum Name="WixToolset.Dtf.WindowsInstaller.CustomActionTypes" Collapsed="true">
726 <Position X="24.25" Y="1.5" Width="2" />
727 <TypeIdentifier>
728 <HashCode>AABCCFAAAAAhJAAHAAAAAoAAAAAAAAABCAIEEQEAgAA=</HashCode>
729 <FileName>ColumnEnums.cs</FileName>
730 </TypeIdentifier>
731 </Enum>
732 <Enum Name="WixToolset.Dtf.WindowsInstaller.DatabaseOpenMode" Collapsed="true">
733 <Position X="22" Y="1.5" Width="2" />
734 <TypeIdentifier>
735 <HashCode>AAAKAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAYAAAAAAA=</HashCode>
736 <FileName>Enums.cs</FileName>
737 </TypeIdentifier>
738 </Enum>
739 <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallLogModes" Collapsed="true">
740 <Position X="22" Y="2" Width="2" />
741 <TypeIdentifier>
742 <HashCode>AAABgEBAAIAAAQBAAAAAABEAAQAAABSAgAAEAEWQAAA=</HashCode>
743 <FileName>Enums.cs</FileName>
744 </TypeIdentifier>
745 </Enum>
746 <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallMessage" Collapsed="true">
747 <Position X="22" Y="2.5" Width="2" />
748 <TypeIdentifier>
749 <HashCode>AAABgABAAAAAAQBAAAAAABEAAQAAABSAgAAEAECAAAA=</HashCode>
750 <FileName>Enums.cs</FileName>
751 </TypeIdentifier>
752 </Enum>
753 <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallMode" Collapsed="true">
754 <Position X="22" Y="3" Width="2" />
755 <TypeIdentifier>
756 <HashCode>AAAABAAAQAAAAAAAAAAAIAAAACAAAAAAAAAAAAAAAAA=</HashCode>
757 <FileName>Enums.cs</FileName>
758 </TypeIdentifier>
759 </Enum>
760 <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallRunMode" Collapsed="true">
761 <Position X="22" Y="3.5" Width="2" />
762 <TypeIdentifier>
763 <HashCode>AAgAIEACAAQAAAQCAAAAAQAACEAABAQAgAECAAAAAgA=</HashCode>
764 <FileName>Enums.cs</FileName>
765 </TypeIdentifier>
766 </Enum>
767 <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallState" Collapsed="true">
768 <Position X="22" Y="4" Width="2" />
769 <TypeIdentifier>
770 <HashCode>AAAAABAAAgAAAFACgACQJAAAAABQAAQAAAAAAAAAABA=</HashCode>
771 <FileName>Enums.cs</FileName>
772 </TypeIdentifier>
773 </Enum>
774 <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallType" Collapsed="true">
775 <Position X="22" Y="4.5" Width="2" />
776 <TypeIdentifier>
777 <HashCode>AAAAAAAAAAAAAAAAIAAAIAAAAAAAAAAAAgAAAAAAAAA=</HashCode>
778 <FileName>Enums.cs</FileName>
779 </TypeIdentifier>
780 </Enum>
781 <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallUIOptions" Collapsed="true">
782 <Position X="22" Y="5" Width="2" />
783 <TypeIdentifier>
784 <HashCode>AAAAAAAIACAAAAAAQAAAIACEAAAAAAAAAgIAAAGAAAA=</HashCode>
785 <FileName>Enums.cs</FileName>
786 </TypeIdentifier>
787 </Enum>
788 <Enum Name="WixToolset.Dtf.WindowsInstaller.MessageResult" Collapsed="true">
789 <Position X="22" Y="5.5" Width="2" />
790 <TypeIdentifier>
791 <HashCode>AAAAAAAASAAQASBAAAAAAAAAACAAAAAAAAABAAEAAAA=</HashCode>
792 <FileName>Enums.cs</FileName>
793 </TypeIdentifier>
794 </Enum>
795 <Enum Name="WixToolset.Dtf.WindowsInstaller.ReinstallModes" Collapsed="true">
796 <Position X="22" Y="6" Width="2" />
797 <TypeIdentifier>
798 <HashCode>IAAAAAAAAACAAAAAAAAQAAAQBAAAABAAAAEAAgAIABA=</HashCode>
799 <FileName>Enums.cs</FileName>
800 </TypeIdentifier>
801 </Enum>
802 <Enum Name="WixToolset.Dtf.WindowsInstaller.TransformErrors" Collapsed="true">
803 <Position X="22" Y="6.5" Width="2" />
804 <TypeIdentifier>
805 <HashCode>AAIEAAAAAAABAAAAAAAAAAQAAAAAAAACAAAgAAEAABA=</HashCode>
806 <FileName>Enums.cs</FileName>
807 </TypeIdentifier>
808 </Enum>
809 <Enum Name="WixToolset.Dtf.WindowsInstaller.TransformValidations" Collapsed="true">
810 <Position X="22" Y="7" Width="2" />
811 <TypeIdentifier>
812 <HashCode>AAACAAAACAQIAAAAAAAAAAAAAEgAAAQAAIAQAQEAAAQ=</HashCode>
813 <FileName>Enums.cs</FileName>
814 </TypeIdentifier>
815 </Enum>
816 <Enum Name="WixToolset.Dtf.WindowsInstaller.UserContexts" Collapsed="true">
817 <Position X="22" Y="7.5" Width="2" />
818 <TypeIdentifier>
819 <HashCode>AAAAAAgAAAAAAAAEAAAAAAIAAAAAAAAAAIAAQAEAAAA=</HashCode>
820 <FileName>Enums.cs</FileName>
821 </TypeIdentifier>
822 </Enum>
823 <Enum Name="WixToolset.Dtf.WindowsInstaller.ValidationError" Collapsed="true">
824 <Position X="22" Y="8" Width="2" />
825 <TypeIdentifier>
826 <HashCode>oCMBAAABEAMAAAAAAAAQAAIASigAABKBABgCgEEAAA4=</HashCode>
827 <FileName>Enums.cs</FileName>
828 </TypeIdentifier>
829 </Enum>
830 <Enum Name="WixToolset.Dtf.WindowsInstaller.ViewModifyMode" Collapsed="true">
831 <Position X="22" Y="8.5" Width="2" />
832 <TypeIdentifier>
833 <HashCode>IAQAAEQAAADgAAAACAAAAACAAAEAAQAAAAAAAAAAACA=</HashCode>
834 <FileName>Enums.cs</FileName>
835 </TypeIdentifier>
836 </Enum>
837 <Enum Name="WixToolset.Dtf.WindowsInstaller.ComponentAttributes" Collapsed="true">
838 <Position X="24.25" Y="2" Width="2" />
839 <TypeIdentifier>
840 <HashCode>AAABAAAAIAAAAAAAEAAACAAAIAAAgQCAAAAgAAEAAAA=</HashCode>
841 <FileName>ColumnEnums.cs</FileName>
842 </TypeIdentifier>
843 </Enum>
844 <Enum Name="WixToolset.Dtf.WindowsInstaller.DialogAttributes" Collapsed="true">
845 <Position X="24.25" Y="2.5" Width="2" />
846 <TypeIdentifier>
847 <HashCode>AAAIAAQAAAAAAQIEAAAAACAAEAAAAAAIgAAggAAAAgA=</HashCode>
848 <FileName>ColumnEnums.cs</FileName>
849 </TypeIdentifier>
850 </Enum>
851 <Enum Name="WixToolset.Dtf.WindowsInstaller.FeatureAttributes" Collapsed="true">
852 <Position X="24.25" Y="3" Width="2" />
853 <TypeIdentifier>
854 <HashCode>AAAAQEAAAEAAAAAAAAAAAAAAEAAAAAABABAAAAEAAAA=</HashCode>
855 <FileName>ColumnEnums.cs</FileName>
856 </TypeIdentifier>
857 </Enum>
858 <Enum Name="WixToolset.Dtf.WindowsInstaller.FileAttributes" Collapsed="true">
859 <Position X="24.25" Y="3.5" Width="2" />
860 <TypeIdentifier>
861 <HashCode>AAAAAABAAIAIAAAAAAAAAgAAAAAEAAAAAAEIAAEEAAA=</HashCode>
862 <FileName>ColumnEnums.cs</FileName>
863 </TypeIdentifier>
864 </Enum>
865 <Enum Name="WixToolset.Dtf.WindowsInstaller.IniFileAction" Collapsed="true">
866 <Position X="24.25" Y="4" Width="2" />
867 <TypeIdentifier>
868 <HashCode>AggAAAAAAAAAEAEAAAAAAAAAAAABAAAAAAAAAAAAAAA=</HashCode>
869 <FileName>ColumnEnums.cs</FileName>
870 </TypeIdentifier>
871 </Enum>
872 <Enum Name="WixToolset.Dtf.WindowsInstaller.LocatorTypes" Collapsed="true">
873 <Position X="24.25" Y="4.5" Width="2" />
874 <TypeIdentifier>
875 <HashCode>ABAAABAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAABAAAA=</HashCode>
876 <FileName>ColumnEnums.cs</FileName>
877 </TypeIdentifier>
878 </Enum>
879 <Enum Name="WixToolset.Dtf.WindowsInstaller.RegistryRoot" Collapsed="true">
880 <Position X="24.25" Y="5" Width="2" />
881 <TypeIdentifier>
882 <HashCode>AACgACAAAIAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAA=</HashCode>
883 <FileName>ColumnEnums.cs</FileName>
884 </TypeIdentifier>
885 </Enum>
886 <Enum Name="WixToolset.Dtf.WindowsInstaller.RemoveFileModes" Collapsed="true">
887 <Position X="24.25" Y="5.5" Width="2" />
888 <TypeIdentifier>
889 <HashCode>AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAACAAAEAAAA=</HashCode>
890 <FileName>ColumnEnums.cs</FileName>
891 </TypeIdentifier>
892 </Enum>
893 <Enum Name="WixToolset.Dtf.WindowsInstaller.ServiceAttributes" Collapsed="true">
894 <Position X="24.25" Y="6" Width="2" />
895 <TypeIdentifier>
896 <HashCode>AQCAAAAAQAAAAAAAAACAAAgAAAAAAAAAAgAAABEAAAI=</HashCode>
897 <FileName>ColumnEnums.cs</FileName>
898 </TypeIdentifier>
899 </Enum>
900 <Enum Name="WixToolset.Dtf.WindowsInstaller.ServiceControlEvents" Collapsed="true">
901 <Position X="24.25" Y="6.5" Width="2" />
902 <TypeIdentifier>
903 <HashCode>AAAAAAAAACAAAAAAAAAAEAAAAAEAAgAAJAAAAAEAAAA=</HashCode>
904 <FileName>ColumnEnums.cs</FileName>
905 </TypeIdentifier>
906 </Enum>
907 <Enum Name="WixToolset.Dtf.WindowsInstaller.TextStyles" Collapsed="true">
908 <Position X="24.25" Y="7" Width="2" />
909 <TypeIdentifier>
910 <HashCode>AAAAAAAAAAAAACAAAIAAAAAAAAACAAAAAAAAAAAAAAE=</HashCode>
911 <FileName>ColumnEnums.cs</FileName>
912 </TypeIdentifier>
913 </Enum>
914 <Enum Name="WixToolset.Dtf.WindowsInstaller.UpgradeAttributes" Collapsed="true">
915 <Position X="24.25" Y="7.5" Width="2" />
916 <TypeIdentifier>
917 <HashCode>AEAAAAAAAAAAAAAQAAAAAAAAAAgIAAAAABAAAAAAIAA=</HashCode>
918 <FileName>ColumnEnums.cs</FileName>
919 </TypeIdentifier>
920 </Enum>
921 <Delegate Name="WixToolset.Dtf.WindowsInstaller.InapplicablePatchHandler" Collapsed="true">
922 <Position X="16.75" Y="9.75" Width="2.25" />
923 <TypeIdentifier>
924 <HashCode>AAAAAAAAAACAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAA=</HashCode>
925 <FileName>Installer.cs</FileName>
926 </TypeIdentifier>
927 </Delegate>
928 <Delegate Name="WixToolset.Dtf.WindowsInstaller.ExternalUIHandler" Collapsed="true">
929 <Position X="16.75" Y="8.5" Width="2.25" />
930 <TypeIdentifier>
931 <HashCode>AAAEAAAAAAAAABAAAAAABAAAAACBAAAAAAAAAAAAAAA=</HashCode>
932 <FileName>ExternalUIHandler.cs</FileName>
933 </TypeIdentifier>
934 </Delegate>
935 <Delegate Name="WixToolset.Dtf.WindowsInstaller.ExternalUIRecordHandler" Collapsed="true">
936 <Position X="16.75" Y="9" Width="2.25" />
937 <TypeIdentifier>
938 <HashCode>AAAEAAAAAAAAAAAAAAAABAAAAACBAAAAAAAAAAAAAAA=</HashCode>
939 <FileName>ExternalUIHandler.cs</FileName>
940 </TypeIdentifier>
941 </Delegate>
942 <Font Name="Verdana" Size="8" />
943</ClassDiagram> \ No newline at end of file
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/WixToolset.Dtf.WindowsInstaller.csproj b/src/dtf/WixToolset.Dtf.WindowsInstaller/WixToolset.Dtf.WindowsInstaller.csproj
new file mode 100644
index 00000000..515e609b
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/WixToolset.Dtf.WindowsInstaller.csproj
@@ -0,0 +1,30 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <RootNamespace>WixToolset.Dtf.WindowsInstaller</RootNamespace>
7 <AssemblyName>WixToolset.Dtf.WindowsInstaller</AssemblyName>
8 <TargetFrameworks>netstandard2.0;net20</TargetFrameworks>
9 <Description>Managed libraries for Windows Installer</Description>
10 <CreateDocumentationFile>true</CreateDocumentationFile>
11 </PropertyGroup>
12
13 <ItemGroup>
14 <EmbeddedResource Include="Errors.resources" />
15 </ItemGroup>
16
17 <ItemGroup>
18 <None Include="Errors.txt" />
19 <None Include="WindowsInstaller.cd" />
20 </ItemGroup>
21
22 <ItemGroup>
23 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
24 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
25 </ItemGroup>
26
27 <ItemGroup Condition=" '$(TargetFramework)'=='net20' ">
28 <Reference Include="System.Configuration" />
29 </ItemGroup>
30</Project>
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/customactiondata.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/customactiondata.cs
new file mode 100644
index 00000000..88a0295d
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/customactiondata.cs
@@ -0,0 +1,469 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.IO;
7 using System.Xml;
8 using System.Xml.Serialization;
9 using System.Text;
10 using System.Collections.Generic;
11 using System.Globalization;
12 using System.Diagnostics.CodeAnalysis;
13
14 /// <summary>
15 /// Contains a collection of key-value pairs suitable for passing between
16 /// immediate and deferred/rollback/commit custom actions.
17 /// </summary>
18 /// <remarks>
19 /// Call the <see cref="CustomActionData.ToString" /> method to get a string
20 /// suitable for storing in a property and reconstructing the custom action data later.
21 /// </remarks>
22 /// <seealso cref="Session.CustomActionData"/>
23 /// <seealso cref="Session.DoAction(string,CustomActionData)"/>
24 [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
25 public sealed class CustomActionData : IDictionary<string, string>
26 {
27 /// <summary>
28 /// "CustomActionData" literal property name.
29 /// </summary>
30 public const string PropertyName = "CustomActionData";
31
32 private const char DataSeparator = ';';
33 private const char KeyValueSeparator = '=';
34
35 private IDictionary<string, string> data;
36
37 /// <summary>
38 /// Creates a new empty custom action data object.
39 /// </summary>
40 public CustomActionData() : this(null)
41 {
42 }
43
44 /// <summary>
45 /// Reconstructs a custom action data object from data that was previously
46 /// persisted in a string.
47 /// </summary>
48 /// <param name="keyValueList">Previous output from <see cref="CustomActionData.ToString" />.</param>
49 public CustomActionData(string keyValueList)
50 {
51 this.data = new Dictionary<string, string>();
52
53 if (keyValueList != null)
54 {
55 this.Parse(keyValueList);
56 }
57 }
58
59 /// <summary>
60 /// Adds a key and value to the data collection.
61 /// </summary>
62 /// <param name="key">Case-sensitive data key.</param>
63 /// <param name="value">Data value (may be null).</param>
64 /// <exception cref="ArgumentException">the key does not consist solely of letters,
65 /// numbers, and the period, underscore, and space characters.</exception>
66 public void Add(string key, string value)
67 {
68 CustomActionData.ValidateKey(key);
69 this.data.Add(key, value);
70 }
71
72 /// <summary>
73 /// Adds a value to the data collection, using XML serialization to persist the object as a string.
74 /// </summary>
75 /// <param name="key">Case-sensitive data key.</param>
76 /// <param name="value">Data value (may be null).</param>
77 /// <exception cref="ArgumentException">the key does not consist solely of letters,
78 /// numbers, and the period, underscore, and space characters.</exception>
79 /// <exception cref="NotSupportedException">The value type does not support XML serialization.</exception>
80 /// <exception cref="InvalidOperationException">The value could not be serialized.</exception>
81 public void AddObject<T>(string key, T value)
82 {
83 if (value == null)
84 {
85 this.Add(key, null);
86 }
87 else if (typeof(T) == typeof(string) ||
88 typeof(T) == typeof(CustomActionData)) // Serialize nested CustomActionData
89 {
90 this.Add(key, value.ToString());
91 }
92 else
93 {
94 string valueString = CustomActionData.Serialize<T>(value);
95 this.Add(key, valueString);
96 }
97 }
98
99 /// <summary>
100 /// Gets a value from the data collection, using XML serialization to load the object from a string.
101 /// </summary>
102 /// <param name="key">Case-sensitive data key.</param>
103 /// <exception cref="InvalidOperationException">The value could not be deserialized.</exception>
104 [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
105 public T GetObject<T>(string key)
106 {
107 string value = this[key];
108 if (value == null)
109 {
110 return default(T);
111 }
112 else if (typeof(T) == typeof(string))
113 {
114 // Funny casting because the compiler doesn't know T is string here.
115 return (T) (object) value;
116 }
117 else if (typeof(T) == typeof(CustomActionData))
118 {
119 // Deserialize nested CustomActionData.
120 return (T) (object) new CustomActionData(value);
121 }
122 else if (value.Length == 0)
123 {
124 return default(T);
125 }
126 else
127 {
128 return CustomActionData.Deserialize<T>(value);
129 }
130 }
131
132 /// <summary>
133 /// Determines whether the data contains an item with the specified key.
134 /// </summary>
135 /// <param name="key">Case-sensitive data key.</param>
136 /// <returns>true if the data contains an item with the key; otherwise, false</returns>
137 public bool ContainsKey(string key)
138 {
139 return this.data.ContainsKey(key);
140 }
141
142 /// <summary>
143 /// Gets a collection object containing all the keys of the data.
144 /// </summary>
145 public ICollection<string> Keys
146 {
147 get
148 {
149 return this.data.Keys;
150 }
151 }
152
153 /// <summary>
154 /// Removes the item with the specified key from the data.
155 /// </summary>
156 /// <param name="key">Case-sensitive data key.</param>
157 /// <returns>true if the item was successfully removed from the data;
158 /// false if an item with the specified key was not found</returns>
159 public bool Remove(string key)
160 {
161 return this.data.Remove(key);
162 }
163
164 /// <summary>
165 /// Gets the value with the specified key.
166 /// </summary>
167 /// <param name="key">Case-sensitive data key.</param>
168 /// <param name="value">Value associated with the specified key, or
169 /// null if an item with the specified key was not found</param>
170 /// <returns>true if the data contains an item with the specified key; otherwise, false.</returns>
171 public bool TryGetValue(string key, out string value)
172 {
173 return this.data.TryGetValue(key, out value);
174 }
175
176 /// <summary>
177 /// Gets a collection containing all the values of the data.
178 /// </summary>
179 public ICollection<string> Values
180 {
181 get
182 {
183 return this.data.Values;
184 }
185 }
186
187 /// <summary>
188 /// Gets or sets a data value with a specified key.
189 /// </summary>
190 /// <param name="key">Case-sensitive data key.</param>
191 /// <exception cref="ArgumentException">the key does not consist solely of letters,
192 /// numbers, and the period, underscore, and space characters.</exception>
193 public string this[string key]
194 {
195 get
196 {
197 return this.data[key];
198 }
199 set
200 {
201 CustomActionData.ValidateKey(key);
202 this.data[key] = value;
203 }
204 }
205
206 /// <summary>
207 /// Adds an item with key and value to the data collection.
208 /// </summary>
209 /// <param name="item">Case-sensitive data key, with a data value that may be null.</param>
210 /// <exception cref="ArgumentException">the key does not consist solely of letters,
211 /// numbers, and the period, underscore, and space characters.</exception>
212 public void Add(KeyValuePair<string, string> item)
213 {
214 CustomActionData.ValidateKey(item.Key);
215 this.data.Add(item);
216 }
217
218 /// <summary>
219 /// Removes all items from the data.
220 /// </summary>
221 public void Clear()
222 {
223 if (this.data.Count > 0)
224 {
225 this.data.Clear();
226 }
227 }
228
229 /// <summary>
230 /// Determines whether the data contains a specified item.
231 /// </summary>
232 /// <param name="item">The data item to locate.</param>
233 /// <returns>true if the data contains the item; otherwise, false</returns>
234 public bool Contains(KeyValuePair<string, string> item)
235 {
236 return this.data.Contains(item);
237 }
238
239 /// <summary>
240 /// Copies the data to an array, starting at a particular array index.
241 /// </summary>
242 /// <param name="array">Destination array.</param>
243 /// <param name="arrayIndex">Index in the array at which copying begins.</param>
244 public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
245 {
246 this.data.CopyTo(array, arrayIndex);
247 }
248
249 /// <summary>
250 /// Gets the number of items in the data.
251 /// </summary>
252 public int Count
253 {
254 get
255 {
256 return this.data.Count;
257 }
258 }
259
260 /// <summary>
261 /// Gets a value indicating whether the data is read-only.
262 /// </summary>
263 public bool IsReadOnly
264 {
265 get
266 {
267 return false;
268 }
269 }
270
271 /// <summary>
272 /// Removes an item from the data.
273 /// </summary>
274 /// <param name="item">The item to remove.</param>
275 /// <returns>true if the item was successfully removed from the data;
276 /// false if the item was not found</returns>
277 public bool Remove(KeyValuePair<string, string> item)
278 {
279 return this.data.Remove(item);
280 }
281
282 /// <summary>
283 /// Returns an enumerator that iterates through the collection.
284 /// </summary>
285 /// <returns>An enumerator that can be used to iterate through the collection.</returns>
286 public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
287 {
288 return this.data.GetEnumerator();
289 }
290
291 /// <summary>
292 /// Returns an enumerator that iterates through the collection.
293 /// </summary>
294 /// <returns>An enumerator that can be used to iterate through the collection.</returns>
295 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
296 {
297 return ((System.Collections.IEnumerable) this.data).GetEnumerator();
298 }
299
300 /// <summary>
301 /// Gets a string representation of the data suitable for persisting in a property.
302 /// </summary>
303 /// <returns>Data string in the form "Key1=Value1;Key2=Value2"</returns>
304 public override string ToString()
305 {
306 StringBuilder buf = new StringBuilder();
307
308 foreach (KeyValuePair<string, string> item in this.data)
309 {
310 if (buf.Length > 0)
311 {
312 buf.Append(CustomActionData.DataSeparator);
313 }
314
315 buf.Append(item.Key);
316
317 if (item.Value != null)
318 {
319 buf.Append(CustomActionData.KeyValueSeparator);
320 buf.Append(CustomActionData.Escape(item.Value));
321 }
322 }
323
324 return buf.ToString();
325 }
326
327 /// <summary>
328 /// Ensures that a key contains valid characters.
329 /// </summary>
330 /// <param name="key">key to be validated</param>
331 /// <exception cref="ArgumentException">the key does not consist solely of letters,
332 /// numbers, and the period, underscore, and space characters.</exception>
333 private static void ValidateKey(string key)
334 {
335 if (String.IsNullOrEmpty(key))
336 {
337 throw new ArgumentNullException("key");
338 }
339
340 for (int i = 0; i < key.Length; i++)
341 {
342 char c = key[i];
343 if (!Char.IsLetterOrDigit(c) && c != '_' && c != '.' &&
344 !(i > 0 && i < key.Length - 1 && c == ' '))
345 {
346 throw new ArgumentOutOfRangeException("key");
347 }
348 }
349 }
350
351 /// <summary>
352 /// Serializes a value into an XML string.
353 /// </summary>
354 /// <typeparam name="T">Type of the value.</typeparam>
355 /// <param name="value">Value to be serialized.</param>
356 /// <returns>Serialized value data as a string.</returns>
357 private static string Serialize<T>(T value)
358 {
359 XmlWriterSettings xws = new XmlWriterSettings();
360 xws.OmitXmlDeclaration = true;
361
362 StringWriter sw = new StringWriter(CultureInfo.InvariantCulture);
363 using (XmlWriter xw = XmlWriter.Create(sw, xws))
364 {
365 XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
366 ns.Add(string.Empty, String.Empty); // Prevent output of any namespaces
367
368 XmlSerializer ser = new XmlSerializer(typeof(T));
369 ser.Serialize(xw, value, ns);
370
371 return sw.ToString();
372 }
373 }
374
375 /// <summary>
376 /// Deserializes a value from an XML string.
377 /// </summary>
378 /// <typeparam name="T">Expected type of the value.</typeparam>
379 /// <param name="value">Serialized value data.</param>
380 /// <returns>Deserialized value object.</returns>
381 private static T Deserialize<T>(string value)
382 {
383 StringReader sr = new StringReader(value);
384 using (XmlReader xr = XmlReader.Create(sr))
385 {
386 XmlSerializer ser = new XmlSerializer(typeof(T));
387 return (T) ser.Deserialize(xr);
388 }
389 }
390
391 /// <summary>
392 /// Escapes a value string by doubling any data-separator (semicolon) characters.
393 /// </summary>
394 /// <param name="value"></param>
395 /// <returns>Escaped value string</returns>
396 private static string Escape(string value)
397 {
398 value = value.Replace(String.Empty + CustomActionData.DataSeparator, String.Empty + CustomActionData.DataSeparator + CustomActionData.DataSeparator);
399 return value;
400 }
401
402 /// <summary>
403 /// Unescapes a value string by undoubling any doubled data-separator (semicolon) characters.
404 /// </summary>
405 /// <param name="value"></param>
406 /// <returns>Unescaped value string</returns>
407 private static string Unescape(string value)
408 {
409 value = value.Replace(String.Empty + CustomActionData.DataSeparator + CustomActionData.DataSeparator, String.Empty + CustomActionData.DataSeparator);
410 return value;
411 }
412
413 /// <summary>
414 /// Loads key-value pairs from a string into the data collection.
415 /// </summary>
416 /// <param name="keyValueList">key-value pair list of the form returned by <see cref="ToString"/></param>
417 private void Parse(string keyValueList)
418 {
419 int itemStart = 0;
420 while (itemStart < keyValueList.Length)
421 {
422 // Find the next non-escaped data separator.
423 int semi = itemStart - 2;
424 do
425 {
426 semi = keyValueList.IndexOf(CustomActionData.DataSeparator, semi + 2);
427 }
428 while (semi >= 0 && semi < keyValueList.Length - 1 && keyValueList[semi + 1] == CustomActionData.DataSeparator);
429
430 if (semi < 0)
431 {
432 semi = keyValueList.Length;
433 }
434
435 // Find the next non-escaped key-value separator.
436 int equals = itemStart - 2;
437 do
438 {
439 equals = keyValueList.IndexOf(CustomActionData.KeyValueSeparator, equals + 2);
440 }
441 while (equals >= 0 && equals < keyValueList.Length - 1 && keyValueList[equals + 1] == CustomActionData.KeyValueSeparator);
442
443 if (equals < 0 || equals > semi)
444 {
445 equals = semi;
446 }
447
448 string key = keyValueList.Substring(itemStart, equals - itemStart);
449 string value = null;
450
451 // If there's a key-value separator before the next data separator, then the item has a value.
452 if (equals < semi)
453 {
454 value = keyValueList.Substring(equals + 1, semi - (equals + 1));
455 value = CustomActionData.Unescape(value);
456 }
457
458 // Add non-duplicate items to the collection.
459 if (key.Length > 0 && !this.data.ContainsKey(key))
460 {
461 this.data.Add(key, value);
462 }
463
464 // Move past the data separator to the next item.
465 itemStart = semi + 1;
466 }
467 }
468 }
469}
diff --git a/src/dtf/WixToolset.Dtf.sln b/src/dtf/WixToolset.Dtf.sln
new file mode 100644
index 00000000..7e58ad8d
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.sln
@@ -0,0 +1,281 @@
1
2Microsoft Visual Studio Solution File, Format Version 12.00
3# Visual Studio 15
4VisualStudioVersion = 15.0.26730.8
5MinimumVisualStudioVersion = 15.0.26124.0
6Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.Compression", "src\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj", "{2D62850C-9F81-4BE9-BDF3-9379389C8F7B}"
7EndProject
8Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.Compression.Cab", "src\WixToolset.Dtf.Compression.Cab\WixToolset.Dtf.Compression.Cab.csproj", "{15895FD1-DD68-407B-8717-08F6DD14F02C}"
9EndProject
10Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.Compression.Zip", "src\WixToolset.Dtf.Compression.Zip\WixToolset.Dtf.Compression.Zip.csproj", "{261F2857-B521-42A4-A3E0-B5165F225E50}"
11EndProject
12Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.Resources", "src\WixToolset.Dtf.Resources\WixToolset.Dtf.Resources.csproj", "{44931ECB-8D6F-4C12-A872-64E261B6A98E}"
13EndProject
14Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.WindowsInstaller", "src\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj", "{24121677-0ED0-41B5-833F-1B9A18E87BF4}"
15EndProject
16Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.WindowsInstaller.Linq", "src\WixToolset.Dtf.WindowsInstaller.Linq\WixToolset.Dtf.WindowsInstaller.Linq.csproj", "{CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}"
17EndProject
18Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.WindowsInstaller.Package", "src\WixToolset.Dtf.WindowsInstaller.Package\WixToolset.Dtf.WindowsInstaller.Package.csproj", "{1A9940A7-3E29-4428-B753-C4CC66058F1A}"
19EndProject
20Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.Compression", "src\WixToolsetTests.Dtf.Compression\WixToolsetTests.Dtf.Compression.csproj", "{F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}"
21EndProject
22Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.Compression.Cab", "src\WixToolsetTests.Dtf.Compression.Cab\WixToolsetTests.Dtf.Compression.Cab.csproj", "{4544158C-2D63-4146-85FF-62169280144E}"
23EndProject
24Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.Compression.Zip", "src\WixToolsetTests.Dtf.Compression.Zip\WixToolsetTests.Dtf.Compression.Zip.csproj", "{328799BB-7B03-4B28-8180-4132211FD07D}"
25EndProject
26Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.WindowsInstaller", "src\WixToolsetTests.Dtf.WindowsInstaller\WixToolsetTests.Dtf.WindowsInstaller.csproj", "{16F5202F-9276-4166-975C-C9654BAF8012}"
27EndProject
28Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.WindowsInstaller.CustomActions", "src\WixToolsetTests.Dtf.WindowsInstaller.CustomActions\WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj", "{137D376B-989F-4FEA-9A67-01D8D38CA0DE}"
29EndProject
30Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.WindowsInstaller.Linq", "src\WixToolsetTests.Dtf.WindowsInstaller.Linq\WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj", "{4F55F9B8-D8B6-41EB-8796-221B4CD98324}"
31EndProject
32Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{A988A768-200F-408F-AE3B-5D9B14AA48EE}"
33EndProject
34Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SfxCA", "src\Tools\SfxCA\SfxCA.vcxproj", "{55D5BA28-D427-4F53-80C2-FE9EF23C1553}"
35EndProject
36Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MakeSfxCA", "src\Tools\MakeSfxCA\MakeSfxCA.csproj", "{3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}"
37EndProject
38Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{4A47EC94-8234-4479-87BC-3D924DB7AA4E}"
39EndProject
40Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedCA", "src\Samples\ManagedCA\ManagedCA.csproj", "{DB9E5F02-8241-440A-9B60-980EB5B42B13}"
41EndProject
42Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbeddedUI", "src\Samples\EmbeddedUI\EmbeddedUI.csproj", "{864B8C50-7895-4485-AC89-900D86FD8C0D}"
43EndProject
44Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.MSBuild", "src\WixToolset.Dtf.MSBuild\WixToolset.Dtf.MSBuild.csproj", "{E7A00377-A0B5-400F-8337-C0814AAC7153}"
45EndProject
46Global
47 GlobalSection(SolutionConfigurationPlatforms) = preSolution
48 Debug|Any CPU = Debug|Any CPU
49 Debug|x64 = Debug|x64
50 Debug|x86 = Debug|x86
51 Release|Any CPU = Release|Any CPU
52 Release|x64 = Release|x64
53 Release|x86 = Release|x86
54 EndGlobalSection
55 GlobalSection(ProjectConfigurationPlatforms) = postSolution
56 {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
57 {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
58 {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Debug|x64.ActiveCfg = Debug|Any CPU
59 {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Debug|x64.Build.0 = Debug|Any CPU
60 {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Debug|x86.ActiveCfg = Debug|Any CPU
61 {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Debug|x86.Build.0 = Debug|Any CPU
62 {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
63 {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Release|Any CPU.Build.0 = Release|Any CPU
64 {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Release|x64.ActiveCfg = Release|Any CPU
65 {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Release|x64.Build.0 = Release|Any CPU
66 {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Release|x86.ActiveCfg = Release|Any CPU
67 {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Release|x86.Build.0 = Release|Any CPU
68 {15895FD1-DD68-407B-8717-08F6DD14F02C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
69 {15895FD1-DD68-407B-8717-08F6DD14F02C}.Debug|Any CPU.Build.0 = Debug|Any CPU
70 {15895FD1-DD68-407B-8717-08F6DD14F02C}.Debug|x64.ActiveCfg = Debug|Any CPU
71 {15895FD1-DD68-407B-8717-08F6DD14F02C}.Debug|x64.Build.0 = Debug|Any CPU
72 {15895FD1-DD68-407B-8717-08F6DD14F02C}.Debug|x86.ActiveCfg = Debug|Any CPU
73 {15895FD1-DD68-407B-8717-08F6DD14F02C}.Debug|x86.Build.0 = Debug|Any CPU
74 {15895FD1-DD68-407B-8717-08F6DD14F02C}.Release|Any CPU.ActiveCfg = Release|Any CPU
75 {15895FD1-DD68-407B-8717-08F6DD14F02C}.Release|Any CPU.Build.0 = Release|Any CPU
76 {15895FD1-DD68-407B-8717-08F6DD14F02C}.Release|x64.ActiveCfg = Release|Any CPU
77 {15895FD1-DD68-407B-8717-08F6DD14F02C}.Release|x64.Build.0 = Release|Any CPU
78 {15895FD1-DD68-407B-8717-08F6DD14F02C}.Release|x86.ActiveCfg = Release|Any CPU
79 {15895FD1-DD68-407B-8717-08F6DD14F02C}.Release|x86.Build.0 = Release|Any CPU
80 {261F2857-B521-42A4-A3E0-B5165F225E50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
81 {261F2857-B521-42A4-A3E0-B5165F225E50}.Debug|Any CPU.Build.0 = Debug|Any CPU
82 {261F2857-B521-42A4-A3E0-B5165F225E50}.Debug|x64.ActiveCfg = Debug|Any CPU
83 {261F2857-B521-42A4-A3E0-B5165F225E50}.Debug|x64.Build.0 = Debug|Any CPU
84 {261F2857-B521-42A4-A3E0-B5165F225E50}.Debug|x86.ActiveCfg = Debug|Any CPU
85 {261F2857-B521-42A4-A3E0-B5165F225E50}.Debug|x86.Build.0 = Debug|Any CPU
86 {261F2857-B521-42A4-A3E0-B5165F225E50}.Release|Any CPU.ActiveCfg = Release|Any CPU
87 {261F2857-B521-42A4-A3E0-B5165F225E50}.Release|Any CPU.Build.0 = Release|Any CPU
88 {261F2857-B521-42A4-A3E0-B5165F225E50}.Release|x64.ActiveCfg = Release|Any CPU
89 {261F2857-B521-42A4-A3E0-B5165F225E50}.Release|x64.Build.0 = Release|Any CPU
90 {261F2857-B521-42A4-A3E0-B5165F225E50}.Release|x86.ActiveCfg = Release|Any CPU
91 {261F2857-B521-42A4-A3E0-B5165F225E50}.Release|x86.Build.0 = Release|Any CPU
92 {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
93 {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Debug|Any CPU.Build.0 = Debug|Any CPU
94 {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Debug|x64.ActiveCfg = Debug|Any CPU
95 {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Debug|x64.Build.0 = Debug|Any CPU
96 {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Debug|x86.ActiveCfg = Debug|Any CPU
97 {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Debug|x86.Build.0 = Debug|Any CPU
98 {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Release|Any CPU.ActiveCfg = Release|Any CPU
99 {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Release|Any CPU.Build.0 = Release|Any CPU
100 {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Release|x64.ActiveCfg = Release|Any CPU
101 {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Release|x64.Build.0 = Release|Any CPU
102 {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Release|x86.ActiveCfg = Release|Any CPU
103 {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Release|x86.Build.0 = Release|Any CPU
104 {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
105 {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
106 {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Debug|x64.ActiveCfg = Debug|Any CPU
107 {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Debug|x64.Build.0 = Debug|Any CPU
108 {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Debug|x86.ActiveCfg = Debug|Any CPU
109 {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Debug|x86.Build.0 = Debug|Any CPU
110 {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
111 {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Release|Any CPU.Build.0 = Release|Any CPU
112 {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Release|x64.ActiveCfg = Release|Any CPU
113 {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Release|x64.Build.0 = Release|Any CPU
114 {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Release|x86.ActiveCfg = Release|Any CPU
115 {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Release|x86.Build.0 = Release|Any CPU
116 {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
117 {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
118 {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Debug|x64.ActiveCfg = Debug|Any CPU
119 {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Debug|x64.Build.0 = Debug|Any CPU
120 {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Debug|x86.ActiveCfg = Debug|Any CPU
121 {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Debug|x86.Build.0 = Debug|Any CPU
122 {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
123 {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Release|Any CPU.Build.0 = Release|Any CPU
124 {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Release|x64.ActiveCfg = Release|Any CPU
125 {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Release|x64.Build.0 = Release|Any CPU
126 {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Release|x86.ActiveCfg = Release|Any CPU
127 {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Release|x86.Build.0 = Release|Any CPU
128 {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
129 {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
130 {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Debug|x64.ActiveCfg = Debug|Any CPU
131 {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Debug|x64.Build.0 = Debug|Any CPU
132 {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Debug|x86.ActiveCfg = Debug|Any CPU
133 {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Debug|x86.Build.0 = Debug|Any CPU
134 {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
135 {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Release|Any CPU.Build.0 = Release|Any CPU
136 {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Release|x64.ActiveCfg = Release|Any CPU
137 {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Release|x64.Build.0 = Release|Any CPU
138 {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Release|x86.ActiveCfg = Release|Any CPU
139 {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Release|x86.Build.0 = Release|Any CPU
140 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
141 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
142 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|x64.ActiveCfg = Debug|Any CPU
143 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|x64.Build.0 = Debug|Any CPU
144 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|x86.ActiveCfg = Debug|Any CPU
145 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|x86.Build.0 = Debug|Any CPU
146 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
147 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|Any CPU.Build.0 = Release|Any CPU
148 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|x64.ActiveCfg = Release|Any CPU
149 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|x64.Build.0 = Release|Any CPU
150 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|x86.ActiveCfg = Release|Any CPU
151 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|x86.Build.0 = Release|Any CPU
152 {4544158C-2D63-4146-85FF-62169280144E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
153 {4544158C-2D63-4146-85FF-62169280144E}.Debug|Any CPU.Build.0 = Debug|Any CPU
154 {4544158C-2D63-4146-85FF-62169280144E}.Debug|x64.ActiveCfg = Debug|Any CPU
155 {4544158C-2D63-4146-85FF-62169280144E}.Debug|x64.Build.0 = Debug|Any CPU
156 {4544158C-2D63-4146-85FF-62169280144E}.Debug|x86.ActiveCfg = Debug|Any CPU
157 {4544158C-2D63-4146-85FF-62169280144E}.Debug|x86.Build.0 = Debug|Any CPU
158 {4544158C-2D63-4146-85FF-62169280144E}.Release|Any CPU.ActiveCfg = Release|Any CPU
159 {4544158C-2D63-4146-85FF-62169280144E}.Release|Any CPU.Build.0 = Release|Any CPU
160 {4544158C-2D63-4146-85FF-62169280144E}.Release|x64.ActiveCfg = Release|Any CPU
161 {4544158C-2D63-4146-85FF-62169280144E}.Release|x64.Build.0 = Release|Any CPU
162 {4544158C-2D63-4146-85FF-62169280144E}.Release|x86.ActiveCfg = Release|Any CPU
163 {4544158C-2D63-4146-85FF-62169280144E}.Release|x86.Build.0 = Release|Any CPU
164 {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
165 {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|Any CPU.Build.0 = Debug|Any CPU
166 {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|x64.ActiveCfg = Debug|Any CPU
167 {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|x64.Build.0 = Debug|Any CPU
168 {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|x86.ActiveCfg = Debug|Any CPU
169 {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|x86.Build.0 = Debug|Any CPU
170 {328799BB-7B03-4B28-8180-4132211FD07D}.Release|Any CPU.ActiveCfg = Release|Any CPU
171 {328799BB-7B03-4B28-8180-4132211FD07D}.Release|Any CPU.Build.0 = Release|Any CPU
172 {328799BB-7B03-4B28-8180-4132211FD07D}.Release|x64.ActiveCfg = Release|Any CPU
173 {328799BB-7B03-4B28-8180-4132211FD07D}.Release|x64.Build.0 = Release|Any CPU
174 {328799BB-7B03-4B28-8180-4132211FD07D}.Release|x86.ActiveCfg = Release|Any CPU
175 {328799BB-7B03-4B28-8180-4132211FD07D}.Release|x86.Build.0 = Release|Any CPU
176 {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
177 {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|Any CPU.Build.0 = Debug|Any CPU
178 {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|x64.ActiveCfg = Debug|Any CPU
179 {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|x64.Build.0 = Debug|Any CPU
180 {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|x86.ActiveCfg = Debug|Any CPU
181 {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|x86.Build.0 = Debug|Any CPU
182 {16F5202F-9276-4166-975C-C9654BAF8012}.Release|Any CPU.ActiveCfg = Release|Any CPU
183 {16F5202F-9276-4166-975C-C9654BAF8012}.Release|Any CPU.Build.0 = Release|Any CPU
184 {16F5202F-9276-4166-975C-C9654BAF8012}.Release|x64.ActiveCfg = Release|Any CPU
185 {16F5202F-9276-4166-975C-C9654BAF8012}.Release|x64.Build.0 = Release|Any CPU
186 {16F5202F-9276-4166-975C-C9654BAF8012}.Release|x86.ActiveCfg = Release|Any CPU
187 {16F5202F-9276-4166-975C-C9654BAF8012}.Release|x86.Build.0 = Release|Any CPU
188 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
189 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
190 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|x64.ActiveCfg = Debug|Any CPU
191 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|x64.Build.0 = Debug|Any CPU
192 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|x86.ActiveCfg = Debug|Any CPU
193 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|x86.Build.0 = Debug|Any CPU
194 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
195 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|Any CPU.Build.0 = Release|Any CPU
196 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|x64.ActiveCfg = Release|Any CPU
197 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|x64.Build.0 = Release|Any CPU
198 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|x86.ActiveCfg = Release|Any CPU
199 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|x86.Build.0 = Release|Any CPU
200 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
201 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|Any CPU.Build.0 = Debug|Any CPU
202 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|x64.ActiveCfg = Debug|Any CPU
203 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|x64.Build.0 = Debug|Any CPU
204 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|x86.ActiveCfg = Debug|Any CPU
205 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|x86.Build.0 = Debug|Any CPU
206 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|Any CPU.ActiveCfg = Release|Any CPU
207 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|Any CPU.Build.0 = Release|Any CPU
208 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x64.ActiveCfg = Release|Any CPU
209 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x64.Build.0 = Release|Any CPU
210 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x86.ActiveCfg = Release|Any CPU
211 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x86.Build.0 = Release|Any CPU
212 {55D5BA28-D427-4F53-80C2-FE9EF23C1553}.Debug|Any CPU.ActiveCfg = Debug|Win32
213 {55D5BA28-D427-4F53-80C2-FE9EF23C1553}.Debug|x64.ActiveCfg = Debug|Win32
214 {55D5BA28-D427-4F53-80C2-FE9EF23C1553}.Debug|x86.ActiveCfg = Debug|Win32
215 {55D5BA28-D427-4F53-80C2-FE9EF23C1553}.Debug|x86.Build.0 = Debug|Win32
216 {55D5BA28-D427-4F53-80C2-FE9EF23C1553}.Release|Any CPU.ActiveCfg = Release|Win32
217 {55D5BA28-D427-4F53-80C2-FE9EF23C1553}.Release|x64.ActiveCfg = Release|x64
218 {55D5BA28-D427-4F53-80C2-FE9EF23C1553}.Release|x86.ActiveCfg = Release|Win32
219 {55D5BA28-D427-4F53-80C2-FE9EF23C1553}.Release|x86.Build.0 = Release|Win32
220 {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
221 {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Debug|Any CPU.Build.0 = Debug|Any CPU
222 {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Debug|x64.ActiveCfg = Debug|Any CPU
223 {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Debug|x64.Build.0 = Debug|Any CPU
224 {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Debug|x86.ActiveCfg = Debug|Any CPU
225 {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Debug|x86.Build.0 = Debug|Any CPU
226 {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Release|Any CPU.ActiveCfg = Release|Any CPU
227 {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Release|Any CPU.Build.0 = Release|Any CPU
228 {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Release|x64.ActiveCfg = Release|Any CPU
229 {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Release|x64.Build.0 = Release|Any CPU
230 {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Release|x86.ActiveCfg = Release|Any CPU
231 {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Release|x86.Build.0 = Release|Any CPU
232 {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
233 {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Debug|Any CPU.Build.0 = Debug|Any CPU
234 {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Debug|x64.ActiveCfg = Debug|Any CPU
235 {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Debug|x64.Build.0 = Debug|Any CPU
236 {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Debug|x86.ActiveCfg = Debug|Any CPU
237 {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Debug|x86.Build.0 = Debug|Any CPU
238 {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Release|Any CPU.ActiveCfg = Release|Any CPU
239 {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Release|Any CPU.Build.0 = Release|Any CPU
240 {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Release|x64.ActiveCfg = Release|Any CPU
241 {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Release|x64.Build.0 = Release|Any CPU
242 {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Release|x86.ActiveCfg = Release|Any CPU
243 {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Release|x86.Build.0 = Release|Any CPU
244 {864B8C50-7895-4485-AC89-900D86FD8C0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
245 {864B8C50-7895-4485-AC89-900D86FD8C0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
246 {864B8C50-7895-4485-AC89-900D86FD8C0D}.Debug|x64.ActiveCfg = Debug|Any CPU
247 {864B8C50-7895-4485-AC89-900D86FD8C0D}.Debug|x64.Build.0 = Debug|Any CPU
248 {864B8C50-7895-4485-AC89-900D86FD8C0D}.Debug|x86.ActiveCfg = Debug|Any CPU
249 {864B8C50-7895-4485-AC89-900D86FD8C0D}.Debug|x86.Build.0 = Debug|Any CPU
250 {864B8C50-7895-4485-AC89-900D86FD8C0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
251 {864B8C50-7895-4485-AC89-900D86FD8C0D}.Release|Any CPU.Build.0 = Release|Any CPU
252 {864B8C50-7895-4485-AC89-900D86FD8C0D}.Release|x64.ActiveCfg = Release|Any CPU
253 {864B8C50-7895-4485-AC89-900D86FD8C0D}.Release|x64.Build.0 = Release|Any CPU
254 {864B8C50-7895-4485-AC89-900D86FD8C0D}.Release|x86.ActiveCfg = Release|Any CPU
255 {864B8C50-7895-4485-AC89-900D86FD8C0D}.Release|x86.Build.0 = Release|Any CPU
256 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
257 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|Any CPU.Build.0 = Debug|Any CPU
258 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|x64.ActiveCfg = Debug|Any CPU
259 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|x64.Build.0 = Debug|Any CPU
260 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|x86.ActiveCfg = Debug|Any CPU
261 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|x86.Build.0 = Debug|Any CPU
262 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|Any CPU.ActiveCfg = Release|Any CPU
263 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|Any CPU.Build.0 = Release|Any CPU
264 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|x64.ActiveCfg = Release|Any CPU
265 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|x64.Build.0 = Release|Any CPU
266 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|x86.ActiveCfg = Release|Any CPU
267 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|x86.Build.0 = Release|Any CPU
268 EndGlobalSection
269 GlobalSection(SolutionProperties) = preSolution
270 HideSolutionNode = FALSE
271 EndGlobalSection
272 GlobalSection(NestedProjects) = preSolution
273 {55D5BA28-D427-4F53-80C2-FE9EF23C1553} = {A988A768-200F-408F-AE3B-5D9B14AA48EE}
274 {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B} = {A988A768-200F-408F-AE3B-5D9B14AA48EE}
275 {DB9E5F02-8241-440A-9B60-980EB5B42B13} = {4A47EC94-8234-4479-87BC-3D924DB7AA4E}
276 {864B8C50-7895-4485-AC89-900D86FD8C0D} = {4A47EC94-8234-4479-87BC-3D924DB7AA4E}
277 EndGlobalSection
278 GlobalSection(ExtensibilityGlobals) = postSolution
279 SolutionGuid = {BB57C98D-C0C2-4805-AED3-C19B47759DBD}
280 EndGlobalSection
281EndGlobal
diff --git a/src/dtf/WixToolsetTests.Dtf.Compression.Cab/CabTest.cs b/src/dtf/WixToolsetTests.Dtf.Compression.Cab/CabTest.cs
new file mode 100644
index 00000000..981ecc69
--- /dev/null
+++ b/src/dtf/WixToolsetTests.Dtf.Compression.Cab/CabTest.cs
@@ -0,0 +1,1165 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Test
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Threading;
9 using System.Collections.Generic;
10 using System.Runtime.Serialization;
11 using System.Runtime.Serialization.Formatters.Binary;
12 using Microsoft.VisualStudio.TestTools.UnitTesting;
13 using WixToolset.Dtf.Compression;
14 using WixToolset.Dtf.Compression.Cab;
15
16 [TestClass]
17 public class CabTest
18 {
19 public CabTest()
20 {
21 }
22
23 [TestInitialize]
24 public void Initialize()
25 {
26 }
27
28 [TestCleanup]
29 public void Cleanup()
30 {
31 }
32
33 [TestMethod]
34 public void CabinetMultithread()
35 {
36 this.multithreadExceptions = new List<Exception>();
37
38 const int threadCount = 10;
39 IList<Thread> threads = new List<Thread>(threadCount);
40
41 for (int i = 0; i < threadCount; i++)
42 {
43 Thread thread = new Thread(new ThreadStart(this.CabinetMultithreadWorker));
44 thread.Name = "CabinetMultithreadWorker_" + i;
45 threads.Add(thread);
46 }
47
48 foreach (Thread thread in threads)
49 {
50 thread.Start();
51 }
52
53 foreach (Thread thread in threads)
54 {
55 thread.Join();
56 }
57
58 foreach (Exception ex in this.multithreadExceptions)
59 {
60 Console.WriteLine();
61 Console.WriteLine(ex);
62 }
63 Assert.AreEqual<int>(0, this.multithreadExceptions.Count);
64 }
65
66 private IList<Exception> multithreadExceptions;
67
68 private void CabinetMultithreadWorker()
69 {
70 try
71 {
72 string threadName = Thread.CurrentThread.Name;
73 int threadNumber = Int32.Parse(threadName.Substring(threadName.IndexOf('_') + 1));
74 this.RunCabinetPackUnpack(100, 10240 + threadNumber, 0, 0, CompressionLevel.Normal);
75 }
76 catch (Exception ex)
77 {
78 this.multithreadExceptions.Add(ex);
79 }
80 }
81
82 [TestMethod]
83 public void CabinetFileCounts()
84 {
85 this.RunCabinetPackUnpack(0, 10, 0, 0, CompressionLevel.Normal);
86 this.RunCabinetPackUnpack(1, 10, 0, 0, CompressionLevel.Normal);
87 this.RunCabinetPackUnpack(100, 10, 0, 0, CompressionLevel.Normal);
88 }
89
90 [TestMethod]
91 [Ignore] // Takes ~5 minutes and 66000 is over the 65535 limit anyway.
92 public void CabinetExtremeFileCounts()
93 {
94 this.RunCabinetPackUnpack(66000, 10);
95 }
96
97 [TestMethod]
98 public void CabinetFileSizes()
99 {
100 this.RunCabinetPackUnpack(1, 0, 0, 0, CompressionLevel.Normal);
101 this.RunCabinetPackUnpack(1, 1, 0, 0, CompressionLevel.Normal);
102 this.RunCabinetPackUnpack(1, 2, 0, 0, CompressionLevel.Normal);
103 this.RunCabinetPackUnpack(1, 3, 0, 0, CompressionLevel.Normal);
104 this.RunCabinetPackUnpack(1, 4, 0, 0, CompressionLevel.Normal);
105 this.RunCabinetPackUnpack(1, 5, 0, 0, CompressionLevel.Normal);
106 this.RunCabinetPackUnpack(1, 6, 0, 0, CompressionLevel.Normal);
107 // Skip file sizes 7-9: see "buggy" file sizes test below.
108 this.RunCabinetPackUnpack(1, 10, 0, 0, CompressionLevel.Normal);
109 this.RunCabinetPackUnpack(1, 11, 0, 0, CompressionLevel.Normal);
110 this.RunCabinetPackUnpack(1, 12, 0, 0, CompressionLevel.Normal);
111 this.RunCabinetPackUnpack(1, 100 * 1024, 0, 0, CompressionLevel.Normal);
112 this.RunCabinetPackUnpack(1, 10 * 1024 * 1024, 0, 0, CompressionLevel.Normal);
113 }
114
115 [TestMethod]
116 public void CabinetBuggyFileSizes()
117 {
118 // Windows' cabinet.dll has a known bug (#55001 in Windows OS Bugs)
119 // LZX compression causes an AV with file sizes of 7, 8, or 9 bytes.
120 try
121 {
122 this.RunCabinetPackUnpack(1, 7, 0, 0, CompressionLevel.Normal);
123 this.RunCabinetPackUnpack(1, 8, 0, 0, CompressionLevel.Normal);
124 this.RunCabinetPackUnpack(1, 9, 0, 0, CompressionLevel.Normal);
125 }
126 catch (AccessViolationException)
127 {
128 Assert.Fail("Known 7,8,9 file size bug detected in Windows' cabinet.dll.");
129 }
130 }
131
132 [Timeout(36000000), TestMethod]
133 [Ignore] // Takes too long to run regularly.
134 public void CabinetExtremeFileSizes()
135 {
136 this.RunCabinetPackUnpack(10, 512L * 1024 * 1024); // 5GB
137 //this.RunCabinetPackUnpack(1, 5L * 1024 * 1024 * 1024); // 5GB
138 }
139
140 [TestMethod]
141 public void CabinetFolders()
142 {
143 this.RunCabinetPackUnpack(0, 10, 1, 0, CompressionLevel.Normal);
144 this.RunCabinetPackUnpack(1, 10, 1, 0, CompressionLevel.Normal);
145 this.RunCabinetPackUnpack(100, 10, 1, 0, CompressionLevel.Normal);
146
147 IList<ArchiveFileInfo> fileInfo;
148 fileInfo = this.RunCabinetPackUnpack(7, 100 * 1024, 250 * 1024, 0, CompressionLevel.None);
149 Assert.AreEqual<int>(2, ((CabFileInfo) fileInfo[fileInfo.Count - 1]).CabinetFolderNumber,
150 "Testing whether cabinet has the correct # of folders.");
151
152 fileInfo = this.RunCabinetPackUnpack(10, 100 * 1024, 250 * 1024, 0, CompressionLevel.None);
153 Assert.AreEqual<int>(3, ((CabFileInfo) fileInfo[fileInfo.Count - 1]).CabinetFolderNumber,
154 "Testing whether cabinet has the correct # of folders.");
155
156 fileInfo = this.RunCabinetPackUnpack(2, 100 * 1024, 40 * 1024, 0, CompressionLevel.None);
157 Assert.AreEqual<int>(1, ((CabFileInfo) fileInfo[fileInfo.Count - 1]).CabinetFolderNumber,
158 "Testing whether cabinet has the correct # of folders.");
159 }
160
161 [TestMethod]
162 public void CabinetArchiveCounts()
163 {
164 IList<ArchiveFileInfo> fileInfo;
165 fileInfo = this.RunCabinetPackUnpack(10, 100 * 1024, 0, 400 * 1024, CompressionLevel.None);
166 Assert.AreEqual<int>(2, fileInfo[fileInfo.Count - 1].ArchiveNumber,
167 "Testing whether archive spans the correct # of cab files.");
168
169 fileInfo = this.RunCabinetPackUnpack(2, 90 * 1024, 0, 40 * 1024, CompressionLevel.None);
170 Assert.AreEqual<int>(2, fileInfo[fileInfo.Count - 1].ArchiveNumber,
171 "Testing whether archive spans the correct # of cab files.");
172 }
173
174 [TestMethod]
175 public void CabinetProgress()
176 {
177 CompressionTestUtil.ExpectedProgress = new List<int[]>(new int[][] {
178 // StatusType, CurFile,TotalFiles,CurFolder,CurCab,TotalCabs
179 new int[] { (int) ArchiveProgressType.StartFile, 0, 15, 0, 0, 1 },
180 new int[] { (int) ArchiveProgressType.FinishFile, 0, 15, 0, 0, 1 },
181 new int[] { (int) ArchiveProgressType.StartFile, 1, 15, 0, 0, 1 },
182 new int[] { (int) ArchiveProgressType.FinishFile, 1, 15, 0, 0, 1 },
183 new int[] { (int) ArchiveProgressType.StartFile, 2, 15, 1, 0, 1 },
184 new int[] { (int) ArchiveProgressType.FinishFile, 2, 15, 1, 0, 1 },
185 new int[] { (int) ArchiveProgressType.StartFile, 3, 15, 1, 0, 1 },
186 new int[] { (int) ArchiveProgressType.FinishFile, 3, 15, 1, 0, 1 },
187 new int[] { (int) ArchiveProgressType.StartFile, 4, 15, 2, 0, 1 },
188 new int[] { (int) ArchiveProgressType.FinishFile, 4, 15, 2, 0, 1 },
189 new int[] { (int) ArchiveProgressType.StartFile, 5, 15, 2, 0, 1 },
190 new int[] { (int) ArchiveProgressType.FinishFile, 5, 15, 2, 0, 1 },
191 new int[] { (int) ArchiveProgressType.StartFile, 6, 15, 3, 0, 1 },
192 new int[] { (int) ArchiveProgressType.FinishFile, 6, 15, 3, 0, 1 },
193 new int[] { (int) ArchiveProgressType.StartFile, 7, 15, 3, 0, 1 },
194 new int[] { (int) ArchiveProgressType.FinishFile, 7, 15, 3, 0, 1 },
195 new int[] { (int) ArchiveProgressType.StartArchive, 7, 15, 3, 0, 1 },
196 new int[] { (int) ArchiveProgressType.FinishArchive, 7, 15, 3, 0, 1 },
197 new int[] { (int) ArchiveProgressType.StartFile, 8, 15, 4, 1, 2 },
198 new int[] { (int) ArchiveProgressType.FinishFile, 8, 15, 4, 1, 2 },
199 new int[] { (int) ArchiveProgressType.StartFile, 9, 15, 4, 1, 2 },
200 new int[] { (int) ArchiveProgressType.FinishFile, 9, 15, 4, 1, 2 },
201 new int[] { (int) ArchiveProgressType.StartFile, 10, 15, 5, 1, 2 },
202 new int[] { (int) ArchiveProgressType.FinishFile, 10, 15, 5, 1, 2 },
203 new int[] { (int) ArchiveProgressType.StartFile, 11, 15, 5, 1, 2 },
204 new int[] { (int) ArchiveProgressType.FinishFile, 11, 15, 5, 1, 2 },
205 new int[] { (int) ArchiveProgressType.StartFile, 12, 15, 6, 1, 2 },
206 new int[] { (int) ArchiveProgressType.FinishFile, 12, 15, 6, 1, 2 },
207 new int[] { (int) ArchiveProgressType.StartFile, 13, 15, 6, 1, 2 },
208 new int[] { (int) ArchiveProgressType.FinishFile, 13, 15, 6, 1, 2 },
209 new int[] { (int) ArchiveProgressType.StartArchive, 13, 15, 6, 1, 2 },
210 new int[] { (int) ArchiveProgressType.FinishArchive, 13, 15, 6, 1, 2 },
211 new int[] { (int) ArchiveProgressType.StartFile, 14, 15, 7, 2, 3 },
212 new int[] { (int) ArchiveProgressType.FinishFile, 14, 15, 7, 2, 3 },
213 new int[] { (int) ArchiveProgressType.StartArchive, 14, 15, 7, 2, 3 },
214 new int[] { (int) ArchiveProgressType.FinishArchive, 14, 15, 7, 2, 3 },
215 // StatusType, CurFile,TotalFiles,CurFolder,CurCab,TotalCabs
216 new int[] { (int) ArchiveProgressType.StartArchive, 0, 15, 0, 0, 3 },
217 new int[] { (int) ArchiveProgressType.StartFile, 0, 15, 0, 0, 3 },
218 new int[] { (int) ArchiveProgressType.FinishFile, 0, 15, 0, 0, 3 },
219 new int[] { (int) ArchiveProgressType.StartFile, 1, 15, 0, 0, 3 },
220 new int[] { (int) ArchiveProgressType.FinishFile, 1, 15, 0, 0, 3 },
221 new int[] { (int) ArchiveProgressType.StartFile, 2, 15, 1, 0, 3 },
222 new int[] { (int) ArchiveProgressType.FinishFile, 2, 15, 1, 0, 3 },
223 new int[] { (int) ArchiveProgressType.StartFile, 3, 15, 1, 0, 3 },
224 new int[] { (int) ArchiveProgressType.FinishFile, 3, 15, 1, 0, 3 },
225 new int[] { (int) ArchiveProgressType.StartFile, 4, 15, 2, 0, 3 },
226 new int[] { (int) ArchiveProgressType.FinishFile, 4, 15, 2, 0, 3 },
227 new int[] { (int) ArchiveProgressType.StartFile, 5, 15, 2, 0, 3 },
228 new int[] { (int) ArchiveProgressType.FinishFile, 5, 15, 2, 0, 3 },
229 new int[] { (int) ArchiveProgressType.StartFile, 6, 15, 3, 0, 3 },
230 new int[] { (int) ArchiveProgressType.FinishArchive, 6, 15, 3, 0, 3 },
231 new int[] { (int) ArchiveProgressType.StartArchive, 6, 15, 3, 1, 3 },
232 new int[] { (int) ArchiveProgressType.FinishFile, 6, 15, 3, 1, 3 },
233 new int[] { (int) ArchiveProgressType.StartFile, 7, 15, 3, 1, 3 },
234 new int[] { (int) ArchiveProgressType.FinishFile, 7, 15, 3, 1, 3 },
235 new int[] { (int) ArchiveProgressType.StartFile, 8, 15, 4, 1, 3 },
236 new int[] { (int) ArchiveProgressType.FinishFile, 8, 15, 4, 1, 3 },
237 new int[] { (int) ArchiveProgressType.StartFile, 9, 15, 4, 1, 3 },
238 new int[] { (int) ArchiveProgressType.FinishFile, 9, 15, 4, 1, 3 },
239 new int[] { (int) ArchiveProgressType.StartFile, 10, 15, 5, 1, 3 },
240 new int[] { (int) ArchiveProgressType.FinishFile, 10, 15, 5, 1, 3 },
241 new int[] { (int) ArchiveProgressType.StartFile, 11, 15, 5, 1, 3 },
242 new int[] { (int) ArchiveProgressType.FinishFile, 11, 15, 5, 1, 3 },
243 new int[] { (int) ArchiveProgressType.StartFile, 12, 15, 6, 1, 3 },
244 new int[] { (int) ArchiveProgressType.FinishArchive, 12, 15, 6, 1, 3 },
245 new int[] { (int) ArchiveProgressType.StartArchive, 12, 15, 6, 2, 3 },
246 new int[] { (int) ArchiveProgressType.FinishFile, 12, 15, 6, 2, 3 },
247 new int[] { (int) ArchiveProgressType.StartFile, 13, 15, 6, 2, 3 },
248 new int[] { (int) ArchiveProgressType.FinishFile, 13, 15, 6, 2, 3 },
249 new int[] { (int) ArchiveProgressType.StartFile, 14, 15, 7, 2, 3 },
250 new int[] { (int) ArchiveProgressType.FinishFile, 14, 15, 7, 2, 3 },
251 new int[] { (int) ArchiveProgressType.FinishArchive, 14, 15, 7, 2, 3 },
252 });
253
254 try
255 {
256 this.RunCabinetPackUnpack(15, 20 * 1024, 1 * 1024, 130 * 1024, CompressionLevel.None);
257 }
258 finally
259 {
260 CompressionTestUtil.ExpectedProgress = null;
261 }
262 }
263
264 [TestMethod]
265 public void CabArchiveSizeParam()
266 {
267 Console.WriteLine("Testing various values for the maxArchiveSize parameter.");
268 this.RunCabinetPackUnpack(5, 1024, 0, Int64.MinValue);
269 this.RunCabinetPackUnpack(5, 1024, 0, -1);
270 this.RunCabinetPackUnpack(5, 10, 0, 2);
271 this.RunCabinetPackUnpack(5, 100, 0, 256);
272 this.RunCabinetPackUnpack(5, 24000, 0, 32768);
273 this.RunCabinetPackUnpack(5, 1024, 0, Int64.MaxValue);
274 }
275
276 [TestMethod]
277 public void CabFolderSizeParam()
278 {
279 Console.WriteLine("Testing various values for the maxFolderSize parameter.");
280 this.RunCabinetPackUnpack(5, 10, Int64.MinValue, 0);
281 this.RunCabinetPackUnpack(5, 10, -1, 0);
282 this.RunCabinetPackUnpack(5, 10, 2, 0);
283 this.RunCabinetPackUnpack(5, 10, 16, 0);
284 this.RunCabinetPackUnpack(5, 10, 100, 0);
285 this.RunCabinetPackUnpack(5, 10, Int64.MaxValue, 0);
286 }
287
288 [TestMethod]
289 public void CabCompLevelParam()
290 {
291 Console.WriteLine("Testing various values for the compressionLevel parameter.");
292 this.RunCabinetPackUnpack(5, 1024, 0, 0, CompressionLevel.None);
293 this.RunCabinetPackUnpack(5, 1024, 0, 0, CompressionLevel.Min);
294 this.RunCabinetPackUnpack(5, 1024, 0, 0, CompressionLevel.Normal);
295 this.RunCabinetPackUnpack(5, 1024, 0, 0, CompressionLevel.Max);
296 this.RunCabinetPackUnpack(5, 1024, 0, 0, (CompressionLevel) ((int) CompressionLevel.None - 1));
297 this.RunCabinetPackUnpack(5, 1024, 0, 0, (CompressionLevel) ((int) CompressionLevel.Max + 1));
298 this.RunCabinetPackUnpack(5, 1024, 0, 0, (CompressionLevel) Int32.MinValue);
299 this.RunCabinetPackUnpack(5, 1024, 0, 0, (CompressionLevel) Int32.MaxValue);
300 }
301
302 [TestMethod]
303 public void CabEngineNullParams()
304 {
305 string[] testFiles = new string[] { "test.txt" };
306 ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext("test.cab", null, null);
307
308 using (CabEngine cabEngine = new CabEngine())
309 {
310 cabEngine.CompressionLevel = CompressionLevel.None;
311
312 CompressionTestUtil.TestCompressionEngineNullParams(
313 cabEngine, streamContext, testFiles);
314 }
315 }
316
317 [TestMethod]
318 public void CabBadPackStreamContexts()
319 {
320 string[] testFiles = new string[] { "test.txt" };
321 CompressionTestUtil.GenerateRandomFile(testFiles[0], 0, 20000);
322
323 using (CabEngine cabEngine = new CabEngine())
324 {
325 cabEngine.CompressionLevel = CompressionLevel.None;
326
327 CompressionTestUtil.TestBadPackStreamContexts(cabEngine, "test.cab", testFiles);
328 }
329 }
330
331 [TestMethod]
332 public void CabEngineNoTempFileTest()
333 {
334 int txtSize = 10240;
335 CompressionTestUtil.GenerateRandomFile("testnotemp.txt", 0, txtSize);
336
337 ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext("testnotemp.cab", null, null);
338
339 using (CabEngine cabEngine = new CabEngine())
340 {
341 cabEngine.UseTempFiles = false;
342 cabEngine.Pack(streamContext, new string[] { "testnotemp.txt" });
343 }
344
345 new CabInfo("testnotemp.cab").UnpackFile("testnotemp.txt", "testnotemp2.txt");
346 Assert.AreEqual(txtSize, new FileInfo("testnotemp2.txt").Length);
347 }
348
349 [TestMethod]
350 public void CabExtractorIsCabinet()
351 {
352 int txtSize = 10240;
353 CompressionTestUtil.GenerateRandomFile("test.txt", 0, txtSize);
354 new CabInfo("test.cab").PackFiles(null, new string[] { "test.txt" }, null);
355 using (CabEngine cabEngine = new CabEngine())
356 {
357 bool isCab;
358 using (Stream fileStream = File.OpenRead("test.txt"))
359 {
360 isCab = cabEngine.IsArchive(fileStream);
361 }
362 Assert.IsFalse(isCab);
363 using (Stream cabStream = File.OpenRead("test.cab"))
364 {
365 isCab = cabEngine.IsArchive(cabStream);
366 }
367 Assert.IsTrue(isCab);
368 using (Stream cabStream = File.OpenRead("test.cab"))
369 {
370 using (Stream fileStream = new FileStream("test.txt", FileMode.Open, FileAccess.ReadWrite))
371 {
372 fileStream.Seek(0, SeekOrigin.End);
373 byte[] buf = new byte[1024];
374 int count;
375 while ((count = cabStream.Read(buf, 0, buf.Length)) > 0)
376 {
377 fileStream.Write(buf, 0, count);
378 }
379 fileStream.Seek(0, SeekOrigin.Begin);
380 isCab = cabEngine.IsArchive(fileStream);
381 }
382 }
383 Assert.IsFalse(isCab);
384 using (Stream fileStream = new FileStream("test.txt", FileMode.Open, FileAccess.ReadWrite))
385 {
386 fileStream.Write(new byte[] { (byte) 'M', (byte) 'S', (byte) 'C', (byte) 'F' }, 0, 4);
387 fileStream.Seek(0, SeekOrigin.Begin);
388 isCab = cabEngine.IsArchive(fileStream);
389 }
390 Assert.IsFalse(isCab);
391 }
392 }
393
394 [TestMethod]
395 public void CabExtractorFindOffset()
396 {
397 int txtSize = 10240;
398 CompressionTestUtil.GenerateRandomFile("test.txt", 0, txtSize);
399 new CabInfo("test.cab").PackFiles(null, new string[] { "test.txt" }, null);
400 using (CabEngine cabEngine = new CabEngine())
401 {
402 long offset;
403 using (Stream fileStream = File.OpenRead("test.txt"))
404 {
405 offset = cabEngine.FindArchiveOffset(fileStream);
406 }
407 Assert.AreEqual<long>(-1, offset);
408 using (Stream cabStream = File.OpenRead("test.cab"))
409 {
410 using (Stream fileStream = new FileStream("test.txt", FileMode.Open, FileAccess.ReadWrite))
411 {
412 fileStream.Seek(0, SeekOrigin.End);
413 byte[] buf = new byte[1024];
414 int count;
415 while ((count = cabStream.Read(buf, 0, buf.Length)) > 0)
416 {
417 fileStream.Write(buf, 0, count);
418 }
419 fileStream.Seek(0, SeekOrigin.Begin);
420 offset = cabEngine.FindArchiveOffset(fileStream);
421 }
422 }
423 Assert.AreEqual<long>(txtSize, offset);
424 }
425 }
426
427 [TestMethod]
428 public void CabExtractorGetFiles()
429 {
430 IList<ArchiveFileInfo> fileInfo;
431 CabInfo cabInfo = new CabInfo("testgetfiles.cab");
432 int txtSize = 10240;
433 CompressionTestUtil.GenerateRandomFile("testgetfiles0.txt", 0, txtSize);
434 CompressionTestUtil.GenerateRandomFile("testgetfiles1.txt", 1, txtSize);
435 cabInfo.PackFiles(null, new string[] { "testgetfiles0.txt", "testgetfiles1.txt" }, null);
436 using (CabEngine cabEngine = new CabEngine())
437 {
438 IList<string> files;
439 using (Stream cabStream = File.OpenRead("testgetfiles.cab"))
440 {
441 files = cabEngine.GetFiles(cabStream);
442 }
443 Assert.IsNotNull(files);
444 Assert.AreEqual<int>(2, files.Count);
445 Assert.AreEqual<string>("testgetfiles0.txt", files[0]);
446 Assert.AreEqual<string>("testgetfiles1.txt", files[1]);
447
448 using (Stream cabStream = File.OpenRead("testgetfiles.cab"))
449 {
450 files = cabEngine.GetFiles(new ArchiveFileStreamContext("testgetfiles.cab"), null);
451 }
452 Assert.IsNotNull(files);
453 Assert.AreEqual<int>(2, files.Count);
454 Assert.AreEqual<string>("testgetfiles0.txt", files[0]);
455 Assert.AreEqual<string>("testgetfiles1.txt", files[1]);
456
457 using (Stream cabStream = File.OpenRead("testgetfiles.cab"))
458 {
459 fileInfo = cabEngine.GetFileInfo(cabStream);
460 }
461 Assert.IsNotNull(fileInfo);
462 Assert.AreEqual<int>(2, fileInfo.Count);
463 Assert.AreEqual<string>("testgetfiles0.txt", fileInfo[0].Name);
464 Assert.AreEqual<string>("testgetfiles1.txt", fileInfo[1].Name);
465 using (Stream cabStream = File.OpenRead("testgetfiles.cab"))
466 {
467 fileInfo = cabEngine.GetFileInfo(new ArchiveFileStreamContext("testgetfiles.cab"), null);
468 }
469 Assert.IsNotNull(fileInfo);
470 Assert.AreEqual<int>(2, fileInfo.Count);
471 Assert.AreEqual<string>("testgetfiles0.txt", fileInfo[0].Name);
472 Assert.AreEqual<string>("testgetfiles1.txt", fileInfo[1].Name);
473 }
474
475 fileInfo = this.RunCabinetPackUnpack(15, 20 * 1024, 1 * 1024, 130 * 1024);
476 Assert.IsNotNull(fileInfo);
477 Assert.AreEqual<int>(15, fileInfo.Count);
478 for (int i = 0; i < fileInfo.Count; i++)
479 {
480 Assert.IsNull(fileInfo[i].Archive);
481 Assert.AreEqual<string>(TEST_FILENAME_PREFIX + i + ".txt", fileInfo[i].Name);
482 Assert.IsTrue(DateTime.Now - fileInfo[i].LastWriteTime < new TimeSpan(0, 1, 0));
483 }
484 }
485
486 [TestMethod]
487 public void CabExtractorExtract()
488 {
489 int txtSize = 40960;
490 CabInfo cabInfo = new CabInfo("test.cab");
491 CompressionTestUtil.GenerateRandomFile("test0.txt", 0, txtSize);
492 CompressionTestUtil.GenerateRandomFile("test1.txt", 1, txtSize);
493 cabInfo.PackFiles(null, new string[] { "test0.txt", "test1.txt" }, null);
494 using (CabEngine cabEngine = new CabEngine())
495 {
496 using (Stream cabStream = File.OpenRead("test.cab"))
497 {
498 using (Stream exStream = cabEngine.Unpack(cabStream, "test0.txt"))
499 {
500 string str = new StreamReader(exStream).ReadToEnd();
501 string expected = new StreamReader("test0.txt").ReadToEnd();
502 Assert.AreEqual<string>(expected, str);
503 }
504 cabStream.Seek(0, SeekOrigin.Begin);
505 using (Stream exStream = cabEngine.Unpack(cabStream, "test1.txt"))
506 {
507 string str = new StreamReader(exStream).ReadToEnd();
508 string expected = new StreamReader("test1.txt").ReadToEnd();
509 Assert.AreEqual<string>(expected, str);
510 }
511 }
512 using (Stream txtStream = File.OpenRead("test0.txt"))
513 {
514 Exception caughtEx = null;
515 try
516 {
517 cabEngine.Unpack(txtStream, "test0.txt");
518 }
519 catch (Exception ex) { caughtEx = ex; }
520 Assert.IsInstanceOfType(caughtEx, typeof(CabException));
521 Assert.AreEqual<int>(2, ((CabException) caughtEx).Error);
522 Assert.AreEqual<int>(0, ((CabException) caughtEx).ErrorCode);
523 Assert.AreEqual<string>("Cabinet file does not have the correct format.", caughtEx.Message);
524 }
525 }
526 }
527
528 [TestMethod]
529 public void CabBadUnpackStreamContexts()
530 {
531 int txtSize = 40960;
532 CabInfo cabInfo = new CabInfo("test2.cab");
533 CompressionTestUtil.GenerateRandomFile("cabtest-0.txt", 0, txtSize);
534 CompressionTestUtil.GenerateRandomFile("cabtest-1.txt", 1, txtSize);
535 cabInfo.PackFiles(null, new string[] { "cabtest-0.txt", "cabtest-1.txt" }, null);
536
537 using (CabEngine cabEngine = new CabEngine())
538 {
539 CompressionTestUtil.TestBadUnpackStreamContexts(cabEngine, "test2.cab");
540 }
541 }
542
543 [TestMethod]
544 public void CabinetExtractUpdate()
545 {
546 int fileCount = 5, fileSize = 2048;
547 string dirA = String.Format("{0}-{1}-A", fileCount, fileSize);
548 if (Directory.Exists(dirA)) Directory.Delete(dirA, true);
549 Directory.CreateDirectory(dirA);
550 string dirB = String.Format("{0}-{1}-B", fileCount, fileSize);
551 if (Directory.Exists(dirB)) Directory.Delete(dirB, true);
552 Directory.CreateDirectory(dirB);
553
554 string[] files = new string[fileCount];
555 for (int iFile = 0; iFile < fileCount; iFile++)
556 {
557 files[iFile] = "€" + iFile + ".txt";
558 CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize);
559 }
560
561 CabInfo cabInfo = new CabInfo("testupdate.cab");
562 cabInfo.Pack(dirA);
563 cabInfo.Unpack(dirB);
564
565 DateTime originalTime = File.GetLastWriteTime(Path.Combine(dirA, "€1.txt"));
566 DateTime pastTime = originalTime - new TimeSpan(0, 5, 0);
567 DateTime futureTime = originalTime + new TimeSpan(0, 5, 0);
568
569 using (CabEngine cabEngine = new CabEngine())
570 {
571 string cabName = "testupdate.cab";
572 ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext(cabName, dirB, null);
573 streamContext.ExtractOnlyNewerFiles = true;
574
575 Assert.AreEqual<bool>(true, streamContext.ExtractOnlyNewerFiles);
576 Assert.IsNotNull(streamContext.ArchiveFiles);
577 Assert.AreEqual<int>(1, streamContext.ArchiveFiles.Count);
578 Assert.AreEqual<string>(cabName, streamContext.ArchiveFiles[0]);
579 Assert.AreEqual<string>(dirB, streamContext.Directory);
580
581 File.SetLastWriteTime(Path.Combine(dirB, "€1.txt"), futureTime);
582 cabEngine.Unpack(streamContext, null);
583 Assert.IsTrue(File.GetLastWriteTime(Path.Combine(dirB, "€1.txt")) - originalTime > new TimeSpan(0, 4, 55));
584
585 File.SetLastWriteTime(Path.Combine(dirB, "€1.txt"), pastTime);
586 File.SetLastWriteTime(Path.Combine(dirB, "€2.txt"), pastTime);
587 File.SetAttributes(Path.Combine(dirB, "€2.txt"), FileAttributes.ReadOnly);
588 File.SetAttributes(Path.Combine(dirB, "€2.txt"), FileAttributes.Hidden);
589 File.SetAttributes(Path.Combine(dirB, "€2.txt"), FileAttributes.System);
590
591 cabEngine.Unpack(streamContext, null);
592 Assert.IsTrue((File.GetLastWriteTime(Path.Combine(dirB, "€1.txt")) - originalTime).Duration() < new TimeSpan(0, 0, 5));
593
594 // Just test the rest of the streamContext properties here.
595 IDictionary<string, string> testMap = new Dictionary<string, string>();
596 streamContext = new ArchiveFileStreamContext(cabName, dirB, testMap);
597 Assert.AreSame(testMap, streamContext.Files);
598
599 Assert.IsFalse(streamContext.EnableOffsetOpen);
600 streamContext.EnableOffsetOpen = true;
601 Assert.IsTrue(streamContext.EnableOffsetOpen);
602 streamContext = new ArchiveFileStreamContext(cabName, ".", testMap);
603 Assert.AreEqual<string>(".", streamContext.Directory);
604 string[] testArchiveFiles = new string[] { cabName };
605 streamContext = new ArchiveFileStreamContext(testArchiveFiles, ".", testMap);
606 Assert.AreSame(testArchiveFiles, streamContext.ArchiveFiles);
607 }
608 }
609
610 [TestMethod]
611 public void CabinetOffset()
612 {
613 int txtSize = 10240;
614 CompressionTestUtil.GenerateRandomFile("test.txt", 0, txtSize);
615 CompressionTestUtil.GenerateRandomFile("base.txt", 1, 2 * txtSize + 4);
616
617 ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext("base.txt", null, null);
618 streamContext.EnableOffsetOpen = true;
619
620 using (CabEngine cabEngine = new CabEngine())
621 {
622 cabEngine.Pack(streamContext, new string[] { "test.txt" });
623 }
624
625 Assert.IsTrue(new FileInfo("base.txt").Length > 2 * txtSize + 4);
626
627 string saveText;
628 using (Stream txtStream = File.OpenRead("test.txt"))
629 {
630 saveText = new StreamReader(txtStream).ReadToEnd();
631 }
632 File.Delete("test.txt");
633
634 using (CabEngine cex = new CabEngine())
635 {
636 cex.Unpack(streamContext, null);
637 }
638 string testText;
639 using (Stream txtStream = File.OpenRead("test.txt"))
640 {
641 testText = new StreamReader(txtStream).ReadToEnd();
642 }
643 Assert.AreEqual<string>(saveText, testText);
644 }
645
646 [TestMethod]
647 public void CabinetUtfPaths()
648 {
649 string[] files = new string[]
650 {
651 "어그리먼트送信ポート1ßà_Agreement.txt",
652 "콘토소ßà_MyProfile.txt",
653 "파트너1ßà_PartnerProfile.txt",
654 };
655
656 string dirA = "utf8-A";
657 if (Directory.Exists(dirA)) Directory.Delete(dirA, true);
658 Directory.CreateDirectory(dirA);
659 string dirB = "utf8-B";
660 if (Directory.Exists(dirB)) Directory.Delete(dirB, true);
661 Directory.CreateDirectory(dirB);
662
663 int txtSize = 1024;
664 CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[0]), 0, txtSize);
665 CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[1]), 1, txtSize);
666 CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[2]), 2, txtSize);
667
668 ArchiveFileStreamContext streamContextA = new ArchiveFileStreamContext("utf8.cab", dirA, null);
669 using (CabEngine cabEngine = new CabEngine())
670 {
671 cabEngine.Pack(streamContextA, files);
672 }
673
674 ArchiveFileStreamContext streamContextB = new ArchiveFileStreamContext("utf8.cab", dirB, null);
675 using (CabEngine cex = new CabEngine())
676 {
677 cex.Unpack(streamContextB, null);
678 }
679
680 bool directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB);
681 Assert.IsTrue(directoryMatch,
682 "Testing whether cabinet output directory matches input directory.");
683 }
684
685 [TestMethod]
686 //[Ignore] // Requires clean environment.
687 public void CabInfoProperties()
688 {
689 Exception caughtEx;
690 CabInfo cabInfo = new CabInfo("test.cab");
691 int txtSize = 10240;
692 CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize);
693 CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize);
694 cabInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null);
695
696 Assert.AreEqual<string>(new FileInfo("test.cab").Directory.FullName, cabInfo.Directory.FullName, "CabInfo.FullName");
697 Assert.AreEqual<string>(new FileInfo("test.cab").DirectoryName, cabInfo.DirectoryName, "CabInfo.DirectoryName");
698 Assert.AreEqual<long>(new FileInfo("test.cab").Length, cabInfo.Length, "CabInfo.Length");
699 Assert.AreEqual<string>("test.cab", cabInfo.Name, "CabInfo.Name");
700 Assert.AreEqual<string>(new FileInfo("test.cab").FullName, cabInfo.ToString(), "CabInfo.ToString()");
701 cabInfo.CopyTo("test3.cab");
702 caughtEx = null;
703 try
704 {
705 cabInfo.CopyTo("test3.cab");
706 }
707 catch (Exception ex) { caughtEx = ex; }
708 Assert.IsInstanceOfType(caughtEx, typeof(IOException), "CabInfo.CopyTo() caught exception: " + caughtEx);
709 cabInfo.CopyTo("test3.cab", true);
710 cabInfo.MoveTo("test4.cab");
711 Assert.AreEqual<string>("test4.cab", cabInfo.Name);
712 Assert.IsTrue(cabInfo.Exists, "CabInfo.Exists()");
713 Assert.IsTrue(cabInfo.IsValid(), "CabInfo.IsValid");
714 cabInfo.Delete();
715 Assert.IsFalse(cabInfo.Exists, "!CabInfo.Exists()");
716 }
717
718 [TestMethod]
719 //[Ignore] // Requires clean environment.
720 public void CabInfoNullParams()
721 {
722 int fileCount = 10, fileSize = 1024;
723 string dirA = String.Format("{0}-{1}-A", fileCount, fileSize);
724 if (Directory.Exists(dirA)) Directory.Delete(dirA, true);
725 Directory.CreateDirectory(dirA);
726 string dirB = String.Format("{0}-{1}-B", fileCount, fileSize);
727 if (Directory.Exists(dirB)) Directory.Delete(dirB, true);
728 Directory.CreateDirectory(dirB);
729
730 string[] files = new string[fileCount];
731 for (int iFile = 0; iFile < fileCount; iFile++)
732 {
733 files[iFile] = "cabinfo-" + iFile + ".txt";
734 CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize);
735 }
736
737 CabInfo cabInfo = new CabInfo("testnull.cab");
738
739 CompressionTestUtil.TestArchiveInfoNullParams(cabInfo, dirA, dirB, files);
740 }
741
742 [TestMethod]
743 public void CabInfoGetFiles()
744 {
745 IList<CabFileInfo> fileInfo;
746 CabInfo cabInfo = new CabInfo("test.cab");
747 int txtSize = 10240;
748 CompressionTestUtil.GenerateRandomFile("testinfo0.txt", 0, txtSize);
749 CompressionTestUtil.GenerateRandomFile("testinfo1.txt", 1, txtSize);
750 cabInfo.PackFiles(null, new string[] { "testinfo0.txt", "testinfo1.txt" }, null);
751
752 fileInfo = cabInfo.GetFiles();
753 Assert.IsNotNull(fileInfo);
754 Assert.AreEqual<int>(2, fileInfo.Count);
755 Assert.AreEqual<string>("testinfo0.txt", fileInfo[0].Name);
756 Assert.AreEqual<string>("testinfo1.txt", fileInfo[1].Name);
757
758 fileInfo = cabInfo.GetFiles("*.txt");
759 Assert.IsNotNull(fileInfo);
760 Assert.AreEqual<int>(2, fileInfo.Count);
761 Assert.AreEqual<string>("testinfo0.txt", fileInfo[0].Name);
762 Assert.AreEqual<string>("testinfo1.txt", fileInfo[1].Name);
763
764 fileInfo = cabInfo.GetFiles("testinfo1.txt");
765 Assert.IsNotNull(fileInfo);
766 Assert.AreEqual<int>(1, fileInfo.Count);
767 Assert.AreEqual<string>("testinfo1.txt", fileInfo[0].Name);
768 }
769
770 [TestMethod]
771 public void CabInfoCompressExtract()
772 {
773 int fileCount = 10, fileSize = 1024;
774 string dirA = String.Format("{0}-{1}-A", fileCount, fileSize);
775 if (Directory.Exists(dirA)) Directory.Delete(dirA, true);
776 Directory.CreateDirectory(dirA);
777 Directory.CreateDirectory(Path.Combine(dirA, "sub"));
778 string dirB = String.Format("{0}-{1}-B", fileCount, fileSize);
779 if (Directory.Exists(dirB)) Directory.Delete(dirB, true);
780 Directory.CreateDirectory(dirB);
781
782 string[] files = new string[fileCount];
783 for (int iFile = 0; iFile < fileCount; iFile++)
784 {
785 files[iFile] = "€" + iFile + ".txt";
786 CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize);
787 }
788 CompressionTestUtil.GenerateRandomFile(Path.Combine(Path.Combine(dirA, "sub"), "€-.txt"), fileCount + 1, fileSize);
789
790 CabInfo cabInfo = new CabInfo("test.cab");
791 cabInfo.Pack(dirA);
792 cabInfo.Unpack(dirB);
793 bool directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB);
794 Assert.IsFalse(directoryMatch,
795 "Testing whether cabinet output directory matches input directory.");
796 Directory.Delete(dirB, true);
797 Directory.CreateDirectory(dirB);
798 cabInfo.Pack(dirA, true, CompressionLevel.Normal, null);
799 cabInfo.Unpack(dirB);
800 directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB);
801 Assert.IsTrue(directoryMatch,
802 "Testing whether cabinet output directory matches input directory.");
803 Directory.Delete(dirB, true);
804 Directory.Delete(Path.Combine(dirA, "sub"), true);
805 Directory.CreateDirectory(dirB);
806 cabInfo.Delete();
807
808 cabInfo.PackFiles(dirA, files, null);
809 cabInfo.UnpackFiles(files, dirB, null);
810 directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB);
811 Assert.IsTrue(directoryMatch,
812 "Testing whether cabinet output directory matches input directory.");
813 Directory.Delete(dirB, true);
814 Directory.CreateDirectory(dirB);
815 cabInfo.Delete();
816
817 IDictionary<string, string> testMap = new Dictionary<string, string>(files.Length);
818 for (int iFile = 0; iFile < fileCount; iFile++)
819 {
820 testMap[files[iFile] + ".key"] = files[iFile];
821 }
822 cabInfo.PackFileSet(dirA, testMap);
823 cabInfo.UnpackFileSet(testMap, dirB);
824 directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB);
825 Assert.IsTrue(directoryMatch,
826 "Testing whether cabinet output directory matches input directory.");
827 Directory.Delete(dirB, true);
828 Directory.CreateDirectory(dirB);
829
830 testMap.Remove(files[1] + ".key");
831 cabInfo.UnpackFileSet(testMap, dirB);
832 directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB);
833 Assert.IsFalse(directoryMatch,
834 "Testing whether cabinet output directory matches input directory.");
835 Directory.Delete(dirB, true);
836 Directory.CreateDirectory(dirB);
837 cabInfo.Delete();
838
839 cabInfo.PackFiles(dirA, files, null);
840 cabInfo.UnpackFile("€2.txt", Path.Combine(dirB, "test.txt"));
841 Assert.IsTrue(File.Exists(Path.Combine(dirB, "test.txt")));
842 Assert.AreEqual<int>(1, Directory.GetFiles(dirB).Length);
843 }
844
845 [TestMethod]
846 //[Ignore] // Requires clean environment.
847 public void CabFileInfoProperties()
848 {
849 CabInfo cabInfo = new CabInfo("test.cab");
850 int txtSize = 10240;
851 CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize);
852 CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize);
853 File.SetAttributes("test01.txt", FileAttributes.ReadOnly | FileAttributes.Archive);
854 DateTime testTime = File.GetLastWriteTime("test01.txt");
855 cabInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null);
856 File.SetAttributes("test01.txt", FileAttributes.Archive);
857
858 CabFileInfo cfi = new CabFileInfo(cabInfo, "test01.txt");
859 Assert.AreEqual(cabInfo.FullName, cfi.CabinetName);
860 Assert.AreEqual<int>(0, ((CabFileInfo) cfi).CabinetFolderNumber);
861 Assert.AreEqual<string>(Path.Combine(cabInfo.FullName, "test01.txt"), cfi.FullName);
862 cfi = new CabFileInfo(cabInfo, "test01.txt");
863 Assert.IsTrue(cfi.Exists);
864 cfi = new CabFileInfo(cabInfo, "test01.txt");
865 Assert.AreEqual<long>(txtSize, cfi.Length);
866 cfi = new CabFileInfo(cabInfo, "test00.txt");
867 Assert.AreEqual<FileAttributes>(FileAttributes.Archive, cfi.Attributes);
868 cfi = new CabFileInfo(cabInfo, "test01.txt");
869 Assert.AreEqual<FileAttributes>(FileAttributes.ReadOnly | FileAttributes.Archive, cfi.Attributes);
870 cfi = new CabFileInfo(cabInfo, "test01.txt");
871 Assert.IsTrue((testTime - cfi.LastWriteTime).Duration() < new TimeSpan(0, 0, 5));
872 Assert.AreEqual<string>(Path.Combine(cabInfo.FullName, "test01.txt"), cfi.ToString());
873 cfi.CopyTo("testcopy.txt");
874 Assert.IsTrue(File.Exists("testCopy.txt"));
875 Assert.AreEqual<long>(cfi.Length, new FileInfo("testCopy.txt").Length);
876
877 Exception caughtEx = null;
878 try
879 {
880 cfi.CopyTo("testcopy.txt", false);
881 }
882 catch (Exception ex) { caughtEx = ex; }
883 Assert.IsInstanceOfType(caughtEx, typeof(IOException));
884 }
885
886 [TestMethod]
887 public void CabFileInfoOpenText()
888 {
889 CabInfo cabInfo = new CabInfo("test.cab");
890 int txtSize = 10240;
891 CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize);
892 CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize);
893
894 string expectedText = File.ReadAllText("test01.txt");
895
896 cabInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null);
897
898 CabFileInfo cfi = new CabFileInfo(cabInfo, "test01.txt");
899 using (StreamReader cabFileReader = cfi.OpenText())
900 {
901 string text = cabFileReader.ReadToEnd();
902 Assert.AreEqual(expectedText, text);
903
904 // Check the assumption that the cab can't be deleted while a stream is open.
905 Exception caughtEx = null;
906 try
907 {
908 File.Delete(cabInfo.FullName);
909 }
910 catch (Exception ex)
911 {
912 caughtEx = ex;
913 }
914
915 Assert.IsInstanceOfType(caughtEx, typeof(IOException));
916 }
917
918 // Ensure all streams are closed after disposing of the StreamReader returned by OpenText.
919 File.Delete(cabInfo.FullName);
920 }
921
922 [TestMethod]
923 public void CabFileInfoNullParams()
924 {
925 Exception caughtEx;
926 CabInfo cabInfo = new CabInfo("test.cab");
927 int txtSize = 10240;
928 CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize);
929 CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize);
930 cabInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null);
931 CabFileInfo cfi = new CabFileInfo(cabInfo, "test01.txt");
932
933 caughtEx = null;
934 try
935 {
936 new CabFileInfo(null, "test00.txt");
937 }
938 catch (Exception ex) { caughtEx = ex; }
939 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException));
940 caughtEx = null;
941 try
942 {
943 new CabFileInfo(cabInfo, null);
944 }
945 catch (Exception ex) { caughtEx = ex; }
946 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException));
947 caughtEx = null;
948 try
949 {
950 cfi.CopyTo(null);
951 }
952 catch (Exception ex) { caughtEx = ex; }
953 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException));
954 }
955
956 [TestMethod]
957 public void CabInfoSerialization()
958 {
959 CabInfo cabInfo = new CabInfo("testser.cab");
960 int txtSize = 10240;
961 CompressionTestUtil.GenerateRandomFile("testser00.txt", 0, txtSize);
962 CompressionTestUtil.GenerateRandomFile("testser01.txt", 1, txtSize);
963 cabInfo.PackFiles(null, new string[] { "testser00.txt", "testser01.txt" }, null);
964 ArchiveFileInfo cfi = cabInfo.GetFiles()[1];
965
966 MemoryStream memStream = new MemoryStream();
967
968 BinaryFormatter formatter = new BinaryFormatter();
969
970 memStream.Seek(0, SeekOrigin.Begin);
971 formatter.Serialize(memStream, cabInfo);
972 memStream.Seek(0, SeekOrigin.Begin);
973 CabInfo cabInfo2 = (CabInfo) formatter.Deserialize(memStream);
974 Assert.AreEqual<string>(cabInfo.FullName, cabInfo2.FullName);
975
976 memStream.Seek(0, SeekOrigin.Begin);
977 formatter.Serialize(memStream, cfi);
978 memStream.Seek(0, SeekOrigin.Begin);
979 CabFileInfo cfi2 = (CabFileInfo) formatter.Deserialize(memStream);
980 Assert.AreEqual<string>(cfi.FullName, cfi2.FullName);
981 Assert.AreEqual<long>(cfi.Length, cfi2.Length);
982
983 CabException cabEx = new CabException();
984 memStream.Seek(0, SeekOrigin.Begin);
985 formatter.Serialize(memStream, cabEx);
986 memStream.Seek(0, SeekOrigin.Begin);
987 formatter.Deserialize(memStream);
988
989 cabEx = new CabException("Test exception.", null);
990 Assert.AreEqual<string>("Test exception.", cabEx.Message);
991 }
992
993 [TestMethod]
994 public void CabFileStreamContextNullParams()
995 {
996 ArchiveFileStreamContext streamContext = null;
997 Exception caughtEx = null;
998 try
999 {
1000 streamContext = new ArchiveFileStreamContext(null);
1001 }
1002 catch (Exception ex) { caughtEx = ex; }
1003 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Passing null to constructor.");
1004 caughtEx = null;
1005 try
1006 {
1007 streamContext = new ArchiveFileStreamContext(new string[] { }, "testDir", new Dictionary<string, string>());
1008 }
1009 catch (Exception ex) { caughtEx = ex; }
1010 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Passing 0-length array to constructor.");
1011 caughtEx = null;
1012 try
1013 {
1014 streamContext = new ArchiveFileStreamContext(new string[] { "test.cab" }, null, null);
1015 }
1016 catch (Exception ex) { caughtEx = ex; }
1017 Assert.IsNull(caughtEx);
1018 }
1019
1020 [TestMethod]
1021 public void CabinetTruncateOnCreate()
1022 {
1023 CabInfo cabInfo = new CabInfo("testtruncate.cab");
1024 int txtSize = 20240;
1025 CompressionTestUtil.GenerateRandomFile("testtruncate0.txt", 0, txtSize);
1026 CompressionTestUtil.GenerateRandomFile("testtruncate1.txt", 1, txtSize);
1027 cabInfo.PackFiles(null, new string[] { "testtruncate0.txt", "testtruncate1.txt" }, null);
1028
1029 long size1 = cabInfo.Length;
1030
1031 txtSize /= 5;
1032 CompressionTestUtil.GenerateRandomFile("testtruncate2.txt", 2, txtSize);
1033 CompressionTestUtil.GenerateRandomFile("testtruncate3.txt", 3, txtSize);
1034 cabInfo.PackFiles(null, new string[] { "testtruncate2.txt", "testtruncate3.txt" }, null);
1035
1036 // The newly created cab file should be smaller than before.
1037 Assert.AreNotEqual<long>(size1, cabInfo.Length, "Checking that cabinet file got truncated when creating a smaller cab in-place.");
1038 }
1039
1040 [TestMethod]
1041 public void CabTruncatedArchive()
1042 {
1043 CabInfo cabInfo = new CabInfo("test-t.cab");
1044 CompressionTestUtil.GenerateRandomFile("cabtest-0.txt", 0, 5);
1045 CompressionTestUtil.GenerateRandomFile("cabtest-1.txt", 1, 5);
1046 cabInfo.PackFiles(null, new string[] { "cabtest-0.txt", "cabtest-1.txt" }, null);
1047
1048 CompressionTestUtil.TestTruncatedArchive(cabInfo, typeof(CabException));
1049 }
1050 private const string TEST_FILENAME_PREFIX = "\x20AC";
1051
1052 private IList<ArchiveFileInfo> RunCabinetPackUnpack(int fileCount, long fileSize)
1053 {
1054 return RunCabinetPackUnpack(fileCount, fileSize, 0, 0);
1055 }
1056 private IList<ArchiveFileInfo> RunCabinetPackUnpack(int fileCount, long fileSize,
1057 long maxFolderSize, long maxArchiveSize)
1058 {
1059 return this.RunCabinetPackUnpack(fileCount, fileSize, maxFolderSize, maxArchiveSize, CompressionLevel.Normal);
1060 }
1061 private IList<ArchiveFileInfo> RunCabinetPackUnpack(int fileCount, long fileSize,
1062 long maxFolderSize, long maxArchiveSize, CompressionLevel compLevel)
1063 {
1064 Console.WriteLine("Creating cabinet with {0} files of size {1}",
1065 fileCount, fileSize);
1066 Console.WriteLine("MaxFolderSize={0}, MaxArchiveSize={1}, CompressionLevel={2}",
1067 maxFolderSize, maxArchiveSize, compLevel);
1068
1069 string dirA = String.Format("{0}-{1}-A", fileCount, fileSize);
1070 if (Directory.Exists(dirA)) Directory.Delete(dirA, true);
1071 Directory.CreateDirectory(dirA);
1072 string dirB = String.Format("{0}-{1}-B", fileCount, fileSize);
1073 if (Directory.Exists(dirB)) Directory.Delete(dirB, true);
1074 Directory.CreateDirectory(dirB);
1075
1076 string[] files = new string[fileCount];
1077 for (int iFile = 0; iFile < fileCount; iFile++)
1078 {
1079 files[iFile] = TEST_FILENAME_PREFIX + iFile + ".txt";
1080 CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize);
1081 }
1082
1083 string[] archiveNames = new string[100];
1084 for (int i = 0; i < archiveNames.Length; i++)
1085 {
1086 archiveNames[i] = String.Format("{0}-{1}{2}{3}.cab", fileCount, fileSize,
1087 (i == 0 ? "" : "-"), (i == 0 ? "" : i.ToString()));
1088 }
1089
1090 string progressTextFile = String.Format("progress_{0}-{1}.txt", fileCount, fileSize);
1091 CompressionTestUtil testUtil = new CompressionTestUtil(progressTextFile);
1092
1093 IList<ArchiveFileInfo> fileInfo;
1094 using (CabEngine cabEngine = new CabEngine())
1095 {
1096 cabEngine.CompressionLevel = compLevel;
1097
1098 File.AppendAllText(progressTextFile,
1099 "\r\n\r\n====================================================\r\nCREATE\r\n\r\n");
1100 cabEngine.Progress += testUtil.PrintArchiveProgress;
1101
1102 OptionStreamContext streamContext = new OptionStreamContext(archiveNames, dirA, null);
1103 if (maxFolderSize == 1)
1104 {
1105 streamContext.OptionHandler =
1106 delegate(string optionName, object[] parameters)
1107 {
1108 if (optionName == "nextFolder") return true;
1109 return null;
1110 };
1111 }
1112 else if (maxFolderSize > 1)
1113 {
1114 streamContext.OptionHandler =
1115 delegate(string optionName, object[] parameters)
1116 {
1117 if (optionName == "maxFolderSize") return maxFolderSize;
1118 return null;
1119 };
1120 }
1121 cabEngine.Pack(streamContext, files, maxArchiveSize);
1122
1123 IList<string> createdArchiveNames = new List<string>(archiveNames.Length);
1124 for (int i = 0; i < archiveNames.Length; i++)
1125 {
1126 if (File.Exists(archiveNames[i]))
1127 {
1128 createdArchiveNames.Add(archiveNames[i]);
1129 }
1130 else
1131 {
1132 break;
1133 }
1134 }
1135
1136 Console.WriteLine("Listing cabinet with {0} files of size {1}",
1137 fileCount, fileSize);
1138 File.AppendAllText(progressTextFile, "\r\n\r\nLIST\r\n\r\n");
1139 fileInfo = cabEngine.GetFileInfo(
1140 new ArchiveFileStreamContext(createdArchiveNames, null, null), null);
1141
1142 Assert.AreEqual<int>(fileCount, fileInfo.Count);
1143 if (fileCount > 0)
1144 {
1145 int folders = ((CabFileInfo) fileInfo[fileInfo.Count - 1]).CabinetFolderNumber + 1;
1146 if (maxFolderSize == 1)
1147 {
1148 Assert.AreEqual<int>(fileCount, folders);
1149 }
1150 }
1151
1152 Console.WriteLine("Extracting cabinet with {0} files of size {1}",
1153 fileCount, fileSize);
1154 File.AppendAllText(progressTextFile, "\r\n\r\nEXTRACT\r\n\r\n");
1155 cabEngine.Unpack(new ArchiveFileStreamContext(createdArchiveNames, dirB, null), null);
1156 }
1157
1158 bool directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB);
1159 Assert.IsTrue(directoryMatch,
1160 "Testing whether cabinet output directory matches input directory.");
1161
1162 return fileInfo;
1163 }
1164 }
1165}
diff --git a/src/dtf/WixToolsetTests.Dtf.Compression.Cab/WixToolsetTests.Dtf.Compression.Cab.csproj b/src/dtf/WixToolsetTests.Dtf.Compression.Cab/WixToolsetTests.Dtf.Compression.Cab.csproj
new file mode 100644
index 00000000..4c269d55
--- /dev/null
+++ b/src/dtf/WixToolsetTests.Dtf.Compression.Cab/WixToolsetTests.Dtf.Compression.Cab.csproj
@@ -0,0 +1,32 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
5 <PropertyGroup>
6 <ProjectGuid>{4544158C-2D63-4146-85FF-62169280144E}</ProjectGuid>
7 <OutputType>Library</OutputType>
8 <RootNamespace>WixToolsetTests.Dtf.Cab</RootNamespace>
9 <AssemblyName>WixToolsetTests.Dtf.Cab</AssemblyName>
10 <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
11 <CreateDocumentation>false</CreateDocumentation>
12 </PropertyGroup>
13
14 <ItemGroup>
15 <Compile Include="CabTest.cs" />
16 </ItemGroup>
17
18 <ItemGroup>
19 <Reference Include="System" />
20 <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
21 <Reference Include="System.Data" />
22 <Reference Include="System.Xml" />
23 </ItemGroup>
24
25 <ItemGroup>
26 <ProjectReference Include="..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" />
27 <ProjectReference Include="..\WixToolset.Dtf.Compression.Cab\WixToolset.Dtf.Compression.Cab.csproj" />
28 <ProjectReference Include="..\WixToolsetTests.Dtf.Compression\WixToolsetTests.Dtf.Compression.csproj" />
29 </ItemGroup>
30
31 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
32</Project>
diff --git a/src/dtf/WixToolsetTests.Dtf.Compression.Zip/WixToolsetTests.Dtf.Compression.Zip.csproj b/src/dtf/WixToolsetTests.Dtf.Compression.Zip/WixToolsetTests.Dtf.Compression.Zip.csproj
new file mode 100644
index 00000000..c65563b6
--- /dev/null
+++ b/src/dtf/WixToolsetTests.Dtf.Compression.Zip/WixToolsetTests.Dtf.Compression.Zip.csproj
@@ -0,0 +1,30 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
5 <PropertyGroup>
6 <ProjectGuid>{328799BB-7B03-4B28-8180-4132211FD07D}</ProjectGuid>
7 <OutputType>Library</OutputType>
8 <RootNamespace>WixToolsetTests.Dtf</RootNamespace>
9 <AssemblyName>WixToolsetTests.Dtf.Zip</AssemblyName>
10 <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
11 <CreateDocumentation>false</CreateDocumentation>
12 </PropertyGroup>
13
14 <ItemGroup>
15 <Compile Include="ZipTest.cs" />
16 </ItemGroup>
17
18 <ItemGroup>
19 <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
20 <Reference Include="System" />
21 </ItemGroup>
22
23 <ItemGroup>
24 <ProjectReference Include="..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" />
25 <ProjectReference Include="..\WixToolset.Dtf.Compression.Zip\WixToolset.Dtf.Compression.Zip.csproj" />
26 <ProjectReference Include="..\WixToolsetTests.Dtf.Compression\WixToolsetTests.Dtf.Compression.csproj" />
27 </ItemGroup>
28
29 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
30</Project>
diff --git a/src/dtf/WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs b/src/dtf/WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs
new file mode 100644
index 00000000..b264ad5b
--- /dev/null
+++ b/src/dtf/WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs
@@ -0,0 +1,518 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Test
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Collections.Generic;
9 using Microsoft.VisualStudio.TestTools.UnitTesting;
10 using WixToolset.Dtf.Compression;
11 using WixToolset.Dtf.Compression.Zip;
12
13 [TestClass]
14 public class ZipTest
15 {
16 public ZipTest()
17 {
18 }
19
20 [TestInitialize]
21 public void Initialize()
22 {
23 }
24
25 [TestCleanup]
26 public void Cleanup()
27 {
28 }
29
30 [TestMethod]
31 public void ZipFileCounts()
32 {
33 this.RunZipPackUnpack(0, 10, 0);
34 this.RunZipPackUnpack(0, 100000, 0);
35 this.RunZipPackUnpack(1, 10, 0);
36 this.RunZipPackUnpack(100, 10, 0);
37 }
38
39 [TestMethod]
40 [Ignore] // Takes too long to run regularly.
41 public void ZipExtremeFileCounts()
42 {
43 this.RunZipPackUnpack(66000, 10, 0);
44 }
45
46 [TestMethod]
47 public void ZipFileSizes()
48 {
49 this.RunZipPackUnpack(1, 0, 0);
50 for (int n = 1; n <= 33; n++)
51 {
52 this.RunZipPackUnpack(1, n, 0);
53 }
54 this.RunZipPackUnpack(1, 100 * 1024, 0);
55 this.RunZipPackUnpack(1, 10 * 1024 * 1024, 0);
56 }
57
58 [Timeout(36000000), TestMethod]
59 [Ignore] // Takes too long to run regularly.
60 public void ZipExtremeFileSizes()
61 {
62 //this.RunZipPackUnpack(10, 512L * 1024 * 1024, 0); // 5GB
63 this.RunZipPackUnpack(1, 5L * 1024 * 1024 * 1024, 0, CompressionLevel.None); // 5GB
64 }
65
66 [TestMethod]
67 public void ZipArchiveCounts()
68 {
69 IList<ArchiveFileInfo> fileInfo;
70 fileInfo = this.RunZipPackUnpack(10, 100 * 1024, 400 * 1024, CompressionLevel.None);
71 Assert.AreEqual<int>(2, fileInfo[fileInfo.Count - 1].ArchiveNumber,
72 "Testing whether archive spans the correct # of zip files.");
73
74 fileInfo = this.RunZipPackUnpack(2, 90 * 1024, 40 * 1024, CompressionLevel.None);
75 Assert.AreEqual<int>(2, fileInfo[fileInfo.Count - 1].ArchiveNumber,
76 "Testing whether archive spans the correct # of zip files.");
77 }
78
79 [TestMethod]
80 public void ZipProgress()
81 {
82 CompressionTestUtil.ExpectedProgress = new List<int[]>(new int[][] {
83 // StatusType, CurFile,TotalFiles,CurFolder,CurArchive,TotalArchives
84 new int[] { (int) ArchiveProgressType.StartArchive, 0, 15, 0, 0, 1 },
85 new int[] { (int) ArchiveProgressType.StartFile, 0, 15, 0, 0, 1 },
86 new int[] { (int) ArchiveProgressType.FinishFile, 0, 15, 0, 0, 1 },
87 new int[] { (int) ArchiveProgressType.StartFile, 1, 15, 0, 0, 1 },
88 new int[] { (int) ArchiveProgressType.FinishFile, 1, 15, 0, 0, 1 },
89 new int[] { (int) ArchiveProgressType.StartFile, 2, 15, 0, 0, 1 },
90 new int[] { (int) ArchiveProgressType.FinishFile, 2, 15, 0, 0, 1 },
91 new int[] { (int) ArchiveProgressType.StartFile, 3, 15, 0, 0, 1 },
92 new int[] { (int) ArchiveProgressType.FinishFile, 3, 15, 0, 0, 1 },
93 new int[] { (int) ArchiveProgressType.StartFile, 4, 15, 0, 0, 1 },
94 new int[] { (int) ArchiveProgressType.FinishFile, 4, 15, 0, 0, 1 },
95 new int[] { (int) ArchiveProgressType.StartFile, 5, 15, 0, 0, 1 },
96 new int[] { (int) ArchiveProgressType.FinishFile, 5, 15, 0, 0, 1 },
97 new int[] { (int) ArchiveProgressType.StartFile, 6, 15, 0, 0, 1 },
98 new int[] { (int) ArchiveProgressType.FinishArchive, 6, 15, 0, 0, 1 },
99 new int[] { (int) ArchiveProgressType.StartArchive, 6, 15, 0, 1, 2 },
100 new int[] { (int) ArchiveProgressType.FinishFile, 6, 15, 0, 1, 2 },
101 new int[] { (int) ArchiveProgressType.StartFile, 7, 15, 0, 1, 2 },
102 new int[] { (int) ArchiveProgressType.FinishFile, 7, 15, 0, 1, 2 },
103 new int[] { (int) ArchiveProgressType.StartFile, 8, 15, 0, 1, 2 },
104 new int[] { (int) ArchiveProgressType.FinishFile, 8, 15, 0, 1, 2 },
105 new int[] { (int) ArchiveProgressType.StartFile, 9, 15, 0, 1, 2 },
106 new int[] { (int) ArchiveProgressType.FinishFile, 9, 15, 0, 1, 2 },
107 new int[] { (int) ArchiveProgressType.StartFile, 10, 15, 0, 1, 2 },
108 new int[] { (int) ArchiveProgressType.FinishFile, 10, 15, 0, 1, 2 },
109 new int[] { (int) ArchiveProgressType.StartFile, 11, 15, 0, 1, 2 },
110 new int[] { (int) ArchiveProgressType.FinishFile, 11, 15, 0, 1, 2 },
111 new int[] { (int) ArchiveProgressType.StartFile, 12, 15, 0, 1, 2 },
112 new int[] { (int) ArchiveProgressType.FinishArchive, 12, 15, 0, 1, 2 },
113 new int[] { (int) ArchiveProgressType.StartArchive, 12, 15, 0, 2, 3 },
114 new int[] { (int) ArchiveProgressType.FinishFile, 12, 15, 0, 2, 3 },
115 new int[] { (int) ArchiveProgressType.StartFile, 13, 15, 0, 2, 3 },
116 new int[] { (int) ArchiveProgressType.FinishFile, 13, 15, 0, 2, 3 },
117 new int[] { (int) ArchiveProgressType.StartFile, 14, 15, 0, 2, 3 },
118 new int[] { (int) ArchiveProgressType.FinishFile, 14, 15, 0, 2, 3 },
119 new int[] { (int) ArchiveProgressType.FinishArchive, 14, 15, 0, 2, 3 },
120 // StatusType, CurFile,TotalFiles,CurFolder,CurArchive,TotalArchives
121 new int[] { (int) ArchiveProgressType.StartArchive, 0, 15, 0, 0, 3 },
122 new int[] { (int) ArchiveProgressType.StartFile, 0, 15, 0, 0, 3 },
123 new int[] { (int) ArchiveProgressType.FinishFile, 0, 15, 0, 0, 3 },
124 new int[] { (int) ArchiveProgressType.StartFile, 1, 15, 0, 0, 3 },
125 new int[] { (int) ArchiveProgressType.FinishFile, 1, 15, 0, 0, 3 },
126 new int[] { (int) ArchiveProgressType.StartFile, 2, 15, 0, 0, 3 },
127 new int[] { (int) ArchiveProgressType.FinishFile, 2, 15, 0, 0, 3 },
128 new int[] { (int) ArchiveProgressType.StartFile, 3, 15, 0, 0, 3 },
129 new int[] { (int) ArchiveProgressType.FinishFile, 3, 15, 0, 0, 3 },
130 new int[] { (int) ArchiveProgressType.StartFile, 4, 15, 0, 0, 3 },
131 new int[] { (int) ArchiveProgressType.FinishFile, 4, 15, 0, 0, 3 },
132 new int[] { (int) ArchiveProgressType.StartFile, 5, 15, 0, 0, 3 },
133 new int[] { (int) ArchiveProgressType.FinishFile, 5, 15, 0, 0, 3 },
134 new int[] { (int) ArchiveProgressType.StartFile, 6, 15, 0, 0, 3 },
135 new int[] { (int) ArchiveProgressType.FinishArchive, 6, 15, 0, 0, 3 },
136 new int[] { (int) ArchiveProgressType.StartArchive, 6, 15, 0, 1, 3 },
137 new int[] { (int) ArchiveProgressType.FinishFile, 6, 15, 0, 1, 3 },
138 new int[] { (int) ArchiveProgressType.StartFile, 7, 15, 0, 1, 3 },
139 new int[] { (int) ArchiveProgressType.FinishFile, 7, 15, 0, 1, 3 },
140 new int[] { (int) ArchiveProgressType.StartFile, 8, 15, 0, 1, 3 },
141 new int[] { (int) ArchiveProgressType.FinishFile, 8, 15, 0, 1, 3 },
142 new int[] { (int) ArchiveProgressType.StartFile, 9, 15, 0, 1, 3 },
143 new int[] { (int) ArchiveProgressType.FinishFile, 9, 15, 0, 1, 3 },
144 new int[] { (int) ArchiveProgressType.StartFile, 10, 15, 0, 1, 3 },
145 new int[] { (int) ArchiveProgressType.FinishFile, 10, 15, 0, 1, 3 },
146 new int[] { (int) ArchiveProgressType.StartFile, 11, 15, 0, 1, 3 },
147 new int[] { (int) ArchiveProgressType.FinishFile, 11, 15, 0, 1, 3 },
148 new int[] { (int) ArchiveProgressType.StartFile, 12, 15, 0, 1, 3 },
149 new int[] { (int) ArchiveProgressType.FinishArchive, 12, 15, 0, 1, 3 },
150 new int[] { (int) ArchiveProgressType.StartArchive, 12, 15, 0, 2, 3 },
151 new int[] { (int) ArchiveProgressType.FinishFile, 12, 15, 0, 2, 3 },
152 new int[] { (int) ArchiveProgressType.StartFile, 13, 15, 0, 2, 3 },
153 new int[] { (int) ArchiveProgressType.FinishFile, 13, 15, 0, 2, 3 },
154 new int[] { (int) ArchiveProgressType.StartFile, 14, 15, 0, 2, 3 },
155 new int[] { (int) ArchiveProgressType.FinishFile, 14, 15, 0, 2, 3 },
156 new int[] { (int) ArchiveProgressType.FinishArchive, 14, 15, 0, 2, 3 },
157 });
158 CompressionTestUtil.ExpectedProgress = null;
159
160 try
161 {
162 this.RunZipPackUnpack(15, 20 * 1024, 130 * 1024, CompressionLevel.None);
163 }
164 finally
165 {
166 CompressionTestUtil.ExpectedProgress = null;
167 }
168 }
169
170 [TestMethod]
171 //[Ignore] // Requires clean environment.
172 public void ZipArchiveSizes()
173 {
174 Console.WriteLine("Testing various values for the maxArchiveSize parameter.");
175 this.RunZipPackUnpack(5, 1024, Int64.MinValue);
176 this.RunZipPackUnpack(5, 1024, -1);
177 this.RunZipPackUnpack(2, 10, 0);
178
179 this.RunZipPackUnpack(1, 10, 1);
180 this.RunZipPackUnpack(2, 10, 2);
181 this.RunZipPackUnpack(2, 10, 3);
182 this.RunZipPackUnpack(2, 10, 4);
183 this.RunZipPackUnpack(2, 10, 5);
184 this.RunZipPackUnpack(2, 10, 6);
185 this.RunZipPackUnpack(2, 10, 7);
186 this.RunZipPackUnpack(5, 10, 8);
187 this.RunZipPackUnpack(5, 10, 9);
188 this.RunZipPackUnpack(5, 10, 10);
189 this.RunZipPackUnpack(5, 10, 11);
190 this.RunZipPackUnpack(5, 10, 12);
191
192 this.RunZipPackUnpack(5, 101, 255);
193 this.RunZipPackUnpack(5, 102, 256);
194 this.RunZipPackUnpack(5, 103, 257);
195 this.RunZipPackUnpack(5, 24000, 32768);
196 this.RunZipPackUnpack(5, 1024, Int64.MaxValue);
197 }
198
199 [TestMethod]
200 public void ZipCompLevelParam()
201 {
202 Console.WriteLine("Testing various values for the compressionLevel parameter.");
203 this.RunZipPackUnpack(5, 1024, 0, CompressionLevel.None);
204 this.RunZipPackUnpack(5, 1024, 0, CompressionLevel.Min);
205 this.RunZipPackUnpack(5, 1024, 0, CompressionLevel.Normal);
206 this.RunZipPackUnpack(5, 1024, 0, CompressionLevel.Max);
207 this.RunZipPackUnpack(5, 1024, 0, (CompressionLevel) ((int) CompressionLevel.None - 1));
208 this.RunZipPackUnpack(5, 1024, 0, (CompressionLevel) ((int) CompressionLevel.Max + 1));
209 this.RunZipPackUnpack(5, 1024, 0, (CompressionLevel) Int32.MinValue);
210 this.RunZipPackUnpack(5, 1024, 0, (CompressionLevel) Int32.MaxValue);
211 }
212
213 [TestMethod]
214 public void ZipInfoGetFiles()
215 {
216 IList<ZipFileInfo> fileInfos;
217 ZipInfo zipInfo = new ZipInfo("testgetfiles.zip");
218
219 int txtSize = 10240;
220 CompressionTestUtil.GenerateRandomFile("testinfo0.txt", 0, txtSize);
221 CompressionTestUtil.GenerateRandomFile("testinfo1.txt", 1, txtSize);
222 CompressionTestUtil.GenerateRandomFile("testinfo2.ini", 2, txtSize);
223 zipInfo.PackFiles(null, new string[] { "testinfo0.txt", "testinfo1.txt", "testinfo2.ini" }, null);
224
225 fileInfos = zipInfo.GetFiles();
226 Assert.IsNotNull(fileInfos);
227 Assert.AreEqual<int>(3, fileInfos.Count);
228 Assert.AreEqual<string>("testinfo0.txt", fileInfos[0].Name);
229 Assert.AreEqual<string>("testinfo1.txt", fileInfos[1].Name);
230 Assert.AreEqual<string>("testinfo2.ini", fileInfos[2].Name);
231
232 fileInfos = zipInfo.GetFiles("*.txt");
233 Assert.IsNotNull(fileInfos);
234 Assert.AreEqual<int>(2, fileInfos.Count);
235 Assert.AreEqual<string>("testinfo0.txt", fileInfos[0].Name);
236 Assert.AreEqual<string>("testinfo1.txt", fileInfos[1].Name);
237
238 fileInfos = zipInfo.GetFiles("testinfo1.txt");
239 Assert.IsNotNull(fileInfos);
240 Assert.AreEqual<int>(1, fileInfos.Count);
241 Assert.AreEqual<string>("testinfo1.txt", fileInfos[0].Name);
242 Assert.IsTrue(DateTime.Now - fileInfos[0].LastWriteTime < TimeSpan.FromMinutes(1),
243 "Checking ZipFileInfo.LastWriteTime is current.");
244 }
245
246 [TestMethod]
247 //[Ignore] // Requires clean environment.
248 public void ZipInfoNullParams()
249 {
250 int fileCount = 10, fileSize = 1024;
251 string dirA = String.Format("{0}-{1}-A", fileCount, fileSize);
252 if (Directory.Exists(dirA)) Directory.Delete(dirA, true);
253 Directory.CreateDirectory(dirA);
254 string dirB = String.Format("{0}-{1}-B", fileCount, fileSize);
255 if (Directory.Exists(dirB)) Directory.Delete(dirB, true);
256 Directory.CreateDirectory(dirB);
257
258 string[] files = new string[fileCount];
259 for (int iFile = 0; iFile < fileCount; iFile++)
260 {
261 files[iFile] = "zipinfo-" + iFile + ".txt";
262 CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize);
263 }
264
265 ZipInfo zipInfo = new ZipInfo("testnull.zip");
266
267 CompressionTestUtil.TestArchiveInfoNullParams(zipInfo, dirA, dirB, files);
268 }
269
270 [TestMethod]
271 public void ZipFileInfoNullParams()
272 {
273 Exception caughtEx;
274 ZipInfo zipInfo = new ZipInfo("test.zip");
275 int txtSize = 10240;
276 CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize);
277 CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize);
278 zipInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null);
279 ZipFileInfo zfi = new ZipFileInfo(zipInfo, "test01.txt");
280
281 caughtEx = null;
282 try
283 {
284 new ZipFileInfo(null, "test00.txt");
285 }
286 catch (Exception ex) { caughtEx = ex; }
287 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException));
288 caughtEx = null;
289 try
290 {
291 new ZipFileInfo(zipInfo, null);
292 }
293 catch (Exception ex) { caughtEx = ex; }
294 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException));
295 caughtEx = null;
296 try
297 {
298 zfi.CopyTo(null);
299 }
300 catch (Exception ex) { caughtEx = ex; }
301 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException));
302 }
303
304 [TestMethod]
305 public void ZipEngineNullParams()
306 {
307 string[] testFiles = new string[] { "test.txt" };
308 ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext("test.zip", null, null);
309
310 using (ZipEngine zipEngine = new ZipEngine())
311 {
312 zipEngine.CompressionLevel = CompressionLevel.None;
313
314 CompressionTestUtil.TestCompressionEngineNullParams(zipEngine, streamContext, testFiles);
315 }
316 }
317
318 [TestMethod]
319 public void ZipBadPackStreamContexts()
320 {
321 string[] testFiles = new string[] { "test.txt" };
322 CompressionTestUtil.GenerateRandomFile(testFiles[0], 0, 20000);
323
324 using (ZipEngine zipEngine = new ZipEngine())
325 {
326 zipEngine.CompressionLevel = CompressionLevel.None;
327
328 CompressionTestUtil.TestBadPackStreamContexts(zipEngine, "test.zip", testFiles);
329 }
330 }
331
332 [TestMethod]
333 public void ZipBadUnpackStreamContexts()
334 {
335 int txtSize = 40960;
336 ZipInfo zipInfo = new ZipInfo("test2.zip");
337 CompressionTestUtil.GenerateRandomFile("ziptest-0.txt", 0, txtSize);
338 CompressionTestUtil.GenerateRandomFile("ziptest-1.txt", 1, txtSize);
339 zipInfo.PackFiles(null, new string[] { "ziptest-0.txt", "ziptest-1.txt" }, null);
340
341 using (ZipEngine zipEngine = new ZipEngine())
342 {
343 CompressionTestUtil.TestBadUnpackStreamContexts(zipEngine, "test2.zip");
344 }
345 }
346
347 [TestMethod]
348 [Ignore] // Failed on build server, need to investigate.
349 public void ZipTruncatedArchive()
350 {
351 ZipInfo zipInfo = new ZipInfo("test-t.zip");
352 CompressionTestUtil.GenerateRandomFile("ziptest-0.txt", 0, 5);
353 CompressionTestUtil.GenerateRandomFile("ziptest-1.txt", 1, 5);
354 zipInfo.PackFiles(null, new string[] { "ziptest-0.txt", "ziptest-1.txt" }, null);
355
356 CompressionTestUtil.TestTruncatedArchive(zipInfo, typeof(ZipException));
357 }
358
359 /*
360 [TestMethod]
361 public void ZipUnpack()
362 {
363 IList<ZipFileInfo> fileInfos;
364 foreach (FileInfo zipFile in new DirectoryInfo("D:\\temp").GetFiles("*.zip"))
365 {
366 Console.WriteLine("=====================================================");
367 Console.WriteLine(zipFile.FullName);
368 Console.WriteLine("=====================================================");
369 ZipInfo zipTest = new ZipInfo(zipFile.FullName);
370 fileInfos = zipTest.GetFiles();
371 Assert.AreNotEqual<int>(0, fileInfos.Count);
372 foreach (ArchiveFileInfo file in fileInfos)
373 {
374 Console.WriteLine("{0}\t{1}\t{2}", Path.Combine(file.Path, file.Name), file.Length, file.LastWriteTime);
375 }
376
377 Directory.CreateDirectory(Path.GetFileNameWithoutExtension(zipFile.Name));
378 zipTest.Unpack(Path.GetFileNameWithoutExtension(zipFile.Name));
379 }
380 }
381 */
382
383 /*
384 [TestMethod]
385 public void ZipUnpackSelfExtractor()
386 {
387 ZipInfo zipTest = new ZipInfo(@"C:\temp\testzip.exe");
388 IList<ZipFileInfo> fileInfos = zipTest.GetFiles();
389 Assert.AreNotEqual<int>(0, fileInfos.Count);
390 foreach (ArchiveFileInfo file in fileInfos)
391 {
392 Console.WriteLine("{0}\t{1}\t{2}", Path.Combine(file.Path, file.Name), file.Length, file.LastWriteTime);
393 }
394
395 string extractDir = Path.GetFileNameWithoutExtension(zipTest.Name);
396 Directory.CreateDirectory(extractDir);
397 zipTest.Unpack(extractDir);
398 }
399 */
400
401 private const string TEST_FILENAME_PREFIX = "\x20AC";
402
403 private IList<ArchiveFileInfo> RunZipPackUnpack(int fileCount, long fileSize,
404 long maxArchiveSize)
405 {
406 return this.RunZipPackUnpack(fileCount, fileSize, maxArchiveSize, CompressionLevel.Normal);
407 }
408
409 private IList<ArchiveFileInfo> RunZipPackUnpack(int fileCount, long fileSize,
410 long maxArchiveSize, CompressionLevel compLevel)
411 {
412 Console.WriteLine("Creating zip archive with {0} files of size {1}",
413 fileCount, fileSize);
414 Console.WriteLine("MaxArchiveSize={0}, CompressionLevel={1}", maxArchiveSize, compLevel);
415
416 string dirA = String.Format("{0}-{1}-A", fileCount, fileSize);
417 if (Directory.Exists(dirA)) Directory.Delete(dirA, true);
418 Directory.CreateDirectory(dirA);
419 string dirB = String.Format("{0}-{1}-B", fileCount, fileSize);
420 if (Directory.Exists(dirB)) Directory.Delete(dirB, true);
421 Directory.CreateDirectory(dirB);
422
423 string[] files = new string[fileCount];
424 for (int iFile = 0; iFile < fileCount; iFile++)
425 {
426 files[iFile] = TEST_FILENAME_PREFIX + iFile + ".txt";
427 CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize);
428 }
429
430 string[] archiveNames = new string[1000];
431 for (int i = 0; i < archiveNames.Length; i++)
432 {
433 if (i < 100)
434 {
435 archiveNames[i] = String.Format(
436 (i == 0 ? "{0}-{1}.zip" : "{0}-{1}.z{2:d02}"),
437 fileCount, fileSize, i);
438 }
439 else
440 {
441 archiveNames[i] = String.Format(
442 "{0}-{1}.{2:d03}", fileCount, fileSize, i);
443 }
444 }
445
446 string progressTextFile = String.Format("progress_{0}-{1}.txt", fileCount, fileSize);
447 CompressionTestUtil testUtil = new CompressionTestUtil(progressTextFile);
448
449 IList<ArchiveFileInfo> fileInfo;
450 using (ZipEngine zipEngine = new ZipEngine())
451 {
452 zipEngine.CompressionLevel = compLevel;
453
454 File.AppendAllText(progressTextFile,
455 "\r\n\r\n====================================================\r\nCREATE\r\n\r\n");
456 zipEngine.Progress += testUtil.PrintArchiveProgress;
457
458 OptionStreamContext streamContext = new OptionStreamContext(archiveNames, dirA, null);
459 streamContext.OptionHandler =
460 delegate(string optionName, object[] parameters)
461 {
462 // For testing purposes, force zip64 for only moderately large files.
463 switch (optionName)
464 {
465 case "forceZip64":
466 return fileSize > UInt16.MaxValue;
467 default:
468 return null;
469 }
470 };
471
472 zipEngine.Pack(streamContext, files, maxArchiveSize);
473
474 string checkArchiveName = archiveNames[0];
475 if (File.Exists(archiveNames[1])) checkArchiveName = archiveNames[1];
476 using (Stream archiveStream = File.OpenRead(checkArchiveName))
477 {
478 bool isArchive = zipEngine.IsArchive(archiveStream);
479 Assert.IsTrue(isArchive, "Checking that created archive appears valid.");
480 }
481
482 IList<string> createdArchiveNames = new List<string>(archiveNames.Length);
483 for (int i = 0; i < archiveNames.Length; i++)
484 {
485 if (File.Exists(archiveNames[i]))
486 {
487 createdArchiveNames.Add(archiveNames[i]);
488 }
489 else
490 {
491 break;
492 }
493 }
494
495 Assert.AreNotEqual<int>(0, createdArchiveNames.Count);
496
497 Console.WriteLine("Listing zip archive with {0} files of size {1}",
498 fileCount, fileSize);
499 File.AppendAllText(progressTextFile, "\r\n\r\nLIST\r\n\r\n");
500 fileInfo = zipEngine.GetFileInfo(
501 new ArchiveFileStreamContext(createdArchiveNames, null, null), null);
502
503 Assert.AreEqual<int>(fileCount, fileInfo.Count);
504
505 Console.WriteLine("Extracting zip archive with {0} files of size {1}",
506 fileCount, fileSize);
507 File.AppendAllText(progressTextFile, "\r\n\r\nEXTRACT\r\n\r\n");
508 zipEngine.Unpack(new ArchiveFileStreamContext(createdArchiveNames, dirB, null), null);
509 }
510
511 bool directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB);
512 Assert.IsTrue(directoryMatch,
513 "Testing whether zip output directory matches input directory.");
514
515 return fileInfo;
516 }
517 }
518}
diff --git a/src/dtf/WixToolsetTests.Dtf.Compression/CompressionTestUtil.cs b/src/dtf/WixToolsetTests.Dtf.Compression/CompressionTestUtil.cs
new file mode 100644
index 00000000..e7a5373d
--- /dev/null
+++ b/src/dtf/WixToolsetTests.Dtf.Compression/CompressionTestUtil.cs
@@ -0,0 +1,649 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.IO;
5using System.Text;
6using System.Collections.Generic;
7using System.Security.Cryptography;
8using Microsoft.VisualStudio.TestTools.UnitTesting;
9using WixToolset.Dtf.Compression;
10
11namespace WixToolset.Dtf.Test
12{
13 public class CompressionTestUtil
14 {
15 private static MD5 md5 = new MD5CryptoServiceProvider();
16
17 private string progressTextFile;
18
19 public CompressionTestUtil(string progressTextFile)
20 {
21 this.progressTextFile = progressTextFile;
22 }
23
24 public static IList<int[]> ExpectedProgress
25 {
26 get { return CompressionTestUtil.expectedProgress; }
27 set { CompressionTestUtil.expectedProgress = value; }
28 }
29 private static IList<int[]> expectedProgress;
30
31 public void PrintArchiveProgress(object source, ArchiveProgressEventArgs e)
32 {
33 switch (e.ProgressType)
34 {
35 case ArchiveProgressType.StartFile:
36 {
37 Console.WriteLine("StartFile: {0}", e.CurrentFileName);
38 } break;
39 case ArchiveProgressType.FinishFile:
40 {
41 Console.WriteLine("FinishFile: {0}", e.CurrentFileName);
42 } break;
43 case ArchiveProgressType.StartArchive:
44 {
45 Console.WriteLine("StartArchive: {0} : {1}", e.CurrentArchiveNumber, e.CurrentArchiveName);
46 } break;
47 case ArchiveProgressType.FinishArchive:
48 {
49 Console.WriteLine("FinishArchive: {0} : {1}", e.CurrentArchiveNumber, e.CurrentArchiveName);
50 } break;
51 }
52
53 File.AppendAllText(this.progressTextFile, e.ToString().Replace("\n", Environment.NewLine));
54
55 if (CompressionTestUtil.expectedProgress != null &&
56 e.ProgressType != ArchiveProgressType.PartialFile &&
57 e.ProgressType != ArchiveProgressType.PartialArchive)
58 {
59 Assert.AreNotEqual<int>(0, CompressionTestUtil.expectedProgress.Count);
60 int[] expected = CompressionTestUtil.expectedProgress[0];
61 CompressionTestUtil.expectedProgress.RemoveAt(0);
62 Assert.AreEqual<ArchiveProgressType>((ArchiveProgressType) expected[0], e.ProgressType, "Checking ProgressType.");
63 Assert.AreEqual<int>(expected[1], e.CurrentFileNumber, "Checking CurrentFileNumber.");
64 Assert.AreEqual<int>(expected[2], e.TotalFiles, "Checking TotalFiles.");
65 Assert.AreEqual<int>(expected[4], e.CurrentArchiveNumber, "Checking CurrentArchiveNumber.");
66 Assert.AreEqual<int>(expected[5], e.TotalArchives, "Checking TotalArchives.");
67 }
68 }
69
70 public static bool CompareDirectories(string dirA, string dirB)
71 {
72 bool difference = false;
73 Console.WriteLine("Comparing directories {0}, {1}", dirA, dirB);
74
75 string[] filesA = Directory.GetFiles(dirA);
76 string[] filesB = Directory.GetFiles(dirB);
77 for (int iA = 0; iA < filesA.Length; iA++)
78 {
79 filesA[iA] = Path.GetFileName(filesA[iA]);
80 }
81 for (int iB = 0; iB < filesB.Length; iB++)
82 {
83 filesB[iB] = Path.GetFileName(filesB[iB]);
84 }
85 Array.Sort(filesA);
86 Array.Sort(filesB);
87
88 for (int iA = 0, iB = 0; iA < filesA.Length || iB < filesB.Length; )
89 {
90 int comp;
91 if (iA == filesA.Length)
92 {
93 comp = 1;
94 }
95 else if (iB == filesB.Length)
96 {
97 comp = -1;
98 }
99 else
100 {
101 comp = String.Compare(filesA[iA], filesB[iB]);
102 }
103 if (comp < 0)
104 {
105 Console.WriteLine("< " + filesA[iA]);
106 difference = true;
107 iA++;
108 }
109 else if (comp > 0)
110 {
111 Console.WriteLine("> " + filesB[iB]);
112 difference = true;
113 iB++;
114 }
115 else
116 {
117 string fileA = Path.Combine(dirA, filesA[iA]);
118 string fileB = Path.Combine(dirB, filesB[iB]);
119
120 byte[] hashA;
121 byte[] hashB;
122
123 lock (CompressionTestUtil.md5)
124 {
125 using (Stream fileAStream = File.OpenRead(fileA))
126 {
127 hashA = CompressionTestUtil.md5.ComputeHash(fileAStream);
128 }
129 using (Stream fileBStream = File.OpenRead(fileB))
130 {
131 hashB = CompressionTestUtil.md5.ComputeHash(fileBStream);
132 }
133 }
134
135 for (int i = 0; i < hashA.Length; i++)
136 {
137 if (hashA[i] != hashB[i])
138 {
139 Console.WriteLine("~ " + filesA[iA]);
140 difference = true;
141 break;
142 }
143 }
144
145 iA++;
146 iB++;
147 }
148 }
149
150 string[] dirsA = Directory.GetDirectories(dirA);
151 string[] dirsB = Directory.GetDirectories(dirB);
152 for (int iA = 0; iA < dirsA.Length; iA++)
153 {
154 dirsA[iA] = Path.GetFileName(dirsA[iA]);
155 }
156 for (int iB = 0; iB < dirsB.Length; iB++)
157 {
158 dirsB[iB] = Path.GetFileName(dirsB[iB]);
159 }
160 Array.Sort(dirsA);
161 Array.Sort(dirsB);
162
163 for (int iA = 0, iB = 0; iA < dirsA.Length || iB < dirsB.Length; )
164 {
165 int comp;
166 if (iA == dirsA.Length)
167 {
168 comp = 1;
169 }
170 else if (iB == dirsB.Length)
171 {
172 comp = -1;
173 }
174 else
175 {
176 comp = String.Compare(dirsA[iA], dirsB[iB]);
177 }
178 if (comp < 0)
179 {
180 Console.WriteLine("< {0}\\", dirsA[iA]);
181 difference = true;
182 iA++;
183 }
184 else if (comp > 0)
185 {
186 Console.WriteLine("> {1}\\", dirsB[iB]);
187 difference = true;
188 iB++;
189 }
190 else
191 {
192 string subDirA = Path.Combine(dirA, dirsA[iA]);
193 string subDirB = Path.Combine(dirB, dirsB[iB]);
194 if (!CompressionTestUtil.CompareDirectories(subDirA, subDirB))
195 {
196 difference = true;
197 }
198 iA++;
199 iB++;
200 }
201 }
202
203 return !difference;
204 }
205
206
207 public static void GenerateRandomFile(string path, int seed, long size)
208 {
209 Console.WriteLine("Generating random file {0} (seed={1}, size={2})",
210 path, seed, size);
211 Random random = new Random(seed);
212 bool easy = random.Next(2) == 1;
213 int chunk = 1024 * random.Next(1, 100);
214 using (TextWriter tw = new StreamWriter(
215 File.Create(path, 4096), Encoding.ASCII))
216 {
217 for (long count = 0; count < size; count++)
218 {
219 char c = (char) (easy ? random.Next('a', 'b' + 1)
220 : random.Next(32, 127));
221 tw.Write(c);
222 if (--chunk == 0)
223 {
224 chunk = 1024 * random.Next(1, 101);
225 easy = random.Next(2) == 1;
226 }
227 }
228 }
229 }
230
231 public static void TestArchiveInfoNullParams(
232 ArchiveInfo archiveInfo,
233 string dirA,
234 string dirB,
235 string[] files)
236 {
237 Exception caughtEx = null;
238 try
239 {
240 archiveInfo.PackFiles(null, null, files);
241 }
242 catch (Exception ex) { caughtEx = ex; }
243 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
244 caughtEx = null;
245 try
246 {
247 archiveInfo.PackFiles(null, files, new string[] { });
248 }
249 catch (Exception ex) { caughtEx = ex; }
250 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentOutOfRangeException), "Caught exception: " + caughtEx);
251 caughtEx = null;
252 try
253 {
254 archiveInfo.PackFileSet(dirA, null);
255 }
256 catch (Exception ex) { caughtEx = ex; }
257 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
258 caughtEx = null;
259 try
260 {
261 archiveInfo.PackFiles(null, files, files);
262 }
263 catch (Exception ex) { caughtEx = ex; }
264 Assert.IsInstanceOfType(caughtEx, typeof(FileNotFoundException), "Caught exception: " + caughtEx);
265 caughtEx = null;
266 try
267 {
268 archiveInfo.PackFiles(dirA, null, files);
269 }
270 catch (Exception ex) { caughtEx = ex; }
271 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
272 caughtEx = null;
273 try
274 {
275 archiveInfo.PackFiles(dirA, files, null);
276 }
277 catch (Exception ex) { caughtEx = ex; }
278 Assert.IsNull(caughtEx, "Caught exception: " + caughtEx);
279
280 caughtEx = null;
281 try
282 {
283 archiveInfo.CopyTo(null);
284 }
285 catch (Exception ex) { caughtEx = ex; }
286 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
287 caughtEx = null;
288 try
289 {
290 archiveInfo.CopyTo(null, true);
291 }
292 catch (Exception ex) { caughtEx = ex; }
293 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
294 caughtEx = null;
295 try
296 {
297 archiveInfo.MoveTo(null);
298 }
299 catch (Exception ex) { caughtEx = ex; }
300 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
301 caughtEx = null;
302 try
303 {
304 archiveInfo.GetFiles(null);
305 }
306 catch (Exception ex) { caughtEx = ex; }
307 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
308 caughtEx = null;
309 try
310 {
311 archiveInfo.UnpackFile(null, "test.txt");
312 }
313 catch (Exception ex) { caughtEx = ex; }
314 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
315 caughtEx = null;
316 try
317 {
318 archiveInfo.UnpackFile("test.txt", null);
319 }
320 catch (Exception ex) { caughtEx = ex; }
321 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
322 caughtEx = null;
323 try
324 {
325 archiveInfo.UnpackFiles(null, dirB, files);
326 }
327 catch (Exception ex) { caughtEx = ex; }
328 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
329 caughtEx = null;
330 try
331 {
332 archiveInfo.UnpackFiles(files, null, null);
333 }
334 catch (Exception ex) { caughtEx = ex; }
335 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
336 caughtEx = null;
337 try
338 {
339 archiveInfo.UnpackFiles(files, null, files);
340 }
341 catch (Exception ex) { caughtEx = ex; }
342 Assert.IsNull(caughtEx, "Caught exception: " + caughtEx);
343 caughtEx = null;
344 try
345 {
346 archiveInfo.UnpackFiles(files, dirB, null);
347 }
348 catch (Exception ex) { caughtEx = ex; }
349 Assert.IsNull(caughtEx, "Caught exception: " + caughtEx);
350 caughtEx = null;
351 try
352 {
353 archiveInfo.UnpackFiles(files, dirB, new string[] { });
354 }
355 catch (Exception ex) { caughtEx = ex; }
356 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentOutOfRangeException), "Caught exception: " + caughtEx);
357 caughtEx = null;
358 try
359 {
360 archiveInfo.UnpackFileSet(null, dirB);
361 }
362 catch (Exception ex) { caughtEx = ex; }
363 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
364 }
365
366 public static void TestCompressionEngineNullParams(
367 CompressionEngine engine,
368 ArchiveFileStreamContext streamContext,
369 string[] testFiles)
370 {
371 Exception caughtEx;
372
373 Console.WriteLine("Testing null streamContext.");
374 caughtEx = null;
375 try
376 {
377 engine.Pack(null, testFiles);
378 }
379 catch (Exception ex) { caughtEx = ex; }
380 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
381 caughtEx = null;
382 try
383 {
384 engine.Pack(null, testFiles, 0);
385 }
386 catch (Exception ex) { caughtEx = ex; }
387 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
388
389 Console.WriteLine("Testing null files.");
390 caughtEx = null;
391 try
392 {
393 engine.Pack(streamContext, null);
394 }
395 catch (Exception ex) { caughtEx = ex; }
396 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
397
398 Console.WriteLine("Testing null files.");
399 caughtEx = null;
400 try
401 {
402 engine.Pack(streamContext, null, 0);
403 }
404 catch (Exception ex) { caughtEx = ex; }
405 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
406
407
408 Console.WriteLine("Testing null stream.");
409 caughtEx = null;
410 try
411 {
412 engine.IsArchive(null);
413 }
414 catch (Exception ex) { caughtEx = ex; }
415 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
416 caughtEx = null;
417 try
418 {
419 engine.FindArchiveOffset(null);
420 }
421 catch (Exception ex) { caughtEx = ex; }
422 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
423 caughtEx = null;
424 try
425 {
426 engine.GetFiles(null);
427 }
428 catch (Exception ex) { caughtEx = ex; }
429 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
430 caughtEx = null;
431 try
432 {
433 engine.GetFileInfo(null);
434 }
435 catch (Exception ex) { caughtEx = ex; }
436 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
437 caughtEx = null;
438 try
439 {
440 engine.Unpack(null, "testUnpack.txt");
441 }
442 catch (Exception ex) { caughtEx = ex; }
443 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
444 Console.WriteLine("Testing null streamContext.");
445 caughtEx = null;
446 try
447 {
448 engine.GetFiles(null, null);
449 }
450 catch (Exception ex) { caughtEx = ex; }
451 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
452 caughtEx = null;
453 try
454 {
455 engine.GetFileInfo(null, null);
456 }
457 catch (Exception ex) { caughtEx = ex; }
458 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
459 caughtEx = null;
460 try
461 {
462 engine.Unpack((IUnpackStreamContext) null, null);
463 }
464 catch (Exception ex) { caughtEx = ex; }
465 Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
466 }
467
468 public static void TestBadPackStreamContexts(
469 CompressionEngine engine, string archiveName, string[] testFiles)
470 {
471 Exception caughtEx;
472
473 Console.WriteLine("Testing streamContext that returns null from GetName.");
474 caughtEx = null;
475 try
476 {
477 engine.Pack(
478 new MisbehavingStreamContext(archiveName, null, null, false, false, true, true, true, true),
479 testFiles);
480 }
481 catch (Exception ex) { caughtEx = ex; }
482 Assert.IsTrue(caughtEx is FileNotFoundException, "Caught exception: " + caughtEx);
483 Console.WriteLine("Testing streamContext that returns null from OpenArchive.");
484 caughtEx = null;
485 try
486 {
487 engine.Pack(
488 new MisbehavingStreamContext(archiveName, null, null, false, true, false, true, true, true),
489 testFiles);
490 }
491 catch (Exception ex) { caughtEx = ex; }
492 Assert.IsTrue(caughtEx is FileNotFoundException, "Caught exception: " + caughtEx);
493 Console.WriteLine("Testing streamContext that returns null from OpenFile.");
494 caughtEx = null;
495 try
496 {
497 engine.Pack(
498 new MisbehavingStreamContext(archiveName, null, null, false, true, true, true, false, true),
499 testFiles);
500 }
501 catch (Exception ex) { caughtEx = ex; }
502 Assert.IsNull(caughtEx, "Caught exception: " + caughtEx);
503 Console.WriteLine("Testing streamContext that throws on GetName.");
504 caughtEx = null;
505 try
506 {
507 engine.Pack(
508 new MisbehavingStreamContext(archiveName, null, null, true, false, true, true, true, true),
509 testFiles);
510 }
511 catch (Exception ex) { caughtEx = ex; }
512 Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx);
513 Console.WriteLine("Testing streamContext that throws on OpenArchive.");
514 caughtEx = null;
515 try
516 {
517 engine.Pack(
518 new MisbehavingStreamContext(archiveName, null, null, true, true, false, true, true, true),
519 testFiles);
520 }
521 catch (Exception ex) { caughtEx = ex; }
522 Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx);
523 Console.WriteLine("Testing streamContext that throws on CloseArchive.");
524 caughtEx = null;
525 try
526 {
527 engine.Pack(
528 new MisbehavingStreamContext(archiveName, null, null, true, true, true, false, true, true),
529 testFiles);
530 }
531 catch (Exception ex) { caughtEx = ex; }
532 Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx);
533 Console.WriteLine("Testing streamContext that throws on OpenFile.");
534 caughtEx = null;
535 try
536 {
537 engine.Pack(
538 new MisbehavingStreamContext(archiveName, null, null, true, true, true, true, false, true),
539 testFiles);
540 }
541 catch (Exception ex) { caughtEx = ex; }
542 Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx);
543 Console.WriteLine("Testing streamContext that throws on CloseFile.");
544 caughtEx = null;
545 try
546 {
547 engine.Pack(
548 new MisbehavingStreamContext(archiveName, null, null, true, true, true, true, true, false),
549 testFiles);
550 }
551 catch (Exception ex) { caughtEx = ex; }
552 Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx);
553 }
554
555 public static void TestBadUnpackStreamContexts(
556 CompressionEngine engine, string archiveName)
557 {
558 Exception caughtEx;
559
560 Console.WriteLine("Testing streamContext that returns null from OpenArchive.");
561 caughtEx = null;
562 try
563 {
564 engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, false, true, false, true, true, true), null);
565 }
566 catch (Exception ex) { caughtEx = ex; }
567 Assert.IsInstanceOfType(caughtEx, typeof(FileNotFoundException), "Caught exception: " + caughtEx);
568 Console.WriteLine("Testing streamContext that returns null from OpenFile.");
569 caughtEx = null;
570 try
571 {
572 engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, false, true, true, true, false, true), null);
573 }
574 catch (Exception ex) { caughtEx = ex; }
575 Assert.IsNull(caughtEx, "Caught exception: " + caughtEx);
576 Console.WriteLine("Testing streamContext that throws on OpenArchive.");
577 caughtEx = null;
578 try
579 {
580 engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, true, true, false, true, true, true), null);
581 }
582 catch (Exception ex) { caughtEx = ex; }
583 Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx);
584 Console.WriteLine("Testing streamContext that throws on CloseArchive.");
585 caughtEx = null;
586 try
587 {
588 engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, true, true, true, false, true, true), null);
589 }
590 catch (Exception ex) { caughtEx = ex; }
591 Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx);
592 Console.WriteLine("Testing streamContext that throws on OpenFile.");
593 caughtEx = null;
594 try
595 {
596 engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, true, true, true, true, false, true), null);
597 }
598 catch (Exception ex) { caughtEx = ex; }
599 Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx);
600 Console.WriteLine("Testing streamContext that throws on CloseFile.");
601 caughtEx = null;
602 try
603 {
604 engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, true, true, true, true, true, false), null);
605 }
606 catch (Exception ex) { caughtEx = ex; }
607 Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx);
608 }
609
610 public static void TestTruncatedArchive(
611 ArchiveInfo archiveInfo, Type expectedExceptionType)
612 {
613 for (long len = archiveInfo.Length - 1; len >= 0; len--)
614 {
615 string testArchive = String.Format("{0}.{1:d06}",
616 archiveInfo.FullName, len);
617 if (File.Exists(testArchive))
618 {
619 File.Delete(testArchive);
620 }
621
622 archiveInfo.CopyTo(testArchive);
623 using (FileStream truncateStream =
624 File.Open(testArchive, FileMode.Open, FileAccess.ReadWrite))
625 {
626 truncateStream.SetLength(len);
627 }
628
629 ArchiveInfo testArchiveInfo = (ArchiveInfo) archiveInfo.GetType()
630 .GetConstructor(new Type[] { typeof(string) }).Invoke(new object[] { testArchive });
631
632 Exception caughtEx = null;
633 try
634 {
635 testArchiveInfo.GetFiles();
636 }
637 catch (Exception ex) { caughtEx = ex; }
638 File.Delete(testArchive);
639
640 if (caughtEx != null)
641 {
642 Assert.IsInstanceOfType(caughtEx, expectedExceptionType,
643 String.Format("Caught exception listing archive truncated to {0}/{1} bytes",
644 len, archiveInfo.Length));
645 }
646 }
647 }
648 }
649}
diff --git a/src/dtf/WixToolsetTests.Dtf.Compression/MisbehavingStreamContext.cs b/src/dtf/WixToolsetTests.Dtf.Compression/MisbehavingStreamContext.cs
new file mode 100644
index 00000000..2531f3bc
--- /dev/null
+++ b/src/dtf/WixToolsetTests.Dtf.Compression/MisbehavingStreamContext.cs
@@ -0,0 +1,202 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.IO;
5using System.Collections.Generic;
6using WixToolset.Dtf.Compression;
7
8namespace WixToolset.Dtf.Test
9{
10 public class MisbehavingStreamContext : ArchiveFileStreamContext
11 {
12 public const string EXCEPTION = "Test exception.";
13
14 private bool throwEx;
15 private bool getName;
16 private bool openArchive;
17 private bool closeArchive;
18 private bool openFile;
19 private bool closeFile;
20 private int closeFileCount;
21
22 public MisbehavingStreamContext(
23 string cabinetFile,
24 string directory,
25 IDictionary<string, string> files,
26 bool throwEx,
27 bool getName,
28 bool openArchive,
29 bool closeArchive,
30 bool openFile,
31 bool closeFile)
32 : base(cabinetFile, directory, files)
33 {
34 this.throwEx = throwEx;
35 this.getName = getName;
36 this.openArchive = openArchive;
37 this.closeArchive = closeArchive;
38 this.openFile = openFile;
39 this.closeFile = closeFile;
40 }
41
42 public override string GetArchiveName(int archiveNumber)
43 {
44 if (!this.getName)
45 {
46 if (throwEx)
47 {
48 throw new Exception(EXCEPTION);
49 }
50 else
51 {
52 return null;
53 }
54 }
55 return base.GetArchiveName(archiveNumber);
56 }
57
58 public override Stream OpenArchiveWriteStream(
59 int archiveNumber,
60 string archiveName,
61 bool truncate,
62 CompressionEngine compressionEngine)
63 {
64 if (!this.openArchive)
65 {
66 if (throwEx)
67 {
68 throw new Exception(EXCEPTION);
69 }
70 else
71 {
72 return null;
73 }
74 }
75 return base.OpenArchiveWriteStream(
76 archiveNumber, archiveName, truncate, compressionEngine);
77 }
78
79 public override void CloseArchiveWriteStream(
80 int archiveNumber,
81 string archiveName,
82 Stream stream)
83 {
84 if (!this.closeArchive)
85 {
86 if (throwEx)
87 {
88 this.closeArchive = true;
89 throw new Exception(EXCEPTION);
90 }
91 return;
92 }
93 base.CloseArchiveWriteStream(archiveNumber, archiveName, stream);
94 }
95
96 public override Stream OpenFileReadStream(
97 string path,
98 out FileAttributes attributes,
99 out DateTime lastWriteTime)
100 {
101 if (!this.openFile)
102 {
103 if (throwEx)
104 {
105 throw new Exception(EXCEPTION);
106 }
107 else
108 {
109 attributes = FileAttributes.Normal;
110 lastWriteTime = DateTime.MinValue;
111 return null;
112 }
113 }
114 return base.OpenFileReadStream(path, out attributes, out lastWriteTime);
115 }
116
117 public override void CloseFileReadStream(string path, Stream stream)
118 {
119 if (!this.closeFile && ++closeFileCount == 2)
120 {
121 if (throwEx)
122 {
123 throw new Exception(EXCEPTION);
124 }
125 return;
126 }
127 base.CloseFileReadStream(path, stream);
128 }
129
130 public override Stream OpenArchiveReadStream(
131 int archiveNumber,
132 string archiveName,
133 CompressionEngine compressionEngine)
134 {
135 if (!this.openArchive)
136 {
137 if (throwEx)
138 {
139 throw new Exception(EXCEPTION);
140 }
141 else
142 {
143 return null;
144 }
145 }
146 return base.OpenArchiveReadStream(archiveNumber, archiveName, compressionEngine);
147 }
148
149 public override void CloseArchiveReadStream(
150 int archiveNumber,
151 string archiveName,
152 Stream stream)
153 {
154 if (!this.closeArchive)
155 {
156 if (throwEx)
157 {
158 this.closeArchive = true;
159 throw new Exception(EXCEPTION);
160 }
161 return;
162 }
163 base.CloseArchiveReadStream(archiveNumber, archiveName, stream);
164 }
165
166 public override Stream OpenFileWriteStream(
167 string path,
168 long fileSize,
169 DateTime lastWriteTime)
170 {
171 if (!this.openFile)
172 {
173 if (throwEx)
174 {
175 throw new Exception(EXCEPTION);
176 }
177 else
178 {
179 return null;
180 }
181 }
182 return base.OpenFileWriteStream(path, fileSize, lastWriteTime);
183 }
184
185 public override void CloseFileWriteStream(
186 string path,
187 Stream stream,
188 FileAttributes attributes,
189 DateTime lastWriteTime)
190 {
191 if (!this.closeFile && ++closeFileCount == 2)
192 {
193 if (throwEx)
194 {
195 throw new Exception(EXCEPTION);
196 }
197 return;
198 }
199 base.CloseFileWriteStream(path, stream, attributes, lastWriteTime);
200 }
201 }
202}
diff --git a/src/dtf/WixToolsetTests.Dtf.Compression/OptionStreamContext.cs b/src/dtf/WixToolsetTests.Dtf.Compression/OptionStreamContext.cs
new file mode 100644
index 00000000..98354d97
--- /dev/null
+++ b/src/dtf/WixToolsetTests.Dtf.Compression/OptionStreamContext.cs
@@ -0,0 +1,42 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.Collections.Generic;
5using WixToolset.Dtf.Compression;
6
7namespace WixToolset.Dtf.Test
8{
9 public class OptionStreamContext : ArchiveFileStreamContext
10 {
11 private PackOptionHandler packOptionHandler;
12
13 public OptionStreamContext(IList<string> archiveFiles, string directory, IDictionary<string, string> files)
14 : base(archiveFiles, directory, files)
15 {
16 }
17
18 public delegate object PackOptionHandler(string optionName, object[] parameters);
19
20 public PackOptionHandler OptionHandler
21 {
22 get
23 {
24 return this.packOptionHandler;
25 }
26 set
27 {
28 this.packOptionHandler = value;
29 }
30 }
31
32 public override object GetOption(string optionName, object[] parameters)
33 {
34 if (this.OptionHandler == null)
35 {
36 return null;
37 }
38
39 return this.OptionHandler(optionName, parameters);
40 }
41 }
42}
diff --git a/src/dtf/WixToolsetTests.Dtf.Compression/WixToolsetTests.Dtf.Compression.csproj b/src/dtf/WixToolsetTests.Dtf.Compression/WixToolsetTests.Dtf.Compression.csproj
new file mode 100644
index 00000000..120779ee
--- /dev/null
+++ b/src/dtf/WixToolsetTests.Dtf.Compression/WixToolsetTests.Dtf.Compression.csproj
@@ -0,0 +1,31 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
5 <PropertyGroup>
6 <ProjectGuid>{F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}</ProjectGuid>
7 <OutputType>Library</OutputType>
8 <RootNamespace>WixToolsetTests.Dtf</RootNamespace>
9 <AssemblyName>WixToolsetTests.Dtf.Compression</AssemblyName>
10 <CreateDocumentation>false</CreateDocumentation>
11 </PropertyGroup>
12
13 <ItemGroup>
14 <Compile Include="CompressionTestUtil.cs" />
15 <Compile Include="MisbehavingStreamContext.cs" />
16 <Compile Include="OptionStreamContext.cs" />
17 </ItemGroup>
18
19 <ItemGroup>
20 <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
21 <Reference Include="System" />
22 <Reference Include="System.Data" />
23 <Reference Include="System.Xml" />
24 </ItemGroup>
25
26 <ItemGroup>
27 <ProjectReference Include="..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" />
28 </ItemGroup>
29
30 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
31</Project>
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/CustomActionTest.cs b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/CustomActionTest.cs
new file mode 100644
index 00000000..bf843024
--- /dev/null
+++ b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/CustomActionTest.cs
@@ -0,0 +1,206 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Test
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Collections.Generic;
9 using Microsoft.VisualStudio.TestTools.UnitTesting;
10 using WixToolset.Dtf.WindowsInstaller;
11
12 [TestClass]
13 public class CustomActionTest
14 {
15 public CustomActionTest()
16 {
17 }
18
19 [TestMethod]
20 [Ignore] // Currently fails.
21 public void CustomActionTest1()
22 {
23 InstallLogModes logEverything =
24 InstallLogModes.FatalExit |
25 InstallLogModes.Error |
26 InstallLogModes.Warning |
27 InstallLogModes.User |
28 InstallLogModes.Info |
29 InstallLogModes.ResolveSource |
30 InstallLogModes.OutOfDiskSpace |
31 InstallLogModes.ActionStart |
32 InstallLogModes.ActionData |
33 InstallLogModes.CommonData |
34 InstallLogModes.Progress |
35 InstallLogModes.Initialize |
36 InstallLogModes.Terminate |
37 InstallLogModes.ShowDialog;
38
39 Installer.SetInternalUI(InstallUIOptions.Silent);
40 ExternalUIHandler prevHandler = Installer.SetExternalUI(
41 WindowsInstallerTest.ExternalUILogger, logEverything);
42
43 try
44 {
45 string[] customActions = new string[] { "SampleCA1", "SampleCA2" };
46 #if DEBUG
47 string caDir = @"..\..\..\..\..\build\debug\x86\";
48 #else
49 string caDir = @"..\..\..\..\..\build\ship\x86\";
50 #endif
51 caDir = Path.GetFullPath(caDir);
52 string caFile = "WixToolset.Dtf.Samples.ManagedCA.dll";
53 string caProduct = "CustomActionTest.msi";
54
55 this.CreateCustomActionProduct(caProduct, caDir + caFile, customActions, false);
56
57 Exception caughtEx = null;
58 try
59 {
60 Installer.InstallProduct(caProduct, String.Empty);
61 }
62 catch (Exception ex) { caughtEx = ex; }
63 Assert.IsInstanceOfType(caughtEx, typeof(InstallCanceledException),
64 "Exception thrown while installing product: " + caughtEx);
65
66 string arch = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE");
67 string arch2 = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432");
68 if (arch == "AMD64" || arch2 == "AMD64")
69 {
70 caDir = caDir.Replace("x86", "x64");
71
72 this.CreateCustomActionProduct(caProduct, caDir + caFile, customActions, true);
73
74 caughtEx = null;
75 try
76 {
77 Installer.InstallProduct(caProduct, String.Empty);
78 }
79 catch (Exception ex) { caughtEx = ex; }
80 Assert.IsInstanceOfType(caughtEx, typeof(InstallCanceledException),
81 "Exception thrown while installing 64bit product: " + caughtEx);
82 }
83 }
84 finally
85 {
86 Installer.SetExternalUI(prevHandler, InstallLogModes.None);
87 }
88 }
89
90 private void CreateCustomActionProduct(
91 string msiFile, string customActionFile, IList<string> customActions, bool sixtyFourBit)
92 {
93 using (Database db = new Database(msiFile, DatabaseOpenMode.CreateDirect))
94 {
95 WindowsInstallerUtils.InitializeProductDatabase(db, sixtyFourBit);
96 WindowsInstallerUtils.CreateTestProduct(db);
97
98 if (!File.Exists(customActionFile))
99 throw new FileNotFoundException(customActionFile);
100
101 using (Record binRec = new Record(2))
102 {
103 binRec[1] = Path.GetFileName(customActionFile);
104 binRec.SetStream(2, customActionFile);
105
106 db.Execute("INSERT INTO `Binary` (`Name`, `Data`) VALUES (?, ?)", binRec);
107 }
108
109 using (Record binRec2 = new Record(2))
110 {
111 binRec2[1] = "TestData";
112 binRec2.SetStream(2, new MemoryStream(Encoding.UTF8.GetBytes("This is a test data stream.")));
113
114 db.Execute("INSERT INTO `Binary` (`Name`, `Data`) VALUES (?, ?)", binRec2);
115 }
116
117 for (int i = 0; i < customActions.Count; i++)
118 {
119 db.Execute(
120 "INSERT INTO `CustomAction` (`Action`, `Type`, `Source`, `Target`) VALUES ('{0}', 1, '{1}', '{2}')",
121 customActions[i],
122 Path.GetFileName(customActionFile),
123 customActions[i]);
124 db.Execute(
125 "INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES ('{0}', '', {1})",
126 customActions[i],
127 101 + i);
128 }
129
130 db.Execute("INSERT INTO `Property` (`Property`, `Value`) VALUES ('SampleCATest', 'TestValue')");
131
132 db.Commit();
133 }
134 }
135
136 [TestMethod]
137 public void CustomActionData()
138 {
139 string dataString = "Key1=Value1;Key2=;Key3;Key4=Value=4;Key5";
140 string dataString2 = "Key1=;Key2=Value2;Key3;Key4;Key6=Value;;6=6;Key7=Value7";
141
142 CustomActionData data = new CustomActionData(dataString);
143 Assert.AreEqual<string>(dataString, data.ToString());
144
145 data["Key1"] = String.Empty;
146 data["Key2"] = "Value2";
147 data["Key4"] = null;
148 data.Remove("Key5");
149 data["Key6"] = "Value;6=6";
150 data["Key7"] = "Value7";
151
152 Assert.AreEqual<string>(dataString2, data.ToString());
153
154 MyDataClass myData = new MyDataClass();
155 myData.Member1 = "test1";
156 myData.Member2 = "test2";
157 data.AddObject("MyData", myData);
158
159 string myDataString = data.ToString();
160 CustomActionData data2 = new CustomActionData(myDataString);
161
162 MyDataClass myData2 = data2.GetObject<MyDataClass>("MyData");
163 Assert.AreEqual<MyDataClass>(myData, myData2);
164
165 List<string> myComplexDataObject = new List<string>();
166 myComplexDataObject.Add("CValue1");
167 myComplexDataObject.Add("CValue2");
168 myComplexDataObject.Add("CValue3");
169
170 CustomActionData myComplexData = new CustomActionData();
171 myComplexData.AddObject("MyComplexData", myComplexDataObject);
172 myComplexData.AddObject("NestedData", data);
173 string myComplexDataString = myComplexData.ToString();
174
175 CustomActionData myComplexData2 = new CustomActionData(myComplexDataString);
176 List<string> myComplexDataObject2 = myComplexData2.GetObject<List<string>>("MyComplexData");
177
178 Assert.AreEqual<int>(myComplexDataObject.Count, myComplexDataObject2.Count);
179 for (int i = 0; i < myComplexDataObject.Count; i++)
180 {
181 Assert.AreEqual<string>(myComplexDataObject[i], myComplexDataObject2[i]);
182 }
183
184 data2 = myComplexData2.GetObject<CustomActionData>("NestedData");
185 Assert.AreEqual<string>(data.ToString(), data2.ToString());
186 }
187
188 public class MyDataClass
189 {
190 public string Member1;
191 public string Member2;
192
193 public override bool Equals(object obj)
194 {
195 MyDataClass other = obj as MyDataClass;
196 return other != null && this.Member1 == other.Member1 && this.Member2 == other.Member2;
197 }
198
199 public override int GetHashCode()
200 {
201 return (this.Member1 != null ? this.Member1.GetHashCode() : 0) ^
202 (this.Member2 != null ? this.Member2.GetHashCode() : 0);
203 }
204 }
205 }
206}
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj
new file mode 100644
index 00000000..aadd4592
--- /dev/null
+++ b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj
@@ -0,0 +1,28 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
6 <PropertyGroup>
7 <ProjectGuid>{137D376B-989F-4FEA-9A67-01D8D38CA0DE}</ProjectGuid>
8 <OutputType>Library</OutputType>
9 <RootNamespace>WixToolsetTests.Dtf</RootNamespace>
10 <AssemblyName>WixToolsetTests.Dtf.WindowsInstaller.CustomActions</AssemblyName>
11 <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
12 <CreateDocumentation>false</CreateDocumentation>
13 </PropertyGroup>
14
15 <ItemGroup>
16 <Compile Include="CustomActionTest.cs" />
17 </ItemGroup>
18
19 <ItemGroup>
20 <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
21 <Reference Include="System" />
22 <Reference Include="System.Windows.Forms" />
23 <Reference Include="$(OutputPath)\WixToolset.Dtf.WindowsInstaller.dll" />
24 <ProjectReference Include="..\WixToolsetTests.Dtf.WindowsInstaller\WixToolsetTests.Dtf.WindowsInstaller.csproj" />
25 </ItemGroup>
26
27 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
28</Project>
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/LinqTest.cs b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/LinqTest.cs
new file mode 100644
index 00000000..7776a1c3
--- /dev/null
+++ b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/LinqTest.cs
@@ -0,0 +1,509 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.Text;
5using System.Collections;
6using System.Collections.Generic;
7using System.Linq;
8using System.Linq.Expressions;
9using Microsoft.VisualStudio.TestTools.UnitTesting;
10using WixToolset.Dtf.WindowsInstaller;
11using WixToolset.Dtf.WindowsInstaller.Linq;
12
13namespace WixToolset.Dtf.Test
14{
15 [TestClass]
16 public class LinqTest
17 {
18 private void InitLinqTestDatabase(QDatabase db)
19 {
20 WindowsInstallerUtils.InitializeProductDatabase(db);
21 WindowsInstallerUtils.CreateTestProduct(db);
22
23 db.Execute(
24 "INSERT INTO `Feature` (`Feature`, `Title`, `Description`, `Level`, `Attributes`) VALUES ('{0}', '{1}', '{2}', {3}, {4})",
25 "TestFeature2",
26 "Test Feature 2",
27 "Test Feature 2 Description",
28 1,
29 (int) FeatureAttributes.None);
30
31 WindowsInstallerUtils.AddRegistryComponent(
32 db, "TestFeature2", "MyTestRegComp",
33 Guid.NewGuid().ToString("B"),
34 "SOFTWARE\\Microsoft\\DTF\\Test",
35 "MyTestRegComp", "test");
36 WindowsInstallerUtils.AddRegistryComponent(
37 db, "TestFeature2", "MyTestRegComp2",
38 Guid.NewGuid().ToString("B"),
39 "SOFTWARE\\Microsoft\\DTF\\Test",
40 "MyTestRegComp2", "test2");
41 WindowsInstallerUtils.AddRegistryComponent(
42 db, "TestFeature2", "excludeComp",
43 Guid.NewGuid().ToString("B"),
44 "SOFTWARE\\Microsoft\\DTF\\Test",
45 "MyTestRegComp3", "test3");
46
47 db.Commit();
48
49 db.Log = Console.Out;
50 }
51
52 [TestMethod]
53 public void LinqSimple()
54 {
55 using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
56 {
57 this.InitLinqTestDatabase(db);
58
59 var comps = from c in db.Components
60 select c;
61
62 int count = 0;
63 foreach (var c in comps)
64 {
65 Console.WriteLine(c);
66 count++;
67 }
68
69 Assert.AreEqual<int>(4, count);
70 }
71 }
72
73 [TestMethod]
74 public void LinqWhereNull()
75 {
76 using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
77 {
78 this.InitLinqTestDatabase(db);
79
80 var features = from f in db.Features
81 where f.Description != null
82 select f;
83
84 int count = 0;
85 foreach (var f in features)
86 {
87 Console.WriteLine(f);
88 Assert.AreEqual<string>("TestFeature2", f.Feature);
89 count++;
90 }
91
92 Assert.AreEqual<int>(1, count);
93
94 var features2 = from f in db.Features
95 where f.Description == null
96 select f;
97
98 count = 0;
99 foreach (var f in features2)
100 {
101 Console.WriteLine(f);
102 Assert.AreEqual<string>("TestFeature1", f.Feature);
103 count++;
104 }
105
106 Assert.AreEqual<int>(1, count);
107 }
108 }
109
110 [TestMethod]
111 public void LinqWhereOperators()
112 {
113 using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
114 {
115 this.InitLinqTestDatabase(db);
116
117 for (int i = 0; i < 100; i++)
118 {
119 var newFile = db.Files.NewRecord();
120 newFile.File = "TestFile" + i;
121 newFile.Component_ = "TestComponent";
122 newFile.FileName = "TestFile" + i + ".txt";
123 newFile.FileSize = i % 10;
124 newFile.Sequence = i;
125 newFile.Insert();
126 }
127
128 var files1 = from f in db.Files where f.Sequence < 40 select f;
129 Assert.AreEqual<int>(40, files1.AsEnumerable().Count());
130
131 var files2 = from f in db.Files where f.Sequence <= 40 select f;
132 Assert.AreEqual<int>(41, files2.AsEnumerable().Count());
133
134 var files3 = from f in db.Files where f.Sequence > 40 select f;
135 Assert.AreEqual<int>(59, files3.AsEnumerable().Count());
136
137 var files4 = from f in db.Files where f.Sequence >= 40 select f;
138 Assert.AreEqual<int>(60, files4.AsEnumerable().Count());
139
140 var files5 = from f in db.Files where 40 < f.Sequence select f;
141 Assert.AreEqual<int>(59, files5.AsEnumerable().Count());
142
143 var files6 = from f in db.Files where 40 <= f.Sequence select f;
144 Assert.AreEqual<int>(60, files6.AsEnumerable().Count());
145
146 var files7 = from f in db.Files where 40 > f.Sequence select f;
147 Assert.AreEqual<int>(40, files7.AsEnumerable().Count());
148
149 var files8 = from f in db.Files where 40 >= f.Sequence select f;
150 Assert.AreEqual<int>(41, files8.AsEnumerable().Count());
151
152 var files9 = from f in db.Files where f.Sequence == 40 select f;
153 Assert.AreEqual<int>(40, files9.AsEnumerable().First().Sequence);
154
155 var files10 = from f in db.Files where f.Sequence != 40 select f;
156 Assert.AreEqual<int>(99, files10.AsEnumerable().Count());
157
158 var files11 = from f in db.Files where 40 == f.Sequence select f;
159 Assert.AreEqual<int>(40, files11.AsEnumerable().First().Sequence);
160
161 var files12 = from f in db.Files where 40 != f.Sequence select f;
162 Assert.AreEqual<int>(99, files12.AsEnumerable().Count());
163 }
164 }
165
166 [TestMethod]
167 public void LinqShapeSelect()
168 {
169 using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
170 {
171 this.InitLinqTestDatabase(db);
172
173 Console.WriteLine("Running LINQ query 1.");
174 var features1 = from f in db.Features
175 select new { Name = f.Feature,
176 Desc = f.Description };
177
178 int count = 0;
179 foreach (var f in features1)
180 {
181 Console.WriteLine(f);
182 count++;
183 }
184
185 Assert.AreEqual<int>(2, count);
186
187 Console.WriteLine();
188 Console.WriteLine("Running LINQ query 2.");
189 var features2 = from f in db.Features
190 where f.Description != null
191 select new { Name = f.Feature,
192 Desc = f.Description.ToLower() };
193
194 count = 0;
195 foreach (var f in features2)
196 {
197 Console.WriteLine(f);
198 Assert.AreEqual<string>("TestFeature2", f.Name);
199 count++;
200 }
201
202 Assert.AreEqual<int>(1, count);
203 }
204 }
205
206 [TestMethod]
207 public void LinqUpdateNullableString()
208 {
209 using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
210 {
211 this.InitLinqTestDatabase(db);
212
213 string newDescription = "New updated feature description.";
214
215 var features = from f in db.Features
216 where f.Description != null
217 select f;
218
219 int count = 0;
220 foreach (var f in features)
221 {
222 Console.WriteLine(f);
223 Assert.AreEqual<string>("TestFeature2", f.Feature);
224 f.Description = newDescription;
225 count++;
226 }
227
228 Assert.AreEqual<int>(1, count);
229
230 var features2 = from f in db.Features
231 where f.Description == newDescription
232 select f;
233 count = 0;
234 foreach (var f in features2)
235 {
236 Console.WriteLine(f);
237 Assert.AreEqual<string>("TestFeature2", f.Feature);
238 f.Description = null;
239 count++;
240 }
241
242 Assert.AreEqual<int>(1, count);
243
244 var features3 = from f in db.Features
245 where f.Description == null
246 select f.Feature;
247 count = 0;
248 foreach (var f in features3)
249 {
250 Console.WriteLine(f);
251 count++;
252 }
253
254 Assert.AreEqual<int>(2, count);
255
256 db.Commit();
257 }
258 }
259
260 [TestMethod]
261 public void LinqInsertDelete()
262 {
263 using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
264 {
265 this.InitLinqTestDatabase(db);
266
267 var newProp = db.Properties.NewRecord();
268 newProp.Property = "TestNewProp1";
269 newProp.Value = "TestNewValue";
270 newProp.Insert();
271
272 string prop = (from p in db.Properties
273 where p.Property == "TestNewProp1"
274 select p.Value).AsEnumerable().First();
275 Assert.AreEqual<string>("TestNewValue", prop);
276
277 newProp.Delete();
278
279 int propCount = (from p in db.Properties
280 where p.Property == "TestNewProp1"
281 select p.Value).AsEnumerable().Count();
282 Assert.AreEqual<int>(0, propCount);
283
284 db.Commit();
285 }
286 }
287
288 [TestMethod]
289 public void LinqQueryQRecord()
290 {
291 using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
292 {
293 this.InitLinqTestDatabase(db);
294
295 var installFilesSeq = (from a in db["InstallExecuteSequence"]
296 where a["Action"] == "InstallFiles"
297 select a["Sequence"]).AsEnumerable().First();
298 Assert.AreEqual<string>("4000", installFilesSeq);
299 }
300 }
301
302 [TestMethod]
303 public void LinqOrderBy()
304 {
305 using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
306 {
307 this.InitLinqTestDatabase(db);
308
309 var actions = from a in db.InstallExecuteSequences
310 orderby a.Sequence
311 select a.Action;
312 foreach (var a in actions)
313 {
314 Console.WriteLine(a);
315 }
316
317 var files = from f in db.Files
318 orderby f.FileSize, f["Sequence"]
319 where f.Attributes == FileAttributes.None
320 select f;
321
322 foreach (var f in files)
323 {
324 Console.WriteLine(f);
325 }
326 }
327 }
328
329 [TestMethod]
330 public void LinqTwoWayJoin()
331 {
332 using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
333 {
334 this.InitLinqTestDatabase(db);
335 int count;
336
337 var regs = from r in db.Registries
338 join c in db["Component"] on r.Component_ equals c["Component"]
339 where c["Component"] == "MyTestRegComp" &&
340 r.Root == RegistryRoot.UserOrMachine
341 select new { Reg = r.Registry, Dir = c["Directory_"] };
342
343 count = 0;
344 foreach (var r in regs)
345 {
346 Console.WriteLine(r);
347 count++;
348 }
349 Assert.AreEqual<int>(1, count);
350
351 var regs2 = from r in db.Registries
352 join c in db.Components on r.Component_ equals c.Component
353 where c.Component == "MyTestRegComp" &&
354 r.Root == RegistryRoot.UserOrMachine
355 select new { Reg = r, Dir = c.Directory_ };
356
357 count = 0;
358 foreach (var r in regs2)
359 {
360 Assert.IsNotNull(r.Reg.Registry);
361 Console.WriteLine(r);
362 count++;
363 }
364 Assert.AreEqual<int>(1, count);
365
366 var regs3 = from r in db.Registries
367 join c in db.Components on r.Component_ equals c.Component
368 where c.Component == "MyTestRegComp" &&
369 r.Root == RegistryRoot.UserOrMachine
370 select r;
371
372 count = 0;
373 foreach (var r in regs3)
374 {
375 Assert.IsNotNull(r.Registry);
376 Console.WriteLine(r);
377 count++;
378 }
379 Assert.AreEqual<int>(1, count);
380
381 }
382 }
383
384 [TestMethod]
385 public void LinqFourWayJoin()
386 {
387 using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
388 {
389 this.InitLinqTestDatabase(db);
390 int count;
391
392 IList<string> pretest = db.ExecuteStringQuery(
393 "SELECT `Feature`.`Feature` " +
394 "FROM `Feature`, `FeatureComponents`, `Component`, `Registry` " +
395 "WHERE `Feature`.`Feature` = `FeatureComponents`.`Feature_` " +
396 "AND `FeatureComponents`.`Component_` = `Component`.`Component` " +
397 "AND `Component`.`Component` = `Registry`.`Component_` " +
398 "AND (`Registry`.`Registry` = 'MyTestRegCompReg1')");
399 Assert.AreEqual<int>(1, pretest.Count);
400
401 var features = from f in db.Features
402 join fc in db.FeatureComponents on f.Feature equals fc.Feature_
403 join c in db.Components on fc.Component_ equals c.Component
404 join r in db.Registries on c.Component equals r.Component_
405 where r.Registry == "MyTestRegCompReg1"
406 select f.Feature;
407
408 count = 0;
409 foreach (var featureName in features)
410 {
411 Console.WriteLine(featureName);
412 count++;
413 }
414 Assert.AreEqual<int>(1, count);
415
416 }
417 }
418
419 [TestMethod]
420 public void EnumTable()
421 {
422 using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
423 {
424 this.InitLinqTestDatabase(db);
425 int count = 0;
426 foreach (var comp in db.Components)
427 {
428 Console.WriteLine(comp);
429 count++;
430 }
431 Assert.AreNotEqual<int>(0, count);
432 }
433 }
434
435 [TestMethod]
436 public void DatabaseAsQueryable()
437 {
438 using (Database db = new Database("testlinq.msi", DatabaseOpenMode.Create))
439 {
440 WindowsInstallerUtils.InitializeProductDatabase(db);
441 WindowsInstallerUtils.CreateTestProduct(db);
442
443 var comps = from c in db.AsQueryable().Components
444 select c;
445
446 int count = 0;
447 foreach (var c in comps)
448 {
449 Console.WriteLine(c);
450 count++;
451 }
452
453 Assert.AreEqual<int>(1, count);
454 }
455 }
456
457 [TestMethod]
458 public void EnumProducts()
459 {
460 var products = from p in ProductInstallation.AllProducts
461 where p.Publisher == ".NET Foundation"
462 select new { Name = p.ProductName,
463 Ver = p.ProductVersion,
464 Company = p.Publisher,
465 InstallDate = p.InstallDate,
466 PackageCode = p.AdvertisedPackageCode };
467
468 foreach (var p in products)
469 {
470 Console.WriteLine(p);
471 Assert.IsTrue(p.Company == ".NET Foundation");
472 }
473 }
474
475 [TestMethod]
476 public void EnumFeatures()
477 {
478 foreach (var p in ProductInstallation.AllProducts)
479 {
480 Console.WriteLine(p.ProductName);
481
482 foreach (var f in p.Features)
483 {
484 Console.WriteLine("\t" + f.FeatureName);
485 }
486 }
487 }
488
489 [TestMethod]
490 public void EnumComponents()
491 {
492 var comps = from c in ComponentInstallation.AllComponents
493 where c.State == InstallState.Local &&
494 c.Product.Publisher == ".NET Foundation"
495 select c.Path;
496
497 int count = 0;
498 foreach (var c in comps)
499 {
500 if (++count == 100) break;
501
502 Console.WriteLine(c);
503 }
504
505 Assert.AreEqual<int>(100, count);
506 }
507 }
508
509}
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj
new file mode 100644
index 00000000..0dc90860
--- /dev/null
+++ b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj
@@ -0,0 +1,31 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
5 <PropertyGroup>
6 <ProjectGuid>{4F55F9B8-D8B6-41EB-8796-221B4CD98324}</ProjectGuid>
7 <OutputType>Library</OutputType>
8 <RootNamespace>WixToolsetTests.Dtf</RootNamespace>
9 <AssemblyName>WixToolsetTests.Dtf.Linq</AssemblyName>
10 <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
11 <CreateDocumentation>false</CreateDocumentation>
12 </PropertyGroup>
13
14 <ItemGroup>
15 <Compile Include="LinqTest.cs" />
16 </ItemGroup>
17
18 <ItemGroup>
19 <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
20 <Reference Include="System" />
21 <Reference Include="System.Core" />
22 </ItemGroup>
23
24 <ItemGroup>
25 <ProjectReference Include="..\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj" />
26 <ProjectReference Include="..\WixToolset.Dtf.WindowsInstaller.Linq\WixToolset.Dtf.WindowsInstaller.Linq.csproj" />
27 <ProjectReference Include="..\WixToolsetTests.Dtf.WindowsInstaller\WixToolsetTests.Dtf.WindowsInstaller.csproj" />
28 </ItemGroup>
29
30 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
31</Project>
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs
new file mode 100644
index 00000000..b0fc00a8
--- /dev/null
+++ b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs
@@ -0,0 +1,173 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Test
4{
5 using System;
6 using System.IO;
7 using System.Reflection;
8 using System.Windows.Forms;
9 using System.Globalization;
10 using System.Collections.Generic;
11 using Microsoft.VisualStudio.TestTools.UnitTesting;
12 using WixToolset.Dtf.WindowsInstaller;
13 using View = WixToolset.Dtf.WindowsInstaller.View;
14
15 [TestClass]
16 public class EmbeddedExternalUI
17 {
18 const InstallLogModes TestLogModes =
19 InstallLogModes.FatalExit |
20 InstallLogModes.Error |
21 InstallLogModes.Warning |
22 InstallLogModes.User |
23 InstallLogModes.Info |
24 InstallLogModes.ResolveSource |
25 InstallLogModes.OutOfDiskSpace |
26 InstallLogModes.ActionStart |
27 InstallLogModes.ActionData |
28 InstallLogModes.CommonData;
29
30#if DEBUG
31 const string EmbeddedUISampleBinDir = @"..\..\build\debug\";
32#else
33 const string EmbeddedUISampleBinDir = @"..\..\build\release\";
34#endif
35
36 [TestMethod]
37 [Ignore] // Requires elevation.
38 public void EmbeddedUISingleInstall()
39 {
40 string dbFile = "EmbeddedUISingleInstall.msi";
41 string productCode;
42
43 string uiDir = Path.GetFullPath(EmbeddedExternalUI.EmbeddedUISampleBinDir);
44 string uiFile = "WixToolset.Dtf.Samples.EmbeddedUI.dll";
45
46 using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect))
47 {
48 WindowsInstallerUtils.InitializeProductDatabase(db);
49 WindowsInstallerUtils.CreateTestProduct(db);
50
51 productCode = db.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0];
52
53 using (Record uiRec = new Record(5))
54 {
55 uiRec[1] = "TestEmbeddedUI";
56 uiRec[2] = Path.GetFileNameWithoutExtension(uiFile) + ".Wrapper.dll";
57 uiRec[3] = 1;
58 uiRec[4] = (int) (
59 EmbeddedExternalUI.TestLogModes |
60 InstallLogModes.Progress |
61 InstallLogModes.Initialize |
62 InstallLogModes.Terminate |
63 InstallLogModes.ShowDialog);
64 uiRec.SetStream(5, Path.Combine(uiDir, uiFile));
65 db.Execute(db.Tables["MsiEmbeddedUI"].SqlInsertString, uiRec);
66 }
67
68 db.Commit();
69 }
70
71 Installer.SetInternalUI(InstallUIOptions.Full);
72
73 ProductInstallation installation = new ProductInstallation(productCode);
74 Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed before starting.");
75
76 Exception caughtEx = null;
77 try
78 {
79 Installer.EnableLog(EmbeddedExternalUI.TestLogModes, "install.log");
80 Installer.InstallProduct(dbFile, String.Empty);
81 }
82 catch (Exception ex) { caughtEx = ex; }
83 Assert.IsNull(caughtEx, "Exception thrown while installing product: " + caughtEx);
84
85 Assert.IsTrue(installation.IsInstalled, "Checking that product is installed.");
86 Console.WriteLine();
87 Console.WriteLine();
88 Console.WriteLine();
89 Console.WriteLine("===================================================================");
90 Console.WriteLine();
91 Console.WriteLine();
92 Console.WriteLine();
93
94 try
95 {
96 Installer.EnableLog(EmbeddedExternalUI.TestLogModes, "uninstall.log");
97 Installer.InstallProduct(dbFile, "REMOVE=All");
98 }
99 catch (Exception ex) { caughtEx = ex; }
100 Assert.IsNull(caughtEx, "Exception thrown while uninstalling product: " + caughtEx);
101 }
102
103 // This test does not pass if run normally.
104 // It only passes when a failure is injected into the EmbeddedUI launcher.
105 ////[TestMethod]
106 public void EmbeddedUIInitializeFails()
107 {
108 string dbFile = "EmbeddedUIInitializeFails.msi";
109 string productCode;
110
111 string uiDir = Path.GetFullPath(EmbeddedExternalUI.EmbeddedUISampleBinDir);
112 string uiFile = "WixToolset.Dtf.Samples.EmbeddedUI.dll";
113
114 // A number that will be used to check whether a type 19 CA runs.
115 const string magicNumber = "3.14159265358979323846264338327950";
116
117 using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect))
118 {
119 WindowsInstallerUtils.InitializeProductDatabase(db);
120 WindowsInstallerUtils.CreateTestProduct(db);
121
122 const string failureActionName = "EmbeddedUIInitializeFails";
123 db.Execute("INSERT INTO `CustomAction` (`Action`, `Type`, `Source`, `Target`) " +
124 "VALUES ('{0}', 19, '', 'Logging magic number: {1}')", failureActionName, magicNumber);
125
126 // This type 19 CA (launch condition) is given a condition of 'UILevel = 3' so that it only runs if the
127 // installation is running in BASIC UI mode, which is what we expect if the EmbeddedUI fails to initialize.
128 db.Execute("INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) " +
129 "VALUES ('{0}', 'UILevel = 3', 1)", failureActionName);
130
131 productCode = db.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0];
132
133 using (Record uiRec = new Record(5))
134 {
135 uiRec[1] = "TestEmbeddedUI";
136 uiRec[2] = Path.GetFileNameWithoutExtension(uiFile) + ".Wrapper.dll";
137 uiRec[3] = 1;
138 uiRec[4] = (int)(
139 EmbeddedExternalUI.TestLogModes |
140 InstallLogModes.Progress |
141 InstallLogModes.Initialize |
142 InstallLogModes.Terminate |
143 InstallLogModes.ShowDialog);
144 uiRec.SetStream(5, Path.Combine(uiDir, uiFile));
145 db.Execute(db.Tables["MsiEmbeddedUI"].SqlInsertString, uiRec);
146 }
147
148 db.Commit();
149 }
150
151 Installer.SetInternalUI(InstallUIOptions.Full);
152
153 ProductInstallation installation = new ProductInstallation(productCode);
154 Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed before starting.");
155
156 string logFile = "install.log";
157 Exception caughtEx = null;
158 try
159 {
160 Installer.EnableLog(EmbeddedExternalUI.TestLogModes, logFile);
161 Installer.InstallProduct(dbFile, String.Empty);
162 }
163 catch (Exception ex) { caughtEx = ex; }
164 Assert.IsInstanceOfType(caughtEx, typeof(InstallerException),
165 "Excpected InstallerException installing product; caught: " + caughtEx);
166
167 Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed.");
168
169 string logText = File.ReadAllText(logFile);
170 Assert.IsTrue(logText.Contains(magicNumber), "Checking that the type 19 custom action ran.");
171 }
172 }
173}
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs
new file mode 100644
index 00000000..26c172c9
--- /dev/null
+++ b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs
@@ -0,0 +1,238 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Test
4{
5 using System;
6 using System.IO;
7 using System.Collections.Generic;
8 using System.Text;
9 using WixToolset.Dtf.WindowsInstaller;
10
11 public static class Schema
12 {
13 public static IList<TableInfo> Tables
14 {
15 get
16 {
17 return new TableInfo[]
18 {
19 Binary,
20 Component,
21 CustomAction,
22 Directory,
23 EmbeddedUI,
24 Feature,
25 FeatureComponents,
26 File,
27 InstallExecuteSequence,
28 Media,
29 Property,
30 Registry
31 };
32 }
33 }
34
35 #region Table data
36
37 public static TableInfo Binary { get { return new TableInfo(
38 "Binary",
39 new ColumnInfo[]
40 {
41 new ColumnInfo("Name", typeof(String), 72, true),
42 new ColumnInfo("Data", typeof(Stream), 0, true),
43 },
44 new string[] { "Name" });
45 } }
46
47 public static TableInfo Component { get { return new TableInfo(
48 "Component",
49 new ColumnInfo[]
50 {
51 new ColumnInfo("Component", typeof(String), 72, true),
52 new ColumnInfo("ComponentId", typeof(String), 38, false),
53 new ColumnInfo("Directory_", typeof(String), 72, true),
54 new ColumnInfo("Attributes", typeof(Int16), 2, true),
55 new ColumnInfo("Condition", typeof(String), 255, false),
56 new ColumnInfo("KeyPath", typeof(String), 72, false),
57 },
58 new string[] { "Component" });
59 } }
60
61 public static TableInfo CustomAction { get { return new TableInfo(
62 "CustomAction",
63 new ColumnInfo[]
64 {
65 new ColumnInfo("Action", typeof(String), 72, true),
66 new ColumnInfo("Type", typeof(Int16), 2, true),
67 new ColumnInfo("Source", typeof(String), 64, false),
68 new ColumnInfo("Target", typeof(String), 255, false),
69 },
70 new string[] { "Action" });
71 } }
72
73 public static TableInfo Directory { get { return new TableInfo(
74 "Directory",
75 new ColumnInfo[]
76 {
77 new ColumnInfo("Directory", typeof(String), 72, true),
78 new ColumnInfo("Directory_Parent", typeof(String), 72, false),
79 new ColumnInfo("DefaultDir", typeof(String), 255, true, false, true),
80 },
81 new string[] { "Directory" });
82 } }
83
84 public static TableInfo EmbeddedUI { get { return new TableInfo(
85 "MsiEmbeddedUI",
86 new ColumnInfo[]
87 {
88 new ColumnInfo("MsiEmbeddedUI", typeof(String), 72, true),
89 new ColumnInfo("FileName", typeof(String), 72, true),
90 new ColumnInfo("Attributes", typeof(Int16), 2, true),
91 new ColumnInfo("MessageFilter", typeof(Int32), 4, false),
92 new ColumnInfo("Data", typeof(Stream), 0, true),
93 },
94 new string[] { "MsiEmbeddedUI" });
95 } }
96
97 public static TableInfo Feature { get { return new TableInfo(
98 "Feature",
99 new ColumnInfo[]
100 {
101 new ColumnInfo("Feature", typeof(String), 38, true),
102 new ColumnInfo("Feature_Parent", typeof(String), 38, false),
103 new ColumnInfo("Title", typeof(String), 64, false, false, true),
104 new ColumnInfo("Description", typeof(String), 64, false, false, true),
105 new ColumnInfo("Display", typeof(Int16), 2, false),
106 new ColumnInfo("Level", typeof(Int16), 2, true),
107 new ColumnInfo("Directory_", typeof(String), 72, false),
108 new ColumnInfo("Attributes", typeof(Int16), 2, true),
109 },
110 new string[] { "Feature" });
111 } }
112
113 public static TableInfo FeatureComponents { get { return new TableInfo(
114 "FeatureComponents",
115 new ColumnInfo[]
116 {
117 new ColumnInfo("Feature_", typeof(String), 38, true),
118 new ColumnInfo("Component_", typeof(String), 72, true),
119 },
120 new string[] { "Feature_", "Component_" });
121 } }
122
123 public static TableInfo File { get { return new TableInfo(
124 "File",
125 new ColumnInfo[]
126 {
127 new ColumnInfo("File", typeof(String), 72, true),
128 new ColumnInfo("Component_", typeof(String), 72, true),
129 new ColumnInfo("FileName", typeof(String), 255, true, false, true),
130 new ColumnInfo("FileSize", typeof(Int32), 4, true),
131 new ColumnInfo("Version", typeof(String), 72, false),
132 new ColumnInfo("Language", typeof(String), 20, false),
133 new ColumnInfo("Attributes", typeof(Int16), 2, false),
134 new ColumnInfo("Sequence", typeof(Int16), 2, true),
135 },
136 new string[] { "File" });
137 } }
138
139 public static TableInfo InstallExecuteSequence { get { return new TableInfo(
140 "InstallExecuteSequence",
141 new ColumnInfo[]
142 {
143 new ColumnInfo("Action", typeof(String), 72, true),
144 new ColumnInfo("Condition", typeof(String), 255, false),
145 new ColumnInfo("Sequence", typeof(Int16), 2, true),
146 },
147 new string[] { "Action" });
148 } }
149
150 public static TableInfo Media { get { return new TableInfo(
151 "Media",
152 new ColumnInfo[]
153 {
154 new ColumnInfo("DiskId", typeof(Int16), 2, true),
155 new ColumnInfo("LastSequence", typeof(Int16), 2, true),
156 new ColumnInfo("DiskPrompt", typeof(String), 64, false, false, true),
157 new ColumnInfo("Cabinet", typeof(String), 255, false),
158 new ColumnInfo("VolumeLabel", typeof(String), 32, false),
159 new ColumnInfo("Source", typeof(String), 32, false),
160 },
161 new string[] { "DiskId" });
162 } }
163
164 public static TableInfo Property { get { return new TableInfo(
165 "Property",
166 new ColumnInfo[]
167 {
168 new ColumnInfo("Property", typeof(String), 72, true),
169 new ColumnInfo("Value", typeof(String), 255, true),
170 },
171 new string[] { "Property" });
172 } }
173
174 public static TableInfo Registry { get { return new TableInfo(
175 "Registry",
176 new ColumnInfo[]
177 {
178 new ColumnInfo("Registry", typeof(String), 72, true),
179 new ColumnInfo("Root", typeof(Int16), 2, true),
180 new ColumnInfo("Key", typeof(String), 255, true, false, true),
181 new ColumnInfo("Name", typeof(String), 255, false, false, true),
182 new ColumnInfo("Value", typeof(String), 0, false, false, true),
183 new ColumnInfo("Component_", typeof(String), 72, true),
184 },
185 new string[] { "Registry" });
186 } }
187
188 #endregion
189
190 }
191
192 public class Action
193 {
194 public readonly string Name;
195 public readonly int Sequence;
196
197 public Action(string name, int sequence)
198 {
199 this.Name = name;
200 this.Sequence = sequence;
201 }
202
203 }
204
205 public class Sequence
206 {
207 public static IList<Action> InstallExecute
208 {
209 get
210 {
211 return new Action[]
212 {
213 new Action("CostInitialize", 800),
214 new Action("FileCost", 900),
215 new Action("CostFinalize", 1000),
216 new Action("InstallValidate", 1400),
217 new Action("InstallInitialize", 1500),
218 new Action("ProcessComponents", 1600),
219 new Action("UnpublishComponents", 1700),
220 new Action("UnpublishFeatures", 1800),
221 new Action("RemoveRegistryValues", 2600),
222 new Action("RemoveFiles", 3500),
223 new Action("RemoveFolders", 3600),
224 new Action("CreateFolders", 3700),
225 new Action("MoveFiles", 3800),
226 new Action("InstallFiles", 4000),
227 new Action("WriteRegistryValues", 5000),
228 new Action("RegisterProduct", 6100),
229 new Action("PublishComponents", 6200),
230 new Action("PublishFeatures", 6300),
231 new Action("PublishProduct", 6400),
232 new Action("InstallFinalize", 6600),
233 };
234 }
235 }
236
237 }
238}
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs
new file mode 100644
index 00000000..f994dfef
--- /dev/null
+++ b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs
@@ -0,0 +1,409 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Test
4{
5 using System;
6 using System.IO;
7 using System.Windows.Forms;
8 using System.Globalization;
9 using System.Collections.Generic;
10 using Microsoft.VisualStudio.TestTools.UnitTesting;
11 using WixToolset.Dtf.WindowsInstaller;
12 using View = WixToolset.Dtf.WindowsInstaller.View;
13
14 [TestClass]
15 public class WindowsInstallerTest
16 {
17 public WindowsInstallerTest()
18 {
19 }
20
21 [TestInitialize()]
22 public void Initialize()
23 {
24 }
25
26 [TestCleanup()]
27 public void Cleanup()
28 {
29 }
30
31 [TestMethod]
32 [Ignore] // Currently fails.
33 public void InstallerErrorMessages()
34 {
35 string msg3002 = Installer.GetErrorMessage(3002);
36 Console.WriteLine("3002=" + msg3002);
37 Assert.IsNotNull(msg3002);
38 Assert.IsTrue(msg3002.Length > 0);
39 }
40
41 [TestMethod]
42 public void InstallerDatabaseSchema()
43 {
44 string dbFile = "InstallerDatabaseSchema.msi";
45
46 using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect))
47 {
48 WindowsInstallerUtils.InitializeProductDatabase(db);
49 db.Commit();
50 }
51
52 Assert.IsTrue(File.Exists(dbFile), "Checking whether created database file " + dbFile + " exists.");
53
54 using (Database db = new Database(dbFile, DatabaseOpenMode.ReadOnly))
55 {
56 TableCollection tables = db.Tables;
57 Assert.AreEqual<int>(Schema.Tables.Count, tables.Count, "Counting tables.");
58 Assert.AreEqual<int>(Schema.Property.Columns.Count, tables["Property"].Columns.Count, "Counting columns in Property table.");
59
60 foreach (TableInfo tableInfo in tables)
61 {
62 Console.WriteLine(tableInfo.Name);
63 foreach (ColumnInfo columnInfo in tableInfo.Columns)
64 {
65 Console.WriteLine("\t{0} {1}", columnInfo.Name, columnInfo.ColumnDefinitionString);
66 }
67 }
68 }
69 }
70
71 [TestMethod]
72 public void InstallerViewTables()
73 {
74 string dbFile = "InstallerViewTables.msi";
75
76 using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect))
77 {
78 WindowsInstallerUtils.InitializeProductDatabase(db);
79 db.Commit();
80
81 using (View view1 = db.OpenView("SELECT `Property`, `Value` FROM `Property` WHERE `Value` IS NOT NULL"))
82 {
83 IList<TableInfo> viewTables = view1.Tables;
84 Assert.IsNotNull(viewTables);
85 Assert.AreEqual<int>(1, viewTables.Count);
86 Assert.AreEqual<String>("Property", viewTables[0].Name);
87 }
88
89 using (View view2 = db.OpenView("INSERT INTO `Property` (`Property`, `Value`) VALUES ('TestViewTables', 1)"))
90 {
91 IList<TableInfo> viewTables = view2.Tables;
92 Assert.IsNotNull(viewTables);
93 Assert.AreEqual<int>(1, viewTables.Count);
94 Assert.AreEqual<String>("Property", viewTables[0].Name);
95 }
96
97 using (View view3 = db.OpenView("UPDATE `Property` SET `Value` = 2 WHERE `Property` = 'TestViewTables'"))
98 {
99 IList<TableInfo> viewTables = view3.Tables;
100 Assert.IsNotNull(viewTables);
101 Assert.AreEqual<int>(1, viewTables.Count);
102 Assert.AreEqual<String>("Property", viewTables[0].Name);
103 }
104
105 using (View view4 = db.OpenView("alter table Property hold"))
106 {
107 IList<TableInfo> viewTables = view4.Tables;
108 Assert.IsNotNull(viewTables);
109 Assert.AreEqual<int>(1, viewTables.Count);
110 Assert.AreEqual<String>("Property", viewTables[0].Name);
111 }
112 }
113 }
114
115 [TestMethod]
116 public void InstallerInstallProduct()
117 {
118 string dbFile = "InstallerInstallProduct.msi";
119 string productCode;
120
121 using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect))
122 {
123 WindowsInstallerUtils.InitializeProductDatabase(db);
124 WindowsInstallerUtils.CreateTestProduct(db);
125
126 productCode = db.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0];
127
128 db.Commit();
129 }
130
131 ProductInstallation installation = new ProductInstallation(productCode);
132 Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed before starting.");
133
134 Installer.SetInternalUI(InstallUIOptions.Silent);
135 ExternalUIHandler prevHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUILogger,
136 InstallLogModes.FatalExit |
137 InstallLogModes.Error |
138 InstallLogModes.Warning |
139 InstallLogModes.User |
140 InstallLogModes.Info |
141 InstallLogModes.ResolveSource |
142 InstallLogModes.OutOfDiskSpace |
143 InstallLogModes.ActionStart |
144 InstallLogModes.ActionData |
145 InstallLogModes.CommonData |
146 InstallLogModes.Progress |
147 InstallLogModes.Initialize |
148 InstallLogModes.Terminate |
149 InstallLogModes.ShowDialog);
150 Assert.IsNull(prevHandler, "Checking that returned previous UI handler is null.");
151
152 Exception caughtEx = null;
153 try
154 {
155 Installer.InstallProduct(dbFile, String.Empty);
156 }
157 catch (Exception ex) { caughtEx = ex; }
158 Assert.IsNull(caughtEx, "Exception thrown while installing product: " + caughtEx);
159
160 prevHandler = Installer.SetExternalUI(prevHandler, InstallLogModes.None);
161 Assert.AreEqual<ExternalUIHandler>(WindowsInstallerTest.ExternalUILogger, prevHandler, "Checking that previously-set UI handler is returned.");
162
163 Assert.IsTrue(installation.IsInstalled, "Checking that product is installed.");
164 Console.WriteLine();
165 Console.WriteLine();
166 Console.WriteLine();
167 Console.WriteLine("===================================================================");
168 Console.WriteLine();
169 Console.WriteLine();
170 Console.WriteLine();
171
172 ExternalUIRecordHandler prevRecHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUIRecordLogger,
173 InstallLogModes.FatalExit |
174 InstallLogModes.Error |
175 InstallLogModes.Warning |
176 InstallLogModes.User |
177 InstallLogModes.Info |
178 InstallLogModes.ResolveSource |
179 InstallLogModes.OutOfDiskSpace |
180 InstallLogModes.ActionStart |
181 InstallLogModes.ActionData |
182 InstallLogModes.CommonData |
183 InstallLogModes.Progress |
184 InstallLogModes.Initialize |
185 InstallLogModes.Terminate |
186 InstallLogModes.ShowDialog);
187 Assert.IsNull(prevRecHandler, "Checking that returned previous UI record handler is null.");
188
189 try
190 {
191 Installer.InstallProduct(dbFile, "REMOVE=All");
192 }
193 catch (Exception ex) { caughtEx = ex; }
194 Assert.IsNull(caughtEx, "Exception thrown while installing product: " + caughtEx);
195
196 Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed after removing.");
197
198 prevRecHandler = Installer.SetExternalUI(prevRecHandler, InstallLogModes.None);
199 Assert.AreEqual<ExternalUIRecordHandler>(WindowsInstallerTest.ExternalUIRecordLogger, prevRecHandler, "Checking that previously-set UI record handler is returned.");
200 }
201
202 public static MessageResult ExternalUILogger(
203 InstallMessage messageType,
204 string message,
205 MessageButtons buttons,
206 MessageIcon icon,
207 MessageDefaultButton defaultButton)
208 {
209 Console.WriteLine("{0}: {1}", messageType, message);
210 return MessageResult.None;
211 }
212
213 public static MessageResult ExternalUIRecordLogger(
214 InstallMessage messageType,
215 Record messageRecord,
216 MessageButtons buttons,
217 MessageIcon icon,
218 MessageDefaultButton defaultButton)
219 {
220 if (messageRecord != null)
221 {
222 if (messageRecord.FormatString.Length == 0 && messageRecord.FieldCount > 0)
223 {
224 messageRecord.FormatString = "1: [1] 2: [2] 3: [3] 4: [4] 5: [5]";
225 }
226 Console.WriteLine("{0}: {1}", messageType, messageRecord.ToString());
227 }
228 else
229 {
230 Console.WriteLine("{0}: (null)", messageType);
231 }
232 return MessageResult.None;
233 }
234
235 [TestMethod]
236 [Ignore] // Currently fails.
237 public void InstallerMessageResources()
238 {
239 string message1101 = Installer.GetErrorMessage(1101);
240 Console.WriteLine("Message 1101: " + message1101);
241 Assert.IsNotNull(message1101);
242 Assert.IsTrue(message1101.Contains("file"));
243
244 message1101 = Installer.GetErrorMessage(1101, CultureInfo.GetCultureInfo(1033));
245 Console.WriteLine("Message 1101: " + message1101);
246 Assert.IsNotNull(message1101);
247 Assert.IsTrue(message1101.Contains("file"));
248
249 string message2621 = Installer.GetErrorMessage(2621);
250 Console.WriteLine("Message 2621: " + message2621);
251 Assert.IsNotNull(message2621);
252 Assert.IsTrue(message2621.Contains("DLL"));
253
254 string message3002 = Installer.GetErrorMessage(3002);
255 Console.WriteLine("Message 3002: " + message3002);
256 Assert.IsNotNull(message3002);
257 Assert.IsTrue(message3002.Contains("sequencing"));
258 }
259
260 [TestMethod]
261 public void EnumComponentQualifiers()
262 {
263 foreach (ComponentInstallation comp in ComponentInstallation.AllComponents)
264 {
265 bool qualifiers = false;
266 foreach (ComponentInstallation.Qualifier qualifier in comp.Qualifiers)
267 {
268 if (!qualifiers)
269 {
270 Console.WriteLine(comp.Path);
271 qualifiers = true;
272 }
273
274 Console.WriteLine("\t{0}: {1}", qualifier.QualifierCode, qualifier.Data);
275 }
276 }
277 }
278
279 [TestMethod]
280 public void DeleteRecord()
281 {
282 string dbFile = "DeleteRecord.msi";
283
284 using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect))
285 {
286 WindowsInstallerUtils.InitializeProductDatabase(db);
287 WindowsInstallerUtils.CreateTestProduct(db);
288
289 string query = "SELECT `Property`, `Value` FROM `Property` WHERE `Property` = 'UpgradeCode'";
290
291 using (View view = db.OpenView(query))
292 {
293 view.Execute();
294
295 Record rec = view.Fetch();
296
297 Console.WriteLine("Calling ToString() : " + rec);
298
299 view.Delete(rec);
300 }
301
302 Assert.AreEqual(0, db.ExecuteStringQuery(query).Count);
303 }
304 }
305
306 [TestMethod]
307 public void InsertRecordThenTryFormatString()
308 {
309 string dbFile = "InsertRecordThenTryFormatString.msi";
310
311 using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect))
312 {
313 WindowsInstallerUtils.InitializeProductDatabase(db);
314 WindowsInstallerUtils.CreateTestProduct(db);
315
316 string parameterFormatString = "[1]";
317 string[] properties = new string[]
318 {
319 "SonGoku", "Over 9000",
320 };
321
322 string query = "SELECT `Property`, `Value` FROM `Property`";
323
324 using (View view = db.OpenView(query))
325 {
326 using (Record rec = new Record(2))
327 {
328 rec[1] = properties[0];
329 rec[2] = properties[1];
330 rec.FormatString = parameterFormatString;
331 Console.WriteLine("Format String before inserting: " + rec.FormatString);
332 view.Insert(rec);
333
334 Console.WriteLine("Format String after inserting: " + rec.FormatString);
335 // After inserting, the format string is invalid.
336 Assert.AreEqual(String.Empty, rec.ToString());
337
338 // Setting the format string manually makes it valid again.
339 rec.FormatString = parameterFormatString;
340 Assert.AreEqual(properties[0], rec.ToString());
341 }
342 }
343 }
344 }
345
346 [TestMethod]
347 public void SeekRecordThenTryFormatString()
348 {
349 string dbFile = "SeekRecordThenTryFormatString.msi";
350
351 using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect))
352 {
353 WindowsInstallerUtils.InitializeProductDatabase(db);
354 WindowsInstallerUtils.CreateTestProduct(db);
355
356 string parameterFormatString = "[1]";
357 string[] properties = new string[]
358 {
359 "SonGoku", "Over 9000",
360 };
361
362 string query = "SELECT `Property`, `Value` FROM `Property`";
363
364 using (View view = db.OpenView(query))
365 {
366 using (Record rec = new Record(2))
367 {
368 rec[1] = properties[0];
369 rec[2] = properties[1];
370 rec.FormatString = parameterFormatString;
371 Console.WriteLine("Record fields before seeking: " + rec[0] + " " + rec[1] + " " + rec[2]);
372 view.Seek(rec);
373
374 //TODO: Why does view.Seek remove the record fields?
375 Console.WriteLine("Record fields after seeking: " + rec[0] + " " + rec[1] + " " + rec[2]);
376 // After inserting, the format string is invalid.
377 Assert.AreEqual(String.Empty, rec.ToString());
378 }
379 }
380 }
381 }
382
383 [TestMethod]
384 public void TestToString()
385 {
386 string defaultString = "1: ";
387 string vegetaShout = "It's OVER 9000!!";
388 string gokuPowerLevel = "9001";
389 string nappaInquiry = "Vegeta, what's the Scouter say about his power level?";
390 string parameterFormatString = "[1]";
391
392 Record rec = new Record(1);
393 Assert.AreEqual(defaultString, rec.ToString(), "Testing default FormatString");
394
395 rec.FormatString = String.Empty;
396 Assert.AreEqual(defaultString, rec.ToString(), "Explicitly set the FormatString to the empty string.");
397
398 rec.FormatString = vegetaShout;
399 Assert.AreEqual(vegetaShout, rec.ToString(), "Testing text only (empty FormatString)");
400
401 rec.FormatString = gokuPowerLevel;
402 Assert.AreEqual(gokuPowerLevel, rec.ToString(), "Testing numbers only from a record that wasn't fetched.");
403
404 Record rec2 = new Record(nappaInquiry);
405 rec2.FormatString = parameterFormatString;
406 Assert.AreEqual(nappaInquiry, rec2.ToString(), "Testing text with a FormatString set.");
407 }
408 }
409}
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs
new file mode 100644
index 00000000..3bdf5acd
--- /dev/null
+++ b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs
@@ -0,0 +1,161 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Test
4{
5 using System;
6 using System.IO;
7 using System.Windows.Forms;
8 using System.Globalization;
9 using System.Collections.Generic;
10 using Microsoft.VisualStudio.TestTools.UnitTesting;
11 using WixToolset.Dtf.WindowsInstaller;
12 using View = WixToolset.Dtf.WindowsInstaller.View;
13
14 [TestClass]
15 public class WindowsInstallerTransactions
16 {
17 [TestInitialize()]
18 public void Initialize()
19 {
20 }
21
22 [TestCleanup()]
23 public void Cleanup()
24 {
25 }
26
27 [TestMethod]
28 [Ignore] // Requires elevation.
29 public void InstallerTransactTwoProducts()
30 {
31 string dbFile1 = "InstallerTransactProduct1.msi";
32 string dbFile2 = "InstallerTransactProduct2.msi";
33 string productCode1;
34 string productCode2;
35
36 using (Database db1 = new Database(dbFile1, DatabaseOpenMode.CreateDirect))
37 {
38 WindowsInstallerUtils.InitializeProductDatabase(db1);
39 WindowsInstallerUtils.CreateTestProduct(db1);
40
41 productCode1 = db1.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0];
42
43 db1.Commit();
44 }
45
46 using (Database db2 = new Database(dbFile2, DatabaseOpenMode.CreateDirect))
47 {
48 WindowsInstallerUtils.InitializeProductDatabase(db2);
49 WindowsInstallerUtils.CreateTestProduct(db2);
50
51 productCode2 = db2.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0];
52
53 db2.Commit();
54 }
55
56 ProductInstallation installation1 = new ProductInstallation(productCode1);
57 ProductInstallation installation2 = new ProductInstallation(productCode2);
58 Assert.IsFalse(installation1.IsInstalled, "Checking that product 1 is not installed before starting.");
59 Assert.IsFalse(installation2.IsInstalled, "Checking that product 2 is not installed before starting.");
60
61 Installer.SetInternalUI(InstallUIOptions.Silent);
62 ExternalUIHandler prevHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUILogger,
63 InstallLogModes.FatalExit |
64 InstallLogModes.Error |
65 InstallLogModes.Warning |
66 InstallLogModes.User |
67 InstallLogModes.Info |
68 InstallLogModes.ResolveSource |
69 InstallLogModes.OutOfDiskSpace |
70 InstallLogModes.ActionStart |
71 InstallLogModes.ActionData |
72 InstallLogModes.CommonData |
73 InstallLogModes.Progress |
74 InstallLogModes.Initialize |
75 InstallLogModes.Terminate |
76 InstallLogModes.ShowDialog);
77 Assert.IsNull(prevHandler, "Checking that returned previous UI handler is null.");
78
79 Transaction transaction = new Transaction("TestInstallTransaction", TransactionAttributes.None);
80
81 Exception caughtEx = null;
82 try
83 {
84 Installer.InstallProduct(dbFile1, String.Empty);
85 }
86 catch (Exception ex) { caughtEx = ex; }
87 Assert.IsNull(caughtEx, "Exception thrown while installing product 1: " + caughtEx);
88
89 Console.WriteLine();
90 Console.WriteLine("===================================================================");
91 Console.WriteLine();
92
93 try
94 {
95 Installer.InstallProduct(dbFile2, String.Empty);
96 }
97 catch (Exception ex) { caughtEx = ex; }
98 Assert.IsNull(caughtEx, "Exception thrown while installing product 2: " + caughtEx);
99
100 transaction.Commit();
101 transaction.Close();
102
103 prevHandler = Installer.SetExternalUI(prevHandler, InstallLogModes.None);
104 Assert.AreEqual<ExternalUIHandler>(WindowsInstallerTest.ExternalUILogger, prevHandler, "Checking that previously-set UI handler is returned.");
105
106 Assert.IsTrue(installation1.IsInstalled, "Checking that product 1 is installed.");
107 Assert.IsTrue(installation2.IsInstalled, "Checking that product 2 is installed.");
108
109 Console.WriteLine();
110 Console.WriteLine();
111 Console.WriteLine();
112 Console.WriteLine("===================================================================");
113 Console.WriteLine("===================================================================");
114 Console.WriteLine();
115 Console.WriteLine();
116 Console.WriteLine();
117
118 ExternalUIRecordHandler prevRecHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUIRecordLogger,
119 InstallLogModes.FatalExit |
120 InstallLogModes.Error |
121 InstallLogModes.Warning |
122 InstallLogModes.User |
123 InstallLogModes.Info |
124 InstallLogModes.ResolveSource |
125 InstallLogModes.OutOfDiskSpace |
126 InstallLogModes.ActionStart |
127 InstallLogModes.ActionData |
128 InstallLogModes.CommonData |
129 InstallLogModes.Progress |
130 InstallLogModes.Initialize |
131 InstallLogModes.Terminate |
132 InstallLogModes.ShowDialog);
133 Assert.IsNull(prevRecHandler, "Checking that returned previous UI record handler is null.");
134
135 transaction = new Transaction("TestUninstallTransaction", TransactionAttributes.None);
136
137 try
138 {
139 Installer.InstallProduct(dbFile1, "REMOVE=All");
140 }
141 catch (Exception ex) { caughtEx = ex; }
142 Assert.IsNull(caughtEx, "Exception thrown while removing product 1: " + caughtEx);
143
144 try
145 {
146 Installer.InstallProduct(dbFile2, "REMOVE=All");
147 }
148 catch (Exception ex) { caughtEx = ex; }
149 Assert.IsNull(caughtEx, "Exception thrown while removing product 2: " + caughtEx);
150
151 transaction.Commit();
152 transaction.Close();
153
154 Assert.IsFalse(installation1.IsInstalled, "Checking that product 1 is not installed after removing.");
155 Assert.IsFalse(installation2.IsInstalled, "Checking that product 2 is not installed after removing.");
156
157 prevRecHandler = Installer.SetExternalUI(prevRecHandler, InstallLogModes.None);
158 Assert.AreEqual<ExternalUIRecordHandler>(WindowsInstallerTest.ExternalUIRecordLogger, prevRecHandler, "Checking that previously-set UI record handler is returned.");
159 }
160 }
161}
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs
new file mode 100644
index 00000000..644f1988
--- /dev/null
+++ b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs
@@ -0,0 +1,174 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Test
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Text;
8 using WixToolset.Dtf.WindowsInstaller;
9
10 public class WindowsInstallerUtils
11 {
12 public static void InitializeProductDatabase(Database db)
13 {
14 InitializeProductDatabase(db, false);
15 }
16
17 public static void InitializeProductDatabase(Database db, bool sixtyFourBit)
18 {
19 db.SummaryInfo.CodePage = (short) Encoding.Default.CodePage;
20 db.SummaryInfo.Title = "Windows Installer Test";
21 db.SummaryInfo.Subject = db.SummaryInfo.Title;
22 db.SummaryInfo.Author = typeof(WindowsInstallerUtils).Assembly.FullName;
23 db.SummaryInfo.CreatingApp = db.SummaryInfo.Author;
24 db.SummaryInfo.Comments = typeof(WindowsInstallerUtils).FullName + ".CreateBasicDatabase()";
25 db.SummaryInfo.Keywords = "Installer,MSI,Database";
26 db.SummaryInfo.PageCount = 300;
27 db.SummaryInfo.WordCount = 0;
28 db.SummaryInfo.RevisionNumber = Guid.NewGuid().ToString("B").ToUpper();
29 db.SummaryInfo.Template = (sixtyFourBit ? "x64" : "Intel") + ";0";
30
31 foreach (TableInfo tableInfo in Schema.Tables)
32 {
33 db.Execute(tableInfo.SqlCreateString);
34 }
35
36 db.Execute("INSERT INTO `Directory` (`Directory`, `DefaultDir`) VALUES ('TARGETDIR', 'SourceDir')");
37 db.Execute("INSERT INTO `Directory` (`Directory`, `Directory_Parent`, `DefaultDir`) VALUES ('ProgramFilesFolder', 'TARGETDIR', '.')");
38
39 foreach (Action action in Sequence.InstallExecute)
40 {
41 db.Execute("INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) VALUES ('{0}', {1})",
42 action.Name, action.Sequence);
43 }
44 }
45
46 public const string UpgradeCode = "{05955FE8-005F-4695-A81F-D559338065BB}";
47
48 public static void CreateTestProduct(Database db)
49 {
50 Guid productGuid = Guid.NewGuid();
51
52 string[] properties = new string[]
53 {
54 "ProductCode", productGuid.ToString("B").ToUpper(),
55 "UpgradeCode", UpgradeCode,
56 "ProductName", "Windows Installer Test Product " + productGuid.ToString("P").ToUpper(),
57 "ProductVersion", "1.0.0.0000",
58 };
59
60 using (View view = db.OpenView("INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)"))
61 {
62 using (Record rec = new Record(2))
63 {
64 for (int i = 0; i < properties.Length; i += 2)
65 {
66 rec[1] = properties[i];
67 rec[2] = properties[i + 1];
68 view.Execute(rec);
69 }
70 }
71 }
72
73 int randomId = new Random().Next(10000);
74 string productDir = "TestDir" + randomId;
75 db.Execute(
76 "INSERT INTO `Directory` (`Directory`, `Directory_Parent`, `DefaultDir`) " +
77 "VALUES ('TestDir', 'ProgramFilesFolder', 'TestDir|{0}:.')", productDir);
78
79 string compId = Guid.NewGuid().ToString("B").ToUpper();
80 db.Execute(
81 "INSERT INTO `Component` " +
82 "(`Component`, `ComponentId`, `Directory_`, `Attributes`, `KeyPath`) " +
83 "VALUES ('{0}', '{1}', '{2}', {3}, '{4}')",
84 "TestRegComp1",
85 compId,
86 "TestDir",
87 (int) ComponentAttributes.RegistryKeyPath,
88 "TestReg1");
89
90 string productReg = "TestReg" + randomId;
91 db.Execute(
92 "INSERT INTO `Registry` (`Registry`, `Root`, `Key`, `Component_`) VALUES ('{0}', {1}, '{2}', '{3}')",
93 "TestReg1",
94 -1,
95 @"Software\Microsoft\Windows Installer Test\" + productReg,
96 "TestRegComp1");
97
98 db.Execute(
99 "INSERT INTO `Feature` (`Feature`, `Title`, `Level`, `Attributes`) VALUES ('{0}', '{1}', {2}, {3})",
100 "TestFeature1",
101 "Test Feature 1",
102 1,
103 (int) FeatureAttributes.None);
104
105 db.Execute(
106 "INSERT INTO `FeatureComponents` (`Feature_`, `Component_`) VALUES ('{0}', '{1}')",
107 "TestFeature1",
108 "TestRegComp1");
109 }
110
111 public static void AddFeature(Database db, string featureName)
112 {
113 db.Execute(
114 "INSERT INTO `Feature` (`Feature`, `Title`, `Level`, `Attributes`) VALUES ('{0}', '{1}', {2}, {3})",
115 featureName,
116 featureName,
117 1,
118 (int) FeatureAttributes.None);
119 }
120
121 public static void AddRegistryComponent(Database db,
122 string featureName, string compName, string compId,
123 string keyName, string keyValueName, string value)
124 {
125 db.Execute(
126 "INSERT INTO `Component` " +
127 "(`Component`, `ComponentId`, `Directory_`, `Attributes`, `KeyPath`) " +
128 "VALUES ('{0}', '{1}', '{2}', {3}, '{4}')",
129 compName,
130 compId,
131 "TestDir",
132 (int) ComponentAttributes.RegistryKeyPath,
133 compName + "Reg1");
134 db.Execute(
135 "INSERT INTO `Registry` (`Registry`, `Root`, `Key`, `Name`, `Value`, `Component_`) VALUES ('{0}', {1}, '{2}', '{3}', '{4}', '{5}')",
136 compName + "Reg1",
137 -1,
138 @"Software\Microsoft\Windows Installer Test\" + keyName,
139 keyValueName,
140 value,
141 compName);
142 db.Execute(
143 "INSERT INTO `FeatureComponents` (`Feature_`, `Component_`) VALUES ('{0}', '{1}')",
144 featureName,
145 compName);
146 }
147
148 public static void AddFileComponent(Database db,
149 string featureName, string compName, string compId,
150 string fileKey, string fileName)
151 {
152 db.Execute(
153 "INSERT INTO `Component` " +
154 "(`Component`, `ComponentId`, `Directory_`, `Attributes`, `KeyPath`) " +
155 "VALUES ('{0}', '{1}', '{2}', {3}, '{4}')",
156 compName,
157 compId,
158 "TestDir",
159 (int) ComponentAttributes.None,
160 fileKey);
161 db.Execute(
162 "INSERT INTO `File` " +
163 "(`File`, `Component_`, `FileName`, `FileSize`, `Attributes`, `Sequence`) " +
164 "VALUES ('{0}', '{1}', '{2}', 1, 0, 1)",
165 fileKey,
166 compName,
167 fileName);
168 db.Execute(
169 "INSERT INTO `FeatureComponents` (`Feature_`, `Component_`) VALUES ('{0}', '{1}')",
170 featureName,
171 compName);
172 }
173 }
174}
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj
new file mode 100644
index 00000000..dbddb682
--- /dev/null
+++ b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj
@@ -0,0 +1,34 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
5 <PropertyGroup>
6 <ProjectGuid>{16F5202F-9276-4166-975C-C9654BAF8012}</ProjectGuid>
7 <OutputType>Library</OutputType>
8 <RootNamespace>WixToolsetTests.Dtf</RootNamespace>
9 <AssemblyName>WixToolsetTests.Dtf.WindowsInstaller</AssemblyName>
10 <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
11 <CreateDocumentation>false</CreateDocumentation>
12 </PropertyGroup>
13
14 <ItemGroup>
15 <Compile Include="EmbeddedExternalUI.cs" />
16 <Compile Include="Schema.cs" />
17 <Compile Include="WindowsInstallerTest.cs" />
18 <Compile Include="WindowsInstallerTransactions.cs" />
19 <Compile Include="WindowsInstallerUtils.cs" />
20 </ItemGroup>
21
22 <ItemGroup>
23 <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
24 <Reference Include="System" />
25 <Reference Include="System.Windows.Forms" />
26 <Reference Include="System.Xml" />
27 </ItemGroup>
28
29 <ItemGroup>
30 <ProjectReference Include="..\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj" />
31 </ItemGroup>
32
33 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
34</Project>
diff --git a/src/dtf/appveyor.cmd b/src/dtf/appveyor.cmd
new file mode 100644
index 00000000..6dd50c8a
--- /dev/null
+++ b/src/dtf/appveyor.cmd
@@ -0,0 +1,28 @@
1@setlocal
2@pushd %~dp0
3@set _C=Release
4@set _P=%~dp0build\%_C%\publish
5
6nuget restore || exit /b
7
8msbuild -p:Configuration=%_C% || exit /b
9
10msbuild src\Tools\SfxCA\SfxCA.vcxproj -p:Configuration=%_C% -p:Platform="x64" -p:OutputPath="%_P%\WixToolset.Dtf.MSBuild\tools\\" || exit /b
11msbuild src\Tools\SfxCA\SfxCA.vcxproj -p:Configuration=%_C% -p:Platform="x86" -p:OutputPath="%_P%\WixToolset.Dtf.MSBuild\tools\\" || exit /b
12
13msbuild src\Tools\MakeSfxCA\MakeSfxCA.csproj -p:Configuration=%_C% -p:TargetFramework="net461" -p:OutputPath="%_P%\WixToolset.Dtf.MSBuild\tools\net461" || exit /b
14msbuild src\Tools\MakeSfxCA\MakeSfxCA.csproj -p:Configuration=%_C% -p:TargetFramework="netcoreapp3.1" -p:OutputPath="%_P%\WixToolset.Dtf.MSBuild\tools\netcoreapp3.1" || exit /b
15
16msbuild src\WixToolset.Dtf.MSBuild\WixToolset.Dtf.MSBuild.csproj -p:Configuration=%_C% -p:OutputPath="%_P%\WixToolset.Dtf.MSBuild" || exit /b
17msbuild src\WixToolset.Dtf.MSBuild\WixToolset.Dtf.MSBuild.csproj -target:Pack -p:Configuration=%_C% || exit /b
18
19msbuild -t:Pack -p:Configuration=%_C% src\WixToolset.Dtf.Compression || exit /b
20msbuild -t:Pack -p:Configuration=%_C% src\WixToolset.Dtf.Compression.Cab || exit /b
21msbuild -t:Pack -p:Configuration=%_C% src\WixToolset.Dtf.Compression.Zip || exit /b
22msbuild -t:Pack -p:Configuration=%_C% src\WixToolset.Dtf.Resources || exit /b
23msbuild -t:Pack -p:Configuration=%_C% src\WixToolset.Dtf.WindowsInstaller || exit /b
24msbuild -t:Pack -p:Configuration=%_C% src\WixToolset.Dtf.WindowsInstaller.Linq || exit /b
25msbuild -t:Pack -p:Configuration=%_C% src\WixToolset.Dtf.WindowsInstaller.Package || exit /b
26
27@popd
28@endlocal \ No newline at end of file
diff --git a/src/dtf/appveyor.yml b/src/dtf/appveyor.yml
new file mode 100644
index 00000000..522e5af3
--- /dev/null
+++ b/src/dtf/appveyor.yml
@@ -0,0 +1,42 @@
1# Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2#
3# Do NOT modify this file. Update the canonical version in Home\repo-template\src\appveyor.yml
4# then update all of the repos.
5
6branches:
7 only:
8 - master
9 - develop
10
11image: Visual Studio 2019
12
13version: 0.0.0.{build}
14configuration: Release
15
16environment:
17 DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
18 DOTNET_CLI_TELEMETRY_OPTOUT: 1
19 NUGET_XMLDOC_MODE: skip
20
21build_script:
22 - appveyor.cmd
23
24pull_requests:
25 do_not_increment_build_number: true
26
27nuget:
28 disable_publish_on_pr: true
29
30skip_branch_with_pr: true
31skip_tags: true
32
33artifacts:
34- path: build\Release\**\*.nupkg
35 name: nuget
36- path: build\Release\**\*.msi
37 name: msi
38
39notifications:
40- provider: Slack
41 incoming_webhook:
42 secure: p5xuu+4x2JHfwGDMDe5KcG1k7gZxqYc4jWVwvyNZv5cvkubPD2waJs5yXMAXZNN7Z63/3PWHb7q4KoY/99AjauYa1nZ4c5qYqRPFRBKTHfA=
diff --git a/src/dtf/nuget.config b/src/dtf/nuget.config
new file mode 100644
index 00000000..6e1ad9b5
--- /dev/null
+++ b/src/dtf/nuget.config
@@ -0,0 +1,7 @@
1<?xml version="1.0" encoding="utf-8"?>
2<configuration>
3 <packageSources>
4 <clear />
5 <add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
6 </packageSources>
7</configuration> \ No newline at end of file
diff --git a/src/internal/CSharp.Build.props b/src/internal/CSharp.Build.props
new file mode 100644
index 00000000..81d24ad1
--- /dev/null
+++ b/src/internal/CSharp.Build.props
@@ -0,0 +1,13 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<!--
3 Do NOT modify this file. Update the canonical version in Home\repo-template\src\CSharp.Build.props
4 then update all of the repos.
5-->
6<Project>
7 <PropertyGroup>
8 <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
9 <SignAssembly>true</SignAssembly>
10 <AssemblyOriginatorKeyFile>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)wix.snk))</AssemblyOriginatorKeyFile>
11 <NBGV_EmitThisAssemblyClass>false</NBGV_EmitThisAssemblyClass>
12 </PropertyGroup>
13</Project>
diff --git a/src/internal/Cpp.Build.props b/src/internal/Cpp.Build.props
new file mode 100644
index 00000000..44a042c7
--- /dev/null
+++ b/src/internal/Cpp.Build.props
@@ -0,0 +1,104 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project>
5 <PropertyGroup>
6 <Platform Condition=" '$(Platform)' == '' OR '$(Platform)' == 'AnyCPU' ">Win32</Platform>
7 <IntDir>$(BaseIntermediateOutputPath)$(Configuration)\$(Platform)\</IntDir>
8 <OutDir>$(OutputPath)$(Platform)\</OutDir>
9 </PropertyGroup>
10
11 <PropertyGroup Condition="'$(WindowsTargetPlatformVersion)'=='' AND '$(VisualStudioVersion)'>='15.0'">
12 <WindowsTargetPlatformVersion>$([Microsoft.Build.Utilities.ToolLocationHelper]::GetLatestSDKTargetPlatformVersion('Windows', '10.0'))</WindowsTargetPlatformVersion>
13 </PropertyGroup>
14
15 <ItemDefinitionGroup>
16 <ClCompile>
17 <DisableSpecificWarnings>$(DisableSpecificCompilerWarnings)</DisableSpecificWarnings>
18 <WarningLevel>Level4</WarningLevel>
19 <AdditionalIncludeDirectories>$(ProjectDir)inc;$(MSBuildProjectDirectory);$(IntDir);$(SqlCESdkIncludePath);$(ProjectAdditionalIncludeDirectories);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
20 <PreprocessorDefinitions>WIN32;_WINDOWS;_WIN32_MSI=500;_WIN32_WINNT=0x0501;$(ArmPreprocessorDefinitions);$(UnicodePreprocessorDefinitions);_CRT_STDIO_LEGACY_WIDE_SPECIFIERS;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
21 <PrecompiledHeader>Use</PrecompiledHeader>
22 <PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
23 <CallingConvention Condition="'$(Platform)'=='Win32'">StdCall</CallingConvention>
24 <TreatWarningAsError>true</TreatWarningAsError>
25 <ExceptionHandling>false</ExceptionHandling>
26 <AdditionalOptions>-YlprecompDefine</AdditionalOptions>
27 <AdditionalOptions Condition=" $(PlatformToolset.StartsWith('v14')) ">/Zc:threadSafeInit- %(AdditionalOptions)</AdditionalOptions>
28 <MultiProcessorCompilation Condition=" $(NUMBER_OF_PROCESSORS) &gt; 4 ">true</MultiProcessorCompilation>
29 </ClCompile>
30 <ResourceCompile>
31 <PreprocessorDefinitions>$(ArmPreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
32 <AdditionalIncludeDirectories>$(ProjectAdditionalResourceIncludeDirectories);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
33 </ResourceCompile>
34 <Lib>
35 <AdditionalLibraryDirectories>$(OutDir);$(AdditionalMultiTargetLibraryPath);$(ProjectAdditionalLibraryDirectories);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
36 </Lib>
37 <Link>
38 <SubSystem>$(ProjectSubSystem)</SubSystem>
39 <ModuleDefinitionFile>$(ProjectModuleDefinitionFile)</ModuleDefinitionFile>
40 <NoEntryPoint>$(ResourceOnlyDll)</NoEntryPoint>
41 <GenerateDebugInformation>true</GenerateDebugInformation>
42 <AdditionalDependencies>$(ProjectAdditionalLinkLibraries);advapi32.lib;comdlg32.lib;user32.lib;oleaut32.lib;gdi32.lib;shell32.lib;ole32.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
43 <AdditionalLibraryDirectories>$(OutDir);$(AdditionalMultiTargetLibraryPath);$(ArmLibraryDirectories);$(ProjectAdditionalLinkLibraryDirectories);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
44 <AdditionalOptions Condition=" $(PlatformToolset.StartsWith('v14')) ">/IGNORE:4099 %(AdditionalOptions)</AdditionalOptions>
45 </Link>
46 </ItemDefinitionGroup>
47
48 <ItemDefinitionGroup Condition=" '$(Platform)'=='Win32' and '$(PlatformToolset)'!='v100'">
49 <ClCompile>
50 <EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
51 </ClCompile>
52 </ItemDefinitionGroup>
53 <ItemDefinitionGroup Condition=" '$(Platform)'=='arm' ">
54 <ClCompile>
55 <CallingConvention>CDecl</CallingConvention>
56 </ClCompile>
57 </ItemDefinitionGroup>
58 <ItemDefinitionGroup Condition=" '$(ConfigurationType)'=='StaticLibrary' ">
59 <ClCompile>
60 <DebugInformationFormat>OldStyle</DebugInformationFormat>
61 <OmitDefaultLibName>true</OmitDefaultLibName>
62 <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
63 </ClCompile>
64 </ItemDefinitionGroup>
65 <ItemDefinitionGroup Condition=" '$(Configuration)'=='Debug' ">
66 <ClCompile>
67 <Optimization>Disabled</Optimization>
68 <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
69 <PreprocessorDefinitions>_DEBUG;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
70 <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
71 </ClCompile>
72 </ItemDefinitionGroup>
73 <ItemDefinitionGroup Condition=" '$(Configuration)'=='Debug' and '$(CLRSupport)'=='true' ">
74 <ClCompile>
75 <BasicRuntimeChecks></BasicRuntimeChecks>
76 <RuntimeLibrary>MultiThreadedDebugDll</RuntimeLibrary>
77 </ClCompile>
78 </ItemDefinitionGroup>
79 <ItemDefinitionGroup Condition=" '$(Configuration)'=='Release' ">
80 <ClCompile>
81 <Optimization>MinSpace</Optimization>
82 <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
83 <FunctionLevelLinking>true</FunctionLevelLinking>
84 <IntrinsicFunctions>true</IntrinsicFunctions>
85 <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
86 </ClCompile>
87 <Link>
88 <EnableCOMDATFolding>true</EnableCOMDATFolding>
89 <OptimizeReferences>true</OptimizeReferences>
90 </Link>
91 </ItemDefinitionGroup>
92 <ItemDefinitionGroup Condition=" '$(Configuration)'=='Release' and '$(CLRSupport)'=='true' ">
93 <ClCompile>
94 <BasicRuntimeChecks></BasicRuntimeChecks>
95 <RuntimeLibrary>MultiThreadedDll</RuntimeLibrary>
96 </ClCompile>
97 </ItemDefinitionGroup>
98 <ItemDefinitionGroup Condition=" '$(CLRSupport)'=='true' ">
99 <Link>
100 <KeyFile>$(LinkKeyFile)</KeyFile>
101 <DelaySign>$(LinkDelaySign)</DelaySign>
102 </Link>
103 </ItemDefinitionGroup>
104</Project>
diff --git a/src/internal/Directory.Build.props b/src/internal/Directory.Build.props
new file mode 100644
index 00000000..f83cc154
--- /dev/null
+++ b/src/internal/Directory.Build.props
@@ -0,0 +1,29 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<!--
4 Do NOT modify this file. Update the canonical version in Home\repo-template\src\Directory.Build.props
5 then update all of the repos.
6-->
7<Project>
8 <PropertyGroup>
9 <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
10 <EnableSourceLink Condition=" '$(NCrunch)' == '1' ">false</EnableSourceLink>
11 <MSBuildWarningsAsMessages>MSB3246</MSBuildWarningsAsMessages>
12
13 <ProjectName Condition=" '$(ProjectName)' == '' ">$(MSBuildProjectName)</ProjectName>
14 <BaseOutputPath>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)..\build\))</BaseOutputPath>
15 <BaseIntermediateOutputPath>$(BaseOutputPath)obj\$(ProjectName)\</BaseIntermediateOutputPath>
16 <OutputPath>$(BaseOutputPath)$(Configuration)\</OutputPath>
17
18 <Authors>WiX Toolset Team</Authors>
19 <Company>WiX Toolset</Company>
20 <Copyright>Copyright (c) .NET Foundation and contributors. All rights reserved.</Copyright>
21 <PackageLicenseExpression>MS-RL</PackageLicenseExpression>
22 <Product>WiX Toolset</Product>
23 </PropertyGroup>
24
25 <Import Project="CSharp.Build.props" Condition=" '$(MSBuildProjectExtension)'=='.csproj' and Exists('CSharp.Build.props') " />
26 <Import Project="Cpp.Build.props" Condition=" Exists('Cpp.Build.props') And '$(MSBuildProjectExtension)'=='.vcxproj' " />
27 <Import Project="Wix.Build.props" Condition=" Exists('Wix.Build.props') And '$(MSBuildProjectExtension)'=='.wixproj' " />
28 <Import Project="Custom.Build.props" Condition=" Exists('Custom.Build.props') " />
29</Project>
diff --git a/src/internal/Directory.Build.targets b/src/internal/Directory.Build.targets
new file mode 100644
index 00000000..cb988931
--- /dev/null
+++ b/src/internal/Directory.Build.targets
@@ -0,0 +1,56 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<!--
4 Do NOT modify this file. Update the canonical version in Home\repo-template\src\Directory.Build.targets
5 then update all of the repos.
6-->
7<!--
8 Replace PackageReferences with ProjectReferences when the projects can be found in .sln.
9 See the original here: https://github.com/dotnet/sdk/issues/1151#issuecomment-385133284
10-->
11<Project>
12 <PropertyGroup>
13 <CreateDocumentation Condition=" '$(CreateDocumentationFile)'!='true' ">false</CreateDocumentation>
14 <DocumentationFile Condition=" '$(CreateDocumentationFile)'=='true' ">$(OutputPath)\$(AssemblyName).xml</DocumentationFile>
15 </PropertyGroup>
16
17 <PropertyGroup>
18 <ReplacePackageReferences>true</ReplacePackageReferences>
19 <TheSolutionPath Condition=" '$(NCrunch)'=='' ">$(SolutionPath)</TheSolutionPath>
20 <TheSolutionPath Condition=" '$(NCrunch)'=='1' ">$(NCrunchOriginalSolutionPath)</TheSolutionPath>
21 </PropertyGroup>
22
23 <Choose>
24 <When Condition="$(ReplacePackageReferences) AND '$(TheSolutionPath)' != '' AND '$(TheSolutionPath)' != '*undefined*' AND Exists('$(TheSolutionPath)')">
25
26 <PropertyGroup>
27 <SolutionFileContent>$([System.IO.File]::ReadAllText($(TheSolutionPath)))</SolutionFileContent>
28 <SmartSolutionDir>$([System.IO.Path]::GetDirectoryName( $(TheSolutionPath) ))</SmartSolutionDir>
29 <RegexPattern>(?&lt;="[PackageName]", ")(.*)(?=", ")</RegexPattern>
30 </PropertyGroup>
31
32 <ItemGroup>
33 <!-- Keep the identity of the PackageReference -->
34 <SmartPackageReference Include="@(PackageReference)">
35 <PackageName>%(Identity)</PackageName>
36 <InSolution>$(SolutionFileContent.Contains('\%(Identity).csproj'))</InSolution>
37 </SmartPackageReference>
38
39 <!-- Filter them by mapping them to another ItemGroup using the WithMetadataValue item function -->
40 <PackageInSolution Include="@(SmartPackageReference->WithMetadataValue('InSolution', True))">
41 <Pattern>$(RegexPattern.Replace('[PackageName]','%(PackageName)') )</Pattern>
42 <SmartPath>$([System.Text.RegularExpressions.Regex]::Match('$(SolutionFileContent)', '%(Pattern)'))</SmartPath>
43 </PackageInSolution>
44
45 <ProjectReference Include="@(PackageInSolution->'$(SmartSolutionDir)\%(SmartPath)' )"/>
46
47 <!-- Remove the package references that are now referenced as projects -->
48 <PackageReference Remove="@(PackageInSolution->'%(PackageName)' )"/>
49 </ItemGroup>
50
51 </When>
52 </Choose>
53
54 <Import Project="Wix.Build.targets" Condition=" Exists('Wix.Build.targets') And '$(MSBuildProjectExtension)'=='.wixproj' " />
55 <Import Project="Custom.Build.targets" Condition=" Exists('Custom.Build.targets') " />
56</Project>
diff --git a/src/internal/MessagesToMessages/MessagesToMessages.csproj b/src/internal/MessagesToMessages/MessagesToMessages.csproj
new file mode 100644
index 00000000..ce1697ae
--- /dev/null
+++ b/src/internal/MessagesToMessages/MessagesToMessages.csproj
@@ -0,0 +1,8 @@
1<Project Sdk="Microsoft.NET.Sdk">
2
3 <PropertyGroup>
4 <OutputType>Exe</OutputType>
5 <TargetFramework>netcoreapp2.0</TargetFramework>
6 </PropertyGroup>
7
8</Project>
diff --git a/src/internal/MessagesToMessages/Program.cs b/src/internal/MessagesToMessages/Program.cs
new file mode 100644
index 00000000..2d5a3777
--- /dev/null
+++ b/src/internal/MessagesToMessages/Program.cs
@@ -0,0 +1,261 @@
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Linq;
5using System.Text;
6using System.Xml.Linq;
7
8namespace MessagesToMessages
9{
10 class Program
11 {
12 static readonly XNamespace ns = "http://schemas.microsoft.com/genmsgs/2004/07/messages";
13 static readonly XName ClassDefinition = ns + "Class";
14 static readonly XName MessageDefinition = ns + "Message";
15 static readonly XName InstanceDefinition = ns + "Instance";
16 static readonly XName ParameterDefinition = ns + "Parameter";
17 static readonly XName Id = "Id";
18 static readonly XName Level = "Level";
19 static readonly XName Number = "Number";
20 static readonly XName SourceLineNumbers = "SourceLineNumbers";
21 static readonly XName Type = "Type";
22 static readonly XName Name = "Name";
23
24 static void Main(string[] args)
25 {
26 if (args.Length == 0)
27 {
28 return;
29 }
30 else if (args.Length < 2)
31 {
32 Console.WriteLine("Need to specify output folder as well.");
33 }
34 else if (!Directory.Exists(args[1]))
35 {
36 Console.WriteLine("Output folder does not exist: {0}", args[1]);
37 }
38
39 var messages = ReadXml(Path.GetFullPath(args[0]));
40
41 foreach (var m in messages.GroupBy(m => m.Level))
42 {
43 var className = m.First().ClassName;
44 var result = GenerateCs(className, m.Key, m);
45
46 var path = Path.Combine(args[1], className + ".cs");
47 File.WriteAllText(path, result);
48 }
49 }
50
51 private static IEnumerable<Message> ReadXml(string inputPath)
52 {
53 var doc = XDocument.Load(inputPath);
54
55 foreach (var xClass in doc.Root.Descendants(ClassDefinition))
56 {
57 var name = xClass.Attribute(Name)?.Value;
58 var level = xClass.Attribute(Level)?.Value;
59
60 if (String.IsNullOrEmpty(name))
61 {
62 name = level + "Messages";
63 }
64 if (String.IsNullOrEmpty(level))
65 {
66 if (name.EndsWith("Errors", StringComparison.InvariantCultureIgnoreCase))
67 {
68 level = "Error";
69 }
70 else if (name.EndsWith("Verboses", StringComparison.InvariantCultureIgnoreCase))
71 {
72 level = "Verbose";
73 }
74 else if (name.EndsWith("Warnings", StringComparison.InvariantCultureIgnoreCase))
75 {
76 level = "Warning";
77 }
78 }
79
80 var unique = new HashSet<string>();
81 var lastNumber = 0;
82 foreach (var xMessage in xClass.Elements(MessageDefinition))
83 {
84 var id = xMessage.Attribute(Id).Value;
85
86 if (!unique.Add(id))
87 {
88 Console.WriteLine("Duplicated message: {0}", id);
89 }
90
91 if (!Int32.TryParse(xMessage.Attribute(Number)?.Value, out var number))
92 {
93 number = lastNumber;
94 }
95 lastNumber = number + 1;
96
97 var sln = xMessage.Attribute(SourceLineNumbers)?.Value != "no";
98
99 var suffix = 0;
100 foreach (var xInstance in xMessage.Elements(InstanceDefinition))
101 {
102 var value = xInstance.Value.Trim();
103
104 var parameters = xInstance.Elements(ParameterDefinition).Select(ReadParameter).ToList();
105
106 yield return new Message { Id = id, ClassName = name, Level = level, Number = number, Parameters = parameters, SourceLineNumbers = sln, Value = value, Suffix = suffix == 0 ? String.Empty : suffix.ToString() };
107
108 ++suffix;
109 }
110 }
111 }
112 }
113
114 private static Parameter ReadParameter(XElement element)
115 {
116 var name = element.Attribute(Name)?.Value;
117 var type = element.Attribute(Type)?.Value ?? "string";
118
119 if (type.StartsWith("System."))
120 {
121 type = type.Substring(7);
122 }
123
124 switch (type)
125 {
126 case "String":
127 type = "string";
128 break;
129
130 case "Int32":
131 type = "int";
132 break;
133
134 case "Int64":
135 type = "long";
136 break;
137 }
138
139 return new Parameter { Name = name, Type = type };
140 }
141
142 private static string GenerateCs(string className, string level, IEnumerable<Message> messages)
143 {
144 var header = String.Join(Environment.NewLine,
145 "// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.",
146 "",
147 "namespace WixToolset.Data",
148 "{",
149 " using System;",
150 " using System.Resources;",
151 "",
152 " public static class {0}",
153 " {");
154
155 var messageFormat = String.Join(Environment.NewLine,
156 " public static Message {0}({1})",
157 " {{",
158 " return Message({4}, Ids.{0}, \"{3}\"{2});",
159 " }}",
160 "");
161
162
163 var endMessagesFormat = String.Join(Environment.NewLine,
164 " private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)",
165 " {{",
166 " return new Message(sourceLineNumber, MessageLevel.{0}, (int)id, format, args);",
167 " }}",
168 "",
169 " private static Message Message(SourceLineNumber sourceLineNumber, Ids id, ResourceManager resourceManager, string resourceName, params object[] args)",
170 " {{",
171 " return new Message(sourceLineNumber, MessageLevel.{0}, (int)id, resourceManager, resourceName, args);",
172 " }}",
173 "",
174 " public enum Ids",
175 " {{");
176
177 var idEnumFormat =
178 " {0} = {1},";
179 var footer = String.Join(Environment.NewLine,
180 " }",
181 " }",
182 "}",
183 "");
184
185 var sb = new StringBuilder();
186
187 sb.AppendLine(header.Replace("{0}", className));
188
189 foreach (var message in messages.OrderBy(m => m.Id))
190 {
191 var paramsWithTypes = String.Join(", ", message.Parameters.Select(p => $"{p.Type} {p.Name}"));
192 var paramsWithoutTypes = String.Join(", ", message.Parameters.Select(p => p.Name));
193
194 if (message.SourceLineNumbers)
195 {
196 paramsWithTypes = String.IsNullOrEmpty(paramsWithTypes)
197 ? "SourceLineNumber sourceLineNumbers"
198 : "SourceLineNumber sourceLineNumbers, " + paramsWithTypes;
199 }
200
201 if (!String.IsNullOrEmpty(paramsWithoutTypes))
202 {
203 paramsWithoutTypes = ", " + paramsWithoutTypes;
204 }
205
206 sb.AppendFormat(messageFormat, message.Id, paramsWithTypes, paramsWithoutTypes, ToCSharpString(message.Value), message.SourceLineNumbers ? "sourceLineNumbers" : "null");
207
208 sb.AppendLine();
209 }
210
211 sb.AppendFormat(endMessagesFormat, level);
212 sb.AppendLine();
213
214 var unique = new HashSet<int>();
215 foreach (var message in messages.OrderBy(m => m.Number))
216 {
217 if (unique.Add(message.Number))
218 {
219 sb.AppendFormat(idEnumFormat, message.Id, message.Number);
220 sb.AppendLine();
221 }
222 }
223
224 sb.Append(footer);
225
226 return sb.ToString();
227 }
228
229 private static string ToCSharpString(string value)
230 {
231 return value.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\r", "\\r").Replace("\n", "\\n").Replace("\t", "\\t");
232 }
233
234 private class Message
235 {
236 public string Id { get; set; }
237
238 public string Suffix { get; set; }
239
240 public string ClassName { get; set; }
241
242 public string Level { get; set; }
243
244 public int Number { get; set; }
245
246 public bool SourceLineNumbers { get; set; }
247
248 public string Value { get; set; }
249
250 public IEnumerable<Parameter> Parameters { get; set; }
251 }
252
253
254 private class Parameter
255 {
256 public string Name { get; set; }
257
258 public string Type { get; set; }
259 }
260 }
261}
diff --git a/src/internal/MessagesToMessages/Properties/launchSettings.json b/src/internal/MessagesToMessages/Properties/launchSettings.json
new file mode 100644
index 00000000..dc7570f6
--- /dev/null
+++ b/src/internal/MessagesToMessages/Properties/launchSettings.json
@@ -0,0 +1,9 @@
1{
2 "profiles": {
3 "TablesAndSymbols": {
4 "commandName": "Project",
5 "commandLineArgs": "E:\\src\\wixtoolset\\Core\\src\\WixToolset.Core\\Data\\messages.xml E:\\src\\wixtoolset\\Data\\src\\WixToolset.Data",
6 "workingDirectory": "E:\\src\\wixtoolset\\Core\\src\\WixToolset.Core\\"
7 }
8 }
9} \ No newline at end of file
diff --git a/src/internal/MessagesToMessages/SimpleJson.cs b/src/internal/MessagesToMessages/SimpleJson.cs
new file mode 100644
index 00000000..3d956f6e
--- /dev/null
+++ b/src/internal/MessagesToMessages/SimpleJson.cs
@@ -0,0 +1,2127 @@
1//-----------------------------------------------------------------------
2// <copyright file="SimpleJson.cs" company="The Outercurve Foundation">
3// Copyright (c) 2011, The Outercurve Foundation.
4//
5// Licensed under the MIT License (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8// http://www.opensource.org/licenses/mit-license.php
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15// </copyright>
16// <author>Nathan Totten (ntotten.com), Jim Zimmerman (jimzimmerman.com) and Prabir Shrestha (prabir.me)</author>
17// <website>https://github.com/facebook-csharp-sdk/simple-json</website>
18//-----------------------------------------------------------------------
19
20// VERSION:
21
22// NOTE: uncomment the following line to make SimpleJson class internal.
23#define SIMPLE_JSON_INTERNAL
24
25// NOTE: uncomment the following line to make JsonArray and JsonObject class internal.
26#define SIMPLE_JSON_OBJARRAYINTERNAL
27
28// NOTE: uncomment the following line to enable dynamic support.
29//#define SIMPLE_JSON_DYNAMIC
30
31// NOTE: uncomment the following line to enable DataContract support.
32//#define SIMPLE_JSON_DATACONTRACT
33
34// NOTE: uncomment the following line to enable IReadOnlyCollection<T> and IReadOnlyList<T> support.
35//#define SIMPLE_JSON_READONLY_COLLECTIONS
36
37// NOTE: uncomment the following line to disable linq expressions/compiled lambda (better performance) instead of method.invoke().
38// define if you are using .net framework <= 3.0 or < WP7.5
39//#define SIMPLE_JSON_NO_LINQ_EXPRESSION
40
41// NOTE: uncomment the following line if you are compiling under Window Metro style application/library.
42// usually already defined in properties
43//#define NETFX_CORE;
44
45// If you are targetting WinStore, WP8 and NET4.5+ PCL make sure to #define SIMPLE_JSON_TYPEINFO;
46
47// original json parsing code from http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html
48
49#if NETFX_CORE
50#define SIMPLE_JSON_TYPEINFO
51#endif
52
53using System;
54using System.CodeDom.Compiler;
55using System.Collections;
56using System.Collections.Generic;
57#if !SIMPLE_JSON_NO_LINQ_EXPRESSION
58using System.Linq.Expressions;
59#endif
60using System.ComponentModel;
61using System.Diagnostics.CodeAnalysis;
62#if SIMPLE_JSON_DYNAMIC
63using System.Dynamic;
64#endif
65using System.Globalization;
66using System.Reflection;
67using System.Runtime.Serialization;
68using System.Text;
69using SimpleJson.Reflection;
70
71// ReSharper disable LoopCanBeConvertedToQuery
72// ReSharper disable RedundantExplicitArrayCreation
73// ReSharper disable SuggestUseVarKeywordEvident
74namespace SimpleJson
75{
76 /// <summary>
77 /// Represents the json array.
78 /// </summary>
79 [GeneratedCode("simple-json", "1.0.0")]
80 [EditorBrowsable(EditorBrowsableState.Never)]
81 [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
82#if SIMPLE_JSON_OBJARRAYINTERNAL
83 internal
84#else
85 public
86#endif
87 class JsonArray : List<object>
88 {
89 /// <summary>
90 /// Initializes a new instance of the <see cref="JsonArray"/> class.
91 /// </summary>
92 public JsonArray() { }
93
94 /// <summary>
95 /// Initializes a new instance of the <see cref="JsonArray"/> class.
96 /// </summary>
97 /// <param name="capacity">The capacity of the json array.</param>
98 public JsonArray(int capacity) : base(capacity) { }
99
100 /// <summary>
101 /// The json representation of the array.
102 /// </summary>
103 /// <returns>The json representation of the array.</returns>
104 public override string ToString()
105 {
106 return SimpleJson.SerializeObject(this) ?? string.Empty;
107 }
108 }
109
110 /// <summary>
111 /// Represents the json object.
112 /// </summary>
113 [GeneratedCode("simple-json", "1.0.0")]
114 [EditorBrowsable(EditorBrowsableState.Never)]
115 [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
116#if SIMPLE_JSON_OBJARRAYINTERNAL
117 internal
118#else
119 public
120#endif
121 class JsonObject :
122#if SIMPLE_JSON_DYNAMIC
123 DynamicObject,
124#endif
125 IDictionary<string, object>
126 {
127 /// <summary>
128 /// The internal member dictionary.
129 /// </summary>
130 private readonly Dictionary<string, object> _members;
131
132 /// <summary>
133 /// Initializes a new instance of <see cref="JsonObject"/>.
134 /// </summary>
135 public JsonObject()
136 {
137 _members = new Dictionary<string, object>();
138 }
139
140 /// <summary>
141 /// Initializes a new instance of <see cref="JsonObject"/>.
142 /// </summary>
143 /// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer`1"/> implementation to use when comparing keys, or null to use the default <see cref="T:System.Collections.Generic.EqualityComparer`1"/> for the type of the key.</param>
144 public JsonObject(IEqualityComparer<string> comparer)
145 {
146 _members = new Dictionary<string, object>(comparer);
147 }
148
149 /// <summary>
150 /// Gets the <see cref="System.Object"/> at the specified index.
151 /// </summary>
152 /// <value></value>
153 public object this[int index]
154 {
155 get { return GetAtIndex(_members, index); }
156 }
157
158 internal static object GetAtIndex(IDictionary<string, object> obj, int index)
159 {
160 if (obj == null)
161 throw new ArgumentNullException("obj");
162 if (index >= obj.Count)
163 throw new ArgumentOutOfRangeException("index");
164 int i = 0;
165 foreach (KeyValuePair<string, object> o in obj)
166 if (i++ == index) return o.Value;
167 return null;
168 }
169
170 /// <summary>
171 /// Adds the specified key.
172 /// </summary>
173 /// <param name="key">The key.</param>
174 /// <param name="value">The value.</param>
175 public void Add(string key, object value)
176 {
177 _members.Add(key, value);
178 }
179
180 /// <summary>
181 /// Determines whether the specified key contains key.
182 /// </summary>
183 /// <param name="key">The key.</param>
184 /// <returns>
185 /// <c>true</c> if the specified key contains key; otherwise, <c>false</c>.
186 /// </returns>
187 public bool ContainsKey(string key)
188 {
189 return _members.ContainsKey(key);
190 }
191
192 /// <summary>
193 /// Gets the keys.
194 /// </summary>
195 /// <value>The keys.</value>
196 public ICollection<string> Keys
197 {
198 get { return _members.Keys; }
199 }
200
201 /// <summary>
202 /// Removes the specified key.
203 /// </summary>
204 /// <param name="key">The key.</param>
205 /// <returns></returns>
206 public bool Remove(string key)
207 {
208 return _members.Remove(key);
209 }
210
211 /// <summary>
212 /// Tries the get value.
213 /// </summary>
214 /// <param name="key">The key.</param>
215 /// <param name="value">The value.</param>
216 /// <returns></returns>
217 public bool TryGetValue(string key, out object value)
218 {
219 return _members.TryGetValue(key, out value);
220 }
221
222 /// <summary>
223 /// Gets the values.
224 /// </summary>
225 /// <value>The values.</value>
226 public ICollection<object> Values
227 {
228 get { return _members.Values; }
229 }
230
231 /// <summary>
232 /// Gets or sets the <see cref="System.Object"/> with the specified key.
233 /// </summary>
234 /// <value></value>
235 public object this[string key]
236 {
237 get { return _members[key]; }
238 set { _members[key] = value; }
239 }
240
241 /// <summary>
242 /// Adds the specified item.
243 /// </summary>
244 /// <param name="item">The item.</param>
245 public void Add(KeyValuePair<string, object> item)
246 {
247 _members.Add(item.Key, item.Value);
248 }
249
250 /// <summary>
251 /// Clears this instance.
252 /// </summary>
253 public void Clear()
254 {
255 _members.Clear();
256 }
257
258 /// <summary>
259 /// Determines whether [contains] [the specified item].
260 /// </summary>
261 /// <param name="item">The item.</param>
262 /// <returns>
263 /// <c>true</c> if [contains] [the specified item]; otherwise, <c>false</c>.
264 /// </returns>
265 public bool Contains(KeyValuePair<string, object> item)
266 {
267 return _members.ContainsKey(item.Key) && _members[item.Key] == item.Value;
268 }
269
270 /// <summary>
271 /// Copies to.
272 /// </summary>
273 /// <param name="array">The array.</param>
274 /// <param name="arrayIndex">Index of the array.</param>
275 public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
276 {
277 if (array == null) throw new ArgumentNullException("array");
278 int num = Count;
279 foreach (KeyValuePair<string, object> kvp in this)
280 {
281 array[arrayIndex++] = kvp;
282 if (--num <= 0)
283 return;
284 }
285 }
286
287 /// <summary>
288 /// Gets the count.
289 /// </summary>
290 /// <value>The count.</value>
291 public int Count
292 {
293 get { return _members.Count; }
294 }
295
296 /// <summary>
297 /// Gets a value indicating whether this instance is read only.
298 /// </summary>
299 /// <value>
300 /// <c>true</c> if this instance is read only; otherwise, <c>false</c>.
301 /// </value>
302 public bool IsReadOnly
303 {
304 get { return false; }
305 }
306
307 /// <summary>
308 /// Removes the specified item.
309 /// </summary>
310 /// <param name="item">The item.</param>
311 /// <returns></returns>
312 public bool Remove(KeyValuePair<string, object> item)
313 {
314 return _members.Remove(item.Key);
315 }
316
317 /// <summary>
318 /// Gets the enumerator.
319 /// </summary>
320 /// <returns></returns>
321 public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
322 {
323 return _members.GetEnumerator();
324 }
325
326 /// <summary>
327 /// Returns an enumerator that iterates through a collection.
328 /// </summary>
329 /// <returns>
330 /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
331 /// </returns>
332 IEnumerator IEnumerable.GetEnumerator()
333 {
334 return _members.GetEnumerator();
335 }
336
337 /// <summary>
338 /// Returns a json <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
339 /// </summary>
340 /// <returns>
341 /// A json <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
342 /// </returns>
343 public override string ToString()
344 {
345 return SimpleJson.SerializeObject(this);
346 }
347
348#if SIMPLE_JSON_DYNAMIC
349 /// <summary>
350 /// Provides implementation for type conversion operations. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for operations that convert an object from one type to another.
351 /// </summary>
352 /// <param name="binder">Provides information about the conversion operation. The binder.Type property provides the type to which the object must be converted. For example, for the statement (String)sampleObject in C# (CType(sampleObject, Type) in Visual Basic), where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, binder.Type returns the <see cref="T:System.String"/> type. The binder.Explicit property provides information about the kind of conversion that occurs. It returns true for explicit conversion and false for implicit conversion.</param>
353 /// <param name="result">The result of the type conversion operation.</param>
354 /// <returns>
355 /// Alwasy returns true.
356 /// </returns>
357 public override bool TryConvert(ConvertBinder binder, out object result)
358 {
359 // <pex>
360 if (binder == null)
361 throw new ArgumentNullException("binder");
362 // </pex>
363 Type targetType = binder.Type;
364
365 if ((targetType == typeof(IEnumerable)) ||
366 (targetType == typeof(IEnumerable<KeyValuePair<string, object>>)) ||
367 (targetType == typeof(IDictionary<string, object>)) ||
368 (targetType == typeof(IDictionary)))
369 {
370 result = this;
371 return true;
372 }
373
374 return base.TryConvert(binder, out result);
375 }
376
377 /// <summary>
378 /// Provides the implementation for operations that delete an object member. This method is not intended for use in C# or Visual Basic.
379 /// </summary>
380 /// <param name="binder">Provides information about the deletion.</param>
381 /// <returns>
382 /// Alwasy returns true.
383 /// </returns>
384 public override bool TryDeleteMember(DeleteMemberBinder binder)
385 {
386 // <pex>
387 if (binder == null)
388 throw new ArgumentNullException("binder");
389 // </pex>
390 return _members.Remove(binder.Name);
391 }
392
393 /// <summary>
394 /// Provides the implementation for operations that get a value by index. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for indexing operations.
395 /// </summary>
396 /// <param name="binder">Provides information about the operation.</param>
397 /// <param name="indexes">The indexes that are used in the operation. For example, for the sampleObject[3] operation in C# (sampleObject(3) in Visual Basic), where sampleObject is derived from the DynamicObject class, <paramref name="indexes"/> is equal to 3.</param>
398 /// <param name="result">The result of the index operation.</param>
399 /// <returns>
400 /// Alwasy returns true.
401 /// </returns>
402 public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
403 {
404 if (indexes == null) throw new ArgumentNullException("indexes");
405 if (indexes.Length == 1)
406 {
407 result = ((IDictionary<string, object>)this)[(string)indexes[0]];
408 return true;
409 }
410 result = null;
411 return true;
412 }
413
414 /// <summary>
415 /// Provides the implementation for operations that get member values. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for operations such as getting a value for a property.
416 /// </summary>
417 /// <param name="binder">Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member on which the dynamic operation is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty) statement, where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive.</param>
418 /// <param name="result">The result of the get operation. For example, if the method is called for a property, you can assign the property value to <paramref name="result"/>.</param>
419 /// <returns>
420 /// Alwasy returns true.
421 /// </returns>
422 public override bool TryGetMember(GetMemberBinder binder, out object result)
423 {
424 object value;
425 if (_members.TryGetValue(binder.Name, out value))
426 {
427 result = value;
428 return true;
429 }
430 result = null;
431 return true;
432 }
433
434 /// <summary>
435 /// Provides the implementation for operations that set a value by index. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for operations that access objects by a specified index.
436 /// </summary>
437 /// <param name="binder">Provides information about the operation.</param>
438 /// <param name="indexes">The indexes that are used in the operation. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, <paramref name="indexes"/> is equal to 3.</param>
439 /// <param name="value">The value to set to the object that has the specified index. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, <paramref name="value"/> is equal to 10.</param>
440 /// <returns>
441 /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.
442 /// </returns>
443 public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
444 {
445 if (indexes == null) throw new ArgumentNullException("indexes");
446 if (indexes.Length == 1)
447 {
448 ((IDictionary<string, object>)this)[(string)indexes[0]] = value;
449 return true;
450 }
451 return base.TrySetIndex(binder, indexes, value);
452 }
453
454 /// <summary>
455 /// Provides the implementation for operations that set member values. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for operations such as setting a value for a property.
456 /// </summary>
457 /// <param name="binder">Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member to which the value is being assigned. For example, for the statement sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive.</param>
458 /// <param name="value">The value to set to the member. For example, for sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, the <paramref name="value"/> is "Test".</param>
459 /// <returns>
460 /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.)
461 /// </returns>
462 public override bool TrySetMember(SetMemberBinder binder, object value)
463 {
464 // <pex>
465 if (binder == null)
466 throw new ArgumentNullException("binder");
467 // </pex>
468 _members[binder.Name] = value;
469 return true;
470 }
471
472 /// <summary>
473 /// Returns the enumeration of all dynamic member names.
474 /// </summary>
475 /// <returns>
476 /// A sequence that contains dynamic member names.
477 /// </returns>
478 public override IEnumerable<string> GetDynamicMemberNames()
479 {
480 foreach (var key in Keys)
481 yield return key;
482 }
483#endif
484 }
485}
486
487namespace SimpleJson
488{
489 /// <summary>
490 /// This class encodes and decodes JSON strings.
491 /// Spec. details, see http://www.json.org/
492 ///
493 /// JSON uses Arrays and Objects. These correspond here to the datatypes JsonArray(IList&lt;object>) and JsonObject(IDictionary&lt;string,object>).
494 /// All numbers are parsed to doubles.
495 /// </summary>
496 [GeneratedCode("simple-json", "1.0.0")]
497#if SIMPLE_JSON_INTERNAL
498 internal
499#else
500 public
501#endif
502 static class SimpleJson
503 {
504 private const int TOKEN_NONE = 0;
505 private const int TOKEN_CURLY_OPEN = 1;
506 private const int TOKEN_CURLY_CLOSE = 2;
507 private const int TOKEN_SQUARED_OPEN = 3;
508 private const int TOKEN_SQUARED_CLOSE = 4;
509 private const int TOKEN_COLON = 5;
510 private const int TOKEN_COMMA = 6;
511 private const int TOKEN_STRING = 7;
512 private const int TOKEN_NUMBER = 8;
513 private const int TOKEN_TRUE = 9;
514 private const int TOKEN_FALSE = 10;
515 private const int TOKEN_NULL = 11;
516 private const int BUILDER_CAPACITY = 2000;
517
518 private static readonly char[] EscapeTable;
519 private static readonly char[] EscapeCharacters = new char[] { '"', '\\', '\b', '\f', '\n', '\r', '\t' };
520 private static readonly string EscapeCharactersString = new string(EscapeCharacters);
521
522 static SimpleJson()
523 {
524 EscapeTable = new char[93];
525 EscapeTable['"'] = '"';
526 EscapeTable['\\'] = '\\';
527 EscapeTable['\b'] = 'b';
528 EscapeTable['\f'] = 'f';
529 EscapeTable['\n'] = 'n';
530 EscapeTable['\r'] = 'r';
531 EscapeTable['\t'] = 't';
532 }
533
534 /// <summary>
535 /// Parses the string json into a value
536 /// </summary>
537 /// <param name="json">A JSON string.</param>
538 /// <returns>An IList&lt;object>, a IDictionary&lt;string,object>, a double, a string, null, true, or false</returns>
539 public static object DeserializeObject(string json)
540 {
541 object obj;
542 if (TryDeserializeObject(json, out obj))
543 return obj;
544 throw new SerializationException("Invalid JSON string");
545 }
546
547 /// <summary>
548 /// Try parsing the json string into a value.
549 /// </summary>
550 /// <param name="json">
551 /// A JSON string.
552 /// </param>
553 /// <param name="obj">
554 /// The object.
555 /// </param>
556 /// <returns>
557 /// Returns true if successfull otherwise false.
558 /// </returns>
559 [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Need to support .NET 2")]
560 public static bool TryDeserializeObject(string json, out object obj)
561 {
562 bool success = true;
563 if (json != null)
564 {
565 char[] charArray = json.ToCharArray();
566 int index = 0;
567 obj = ParseValue(charArray, ref index, ref success);
568 }
569 else
570 obj = null;
571
572 return success;
573 }
574
575 public static object DeserializeObject(string json, Type type, IJsonSerializerStrategy jsonSerializerStrategy)
576 {
577 object jsonObject = DeserializeObject(json);
578 return type == null || jsonObject != null && ReflectionUtils.IsAssignableFrom(jsonObject.GetType(), type)
579 ? jsonObject
580 : (jsonSerializerStrategy ?? CurrentJsonSerializerStrategy).DeserializeObject(jsonObject, type);
581 }
582
583 public static object DeserializeObject(string json, Type type)
584 {
585 return DeserializeObject(json, type, null);
586 }
587
588 public static T DeserializeObject<T>(string json, IJsonSerializerStrategy jsonSerializerStrategy)
589 {
590 return (T)DeserializeObject(json, typeof(T), jsonSerializerStrategy);
591 }
592
593 public static T DeserializeObject<T>(string json)
594 {
595 return (T)DeserializeObject(json, typeof(T), null);
596 }
597
598 /// <summary>
599 /// Converts a IDictionary&lt;string,object> / IList&lt;object> object into a JSON string
600 /// </summary>
601 /// <param name="json">A IDictionary&lt;string,object> / IList&lt;object></param>
602 /// <param name="jsonSerializerStrategy">Serializer strategy to use</param>
603 /// <returns>A JSON encoded string, or null if object 'json' is not serializable</returns>
604 public static string SerializeObject(object json, IJsonSerializerStrategy jsonSerializerStrategy)
605 {
606 StringBuilder builder = new StringBuilder(BUILDER_CAPACITY);
607 bool success = SerializeValue(jsonSerializerStrategy, json, builder);
608 return (success ? builder.ToString() : null);
609 }
610
611 public static string SerializeObject(object json)
612 {
613 return SerializeObject(json, CurrentJsonSerializerStrategy);
614 }
615
616 public static string EscapeToJavascriptString(string jsonString)
617 {
618 if (string.IsNullOrEmpty(jsonString))
619 return jsonString;
620
621 StringBuilder sb = new StringBuilder();
622 char c;
623
624 for (int i = 0; i < jsonString.Length;)
625 {
626 c = jsonString[i++];
627
628 if (c == '\\')
629 {
630 int remainingLength = jsonString.Length - i;
631 if (remainingLength >= 2)
632 {
633 char lookahead = jsonString[i];
634 if (lookahead == '\\')
635 {
636 sb.Append('\\');
637 ++i;
638 }
639 else if (lookahead == '"')
640 {
641 sb.Append("\"");
642 ++i;
643 }
644 else if (lookahead == 't')
645 {
646 sb.Append('\t');
647 ++i;
648 }
649 else if (lookahead == 'b')
650 {
651 sb.Append('\b');
652 ++i;
653 }
654 else if (lookahead == 'n')
655 {
656 sb.Append('\n');
657 ++i;
658 }
659 else if (lookahead == 'r')
660 {
661 sb.Append('\r');
662 ++i;
663 }
664 }
665 }
666 else
667 {
668 sb.Append(c);
669 }
670 }
671 return sb.ToString();
672 }
673
674 static IDictionary<string, object> ParseObject(char[] json, ref int index, ref bool success)
675 {
676 IDictionary<string, object> table = new JsonObject();
677 int token;
678
679 // {
680 NextToken(json, ref index);
681
682 bool done = false;
683 while (!done)
684 {
685 token = LookAhead(json, index);
686 if (token == TOKEN_NONE)
687 {
688 success = false;
689 return null;
690 }
691 else if (token == TOKEN_COMMA)
692 NextToken(json, ref index);
693 else if (token == TOKEN_CURLY_CLOSE)
694 {
695 NextToken(json, ref index);
696 return table;
697 }
698 else
699 {
700 // name
701 string name = ParseString(json, ref index, ref success);
702 if (!success)
703 {
704 success = false;
705 return null;
706 }
707 // :
708 token = NextToken(json, ref index);
709 if (token != TOKEN_COLON)
710 {
711 success = false;
712 return null;
713 }
714 // value
715 object value = ParseValue(json, ref index, ref success);
716 if (!success)
717 {
718 success = false;
719 return null;
720 }
721 table[name] = value;
722 }
723 }
724 return table;
725 }
726
727 static JsonArray ParseArray(char[] json, ref int index, ref bool success)
728 {
729 JsonArray array = new JsonArray();
730
731 // [
732 NextToken(json, ref index);
733
734 bool done = false;
735 while (!done)
736 {
737 int token = LookAhead(json, index);
738 if (token == TOKEN_NONE)
739 {
740 success = false;
741 return null;
742 }
743 else if (token == TOKEN_COMMA)
744 NextToken(json, ref index);
745 else if (token == TOKEN_SQUARED_CLOSE)
746 {
747 NextToken(json, ref index);
748 break;
749 }
750 else
751 {
752 object value = ParseValue(json, ref index, ref success);
753 if (!success)
754 return null;
755 array.Add(value);
756 }
757 }
758 return array;
759 }
760
761 static object ParseValue(char[] json, ref int index, ref bool success)
762 {
763 switch (LookAhead(json, index))
764 {
765 case TOKEN_STRING:
766 return ParseString(json, ref index, ref success);
767 case TOKEN_NUMBER:
768 return ParseNumber(json, ref index, ref success);
769 case TOKEN_CURLY_OPEN:
770 return ParseObject(json, ref index, ref success);
771 case TOKEN_SQUARED_OPEN:
772 return ParseArray(json, ref index, ref success);
773 case TOKEN_TRUE:
774 NextToken(json, ref index);
775 return true;
776 case TOKEN_FALSE:
777 NextToken(json, ref index);
778 return false;
779 case TOKEN_NULL:
780 NextToken(json, ref index);
781 return null;
782 case TOKEN_NONE:
783 break;
784 }
785 success = false;
786 return null;
787 }
788
789 static string ParseString(char[] json, ref int index, ref bool success)
790 {
791 StringBuilder s = new StringBuilder(BUILDER_CAPACITY);
792 char c;
793
794 EatWhitespace(json, ref index);
795
796 // "
797 c = json[index++];
798 bool complete = false;
799 while (!complete)
800 {
801 if (index == json.Length)
802 break;
803
804 c = json[index++];
805 if (c == '"')
806 {
807 complete = true;
808 break;
809 }
810 else if (c == '\\')
811 {
812 if (index == json.Length)
813 break;
814 c = json[index++];
815 if (c == '"')
816 s.Append('"');
817 else if (c == '\\')
818 s.Append('\\');
819 else if (c == '/')
820 s.Append('/');
821 else if (c == 'b')
822 s.Append('\b');
823 else if (c == 'f')
824 s.Append('\f');
825 else if (c == 'n')
826 s.Append('\n');
827 else if (c == 'r')
828 s.Append('\r');
829 else if (c == 't')
830 s.Append('\t');
831 else if (c == 'u')
832 {
833 int remainingLength = json.Length - index;
834 if (remainingLength >= 4)
835 {
836 // parse the 32 bit hex into an integer codepoint
837 uint codePoint;
838 if (!(success = UInt32.TryParse(new string(json, index, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out codePoint)))
839 return "";
840
841 // convert the integer codepoint to a unicode char and add to string
842 if (0xD800 <= codePoint && codePoint <= 0xDBFF) // if high surrogate
843 {
844 index += 4; // skip 4 chars
845 remainingLength = json.Length - index;
846 if (remainingLength >= 6)
847 {
848 uint lowCodePoint;
849 if (new string(json, index, 2) == "\\u" && UInt32.TryParse(new string(json, index + 2, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out lowCodePoint))
850 {
851 if (0xDC00 <= lowCodePoint && lowCodePoint <= 0xDFFF) // if low surrogate
852 {
853 s.Append((char)codePoint);
854 s.Append((char)lowCodePoint);
855 index += 6; // skip 6 chars
856 continue;
857 }
858 }
859 }
860 success = false; // invalid surrogate pair
861 return "";
862 }
863 s.Append(ConvertFromUtf32((int)codePoint));
864 // skip 4 chars
865 index += 4;
866 }
867 else
868 break;
869 }
870 }
871 else
872 s.Append(c);
873 }
874 if (!complete)
875 {
876 success = false;
877 return null;
878 }
879 return s.ToString();
880 }
881
882 private static string ConvertFromUtf32(int utf32)
883 {
884 // http://www.java2s.com/Open-Source/CSharp/2.6.4-mono-.net-core/System/System/Char.cs.htm
885 if (utf32 < 0 || utf32 > 0x10FFFF)
886 throw new ArgumentOutOfRangeException("utf32", "The argument must be from 0 to 0x10FFFF.");
887 if (0xD800 <= utf32 && utf32 <= 0xDFFF)
888 throw new ArgumentOutOfRangeException("utf32", "The argument must not be in surrogate pair range.");
889 if (utf32 < 0x10000)
890 return new string((char)utf32, 1);
891 utf32 -= 0x10000;
892 return new string(new char[] { (char)((utf32 >> 10) + 0xD800), (char)(utf32 % 0x0400 + 0xDC00) });
893 }
894
895 static object ParseNumber(char[] json, ref int index, ref bool success)
896 {
897 EatWhitespace(json, ref index);
898 int lastIndex = GetLastIndexOfNumber(json, index);
899 int charLength = (lastIndex - index) + 1;
900 object returnNumber;
901 string str = new string(json, index, charLength);
902 if (str.IndexOf(".", StringComparison.OrdinalIgnoreCase) != -1 || str.IndexOf("e", StringComparison.OrdinalIgnoreCase) != -1)
903 {
904 double number;
905 success = double.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number);
906 returnNumber = number;
907 }
908 else
909 {
910 long number;
911 success = long.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number);
912 returnNumber = number;
913 }
914 index = lastIndex + 1;
915 return returnNumber;
916 }
917
918 static int GetLastIndexOfNumber(char[] json, int index)
919 {
920 int lastIndex;
921 for (lastIndex = index; lastIndex < json.Length; lastIndex++)
922 if ("0123456789+-.eE".IndexOf(json[lastIndex]) == -1) break;
923 return lastIndex - 1;
924 }
925
926 static void EatWhitespace(char[] json, ref int index)
927 {
928 for (; index < json.Length; index++)
929 if (" \t\n\r\b\f".IndexOf(json[index]) == -1) break;
930 }
931
932 static int LookAhead(char[] json, int index)
933 {
934 int saveIndex = index;
935 return NextToken(json, ref saveIndex);
936 }
937
938 [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
939 static int NextToken(char[] json, ref int index)
940 {
941 EatWhitespace(json, ref index);
942 if (index == json.Length)
943 return TOKEN_NONE;
944 char c = json[index];
945 index++;
946 switch (c)
947 {
948 case '{':
949 return TOKEN_CURLY_OPEN;
950 case '}':
951 return TOKEN_CURLY_CLOSE;
952 case '[':
953 return TOKEN_SQUARED_OPEN;
954 case ']':
955 return TOKEN_SQUARED_CLOSE;
956 case ',':
957 return TOKEN_COMMA;
958 case '"':
959 return TOKEN_STRING;
960 case '0':
961 case '1':
962 case '2':
963 case '3':
964 case '4':
965 case '5':
966 case '6':
967 case '7':
968 case '8':
969 case '9':
970 case '-':
971 return TOKEN_NUMBER;
972 case ':':
973 return TOKEN_COLON;
974 }
975 index--;
976 int remainingLength = json.Length - index;
977 // false
978 if (remainingLength >= 5)
979 {
980 if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' && json[index + 4] == 'e')
981 {
982 index += 5;
983 return TOKEN_FALSE;
984 }
985 }
986 // true
987 if (remainingLength >= 4)
988 {
989 if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e')
990 {
991 index += 4;
992 return TOKEN_TRUE;
993 }
994 }
995 // null
996 if (remainingLength >= 4)
997 {
998 if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l')
999 {
1000 index += 4;
1001 return TOKEN_NULL;
1002 }
1003 }
1004 return TOKEN_NONE;
1005 }
1006
1007 static bool SerializeValue(IJsonSerializerStrategy jsonSerializerStrategy, object value, StringBuilder builder)
1008 {
1009 bool success = true;
1010 string stringValue = value as string;
1011 if (stringValue != null)
1012 success = SerializeString(stringValue, builder);
1013 else
1014 {
1015 IDictionary<string, object> dict = value as IDictionary<string, object>;
1016 if (dict != null)
1017 {
1018 success = SerializeObject(jsonSerializerStrategy, dict.Keys, dict.Values, builder);
1019 }
1020 else
1021 {
1022 IDictionary<string, string> stringDictionary = value as IDictionary<string, string>;
1023 if (stringDictionary != null)
1024 {
1025 success = SerializeObject(jsonSerializerStrategy, stringDictionary.Keys, stringDictionary.Values, builder);
1026 }
1027 else
1028 {
1029 IEnumerable enumerableValue = value as IEnumerable;
1030 if (enumerableValue != null)
1031 success = SerializeArray(jsonSerializerStrategy, enumerableValue, builder);
1032 else if (IsNumeric(value))
1033 success = SerializeNumber(value, builder);
1034 else if (value is bool)
1035 builder.Append((bool)value ? "true" : "false");
1036 else if (value == null)
1037 builder.Append("null");
1038 else
1039 {
1040 object serializedObject;
1041 success = jsonSerializerStrategy.TrySerializeNonPrimitiveObject(value, out serializedObject);
1042 if (success)
1043 SerializeValue(jsonSerializerStrategy, serializedObject, builder);
1044 }
1045 }
1046 }
1047 }
1048 return success;
1049 }
1050
1051 static bool SerializeObject(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable keys, IEnumerable values, StringBuilder builder)
1052 {
1053 builder.Append("{");
1054 IEnumerator ke = keys.GetEnumerator();
1055 IEnumerator ve = values.GetEnumerator();
1056 bool first = true;
1057 while (ke.MoveNext() && ve.MoveNext())
1058 {
1059 object key = ke.Current;
1060 object value = ve.Current;
1061 if (!first)
1062 builder.Append(",");
1063 string stringKey = key as string;
1064 if (stringKey != null)
1065 SerializeString(stringKey, builder);
1066 else
1067 if (!SerializeValue(jsonSerializerStrategy, value, builder)) return false;
1068 builder.Append(":");
1069 if (!SerializeValue(jsonSerializerStrategy, value, builder))
1070 return false;
1071 first = false;
1072 }
1073 builder.Append("}");
1074 return true;
1075 }
1076
1077 static bool SerializeArray(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable anArray, StringBuilder builder)
1078 {
1079 builder.Append("[");
1080 bool first = true;
1081 foreach (object value in anArray)
1082 {
1083 if (!first)
1084 builder.Append(",");
1085 if (!SerializeValue(jsonSerializerStrategy, value, builder))
1086 return false;
1087 first = false;
1088 }
1089 builder.Append("]");
1090 return true;
1091 }
1092
1093 static bool SerializeString(string aString, StringBuilder builder)
1094 {
1095 // Happy path if there's nothing to be escaped. IndexOfAny is highly optimized (and unmanaged)
1096 if (aString.IndexOfAny(EscapeCharacters) == -1)
1097 {
1098 builder.Append('"');
1099 builder.Append(aString);
1100 builder.Append('"');
1101
1102 return true;
1103 }
1104
1105 builder.Append('"');
1106 int safeCharacterCount = 0;
1107 char[] charArray = aString.ToCharArray();
1108
1109 for (int i = 0; i < charArray.Length; i++)
1110 {
1111 char c = charArray[i];
1112
1113 // Non ascii characters are fine, buffer them up and send them to the builder
1114 // in larger chunks if possible. The escape table is a 1:1 translation table
1115 // with \0 [default(char)] denoting a safe character.
1116 if (c >= EscapeTable.Length || EscapeTable[c] == default(char))
1117 {
1118 safeCharacterCount++;
1119 }
1120 else
1121 {
1122 if (safeCharacterCount > 0)
1123 {
1124 builder.Append(charArray, i - safeCharacterCount, safeCharacterCount);
1125 safeCharacterCount = 0;
1126 }
1127
1128 builder.Append('\\');
1129 builder.Append(EscapeTable[c]);
1130 }
1131 }
1132
1133 if (safeCharacterCount > 0)
1134 {
1135 builder.Append(charArray, charArray.Length - safeCharacterCount, safeCharacterCount);
1136 }
1137
1138 builder.Append('"');
1139 return true;
1140 }
1141
1142 static bool SerializeNumber(object number, StringBuilder builder)
1143 {
1144 if (number is long)
1145 builder.Append(((long)number).ToString(CultureInfo.InvariantCulture));
1146 else if (number is ulong)
1147 builder.Append(((ulong)number).ToString(CultureInfo.InvariantCulture));
1148 else if (number is int)
1149 builder.Append(((int)number).ToString(CultureInfo.InvariantCulture));
1150 else if (number is uint)
1151 builder.Append(((uint)number).ToString(CultureInfo.InvariantCulture));
1152 else if (number is decimal)
1153 builder.Append(((decimal)number).ToString(CultureInfo.InvariantCulture));
1154 else if (number is float)
1155 builder.Append(((float)number).ToString(CultureInfo.InvariantCulture));
1156 else
1157 builder.Append(Convert.ToDouble(number, CultureInfo.InvariantCulture).ToString("r", CultureInfo.InvariantCulture));
1158 return true;
1159 }
1160
1161 /// <summary>
1162 /// Determines if a given object is numeric in any way
1163 /// (can be integer, double, null, etc).
1164 /// </summary>
1165 static bool IsNumeric(object value)
1166 {
1167 if (value is sbyte) return true;
1168 if (value is byte) return true;
1169 if (value is short) return true;
1170 if (value is ushort) return true;
1171 if (value is int) return true;
1172 if (value is uint) return true;
1173 if (value is long) return true;
1174 if (value is ulong) return true;
1175 if (value is float) return true;
1176 if (value is double) return true;
1177 if (value is decimal) return true;
1178 return false;
1179 }
1180
1181 private static IJsonSerializerStrategy _currentJsonSerializerStrategy;
1182 public static IJsonSerializerStrategy CurrentJsonSerializerStrategy
1183 {
1184 get
1185 {
1186 return _currentJsonSerializerStrategy ??
1187 (_currentJsonSerializerStrategy =
1188#if SIMPLE_JSON_DATACONTRACT
1189 DataContractJsonSerializerStrategy
1190#else
1191 PocoJsonSerializerStrategy
1192#endif
1193);
1194 }
1195 set
1196 {
1197 _currentJsonSerializerStrategy = value;
1198 }
1199 }
1200
1201 private static PocoJsonSerializerStrategy _pocoJsonSerializerStrategy;
1202 [EditorBrowsable(EditorBrowsableState.Advanced)]
1203 public static PocoJsonSerializerStrategy PocoJsonSerializerStrategy
1204 {
1205 get
1206 {
1207 return _pocoJsonSerializerStrategy ?? (_pocoJsonSerializerStrategy = new PocoJsonSerializerStrategy());
1208 }
1209 }
1210
1211#if SIMPLE_JSON_DATACONTRACT
1212
1213 private static DataContractJsonSerializerStrategy _dataContractJsonSerializerStrategy;
1214 [System.ComponentModel.EditorBrowsable(EditorBrowsableState.Advanced)]
1215 public static DataContractJsonSerializerStrategy DataContractJsonSerializerStrategy
1216 {
1217 get
1218 {
1219 return _dataContractJsonSerializerStrategy ?? (_dataContractJsonSerializerStrategy = new DataContractJsonSerializerStrategy());
1220 }
1221 }
1222
1223#endif
1224 }
1225
1226 [GeneratedCode("simple-json", "1.0.0")]
1227#if SIMPLE_JSON_INTERNAL
1228 internal
1229#else
1230 public
1231#endif
1232 interface IJsonSerializerStrategy
1233 {
1234 [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Need to support .NET 2")]
1235 bool TrySerializeNonPrimitiveObject(object input, out object output);
1236 object DeserializeObject(object value, Type type);
1237 }
1238
1239 [GeneratedCode("simple-json", "1.0.0")]
1240#if SIMPLE_JSON_INTERNAL
1241 internal
1242#else
1243 public
1244#endif
1245 class PocoJsonSerializerStrategy : IJsonSerializerStrategy
1246 {
1247 internal IDictionary<Type, ReflectionUtils.ConstructorDelegate> ConstructorCache;
1248 internal IDictionary<Type, IDictionary<string, ReflectionUtils.GetDelegate>> GetCache;
1249 internal IDictionary<Type, IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>> SetCache;
1250
1251 internal static readonly Type[] EmptyTypes = new Type[0];
1252 internal static readonly Type[] ArrayConstructorParameterTypes = new Type[] { typeof(int) };
1253
1254 private static readonly string[] Iso8601Format = new string[]
1255 {
1256 @"yyyy-MM-dd\THH:mm:ss.FFFFFFF\Z",
1257 @"yyyy-MM-dd\THH:mm:ss\Z",
1258 @"yyyy-MM-dd\THH:mm:ssK"
1259 };
1260
1261 public PocoJsonSerializerStrategy()
1262 {
1263 ConstructorCache = new ReflectionUtils.ThreadSafeDictionary<Type, ReflectionUtils.ConstructorDelegate>(ContructorDelegateFactory);
1264 GetCache = new ReflectionUtils.ThreadSafeDictionary<Type, IDictionary<string, ReflectionUtils.GetDelegate>>(GetterValueFactory);
1265 SetCache = new ReflectionUtils.ThreadSafeDictionary<Type, IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>>(SetterValueFactory);
1266 }
1267
1268 protected virtual string MapClrMemberNameToJsonFieldName(string clrPropertyName)
1269 {
1270 return clrPropertyName;
1271 }
1272
1273 internal virtual ReflectionUtils.ConstructorDelegate ContructorDelegateFactory(Type key)
1274 {
1275 return ReflectionUtils.GetContructor(key, key.IsArray ? ArrayConstructorParameterTypes : EmptyTypes);
1276 }
1277
1278 internal virtual IDictionary<string, ReflectionUtils.GetDelegate> GetterValueFactory(Type type)
1279 {
1280 IDictionary<string, ReflectionUtils.GetDelegate> result = new Dictionary<string, ReflectionUtils.GetDelegate>();
1281 foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type))
1282 {
1283 if (propertyInfo.CanRead)
1284 {
1285 MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo);
1286 if (getMethod.IsStatic || !getMethod.IsPublic)
1287 continue;
1288 result[MapClrMemberNameToJsonFieldName(propertyInfo.Name)] = ReflectionUtils.GetGetMethod(propertyInfo);
1289 }
1290 }
1291 foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type))
1292 {
1293 if (fieldInfo.IsStatic || !fieldInfo.IsPublic)
1294 continue;
1295 result[MapClrMemberNameToJsonFieldName(fieldInfo.Name)] = ReflectionUtils.GetGetMethod(fieldInfo);
1296 }
1297 return result;
1298 }
1299
1300 internal virtual IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> SetterValueFactory(Type type)
1301 {
1302 IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> result = new Dictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>();
1303 foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type))
1304 {
1305 if (propertyInfo.CanWrite)
1306 {
1307 MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo);
1308 if (setMethod.IsStatic || !setMethod.IsPublic)
1309 continue;
1310 result[MapClrMemberNameToJsonFieldName(propertyInfo.Name)] = new KeyValuePair<Type, ReflectionUtils.SetDelegate>(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo));
1311 }
1312 }
1313 foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type))
1314 {
1315 if (fieldInfo.IsInitOnly || fieldInfo.IsStatic || !fieldInfo.IsPublic)
1316 continue;
1317 result[MapClrMemberNameToJsonFieldName(fieldInfo.Name)] = new KeyValuePair<Type, ReflectionUtils.SetDelegate>(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo));
1318 }
1319 return result;
1320 }
1321
1322 public virtual bool TrySerializeNonPrimitiveObject(object input, out object output)
1323 {
1324 return TrySerializeKnownTypes(input, out output) || TrySerializeUnknownTypes(input, out output);
1325 }
1326
1327 [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
1328 public virtual object DeserializeObject(object value, Type type)
1329 {
1330 if (type == null) throw new ArgumentNullException("type");
1331 string str = value as string;
1332
1333 if (type == typeof(Guid) && string.IsNullOrEmpty(str))
1334 return default(Guid);
1335
1336 if (value == null)
1337 return null;
1338
1339 object obj = null;
1340
1341 if (str != null)
1342 {
1343 if (str.Length != 0) // We know it can't be null now.
1344 {
1345 if (type == typeof(DateTime) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTime)))
1346 return DateTime.ParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
1347 if (type == typeof(DateTimeOffset) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTimeOffset)))
1348 return DateTimeOffset.ParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
1349 if (type == typeof(Guid) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid)))
1350 return new Guid(str);
1351 if (type == typeof(Uri))
1352 {
1353 bool isValid = Uri.IsWellFormedUriString(str, UriKind.RelativeOrAbsolute);
1354
1355 Uri result;
1356 if (isValid && Uri.TryCreate(str, UriKind.RelativeOrAbsolute, out result))
1357 return result;
1358
1359 return null;
1360 }
1361
1362 if (type == typeof(string))
1363 return str;
1364
1365 return Convert.ChangeType(str, type, CultureInfo.InvariantCulture);
1366 }
1367 else
1368 {
1369 if (type == typeof(Guid))
1370 obj = default(Guid);
1371 else if (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid))
1372 obj = null;
1373 else
1374 obj = str;
1375 }
1376 // Empty string case
1377 if (!ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid))
1378 return str;
1379 }
1380 else if (value is bool)
1381 return value;
1382
1383 bool valueIsLong = value is long;
1384 bool valueIsDouble = value is double;
1385 if ((valueIsLong && type == typeof(long)) || (valueIsDouble && type == typeof(double)))
1386 return value;
1387 if ((valueIsDouble && type != typeof(double)) || (valueIsLong && type != typeof(long)))
1388 {
1389 obj = type == typeof(int) || type == typeof(long) || type == typeof(double) || type == typeof(float) || type == typeof(bool) || type == typeof(decimal) || type == typeof(byte) || type == typeof(short)
1390 ? Convert.ChangeType(value, type, CultureInfo.InvariantCulture)
1391 : value;
1392 }
1393 else
1394 {
1395 IDictionary<string, object> objects = value as IDictionary<string, object>;
1396 if (objects != null)
1397 {
1398 IDictionary<string, object> jsonObject = objects;
1399
1400 if (ReflectionUtils.IsTypeDictionary(type))
1401 {
1402 // if dictionary then
1403 Type[] types = ReflectionUtils.GetGenericTypeArguments(type);
1404 Type keyType = types[0];
1405 Type valueType = types[1];
1406
1407 Type genericType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType);
1408
1409 IDictionary dict = (IDictionary)ConstructorCache[genericType]();
1410
1411 foreach (KeyValuePair<string, object> kvp in jsonObject)
1412 dict.Add(kvp.Key, DeserializeObject(kvp.Value, valueType));
1413
1414 obj = dict;
1415 }
1416 else
1417 {
1418 if (type == typeof(object))
1419 obj = value;
1420 else
1421 {
1422 obj = ConstructorCache[type]();
1423 foreach (KeyValuePair<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> setter in SetCache[type])
1424 {
1425 object jsonValue;
1426 if (jsonObject.TryGetValue(setter.Key, out jsonValue))
1427 {
1428 jsonValue = DeserializeObject(jsonValue, setter.Value.Key);
1429 setter.Value.Value(obj, jsonValue);
1430 }
1431 }
1432 }
1433 }
1434 }
1435 else
1436 {
1437 IList<object> valueAsList = value as IList<object>;
1438 if (valueAsList != null)
1439 {
1440 IList<object> jsonObject = valueAsList;
1441 IList list = null;
1442
1443 if (type.IsArray)
1444 {
1445 list = (IList)ConstructorCache[type](jsonObject.Count);
1446 int i = 0;
1447 foreach (object o in jsonObject)
1448 list[i++] = DeserializeObject(o, type.GetElementType());
1449 }
1450 else if (ReflectionUtils.IsTypeGenericeCollectionInterface(type) || ReflectionUtils.IsAssignableFrom(typeof(IList), type))
1451 {
1452 Type innerType = ReflectionUtils.GetGenericListElementType(type);
1453 list = (IList)(ConstructorCache[type] ?? ConstructorCache[typeof(List<>).MakeGenericType(innerType)])(jsonObject.Count);
1454 foreach (object o in jsonObject)
1455 list.Add(DeserializeObject(o, innerType));
1456 }
1457 obj = list;
1458 }
1459 }
1460 return obj;
1461 }
1462 if (ReflectionUtils.IsNullableType(type))
1463 return ReflectionUtils.ToNullableType(obj, type);
1464 return obj;
1465 }
1466
1467 protected virtual object SerializeEnum(Enum p)
1468 {
1469 return Convert.ToDouble(p, CultureInfo.InvariantCulture);
1470 }
1471
1472 [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Need to support .NET 2")]
1473 protected virtual bool TrySerializeKnownTypes(object input, out object output)
1474 {
1475 bool returnValue = true;
1476 if (input is DateTime)
1477 output = ((DateTime)input).ToUniversalTime().ToString(Iso8601Format[0], CultureInfo.InvariantCulture);
1478 else if (input is DateTimeOffset)
1479 output = ((DateTimeOffset)input).ToUniversalTime().ToString(Iso8601Format[0], CultureInfo.InvariantCulture);
1480 else if (input is Guid)
1481 output = ((Guid)input).ToString("D");
1482 else if (input is Uri)
1483 output = input.ToString();
1484 else
1485 {
1486 Enum inputEnum = input as Enum;
1487 if (inputEnum != null)
1488 output = SerializeEnum(inputEnum);
1489 else
1490 {
1491 returnValue = false;
1492 output = null;
1493 }
1494 }
1495 return returnValue;
1496 }
1497 [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Need to support .NET 2")]
1498 protected virtual bool TrySerializeUnknownTypes(object input, out object output)
1499 {
1500 if (input == null) throw new ArgumentNullException("input");
1501 output = null;
1502 Type type = input.GetType();
1503 if (type.FullName == null)
1504 return false;
1505 IDictionary<string, object> obj = new JsonObject();
1506 IDictionary<string, ReflectionUtils.GetDelegate> getters = GetCache[type];
1507 foreach (KeyValuePair<string, ReflectionUtils.GetDelegate> getter in getters)
1508 {
1509 if (getter.Value != null)
1510 obj.Add(MapClrMemberNameToJsonFieldName(getter.Key), getter.Value(input));
1511 }
1512 output = obj;
1513 return true;
1514 }
1515 }
1516
1517#if SIMPLE_JSON_DATACONTRACT
1518 [GeneratedCode("simple-json", "1.0.0")]
1519#if SIMPLE_JSON_INTERNAL
1520 internal
1521#else
1522 public
1523#endif
1524 class DataContractJsonSerializerStrategy : PocoJsonSerializerStrategy
1525 {
1526 public DataContractJsonSerializerStrategy()
1527 {
1528 GetCache = new ReflectionUtils.ThreadSafeDictionary<Type, IDictionary<string, ReflectionUtils.GetDelegate>>(GetterValueFactory);
1529 SetCache = new ReflectionUtils.ThreadSafeDictionary<Type, IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>>(SetterValueFactory);
1530 }
1531
1532 internal override IDictionary<string, ReflectionUtils.GetDelegate> GetterValueFactory(Type type)
1533 {
1534 bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null;
1535 if (!hasDataContract)
1536 return base.GetterValueFactory(type);
1537 string jsonKey;
1538 IDictionary<string, ReflectionUtils.GetDelegate> result = new Dictionary<string, ReflectionUtils.GetDelegate>();
1539 foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type))
1540 {
1541 if (propertyInfo.CanRead)
1542 {
1543 MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo);
1544 if (!getMethod.IsStatic && CanAdd(propertyInfo, out jsonKey))
1545 result[jsonKey] = ReflectionUtils.GetGetMethod(propertyInfo);
1546 }
1547 }
1548 foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type))
1549 {
1550 if (!fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey))
1551 result[jsonKey] = ReflectionUtils.GetGetMethod(fieldInfo);
1552 }
1553 return result;
1554 }
1555
1556 internal override IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> SetterValueFactory(Type type)
1557 {
1558 bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null;
1559 if (!hasDataContract)
1560 return base.SetterValueFactory(type);
1561 string jsonKey;
1562 IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> result = new Dictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>();
1563 foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type))
1564 {
1565 if (propertyInfo.CanWrite)
1566 {
1567 MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo);
1568 if (!setMethod.IsStatic && CanAdd(propertyInfo, out jsonKey))
1569 result[jsonKey] = new KeyValuePair<Type, ReflectionUtils.SetDelegate>(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo));
1570 }
1571 }
1572 foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type))
1573 {
1574 if (!fieldInfo.IsInitOnly && !fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey))
1575 result[jsonKey] = new KeyValuePair<Type, ReflectionUtils.SetDelegate>(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo));
1576 }
1577 // todo implement sorting for DATACONTRACT.
1578 return result;
1579 }
1580
1581 private static bool CanAdd(MemberInfo info, out string jsonKey)
1582 {
1583 jsonKey = null;
1584 if (ReflectionUtils.GetAttribute(info, typeof(IgnoreDataMemberAttribute)) != null)
1585 return false;
1586 DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)ReflectionUtils.GetAttribute(info, typeof(DataMemberAttribute));
1587 if (dataMemberAttribute == null)
1588 return false;
1589 jsonKey = string.IsNullOrEmpty(dataMemberAttribute.Name) ? info.Name : dataMemberAttribute.Name;
1590 return true;
1591 }
1592 }
1593
1594#endif
1595
1596 namespace Reflection
1597 {
1598 // This class is meant to be copied into other libraries. So we want to exclude it from Code Analysis rules
1599 // that might be in place in the target project.
1600 [GeneratedCode("reflection-utils", "1.0.0")]
1601#if SIMPLE_JSON_REFLECTION_UTILS_PUBLIC
1602 public
1603#else
1604 internal
1605#endif
1606 class ReflectionUtils
1607 {
1608 private static readonly object[] EmptyObjects = new object[] { };
1609
1610 public delegate object GetDelegate(object source);
1611 public delegate void SetDelegate(object source, object value);
1612 public delegate object ConstructorDelegate(params object[] args);
1613
1614 public delegate TValue ThreadSafeDictionaryValueFactory<TKey, TValue>(TKey key);
1615
1616#if SIMPLE_JSON_TYPEINFO
1617 public static TypeInfo GetTypeInfo(Type type)
1618 {
1619 return type.GetTypeInfo();
1620 }
1621#else
1622 public static Type GetTypeInfo(Type type)
1623 {
1624 return type;
1625 }
1626#endif
1627
1628 public static Attribute GetAttribute(MemberInfo info, Type type)
1629 {
1630#if SIMPLE_JSON_TYPEINFO
1631 if (info == null || type == null || !info.IsDefined(type))
1632 return null;
1633 return info.GetCustomAttribute(type);
1634#else
1635 if (info == null || type == null || !Attribute.IsDefined(info, type))
1636 return null;
1637 return Attribute.GetCustomAttribute(info, type);
1638#endif
1639 }
1640
1641 public static Type GetGenericListElementType(Type type)
1642 {
1643 IEnumerable<Type> interfaces;
1644#if SIMPLE_JSON_TYPEINFO
1645 interfaces = type.GetTypeInfo().ImplementedInterfaces;
1646#else
1647 interfaces = type.GetInterfaces();
1648#endif
1649 foreach (Type implementedInterface in interfaces)
1650 {
1651 if (IsTypeGeneric(implementedInterface) &&
1652 implementedInterface.GetGenericTypeDefinition() == typeof(IList<>))
1653 {
1654 return GetGenericTypeArguments(implementedInterface)[0];
1655 }
1656 }
1657 return GetGenericTypeArguments(type)[0];
1658 }
1659
1660 public static Attribute GetAttribute(Type objectType, Type attributeType)
1661 {
1662
1663#if SIMPLE_JSON_TYPEINFO
1664 if (objectType == null || attributeType == null || !objectType.GetTypeInfo().IsDefined(attributeType))
1665 return null;
1666 return objectType.GetTypeInfo().GetCustomAttribute(attributeType);
1667#else
1668 if (objectType == null || attributeType == null || !Attribute.IsDefined(objectType, attributeType))
1669 return null;
1670 return Attribute.GetCustomAttribute(objectType, attributeType);
1671#endif
1672 }
1673
1674 public static Type[] GetGenericTypeArguments(Type type)
1675 {
1676#if SIMPLE_JSON_TYPEINFO
1677 return type.GetTypeInfo().GenericTypeArguments;
1678#else
1679 return type.GetGenericArguments();
1680#endif
1681 }
1682
1683 public static bool IsTypeGeneric(Type type)
1684 {
1685 return GetTypeInfo(type).IsGenericType;
1686 }
1687
1688 public static bool IsTypeGenericeCollectionInterface(Type type)
1689 {
1690 if (!IsTypeGeneric(type))
1691 return false;
1692
1693 Type genericDefinition = type.GetGenericTypeDefinition();
1694
1695 return (genericDefinition == typeof(IList<>)
1696 || genericDefinition == typeof(ICollection<>)
1697 || genericDefinition == typeof(IEnumerable<>)
1698#if SIMPLE_JSON_READONLY_COLLECTIONS
1699 || genericDefinition == typeof(IReadOnlyCollection<>)
1700 || genericDefinition == typeof(IReadOnlyList<>)
1701#endif
1702 );
1703 }
1704
1705 public static bool IsAssignableFrom(Type type1, Type type2)
1706 {
1707 return GetTypeInfo(type1).IsAssignableFrom(GetTypeInfo(type2));
1708 }
1709
1710 public static bool IsTypeDictionary(Type type)
1711 {
1712#if SIMPLE_JSON_TYPEINFO
1713 if (typeof(IDictionary<,>).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
1714 return true;
1715#else
1716 if (typeof(System.Collections.IDictionary).IsAssignableFrom(type))
1717 return true;
1718#endif
1719 if (!GetTypeInfo(type).IsGenericType)
1720 return false;
1721
1722 Type genericDefinition = type.GetGenericTypeDefinition();
1723 return genericDefinition == typeof(IDictionary<,>);
1724 }
1725
1726 public static bool IsNullableType(Type type)
1727 {
1728 return GetTypeInfo(type).IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
1729 }
1730
1731 public static object ToNullableType(object obj, Type nullableType)
1732 {
1733 return obj == null ? null : Convert.ChangeType(obj, Nullable.GetUnderlyingType(nullableType), CultureInfo.InvariantCulture);
1734 }
1735
1736 public static bool IsValueType(Type type)
1737 {
1738 return GetTypeInfo(type).IsValueType;
1739 }
1740
1741 public static IEnumerable<ConstructorInfo> GetConstructors(Type type)
1742 {
1743#if SIMPLE_JSON_TYPEINFO
1744 return type.GetTypeInfo().DeclaredConstructors;
1745#else
1746 return type.GetConstructors();
1747#endif
1748 }
1749
1750 public static ConstructorInfo GetConstructorInfo(Type type, params Type[] argsType)
1751 {
1752 IEnumerable<ConstructorInfo> constructorInfos = GetConstructors(type);
1753 int i;
1754 bool matches;
1755 foreach (ConstructorInfo constructorInfo in constructorInfos)
1756 {
1757 ParameterInfo[] parameters = constructorInfo.GetParameters();
1758 if (argsType.Length != parameters.Length)
1759 continue;
1760
1761 i = 0;
1762 matches = true;
1763 foreach (ParameterInfo parameterInfo in constructorInfo.GetParameters())
1764 {
1765 if (parameterInfo.ParameterType != argsType[i])
1766 {
1767 matches = false;
1768 break;
1769 }
1770 }
1771
1772 if (matches)
1773 return constructorInfo;
1774 }
1775
1776 return null;
1777 }
1778
1779 public static IEnumerable<PropertyInfo> GetProperties(Type type)
1780 {
1781#if SIMPLE_JSON_TYPEINFO
1782 return type.GetRuntimeProperties();
1783#else
1784 return type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
1785#endif
1786 }
1787
1788 public static IEnumerable<FieldInfo> GetFields(Type type)
1789 {
1790#if SIMPLE_JSON_TYPEINFO
1791 return type.GetRuntimeFields();
1792#else
1793 return type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
1794#endif
1795 }
1796
1797 public static MethodInfo GetGetterMethodInfo(PropertyInfo propertyInfo)
1798 {
1799#if SIMPLE_JSON_TYPEINFO
1800 return propertyInfo.GetMethod;
1801#else
1802 return propertyInfo.GetGetMethod(true);
1803#endif
1804 }
1805
1806 public static MethodInfo GetSetterMethodInfo(PropertyInfo propertyInfo)
1807 {
1808#if SIMPLE_JSON_TYPEINFO
1809 return propertyInfo.SetMethod;
1810#else
1811 return propertyInfo.GetSetMethod(true);
1812#endif
1813 }
1814
1815 public static ConstructorDelegate GetContructor(ConstructorInfo constructorInfo)
1816 {
1817#if SIMPLE_JSON_NO_LINQ_EXPRESSION
1818 return GetConstructorByReflection(constructorInfo);
1819#else
1820 return GetConstructorByExpression(constructorInfo);
1821#endif
1822 }
1823
1824 public static ConstructorDelegate GetContructor(Type type, params Type[] argsType)
1825 {
1826#if SIMPLE_JSON_NO_LINQ_EXPRESSION
1827 return GetConstructorByReflection(type, argsType);
1828#else
1829 return GetConstructorByExpression(type, argsType);
1830#endif
1831 }
1832
1833 public static ConstructorDelegate GetConstructorByReflection(ConstructorInfo constructorInfo)
1834 {
1835 return delegate (object[] args) { return constructorInfo.Invoke(args); };
1836 }
1837
1838 public static ConstructorDelegate GetConstructorByReflection(Type type, params Type[] argsType)
1839 {
1840 ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType);
1841 return constructorInfo == null ? null : GetConstructorByReflection(constructorInfo);
1842 }
1843
1844#if !SIMPLE_JSON_NO_LINQ_EXPRESSION
1845
1846 public static ConstructorDelegate GetConstructorByExpression(ConstructorInfo constructorInfo)
1847 {
1848 ParameterInfo[] paramsInfo = constructorInfo.GetParameters();
1849 ParameterExpression param = Expression.Parameter(typeof(object[]), "args");
1850 Expression[] argsExp = new Expression[paramsInfo.Length];
1851 for (int i = 0; i < paramsInfo.Length; i++)
1852 {
1853 Expression index = Expression.Constant(i);
1854 Type paramType = paramsInfo[i].ParameterType;
1855 Expression paramAccessorExp = Expression.ArrayIndex(param, index);
1856 Expression paramCastExp = Expression.Convert(paramAccessorExp, paramType);
1857 argsExp[i] = paramCastExp;
1858 }
1859 NewExpression newExp = Expression.New(constructorInfo, argsExp);
1860 Expression<Func<object[], object>> lambda = Expression.Lambda<Func<object[], object>>(newExp, param);
1861 Func<object[], object> compiledLambda = lambda.Compile();
1862 return delegate (object[] args) { return compiledLambda(args); };
1863 }
1864
1865 public static ConstructorDelegate GetConstructorByExpression(Type type, params Type[] argsType)
1866 {
1867 ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType);
1868 return constructorInfo == null ? null : GetConstructorByExpression(constructorInfo);
1869 }
1870
1871#endif
1872
1873 public static GetDelegate GetGetMethod(PropertyInfo propertyInfo)
1874 {
1875#if SIMPLE_JSON_NO_LINQ_EXPRESSION
1876 return GetGetMethodByReflection(propertyInfo);
1877#else
1878 return GetGetMethodByExpression(propertyInfo);
1879#endif
1880 }
1881
1882 public static GetDelegate GetGetMethod(FieldInfo fieldInfo)
1883 {
1884#if SIMPLE_JSON_NO_LINQ_EXPRESSION
1885 return GetGetMethodByReflection(fieldInfo);
1886#else
1887 return GetGetMethodByExpression(fieldInfo);
1888#endif
1889 }
1890
1891 public static GetDelegate GetGetMethodByReflection(PropertyInfo propertyInfo)
1892 {
1893 MethodInfo methodInfo = GetGetterMethodInfo(propertyInfo);
1894 return delegate (object source) { return methodInfo.Invoke(source, EmptyObjects); };
1895 }
1896
1897 public static GetDelegate GetGetMethodByReflection(FieldInfo fieldInfo)
1898 {
1899 return delegate (object source) { return fieldInfo.GetValue(source); };
1900 }
1901
1902#if !SIMPLE_JSON_NO_LINQ_EXPRESSION
1903
1904 public static GetDelegate GetGetMethodByExpression(PropertyInfo propertyInfo)
1905 {
1906 MethodInfo getMethodInfo = GetGetterMethodInfo(propertyInfo);
1907 ParameterExpression instance = Expression.Parameter(typeof(object), "instance");
1908 UnaryExpression instanceCast = (!IsValueType(propertyInfo.DeclaringType)) ? Expression.TypeAs(instance, propertyInfo.DeclaringType) : Expression.Convert(instance, propertyInfo.DeclaringType);
1909 Func<object, object> compiled = Expression.Lambda<Func<object, object>>(Expression.TypeAs(Expression.Call(instanceCast, getMethodInfo), typeof(object)), instance).Compile();
1910 return delegate (object source) { return compiled(source); };
1911 }
1912
1913 public static GetDelegate GetGetMethodByExpression(FieldInfo fieldInfo)
1914 {
1915 ParameterExpression instance = Expression.Parameter(typeof(object), "instance");
1916 MemberExpression member = Expression.Field(Expression.Convert(instance, fieldInfo.DeclaringType), fieldInfo);
1917 GetDelegate compiled = Expression.Lambda<GetDelegate>(Expression.Convert(member, typeof(object)), instance).Compile();
1918 return delegate (object source) { return compiled(source); };
1919 }
1920
1921#endif
1922
1923 public static SetDelegate GetSetMethod(PropertyInfo propertyInfo)
1924 {
1925#if SIMPLE_JSON_NO_LINQ_EXPRESSION
1926 return GetSetMethodByReflection(propertyInfo);
1927#else
1928 return GetSetMethodByExpression(propertyInfo);
1929#endif
1930 }
1931
1932 public static SetDelegate GetSetMethod(FieldInfo fieldInfo)
1933 {
1934#if SIMPLE_JSON_NO_LINQ_EXPRESSION
1935 return GetSetMethodByReflection(fieldInfo);
1936#else
1937 return GetSetMethodByExpression(fieldInfo);
1938#endif
1939 }
1940
1941 public static SetDelegate GetSetMethodByReflection(PropertyInfo propertyInfo)
1942 {
1943 MethodInfo methodInfo = GetSetterMethodInfo(propertyInfo);
1944 return delegate (object source, object value) { methodInfo.Invoke(source, new object[] { value }); };
1945 }
1946
1947 public static SetDelegate GetSetMethodByReflection(FieldInfo fieldInfo)
1948 {
1949 return delegate (object source, object value) { fieldInfo.SetValue(source, value); };
1950 }
1951
1952#if !SIMPLE_JSON_NO_LINQ_EXPRESSION
1953
1954 public static SetDelegate GetSetMethodByExpression(PropertyInfo propertyInfo)
1955 {
1956 MethodInfo setMethodInfo = GetSetterMethodInfo(propertyInfo);
1957 ParameterExpression instance = Expression.Parameter(typeof(object), "instance");
1958 ParameterExpression value = Expression.Parameter(typeof(object), "value");
1959 UnaryExpression instanceCast = (!IsValueType(propertyInfo.DeclaringType)) ? Expression.TypeAs(instance, propertyInfo.DeclaringType) : Expression.Convert(instance, propertyInfo.DeclaringType);
1960 UnaryExpression valueCast = (!IsValueType(propertyInfo.PropertyType)) ? Expression.TypeAs(value, propertyInfo.PropertyType) : Expression.Convert(value, propertyInfo.PropertyType);
1961 Action<object, object> compiled = Expression.Lambda<Action<object, object>>(Expression.Call(instanceCast, setMethodInfo, valueCast), new ParameterExpression[] { instance, value }).Compile();
1962 return delegate (object source, object val) { compiled(source, val); };
1963 }
1964
1965 public static SetDelegate GetSetMethodByExpression(FieldInfo fieldInfo)
1966 {
1967 ParameterExpression instance = Expression.Parameter(typeof(object), "instance");
1968 ParameterExpression value = Expression.Parameter(typeof(object), "value");
1969 Action<object, object> compiled = Expression.Lambda<Action<object, object>>(
1970 Assign(Expression.Field(Expression.Convert(instance, fieldInfo.DeclaringType), fieldInfo), Expression.Convert(value, fieldInfo.FieldType)), instance, value).Compile();
1971 return delegate (object source, object val) { compiled(source, val); };
1972 }
1973
1974 public static BinaryExpression Assign(Expression left, Expression right)
1975 {
1976#if SIMPLE_JSON_TYPEINFO
1977 return Expression.Assign(left, right);
1978#else
1979 MethodInfo assign = typeof(Assigner<>).MakeGenericType(left.Type).GetMethod("Assign");
1980 BinaryExpression assignExpr = Expression.Add(left, right, assign);
1981 return assignExpr;
1982#endif
1983 }
1984
1985 private static class Assigner<T>
1986 {
1987 public static T Assign(ref T left, T right)
1988 {
1989 return (left = right);
1990 }
1991 }
1992
1993#endif
1994
1995 public sealed class ThreadSafeDictionary<TKey, TValue> : IDictionary<TKey, TValue>
1996 {
1997 private readonly object _lock = new object();
1998 private readonly ThreadSafeDictionaryValueFactory<TKey, TValue> _valueFactory;
1999 private Dictionary<TKey, TValue> _dictionary;
2000
2001 public ThreadSafeDictionary(ThreadSafeDictionaryValueFactory<TKey, TValue> valueFactory)
2002 {
2003 _valueFactory = valueFactory;
2004 }
2005
2006 private TValue Get(TKey key)
2007 {
2008 if (_dictionary == null)
2009 return AddValue(key);
2010 TValue value;
2011 if (!_dictionary.TryGetValue(key, out value))
2012 return AddValue(key);
2013 return value;
2014 }
2015
2016 private TValue AddValue(TKey key)
2017 {
2018 TValue value = _valueFactory(key);
2019 lock (_lock)
2020 {
2021 if (_dictionary == null)
2022 {
2023 _dictionary = new Dictionary<TKey, TValue>();
2024 _dictionary[key] = value;
2025 }
2026 else
2027 {
2028 TValue val;
2029 if (_dictionary.TryGetValue(key, out val))
2030 return val;
2031 Dictionary<TKey, TValue> dict = new Dictionary<TKey, TValue>(_dictionary);
2032 dict[key] = value;
2033 _dictionary = dict;
2034 }
2035 }
2036 return value;
2037 }
2038
2039 public void Add(TKey key, TValue value)
2040 {
2041 throw new NotImplementedException();
2042 }
2043
2044 public bool ContainsKey(TKey key)
2045 {
2046 return _dictionary.ContainsKey(key);
2047 }
2048
2049 public ICollection<TKey> Keys
2050 {
2051 get { return _dictionary.Keys; }
2052 }
2053
2054 public bool Remove(TKey key)
2055 {
2056 throw new NotImplementedException();
2057 }
2058
2059 public bool TryGetValue(TKey key, out TValue value)
2060 {
2061 value = this[key];
2062 return true;
2063 }
2064
2065 public ICollection<TValue> Values
2066 {
2067 get { return _dictionary.Values; }
2068 }
2069
2070 public TValue this[TKey key]
2071 {
2072 get { return Get(key); }
2073 set { throw new NotImplementedException(); }
2074 }
2075
2076 public void Add(KeyValuePair<TKey, TValue> item)
2077 {
2078 throw new NotImplementedException();
2079 }
2080
2081 public void Clear()
2082 {
2083 throw new NotImplementedException();
2084 }
2085
2086 public bool Contains(KeyValuePair<TKey, TValue> item)
2087 {
2088 throw new NotImplementedException();
2089 }
2090
2091 public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
2092 {
2093 throw new NotImplementedException();
2094 }
2095
2096 public int Count
2097 {
2098 get { return _dictionary.Count; }
2099 }
2100
2101 public bool IsReadOnly
2102 {
2103 get { throw new NotImplementedException(); }
2104 }
2105
2106 public bool Remove(KeyValuePair<TKey, TValue> item)
2107 {
2108 throw new NotImplementedException();
2109 }
2110
2111 public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
2112 {
2113 return _dictionary.GetEnumerator();
2114 }
2115
2116 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
2117 {
2118 return _dictionary.GetEnumerator();
2119 }
2120 }
2121
2122 }
2123 }
2124}
2125// ReSharper restore LoopCanBeConvertedToQuery
2126// ReSharper restore RedundantExplicitArrayCreation
2127// ReSharper restore SuggestUseVarKeywordEvident \ No newline at end of file
diff --git a/src/internal/README.md b/src/internal/README.md
new file mode 100644
index 00000000..da42c09e
--- /dev/null
+++ b/src/internal/README.md
@@ -0,0 +1,2 @@
1# WixBuildTools
2Internal shared props/targets/tools used to build the WiX Toolset
diff --git a/src/internal/TablesAndTuples/ColumnDefinitionEnums.cs b/src/internal/TablesAndTuples/ColumnDefinitionEnums.cs
new file mode 100644
index 00000000..1499500e
--- /dev/null
+++ b/src/internal/TablesAndTuples/ColumnDefinitionEnums.cs
@@ -0,0 +1,56 @@
1namespace TablesAndSymbols
2{
3 public enum ColumnCategory
4 {
5 Unknown,
6 Text,
7 UpperCase,
8 LowerCase,
9 Integer,
10 DoubleInteger,
11 TimeDate,
12 Identifier,
13 Property,
14 Filename,
15 WildCardFilename,
16 Path,
17 Paths,
18 AnyPath,
19 DefaultDir,
20 RegPath,
21 Formatted,
22 Template,
23 Condition,
24 Guid,
25 Version,
26 Language,
27 Binary,
28 CustomSource,
29 Cabinet,
30 Shortcut,
31 FormattedSDDLText,
32 }
33
34 public enum ColumnModularizeType
35 {
36 None,
37 Column,
38 Icon,
39 CompanionFile,
40 Condition,
41 ControlEventArgument,
42 ControlText,
43 Property,
44 SemicolonDelimited,
45 }
46
47 public enum ColumnType
48 {
49 Unknown,
50 String,
51 Localized,
52 Number,
53 Object,
54 Preserved,
55 }
56}
diff --git a/src/internal/TablesAndTuples/Program.cs b/src/internal/TablesAndTuples/Program.cs
new file mode 100644
index 00000000..634acf9d
--- /dev/null
+++ b/src/internal/TablesAndTuples/Program.cs
@@ -0,0 +1,528 @@
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Linq;
5using System.Text;
6using System.Text.RegularExpressions;
7using System.Xml.Linq;
8using SimpleJson;
9
10namespace TablesAndSymbols
11{
12 class Program
13 {
14 static void Main(string[] args)
15 {
16 if (args.Length == 0)
17 {
18 return;
19 }
20 else if (Path.GetExtension(args[0]) == ".xml")
21 {
22 if (args.Length < 2)
23 {
24 Console.WriteLine("Need to specify output json file as well.");
25 return;
26 }
27 if (Path.GetExtension(args[1]) != ".json")
28 {
29 Console.WriteLine("Output needs to be .json");
30 return;
31 }
32
33 string prefix = null;
34 if (args.Length > 2)
35 {
36 prefix = args[2];
37 }
38
39 var csFile = Path.Combine(Path.GetDirectoryName(args[1]), String.Concat(prefix ?? "WindowsInstaller", "TableDefinitions.cs"));
40
41 ReadXmlWriteJson(Path.GetFullPath(args[0]), Path.GetFullPath(args[1]), Path.GetFullPath(csFile), prefix);
42 }
43 else if (Path.GetExtension(args[0]) == ".json")
44 {
45 string prefix = null;
46 if (args.Length < 2)
47 {
48 Console.WriteLine("Need to specify output folder.");
49 return;
50 }
51 else if (args.Length > 2)
52 {
53 prefix = args[2];
54 }
55
56 ReadJsonWriteCs(Path.GetFullPath(args[0]), Path.GetFullPath(args[1]), prefix);
57 }
58 }
59
60 private static void ReadXmlWriteJson(string inputPath, string outputPath, string csOutputPath, string prefix)
61 {
62 var tableDefinitions = ReadXmlWriteCs(inputPath, csOutputPath, prefix);
63
64 var array = new JsonArray();
65
66 foreach (var tableDefinition in tableDefinitions)
67 {
68 if (tableDefinition.Symbolless)
69 {
70 continue;
71 }
72 var symbolType = tableDefinition.SymbolDefinitionName;
73
74 var fields = new JsonArray();
75 var firstField = true;
76
77 foreach (var columnDefinition in tableDefinition.Columns)
78 {
79 if (firstField)
80 {
81 firstField = false;
82 if (tableDefinition.SymbolIdIsPrimaryKey)
83 {
84 continue;
85 }
86 }
87
88 var fieldName = columnDefinition.Name;
89 fieldName = Regex.Replace(fieldName, "^([^_]+)_([^_]*)$", x =>
90 {
91 return $"{x.Groups[2].Value}{x.Groups[1].Value}Ref";
92 });
93 var type = columnDefinition.Type.ToString().ToLower();
94
95 if (type == "localized")
96 {
97 type = "string";
98 }
99 else if (type == "object")
100 {
101 type = "path";
102 }
103 else if (columnDefinition.Type == ColumnType.Number && columnDefinition.Length == 2 &&
104 columnDefinition.MinValue == 0 && columnDefinition.MaxValue == 1)
105 {
106 type = "bool";
107 }
108
109 if (columnDefinition.Type == ColumnType.Number && columnDefinition.Nullable)
110 {
111 type += "?";
112 }
113
114 var field = new JsonObject
115 {
116 { fieldName, type }
117 };
118
119 fields.Add(field);
120 }
121
122 var obj = new JsonObject
123 {
124 { symbolType, fields }
125 };
126 array.Add(obj);
127 }
128
129 array.Sort(CompareSymbolDefinitions);
130
131 var strat = new PocoJsonSerializerStrategy();
132 var json = SimpleJson.SimpleJson.SerializeObject(array, strat);
133
134 Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
135 File.WriteAllText(outputPath, json);
136 }
137
138 private static List<WixTableDefinition> ReadXmlWriteCs(string inputPath, string outputPath, string prefix)
139 {
140 var tableDefinitions = WixTableDefinition.LoadCollection(inputPath);
141 var text = GenerateCsTableDefinitionsFileText(prefix, tableDefinitions);
142 Console.WriteLine("Writing: {0}", outputPath);
143 File.WriteAllText(outputPath, text);
144 return tableDefinitions;
145 }
146
147 private static void ReadJsonWriteCs(string inputPath, string outputFolder, string prefix)
148 {
149 var json = File.ReadAllText(inputPath);
150 var symbols = SimpleJson.SimpleJson.DeserializeObject(json) as JsonArray;
151
152 var symbolNames = new List<string>();
153
154 foreach (var symbolDefinition in symbols.Cast<JsonObject>())
155 {
156 var symbolName = symbolDefinition.Keys.Single();
157 var fields = symbolDefinition.Values.Single() as JsonArray;
158
159 var list = GetFields(fields).ToList();
160
161 symbolNames.Add(symbolName);
162
163 var text = GenerateSymbolFileText(prefix, symbolName, list);
164
165 var pathSymbol = Path.Combine(outputFolder, symbolName + "Symbol.cs");
166 Console.WriteLine("Writing: {0}", pathSymbol);
167 File.WriteAllText(pathSymbol, text);
168 }
169
170 var content = SymbolNamesFileContent(prefix, symbolNames);
171 var pathNames = Path.Combine(outputFolder, String.Concat(prefix, "SymbolDefinitions.cs"));
172 Console.WriteLine("Writing: {0}", pathNames);
173 File.WriteAllText(pathNames, content);
174 }
175
176 private static IEnumerable<(string Name, string Type, string ClrType, string AsFunction)> GetFields(JsonArray fields)
177 {
178 foreach (var field in fields.Cast<JsonObject>())
179 {
180 var fieldName = field.Keys.Single();
181 var fieldType = field.Values.Single() as string;
182
183 var clrType = ConvertToClrType(fieldType);
184 fieldType = ConvertToFieldType(fieldType);
185
186 var asFunction = $"As{(clrType.Contains("?") ? "Nullable" : "")}{fieldType}()";
187
188 yield return (Name: fieldName, Type: fieldType, ClrType: clrType, AsFunction: asFunction);
189 }
190 }
191
192 private static string GenerateCsTableDefinitionsFileText(string prefix, List<WixTableDefinition> tableDefinitions)
193 {
194 var ns = prefix ?? "Data";
195
196 var startClassDef = String.Join(Environment.NewLine,
197 "// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.",
198 "",
199 "namespace WixToolset.{1}",
200 "{",
201 " using WixToolset.Data.WindowsInstaller;",
202 "",
203 " public static class {2}TableDefinitions",
204 " {");
205 var startTableDef = String.Join(Environment.NewLine,
206 " public static readonly TableDefinition {1} = new TableDefinition(",
207 " \"{2}\",",
208 " {3},",
209 " new[]",
210 " {");
211 var columnDef =
212 " new ColumnDefinition(\"{1}\", ColumnType.{2}, {3}, primaryKey: {4}, nullable: {5}, ColumnCategory.{6}";
213 var endColumnsDef = String.Join(Environment.NewLine,
214 " },");
215 var unrealDef =
216 " unreal: true,";
217 var endTableDef = String.Join(Environment.NewLine,
218 " symbolIdIsPrimaryKey: {1}",
219 " );",
220 "");
221 var startAllTablesDef = String.Join(Environment.NewLine,
222 " public static readonly TableDefinition[] All = new[]",
223 " {");
224 var allTableDef =
225 " {1},";
226 var endAllTablesDef =
227 " };";
228 var endClassDef = String.Join(Environment.NewLine,
229 " }",
230 "}");
231
232 var sb = new StringBuilder();
233
234 sb.AppendLine(startClassDef.Replace("{1}", ns).Replace("{2}", prefix));
235 foreach (var tableDefinition in tableDefinitions)
236 {
237 var symbolDefinition = tableDefinition.Symbolless ? "null" : $"{prefix}SymbolDefinitions.{tableDefinition.SymbolDefinitionName}";
238 sb.AppendLine(startTableDef.Replace("{1}", tableDefinition.VariableName).Replace("{2}", tableDefinition.Name).Replace("{3}", symbolDefinition));
239 foreach (var columnDefinition in tableDefinition.Columns)
240 {
241 sb.Append(columnDef.Replace("{1}", columnDefinition.Name).Replace("{2}", columnDefinition.Type.ToString()).Replace("{3}", columnDefinition.Length.ToString())
242 .Replace("{4}", columnDefinition.PrimaryKey.ToString().ToLower()).Replace("{5}", columnDefinition.Nullable.ToString().ToLower()).Replace("{6}", columnDefinition.Category.ToString()));
243 if (columnDefinition.MinValue.HasValue)
244 {
245 sb.AppendFormat(", minValue: {0}", columnDefinition.MinValue.Value);
246 }
247 if (columnDefinition.MaxValue.HasValue)
248 {
249 sb.AppendFormat(", maxValue: {0}", columnDefinition.MaxValue.Value);
250 }
251 if (!String.IsNullOrEmpty(columnDefinition.KeyTable))
252 {
253 sb.AppendFormat(", keyTable: \"{0}\"", columnDefinition.KeyTable);
254 }
255 if (columnDefinition.KeyColumn.HasValue)
256 {
257 sb.AppendFormat(", keyColumn: {0}", columnDefinition.KeyColumn.Value);
258 }
259 if (!String.IsNullOrEmpty(columnDefinition.Possibilities))
260 {
261 sb.AppendFormat(", possibilities: \"{0}\"", columnDefinition.Possibilities);
262 }
263 if (!String.IsNullOrEmpty(columnDefinition.Description))
264 {
265 sb.AppendFormat(", description: \"{0}\"", columnDefinition.Description.Replace("\\", "\\\\").Replace("\"", "\\\""));
266 }
267 if (columnDefinition.ModularizeType.HasValue && columnDefinition.ModularizeType.Value != ColumnModularizeType.None)
268 {
269 sb.AppendFormat(", modularizeType: ColumnModularizeType.{0}", columnDefinition.ModularizeType.ToString());
270 }
271 if (columnDefinition.ForceLocalizable)
272 {
273 sb.Append(", forceLocalizable: true");
274 }
275 if (columnDefinition.UseCData)
276 {
277 sb.Append(", useCData: true");
278 }
279 if (columnDefinition.Unreal)
280 {
281 sb.Append(", unreal: true");
282 }
283 sb.AppendLine("),");
284 }
285 sb.AppendLine(endColumnsDef);
286 if (tableDefinition.Unreal)
287 {
288 sb.AppendLine(unrealDef);
289 }
290 sb.AppendLine(endTableDef.Replace("{1}", tableDefinition.SymbolIdIsPrimaryKey.ToString().ToLower()));
291 }
292 sb.AppendLine(startAllTablesDef);
293 foreach (var tableDefinition in tableDefinitions)
294 {
295 sb.AppendLine(allTableDef.Replace("{1}", tableDefinition.VariableName));
296 }
297 sb.AppendLine(endAllTablesDef);
298 sb.AppendLine(endClassDef);
299
300 return sb.ToString();
301 }
302
303 private static string GenerateSymbolFileText(string prefix, string symbolName, List<(string Name, string Type, string ClrType, string AsFunction)> symbolFields)
304 {
305 var ns = prefix ?? "Data";
306 var toString = String.IsNullOrEmpty(prefix) ? null : ".ToString()";
307
308 var startFileDef = String.Join(Environment.NewLine,
309 "// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.",
310 "",
311 "namespace WixToolset.{2}",
312 "{");
313 var usingDataDef =
314 " using WixToolset.Data;";
315 var startSymbolDef = String.Join(Environment.NewLine,
316 " using WixToolset.{2}.Symbols;",
317 "",
318 " public static partial class {3}SymbolDefinitions",
319 " {",
320 " public static readonly IntermediateSymbolDefinition {1} = new IntermediateSymbolDefinition(",
321 " {3}SymbolDefinitionType.{1}{4},",
322 " new{5}[]",
323 " {");
324 var fieldDef =
325 " new IntermediateFieldDefinition(nameof({1}SymbolFields.{2}), IntermediateFieldType.{3}),";
326 var endSymbolDef = String.Join(Environment.NewLine,
327 " },",
328 " typeof({1}Symbol));",
329 " }",
330 "}",
331 "",
332 "namespace WixToolset.{2}.Symbols",
333 "{");
334 var startEnumDef = String.Join(Environment.NewLine,
335 " public enum {1}SymbolFields",
336 " {");
337 var fieldEnum =
338 " {2},";
339 var startSymbol = String.Join(Environment.NewLine,
340 " }",
341 "",
342 " public class {1}Symbol : IntermediateSymbol",
343 " {",
344 " public {1}Symbol() : base({3}SymbolDefinitions.{1}, null, null)",
345 " {",
346 " }",
347 "",
348 " public {1}Symbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base({3}SymbolDefinitions.{1}, sourceLineNumber, id)",
349 " {",
350 " }",
351 "",
352 " public IntermediateField this[{1}SymbolFields index] => this.Fields[(int)index];");
353 var fieldProp = String.Join(Environment.NewLine,
354 "",
355 " public {4} {2}",
356 " {",
357 " get => {6}this.Fields[(int){1}SymbolFields.{2}]{5};",
358 " set => this.Set((int){1}SymbolFields.{2}, value);",
359 " }");
360 var endSymbol = String.Join(Environment.NewLine,
361 " }",
362 "}");
363
364 var sb = new StringBuilder();
365
366 sb.AppendLine(startFileDef.Replace("{2}", ns));
367 if (ns != "Data")
368 {
369 sb.AppendLine(usingDataDef);
370 }
371 sb.AppendLine(startSymbolDef.Replace("{1}", symbolName).Replace("{2}", ns).Replace("{3}", prefix).Replace("{4}", toString).Replace("{5}", symbolFields.Any() ? null : " IntermediateFieldDefinition"));
372 foreach (var field in symbolFields)
373 {
374 sb.AppendLine(fieldDef.Replace("{1}", symbolName).Replace("{2}", field.Name).Replace("{3}", field.Type));
375 }
376 sb.AppendLine(endSymbolDef.Replace("{1}", symbolName).Replace("{2}", ns).Replace("{3}", prefix));
377 if (ns != "Data")
378 {
379 sb.AppendLine(usingDataDef);
380 sb.AppendLine();
381 }
382 sb.AppendLine(startEnumDef.Replace("{1}", symbolName));
383 foreach (var field in symbolFields)
384 {
385 sb.AppendLine(fieldEnum.Replace("{1}", symbolName).Replace("{2}", field.Name));
386 }
387 sb.AppendLine(startSymbol.Replace("{1}", symbolName).Replace("{2}", ns).Replace("{3}", prefix));
388 foreach (var field in symbolFields)
389 {
390 var useCast = ns == "Data" && field.AsFunction != "AsPath()";
391 var cast = useCast ? $"({field.ClrType})" : null;
392 var asFunction = useCast ? null : $".{field.AsFunction}";
393 sb.AppendLine(fieldProp.Replace("{1}", symbolName).Replace("{2}", field.Name).Replace("{3}", field.Type).Replace("{4}", field.ClrType).Replace("{5}", asFunction).Replace("{6}", cast));
394 }
395 sb.Append(endSymbol);
396
397 return sb.ToString();
398 }
399
400 private static string SymbolNamesFileContent(string prefix, List<string> symbolNames)
401 {
402 var ns = prefix ?? "Data";
403
404 var header = String.Join(Environment.NewLine,
405 "// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.",
406 "",
407 "namespace WixToolset.{2}",
408 "{",
409 " using System;",
410 " using WixToolset.Data;",
411 "",
412 " public enum {3}SymbolDefinitionType",
413 " {");
414 var namesFormat =
415 " {1},";
416 var midpoint = String.Join(Environment.NewLine,
417 " }",
418 "",
419 " public static partial class {3}SymbolDefinitions",
420 " {",
421 " public static readonly Version Version = new Version(\"4.0.0\");",
422 "",
423 " public static IntermediateSymbolDefinition ByName(string name)",
424 " {",
425 " if (!Enum.TryParse(name, out {3}SymbolDefinitionType type))",
426 " {",
427 " return null;",
428 " }",
429 "",
430 " return ByType(type);",
431 " }",
432 "",
433 " public static IntermediateSymbolDefinition ByType({3}SymbolDefinitionType type)",
434 " {",
435 " switch (type)",
436 " {");
437
438 var caseFormat = String.Join(Environment.NewLine,
439 " case {3}SymbolDefinitionType.{1}:",
440 " return {3}SymbolDefinitions.{1};",
441 "");
442
443 var footer = String.Join(Environment.NewLine,
444 " default:",
445 " throw new ArgumentOutOfRangeException(nameof(type));",
446 " }",
447 " }",
448 " }",
449 "}");
450
451 var sb = new StringBuilder();
452
453 sb.AppendLine(header.Replace("{2}", ns).Replace("{3}", prefix));
454 foreach (var symbolName in symbolNames)
455 {
456 sb.AppendLine(namesFormat.Replace("{1}", symbolName).Replace("{2}", ns).Replace("{3}", prefix));
457 }
458 sb.AppendLine(midpoint.Replace("{2}", ns).Replace("{3}", prefix));
459 foreach (var symbolName in symbolNames)
460 {
461 sb.AppendLine(caseFormat.Replace("{1}", symbolName).Replace("{2}", ns).Replace("{3}", prefix));
462 }
463 sb.AppendLine(footer);
464
465 return sb.ToString();
466 }
467
468 private static string ConvertToFieldType(string fieldType)
469 {
470 switch (fieldType.ToLowerInvariant())
471 {
472 case "bool":
473 return "Bool";
474 case "bool?":
475 return "Number";
476
477 case "string":
478 case "preserved":
479 return "String";
480
481 case "number":
482 case "number?":
483 return "Number";
484
485 case "path":
486 return "Path";
487 }
488
489 throw new ArgumentException(fieldType);
490 }
491
492 private static string ConvertToClrType(string fieldType)
493 {
494 switch (fieldType.ToLowerInvariant())
495 {
496 case "bool":
497 return "bool";
498 case "bool?":
499 return "bool?";
500
501 case "string":
502 case "preserved":
503 return "string";
504
505 case "number":
506 return "int";
507 case "number?":
508 return "int?";
509
510 case "path":
511 return "IntermediateFieldPathValue";
512 }
513
514 throw new ArgumentException(fieldType);
515 }
516
517 private static int CompareSymbolDefinitions(object x, object y)
518 {
519 var first = (JsonObject)x;
520 var second = (JsonObject)y;
521
522 var firstType = first.Keys.Single();
523 var secondType = second.Keys.Single();
524
525 return firstType.CompareTo(secondType);
526 }
527 }
528}
diff --git a/src/internal/TablesAndTuples/SimpleJson.cs b/src/internal/TablesAndTuples/SimpleJson.cs
new file mode 100644
index 00000000..3d956f6e
--- /dev/null
+++ b/src/internal/TablesAndTuples/SimpleJson.cs
@@ -0,0 +1,2127 @@
1//-----------------------------------------------------------------------
2// <copyright file="SimpleJson.cs" company="The Outercurve Foundation">
3// Copyright (c) 2011, The Outercurve Foundation.
4//
5// Licensed under the MIT License (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8// http://www.opensource.org/licenses/mit-license.php
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15// </copyright>
16// <author>Nathan Totten (ntotten.com), Jim Zimmerman (jimzimmerman.com) and Prabir Shrestha (prabir.me)</author>
17// <website>https://github.com/facebook-csharp-sdk/simple-json</website>
18//-----------------------------------------------------------------------
19
20// VERSION:
21
22// NOTE: uncomment the following line to make SimpleJson class internal.
23#define SIMPLE_JSON_INTERNAL
24
25// NOTE: uncomment the following line to make JsonArray and JsonObject class internal.
26#define SIMPLE_JSON_OBJARRAYINTERNAL
27
28// NOTE: uncomment the following line to enable dynamic support.
29//#define SIMPLE_JSON_DYNAMIC
30
31// NOTE: uncomment the following line to enable DataContract support.
32//#define SIMPLE_JSON_DATACONTRACT
33
34// NOTE: uncomment the following line to enable IReadOnlyCollection<T> and IReadOnlyList<T> support.
35//#define SIMPLE_JSON_READONLY_COLLECTIONS
36
37// NOTE: uncomment the following line to disable linq expressions/compiled lambda (better performance) instead of method.invoke().
38// define if you are using .net framework <= 3.0 or < WP7.5
39//#define SIMPLE_JSON_NO_LINQ_EXPRESSION
40
41// NOTE: uncomment the following line if you are compiling under Window Metro style application/library.
42// usually already defined in properties
43//#define NETFX_CORE;
44
45// If you are targetting WinStore, WP8 and NET4.5+ PCL make sure to #define SIMPLE_JSON_TYPEINFO;
46
47// original json parsing code from http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html
48
49#if NETFX_CORE
50#define SIMPLE_JSON_TYPEINFO
51#endif
52
53using System;
54using System.CodeDom.Compiler;
55using System.Collections;
56using System.Collections.Generic;
57#if !SIMPLE_JSON_NO_LINQ_EXPRESSION
58using System.Linq.Expressions;
59#endif
60using System.ComponentModel;
61using System.Diagnostics.CodeAnalysis;
62#if SIMPLE_JSON_DYNAMIC
63using System.Dynamic;
64#endif
65using System.Globalization;
66using System.Reflection;
67using System.Runtime.Serialization;
68using System.Text;
69using SimpleJson.Reflection;
70
71// ReSharper disable LoopCanBeConvertedToQuery
72// ReSharper disable RedundantExplicitArrayCreation
73// ReSharper disable SuggestUseVarKeywordEvident
74namespace SimpleJson
75{
76 /// <summary>
77 /// Represents the json array.
78 /// </summary>
79 [GeneratedCode("simple-json", "1.0.0")]
80 [EditorBrowsable(EditorBrowsableState.Never)]
81 [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
82#if SIMPLE_JSON_OBJARRAYINTERNAL
83 internal
84#else
85 public
86#endif
87 class JsonArray : List<object>
88 {
89 /// <summary>
90 /// Initializes a new instance of the <see cref="JsonArray"/> class.
91 /// </summary>
92 public JsonArray() { }
93
94 /// <summary>
95 /// Initializes a new instance of the <see cref="JsonArray"/> class.
96 /// </summary>
97 /// <param name="capacity">The capacity of the json array.</param>
98 public JsonArray(int capacity) : base(capacity) { }
99
100 /// <summary>
101 /// The json representation of the array.
102 /// </summary>
103 /// <returns>The json representation of the array.</returns>
104 public override string ToString()
105 {
106 return SimpleJson.SerializeObject(this) ?? string.Empty;
107 }
108 }
109
110 /// <summary>
111 /// Represents the json object.
112 /// </summary>
113 [GeneratedCode("simple-json", "1.0.0")]
114 [EditorBrowsable(EditorBrowsableState.Never)]
115 [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
116#if SIMPLE_JSON_OBJARRAYINTERNAL
117 internal
118#else
119 public
120#endif
121 class JsonObject :
122#if SIMPLE_JSON_DYNAMIC
123 DynamicObject,
124#endif
125 IDictionary<string, object>
126 {
127 /// <summary>
128 /// The internal member dictionary.
129 /// </summary>
130 private readonly Dictionary<string, object> _members;
131
132 /// <summary>
133 /// Initializes a new instance of <see cref="JsonObject"/>.
134 /// </summary>
135 public JsonObject()
136 {
137 _members = new Dictionary<string, object>();
138 }
139
140 /// <summary>
141 /// Initializes a new instance of <see cref="JsonObject"/>.
142 /// </summary>
143 /// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer`1"/> implementation to use when comparing keys, or null to use the default <see cref="T:System.Collections.Generic.EqualityComparer`1"/> for the type of the key.</param>
144 public JsonObject(IEqualityComparer<string> comparer)
145 {
146 _members = new Dictionary<string, object>(comparer);
147 }
148
149 /// <summary>
150 /// Gets the <see cref="System.Object"/> at the specified index.
151 /// </summary>
152 /// <value></value>
153 public object this[int index]
154 {
155 get { return GetAtIndex(_members, index); }
156 }
157
158 internal static object GetAtIndex(IDictionary<string, object> obj, int index)
159 {
160 if (obj == null)
161 throw new ArgumentNullException("obj");
162 if (index >= obj.Count)
163 throw new ArgumentOutOfRangeException("index");
164 int i = 0;
165 foreach (KeyValuePair<string, object> o in obj)
166 if (i++ == index) return o.Value;
167 return null;
168 }
169
170 /// <summary>
171 /// Adds the specified key.
172 /// </summary>
173 /// <param name="key">The key.</param>
174 /// <param name="value">The value.</param>
175 public void Add(string key, object value)
176 {
177 _members.Add(key, value);
178 }
179
180 /// <summary>
181 /// Determines whether the specified key contains key.
182 /// </summary>
183 /// <param name="key">The key.</param>
184 /// <returns>
185 /// <c>true</c> if the specified key contains key; otherwise, <c>false</c>.
186 /// </returns>
187 public bool ContainsKey(string key)
188 {
189 return _members.ContainsKey(key);
190 }
191
192 /// <summary>
193 /// Gets the keys.
194 /// </summary>
195 /// <value>The keys.</value>
196 public ICollection<string> Keys
197 {
198 get { return _members.Keys; }
199 }
200
201 /// <summary>
202 /// Removes the specified key.
203 /// </summary>
204 /// <param name="key">The key.</param>
205 /// <returns></returns>
206 public bool Remove(string key)
207 {
208 return _members.Remove(key);
209 }
210
211 /// <summary>
212 /// Tries the get value.
213 /// </summary>
214 /// <param name="key">The key.</param>
215 /// <param name="value">The value.</param>
216 /// <returns></returns>
217 public bool TryGetValue(string key, out object value)
218 {
219 return _members.TryGetValue(key, out value);
220 }
221
222 /// <summary>
223 /// Gets the values.
224 /// </summary>
225 /// <value>The values.</value>
226 public ICollection<object> Values
227 {
228 get { return _members.Values; }
229 }
230
231 /// <summary>
232 /// Gets or sets the <see cref="System.Object"/> with the specified key.
233 /// </summary>
234 /// <value></value>
235 public object this[string key]
236 {
237 get { return _members[key]; }
238 set { _members[key] = value; }
239 }
240
241 /// <summary>
242 /// Adds the specified item.
243 /// </summary>
244 /// <param name="item">The item.</param>
245 public void Add(KeyValuePair<string, object> item)
246 {
247 _members.Add(item.Key, item.Value);
248 }
249
250 /// <summary>
251 /// Clears this instance.
252 /// </summary>
253 public void Clear()
254 {
255 _members.Clear();
256 }
257
258 /// <summary>
259 /// Determines whether [contains] [the specified item].
260 /// </summary>
261 /// <param name="item">The item.</param>
262 /// <returns>
263 /// <c>true</c> if [contains] [the specified item]; otherwise, <c>false</c>.
264 /// </returns>
265 public bool Contains(KeyValuePair<string, object> item)
266 {
267 return _members.ContainsKey(item.Key) && _members[item.Key] == item.Value;
268 }
269
270 /// <summary>
271 /// Copies to.
272 /// </summary>
273 /// <param name="array">The array.</param>
274 /// <param name="arrayIndex">Index of the array.</param>
275 public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
276 {
277 if (array == null) throw new ArgumentNullException("array");
278 int num = Count;
279 foreach (KeyValuePair<string, object> kvp in this)
280 {
281 array[arrayIndex++] = kvp;
282 if (--num <= 0)
283 return;
284 }
285 }
286
287 /// <summary>
288 /// Gets the count.
289 /// </summary>
290 /// <value>The count.</value>
291 public int Count
292 {
293 get { return _members.Count; }
294 }
295
296 /// <summary>
297 /// Gets a value indicating whether this instance is read only.
298 /// </summary>
299 /// <value>
300 /// <c>true</c> if this instance is read only; otherwise, <c>false</c>.
301 /// </value>
302 public bool IsReadOnly
303 {
304 get { return false; }
305 }
306
307 /// <summary>
308 /// Removes the specified item.
309 /// </summary>
310 /// <param name="item">The item.</param>
311 /// <returns></returns>
312 public bool Remove(KeyValuePair<string, object> item)
313 {
314 return _members.Remove(item.Key);
315 }
316
317 /// <summary>
318 /// Gets the enumerator.
319 /// </summary>
320 /// <returns></returns>
321 public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
322 {
323 return _members.GetEnumerator();
324 }
325
326 /// <summary>
327 /// Returns an enumerator that iterates through a collection.
328 /// </summary>
329 /// <returns>
330 /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
331 /// </returns>
332 IEnumerator IEnumerable.GetEnumerator()
333 {
334 return _members.GetEnumerator();
335 }
336
337 /// <summary>
338 /// Returns a json <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
339 /// </summary>
340 /// <returns>
341 /// A json <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
342 /// </returns>
343 public override string ToString()
344 {
345 return SimpleJson.SerializeObject(this);
346 }
347
348#if SIMPLE_JSON_DYNAMIC
349 /// <summary>
350 /// Provides implementation for type conversion operations. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for operations that convert an object from one type to another.
351 /// </summary>
352 /// <param name="binder">Provides information about the conversion operation. The binder.Type property provides the type to which the object must be converted. For example, for the statement (String)sampleObject in C# (CType(sampleObject, Type) in Visual Basic), where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, binder.Type returns the <see cref="T:System.String"/> type. The binder.Explicit property provides information about the kind of conversion that occurs. It returns true for explicit conversion and false for implicit conversion.</param>
353 /// <param name="result">The result of the type conversion operation.</param>
354 /// <returns>
355 /// Alwasy returns true.
356 /// </returns>
357 public override bool TryConvert(ConvertBinder binder, out object result)
358 {
359 // <pex>
360 if (binder == null)
361 throw new ArgumentNullException("binder");
362 // </pex>
363 Type targetType = binder.Type;
364
365 if ((targetType == typeof(IEnumerable)) ||
366 (targetType == typeof(IEnumerable<KeyValuePair<string, object>>)) ||
367 (targetType == typeof(IDictionary<string, object>)) ||
368 (targetType == typeof(IDictionary)))
369 {
370 result = this;
371 return true;
372 }
373
374 return base.TryConvert(binder, out result);
375 }
376
377 /// <summary>
378 /// Provides the implementation for operations that delete an object member. This method is not intended for use in C# or Visual Basic.
379 /// </summary>
380 /// <param name="binder">Provides information about the deletion.</param>
381 /// <returns>
382 /// Alwasy returns true.
383 /// </returns>
384 public override bool TryDeleteMember(DeleteMemberBinder binder)
385 {
386 // <pex>
387 if (binder == null)
388 throw new ArgumentNullException("binder");
389 // </pex>
390 return _members.Remove(binder.Name);
391 }
392
393 /// <summary>
394 /// Provides the implementation for operations that get a value by index. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for indexing operations.
395 /// </summary>
396 /// <param name="binder">Provides information about the operation.</param>
397 /// <param name="indexes">The indexes that are used in the operation. For example, for the sampleObject[3] operation in C# (sampleObject(3) in Visual Basic), where sampleObject is derived from the DynamicObject class, <paramref name="indexes"/> is equal to 3.</param>
398 /// <param name="result">The result of the index operation.</param>
399 /// <returns>
400 /// Alwasy returns true.
401 /// </returns>
402 public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
403 {
404 if (indexes == null) throw new ArgumentNullException("indexes");
405 if (indexes.Length == 1)
406 {
407 result = ((IDictionary<string, object>)this)[(string)indexes[0]];
408 return true;
409 }
410 result = null;
411 return true;
412 }
413
414 /// <summary>
415 /// Provides the implementation for operations that get member values. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for operations such as getting a value for a property.
416 /// </summary>
417 /// <param name="binder">Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member on which the dynamic operation is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty) statement, where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive.</param>
418 /// <param name="result">The result of the get operation. For example, if the method is called for a property, you can assign the property value to <paramref name="result"/>.</param>
419 /// <returns>
420 /// Alwasy returns true.
421 /// </returns>
422 public override bool TryGetMember(GetMemberBinder binder, out object result)
423 {
424 object value;
425 if (_members.TryGetValue(binder.Name, out value))
426 {
427 result = value;
428 return true;
429 }
430 result = null;
431 return true;
432 }
433
434 /// <summary>
435 /// Provides the implementation for operations that set a value by index. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for operations that access objects by a specified index.
436 /// </summary>
437 /// <param name="binder">Provides information about the operation.</param>
438 /// <param name="indexes">The indexes that are used in the operation. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, <paramref name="indexes"/> is equal to 3.</param>
439 /// <param name="value">The value to set to the object that has the specified index. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, <paramref name="value"/> is equal to 10.</param>
440 /// <returns>
441 /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.
442 /// </returns>
443 public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
444 {
445 if (indexes == null) throw new ArgumentNullException("indexes");
446 if (indexes.Length == 1)
447 {
448 ((IDictionary<string, object>)this)[(string)indexes[0]] = value;
449 return true;
450 }
451 return base.TrySetIndex(binder, indexes, value);
452 }
453
454 /// <summary>
455 /// Provides the implementation for operations that set member values. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for operations such as setting a value for a property.
456 /// </summary>
457 /// <param name="binder">Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member to which the value is being assigned. For example, for the statement sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive.</param>
458 /// <param name="value">The value to set to the member. For example, for sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, the <paramref name="value"/> is "Test".</param>
459 /// <returns>
460 /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.)
461 /// </returns>
462 public override bool TrySetMember(SetMemberBinder binder, object value)
463 {
464 // <pex>
465 if (binder == null)
466 throw new ArgumentNullException("binder");
467 // </pex>
468 _members[binder.Name] = value;
469 return true;
470 }
471
472 /// <summary>
473 /// Returns the enumeration of all dynamic member names.
474 /// </summary>
475 /// <returns>
476 /// A sequence that contains dynamic member names.
477 /// </returns>
478 public override IEnumerable<string> GetDynamicMemberNames()
479 {
480 foreach (var key in Keys)
481 yield return key;
482 }
483#endif
484 }
485}
486
487namespace SimpleJson
488{
489 /// <summary>
490 /// This class encodes and decodes JSON strings.
491 /// Spec. details, see http://www.json.org/
492 ///
493 /// JSON uses Arrays and Objects. These correspond here to the datatypes JsonArray(IList&lt;object>) and JsonObject(IDictionary&lt;string,object>).
494 /// All numbers are parsed to doubles.
495 /// </summary>
496 [GeneratedCode("simple-json", "1.0.0")]
497#if SIMPLE_JSON_INTERNAL
498 internal
499#else
500 public
501#endif
502 static class SimpleJson
503 {
504 private const int TOKEN_NONE = 0;
505 private const int TOKEN_CURLY_OPEN = 1;
506 private const int TOKEN_CURLY_CLOSE = 2;
507 private const int TOKEN_SQUARED_OPEN = 3;
508 private const int TOKEN_SQUARED_CLOSE = 4;
509 private const int TOKEN_COLON = 5;
510 private const int TOKEN_COMMA = 6;
511 private const int TOKEN_STRING = 7;
512 private const int TOKEN_NUMBER = 8;
513 private const int TOKEN_TRUE = 9;
514 private const int TOKEN_FALSE = 10;
515 private const int TOKEN_NULL = 11;
516 private const int BUILDER_CAPACITY = 2000;
517
518 private static readonly char[] EscapeTable;
519 private static readonly char[] EscapeCharacters = new char[] { '"', '\\', '\b', '\f', '\n', '\r', '\t' };
520 private static readonly string EscapeCharactersString = new string(EscapeCharacters);
521
522 static SimpleJson()
523 {
524 EscapeTable = new char[93];
525 EscapeTable['"'] = '"';
526 EscapeTable['\\'] = '\\';
527 EscapeTable['\b'] = 'b';
528 EscapeTable['\f'] = 'f';
529 EscapeTable['\n'] = 'n';
530 EscapeTable['\r'] = 'r';
531 EscapeTable['\t'] = 't';
532 }
533
534 /// <summary>
535 /// Parses the string json into a value
536 /// </summary>
537 /// <param name="json">A JSON string.</param>
538 /// <returns>An IList&lt;object>, a IDictionary&lt;string,object>, a double, a string, null, true, or false</returns>
539 public static object DeserializeObject(string json)
540 {
541 object obj;
542 if (TryDeserializeObject(json, out obj))
543 return obj;
544 throw new SerializationException("Invalid JSON string");
545 }
546
547 /// <summary>
548 /// Try parsing the json string into a value.
549 /// </summary>
550 /// <param name="json">
551 /// A JSON string.
552 /// </param>
553 /// <param name="obj">
554 /// The object.
555 /// </param>
556 /// <returns>
557 /// Returns true if successfull otherwise false.
558 /// </returns>
559 [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Need to support .NET 2")]
560 public static bool TryDeserializeObject(string json, out object obj)
561 {
562 bool success = true;
563 if (json != null)
564 {
565 char[] charArray = json.ToCharArray();
566 int index = 0;
567 obj = ParseValue(charArray, ref index, ref success);
568 }
569 else
570 obj = null;
571
572 return success;
573 }
574
575 public static object DeserializeObject(string json, Type type, IJsonSerializerStrategy jsonSerializerStrategy)
576 {
577 object jsonObject = DeserializeObject(json);
578 return type == null || jsonObject != null && ReflectionUtils.IsAssignableFrom(jsonObject.GetType(), type)
579 ? jsonObject
580 : (jsonSerializerStrategy ?? CurrentJsonSerializerStrategy).DeserializeObject(jsonObject, type);
581 }
582
583 public static object DeserializeObject(string json, Type type)
584 {
585 return DeserializeObject(json, type, null);
586 }
587
588 public static T DeserializeObject<T>(string json, IJsonSerializerStrategy jsonSerializerStrategy)
589 {
590 return (T)DeserializeObject(json, typeof(T), jsonSerializerStrategy);
591 }
592
593 public static T DeserializeObject<T>(string json)
594 {
595 return (T)DeserializeObject(json, typeof(T), null);
596 }
597
598 /// <summary>
599 /// Converts a IDictionary&lt;string,object> / IList&lt;object> object into a JSON string
600 /// </summary>
601 /// <param name="json">A IDictionary&lt;string,object> / IList&lt;object></param>
602 /// <param name="jsonSerializerStrategy">Serializer strategy to use</param>
603 /// <returns>A JSON encoded string, or null if object 'json' is not serializable</returns>
604 public static string SerializeObject(object json, IJsonSerializerStrategy jsonSerializerStrategy)
605 {
606 StringBuilder builder = new StringBuilder(BUILDER_CAPACITY);
607 bool success = SerializeValue(jsonSerializerStrategy, json, builder);
608 return (success ? builder.ToString() : null);
609 }
610
611 public static string SerializeObject(object json)
612 {
613 return SerializeObject(json, CurrentJsonSerializerStrategy);
614 }
615
616 public static string EscapeToJavascriptString(string jsonString)
617 {
618 if (string.IsNullOrEmpty(jsonString))
619 return jsonString;
620
621 StringBuilder sb = new StringBuilder();
622 char c;
623
624 for (int i = 0; i < jsonString.Length;)
625 {
626 c = jsonString[i++];
627
628 if (c == '\\')
629 {
630 int remainingLength = jsonString.Length - i;
631 if (remainingLength >= 2)
632 {
633 char lookahead = jsonString[i];
634 if (lookahead == '\\')
635 {
636 sb.Append('\\');
637 ++i;
638 }
639 else if (lookahead == '"')
640 {
641 sb.Append("\"");
642 ++i;
643 }
644 else if (lookahead == 't')
645 {
646 sb.Append('\t');
647 ++i;
648 }
649 else if (lookahead == 'b')
650 {
651 sb.Append('\b');
652 ++i;
653 }
654 else if (lookahead == 'n')
655 {
656 sb.Append('\n');
657 ++i;
658 }
659 else if (lookahead == 'r')
660 {
661 sb.Append('\r');
662 ++i;
663 }
664 }
665 }
666 else
667 {
668 sb.Append(c);
669 }
670 }
671 return sb.ToString();
672 }
673
674 static IDictionary<string, object> ParseObject(char[] json, ref int index, ref bool success)
675 {
676 IDictionary<string, object> table = new JsonObject();
677 int token;
678
679 // {
680 NextToken(json, ref index);
681
682 bool done = false;
683 while (!done)
684 {
685 token = LookAhead(json, index);
686 if (token == TOKEN_NONE)
687 {
688 success = false;
689 return null;
690 }
691 else if (token == TOKEN_COMMA)
692 NextToken(json, ref index);
693 else if (token == TOKEN_CURLY_CLOSE)
694 {
695 NextToken(json, ref index);
696 return table;
697 }
698 else
699 {
700 // name
701 string name = ParseString(json, ref index, ref success);
702 if (!success)
703 {
704 success = false;
705 return null;
706 }
707 // :
708 token = NextToken(json, ref index);
709 if (token != TOKEN_COLON)
710 {
711 success = false;
712 return null;
713 }
714 // value
715 object value = ParseValue(json, ref index, ref success);
716 if (!success)
717 {
718 success = false;
719 return null;
720 }
721 table[name] = value;
722 }
723 }
724 return table;
725 }
726
727 static JsonArray ParseArray(char[] json, ref int index, ref bool success)
728 {
729 JsonArray array = new JsonArray();
730
731 // [
732 NextToken(json, ref index);
733
734 bool done = false;
735 while (!done)
736 {
737 int token = LookAhead(json, index);
738 if (token == TOKEN_NONE)
739 {
740 success = false;
741 return null;
742 }
743 else if (token == TOKEN_COMMA)
744 NextToken(json, ref index);
745 else if (token == TOKEN_SQUARED_CLOSE)
746 {
747 NextToken(json, ref index);
748 break;
749 }
750 else
751 {
752 object value = ParseValue(json, ref index, ref success);
753 if (!success)
754 return null;
755 array.Add(value);
756 }
757 }
758 return array;
759 }
760
761 static object ParseValue(char[] json, ref int index, ref bool success)
762 {
763 switch (LookAhead(json, index))
764 {
765 case TOKEN_STRING:
766 return ParseString(json, ref index, ref success);
767 case TOKEN_NUMBER:
768 return ParseNumber(json, ref index, ref success);
769 case TOKEN_CURLY_OPEN:
770 return ParseObject(json, ref index, ref success);
771 case TOKEN_SQUARED_OPEN:
772 return ParseArray(json, ref index, ref success);
773 case TOKEN_TRUE:
774 NextToken(json, ref index);
775 return true;
776 case TOKEN_FALSE:
777 NextToken(json, ref index);
778 return false;
779 case TOKEN_NULL:
780 NextToken(json, ref index);
781 return null;
782 case TOKEN_NONE:
783 break;
784 }
785 success = false;
786 return null;
787 }
788
789 static string ParseString(char[] json, ref int index, ref bool success)
790 {
791 StringBuilder s = new StringBuilder(BUILDER_CAPACITY);
792 char c;
793
794 EatWhitespace(json, ref index);
795
796 // "
797 c = json[index++];
798 bool complete = false;
799 while (!complete)
800 {
801 if (index == json.Length)
802 break;
803
804 c = json[index++];
805 if (c == '"')
806 {
807 complete = true;
808 break;
809 }
810 else if (c == '\\')
811 {
812 if (index == json.Length)
813 break;
814 c = json[index++];
815 if (c == '"')
816 s.Append('"');
817 else if (c == '\\')
818 s.Append('\\');
819 else if (c == '/')
820 s.Append('/');
821 else if (c == 'b')
822 s.Append('\b');
823 else if (c == 'f')
824 s.Append('\f');
825 else if (c == 'n')
826 s.Append('\n');
827 else if (c == 'r')
828 s.Append('\r');
829 else if (c == 't')
830 s.Append('\t');
831 else if (c == 'u')
832 {
833 int remainingLength = json.Length - index;
834 if (remainingLength >= 4)
835 {
836 // parse the 32 bit hex into an integer codepoint
837 uint codePoint;
838 if (!(success = UInt32.TryParse(new string(json, index, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out codePoint)))
839 return "";
840
841 // convert the integer codepoint to a unicode char and add to string
842 if (0xD800 <= codePoint && codePoint <= 0xDBFF) // if high surrogate
843 {
844 index += 4; // skip 4 chars
845 remainingLength = json.Length - index;
846 if (remainingLength >= 6)
847 {
848 uint lowCodePoint;
849 if (new string(json, index, 2) == "\\u" && UInt32.TryParse(new string(json, index + 2, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out lowCodePoint))
850 {
851 if (0xDC00 <= lowCodePoint && lowCodePoint <= 0xDFFF) // if low surrogate
852 {
853 s.Append((char)codePoint);
854 s.Append((char)lowCodePoint);
855 index += 6; // skip 6 chars
856 continue;
857 }
858 }
859 }
860 success = false; // invalid surrogate pair
861 return "";
862 }
863 s.Append(ConvertFromUtf32((int)codePoint));
864 // skip 4 chars
865 index += 4;
866 }
867 else
868 break;
869 }
870 }
871 else
872 s.Append(c);
873 }
874 if (!complete)
875 {
876 success = false;
877 return null;
878 }
879 return s.ToString();
880 }
881
882 private static string ConvertFromUtf32(int utf32)
883 {
884 // http://www.java2s.com/Open-Source/CSharp/2.6.4-mono-.net-core/System/System/Char.cs.htm
885 if (utf32 < 0 || utf32 > 0x10FFFF)
886 throw new ArgumentOutOfRangeException("utf32", "The argument must be from 0 to 0x10FFFF.");
887 if (0xD800 <= utf32 && utf32 <= 0xDFFF)
888 throw new ArgumentOutOfRangeException("utf32", "The argument must not be in surrogate pair range.");
889 if (utf32 < 0x10000)
890 return new string((char)utf32, 1);
891 utf32 -= 0x10000;
892 return new string(new char[] { (char)((utf32 >> 10) + 0xD800), (char)(utf32 % 0x0400 + 0xDC00) });
893 }
894
895 static object ParseNumber(char[] json, ref int index, ref bool success)
896 {
897 EatWhitespace(json, ref index);
898 int lastIndex = GetLastIndexOfNumber(json, index);
899 int charLength = (lastIndex - index) + 1;
900 object returnNumber;
901 string str = new string(json, index, charLength);
902 if (str.IndexOf(".", StringComparison.OrdinalIgnoreCase) != -1 || str.IndexOf("e", StringComparison.OrdinalIgnoreCase) != -1)
903 {
904 double number;
905 success = double.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number);
906 returnNumber = number;
907 }
908 else
909 {
910 long number;
911 success = long.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number);
912 returnNumber = number;
913 }
914 index = lastIndex + 1;
915 return returnNumber;
916 }
917
918 static int GetLastIndexOfNumber(char[] json, int index)
919 {
920 int lastIndex;
921 for (lastIndex = index; lastIndex < json.Length; lastIndex++)
922 if ("0123456789+-.eE".IndexOf(json[lastIndex]) == -1) break;
923 return lastIndex - 1;
924 }
925
926 static void EatWhitespace(char[] json, ref int index)
927 {
928 for (; index < json.Length; index++)
929 if (" \t\n\r\b\f".IndexOf(json[index]) == -1) break;
930 }
931
932 static int LookAhead(char[] json, int index)
933 {
934 int saveIndex = index;
935 return NextToken(json, ref saveIndex);
936 }
937
938 [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
939 static int NextToken(char[] json, ref int index)
940 {
941 EatWhitespace(json, ref index);
942 if (index == json.Length)
943 return TOKEN_NONE;
944 char c = json[index];
945 index++;
946 switch (c)
947 {
948 case '{':
949 return TOKEN_CURLY_OPEN;
950 case '}':
951 return TOKEN_CURLY_CLOSE;
952 case '[':
953 return TOKEN_SQUARED_OPEN;
954 case ']':
955 return TOKEN_SQUARED_CLOSE;
956 case ',':
957 return TOKEN_COMMA;
958 case '"':
959 return TOKEN_STRING;
960 case '0':
961 case '1':
962 case '2':
963 case '3':
964 case '4':
965 case '5':
966 case '6':
967 case '7':
968 case '8':
969 case '9':
970 case '-':
971 return TOKEN_NUMBER;
972 case ':':
973 return TOKEN_COLON;
974 }
975 index--;
976 int remainingLength = json.Length - index;
977 // false
978 if (remainingLength >= 5)
979 {
980 if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' && json[index + 4] == 'e')
981 {
982 index += 5;
983 return TOKEN_FALSE;
984 }
985 }
986 // true
987 if (remainingLength >= 4)
988 {
989 if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e')
990 {
991 index += 4;
992 return TOKEN_TRUE;
993 }
994 }
995 // null
996 if (remainingLength >= 4)
997 {
998 if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l')
999 {
1000 index += 4;
1001 return TOKEN_NULL;
1002 }
1003 }
1004 return TOKEN_NONE;
1005 }
1006
1007 static bool SerializeValue(IJsonSerializerStrategy jsonSerializerStrategy, object value, StringBuilder builder)
1008 {
1009 bool success = true;
1010 string stringValue = value as string;
1011 if (stringValue != null)
1012 success = SerializeString(stringValue, builder);
1013 else
1014 {
1015 IDictionary<string, object> dict = value as IDictionary<string, object>;
1016 if (dict != null)
1017 {
1018 success = SerializeObject(jsonSerializerStrategy, dict.Keys, dict.Values, builder);
1019 }
1020 else
1021 {
1022 IDictionary<string, string> stringDictionary = value as IDictionary<string, string>;
1023 if (stringDictionary != null)
1024 {
1025 success = SerializeObject(jsonSerializerStrategy, stringDictionary.Keys, stringDictionary.Values, builder);
1026 }
1027 else
1028 {
1029 IEnumerable enumerableValue = value as IEnumerable;
1030 if (enumerableValue != null)
1031 success = SerializeArray(jsonSerializerStrategy, enumerableValue, builder);
1032 else if (IsNumeric(value))
1033 success = SerializeNumber(value, builder);
1034 else if (value is bool)
1035 builder.Append((bool)value ? "true" : "false");
1036 else if (value == null)
1037 builder.Append("null");
1038 else
1039 {
1040 object serializedObject;
1041 success = jsonSerializerStrategy.TrySerializeNonPrimitiveObject(value, out serializedObject);
1042 if (success)
1043 SerializeValue(jsonSerializerStrategy, serializedObject, builder);
1044 }
1045 }
1046 }
1047 }
1048 return success;
1049 }
1050
1051 static bool SerializeObject(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable keys, IEnumerable values, StringBuilder builder)
1052 {
1053 builder.Append("{");
1054 IEnumerator ke = keys.GetEnumerator();
1055 IEnumerator ve = values.GetEnumerator();
1056 bool first = true;
1057 while (ke.MoveNext() && ve.MoveNext())
1058 {
1059 object key = ke.Current;
1060 object value = ve.Current;
1061 if (!first)
1062 builder.Append(",");
1063 string stringKey = key as string;
1064 if (stringKey != null)
1065 SerializeString(stringKey, builder);
1066 else
1067 if (!SerializeValue(jsonSerializerStrategy, value, builder)) return false;
1068 builder.Append(":");
1069 if (!SerializeValue(jsonSerializerStrategy, value, builder))
1070 return false;
1071 first = false;
1072 }
1073 builder.Append("}");
1074 return true;
1075 }
1076
1077 static bool SerializeArray(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable anArray, StringBuilder builder)
1078 {
1079 builder.Append("[");
1080 bool first = true;
1081 foreach (object value in anArray)
1082 {
1083 if (!first)
1084 builder.Append(",");
1085 if (!SerializeValue(jsonSerializerStrategy, value, builder))
1086 return false;
1087 first = false;
1088 }
1089 builder.Append("]");
1090 return true;
1091 }
1092
1093 static bool SerializeString(string aString, StringBuilder builder)
1094 {
1095 // Happy path if there's nothing to be escaped. IndexOfAny is highly optimized (and unmanaged)
1096 if (aString.IndexOfAny(EscapeCharacters) == -1)
1097 {
1098 builder.Append('"');
1099 builder.Append(aString);
1100 builder.Append('"');
1101
1102 return true;
1103 }
1104
1105 builder.Append('"');
1106 int safeCharacterCount = 0;
1107 char[] charArray = aString.ToCharArray();
1108
1109 for (int i = 0; i < charArray.Length; i++)
1110 {
1111 char c = charArray[i];
1112
1113 // Non ascii characters are fine, buffer them up and send them to the builder
1114 // in larger chunks if possible. The escape table is a 1:1 translation table
1115 // with \0 [default(char)] denoting a safe character.
1116 if (c >= EscapeTable.Length || EscapeTable[c] == default(char))
1117 {
1118 safeCharacterCount++;
1119 }
1120 else
1121 {
1122 if (safeCharacterCount > 0)
1123 {
1124 builder.Append(charArray, i - safeCharacterCount, safeCharacterCount);
1125 safeCharacterCount = 0;
1126 }
1127
1128 builder.Append('\\');
1129 builder.Append(EscapeTable[c]);
1130 }
1131 }
1132
1133 if (safeCharacterCount > 0)
1134 {
1135 builder.Append(charArray, charArray.Length - safeCharacterCount, safeCharacterCount);
1136 }
1137
1138 builder.Append('"');
1139 return true;
1140 }
1141
1142 static bool SerializeNumber(object number, StringBuilder builder)
1143 {
1144 if (number is long)
1145 builder.Append(((long)number).ToString(CultureInfo.InvariantCulture));
1146 else if (number is ulong)
1147 builder.Append(((ulong)number).ToString(CultureInfo.InvariantCulture));
1148 else if (number is int)
1149 builder.Append(((int)number).ToString(CultureInfo.InvariantCulture));
1150 else if (number is uint)
1151 builder.Append(((uint)number).ToString(CultureInfo.InvariantCulture));
1152 else if (number is decimal)
1153 builder.Append(((decimal)number).ToString(CultureInfo.InvariantCulture));
1154 else if (number is float)
1155 builder.Append(((float)number).ToString(CultureInfo.InvariantCulture));
1156 else
1157 builder.Append(Convert.ToDouble(number, CultureInfo.InvariantCulture).ToString("r", CultureInfo.InvariantCulture));
1158 return true;
1159 }
1160
1161 /// <summary>
1162 /// Determines if a given object is numeric in any way
1163 /// (can be integer, double, null, etc).
1164 /// </summary>
1165 static bool IsNumeric(object value)
1166 {
1167 if (value is sbyte) return true;
1168 if (value is byte) return true;
1169 if (value is short) return true;
1170 if (value is ushort) return true;
1171 if (value is int) return true;
1172 if (value is uint) return true;
1173 if (value is long) return true;
1174 if (value is ulong) return true;
1175 if (value is float) return true;
1176 if (value is double) return true;
1177 if (value is decimal) return true;
1178 return false;
1179 }
1180
1181 private static IJsonSerializerStrategy _currentJsonSerializerStrategy;
1182 public static IJsonSerializerStrategy CurrentJsonSerializerStrategy
1183 {
1184 get
1185 {
1186 return _currentJsonSerializerStrategy ??
1187 (_currentJsonSerializerStrategy =
1188#if SIMPLE_JSON_DATACONTRACT
1189 DataContractJsonSerializerStrategy
1190#else
1191 PocoJsonSerializerStrategy
1192#endif
1193);
1194 }
1195 set
1196 {
1197 _currentJsonSerializerStrategy = value;
1198 }
1199 }
1200
1201 private static PocoJsonSerializerStrategy _pocoJsonSerializerStrategy;
1202 [EditorBrowsable(EditorBrowsableState.Advanced)]
1203 public static PocoJsonSerializerStrategy PocoJsonSerializerStrategy
1204 {
1205 get
1206 {
1207 return _pocoJsonSerializerStrategy ?? (_pocoJsonSerializerStrategy = new PocoJsonSerializerStrategy());
1208 }
1209 }
1210
1211#if SIMPLE_JSON_DATACONTRACT
1212
1213 private static DataContractJsonSerializerStrategy _dataContractJsonSerializerStrategy;
1214 [System.ComponentModel.EditorBrowsable(EditorBrowsableState.Advanced)]
1215 public static DataContractJsonSerializerStrategy DataContractJsonSerializerStrategy
1216 {
1217 get
1218 {
1219 return _dataContractJsonSerializerStrategy ?? (_dataContractJsonSerializerStrategy = new DataContractJsonSerializerStrategy());
1220 }
1221 }
1222
1223#endif
1224 }
1225
1226 [GeneratedCode("simple-json", "1.0.0")]
1227#if SIMPLE_JSON_INTERNAL
1228 internal
1229#else
1230 public
1231#endif
1232 interface IJsonSerializerStrategy
1233 {
1234 [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Need to support .NET 2")]
1235 bool TrySerializeNonPrimitiveObject(object input, out object output);
1236 object DeserializeObject(object value, Type type);
1237 }
1238
1239 [GeneratedCode("simple-json", "1.0.0")]
1240#if SIMPLE_JSON_INTERNAL
1241 internal
1242#else
1243 public
1244#endif
1245 class PocoJsonSerializerStrategy : IJsonSerializerStrategy
1246 {
1247 internal IDictionary<Type, ReflectionUtils.ConstructorDelegate> ConstructorCache;
1248 internal IDictionary<Type, IDictionary<string, ReflectionUtils.GetDelegate>> GetCache;
1249 internal IDictionary<Type, IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>> SetCache;
1250
1251 internal static readonly Type[] EmptyTypes = new Type[0];
1252 internal static readonly Type[] ArrayConstructorParameterTypes = new Type[] { typeof(int) };
1253
1254 private static readonly string[] Iso8601Format = new string[]
1255 {
1256 @"yyyy-MM-dd\THH:mm:ss.FFFFFFF\Z",
1257 @"yyyy-MM-dd\THH:mm:ss\Z",
1258 @"yyyy-MM-dd\THH:mm:ssK"
1259 };
1260
1261 public PocoJsonSerializerStrategy()
1262 {
1263 ConstructorCache = new ReflectionUtils.ThreadSafeDictionary<Type, ReflectionUtils.ConstructorDelegate>(ContructorDelegateFactory);
1264 GetCache = new ReflectionUtils.ThreadSafeDictionary<Type, IDictionary<string, ReflectionUtils.GetDelegate>>(GetterValueFactory);
1265 SetCache = new ReflectionUtils.ThreadSafeDictionary<Type, IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>>(SetterValueFactory);
1266 }
1267
1268 protected virtual string MapClrMemberNameToJsonFieldName(string clrPropertyName)
1269 {
1270 return clrPropertyName;
1271 }
1272
1273 internal virtual ReflectionUtils.ConstructorDelegate ContructorDelegateFactory(Type key)
1274 {
1275 return ReflectionUtils.GetContructor(key, key.IsArray ? ArrayConstructorParameterTypes : EmptyTypes);
1276 }
1277
1278 internal virtual IDictionary<string, ReflectionUtils.GetDelegate> GetterValueFactory(Type type)
1279 {
1280 IDictionary<string, ReflectionUtils.GetDelegate> result = new Dictionary<string, ReflectionUtils.GetDelegate>();
1281 foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type))
1282 {
1283 if (propertyInfo.CanRead)
1284 {
1285 MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo);
1286 if (getMethod.IsStatic || !getMethod.IsPublic)
1287 continue;
1288 result[MapClrMemberNameToJsonFieldName(propertyInfo.Name)] = ReflectionUtils.GetGetMethod(propertyInfo);
1289 }
1290 }
1291 foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type))
1292 {
1293 if (fieldInfo.IsStatic || !fieldInfo.IsPublic)
1294 continue;
1295 result[MapClrMemberNameToJsonFieldName(fieldInfo.Name)] = ReflectionUtils.GetGetMethod(fieldInfo);
1296 }
1297 return result;
1298 }
1299
1300 internal virtual IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> SetterValueFactory(Type type)
1301 {
1302 IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> result = new Dictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>();
1303 foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type))
1304 {
1305 if (propertyInfo.CanWrite)
1306 {
1307 MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo);
1308 if (setMethod.IsStatic || !setMethod.IsPublic)
1309 continue;
1310 result[MapClrMemberNameToJsonFieldName(propertyInfo.Name)] = new KeyValuePair<Type, ReflectionUtils.SetDelegate>(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo));
1311 }
1312 }
1313 foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type))
1314 {
1315 if (fieldInfo.IsInitOnly || fieldInfo.IsStatic || !fieldInfo.IsPublic)
1316 continue;
1317 result[MapClrMemberNameToJsonFieldName(fieldInfo.Name)] = new KeyValuePair<Type, ReflectionUtils.SetDelegate>(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo));
1318 }
1319 return result;
1320 }
1321
1322 public virtual bool TrySerializeNonPrimitiveObject(object input, out object output)
1323 {
1324 return TrySerializeKnownTypes(input, out output) || TrySerializeUnknownTypes(input, out output);
1325 }
1326
1327 [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
1328 public virtual object DeserializeObject(object value, Type type)
1329 {
1330 if (type == null) throw new ArgumentNullException("type");
1331 string str = value as string;
1332
1333 if (type == typeof(Guid) && string.IsNullOrEmpty(str))
1334 return default(Guid);
1335
1336 if (value == null)
1337 return null;
1338
1339 object obj = null;
1340
1341 if (str != null)
1342 {
1343 if (str.Length != 0) // We know it can't be null now.
1344 {
1345 if (type == typeof(DateTime) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTime)))
1346 return DateTime.ParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
1347 if (type == typeof(DateTimeOffset) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTimeOffset)))
1348 return DateTimeOffset.ParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
1349 if (type == typeof(Guid) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid)))
1350 return new Guid(str);
1351 if (type == typeof(Uri))
1352 {
1353 bool isValid = Uri.IsWellFormedUriString(str, UriKind.RelativeOrAbsolute);
1354
1355 Uri result;
1356 if (isValid && Uri.TryCreate(str, UriKind.RelativeOrAbsolute, out result))
1357 return result;
1358
1359 return null;
1360 }
1361
1362 if (type == typeof(string))
1363 return str;
1364
1365 return Convert.ChangeType(str, type, CultureInfo.InvariantCulture);
1366 }
1367 else
1368 {
1369 if (type == typeof(Guid))
1370 obj = default(Guid);
1371 else if (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid))
1372 obj = null;
1373 else
1374 obj = str;
1375 }
1376 // Empty string case
1377 if (!ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid))
1378 return str;
1379 }
1380 else if (value is bool)
1381 return value;
1382
1383 bool valueIsLong = value is long;
1384 bool valueIsDouble = value is double;
1385 if ((valueIsLong && type == typeof(long)) || (valueIsDouble && type == typeof(double)))
1386 return value;
1387 if ((valueIsDouble && type != typeof(double)) || (valueIsLong && type != typeof(long)))
1388 {
1389 obj = type == typeof(int) || type == typeof(long) || type == typeof(double) || type == typeof(float) || type == typeof(bool) || type == typeof(decimal) || type == typeof(byte) || type == typeof(short)
1390 ? Convert.ChangeType(value, type, CultureInfo.InvariantCulture)
1391 : value;
1392 }
1393 else
1394 {
1395 IDictionary<string, object> objects = value as IDictionary<string, object>;
1396 if (objects != null)
1397 {
1398 IDictionary<string, object> jsonObject = objects;
1399
1400 if (ReflectionUtils.IsTypeDictionary(type))
1401 {
1402 // if dictionary then
1403 Type[] types = ReflectionUtils.GetGenericTypeArguments(type);
1404 Type keyType = types[0];
1405 Type valueType = types[1];
1406
1407 Type genericType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType);
1408
1409 IDictionary dict = (IDictionary)ConstructorCache[genericType]();
1410
1411 foreach (KeyValuePair<string, object> kvp in jsonObject)
1412 dict.Add(kvp.Key, DeserializeObject(kvp.Value, valueType));
1413
1414 obj = dict;
1415 }
1416 else
1417 {
1418 if (type == typeof(object))
1419 obj = value;
1420 else
1421 {
1422 obj = ConstructorCache[type]();
1423 foreach (KeyValuePair<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> setter in SetCache[type])
1424 {
1425 object jsonValue;
1426 if (jsonObject.TryGetValue(setter.Key, out jsonValue))
1427 {
1428 jsonValue = DeserializeObject(jsonValue, setter.Value.Key);
1429 setter.Value.Value(obj, jsonValue);
1430 }
1431 }
1432 }
1433 }
1434 }
1435 else
1436 {
1437 IList<object> valueAsList = value as IList<object>;
1438 if (valueAsList != null)
1439 {
1440 IList<object> jsonObject = valueAsList;
1441 IList list = null;
1442
1443 if (type.IsArray)
1444 {
1445 list = (IList)ConstructorCache[type](jsonObject.Count);
1446 int i = 0;
1447 foreach (object o in jsonObject)
1448 list[i++] = DeserializeObject(o, type.GetElementType());
1449 }
1450 else if (ReflectionUtils.IsTypeGenericeCollectionInterface(type) || ReflectionUtils.IsAssignableFrom(typeof(IList), type))
1451 {
1452 Type innerType = ReflectionUtils.GetGenericListElementType(type);
1453 list = (IList)(ConstructorCache[type] ?? ConstructorCache[typeof(List<>).MakeGenericType(innerType)])(jsonObject.Count);
1454 foreach (object o in jsonObject)
1455 list.Add(DeserializeObject(o, innerType));
1456 }
1457 obj = list;
1458 }
1459 }
1460 return obj;
1461 }
1462 if (ReflectionUtils.IsNullableType(type))
1463 return ReflectionUtils.ToNullableType(obj, type);
1464 return obj;
1465 }
1466
1467 protected virtual object SerializeEnum(Enum p)
1468 {
1469 return Convert.ToDouble(p, CultureInfo.InvariantCulture);
1470 }
1471
1472 [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Need to support .NET 2")]
1473 protected virtual bool TrySerializeKnownTypes(object input, out object output)
1474 {
1475 bool returnValue = true;
1476 if (input is DateTime)
1477 output = ((DateTime)input).ToUniversalTime().ToString(Iso8601Format[0], CultureInfo.InvariantCulture);
1478 else if (input is DateTimeOffset)
1479 output = ((DateTimeOffset)input).ToUniversalTime().ToString(Iso8601Format[0], CultureInfo.InvariantCulture);
1480 else if (input is Guid)
1481 output = ((Guid)input).ToString("D");
1482 else if (input is Uri)
1483 output = input.ToString();
1484 else
1485 {
1486 Enum inputEnum = input as Enum;
1487 if (inputEnum != null)
1488 output = SerializeEnum(inputEnum);
1489 else
1490 {
1491 returnValue = false;
1492 output = null;
1493 }
1494 }
1495 return returnValue;
1496 }
1497 [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Need to support .NET 2")]
1498 protected virtual bool TrySerializeUnknownTypes(object input, out object output)
1499 {
1500 if (input == null) throw new ArgumentNullException("input");
1501 output = null;
1502 Type type = input.GetType();
1503 if (type.FullName == null)
1504 return false;
1505 IDictionary<string, object> obj = new JsonObject();
1506 IDictionary<string, ReflectionUtils.GetDelegate> getters = GetCache[type];
1507 foreach (KeyValuePair<string, ReflectionUtils.GetDelegate> getter in getters)
1508 {
1509 if (getter.Value != null)
1510 obj.Add(MapClrMemberNameToJsonFieldName(getter.Key), getter.Value(input));
1511 }
1512 output = obj;
1513 return true;
1514 }
1515 }
1516
1517#if SIMPLE_JSON_DATACONTRACT
1518 [GeneratedCode("simple-json", "1.0.0")]
1519#if SIMPLE_JSON_INTERNAL
1520 internal
1521#else
1522 public
1523#endif
1524 class DataContractJsonSerializerStrategy : PocoJsonSerializerStrategy
1525 {
1526 public DataContractJsonSerializerStrategy()
1527 {
1528 GetCache = new ReflectionUtils.ThreadSafeDictionary<Type, IDictionary<string, ReflectionUtils.GetDelegate>>(GetterValueFactory);
1529 SetCache = new ReflectionUtils.ThreadSafeDictionary<Type, IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>>(SetterValueFactory);
1530 }
1531
1532 internal override IDictionary<string, ReflectionUtils.GetDelegate> GetterValueFactory(Type type)
1533 {
1534 bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null;
1535 if (!hasDataContract)
1536 return base.GetterValueFactory(type);
1537 string jsonKey;
1538 IDictionary<string, ReflectionUtils.GetDelegate> result = new Dictionary<string, ReflectionUtils.GetDelegate>();
1539 foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type))
1540 {
1541 if (propertyInfo.CanRead)
1542 {
1543 MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo);
1544 if (!getMethod.IsStatic && CanAdd(propertyInfo, out jsonKey))
1545 result[jsonKey] = ReflectionUtils.GetGetMethod(propertyInfo);
1546 }
1547 }
1548 foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type))
1549 {
1550 if (!fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey))
1551 result[jsonKey] = ReflectionUtils.GetGetMethod(fieldInfo);
1552 }
1553 return result;
1554 }
1555
1556 internal override IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> SetterValueFactory(Type type)
1557 {
1558 bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null;
1559 if (!hasDataContract)
1560 return base.SetterValueFactory(type);
1561 string jsonKey;
1562 IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> result = new Dictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>();
1563 foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type))
1564 {
1565 if (propertyInfo.CanWrite)
1566 {
1567 MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo);
1568 if (!setMethod.IsStatic && CanAdd(propertyInfo, out jsonKey))
1569 result[jsonKey] = new KeyValuePair<Type, ReflectionUtils.SetDelegate>(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo));
1570 }
1571 }
1572 foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type))
1573 {
1574 if (!fieldInfo.IsInitOnly && !fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey))
1575 result[jsonKey] = new KeyValuePair<Type, ReflectionUtils.SetDelegate>(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo));
1576 }
1577 // todo implement sorting for DATACONTRACT.
1578 return result;
1579 }
1580
1581 private static bool CanAdd(MemberInfo info, out string jsonKey)
1582 {
1583 jsonKey = null;
1584 if (ReflectionUtils.GetAttribute(info, typeof(IgnoreDataMemberAttribute)) != null)
1585 return false;
1586 DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)ReflectionUtils.GetAttribute(info, typeof(DataMemberAttribute));
1587 if (dataMemberAttribute == null)
1588 return false;
1589 jsonKey = string.IsNullOrEmpty(dataMemberAttribute.Name) ? info.Name : dataMemberAttribute.Name;
1590 return true;
1591 }
1592 }
1593
1594#endif
1595
1596 namespace Reflection
1597 {
1598 // This class is meant to be copied into other libraries. So we want to exclude it from Code Analysis rules
1599 // that might be in place in the target project.
1600 [GeneratedCode("reflection-utils", "1.0.0")]
1601#if SIMPLE_JSON_REFLECTION_UTILS_PUBLIC
1602 public
1603#else
1604 internal
1605#endif
1606 class ReflectionUtils
1607 {
1608 private static readonly object[] EmptyObjects = new object[] { };
1609
1610 public delegate object GetDelegate(object source);
1611 public delegate void SetDelegate(object source, object value);
1612 public delegate object ConstructorDelegate(params object[] args);
1613
1614 public delegate TValue ThreadSafeDictionaryValueFactory<TKey, TValue>(TKey key);
1615
1616#if SIMPLE_JSON_TYPEINFO
1617 public static TypeInfo GetTypeInfo(Type type)
1618 {
1619 return type.GetTypeInfo();
1620 }
1621#else
1622 public static Type GetTypeInfo(Type type)
1623 {
1624 return type;
1625 }
1626#endif
1627
1628 public static Attribute GetAttribute(MemberInfo info, Type type)
1629 {
1630#if SIMPLE_JSON_TYPEINFO
1631 if (info == null || type == null || !info.IsDefined(type))
1632 return null;
1633 return info.GetCustomAttribute(type);
1634#else
1635 if (info == null || type == null || !Attribute.IsDefined(info, type))
1636 return null;
1637 return Attribute.GetCustomAttribute(info, type);
1638#endif
1639 }
1640
1641 public static Type GetGenericListElementType(Type type)
1642 {
1643 IEnumerable<Type> interfaces;
1644#if SIMPLE_JSON_TYPEINFO
1645 interfaces = type.GetTypeInfo().ImplementedInterfaces;
1646#else
1647 interfaces = type.GetInterfaces();
1648#endif
1649 foreach (Type implementedInterface in interfaces)
1650 {
1651 if (IsTypeGeneric(implementedInterface) &&
1652 implementedInterface.GetGenericTypeDefinition() == typeof(IList<>))
1653 {
1654 return GetGenericTypeArguments(implementedInterface)[0];
1655 }
1656 }
1657 return GetGenericTypeArguments(type)[0];
1658 }
1659
1660 public static Attribute GetAttribute(Type objectType, Type attributeType)
1661 {
1662
1663#if SIMPLE_JSON_TYPEINFO
1664 if (objectType == null || attributeType == null || !objectType.GetTypeInfo().IsDefined(attributeType))
1665 return null;
1666 return objectType.GetTypeInfo().GetCustomAttribute(attributeType);
1667#else
1668 if (objectType == null || attributeType == null || !Attribute.IsDefined(objectType, attributeType))
1669 return null;
1670 return Attribute.GetCustomAttribute(objectType, attributeType);
1671#endif
1672 }
1673
1674 public static Type[] GetGenericTypeArguments(Type type)
1675 {
1676#if SIMPLE_JSON_TYPEINFO
1677 return type.GetTypeInfo().GenericTypeArguments;
1678#else
1679 return type.GetGenericArguments();
1680#endif
1681 }
1682
1683 public static bool IsTypeGeneric(Type type)
1684 {
1685 return GetTypeInfo(type).IsGenericType;
1686 }
1687
1688 public static bool IsTypeGenericeCollectionInterface(Type type)
1689 {
1690 if (!IsTypeGeneric(type))
1691 return false;
1692
1693 Type genericDefinition = type.GetGenericTypeDefinition();
1694
1695 return (genericDefinition == typeof(IList<>)
1696 || genericDefinition == typeof(ICollection<>)
1697 || genericDefinition == typeof(IEnumerable<>)
1698#if SIMPLE_JSON_READONLY_COLLECTIONS
1699 || genericDefinition == typeof(IReadOnlyCollection<>)
1700 || genericDefinition == typeof(IReadOnlyList<>)
1701#endif
1702 );
1703 }
1704
1705 public static bool IsAssignableFrom(Type type1, Type type2)
1706 {
1707 return GetTypeInfo(type1).IsAssignableFrom(GetTypeInfo(type2));
1708 }
1709
1710 public static bool IsTypeDictionary(Type type)
1711 {
1712#if SIMPLE_JSON_TYPEINFO
1713 if (typeof(IDictionary<,>).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
1714 return true;
1715#else
1716 if (typeof(System.Collections.IDictionary).IsAssignableFrom(type))
1717 return true;
1718#endif
1719 if (!GetTypeInfo(type).IsGenericType)
1720 return false;
1721
1722 Type genericDefinition = type.GetGenericTypeDefinition();
1723 return genericDefinition == typeof(IDictionary<,>);
1724 }
1725
1726 public static bool IsNullableType(Type type)
1727 {
1728 return GetTypeInfo(type).IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
1729 }
1730
1731 public static object ToNullableType(object obj, Type nullableType)
1732 {
1733 return obj == null ? null : Convert.ChangeType(obj, Nullable.GetUnderlyingType(nullableType), CultureInfo.InvariantCulture);
1734 }
1735
1736 public static bool IsValueType(Type type)
1737 {
1738 return GetTypeInfo(type).IsValueType;
1739 }
1740
1741 public static IEnumerable<ConstructorInfo> GetConstructors(Type type)
1742 {
1743#if SIMPLE_JSON_TYPEINFO
1744 return type.GetTypeInfo().DeclaredConstructors;
1745#else
1746 return type.GetConstructors();
1747#endif
1748 }
1749
1750 public static ConstructorInfo GetConstructorInfo(Type type, params Type[] argsType)
1751 {
1752 IEnumerable<ConstructorInfo> constructorInfos = GetConstructors(type);
1753 int i;
1754 bool matches;
1755 foreach (ConstructorInfo constructorInfo in constructorInfos)
1756 {
1757 ParameterInfo[] parameters = constructorInfo.GetParameters();
1758 if (argsType.Length != parameters.Length)
1759 continue;
1760
1761 i = 0;
1762 matches = true;
1763 foreach (ParameterInfo parameterInfo in constructorInfo.GetParameters())
1764 {
1765 if (parameterInfo.ParameterType != argsType[i])
1766 {
1767 matches = false;
1768 break;
1769 }
1770 }
1771
1772 if (matches)
1773 return constructorInfo;
1774 }
1775
1776 return null;
1777 }
1778
1779 public static IEnumerable<PropertyInfo> GetProperties(Type type)
1780 {
1781#if SIMPLE_JSON_TYPEINFO
1782 return type.GetRuntimeProperties();
1783#else
1784 return type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
1785#endif
1786 }
1787
1788 public static IEnumerable<FieldInfo> GetFields(Type type)
1789 {
1790#if SIMPLE_JSON_TYPEINFO
1791 return type.GetRuntimeFields();
1792#else
1793 return type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
1794#endif
1795 }
1796
1797 public static MethodInfo GetGetterMethodInfo(PropertyInfo propertyInfo)
1798 {
1799#if SIMPLE_JSON_TYPEINFO
1800 return propertyInfo.GetMethod;
1801#else
1802 return propertyInfo.GetGetMethod(true);
1803#endif
1804 }
1805
1806 public static MethodInfo GetSetterMethodInfo(PropertyInfo propertyInfo)
1807 {
1808#if SIMPLE_JSON_TYPEINFO
1809 return propertyInfo.SetMethod;
1810#else
1811 return propertyInfo.GetSetMethod(true);
1812#endif
1813 }
1814
1815 public static ConstructorDelegate GetContructor(ConstructorInfo constructorInfo)
1816 {
1817#if SIMPLE_JSON_NO_LINQ_EXPRESSION
1818 return GetConstructorByReflection(constructorInfo);
1819#else
1820 return GetConstructorByExpression(constructorInfo);
1821#endif
1822 }
1823
1824 public static ConstructorDelegate GetContructor(Type type, params Type[] argsType)
1825 {
1826#if SIMPLE_JSON_NO_LINQ_EXPRESSION
1827 return GetConstructorByReflection(type, argsType);
1828#else
1829 return GetConstructorByExpression(type, argsType);
1830#endif
1831 }
1832
1833 public static ConstructorDelegate GetConstructorByReflection(ConstructorInfo constructorInfo)
1834 {
1835 return delegate (object[] args) { return constructorInfo.Invoke(args); };
1836 }
1837
1838 public static ConstructorDelegate GetConstructorByReflection(Type type, params Type[] argsType)
1839 {
1840 ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType);
1841 return constructorInfo == null ? null : GetConstructorByReflection(constructorInfo);
1842 }
1843
1844#if !SIMPLE_JSON_NO_LINQ_EXPRESSION
1845
1846 public static ConstructorDelegate GetConstructorByExpression(ConstructorInfo constructorInfo)
1847 {
1848 ParameterInfo[] paramsInfo = constructorInfo.GetParameters();
1849 ParameterExpression param = Expression.Parameter(typeof(object[]), "args");
1850 Expression[] argsExp = new Expression[paramsInfo.Length];
1851 for (int i = 0; i < paramsInfo.Length; i++)
1852 {
1853 Expression index = Expression.Constant(i);
1854 Type paramType = paramsInfo[i].ParameterType;
1855 Expression paramAccessorExp = Expression.ArrayIndex(param, index);
1856 Expression paramCastExp = Expression.Convert(paramAccessorExp, paramType);
1857 argsExp[i] = paramCastExp;
1858 }
1859 NewExpression newExp = Expression.New(constructorInfo, argsExp);
1860 Expression<Func<object[], object>> lambda = Expression.Lambda<Func<object[], object>>(newExp, param);
1861 Func<object[], object> compiledLambda = lambda.Compile();
1862 return delegate (object[] args) { return compiledLambda(args); };
1863 }
1864
1865 public static ConstructorDelegate GetConstructorByExpression(Type type, params Type[] argsType)
1866 {
1867 ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType);
1868 return constructorInfo == null ? null : GetConstructorByExpression(constructorInfo);
1869 }
1870
1871#endif
1872
1873 public static GetDelegate GetGetMethod(PropertyInfo propertyInfo)
1874 {
1875#if SIMPLE_JSON_NO_LINQ_EXPRESSION
1876 return GetGetMethodByReflection(propertyInfo);
1877#else
1878 return GetGetMethodByExpression(propertyInfo);
1879#endif
1880 }
1881
1882 public static GetDelegate GetGetMethod(FieldInfo fieldInfo)
1883 {
1884#if SIMPLE_JSON_NO_LINQ_EXPRESSION
1885 return GetGetMethodByReflection(fieldInfo);
1886#else
1887 return GetGetMethodByExpression(fieldInfo);
1888#endif
1889 }
1890
1891 public static GetDelegate GetGetMethodByReflection(PropertyInfo propertyInfo)
1892 {
1893 MethodInfo methodInfo = GetGetterMethodInfo(propertyInfo);
1894 return delegate (object source) { return methodInfo.Invoke(source, EmptyObjects); };
1895 }
1896
1897 public static GetDelegate GetGetMethodByReflection(FieldInfo fieldInfo)
1898 {
1899 return delegate (object source) { return fieldInfo.GetValue(source); };
1900 }
1901
1902#if !SIMPLE_JSON_NO_LINQ_EXPRESSION
1903
1904 public static GetDelegate GetGetMethodByExpression(PropertyInfo propertyInfo)
1905 {
1906 MethodInfo getMethodInfo = GetGetterMethodInfo(propertyInfo);
1907 ParameterExpression instance = Expression.Parameter(typeof(object), "instance");
1908 UnaryExpression instanceCast = (!IsValueType(propertyInfo.DeclaringType)) ? Expression.TypeAs(instance, propertyInfo.DeclaringType) : Expression.Convert(instance, propertyInfo.DeclaringType);
1909 Func<object, object> compiled = Expression.Lambda<Func<object, object>>(Expression.TypeAs(Expression.Call(instanceCast, getMethodInfo), typeof(object)), instance).Compile();
1910 return delegate (object source) { return compiled(source); };
1911 }
1912
1913 public static GetDelegate GetGetMethodByExpression(FieldInfo fieldInfo)
1914 {
1915 ParameterExpression instance = Expression.Parameter(typeof(object), "instance");
1916 MemberExpression member = Expression.Field(Expression.Convert(instance, fieldInfo.DeclaringType), fieldInfo);
1917 GetDelegate compiled = Expression.Lambda<GetDelegate>(Expression.Convert(member, typeof(object)), instance).Compile();
1918 return delegate (object source) { return compiled(source); };
1919 }
1920
1921#endif
1922
1923 public static SetDelegate GetSetMethod(PropertyInfo propertyInfo)
1924 {
1925#if SIMPLE_JSON_NO_LINQ_EXPRESSION
1926 return GetSetMethodByReflection(propertyInfo);
1927#else
1928 return GetSetMethodByExpression(propertyInfo);
1929#endif
1930 }
1931
1932 public static SetDelegate GetSetMethod(FieldInfo fieldInfo)
1933 {
1934#if SIMPLE_JSON_NO_LINQ_EXPRESSION
1935 return GetSetMethodByReflection(fieldInfo);
1936#else
1937 return GetSetMethodByExpression(fieldInfo);
1938#endif
1939 }
1940
1941 public static SetDelegate GetSetMethodByReflection(PropertyInfo propertyInfo)
1942 {
1943 MethodInfo methodInfo = GetSetterMethodInfo(propertyInfo);
1944 return delegate (object source, object value) { methodInfo.Invoke(source, new object[] { value }); };
1945 }
1946
1947 public static SetDelegate GetSetMethodByReflection(FieldInfo fieldInfo)
1948 {
1949 return delegate (object source, object value) { fieldInfo.SetValue(source, value); };
1950 }
1951
1952#if !SIMPLE_JSON_NO_LINQ_EXPRESSION
1953
1954 public static SetDelegate GetSetMethodByExpression(PropertyInfo propertyInfo)
1955 {
1956 MethodInfo setMethodInfo = GetSetterMethodInfo(propertyInfo);
1957 ParameterExpression instance = Expression.Parameter(typeof(object), "instance");
1958 ParameterExpression value = Expression.Parameter(typeof(object), "value");
1959 UnaryExpression instanceCast = (!IsValueType(propertyInfo.DeclaringType)) ? Expression.TypeAs(instance, propertyInfo.DeclaringType) : Expression.Convert(instance, propertyInfo.DeclaringType);
1960 UnaryExpression valueCast = (!IsValueType(propertyInfo.PropertyType)) ? Expression.TypeAs(value, propertyInfo.PropertyType) : Expression.Convert(value, propertyInfo.PropertyType);
1961 Action<object, object> compiled = Expression.Lambda<Action<object, object>>(Expression.Call(instanceCast, setMethodInfo, valueCast), new ParameterExpression[] { instance, value }).Compile();
1962 return delegate (object source, object val) { compiled(source, val); };
1963 }
1964
1965 public static SetDelegate GetSetMethodByExpression(FieldInfo fieldInfo)
1966 {
1967 ParameterExpression instance = Expression.Parameter(typeof(object), "instance");
1968 ParameterExpression value = Expression.Parameter(typeof(object), "value");
1969 Action<object, object> compiled = Expression.Lambda<Action<object, object>>(
1970 Assign(Expression.Field(Expression.Convert(instance, fieldInfo.DeclaringType), fieldInfo), Expression.Convert(value, fieldInfo.FieldType)), instance, value).Compile();
1971 return delegate (object source, object val) { compiled(source, val); };
1972 }
1973
1974 public static BinaryExpression Assign(Expression left, Expression right)
1975 {
1976#if SIMPLE_JSON_TYPEINFO
1977 return Expression.Assign(left, right);
1978#else
1979 MethodInfo assign = typeof(Assigner<>).MakeGenericType(left.Type).GetMethod("Assign");
1980 BinaryExpression assignExpr = Expression.Add(left, right, assign);
1981 return assignExpr;
1982#endif
1983 }
1984
1985 private static class Assigner<T>
1986 {
1987 public static T Assign(ref T left, T right)
1988 {
1989 return (left = right);
1990 }
1991 }
1992
1993#endif
1994
1995 public sealed class ThreadSafeDictionary<TKey, TValue> : IDictionary<TKey, TValue>
1996 {
1997 private readonly object _lock = new object();
1998 private readonly ThreadSafeDictionaryValueFactory<TKey, TValue> _valueFactory;
1999 private Dictionary<TKey, TValue> _dictionary;
2000
2001 public ThreadSafeDictionary(ThreadSafeDictionaryValueFactory<TKey, TValue> valueFactory)
2002 {
2003 _valueFactory = valueFactory;
2004 }
2005
2006 private TValue Get(TKey key)
2007 {
2008 if (_dictionary == null)
2009 return AddValue(key);
2010 TValue value;
2011 if (!_dictionary.TryGetValue(key, out value))
2012 return AddValue(key);
2013 return value;
2014 }
2015
2016 private TValue AddValue(TKey key)
2017 {
2018 TValue value = _valueFactory(key);
2019 lock (_lock)
2020 {
2021 if (_dictionary == null)
2022 {
2023 _dictionary = new Dictionary<TKey, TValue>();
2024 _dictionary[key] = value;
2025 }
2026 else
2027 {
2028 TValue val;
2029 if (_dictionary.TryGetValue(key, out val))
2030 return val;
2031 Dictionary<TKey, TValue> dict = new Dictionary<TKey, TValue>(_dictionary);
2032 dict[key] = value;
2033 _dictionary = dict;
2034 }
2035 }
2036 return value;
2037 }
2038
2039 public void Add(TKey key, TValue value)
2040 {
2041 throw new NotImplementedException();
2042 }
2043
2044 public bool ContainsKey(TKey key)
2045 {
2046 return _dictionary.ContainsKey(key);
2047 }
2048
2049 public ICollection<TKey> Keys
2050 {
2051 get { return _dictionary.Keys; }
2052 }
2053
2054 public bool Remove(TKey key)
2055 {
2056 throw new NotImplementedException();
2057 }
2058
2059 public bool TryGetValue(TKey key, out TValue value)
2060 {
2061 value = this[key];
2062 return true;
2063 }
2064
2065 public ICollection<TValue> Values
2066 {
2067 get { return _dictionary.Values; }
2068 }
2069
2070 public TValue this[TKey key]
2071 {
2072 get { return Get(key); }
2073 set { throw new NotImplementedException(); }
2074 }
2075
2076 public void Add(KeyValuePair<TKey, TValue> item)
2077 {
2078 throw new NotImplementedException();
2079 }
2080
2081 public void Clear()
2082 {
2083 throw new NotImplementedException();
2084 }
2085
2086 public bool Contains(KeyValuePair<TKey, TValue> item)
2087 {
2088 throw new NotImplementedException();
2089 }
2090
2091 public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
2092 {
2093 throw new NotImplementedException();
2094 }
2095
2096 public int Count
2097 {
2098 get { return _dictionary.Count; }
2099 }
2100
2101 public bool IsReadOnly
2102 {
2103 get { throw new NotImplementedException(); }
2104 }
2105
2106 public bool Remove(KeyValuePair<TKey, TValue> item)
2107 {
2108 throw new NotImplementedException();
2109 }
2110
2111 public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
2112 {
2113 return _dictionary.GetEnumerator();
2114 }
2115
2116 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
2117 {
2118 return _dictionary.GetEnumerator();
2119 }
2120 }
2121
2122 }
2123 }
2124}
2125// ReSharper restore LoopCanBeConvertedToQuery
2126// ReSharper restore RedundantExplicitArrayCreation
2127// ReSharper restore SuggestUseVarKeywordEvident \ No newline at end of file
diff --git a/src/internal/TablesAndTuples/TablesAndTuples.csproj b/src/internal/TablesAndTuples/TablesAndTuples.csproj
new file mode 100644
index 00000000..ce1697ae
--- /dev/null
+++ b/src/internal/TablesAndTuples/TablesAndTuples.csproj
@@ -0,0 +1,8 @@
1<Project Sdk="Microsoft.NET.Sdk">
2
3 <PropertyGroup>
4 <OutputType>Exe</OutputType>
5 <TargetFramework>netcoreapp2.0</TargetFramework>
6 </PropertyGroup>
7
8</Project>
diff --git a/src/internal/TablesAndTuples/WixColumnDefinition.cs b/src/internal/TablesAndTuples/WixColumnDefinition.cs
new file mode 100644
index 00000000..2d60c9dc
--- /dev/null
+++ b/src/internal/TablesAndTuples/WixColumnDefinition.cs
@@ -0,0 +1,296 @@
1using System;
2using System.Xml;
3
4namespace TablesAndSymbols
5{
6 class WixColumnDefinition
7 {
8 public WixColumnDefinition(string name, ColumnType type, int length, bool primaryKey, bool nullable, ColumnCategory category, long? minValue = null, long? maxValue = null, string keyTable = null, int? keyColumn = null, string possibilities = null, string description = null, ColumnModularizeType? modularizeType = null, bool forceLocalizable = false, bool useCData = false, bool unreal = false)
9 {
10 this.Name = name;
11 this.Type = type;
12 this.Length = length;
13 this.PrimaryKey = primaryKey;
14 this.Nullable = nullable;
15 this.ModularizeType = modularizeType;
16 this.ForceLocalizable = forceLocalizable;
17 this.MinValue = minValue;
18 this.MaxValue = maxValue;
19 this.KeyTable = keyTable;
20 this.KeyColumn = keyColumn;
21 this.Category = category;
22 this.Possibilities = possibilities;
23 this.Description = description;
24 this.UseCData = useCData;
25 this.Unreal = unreal;
26 }
27
28 public string Name { get; }
29 public ColumnType Type { get; }
30 public int Length { get; }
31 public bool PrimaryKey { get; }
32 public bool Nullable { get; }
33 public ColumnModularizeType? ModularizeType { get; }
34 public bool ForceLocalizable { get; }
35 public long? MinValue { get; }
36 public long? MaxValue { get; }
37 public string KeyTable { get; }
38 public int? KeyColumn { get; }
39 public ColumnCategory Category { get; }
40 public string Possibilities { get; }
41 public string Description { get; }
42 public bool UseCData { get; }
43 public bool Unreal { get; }
44
45 internal static WixColumnDefinition Read(XmlReader reader)
46 {
47 if (!reader.LocalName.Equals("columnDefinition"))
48 {
49 throw new XmlException();
50 }
51
52 ColumnCategory category = ColumnCategory.Unknown;
53 string description = null;
54 bool empty = reader.IsEmptyElement;
55 int? keyColumn = null;
56 string keyTable = null;
57 int length = -1;
58 bool localizable = false;
59 long? maxValue = null;
60 long? minValue = null;
61 var modularize = ColumnModularizeType.None;
62 string name = null;
63 bool nullable = false;
64 string possibilities = null;
65 bool primaryKey = false;
66 var type = ColumnType.Unknown;
67 bool useCData = false;
68 bool unreal = false;
69
70 // parse the attributes
71 while (reader.MoveToNextAttribute())
72 {
73 switch (reader.LocalName)
74 {
75 case "category":
76 switch (reader.Value)
77 {
78 case "anyPath":
79 category = ColumnCategory.AnyPath;
80 break;
81 case "binary":
82 category = ColumnCategory.Binary;
83 break;
84 case "cabinet":
85 category = ColumnCategory.Cabinet;
86 break;
87 case "condition":
88 category = ColumnCategory.Condition;
89 break;
90 case "customSource":
91 category = ColumnCategory.CustomSource;
92 break;
93 case "defaultDir":
94 category = ColumnCategory.DefaultDir;
95 break;
96 case "doubleInteger":
97 category = ColumnCategory.DoubleInteger;
98 break;
99 case "filename":
100 category = ColumnCategory.Filename;
101 break;
102 case "formatted":
103 category = ColumnCategory.Formatted;
104 break;
105 case "formattedSddl":
106 category = ColumnCategory.FormattedSDDLText;
107 break;
108 case "guid":
109 category = ColumnCategory.Guid;
110 break;
111 case "identifier":
112 category = ColumnCategory.Identifier;
113 break;
114 case "integer":
115 category = ColumnCategory.Integer;
116 break;
117 case "language":
118 category = ColumnCategory.Language;
119 break;
120 case "lowerCase":
121 category = ColumnCategory.LowerCase;
122 break;
123 case "path":
124 category = ColumnCategory.Path;
125 break;
126 case "paths":
127 category = ColumnCategory.Paths;
128 break;
129 case "property":
130 category = ColumnCategory.Property;
131 break;
132 case "regPath":
133 category = ColumnCategory.RegPath;
134 break;
135 case "shortcut":
136 category = ColumnCategory.Shortcut;
137 break;
138 case "template":
139 category = ColumnCategory.Template;
140 break;
141 case "text":
142 category = ColumnCategory.Text;
143 break;
144 case "timeDate":
145 category = ColumnCategory.TimeDate;
146 break;
147 case "upperCase":
148 category = ColumnCategory.UpperCase;
149 break;
150 case "version":
151 category = ColumnCategory.Version;
152 break;
153 case "wildCardFilename":
154 category = ColumnCategory.WildCardFilename;
155 break;
156 default:
157 throw new InvalidOperationException();
158 }
159 break;
160 case "description":
161 description = reader.Value;
162 break;
163 case "keyColumn":
164 keyColumn = Convert.ToInt32(reader.Value, 10);
165 break;
166 case "keyTable":
167 keyTable = reader.Value;
168 break;
169 case "length":
170 length = Convert.ToInt32(reader.Value, 10);
171 break;
172 case "localizable":
173 localizable = reader.Value.Equals("yes");
174 break;
175 case "maxValue":
176 maxValue = Convert.ToInt32(reader.Value, 10);
177 break;
178 case "minValue":
179 minValue = Convert.ToInt32(reader.Value, 10);
180 break;
181 case "modularize":
182 switch (reader.Value)
183 {
184 case "column":
185 modularize = ColumnModularizeType.Column;
186 break;
187 case "companionFile":
188 modularize = ColumnModularizeType.CompanionFile;
189 break;
190 case "condition":
191 modularize = ColumnModularizeType.Condition;
192 break;
193 case "controlEventArgument":
194 modularize = ColumnModularizeType.ControlEventArgument;
195 break;
196 case "controlText":
197 modularize = ColumnModularizeType.ControlText;
198 break;
199 case "icon":
200 modularize = ColumnModularizeType.Icon;
201 break;
202 case "none":
203 modularize = ColumnModularizeType.None;
204 break;
205 case "property":
206 modularize = ColumnModularizeType.Property;
207 break;
208 case "semicolonDelimited":
209 modularize = ColumnModularizeType.SemicolonDelimited;
210 break;
211 default:
212 throw new XmlException();
213 }
214 break;
215 case "name":
216 switch (reader.Value)
217 {
218 case "CREATE":
219 case "DELETE":
220 case "DROP":
221 case "INSERT":
222 throw new XmlException();
223 default:
224 name = reader.Value;
225 break;
226 }
227 break;
228 case "nullable":
229 nullable = reader.Value.Equals("yes");
230 break;
231 case "primaryKey":
232 primaryKey = reader.Value.Equals("yes");
233 break;
234 case "set":
235 possibilities = reader.Value;
236 break;
237 case "type":
238 switch (reader.Value)
239 {
240 case "localized":
241 type = ColumnType.Localized;
242 break;
243 case "number":
244 type = ColumnType.Number;
245 break;
246 case "object":
247 type = ColumnType.Object;
248 break;
249 case "string":
250 type = ColumnType.String;
251 break;
252 case "preserved":
253 type = ColumnType.Preserved;
254 break;
255 default:
256 throw new XmlException();
257 }
258 break;
259 case "useCData":
260 useCData = reader.Value.Equals("yes");
261 break;
262 case "unreal":
263 unreal = reader.Value.Equals("yes");
264 break;
265 }
266 }
267
268 // parse the child elements (there should be none)
269 if (!empty)
270 {
271 bool done = false;
272
273 while (!done && reader.Read())
274 {
275 switch (reader.NodeType)
276 {
277 case XmlNodeType.Element:
278 throw new XmlException();
279 case XmlNodeType.EndElement:
280 done = true;
281 break;
282 }
283 }
284
285 if (!done)
286 {
287 throw new XmlException();
288 }
289 }
290
291 WixColumnDefinition columnDefinition = new WixColumnDefinition(name, type, length, primaryKey, nullable, category, minValue, maxValue, keyTable, keyColumn, possibilities, description, modularize, localizable, useCData, unreal);
292
293 return columnDefinition;
294 }
295 }
296}
diff --git a/src/internal/TablesAndTuples/WixTableDefinition.cs b/src/internal/TablesAndTuples/WixTableDefinition.cs
new file mode 100644
index 00000000..61dcbb0a
--- /dev/null
+++ b/src/internal/TablesAndTuples/WixTableDefinition.cs
@@ -0,0 +1,169 @@
1using System.Collections.Generic;
2using System.Linq;
3using System.Xml;
4
5namespace TablesAndSymbols
6{
7 class WixTableDefinition
8 {
9 public WixTableDefinition(string name, IEnumerable<WixColumnDefinition> columns, bool unreal, bool symbolless, string symbolDefinitionName, bool? symbolIdIsPrimaryKey)
10 {
11 this.Name = name;
12 this.VariableName = name.Replace("_", "");
13 this.Unreal = unreal;
14 this.Columns = columns?.ToArray();
15 this.Symbolless = symbolless;
16 this.SymbolDefinitionName = symbolless ? null : symbolDefinitionName ?? this.VariableName;
17 this.SymbolIdIsPrimaryKey = symbolIdIsPrimaryKey ?? DeriveSymbolIdIsPrimaryKey(this.Columns);
18 }
19
20 public string Name { get; }
21
22 public string VariableName { get; }
23
24 public string SymbolDefinitionName { get; }
25
26 public bool Unreal { get; }
27
28 public WixColumnDefinition[] Columns { get; }
29
30 public bool SymbolIdIsPrimaryKey { get; }
31
32 public bool Symbolless { get; }
33
34 static WixTableDefinition Read(XmlReader reader)
35 {
36 var empty = reader.IsEmptyElement;
37 string name = null;
38 string symbolDefinitionName = null;
39 var unreal = false;
40 bool? symbolIdIsPrimaryKey = null;
41 var symbolless = false;
42
43 while (reader.MoveToNextAttribute())
44 {
45 switch (reader.LocalName)
46 {
47 case "name":
48 name = reader.Value;
49 break;
50 case "symbolDefinitionName":
51 symbolDefinitionName = reader.Value;
52 break;
53 case "symbolIdIsPrimaryKey":
54 symbolIdIsPrimaryKey = reader.Value.Equals("yes");
55 break;
56 case "symbolless":
57 symbolless = reader.Value.Equals("yes");
58 break;
59 case "unreal":
60 unreal = reader.Value.Equals("yes");
61 break;
62 }
63 }
64
65 if (null == name)
66 {
67 throw new XmlException();
68 }
69
70 var columns = new List<WixColumnDefinition>();
71
72 // parse the child elements
73 if (!empty)
74 {
75 var done = false;
76
77 while (!done && reader.Read())
78 {
79 switch (reader.NodeType)
80 {
81 case XmlNodeType.Element:
82 switch (reader.LocalName)
83 {
84 case "columnDefinition":
85 var columnDefinition = WixColumnDefinition.Read(reader);
86 columns.Add(columnDefinition);
87 break;
88 default:
89 throw new XmlException();
90 }
91 break;
92 case XmlNodeType.EndElement:
93 done = true;
94 break;
95 }
96 }
97
98 if (!done)
99 {
100 throw new XmlException();
101 }
102 }
103
104 return new WixTableDefinition(name, columns.ToArray(), unreal, symbolless, symbolDefinitionName, symbolIdIsPrimaryKey);
105 }
106
107 static bool DeriveSymbolIdIsPrimaryKey(WixColumnDefinition[] columns)
108 {
109 return columns[0].PrimaryKey &&
110 columns[0].Type == ColumnType.String &&
111 columns[0].Category == ColumnCategory.Identifier &&
112 !columns[0].Name.EndsWith("_") &&
113 (columns.Length == 1 || !columns.Skip(1).Any(t => t.PrimaryKey));
114 }
115
116 public static List<WixTableDefinition> LoadCollection(string inputPath)
117 {
118 using (var reader = XmlReader.Create(inputPath))
119 {
120 reader.MoveToContent();
121
122 if ("tableDefinitions" != reader.LocalName)
123 {
124 throw new XmlException();
125 }
126
127 var empty = reader.IsEmptyElement;
128 var tableDefinitions = new List<WixTableDefinition>();
129
130 while (reader.MoveToNextAttribute())
131 {
132 }
133
134 // parse the child elements
135 if (!empty)
136 {
137 var done = false;
138
139 while (!done && reader.Read())
140 {
141 switch (reader.NodeType)
142 {
143 case XmlNodeType.Element:
144 switch (reader.LocalName)
145 {
146 case "tableDefinition":
147 tableDefinitions.Add(WixTableDefinition.Read(reader));
148 break;
149 default:
150 throw new XmlException();
151 }
152 break;
153 case XmlNodeType.EndElement:
154 done = true;
155 break;
156 }
157 }
158
159 if (!done)
160 {
161 throw new XmlException();
162 }
163 }
164
165 return tableDefinitions;
166 }
167 }
168 }
169}
diff --git a/src/internal/WixBuildTools.MsgGen/AssemblyInfo.cs b/src/internal/WixBuildTools.MsgGen/AssemblyInfo.cs
new file mode 100644
index 00000000..378adbf0
--- /dev/null
+++ b/src/internal/WixBuildTools.MsgGen/AssemblyInfo.cs
@@ -0,0 +1,8 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System.Reflection;
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: AssemblyCulture("")]
diff --git a/src/internal/WixBuildTools.MsgGen/GenerateMessageFiles.cs b/src/internal/WixBuildTools.MsgGen/GenerateMessageFiles.cs
new file mode 100644
index 00000000..6f51dbf9
--- /dev/null
+++ b/src/internal/WixBuildTools.MsgGen/GenerateMessageFiles.cs
@@ -0,0 +1,250 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixBuildTools.MsgGen
4{
5 using System;
6 using System.CodeDom;
7 using System.Collections;
8 using System.Globalization;
9 using System.Reflection;
10 using System.Resources;
11 using System.Xml;
12
13 /// <summary>
14 /// Message files generation class.
15 /// </summary>
16 public class GenerateMessageFiles
17 {
18 /// <summary>
19 /// Generate the message files.
20 /// </summary>
21 /// <param name="messagesDoc">Input Xml document containing message definitions.</param>
22 /// <param name="codeCompileUnit">CodeDom container.</param>
23 /// <param name="resourceWriter">Writer for default resource file.</param>
24 public static void Generate(XmlDocument messagesDoc, CodeCompileUnit codeCompileUnit, ResourceWriter resourceWriter)
25 {
26 Hashtable usedNumbers = new Hashtable();
27
28 if (null == messagesDoc)
29 {
30 throw new ArgumentNullException("messagesDoc");
31 }
32
33 if (null == codeCompileUnit)
34 {
35 throw new ArgumentNullException("codeCompileUnit");
36 }
37
38 if (null == resourceWriter)
39 {
40 throw new ArgumentNullException("resourceWriter");
41 }
42
43 string namespaceAttr = messagesDoc.DocumentElement.GetAttribute("Namespace");
44 string resourcesAttr = messagesDoc.DocumentElement.GetAttribute("Resources");
45
46 // namespace
47 CodeNamespace messagesNamespace = new CodeNamespace(namespaceAttr);
48 codeCompileUnit.Namespaces.Add(messagesNamespace);
49
50 // imports
51 messagesNamespace.Imports.Add(new CodeNamespaceImport("System"));
52 messagesNamespace.Imports.Add(new CodeNamespaceImport("System.Reflection"));
53 messagesNamespace.Imports.Add(new CodeNamespaceImport("System.Resources"));
54 if (namespaceAttr != "WixToolset.Data")
55 {
56 messagesNamespace.Imports.Add(new CodeNamespaceImport("WixToolset.Data"));
57 }
58
59 foreach (XmlElement classElement in messagesDoc.DocumentElement.ChildNodes)
60 {
61 string className = classElement.GetAttribute("Name");
62 string baseContainerName = classElement.GetAttribute("BaseContainerName");
63 string containerName = classElement.GetAttribute("ContainerName");
64 string messageLevel = classElement.GetAttribute("Level");
65
66 // message container class
67 messagesNamespace.Types.Add(CreateContainer(namespaceAttr, baseContainerName, containerName, messageLevel, resourcesAttr));
68
69 // class
70 CodeTypeDeclaration messagesClass = new CodeTypeDeclaration(className);
71 messagesClass.TypeAttributes = TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed;
72 messagesNamespace.Types.Add(messagesClass);
73
74 // private constructor (needed since all methods in this class are static)
75 CodeConstructor constructor = new CodeConstructor();
76 constructor.Attributes = MemberAttributes.Private;
77 constructor.ReturnType = null;
78 messagesClass.Members.Add(constructor);
79
80 // messages
81 foreach (XmlElement messageElement in classElement.ChildNodes)
82 {
83 int number;
84 string id = messageElement.GetAttribute("Id");
85 string numberString = messageElement.GetAttribute("Number");
86 bool sourceLineNumbers = true;
87
88 // determine the message number (and ensure it was set properly)
89 if (0 < numberString.Length)
90 {
91 number = Convert.ToInt32(numberString, CultureInfo.InvariantCulture);
92 }
93 else
94 {
95 throw new ApplicationException(String.Format("Message number must be assigned for {0} '{1}'.", containerName, id));
96 }
97
98 // check for message number collisions
99 if (usedNumbers.Contains(number))
100 {
101 throw new ApplicationException(String.Format("Collision detected between two or more messages with number '{0}'.", number));
102 }
103
104 usedNumbers.Add(number, null);
105
106 if ("no" == messageElement.GetAttribute("SourceLineNumbers"))
107 {
108 sourceLineNumbers = false;
109 }
110
111 int instanceCount = 0;
112 foreach (XmlElement instanceElement in messageElement.ChildNodes)
113 {
114 string formatString = instanceElement.InnerText.Trim();
115 string resourceName = String.Concat(className, "_", id, "_", (++instanceCount).ToString());
116
117 // create a resource
118 resourceWriter.AddResource(resourceName, formatString);
119
120 // create method
121 CodeMemberMethod method = new CodeMemberMethod();
122 method.ReturnType = new CodeTypeReference(baseContainerName);
123 method.Attributes = MemberAttributes.Public | MemberAttributes.Static;
124 messagesClass.Members.Add(method);
125
126 // method name
127 method.Name = id;
128
129 // return statement
130 CodeMethodReturnStatement stmt = new CodeMethodReturnStatement();
131 method.Statements.Add(stmt);
132
133 // return statement expression
134 CodeObjectCreateExpression expr = new CodeObjectCreateExpression();
135 stmt.Expression = expr;
136
137 // new struct
138 expr.CreateType = new CodeTypeReference(containerName);
139
140 // optionally have sourceLineNumbers as the first parameter
141 if (sourceLineNumbers)
142 {
143 // sourceLineNumbers parameter
144 expr.Parameters.Add(new CodeArgumentReferenceExpression("sourceLineNumbers"));
145 }
146 else
147 {
148 expr.Parameters.Add(new CodePrimitiveExpression(null));
149 }
150
151 // message number parameter
152 expr.Parameters.Add(new CodePrimitiveExpression(number));
153
154 // resource name parameter
155 expr.Parameters.Add(new CodePrimitiveExpression(resourceName));
156
157 // optionally have sourceLineNumbers as the first parameter
158 if (sourceLineNumbers)
159 {
160 method.Parameters.Add(new CodeParameterDeclarationExpression("SourceLineNumber", "sourceLineNumbers"));
161 }
162
163 foreach (XmlNode parameterNode in instanceElement.ChildNodes)
164 {
165 XmlElement parameterElement;
166
167 if (null != (parameterElement = parameterNode as XmlElement))
168 {
169 string type = parameterElement.GetAttribute("Type");
170 string name = parameterElement.GetAttribute("Name");
171
172 // method parameter
173 method.Parameters.Add(new CodeParameterDeclarationExpression(type, name));
174
175 // String.Format parameter
176 expr.Parameters.Add(new CodeArgumentReferenceExpression(name));
177 }
178 }
179 }
180 }
181 }
182 }
183
184 /// <summary>
185 /// Create message container class.
186 /// </summary>
187 /// <param name="namespaceName">Namespace to use for resources stream.</param>
188 /// <param name="baseContainerName">Name of the base message container class.</param>
189 /// <param name="containerName">Name of the message container class.</param>
190 /// <param name="messageLevel">Message level of for the message.</param>
191 /// <param name="resourcesName">Name of the resources stream (will get namespace prepended).</param>
192 /// <returns>Message container class CodeDom object.</returns>
193 private static CodeTypeDeclaration CreateContainer(string namespaceName, string baseContainerName, string containerName, string messageLevel, string resourcesName)
194 {
195 CodeTypeDeclaration messageContainer = new CodeTypeDeclaration();
196
197 messageContainer.Name = containerName;
198 messageContainer.BaseTypes.Add(new CodeTypeReference(baseContainerName));
199
200 // constructor
201 CodeConstructor constructor = new CodeConstructor();
202 constructor.Attributes = MemberAttributes.Public;
203 constructor.ReturnType = null;
204 messageContainer.Members.Add(constructor);
205
206 CodeMemberField resourceManager = new CodeMemberField();
207 resourceManager.Attributes = MemberAttributes.Private | MemberAttributes.Static;
208 resourceManager.Name = "resourceManager";
209 resourceManager.Type = new CodeTypeReference("ResourceManager");
210 resourceManager.InitExpression = new CodeObjectCreateExpression("ResourceManager", new CodeSnippetExpression(String.Format("\"{0}.{1}\"", namespaceName, resourcesName)), new CodeSnippetExpression("Assembly.GetExecutingAssembly()"));
211 messageContainer.Members.Add(resourceManager);
212
213 // constructor parameters
214 constructor.Parameters.Add(new CodeParameterDeclarationExpression("SourceLineNumber", "sourceLineNumbers"));
215 constructor.Parameters.Add(new CodeParameterDeclarationExpression(typeof(int), "id"));
216 constructor.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "resourceName"));
217 CodeParameterDeclarationExpression messageArgsParam = new CodeParameterDeclarationExpression("params object[]", "messageArgs");
218 constructor.Parameters.Add(messageArgsParam);
219
220 constructor.BaseConstructorArgs.Add(new CodeArgumentReferenceExpression("sourceLineNumbers"));
221 constructor.BaseConstructorArgs.Add(new CodeArgumentReferenceExpression("id"));
222 constructor.BaseConstructorArgs.Add(new CodeArgumentReferenceExpression("resourceName"));
223 constructor.BaseConstructorArgs.Add(new CodeArgumentReferenceExpression("messageArgs"));
224
225 // assign base.Level if messageLevel is specified
226 if (!String.IsNullOrEmpty(messageLevel))
227 {
228 CodePropertyReferenceExpression levelReference = new CodePropertyReferenceExpression(new CodeBaseReferenceExpression(), "Level");
229 CodeFieldReferenceExpression messageLevelField = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("MessageLevel"), messageLevel);
230 constructor.Statements.Add(new CodeAssignStatement(levelReference, messageLevelField));
231 }
232
233 // Assign base.ResourceManager property
234 CodePropertyReferenceExpression baseResourceManagerReference = new CodePropertyReferenceExpression(new CodeBaseReferenceExpression(), "ResourceManager");
235 CodeFieldReferenceExpression resourceManagerField = new CodeFieldReferenceExpression(null, "resourceManager");
236 constructor.Statements.Add(new CodeAssignStatement(baseResourceManagerReference, resourceManagerField));
237
238 //CodeMemberProperty resourceManagerProperty = new CodeMemberProperty();
239 //resourceManagerProperty.Attributes = MemberAttributes.Public | MemberAttributes.Override;
240 //resourceManagerProperty.Name = "ResourceManager";
241 //resourceManagerProperty.Type = new CodeTypeReference("ResourceManager");
242 //CodeFieldReferenceExpression resourceManagerReference = new CodeFieldReferenceExpression();
243 //resourceManagerReference.FieldName = "resourceManager";
244 //resourceManagerProperty.GetStatements.Add(new CodeMethodReturnStatement(resourceManagerReference));
245 //messageContainer.Members.Add(resourceManagerProperty);
246
247 return messageContainer;
248 }
249 }
250}
diff --git a/src/internal/WixBuildTools.MsgGen/MsgGen.cs b/src/internal/WixBuildTools.MsgGen/MsgGen.cs
new file mode 100644
index 00000000..ff4a4a90
--- /dev/null
+++ b/src/internal/WixBuildTools.MsgGen/MsgGen.cs
@@ -0,0 +1,261 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixBuildTools.MsgGen
4{
5 using Microsoft.CSharp;
6 using System;
7 using System.CodeDom;
8 using System.CodeDom.Compiler;
9 using System.Collections;
10 using System.IO;
11 using System.Reflection;
12 using System.Resources;
13 using System.Runtime.InteropServices;
14 using System.Xml;
15 using System.Xml.Schema;
16
17 /// <summary>
18 /// The main entry point for MsgGen.
19 /// </summary>
20 public class MsgGen
21 {
22 /// <summary>
23 /// The main entry point for MsgGen.
24 /// </summary>
25 /// <param name="args">Commandline arguments for the application.</param>
26 /// <returns>Returns the application error code.</returns>
27 [STAThread]
28 public static int Main(string[] args)
29 {
30 try
31 {
32 MsgGenMain msgGen = new MsgGenMain(args);
33 }
34 catch (Exception e)
35 {
36 Console.WriteLine("MsgGen.exe : fatal error MSGG0000: {0}\r\n\r\nStack Trace:\r\n{1}", e.Message, e.StackTrace);
37 if (e is NullReferenceException || e is SEHException)
38 {
39 throw;
40 }
41 return 2;
42 }
43
44 return 0;
45 }
46
47 /// <summary>
48 /// Main class for MsgGen.
49 /// </summary>
50 private class MsgGenMain
51 {
52 private bool showLogo;
53 private bool showHelp;
54
55 private string sourceFile;
56 private string destClassFile;
57 private string destResourcesFile;
58
59 /// <summary>
60 /// Main method for the MsgGen application within the MsgGenMain class.
61 /// </summary>
62 /// <param name="args">Commandline arguments to the application.</param>
63 public MsgGenMain(string[] args)
64 {
65 this.showLogo = true;
66 this.showHelp = false;
67
68 this.sourceFile = null;
69 this.destClassFile = null;
70 this.destResourcesFile = null;
71
72 // parse the command line
73 this.ParseCommandLine(args);
74
75 if (null == this.sourceFile || null == this.destClassFile)
76 {
77 this.showHelp = true;
78 }
79 if (null == this.destResourcesFile)
80 {
81 this.destResourcesFile = Path.ChangeExtension(this.destClassFile, ".resources");
82 }
83
84 // get the assemblies
85 Assembly msgGenAssembly = Assembly.GetExecutingAssembly();
86
87 if (this.showLogo)
88 {
89 Console.WriteLine("Microsoft (R) Message Generation Tool version {0}", msgGenAssembly.GetName().Version.ToString());
90 Console.WriteLine("Copyright (C) Microsoft Corporation 2004. All rights reserved.");
91 Console.WriteLine();
92 }
93 if (this.showHelp)
94 {
95 Console.WriteLine(" usage: MsgGen.exe [-?] [-nologo] sourceFile destClassFile [destResourcesFile]");
96 Console.WriteLine();
97 Console.WriteLine(" -? this help information");
98 Console.WriteLine();
99 Console.WriteLine("For more information see: http://wix.sourceforge.net");
100 return; // exit
101 }
102
103 // load the schema
104 XmlReader reader = null;
105 XmlSchemaCollection schemaCollection = null;
106 try
107 {
108 reader = new XmlTextReader(msgGenAssembly.GetManifestResourceStream("WixBuildTools.MsgGen.Xsd.messages.xsd"));
109 schemaCollection = new XmlSchemaCollection();
110 schemaCollection.Add("http://schemas.microsoft.com/genmsgs/2004/07/messages", reader);
111 }
112 finally
113 {
114 reader.Close();
115 }
116
117 // load the source file and process it
118 using (StreamReader sr = new StreamReader(this.sourceFile))
119 {
120 XmlParserContext context = new XmlParserContext(null, null, null, XmlSpace.None);
121 XmlValidatingReader validatingReader = new XmlValidatingReader(sr.BaseStream, XmlNodeType.Document, context);
122 validatingReader.Schemas.Add(schemaCollection);
123
124 XmlDocument errorsDoc = new XmlDocument();
125 errorsDoc.Load(validatingReader);
126
127 CodeCompileUnit codeCompileUnit = new CodeCompileUnit();
128
129 using (ResourceWriter resourceWriter = new ResourceWriter(this.destResourcesFile))
130 {
131 GenerateMessageFiles.Generate(errorsDoc, codeCompileUnit, resourceWriter);
132
133 GenerateCSharpCode(codeCompileUnit, this.destClassFile);
134 }
135 }
136 }
137
138 /// <summary>
139 /// Generate the actual C# code.
140 /// </summary>
141 /// <param name="codeCompileUnit">The code DOM.</param>
142 /// <param name="destClassFile">Destination C# source file.</param>
143 public static void GenerateCSharpCode(CodeCompileUnit codeCompileUnit, string destClassFile)
144 {
145 // generate the code with the C# code provider
146 CSharpCodeProvider provider = new CSharpCodeProvider();
147
148 // obtain an ICodeGenerator from the CodeDomProvider class
149 ICodeGenerator gen = provider.CreateGenerator();
150
151 // create a TextWriter to a StreamWriter to the output file
152 using (StreamWriter sw = new StreamWriter(destClassFile))
153 {
154 using (IndentedTextWriter tw = new IndentedTextWriter(sw, " "))
155 {
156 CodeGeneratorOptions options = new CodeGeneratorOptions();
157
158 // code generation options
159 options.BlankLinesBetweenMembers = true;
160 options.BracingStyle = "C";
161
162 // generate source code using the code generator
163 gen.GenerateCodeFromCompileUnit(codeCompileUnit, tw, options);
164 }
165 }
166 }
167
168 /// <summary>
169 /// Parse the commandline arguments.
170 /// </summary>
171 /// <param name="args">Commandline arguments.</param>
172 private void ParseCommandLine(string[] args)
173 {
174 for (int i = 0; i < args.Length; ++i)
175 {
176 string arg = args[i];
177 if (null == arg || "" == arg) // skip blank arguments
178 {
179 continue;
180 }
181
182 if ('-' == arg[0] || '/' == arg[0])
183 {
184 string parameter = arg.Substring(1);
185 if ("nologo" == parameter)
186 {
187 this.showLogo = false;
188 }
189 else if ("?" == parameter || "help" == parameter)
190 {
191 this.showHelp = true;
192 }
193 }
194 else if ('@' == arg[0])
195 {
196 using (StreamReader reader = new StreamReader(arg.Substring(1)))
197 {
198 string line;
199 ArrayList newArgs = new ArrayList();
200
201 while (null != (line = reader.ReadLine()))
202 {
203 string newArg = "";
204 bool betweenQuotes = false;
205 for (int j = 0; j < line.Length; ++j)
206 {
207 // skip whitespace
208 if (!betweenQuotes && (' ' == line[j] || '\t' == line[j]))
209 {
210 if ("" != newArg)
211 {
212 newArgs.Add(newArg);
213 newArg = null;
214 }
215
216 continue;
217 }
218
219 // if we're escaping a quote
220 if ('\\' == line[j] && '"' == line[j])
221 {
222 ++j;
223 }
224 else if ('"' == line[j]) // if we've hit a new quote
225 {
226 betweenQuotes = !betweenQuotes;
227 continue;
228 }
229
230 newArg = String.Concat(newArg, line[j]);
231 }
232 if ("" != newArg)
233 {
234 newArgs.Add(newArg);
235 }
236 }
237 string[] ar = (string[])newArgs.ToArray(typeof(string));
238 this.ParseCommandLine(ar);
239 }
240 }
241 else if (null == this.sourceFile)
242 {
243 this.sourceFile = arg;
244 }
245 else if (null == this.destClassFile)
246 {
247 this.destClassFile = arg;
248 }
249 else if (null == this.destResourcesFile)
250 {
251 this.destResourcesFile = arg;
252 }
253 else
254 {
255 throw new ArgumentException(String.Format("Unknown argument '{0}'.", arg));
256 }
257 }
258 }
259 }
260 }
261}
diff --git a/src/internal/WixBuildTools.MsgGen/WixBuildTools.MsgGen.csproj b/src/internal/WixBuildTools.MsgGen/WixBuildTools.MsgGen.csproj
new file mode 100644
index 00000000..954ffa59
--- /dev/null
+++ b/src/internal/WixBuildTools.MsgGen/WixBuildTools.MsgGen.csproj
@@ -0,0 +1,27 @@
1<Project Sdk="Microsoft.NET.Sdk">
2
3 <PropertyGroup>
4 <OutputType>Exe</OutputType>
5 <TargetFramework>net461</TargetFramework>
6 <IsTool>true</IsTool>
7 <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
8 <DebugType>embedded</DebugType>
9 <PublishRepositoryUrl>true</PublishRepositoryUrl>
10 <NoWarn>CS0618</NoWarn>
11 </PropertyGroup>
12
13 <ItemGroup>
14 <Content Include="build\WixBuildTools.MsgGen.targets" PackagePath="build\" />
15 <Content Include="buildCrossTargeting\WixBuildTools.MsgGen.targets" PackagePath="buildCrossTargeting\" />
16 </ItemGroup>
17
18 <ItemGroup>
19 <EmbeddedResource Include="Xsd\messages.xsd" />
20 </ItemGroup>
21
22 <ItemGroup>
23 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
24 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
25 </ItemGroup>
26
27</Project>
diff --git a/src/internal/WixBuildTools.MsgGen/Xsd/messages.xsd b/src/internal/WixBuildTools.MsgGen/Xsd/messages.xsd
new file mode 100644
index 00000000..fd086502
--- /dev/null
+++ b/src/internal/WixBuildTools.MsgGen/Xsd/messages.xsd
@@ -0,0 +1,101 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
6 targetNamespace="http://schemas.microsoft.com/genmsgs/2004/07/messages"
7 xmlns="http://schemas.microsoft.com/genmsgs/2004/07/messages">
8 <xs:annotation>
9 <xs:documentation>
10 Schema for describing any kind of messages.
11 </xs:documentation>
12 </xs:annotation>
13
14 <xs:element name="Messages">
15 <xs:complexType>
16 <xs:sequence maxOccurs="unbounded">
17 <xs:element ref="Class"/>
18 </xs:sequence>
19 <xs:attribute name="Namespace" type="xs:string" use="required">
20 <xs:annotation><xs:documentation>Namespace of the generated class.</xs:documentation></xs:annotation>
21 </xs:attribute>
22 <xs:attribute name="Resources" type="xs:string" use="required">
23 <xs:annotation><xs:documentation>Resources stream for messages. Will get namespace prepended to it.</xs:documentation></xs:annotation>
24 </xs:attribute>
25 </xs:complexType>
26 </xs:element>
27
28 <xs:element name="Class">
29 <xs:complexType>
30 <xs:sequence minOccurs="0" maxOccurs="unbounded">
31 <xs:element ref="Message"/>
32 </xs:sequence>
33 <xs:attribute name="Name" type="xs:string" use="required">
34 <xs:annotation><xs:documentation>Name of the generated class.</xs:documentation></xs:annotation>
35 </xs:attribute>
36 <xs:attribute name="ContainerName" type="xs:string" use="required">
37 <xs:annotation><xs:documentation>Name of the generated container class.</xs:documentation></xs:annotation>
38 </xs:attribute>
39 <xs:attribute name="BaseContainerName" type="xs:string" use="required">
40 <xs:annotation><xs:documentation>Name of the base container class.</xs:documentation></xs:annotation>
41 </xs:attribute>
42 <xs:attribute name="Level" type="MessageLevelType">
43 <xs:annotation><xs:documentation>Optional message level for this container class and derivative classes.</xs:documentation></xs:annotation>
44 </xs:attribute>
45 </xs:complexType>
46 </xs:element>
47
48 <xs:element name="Message">
49 <xs:complexType>
50 <xs:sequence maxOccurs="unbounded">
51 <xs:element ref="Instance"/>
52 </xs:sequence>
53 <xs:attribute name="Id" type="xs:string" use="required">
54 <xs:annotation><xs:documentation>Name of the message type.</xs:documentation></xs:annotation>
55 </xs:attribute>
56 <xs:attribute name="Number" type="xs:integer" use="required">
57 <xs:annotation><xs:documentation>Override the number for this message type.</xs:documentation></xs:annotation>
58 </xs:attribute>
59 <xs:attribute name="SourceLineNumbers" type="YesNoType">
60 <xs:annotation><xs:documentation>Associate SourceLineNumbers with this message. The default value is "yes".</xs:documentation></xs:annotation>
61 </xs:attribute>
62 </xs:complexType>
63 </xs:element>
64
65 <xs:element name="Instance">
66 <xs:complexType mixed="true">
67 <xs:sequence minOccurs="0" maxOccurs="unbounded">
68 <xs:element ref="Parameter"/>
69 </xs:sequence>
70 </xs:complexType>
71 </xs:element>
72
73 <xs:element name="Parameter">
74 <xs:complexType>
75 <xs:attribute name="Type" type="xs:string" use="required">
76 <xs:annotation><xs:documentation>Type of the parameter.</xs:documentation></xs:annotation>
77 </xs:attribute>
78 <xs:attribute name="Name" type="xs:string" use="required">
79 <xs:annotation><xs:documentation>Name of the parameter.</xs:documentation></xs:annotation>
80 </xs:attribute>
81 </xs:complexType>
82 </xs:element>
83
84 <xs:simpleType name="YesNoType">
85 <xs:annotation><xs:documentation>Values of this type will either be "yes" or "no".</xs:documentation></xs:annotation>
86 <xs:restriction base="xs:NMTOKEN">
87 <xs:enumeration value="no"/>
88 <xs:enumeration value="yes"/>
89 </xs:restriction>
90 </xs:simpleType>
91
92 <xs:simpleType name="MessageLevelType">
93 <xs:annotation><xs:documentation>The message level for this message which corresponds to the WixToolset.MessageLevel enumeration.</xs:documentation></xs:annotation>
94 <xs:restriction base="xs:NMTOKEN">
95 <xs:enumeration value="Verbose"/>
96 <xs:enumeration value="Information"/>
97 <xs:enumeration value="Warning"/>
98 <xs:enumeration value="Error"/>
99 </xs:restriction>
100 </xs:simpleType>
101</xs:schema>
diff --git a/src/internal/WixBuildTools.MsgGen/build/WixBuildTools.MsgGen.targets b/src/internal/WixBuildTools.MsgGen/build/WixBuildTools.MsgGen.targets
new file mode 100644
index 00000000..dfa7bcbe
--- /dev/null
+++ b/src/internal/WixBuildTools.MsgGen/build/WixBuildTools.MsgGen.targets
@@ -0,0 +1,102 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
5 <PropertyGroup>
6 <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
7 </PropertyGroup>
8
9 <ItemGroup>
10 <!--Provide support for setting type (BuildAction) from VS-->
11 <AvailableItemName Include="MsgGenSource" />
12 </ItemGroup>
13
14 <PropertyGroup>
15 <MsgGenPath Condition=" '$(MsgGenPath)'=='' ">$(MSBuildThisFileDirectory)..\tools\</MsgGenPath>
16 </PropertyGroup>
17
18 <!--
19 ================================================================================================
20 MsgGen
21
22 Generates a .cs class file and a .resx file from an XML file.
23
24 [IN]
25 @(MsgGenSource) - The items to run through the MsgGen tool.
26
27 [OUT]
28 $(IntermediateOutputPath)%(Filename).cs - The generated .cs files to include in the compilation.
29 $(IntermediateOutputPath)%(MsgGenSource.ResourcesLogicalName) - The generated .resources file to embed in the assembly.
30 ================================================================================================
31 -->
32 <PropertyGroup>
33 <MsgGenDependsOn>
34 PrepareMsgGen
35 </MsgGenDependsOn>
36 <PrepareResourcesDependsOn>
37 MsgGen;
38 $(PrepareResourcesDependsOn)
39 </PrepareResourcesDependsOn>
40 </PropertyGroup>
41 <Target
42 Name="MsgGen"
43 BeforeTargets="PrepareResources"
44 DependsOnTargets="$(MsgGenDependsOn)"
45 Condition=" '@(MsgGenSource)' != '' "
46 Inputs="@(MsgGenSource)"
47 Outputs="$(IntermediateOutputPath)%(MsgGenSource.Filename).cs;
48 $(IntermediateOutputPath)%(MsgGenSource.ResourcesLogicalName)">
49
50 <Exec Command="&quot;$(MsgGenPath)WixBuildTools.MsgGen.exe&quot; -nologo &quot;%(MsgGenSource.FullPath)&quot; &quot;$(MsgGenCsFile)&quot; &quot;$(MsgGenResourcesFile)&quot;"
51 Outputs="$(MsgGenCsFile);$(MsgGenResourcesFile)" />
52
53 <ItemGroup>
54 <!-- This will tell MSBuild to clean up the .cs and .resources file during a Clean build -->
55 <FileWrites Include="$(MsgGenCsFile);$(MsgGenResourcesFile)" />
56 </ItemGroup>
57 </Target>
58
59 <!--
60 ================================================================================================
61 PrepareMsgGen
62
63 Creates properties and Include items for MsgGen. This must be separate from the MsgGen target
64 to workaround an MSBuild bug: AdditionalMetadata is ignored when the target is up-to-date.
65
66 ================================================================================================
67 -->
68 <Target
69 Name="PrepareMsgGen"
70 Condition=" '@(MsgGenSource)' != '' ">
71
72 <CreateProperty Value="$(IntermediateOutputPath)%(MsgGenSource.Filename).cs">
73 <Output TaskParameter="Value" PropertyName="MsgGenCsFile" />
74 </CreateProperty>
75
76 <CreateProperty
77 Value="$(IntermediateOutputPath)%(MsgGenSource.ResourcesLogicalName)"
78 Condition=" '%(MsgGenSource.ResourcesLogicalName)' != '' ">
79
80 <Output TaskParameter="Value" PropertyName="MsgGenResourcesFile" />
81 </CreateProperty>
82
83 <!-- Add the generated .cs file to the list of source files to compile -->
84 <CreateItem
85 Include="$(MsgGenCsFile)"
86 AdditionalMetadata="Link=%(MsgGenCsFile.Filename)%(MsgGenCsFile.Extension)">
87
88 <Output TaskParameter="Include" ItemName="Compile" />
89 </CreateItem>
90
91 <!-- Add the generated .resources file to the list of resources to embed -->
92 <CreateItem
93 Include="$(MsgGenResourcesFile)"
94 AdditionalMetadata="Link=%(MsgGenResourcesFile.Filename)%(MsgGenResourcesFile.Extension);
95 LogicalName=%(MsgGenSource.ResourcesLogicalName)"
96 Condition=" '$(MsgGenResourcesFile)' != '' ">
97
98 <Output TaskParameter="Include" ItemName="EmbeddedResource" />
99 </CreateItem>
100 </Target>
101
102</Project>
diff --git a/src/internal/WixBuildTools.MsgGen/buildCrossTargeting/WixBuildTools.MsgGen.targets b/src/internal/WixBuildTools.MsgGen/buildCrossTargeting/WixBuildTools.MsgGen.targets
new file mode 100644
index 00000000..a3985af5
--- /dev/null
+++ b/src/internal/WixBuildTools.MsgGen/buildCrossTargeting/WixBuildTools.MsgGen.targets
@@ -0,0 +1,6 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
5 <Import Project="..\build\WixBuildTools.MsgGen.targets" />
6</Project>
diff --git a/src/internal/WixBuildTools.TestSupport.Native/AssemblyInfo.cpp b/src/internal/WixBuildTools.TestSupport.Native/AssemblyInfo.cpp
new file mode 100644
index 00000000..23a48993
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport.Native/AssemblyInfo.cpp
@@ -0,0 +1,17 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5using namespace System::Reflection;
6using namespace System::Runtime::CompilerServices;
7using namespace System::Runtime::InteropServices;
8
9//
10// General Information about an assembly is controlled through the following
11// set of attributes. Change these attribute values to modify the information
12// associated with an assembly.
13//
14[assembly: AssemblyTitleAttribute("WixBuildTools.TestSupport.Native")];
15[assembly: AssemblyDescriptionAttribute("")];
16[assembly: AssemblyCultureAttribute("")];
17[assembly: ComVisible(false)];
diff --git a/src/internal/WixBuildTools.TestSupport.Native/NativeAssert.h b/src/internal/WixBuildTools.TestSupport.Native/NativeAssert.h
new file mode 100644
index 00000000..34af4f34
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport.Native/NativeAssert.h
@@ -0,0 +1,85 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5namespace WixBuildTools {
6namespace TestSupport {
7
8 using namespace System;
9 using namespace System::Collections::Generic;
10 using namespace System::Linq;
11 using namespace Xunit;
12
13 public ref class NativeAssert : WixAssert
14 {
15 public:
16 static void NotNull(LPCWSTR wz)
17 {
18 if (!wz)
19 {
20 Assert::NotNull(nullptr);
21 }
22 }
23
24 // For some reason, naming these NotStringEqual methods "NotEqual" breaks Intellisense in files that call any overload of the NotEqual method.
25 static void NotStringEqual(LPCWSTR expected, LPCWSTR actual)
26 {
27 NativeAssert::NotStringEqual(expected, actual, FALSE);
28 }
29
30 static void NotStringEqual(LPCWSTR expected, LPCWSTR actual, BOOL ignoreCase)
31 {
32 IEqualityComparer<String^>^ comparer = ignoreCase ? StringComparer::InvariantCultureIgnoreCase : StringComparer::InvariantCulture;
33 Assert::NotEqual(NativeAssert::LPWSTRToString(expected), NativeAssert::LPWSTRToString(actual), comparer);
34 }
35
36 // For some reason, naming these StringEqual methods "Equal" breaks Intellisense in files that call any overload of the Equal method.
37 static void StringEqual(LPCWSTR expected, LPCWSTR actual)
38 {
39 NativeAssert::StringEqual(expected, actual, FALSE);
40 }
41
42 static void StringEqual(LPCWSTR expected, LPCWSTR actual, BOOL ignoreCase)
43 {
44 IEqualityComparer<String^>^ comparer = ignoreCase ? StringComparer::InvariantCultureIgnoreCase : StringComparer::InvariantCulture;
45 Assert::Equal(NativeAssert::LPWSTRToString(expected), NativeAssert::LPWSTRToString(actual), comparer);
46 }
47
48 static void Succeeded(HRESULT hr, LPCSTR zFormat, LPCSTR zArg, ... array<LPCSTR>^ zArgs)
49 {
50 array<Object^>^ formatArgs = gcnew array<Object^, 1>(zArgs->Length + 1);
51 formatArgs[0] = NativeAssert::LPSTRToString(zArg);
52 for (int i = 0; i < zArgs->Length; ++i)
53 {
54 formatArgs[i + 1] = NativeAssert::LPSTRToString(zArgs[i]);
55 }
56 WixAssert::Succeeded(hr, gcnew String(zFormat), formatArgs);
57 }
58
59 static void Succeeded(HRESULT hr, LPCSTR zFormat, ... array<LPCWSTR>^ wzArgs)
60 {
61 array<Object^>^ formatArgs = gcnew array<Object^, 1>(wzArgs->Length);
62 for (int i = 0; i < wzArgs->Length; ++i)
63 {
64 formatArgs[i] = NativeAssert::LPWSTRToString(wzArgs[i]);
65 }
66 WixAssert::Succeeded(hr, gcnew String(zFormat), formatArgs);
67 }
68
69 static void ValidReturnCode(HRESULT hr, ... array<HRESULT>^ validReturnCodes)
70 {
71 Assert::Contains(hr, (IEnumerable<HRESULT>^)validReturnCodes);
72 }
73
74 private:
75 static String^ LPSTRToString(LPCSTR z)
76 {
77 return z ? gcnew String(z) : nullptr;
78 }
79 static String^ LPWSTRToString(LPCWSTR wz)
80 {
81 return wz ? gcnew String(wz) : nullptr;
82 }
83 };
84}
85}
diff --git a/src/internal/WixBuildTools.TestSupport.Native/WixBuildTools.TestSupport.Native.nuspec b/src/internal/WixBuildTools.TestSupport.Native/WixBuildTools.TestSupport.Native.nuspec
new file mode 100644
index 00000000..2852826b
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport.Native/WixBuildTools.TestSupport.Native.nuspec
@@ -0,0 +1,26 @@
1<?xml version="1.0"?>
2<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
3 <metadata minClientVersion="4.0">
4 <id>$id$</id>
5 <version>$version$</version>
6 <authors>$authors$</authors>
7 <owners>$authors$</owners>
8 <license type="expression">MS-RL</license>
9 <projectUrl>https://github.com/wixtoolset/WixBuildTools</projectUrl>
10 <requireLicenseAcceptance>false</requireLicenseAcceptance>
11 <description>$description$</description>
12 <copyright>$copyright$</copyright>
13 <repository type="$repositorytype$" url="$repositoryurl$" commit="$repositorycommit$" />
14 <dependencies>
15 <group targetFramework=".NETFramework4.7.2" />
16 </dependencies>
17 </metadata>
18
19 <files>
20 <file src="build\$id$.props" target="build" />
21 <file src="build\$id$.targets" target="build" />
22
23 <file src="$outputpath$$id$.dll" target="lib\net472" />
24 <file src="$outputpath$$id$.pdb" target="lib\net472" />
25 </files>
26</package>
diff --git a/src/internal/WixBuildTools.TestSupport.Native/WixBuildTools.TestSupport.Native.vcxproj b/src/internal/WixBuildTools.TestSupport.Native/WixBuildTools.TestSupport.Native.vcxproj
new file mode 100644
index 00000000..aefdb4fb
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport.Native/WixBuildTools.TestSupport.Native.vcxproj
@@ -0,0 +1,85 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
6 <Import Project="build\WixBuildTools.TestSupport.Native.props" />
7 <Import Project="..\..\packages\Microsoft.SourceLink.GitHub.1.0.0\build\Microsoft.SourceLink.GitHub.props" Condition="Exists('..\..\packages\Microsoft.SourceLink.GitHub.1.0.0\build\Microsoft.SourceLink.GitHub.props')" />
8 <Import Project="..\..\packages\Microsoft.SourceLink.Common.1.0.0\build\Microsoft.SourceLink.Common.props" Condition="Exists('..\..\packages\Microsoft.SourceLink.Common.1.0.0\build\Microsoft.SourceLink.Common.props')" />
9 <Import Project="..\..\packages\Microsoft.Build.Tasks.Git.1.0.0\build\Microsoft.Build.Tasks.Git.props" Condition="Exists('..\..\packages\Microsoft.Build.Tasks.Git.1.0.0\build\Microsoft.Build.Tasks.Git.props')" />
10 <ItemGroup Label="ProjectConfigurations">
11 <ProjectConfiguration Include="Debug|Win32">
12 <Configuration>Debug</Configuration>
13 <Platform>Win32</Platform>
14 </ProjectConfiguration>
15 <ProjectConfiguration Include="Release|Win32">
16 <Configuration>Release</Configuration>
17 <Platform>Win32</Platform>
18 </ProjectConfiguration>
19 </ItemGroup>
20 <PropertyGroup Label="Globals">
21 <ProjectTypes>{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}</ProjectTypes>
22 <ProjectGuid>{95BABD97-FBDB-453A-AF8A-FA031A07B599}</ProjectGuid>
23 <RootNamespace>WixBuildTools::TestSupport</RootNamespace>
24 <Keyword>ManagedCProj</Keyword>
25 <ConfigurationType>DynamicLibrary</ConfigurationType>
26 <CharacterSet>Unicode</CharacterSet>
27 <CLRSupport>true</CLRSupport>
28 <Description>WixBuildTools C++/CLI Test Support</Description>
29 </PropertyGroup>
30 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
31 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
32 <ImportGroup Label="Shared">
33 <Import Project="..\..\packages\Microsoft.Build.Tasks.Git.1.0.0\build\Microsoft.Build.Tasks.Git.targets" Condition="Exists('..\..\packages\Microsoft.Build.Tasks.Git.1.0.0\build\Microsoft.Build.Tasks.Git.targets')" />
34 <Import Project="..\..\packages\Microsoft.SourceLink.Common.1.0.0\build\Microsoft.SourceLink.Common.targets" Condition="Exists('..\..\packages\Microsoft.SourceLink.Common.1.0.0\build\Microsoft.SourceLink.Common.targets')" />
35 <Import Project="..\..\packages\Microsoft.SourceLink.GitHub.1.0.0\build\Microsoft.SourceLink.GitHub.targets" Condition="Exists('..\..\packages\Microsoft.SourceLink.GitHub.1.0.0\build\Microsoft.SourceLink.GitHub.targets')" />
36 <Import Project="..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets" Condition="Exists('..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets')" />
37 </ImportGroup>
38 <PropertyGroup>
39 <ProjectAdditionalIncludeDirectories>
40 </ProjectAdditionalIncludeDirectories>
41 <ProjectAdditionalLinkLibraries>
42 </ProjectAdditionalLinkLibraries>
43 </PropertyGroup>
44 <ItemGroup>
45 <ClCompile Include="AssemblyInfo.cpp" />
46 <ClCompile Include="precomp.cpp">
47 <PrecompiledHeader>Create</PrecompiledHeader>
48 <!-- Warnings from NativeAssert.h from referencing netstandard dlls -->
49 <DisableSpecificWarnings>4564;4691</DisableSpecificWarnings>
50 </ClCompile>
51 </ItemGroup>
52 <ItemGroup>
53 <ClInclude Include="precomp.h" />
54 <ClInclude Include="NativeAssert.h" />
55 </ItemGroup>
56 <ItemGroup>
57 <None Include="packages.config" />
58 </ItemGroup>
59 <ItemGroup>
60 <Reference Include="System" />
61 <Reference Include="System.Core" />
62 </ItemGroup>
63 <ItemGroup>
64 <ProjectReference Include="..\WixBuildTools.TestSupport\WixBuildTools.TestSupport.csproj">
65 <Project>{6C57EF2C-979A-4106-A9E5-FE342810619A}</Project>
66 </ProjectReference>
67 </ItemGroup>
68 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
69 <Import Project="build\WixBuildTools.TestSupport.Native.targets" />
70 <Target Name="PackNativeNuget" DependsOnTargets="Build">
71 <Exec Command='nuget pack $(MSBuildThisFileName).nuspec -OutputDirectory "$(OutputPath).." -Properties Id=$(MSBuildThisFileName);Version="$(BuildVersionSimple)";Authors="$(Authors)";Copyright="$(Copyright)";Description="$(Description)";Title="$(Title);RepositoryCommit=$(SourceRevisionId);RepositoryType=$(RepositoryType);RepositoryUrl=$(PrivateRepositoryUrl);OutputPath=$(OutputPath)' />
72 </Target>
73 <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
74 <PropertyGroup>
75 <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
76 </PropertyGroup>
77 <Error Condition="!Exists('..\..\packages\Microsoft.Build.Tasks.Git.1.0.0\build\Microsoft.Build.Tasks.Git.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Build.Tasks.Git.1.0.0\build\Microsoft.Build.Tasks.Git.props'))" />
78 <Error Condition="!Exists('..\..\packages\Microsoft.Build.Tasks.Git.1.0.0\build\Microsoft.Build.Tasks.Git.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Build.Tasks.Git.1.0.0\build\Microsoft.Build.Tasks.Git.targets'))" />
79 <Error Condition="!Exists('..\..\packages\Microsoft.SourceLink.Common.1.0.0\build\Microsoft.SourceLink.Common.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.SourceLink.Common.1.0.0\build\Microsoft.SourceLink.Common.props'))" />
80 <Error Condition="!Exists('..\..\packages\Microsoft.SourceLink.Common.1.0.0\build\Microsoft.SourceLink.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.SourceLink.Common.1.0.0\build\Microsoft.SourceLink.Common.targets'))" />
81 <Error Condition="!Exists('..\..\packages\Microsoft.SourceLink.GitHub.1.0.0\build\Microsoft.SourceLink.GitHub.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.SourceLink.GitHub.1.0.0\build\Microsoft.SourceLink.GitHub.props'))" />
82 <Error Condition="!Exists('..\..\packages\Microsoft.SourceLink.GitHub.1.0.0\build\Microsoft.SourceLink.GitHub.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.SourceLink.GitHub.1.0.0\build\Microsoft.SourceLink.GitHub.targets'))" />
83 <Error Condition="!Exists('..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets'))" />
84 </Target>
85</Project> \ No newline at end of file
diff --git a/src/internal/WixBuildTools.TestSupport.Native/WixBuildTools.TestSupport.Native.vcxproj.filters b/src/internal/WixBuildTools.TestSupport.Native/WixBuildTools.TestSupport.Native.vcxproj.filters
new file mode 100644
index 00000000..34c1380f
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport.Native/WixBuildTools.TestSupport.Native.vcxproj.filters
@@ -0,0 +1,33 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 <ItemGroup>
4 <Filter Include="Source Files">
5 <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
6 <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
7 </Filter>
8 <Filter Include="Header Files">
9 <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
10 <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
11 </Filter>
12 <Filter Include="Resource Files">
13 <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
14 <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
15 </Filter>
16 </ItemGroup>
17 <ItemGroup>
18 <ClInclude Include="precomp.h">
19 <Filter>Header Files</Filter>
20 </ClInclude>
21 <ClInclude Include="NativeAssert.h">
22 <Filter>Header Files</Filter>
23 </ClInclude>
24 </ItemGroup>
25 <ItemGroup>
26 <ClCompile Include="AssemblyInfo.cpp">
27 <Filter>Source Files</Filter>
28 </ClCompile>
29 <ClCompile Include="precomp.cpp">
30 <Filter>Source Files</Filter>
31 </ClCompile>
32 </ItemGroup>
33</Project> \ No newline at end of file
diff --git a/src/internal/WixBuildTools.TestSupport.Native/build/WixBuildTools.TestSupport.Native.props b/src/internal/WixBuildTools.TestSupport.Native/build/WixBuildTools.TestSupport.Native.props
new file mode 100644
index 00000000..4a7a0035
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport.Native/build/WixBuildTools.TestSupport.Native.props
@@ -0,0 +1,29 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
6 <PropertyGroup>
7 <RepoRootDir Condition=" '$(RepoRootDir)' == '' ">$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), version.json))</RepoRootDir>
8 </PropertyGroup>
9 <Import Project="$(RepoRootDir)\packages\xunit.core.2.4.1\build\xunit.core.props" Condition="Exists('$(RepoRootDir)\packages\xunit.core.2.4.1\build\xunit.core.props')" />
10 <Import Project="$(RepoRootDir)\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props" Condition="Exists('$(RepoRootDir)\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props')" />
11 <PropertyGroup>
12 <PlatformToolset>v142</PlatformToolset>
13 <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
14 </PropertyGroup>
15 <ItemGroup>
16 <Reference Include="xunit.abstractions">
17 <HintPath>$(RepoRootDir)\packages\xunit.abstractions.2.0.3\lib\netstandard2.0\xunit.abstractions.dll</HintPath>
18 </Reference>
19 <Reference Include="xunit.assert">
20 <HintPath>$(RepoRootDir)\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll</HintPath>
21 </Reference>
22 <Reference Include="xunit.core">
23 <HintPath>$(RepoRootDir)\packages\xunit.extensibility.core.2.4.1\lib\netstandard1.1\xunit.core.dll</HintPath>
24 </Reference>
25 <Reference Include="xunit.execution.desktop">
26 <HintPath>$(RepoRootDir)\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll</HintPath>
27 </Reference>
28 </ItemGroup>
29</Project> \ No newline at end of file
diff --git a/src/internal/WixBuildTools.TestSupport.Native/build/WixBuildTools.TestSupport.Native.targets b/src/internal/WixBuildTools.TestSupport.Native/build/WixBuildTools.TestSupport.Native.targets
new file mode 100644
index 00000000..77e72e95
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport.Native/build/WixBuildTools.TestSupport.Native.targets
@@ -0,0 +1,19 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
6 <Import Project="$(RepoRootDir)\packages\xunit.core.2.4.1\build\xunit.core.targets" Condition="Exists('$(RepoRootDir)\packages\xunit.core.2.4.1\build\xunit.core.targets')" />
7 <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
8 <PropertyGroup>
9 <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
10 </PropertyGroup>
11 <Error Condition="!Exists('$(RepoRootDir)\packages\xunit.core.2.4.1\build\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRootDir)\packages\xunit.core.2.4.1\build\xunit.core.props'))" />
12 <Error Condition="!Exists('$(RepoRootDir)\packages\xunit.core.2.4.1\build\xunit.core.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRootDir)\packages\xunit.core.2.4.1\build\xunit.core.targets'))" />
13 <Error Condition="!Exists('$(RepoRootDir)\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRootDir)\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props'))" />
14 </Target>
15 <UsingTask AssemblyFile="$(RepoRootDir)\packages\xunit.runner.msbuild.2.4.1\build\net452\xunit.runner.msbuild.net452.dll" TaskName="Xunit.Runner.MSBuild.xunit" />
16 <Target Name="Test" DependsOnTargets="Build">
17 <xunit Assemblies="$(TargetPath)" />
18 </Target>
19</Project> \ No newline at end of file
diff --git a/src/internal/WixBuildTools.TestSupport.Native/packages.config b/src/internal/WixBuildTools.TestSupport.Native/packages.config
new file mode 100644
index 00000000..917d7a63
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport.Native/packages.config
@@ -0,0 +1,17 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<packages>
6 <package id="Microsoft.Build.Tasks.Git" version="1.0.0" targetFramework="native" developmentDependency="true" />
7 <package id="Microsoft.SourceLink.Common" version="1.0.0" targetFramework="native" developmentDependency="true" />
8 <package id="Microsoft.SourceLink.GitHub" version="1.0.0" targetFramework="native" developmentDependency="true" />
9 <package id="Nerdbank.GitVersioning" version="3.3.37" targetFramework="native" developmentDependency="true" />
10 <package id="xunit.abstractions" version="2.0.3" />
11 <package id="xunit.assert" version="2.4.1" />
12 <package id="xunit.core" version="2.4.1" />
13 <package id="xunit.extensibility.core" version="2.4.1" />
14 <package id="xunit.extensibility.execution" version="2.4.1" />
15 <package id="xunit.runner.msbuild" version="2.4.1" />
16 <package id="xunit.runner.visualstudio" version="2.4.1" />
17</packages> \ No newline at end of file
diff --git a/src/internal/WixBuildTools.TestSupport.Native/precomp.cpp b/src/internal/WixBuildTools.TestSupport.Native/precomp.cpp
new file mode 100644
index 00000000..37664a1c
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport.Native/precomp.cpp
@@ -0,0 +1,3 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
diff --git a/src/internal/WixBuildTools.TestSupport.Native/precomp.h b/src/internal/WixBuildTools.TestSupport.Native/precomp.h
new file mode 100644
index 00000000..f54b55be
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport.Native/precomp.h
@@ -0,0 +1,11 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#include <windows.h>
6#include <strsafe.h>
7
8#include "NativeAssert.h"
9
10#pragma managed
11#include <vcclr.h>
diff --git a/src/internal/WixBuildTools.TestSupport/Builder.cs b/src/internal/WixBuildTools.TestSupport/Builder.cs
new file mode 100644
index 00000000..ef0de8c9
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/Builder.cs
@@ -0,0 +1,70 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixBuildTools.TestSupport
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8
9 public class Builder
10 {
11 public Builder(string sourceFolder, Type extensionType = null, string[] bindPaths = null, string outputFile = null)
12 {
13 this.SourceFolder = sourceFolder;
14 this.ExtensionType = extensionType;
15 this.BindPaths = bindPaths;
16 this.OutputFile = outputFile ?? "test.msi";
17 }
18
19 public string[] BindPaths { get; set; }
20
21 public Type ExtensionType { get; set; }
22
23 public string OutputFile { get; set; }
24
25 public string SourceFolder { get; }
26
27 public string[] BuildAndQuery(Action<string[]> buildFunc, params string[] tables)
28 {
29 var sourceFiles = Directory.GetFiles(this.SourceFolder, "*.wxs");
30 var wxlFiles = Directory.GetFiles(this.SourceFolder, "*.wxl");
31
32 using (var fs = new DisposableFileSystem())
33 {
34 var intermediateFolder = fs.GetFolder();
35 var outputPath = Path.Combine(intermediateFolder, "bin", this.OutputFile);
36
37 var args = new List<string>
38 {
39 "build",
40 "-o", outputPath,
41 "-intermediateFolder", intermediateFolder,
42 };
43
44 if (this.ExtensionType != null)
45 {
46 args.Add("-ext");
47 args.Add(Path.GetFullPath(new Uri(this.ExtensionType.Assembly.CodeBase).LocalPath));
48 }
49
50 args.AddRange(sourceFiles);
51
52 foreach (var wxlFile in wxlFiles)
53 {
54 args.Add("-loc");
55 args.Add(wxlFile);
56 }
57
58 foreach (var bindPath in this.BindPaths)
59 {
60 args.Add("-bindpath");
61 args.Add(bindPath);
62 }
63
64 buildFunc(args.ToArray());
65
66 return Query.QueryDatabase(outputPath, tables);
67 }
68 }
69 }
70}
diff --git a/src/internal/WixBuildTools.TestSupport/DisposableFileSystem.cs b/src/internal/WixBuildTools.TestSupport/DisposableFileSystem.cs
new file mode 100644
index 00000000..f096db72
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/DisposableFileSystem.cs
@@ -0,0 +1,93 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixBuildTools.TestSupport
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8
9 public class DisposableFileSystem : IDisposable
10 {
11 protected bool Disposed { get; private set; }
12
13 private List<string> CleanupPaths { get; } = new List<string>();
14
15 public bool Keep { get; }
16
17 public DisposableFileSystem(bool keep = false)
18 {
19 this.Keep = keep;
20 }
21
22 protected string GetFile(bool create = false)
23 {
24 var path = Path.GetTempFileName();
25
26 if (!create)
27 {
28 File.Delete(path);
29 }
30
31 this.CleanupPaths.Add(path);
32
33 return path;
34 }
35
36 public string GetFolder(bool create = false)
37 {
38 var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
39
40 if (create)
41 {
42 Directory.CreateDirectory(path);
43 }
44
45 this.CleanupPaths.Add(path);
46
47 return path;
48 }
49
50
51 #region // IDisposable
52
53 public void Dispose()
54 {
55 this.Dispose(true);
56 GC.SuppressFinalize(this);
57 }
58
59 protected virtual void Dispose(bool disposing)
60 {
61 if (this.Disposed)
62 {
63 return;
64 }
65
66 if (disposing && !this.Keep)
67 {
68 foreach (var path in this.CleanupPaths)
69 {
70 try
71 {
72 if (File.Exists(path))
73 {
74 File.Delete(path);
75 }
76 else if (Directory.Exists(path))
77 {
78 Directory.Delete(path, true);
79 }
80 }
81 catch
82 {
83 // Best effort delete, so ignore any failures.
84 }
85 }
86 }
87
88 this.Disposed = true;
89 }
90
91 #endregion
92 }
93}
diff --git a/src/internal/WixBuildTools.TestSupport/DotnetRunner.cs b/src/internal/WixBuildTools.TestSupport/DotnetRunner.cs
new file mode 100644
index 00000000..82391178
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/DotnetRunner.cs
@@ -0,0 +1,57 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixBuildTools.TestSupport
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8
9 public class DotnetRunner : ExternalExecutable
10 {
11 private static readonly object InitLock = new object();
12 private static bool Initialized;
13 private static DotnetRunner Instance;
14
15 public static ExternalExecutableResult Execute(string command, string[] arguments = null) =>
16 InitAndExecute(command, arguments);
17
18 private static ExternalExecutableResult InitAndExecute(string command, string[] arguments)
19 {
20 lock (InitLock)
21 {
22 if (!Initialized)
23 {
24 Initialized = true;
25 var dotnetPath = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH");
26 if (String.IsNullOrEmpty(dotnetPath) || !File.Exists(dotnetPath))
27 {
28 dotnetPath = "dotnet";
29 }
30
31 Instance = new DotnetRunner(dotnetPath);
32 }
33 }
34
35 return Instance.ExecuteCore(command, arguments);
36 }
37
38 private DotnetRunner(string exePath) : base(exePath) { }
39
40 private ExternalExecutableResult ExecuteCore(string command, string[] arguments)
41 {
42 var total = new List<string>
43 {
44 command,
45 };
46
47 if (arguments != null)
48 {
49 total.AddRange(arguments);
50 }
51
52 var args = CombineArguments(total);
53 var mergeErrorIntoOutput = true;
54 return this.Run(args, mergeErrorIntoOutput);
55 }
56 }
57}
diff --git a/src/internal/WixBuildTools.TestSupport/ExternalExecutable.cs b/src/internal/WixBuildTools.TestSupport/ExternalExecutable.cs
new file mode 100644
index 00000000..eb07aa13
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/ExternalExecutable.cs
@@ -0,0 +1,88 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixBuildTools.TestSupport
4{
5 using System.Collections.Concurrent;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.IO;
9 using System.Text;
10
11 public abstract class ExternalExecutable
12 {
13 private readonly string exePath;
14
15 protected ExternalExecutable(string exePath)
16 {
17 this.exePath = exePath;
18 }
19
20 protected ExternalExecutableResult Run(string args, bool mergeErrorIntoOutput = false, string workingDirectory = null)
21 {
22 var startInfo = new ProcessStartInfo(this.exePath, args)
23 {
24 CreateNoWindow = true,
25 RedirectStandardError = true,
26 RedirectStandardOutput = true,
27 UseShellExecute = false,
28 WorkingDirectory = workingDirectory ?? Path.GetDirectoryName(this.exePath),
29 };
30
31 using (var process = Process.Start(startInfo))
32 {
33 // This implementation of merging the streams does not guarantee that lines are retrieved in the same order that they were written.
34 // If the process is simultaneously writing to both streams, this is impossible to do anyway.
35 var standardOutput = new ConcurrentQueue<string>();
36 var standardError = mergeErrorIntoOutput ? standardOutput : new ConcurrentQueue<string>();
37
38 process.ErrorDataReceived += (s, e) => { if (e.Data != null) { standardError.Enqueue(e.Data); } };
39 process.OutputDataReceived += (s, e) => { if (e.Data != null) { standardOutput.Enqueue(e.Data); } };
40
41 process.BeginErrorReadLine();
42 process.BeginOutputReadLine();
43
44 process.WaitForExit();
45
46 return new ExternalExecutableResult
47 {
48 ExitCode = process.ExitCode,
49 StandardError = mergeErrorIntoOutput ? null : standardError.ToArray(),
50 StandardOutput = standardOutput.ToArray(),
51 StartInfo = startInfo,
52 };
53 }
54 }
55
56 // This is internal because it assumes backslashes aren't used as escape characters and there aren't any double quotes.
57 internal static string CombineArguments(IEnumerable<string> arguments)
58 {
59 if (arguments == null)
60 {
61 return null;
62 }
63
64 var sb = new StringBuilder();
65
66 foreach (var arg in arguments)
67 {
68 if (sb.Length > 0)
69 {
70 sb.Append(' ');
71 }
72
73 if (arg.IndexOf(' ') > -1)
74 {
75 sb.Append("\"");
76 sb.Append(arg);
77 sb.Append("\"");
78 }
79 else
80 {
81 sb.Append(arg);
82 }
83 }
84
85 return sb.ToString();
86 }
87 }
88}
diff --git a/src/internal/WixBuildTools.TestSupport/ExternalExecutableResult.cs b/src/internal/WixBuildTools.TestSupport/ExternalExecutableResult.cs
new file mode 100644
index 00000000..19b5183b
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/ExternalExecutableResult.cs
@@ -0,0 +1,17 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixBuildTools.TestSupport
4{
5 using System.Diagnostics;
6
7 public class ExternalExecutableResult
8 {
9 public int ExitCode { get; set; }
10
11 public string[] StandardError { get; set; }
12
13 public string[] StandardOutput { get; set; }
14
15 public ProcessStartInfo StartInfo { get; set; }
16 }
17}
diff --git a/src/internal/WixBuildTools.TestSupport/FakeBuildEngine.cs b/src/internal/WixBuildTools.TestSupport/FakeBuildEngine.cs
new file mode 100644
index 00000000..20545970
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/FakeBuildEngine.cs
@@ -0,0 +1,33 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixBuildTools.TestSupport
4{
5 using System.Collections;
6 using System.Text;
7 using Microsoft.Build.Framework;
8
9 public class FakeBuildEngine : IBuildEngine
10 {
11 private readonly StringBuilder output = new StringBuilder();
12
13 public int ColumnNumberOfTaskNode => 0;
14
15 public bool ContinueOnError => false;
16
17 public int LineNumberOfTaskNode => 0;
18
19 public string ProjectFileOfTaskNode => "fake_wix.targets";
20
21 public string Output => this.output.ToString();
22
23 public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs) => throw new System.NotImplementedException();
24
25 public void LogCustomEvent(CustomBuildEventArgs e) => this.output.AppendLine(e.Message);
26
27 public void LogErrorEvent(BuildErrorEventArgs e) => this.output.AppendLine(e.Message);
28
29 public void LogMessageEvent(BuildMessageEventArgs e) => this.output.AppendLine(e.Message);
30
31 public void LogWarningEvent(BuildWarningEventArgs e) => this.output.AppendLine(e.Message);
32 }
33}
diff --git a/src/internal/WixBuildTools.TestSupport/MsbuildRunner.cs b/src/internal/WixBuildTools.TestSupport/MsbuildRunner.cs
new file mode 100644
index 00000000..35e53de6
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/MsbuildRunner.cs
@@ -0,0 +1,168 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixBuildTools.TestSupport
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8
9 public class MsbuildRunner : ExternalExecutable
10 {
11 private static readonly string VswhereFindArguments = "-property installationPath";
12 private static readonly string Msbuild15RelativePath = @"MSBuild\15.0\Bin\MSBuild.exe";
13 private static readonly string Msbuild15RelativePath64 = @"MSBuild\15.0\Bin\amd64\MSBuild.exe";
14 private static readonly string MsbuildCurrentRelativePath = @"MSBuild\Current\Bin\MSBuild.exe";
15 private static readonly string MsbuildCurrentRelativePath64 = @"MSBuild\Current\Bin\amd64\MSBuild.exe";
16
17 private static readonly object InitLock = new object();
18
19 private static bool Initialized;
20 private static MsbuildRunner Msbuild15Runner;
21 private static MsbuildRunner Msbuild15Runner64;
22 private static MsbuildRunner MsbuildCurrentRunner;
23 private static MsbuildRunner MsbuildCurrentRunner64;
24
25 public static MsbuildRunnerResult Execute(string projectPath, string[] arguments = null, bool x64 = false) =>
26 InitAndExecute(String.Empty, projectPath, arguments, x64);
27
28 public static MsbuildRunnerResult ExecuteWithMsbuild15(string projectPath, string[] arguments = null, bool x64 = false) =>
29 InitAndExecute("15", projectPath, arguments, x64);
30
31 public static MsbuildRunnerResult ExecuteWithMsbuildCurrent(string projectPath, string[] arguments = null, bool x64 = false) =>
32 InitAndExecute("Current", projectPath, arguments, x64);
33
34 private static MsbuildRunnerResult InitAndExecute(string msbuildVersion, string projectPath, string[] arguments, bool x64)
35 {
36 lock (InitLock)
37 {
38 if (!Initialized)
39 {
40 Initialized = true;
41 var vswhereResult = VswhereRunner.Execute(VswhereFindArguments, true);
42 if (vswhereResult.ExitCode != 0)
43 {
44 throw new InvalidOperationException($"Failed to execute vswhere.exe, exit code: {vswhereResult.ExitCode}. Output:\r\n{String.Join("\r\n", vswhereResult.StandardOutput)}");
45 }
46
47 string msbuild15Path = null;
48 string msbuild15Path64 = null;
49 string msbuildCurrentPath = null;
50 string msbuildCurrentPath64 = null;
51
52 foreach (var installPath in vswhereResult.StandardOutput)
53 {
54 if (msbuildCurrentPath == null)
55 {
56 var path = Path.Combine(installPath, MsbuildCurrentRelativePath);
57 if (File.Exists(path))
58 {
59 msbuildCurrentPath = path;
60 }
61 }
62
63 if (msbuildCurrentPath64 == null)
64 {
65 var path = Path.Combine(installPath, MsbuildCurrentRelativePath64);
66 if (File.Exists(path))
67 {
68 msbuildCurrentPath64 = path;
69 }
70 }
71
72 if (msbuild15Path == null)
73 {
74 var path = Path.Combine(installPath, Msbuild15RelativePath);
75 if (File.Exists(path))
76 {
77 msbuild15Path = path;
78 }
79 }
80
81 if (msbuild15Path64 == null)
82 {
83 var path = Path.Combine(installPath, Msbuild15RelativePath64);
84 if (File.Exists(path))
85 {
86 msbuild15Path64 = path;
87 }
88 }
89 }
90
91 if (msbuildCurrentPath != null)
92 {
93 MsbuildCurrentRunner = new MsbuildRunner(msbuildCurrentPath);
94 }
95
96 if (msbuildCurrentPath64 != null)
97 {
98 MsbuildCurrentRunner64 = new MsbuildRunner(msbuildCurrentPath64);
99 }
100
101 if (msbuild15Path != null)
102 {
103 Msbuild15Runner = new MsbuildRunner(msbuild15Path);
104 }
105
106 if (msbuild15Path64 != null)
107 {
108 Msbuild15Runner64 = new MsbuildRunner(msbuild15Path64);
109 }
110 }
111 }
112
113 MsbuildRunner runner;
114 switch (msbuildVersion)
115 {
116 case "15":
117 {
118 runner = x64 ? Msbuild15Runner64 : Msbuild15Runner;
119 break;
120 }
121 case "Current":
122 {
123 runner = x64 ? MsbuildCurrentRunner64 : MsbuildCurrentRunner;
124 break;
125 }
126 default:
127 {
128 runner = x64 ? MsbuildCurrentRunner64 ?? Msbuild15Runner64
129 : MsbuildCurrentRunner ?? Msbuild15Runner;
130 break;
131 }
132 }
133
134 if (runner == null)
135 {
136 throw new InvalidOperationException($"Failed to find an installed{(x64 ? " 64-bit" : String.Empty)} MSBuild{msbuildVersion}");
137 }
138
139 return runner.ExecuteCore(projectPath, arguments);
140 }
141
142 private MsbuildRunner(string exePath) : base(exePath) { }
143
144 private MsbuildRunnerResult ExecuteCore(string projectPath, string[] arguments)
145 {
146 var total = new List<string>
147 {
148 projectPath,
149 };
150
151 if (arguments != null)
152 {
153 total.AddRange(arguments);
154 }
155
156 var args = CombineArguments(total);
157 var mergeErrorIntoOutput = true;
158 var workingFolder = Path.GetDirectoryName(projectPath);
159 var result = this.Run(args, mergeErrorIntoOutput, workingFolder);
160
161 return new MsbuildRunnerResult
162 {
163 ExitCode = result.ExitCode,
164 Output = result.StandardOutput,
165 };
166 }
167 }
168}
diff --git a/src/internal/WixBuildTools.TestSupport/MsbuildRunnerResult.cs b/src/internal/WixBuildTools.TestSupport/MsbuildRunnerResult.cs
new file mode 100644
index 00000000..5610987e
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/MsbuildRunnerResult.cs
@@ -0,0 +1,19 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixBuildTools.TestSupport
4{
5 using System;
6 using Xunit;
7
8 public class MsbuildRunnerResult
9 {
10 public int ExitCode { get; set; }
11
12 public string[] Output { get; set; }
13
14 public void AssertSuccess()
15 {
16 Assert.True(0 == this.ExitCode, $"MSBuild failed unexpectedly. Output:\r\n{String.Join("\r\n", this.Output)}");
17 }
18 }
19}
diff --git a/src/internal/WixBuildTools.TestSupport/Pushd.cs b/src/internal/WixBuildTools.TestSupport/Pushd.cs
new file mode 100644
index 00000000..d0545215
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/Pushd.cs
@@ -0,0 +1,46 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixBuildTools.TestSupport
4{
5 using System;
6 using System.IO;
7
8 public class Pushd : IDisposable
9 {
10 protected bool Disposed { get; private set; }
11
12 public Pushd(string path)
13 {
14 this.PreviousDirectory = Directory.GetCurrentDirectory();
15
16 Directory.SetCurrentDirectory(path);
17 }
18
19 public string PreviousDirectory { get; }
20
21 #region // IDisposable
22
23 public void Dispose()
24 {
25 this.Dispose(true);
26 GC.SuppressFinalize(this);
27 }
28
29 protected virtual void Dispose(bool disposing)
30 {
31 if (this.Disposed)
32 {
33 return;
34 }
35
36 if (disposing)
37 {
38 Directory.SetCurrentDirectory(this.PreviousDirectory);
39 }
40
41 this.Disposed = true;
42 }
43
44 #endregion
45 }
46}
diff --git a/src/internal/WixBuildTools.TestSupport/Query.cs b/src/internal/WixBuildTools.TestSupport/Query.cs
new file mode 100644
index 00000000..101a8890
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/Query.cs
@@ -0,0 +1,172 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixBuildTools.TestSupport
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Text;
10 using WixToolset.Dtf.Compression.Cab;
11 using WixToolset.Dtf.WindowsInstaller;
12
13 public class Query
14 {
15 public static string[] QueryDatabase(string path, string[] tables)
16 {
17 var results = new List<string>();
18 var resultsByTable = QueryDatabaseByTable(path, tables);
19 var sortedTables = tables.ToList();
20 sortedTables.Sort();
21 foreach (var tableName in sortedTables)
22 {
23 var rows = resultsByTable[tableName];
24 rows?.ForEach(r => results.Add($"{tableName}:{r}"));
25 }
26 return results.ToArray();
27 }
28
29 /// <summary>
30 /// Returns rows from requested tables formatted to facilitate testing.
31 /// If the table did not exist in the database, its list will be null.
32 /// </summary>
33 /// <param name="path"></param>
34 /// <param name="tables"></param>
35 /// <returns></returns>
36 public static Dictionary<string, List<string>> QueryDatabaseByTable(string path, string[] tables)
37 {
38 var results = new Dictionary<string, List<string>>();
39
40 if (tables?.Length > 0)
41 {
42 var sb = new StringBuilder();
43 using (var db = new Database(path))
44 {
45 foreach (var table in tables)
46 {
47 if (table == "_SummaryInformation")
48 {
49 var entries = new List<string>();
50 results.Add(table, entries);
51
52 entries.Add($"Title\t{db.SummaryInfo.Title}");
53 entries.Add($"Subject\t{db.SummaryInfo.Subject}");
54 entries.Add($"Author\t{db.SummaryInfo.Author}");
55 entries.Add($"Keywords\t{db.SummaryInfo.Keywords}");
56 entries.Add($"Comments\t{db.SummaryInfo.Comments}");
57 entries.Add($"Template\t{db.SummaryInfo.Template}");
58 entries.Add($"CodePage\t{db.SummaryInfo.CodePage}");
59 entries.Add($"PageCount\t{db.SummaryInfo.PageCount}");
60 entries.Add($"WordCount\t{db.SummaryInfo.WordCount}");
61 entries.Add($"CharacterCount\t{db.SummaryInfo.CharacterCount}");
62 entries.Add($"Security\t{db.SummaryInfo.Security}");
63
64 continue;
65 }
66
67 if (!db.IsTablePersistent(table))
68 {
69 results.Add(table, null);
70 continue;
71 }
72
73 var rows = new List<string>();
74 results.Add(table, rows);
75
76 using (var view = db.OpenView("SELECT * FROM `{0}`", table))
77 {
78 view.Execute();
79
80 Record record;
81 while ((record = view.Fetch()) != null)
82 {
83 sb.Clear();
84
85 using (record)
86 {
87 for (var i = 0; i < record.FieldCount; ++i)
88 {
89 if (i > 0)
90 {
91 sb.Append("\t");
92 }
93
94 sb.Append(record[i + 1]?.ToString());
95 }
96 }
97
98 rows.Add(sb.ToString());
99 }
100 }
101 rows.Sort();
102 }
103 }
104 }
105
106 return results;
107 }
108
109 public static CabFileInfo[] GetCabinetFiles(string path)
110 {
111 var cab = new CabInfo(path);
112
113 var result = cab.GetFiles();
114
115 return result.Select(c => c).ToArray();
116 }
117
118 public static void ExtractStream(string path, string streamName, string outputPath)
119 {
120 Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
121
122 using (var db = new Database(path))
123 using (var view = db.OpenView("SELECT `Data` FROM `_Streams` WHERE `Name` = '{0}'", streamName))
124 {
125 view.Execute();
126
127 using (var record = view.Fetch())
128 {
129 record.GetStream(1, outputPath);
130 }
131 }
132 }
133
134 public static void ExtractSubStorage(string path, string subStorageName, string outputPath)
135 {
136 Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
137
138 using (var db = new Database(path))
139 using (var view = db.OpenView("SELECT `Name`, `Data` FROM `_Storages` WHERE `Name` = '{0}'", subStorageName))
140 {
141 view.Execute();
142
143 using (var record = view.Fetch())
144 {
145 var name = record.GetString(1);
146 record.GetStream(2, outputPath);
147 }
148 }
149 }
150
151 public static string[] GetSubStorageNames(string path)
152 {
153 var result = new List<string>();
154
155 using (var db = new Database(path))
156 using (var view = db.OpenView("SELECT `Name` FROM `_Storages`"))
157 {
158 view.Execute();
159
160 Record record;
161 while ((record = view.Fetch()) != null)
162 {
163 var name = record.GetString(1);
164 result.Add(name);
165 }
166 }
167
168 result.Sort();
169 return result.ToArray();
170 }
171 }
172}
diff --git a/src/internal/WixBuildTools.TestSupport/RobocopyRunner.cs b/src/internal/WixBuildTools.TestSupport/RobocopyRunner.cs
new file mode 100644
index 00000000..49d53351
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/RobocopyRunner.cs
@@ -0,0 +1,16 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixBuildTools.TestSupport
4{
5 public class RobocopyRunner : ExternalExecutable
6 {
7 private static readonly RobocopyRunner Instance = new RobocopyRunner();
8
9 private RobocopyRunner() : base("robocopy") { }
10
11 public static ExternalExecutableResult Execute(string args)
12 {
13 return Instance.Run(args);
14 }
15 }
16}
diff --git a/src/internal/WixBuildTools.TestSupport/SucceededException.cs b/src/internal/WixBuildTools.TestSupport/SucceededException.cs
new file mode 100644
index 00000000..00b31d68
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/SucceededException.cs
@@ -0,0 +1,18 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixBuildTools.TestSupport
4{
5 using System;
6 using Xunit.Sdk;
7
8 public class SucceededException : XunitException
9 {
10 public SucceededException(int hr, string userMessage)
11 : base(String.Format("WixAssert.Succeeded() Failure\r\n" +
12 "HRESULT: 0x{0:X8}\r\n" +
13 "Message: {1}",
14 hr, userMessage))
15 {
16 }
17 }
18}
diff --git a/src/internal/WixBuildTools.TestSupport/TestData.cs b/src/internal/WixBuildTools.TestSupport/TestData.cs
new file mode 100644
index 00000000..8587330d
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/TestData.cs
@@ -0,0 +1,16 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixBuildTools.TestSupport
4{
5 using System;
6 using System.IO;
7
8 public class TestData
9 {
10 public static string Get(params string[] paths)
11 {
12 var localPath = Path.GetDirectoryName(new Uri(System.Reflection.Assembly.GetCallingAssembly().CodeBase).LocalPath);
13 return Path.Combine(localPath, Path.Combine(paths));
14 }
15 }
16}
diff --git a/src/internal/WixBuildTools.TestSupport/TestDataFolderFileSystem.cs b/src/internal/WixBuildTools.TestSupport/TestDataFolderFileSystem.cs
new file mode 100644
index 00000000..8d670bf0
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/TestDataFolderFileSystem.cs
@@ -0,0 +1,42 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixBuildTools.TestSupport
4{
5 using System;
6
7 /// <summary>
8 /// This class builds on top of DisposableFileSystem
9 /// to make it easy to write a test that needs a whole folder of test data copied to a temp location
10 /// that will automatically be cleaned up at the end of the test.
11 /// </summary>
12 public class TestDataFolderFileSystem : IDisposable
13 {
14 private DisposableFileSystem fileSystem;
15
16 public string BaseFolder { get; private set; }
17
18 public void Dispose()
19 {
20 this.fileSystem?.Dispose();
21 }
22
23 public void Initialize(string sourceDirectoryPath)
24 {
25 if (this.fileSystem != null)
26 {
27 throw new InvalidOperationException();
28 }
29 this.fileSystem = new DisposableFileSystem();
30
31 this.BaseFolder = this.fileSystem.GetFolder();
32
33 RobocopyFolder(sourceDirectoryPath, this.BaseFolder);
34 }
35
36 private static ExternalExecutableResult RobocopyFolder(string sourceFolderPath, string destinationFolderPath)
37 {
38 var args = $"\"{sourceFolderPath}\" \"{destinationFolderPath}\" /E /R:1 /W:1";
39 return RobocopyRunner.Execute(args);
40 }
41 }
42}
diff --git a/src/internal/WixBuildTools.TestSupport/VswhereRunner.cs b/src/internal/WixBuildTools.TestSupport/VswhereRunner.cs
new file mode 100644
index 00000000..0197e125
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/VswhereRunner.cs
@@ -0,0 +1,41 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixBuildTools.TestSupport
4{
5 using System;
6 using System.IO;
7
8 public class VswhereRunner : ExternalExecutable
9 {
10 private static readonly string VswhereRelativePath = @"Microsoft Visual Studio\Installer\vswhere.exe";
11
12 private static readonly object InitLock = new object();
13 private static bool Initialized;
14 private static VswhereRunner Instance;
15
16 public static ExternalExecutableResult Execute(string args, bool mergeErrorIntoOutput = false) =>
17 InitAndExecute(args, mergeErrorIntoOutput);
18
19 private static ExternalExecutableResult InitAndExecute(string args, bool mergeErrorIntoOutput)
20 {
21 lock (InitLock)
22 {
23 if (!Initialized)
24 {
25 Initialized = true;
26 var vswherePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), VswhereRelativePath);
27 if (!File.Exists(vswherePath))
28 {
29 throw new InvalidOperationException($"Failed to find vswhere at: {vswherePath}");
30 }
31
32 Instance = new VswhereRunner(vswherePath);
33 }
34 }
35
36 return Instance.Run(args, mergeErrorIntoOutput);
37 }
38
39 private VswhereRunner(string exePath) : base(exePath) { }
40 }
41}
diff --git a/src/internal/WixBuildTools.TestSupport/WixAssert.cs b/src/internal/WixBuildTools.TestSupport/WixAssert.cs
new file mode 100644
index 00000000..5638a787
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/WixAssert.cs
@@ -0,0 +1,47 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixBuildTools.TestSupport
4{
5 using System;
6 using System.Linq;
7 using System.Xml.Linq;
8 using Xunit;
9
10 public class WixAssert : Assert
11 {
12 public static void CompareLineByLine(string[] expectedLines, string[] actualLines)
13 {
14 for (var i = 0; i < expectedLines.Length; ++i)
15 {
16 Assert.True(actualLines.Length > i, $"{i}: expectedLines longer than actualLines");
17 Assert.Equal($"{i}: {expectedLines[i]}", $"{i}: {actualLines[i]}");
18 }
19
20 Assert.True(expectedLines.Length == actualLines.Length, "actualLines longer than expectedLines");
21 }
22
23 public static void CompareXml(XContainer xExpected, XContainer xActual)
24 {
25 var expecteds = xExpected.Descendants().Select(x => $"{x.Name.LocalName}:{String.Join(",", x.Attributes().OrderBy(a => a.Name.LocalName).Select(a => $"{a.Name.LocalName}={a.Value}"))}");
26 var actuals = xActual.Descendants().Select(x => $"{x.Name.LocalName}:{String.Join(",", x.Attributes().OrderBy(a => a.Name.LocalName).Select(a => $"{a.Name.LocalName}={a.Value}"))}");
27
28 CompareLineByLine(expecteds.OrderBy(s => s).ToArray(), actuals.OrderBy(s => s).ToArray());
29 }
30
31 public static void CompareXml(string expectedPath, string actualPath)
32 {
33 var expectedDoc = XDocument.Load(expectedPath, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);
34 var actualDoc = XDocument.Load(actualPath, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);
35
36 CompareXml(expectedDoc, actualDoc);
37 }
38
39 public static void Succeeded(int hr, string format, params object[] formatArgs)
40 {
41 if (0 > hr)
42 {
43 throw new SucceededException(hr, String.Format(format, formatArgs));
44 }
45 }
46 }
47}
diff --git a/src/internal/WixBuildTools.TestSupport/WixBuildTools.TestSupport.csproj b/src/internal/WixBuildTools.TestSupport/WixBuildTools.TestSupport.csproj
new file mode 100644
index 00000000..f59e5eca
--- /dev/null
+++ b/src/internal/WixBuildTools.TestSupport/WixBuildTools.TestSupport.csproj
@@ -0,0 +1,31 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5
6 <PropertyGroup>
7 <TargetFrameworks>netstandard2.0;net461;net472</TargetFrameworks>
8 <IsPackable>true</IsPackable>
9 <DebugType>embedded</DebugType>
10 <PublishRepositoryUrl>true</PublishRepositoryUrl>
11 <CreateDocumentationFile>true</CreateDocumentationFile>
12 <NoWarn>CS1591</NoWarn>
13 </PropertyGroup>
14
15 <ItemGroup>
16 <PackageReference Include="Microsoft.Build.Tasks.Core" Version="14.3" />
17 <PackageReference Include="WixToolset.Dtf.WindowsInstaller" Version="4.0.*" />
18 <PackageReference Include="WixToolset.Dtf.Compression" Version="4.0.*" />
19 <PackageReference Include="WixToolset.Dtf.Compression.Cab" Version="4.0.*" />
20 </ItemGroup>
21
22 <ItemGroup>
23 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
24 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
25 </ItemGroup>
26
27 <ItemGroup>
28 <PackageReference Include="xunit.assert" Version="2.4.1" />
29 </ItemGroup>
30
31</Project>
diff --git a/src/internal/WixBuildTools.XsdGen/AssemblyInfo.cs b/src/internal/WixBuildTools.XsdGen/AssemblyInfo.cs
new file mode 100644
index 00000000..b3740b2a
--- /dev/null
+++ b/src/internal/WixBuildTools.XsdGen/AssemblyInfo.cs
@@ -0,0 +1,9 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.Reflection;
5using System.Runtime.InteropServices;
6
7[assembly: AssemblyCulture("")]
8[assembly: CLSCompliant(true)]
9[assembly: ComVisible(false)]
diff --git a/src/internal/WixBuildTools.XsdGen/CodeDomInterfaces.cs b/src/internal/WixBuildTools.XsdGen/CodeDomInterfaces.cs
new file mode 100644
index 00000000..850839d4
--- /dev/null
+++ b/src/internal/WixBuildTools.XsdGen/CodeDomInterfaces.cs
@@ -0,0 +1,96 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Serialize
4{
5 using System;
6 using System.Collections;
7 using System.Xml;
8
9 /// <summary>
10 /// Interface for generated schema elements.
11 /// </summary>
12 public interface ISchemaElement
13 {
14 /// <summary>
15 /// Gets and sets the parent of this element. May be null.
16 /// </summary>
17 /// <value>An ISchemaElement that has this element as a child.</value>
18 ISchemaElement ParentElement
19 {
20 get;
21 set;
22 }
23
24 /// <summary>
25 /// Outputs xml representing this element, including the associated attributes
26 /// and any nested elements.
27 /// </summary>
28 /// <param name="writer">XmlTextWriter to be used when outputting the element.</param>
29 void OutputXml(XmlWriter writer);
30 }
31
32 /// <summary>
33 /// Interface for generated schema elements. Implemented by elements that have child
34 /// elements.
35 /// </summary>
36 public interface IParentElement
37 {
38 /// <summary>
39 /// Gets an enumerable collection of the children of this element.
40 /// </summary>
41 /// <value>An enumerable collection of the children of this element.</value>
42 IEnumerable Children
43 {
44 get;
45 }
46
47 /// <summary>
48 /// Gets an enumerable collection of the children of this element, filtered
49 /// by the passed in type.
50 /// </summary>
51 /// <param name="childType">The type of children to retrieve.</param>
52 IEnumerable this[Type childType]
53 {
54 get;
55 }
56
57 /// <summary>
58 /// Adds a child to this element.
59 /// </summary>
60 /// <param name="child">Child to add.</param>
61 void AddChild(ISchemaElement child);
62
63 /// <summary>
64 /// Removes a child from this element.
65 /// </summary>
66 /// <param name="child">Child to remove.</param>
67 void RemoveChild(ISchemaElement child);
68 }
69
70 /// <summary>
71 /// Interface for generated schema elements. Implemented by classes with attributes.
72 /// </summary>
73 public interface ISetAttributes
74 {
75 /// <summary>
76 /// Sets the attribute with the given name to the given value. The value here is
77 /// a string, and is converted to the strongly-typed version inside this method.
78 /// </summary>
79 /// <param name="name">The name of the attribute to set.</param>
80 /// <param name="value">The value to assign to the attribute.</param>
81 void SetAttribute(string name, string value);
82 }
83
84 /// <summary>
85 /// Interface for generated schema elements. Implemented by classes with children.
86 /// </summary>
87 public interface ICreateChildren
88 {
89 /// <summary>
90 /// Creates an instance of the child with the passed in name.
91 /// </summary>
92 /// <param name="childName">String matching the element name of the child when represented in XML.</param>
93 /// <returns>An instance of that child.</returns>
94 ISchemaElement CreateChild(string childName);
95 }
96}
diff --git a/src/internal/WixBuildTools.XsdGen/CodeDomReader.cs b/src/internal/WixBuildTools.XsdGen/CodeDomReader.cs
new file mode 100644
index 00000000..5198f264
--- /dev/null
+++ b/src/internal/WixBuildTools.XsdGen/CodeDomReader.cs
@@ -0,0 +1,159 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Serialize
4{
5 using System;
6 using System.Collections;
7 using System.Reflection;
8 using System.Xml;
9
10 /// <summary>
11 /// Class used for reading XML files in to the CodeDom.
12 /// </summary>
13 public class CodeDomReader
14 {
15 private Assembly[] assemblies;
16
17 /// <summary>
18 /// Creates a new CodeDomReader, using the current assembly.
19 /// </summary>
20 public CodeDomReader()
21 {
22 this.assemblies = new Assembly[] { Assembly.GetExecutingAssembly() };
23 }
24
25 /// <summary>
26 /// Creates a new CodeDomReader, and takes in a list of assemblies in which to
27 /// look for elements.
28 /// </summary>
29 /// <param name="assemblies">Assemblies in which to look for types that correspond
30 /// to elements.</param>
31 public CodeDomReader(Assembly[] assemblies)
32 {
33 this.assemblies = assemblies;
34 }
35
36 /// <summary>
37 /// Loads an XML file into a strongly-typed code dom.
38 /// </summary>
39 /// <param name="filePath">File to load into the code dom.</param>
40 /// <returns>The strongly-typed object at the root of the tree.</returns>
41 public ISchemaElement Load(string filePath)
42 {
43 XmlDocument document = new XmlDocument();
44 document.Load(filePath);
45 ISchemaElement schemaElement = null;
46
47 foreach (XmlNode node in document.ChildNodes)
48 {
49 XmlElement element = node as XmlElement;
50 if (element != null)
51 {
52 if (schemaElement != null)
53 {
54 throw new InvalidOperationException("Multiple root elements found in file.");
55 }
56
57 schemaElement = this.CreateObjectFromElement(element);
58 this.ParseObjectFromElement(schemaElement, element);
59 }
60 }
61 return schemaElement;
62 }
63
64 /// <summary>
65 /// Parses an ISchemaElement from the XmlElement.
66 /// </summary>
67 /// <param name="schemaElement">ISchemaElement to fill in.</param>
68 /// <param name="element">XmlElement to parse from.</param>
69 private void ParseObjectFromElement(ISchemaElement schemaElement, XmlElement element)
70 {
71 foreach (XmlAttribute attribute in element.Attributes)
72 {
73 this.SetAttributeOnObject(schemaElement, attribute.LocalName, attribute.Value);
74 }
75
76 foreach (XmlNode node in element.ChildNodes)
77 {
78 XmlElement childElement = node as XmlElement;
79 if (childElement != null)
80 {
81 ISchemaElement childSchemaElement = null;
82 ICreateChildren createChildren = schemaElement as ICreateChildren;
83 if (createChildren == null)
84 {
85 throw new InvalidOperationException("ISchemaElement with name " + element.LocalName + " does not implement ICreateChildren.");
86 }
87 else
88 {
89 childSchemaElement = createChildren.CreateChild(childElement.LocalName);
90 }
91
92 if (childSchemaElement == null)
93 {
94 childSchemaElement = this.CreateObjectFromElement(childElement);
95 if (childSchemaElement == null)
96 {
97 throw new InvalidOperationException("XmlElement with name " + childElement.LocalName + " does not have a corresponding ISchemaElement.");
98 }
99 }
100
101 this.ParseObjectFromElement(childSchemaElement, childElement);
102 IParentElement parentElement = (IParentElement)schemaElement;
103 parentElement.AddChild(childSchemaElement);
104 }
105 else
106 {
107 XmlText childText = node as XmlText;
108 if (childText != null)
109 {
110 this.SetAttributeOnObject(schemaElement, "Content", childText.Value);
111 }
112 }
113 }
114 }
115
116 /// <summary>
117 /// Sets an attribute on an ISchemaElement.
118 /// </summary>
119 /// <param name="schemaElement">Schema element to set attribute on.</param>
120 /// <param name="name">Name of the attribute to set.</param>
121 /// <param name="value">Value to set on the attribute.</param>
122 private void SetAttributeOnObject(ISchemaElement schemaElement, string name, string value)
123 {
124 ISetAttributes setAttributes = schemaElement as ISetAttributes;
125 if (setAttributes == null)
126 {
127 throw new InvalidOperationException("ISchemaElement with name "
128 + schemaElement.GetType().FullName.ToString()
129 + " does not implement ISetAttributes.");
130 }
131 else
132 {
133 setAttributes.SetAttribute(name, value);
134 }
135 }
136
137 /// <summary>
138 /// Creates an object from an XML element by digging through the assembly list.
139 /// </summary>
140 /// <param name="element">XML Element to create an ISchemaElement from.</param>
141 /// <returns>A constructed ISchemaElement.</returns>
142 private ISchemaElement CreateObjectFromElement(XmlElement element)
143 {
144 ISchemaElement schemaElement = null;
145 foreach (Assembly assembly in this.assemblies)
146 {
147 foreach (Type type in assembly.GetTypes())
148 {
149 if (type.FullName.EndsWith(element.LocalName)
150 && typeof(ISchemaElement).IsAssignableFrom(type))
151 {
152 schemaElement = (ISchemaElement)Activator.CreateInstance(type);
153 }
154 }
155 }
156 return schemaElement;
157 }
158 }
159}
diff --git a/src/internal/WixBuildTools.XsdGen/ElementCollection.cs b/src/internal/WixBuildTools.XsdGen/ElementCollection.cs
new file mode 100644
index 00000000..3f0bff16
--- /dev/null
+++ b/src/internal/WixBuildTools.XsdGen/ElementCollection.cs
@@ -0,0 +1,642 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Serialize
4{
5 using System;
6 using System.Collections;
7 using System.Globalization;
8
9 /// <summary>
10 /// Collection used in the CodeDOM for the children of a given element. Provides type-checking
11 /// on the allowed children to ensure that only allowed types are added.
12 /// </summary>
13 public class ElementCollection : ICollection, IEnumerable
14 {
15 private CollectionType collectionType;
16 private int minimum = 1;
17 private int maximum = 1;
18 private int totalContainedItems;
19 private int containersUsed;
20 private ArrayList items;
21
22 /// <summary>
23 /// Creates a new element collection.
24 /// </summary>
25 /// <param name="collectionType">Type of the collection to create.</param>
26 public ElementCollection(CollectionType collectionType)
27 {
28 this.collectionType = collectionType;
29 this.items = new ArrayList();
30 }
31
32 /// <summary>
33 /// Creates a new element collection.
34 /// </summary>
35 /// <param name="collectionType">Type of the collection to create.</param>
36 /// <param name="minimum">When used with a type 'Choice', specifies a minimum number of allowed children.</param>
37 /// <param name="maximum">When used with a type 'Choice', specifies a maximum number of allowed children.</param>
38 public ElementCollection(CollectionType collectionType, int minimum, int maximum) : this(collectionType)
39 {
40 this.minimum = minimum;
41 this.maximum = maximum;
42 }
43
44 /// <summary>
45 /// Enum representing types of XML collections.
46 /// </summary>
47 public enum CollectionType
48 {
49 /// <summary>
50 /// A choice type, corresponding to the XSD choice element.
51 /// </summary>
52 Choice,
53
54 /// <summary>
55 /// A sequence type, corresponding to the XSD sequence element.
56 /// </summary>
57 Sequence
58 }
59
60 /// <summary>
61 /// Gets the type of collection.
62 /// </summary>
63 /// <value>The type of collection.</value>
64 public CollectionType Type
65 {
66 get { return this.collectionType; }
67 }
68
69 /// <summary>
70 /// Gets the count of child elements in this collection (counts ISchemaElements, not nested collections).
71 /// </summary>
72 /// <value>The count of child elements in this collection (counts ISchemaElements, not nested collections).</value>
73 public int Count
74 {
75 get { return this.totalContainedItems; }
76 }
77
78 /// <summary>
79 /// Gets the flag specifying whether this collection is synchronized. Always returns false.
80 /// </summary>
81 /// <value>The flag specifying whether this collection is synchronized. Always returns false.</value>
82 public bool IsSynchronized
83 {
84 get { return false; }
85 }
86
87 /// <summary>
88 /// Gets an object external callers can synchronize on.
89 /// </summary>
90 /// <value>An object external callers can synchronize on.</value>
91 public object SyncRoot
92 {
93 get { return this; }
94 }
95
96 /// <summary>
97 /// Adds a child element to this collection.
98 /// </summary>
99 /// <param name="element">The element to add.</param>
100 /// <exception cref="ArgumentException">Thrown if the child is not of an allowed type.</exception>
101 public void AddElement(ISchemaElement element)
102 {
103 foreach (object obj in this.items)
104 {
105 bool containerUsed;
106
107 CollectionItem collectionItem = obj as CollectionItem;
108 if (collectionItem != null)
109 {
110 containerUsed = collectionItem.Elements.Count != 0;
111 if (collectionItem.ElementType.IsAssignableFrom(element.GetType()))
112 {
113 collectionItem.AddElement(element);
114
115 if (!containerUsed)
116 {
117 this.containersUsed++;
118 }
119
120 this.totalContainedItems++;
121 return;
122 }
123
124 continue;
125 }
126
127 ElementCollection collection = obj as ElementCollection;
128 if (collection != null)
129 {
130 containerUsed = collection.Count != 0;
131
132 try
133 {
134 collection.AddElement(element);
135
136 if (!containerUsed)
137 {
138 this.containersUsed++;
139 }
140
141 this.totalContainedItems++;
142 return;
143 }
144 catch (ArgumentException)
145 {
146 // Eat the exception and keep looking. We'll throw our own if we can't find its home.
147 }
148
149 continue;
150 }
151 }
152
153 throw new ArgumentException(String.Format(
154 CultureInfo.InvariantCulture,
155 "Element of type {0} is not valid for this collection.",
156 element.GetType().Name));
157 }
158
159 /// <summary>
160 /// Removes a child element from this collection.
161 /// </summary>
162 /// <param name="element">The element to remove.</param>
163 /// <exception cref="ArgumentException">Thrown if the element is not of an allowed type.</exception>
164 public void RemoveElement(ISchemaElement element)
165 {
166 foreach (object obj in this.items)
167 {
168 CollectionItem collectionItem = obj as CollectionItem;
169 if (collectionItem != null)
170 {
171 if (collectionItem.ElementType.IsAssignableFrom(element.GetType()))
172 {
173 if (collectionItem.Elements.Count == 0)
174 {
175 return;
176 }
177
178 collectionItem.RemoveElement(element);
179
180 if (collectionItem.Elements.Count == 0)
181 {
182 this.containersUsed--;
183 }
184
185 this.totalContainedItems--;
186 return;
187 }
188
189 continue;
190 }
191
192 ElementCollection collection = obj as ElementCollection;
193 if (collection != null)
194 {
195 if (collection.Count == 0)
196 {
197 continue;
198 }
199
200 try
201 {
202 collection.RemoveElement(element);
203
204 if (collection.Count == 0)
205 {
206 this.containersUsed--;
207 }
208
209 this.totalContainedItems--;
210 return;
211 }
212 catch (ArgumentException)
213 {
214 // Eat the exception and keep looking. We'll throw our own if we can't find its home.
215 }
216
217 continue;
218 }
219 }
220
221 throw new ArgumentException(String.Format(
222 CultureInfo.InvariantCulture,
223 "Element of type {0} is not valid for this collection.",
224 element.GetType().Name));
225 }
226
227 /// <summary>
228 /// Copies this collection to an array.
229 /// </summary>
230 /// <param name="array">Array to copy to.</param>
231 /// <param name="index">Offset into the array.</param>
232 public void CopyTo(Array array, int index)
233 {
234 int item = 0;
235 foreach (ISchemaElement element in this)
236 {
237 array.SetValue(element, (long)(item + index));
238 item++;
239 }
240 }
241
242 /// <summary>
243 /// Creates an enumerator for walking the elements in this collection.
244 /// </summary>
245 /// <returns>A newly created enumerator.</returns>
246 public IEnumerator GetEnumerator()
247 {
248 return new ElementCollectionEnumerator(this);
249 }
250
251 /// <summary>
252 /// Gets an enumerable collection of children of a given type.
253 /// </summary>
254 /// <param name="childType">Type of children to get.</param>
255 /// <returns>A collection of children.</returns>
256 /// <exception cref="ArgumentException">Thrown if the type isn't a valid child type.</exception>
257 public IEnumerable Filter(Type childType)
258 {
259 foreach (object container in this.items)
260 {
261 CollectionItem collectionItem = container as CollectionItem;
262 if (collectionItem != null)
263 {
264 if (collectionItem.ElementType.IsAssignableFrom(childType))
265 {
266 return collectionItem.Elements;
267 }
268
269 continue;
270 }
271
272 ElementCollection elementCollection = container as ElementCollection;
273 if (elementCollection != null)
274 {
275 IEnumerable nestedFilter = elementCollection.Filter(childType);
276 if (nestedFilter != null)
277 {
278 return nestedFilter;
279 }
280
281 continue;
282 }
283 }
284
285 throw new ArgumentException(String.Format(
286 CultureInfo.InvariantCulture,
287 "Type {0} is not valid for this collection.",
288 childType.Name));
289 }
290
291 /// <summary>
292 /// Adds a type to this collection.
293 /// </summary>
294 /// <param name="collectionItem">CollectionItem representing the type to add.</param>
295 public void AddItem(CollectionItem collectionItem)
296 {
297 this.items.Add(collectionItem);
298 }
299
300 /// <summary>
301 /// Adds a nested collection to this collection.
302 /// </summary>
303 /// <param name="collection">ElementCollection to add.</param>
304 public void AddCollection(ElementCollection collection)
305 {
306 this.items.Add(collection);
307 }
308
309 /// <summary>
310 /// Class used to represent a given type in the child collection of an element. Abstract,
311 /// has subclasses for choice and sequence (which can do cardinality checks).
312 /// </summary>
313 public abstract class CollectionItem
314 {
315 private Type elementType;
316 private ArrayList elements;
317
318 /// <summary>
319 /// Creates a new CollectionItem for the given element type.
320 /// </summary>
321 /// <param name="elementType">Type of the element for this collection item.</param>
322 public CollectionItem(Type elementType)
323 {
324 this.elementType = elementType;
325 this.elements = new ArrayList();
326 }
327
328 /// <summary>
329 /// Gets the type of this collection's items.
330 /// </summary>
331 public Type ElementType
332 {
333 get { return this.elementType; }
334 }
335
336 /// <summary>
337 /// Gets the elements of this collection.
338 /// </summary>
339 public ArrayList Elements
340 {
341 get { return this.elements; }
342 }
343
344 /// <summary>
345 /// Adds an element to this collection. Must be of an assignable type to the collection's
346 /// type.
347 /// </summary>
348 /// <param name="element">The element to add.</param>
349 /// <exception cref="ArgumentException">Thrown if the type isn't assignable to the collection's type.</exception>
350 public void AddElement(ISchemaElement element)
351 {
352 if (!this.elementType.IsAssignableFrom(element.GetType()))
353 {
354 throw new ArgumentException(
355 String.Format(
356 CultureInfo.InvariantCulture,
357 "Element must be a subclass of {0}, but was of type {1}.",
358 this.elementType.Name,
359 element.GetType().Name),
360 "element");
361 }
362
363 this.elements.Add(element);
364 }
365
366 /// <summary>
367 /// Removes an element from this collection.
368 /// </summary>
369 /// <param name="element">The element to remove.</param>
370 /// <exception cref="ArgumentException">Thrown if the element's type isn't assignable to the collection's type.</exception>
371 public void RemoveElement(ISchemaElement element)
372 {
373 if (!this.elementType.IsAssignableFrom(element.GetType()))
374 {
375 throw new ArgumentException(
376 String.Format(
377 CultureInfo.InvariantCulture,
378 "Element must be a subclass of {0}, but was of type {1}.",
379 this.elementType.Name,
380 element.GetType().Name),
381 "element");
382 }
383
384 this.elements.Remove(element);
385 }
386 }
387
388 /// <summary>
389 /// Class representing a choice item. Doesn't do cardinality checks.
390 /// </summary>
391 public class ChoiceItem : CollectionItem
392 {
393 /// <summary>
394 /// Creates a new choice item.
395 /// </summary>
396 /// <param name="elementType">Type of the created item.</param>
397 public ChoiceItem(Type elementType) : base(elementType)
398 {
399 }
400 }
401
402 /// <summary>
403 /// Class representing a sequence item. Can do cardinality checks, if required.
404 /// </summary>
405 public class SequenceItem : CollectionItem
406 {
407 private int minimum = 1;
408 private int maximum = 1;
409
410 /// <summary>
411 /// Creates a new sequence item.
412 /// </summary>
413 /// <param name="elementType">Type of the created item.</param>
414 public SequenceItem(Type elementType) : base(elementType)
415 {
416 }
417
418 /// <summary>
419 /// Creates a new sequence item with the specified minimum and maximum.
420 /// </summary>
421 /// <param name="elementType">Type of the created item.</param>
422 /// <param name="minimum">Minimum number of elements.</param>
423 /// <param name="maximum">Maximum number of elements.</param>
424 public SequenceItem(Type elementType, int minimum, int maximum) : base(elementType)
425 {
426 this.minimum = minimum;
427 this.maximum = maximum;
428 }
429 }
430
431 /// <summary>
432 /// Enumerator for the ElementCollection.
433 /// </summary>
434 private class ElementCollectionEnumerator : IEnumerator
435 {
436 private ElementCollection collection;
437 private Stack collectionStack;
438
439 /// <summary>
440 /// Creates a new ElementCollectionEnumerator.
441 /// </summary>
442 /// <param name="collection">The collection to create an enumerator for.</param>
443 public ElementCollectionEnumerator(ElementCollection collection)
444 {
445 this.collection = collection;
446 }
447
448 /// <summary>
449 /// Gets the current object from the enumerator.
450 /// </summary>
451 public object Current
452 {
453 get
454 {
455 if (this.collectionStack != null && this.collectionStack.Count > 0)
456 {
457 CollectionSymbol symbol = (CollectionSymbol)this.collectionStack.Peek();
458 object container = symbol.Collection.items[symbol.ContainerIndex];
459
460 CollectionItem collectionItem = container as CollectionItem;
461 if (collectionItem != null)
462 {
463 return collectionItem.Elements[symbol.ItemIndex];
464 }
465
466 throw new InvalidOperationException(String.Format(
467 CultureInfo.InvariantCulture,
468 "Element of type {0} found in enumerator. Must be ChoiceItem or SequenceItem.",
469 container.GetType().Name));
470 }
471
472 return null;
473 }
474 }
475
476 /// <summary>
477 /// Resets the enumerator to the beginning.
478 /// </summary>
479 public void Reset()
480 {
481 if (this.collectionStack != null)
482 {
483 this.collectionStack.Clear();
484 this.collectionStack = null;
485 }
486 }
487
488 /// <summary>
489 /// Moves the enumerator to the next item.
490 /// </summary>
491 /// <returns>True if there is a next item, false otherwise.</returns>
492 public bool MoveNext()
493 {
494 if (this.collectionStack == null)
495 {
496 if (this.collection.Count == 0)
497 {
498 return false;
499 }
500
501 this.collectionStack = new Stack();
502 this.collectionStack.Push(new CollectionSymbol(this.collection));
503 }
504
505 CollectionSymbol symbol = (CollectionSymbol)this.collectionStack.Peek();
506
507 if (this.FindNext(symbol))
508 {
509 return true;
510 }
511
512 this.collectionStack.Pop();
513 if (this.collectionStack.Count == 0)
514 {
515 return false;
516 }
517
518 return this.MoveNext();
519 }
520
521 /// <summary>
522 /// Pushes a collection onto the stack.
523 /// </summary>
524 /// <param name="collection">The collection to push.</param>
525 private void PushCollection(ElementCollection collection)
526 {
527 if (collection.Count <= 0)
528 {
529 throw new ArgumentException(String.Format(
530 CultureInfo.InvariantCulture,
531 "Collection has {0} elements. Must have at least one.",
532 collection.Count));
533 }
534
535 CollectionSymbol symbol = new CollectionSymbol(collection);
536 this.collectionStack.Push(symbol);
537 this.FindNext(symbol);
538 }
539
540 /// <summary>
541 /// Finds the next item from a given symbol.
542 /// </summary>
543 /// <param name="symbol">The symbol to start looking from.</param>
544 /// <returns>True if a next element is found, false otherwise.</returns>
545 private bool FindNext(CollectionSymbol symbol)
546 {
547 object container = symbol.Collection.items[symbol.ContainerIndex];
548
549 CollectionItem collectionItem = container as CollectionItem;
550 if (collectionItem != null)
551 {
552 if (symbol.ItemIndex + 1 < collectionItem.Elements.Count)
553 {
554 symbol.ItemIndex++;
555 return true;
556 }
557 }
558
559 ElementCollection elementCollection = container as ElementCollection;
560 if (elementCollection != null && elementCollection.Count > 0 && symbol.ItemIndex == -1)
561 {
562 symbol.ItemIndex++;
563 this.PushCollection(elementCollection);
564 return true;
565 }
566
567 symbol.ItemIndex = 0;
568
569 for (int i = symbol.ContainerIndex + 1; i < symbol.Collection.items.Count; ++i)
570 {
571 object nestedContainer = symbol.Collection.items[i];
572
573 CollectionItem nestedCollectionItem = nestedContainer as CollectionItem;
574 if (nestedCollectionItem != null)
575 {
576 if (nestedCollectionItem.Elements.Count > 0)
577 {
578 symbol.ContainerIndex = i;
579 return true;
580 }
581 }
582
583 ElementCollection nestedElementCollection = nestedContainer as ElementCollection;
584 if (nestedElementCollection != null && nestedElementCollection.Count > 0)
585 {
586 symbol.ContainerIndex = i;
587 this.PushCollection(nestedElementCollection);
588 return true;
589 }
590 }
591
592 return false;
593 }
594
595 /// <summary>
596 /// Class representing a single point in the collection. Consists of an ElementCollection,
597 /// a container index, and an index into the container.
598 /// </summary>
599 private class CollectionSymbol
600 {
601 private ElementCollection collection;
602 private int containerIndex;
603 private int itemIndex = -1;
604
605 /// <summary>
606 /// Creates a new CollectionSymbol.
607 /// </summary>
608 /// <param name="collection">The collection for the symbol.</param>
609 public CollectionSymbol(ElementCollection collection)
610 {
611 this.collection = collection;
612 }
613
614 /// <summary>
615 /// Gets the collection for the symbol.
616 /// </summary>
617 public ElementCollection Collection
618 {
619 get { return this.collection; }
620 }
621
622 /// <summary>
623 /// Gets and sets the index of the container in the collection.
624 /// </summary>
625 public int ContainerIndex
626 {
627 get { return this.containerIndex; }
628 set { this.containerIndex = value; }
629 }
630
631 /// <summary>
632 /// Gets and sets the index of the item in the container.
633 /// </summary>
634 public int ItemIndex
635 {
636 get { return this.itemIndex; }
637 set { this.itemIndex = value; }
638 }
639 }
640 }
641 }
642}
diff --git a/src/internal/WixBuildTools.XsdGen/StronglyTypedClasses.cs b/src/internal/WixBuildTools.XsdGen/StronglyTypedClasses.cs
new file mode 100644
index 00000000..4a41f8a9
--- /dev/null
+++ b/src/internal/WixBuildTools.XsdGen/StronglyTypedClasses.cs
@@ -0,0 +1,1498 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Tools
4{
5 using System;
6 using System.CodeDom;
7 using System.Collections;
8 using System.Collections.Generic;
9 using System.Collections.Specialized;
10 using System.IO;
11 using System.Reflection;
12 using System.Text;
13 using System.Text.RegularExpressions;
14 using System.Xml;
15 using System.Xml.Schema;
16
17 /// <summary>
18 /// Type containing static Generate method, which fills in a compile unit from a
19 /// given schema.
20 /// </summary>
21 internal class StronglyTypedClasses
22 {
23 private static string outputXmlComment = "Processes this element and all child elements into an XmlWriter.";
24 private static Hashtable simpleTypeNamesToClrTypeNames;
25 private static Dictionary<string, EnumDeclaration> typeNamesToEnumDeclarations;
26 private static Dictionary<EnumDeclaration, CodeTypeDeclaration> enumsToParseMethodClasses;
27 private static Regex multiUppercaseNameRegex = new Regex("[A-Z][A-Z][A-Z]", RegexOptions.Compiled);
28 private static Dictionary<string, XmlSchemaAttributeGroup> refToAttributeGroups;
29 private static CodeTypeDeclaration enumHelperClass;
30
31 /// <summary>
32 /// Private constructor for static class.
33 /// </summary>
34 private StronglyTypedClasses()
35 {
36 }
37
38 /// <summary>
39 /// Generates strongly typed serialization classes for the given schema document
40 /// under the given namespace and generates a code compile unit.
41 /// </summary>
42 /// <param name="xmlSchema">Schema document to generate classes for.</param>
43 /// <param name="generateNamespace">Namespace to be used for the generated code.</param>
44 /// <param name="commonNamespace">Namespace in which to find common classes and interfaces,
45 /// like ISchemaElement.</param>
46 /// <returns>A fully populated CodeCompileUnit, which can be serialized in the language of choice.</returns>
47 public static CodeCompileUnit Generate(XmlSchema xmlSchema, string generateNamespace, string commonNamespace)
48 {
49 if (xmlSchema == null)
50 {
51 throw new ArgumentNullException("xmlSchema");
52 }
53 if (generateNamespace == null)
54 {
55 throw new ArgumentNullException("generateNamespace");
56 }
57
58 simpleTypeNamesToClrTypeNames = new Hashtable();
59 typeNamesToEnumDeclarations = new Dictionary<string, EnumDeclaration>();
60 refToAttributeGroups = new Dictionary<string, XmlSchemaAttributeGroup>();
61 enumsToParseMethodClasses = new Dictionary<EnumDeclaration, CodeTypeDeclaration>();
62
63 CodeCompileUnit codeCompileUnit = new CodeCompileUnit();
64 CodeNamespace codeNamespace = new CodeNamespace(generateNamespace);
65 codeCompileUnit.Namespaces.Add(codeNamespace);
66 codeNamespace.Imports.Add(new CodeNamespaceImport("System"));
67 codeNamespace.Imports.Add(new CodeNamespaceImport("System.CodeDom.Compiler")); // for GeneratedCodeAttribute
68 codeNamespace.Imports.Add(new CodeNamespaceImport("System.Collections"));
69 codeNamespace.Imports.Add(new CodeNamespaceImport("System.Diagnostics.CodeAnalysis"));
70 codeNamespace.Imports.Add(new CodeNamespaceImport("System.Globalization"));
71 codeNamespace.Imports.Add(new CodeNamespaceImport("System.Xml"));
72 if (commonNamespace != null)
73 {
74 codeNamespace.Imports.Add(new CodeNamespaceImport(commonNamespace));
75 }
76
77 // NOTE: This hash table serves double duty so be sure to have the XSD
78 // type name mapped to the CLR type name *and* the CLR type name
79 // mapped to the same CLR type name. Look at long and bool for
80 // examples below (and before you ask, no I don't know why DateTime
81 // just works).
82 simpleTypeNamesToClrTypeNames.Add("dateTime", "DateTime");
83 simpleTypeNamesToClrTypeNames.Add("integer", "int");
84 simpleTypeNamesToClrTypeNames.Add("int", "int");
85 simpleTypeNamesToClrTypeNames.Add("NMTOKEN", "string");
86 simpleTypeNamesToClrTypeNames.Add("string", "string");
87 simpleTypeNamesToClrTypeNames.Add("nonNegativeInteger", "long");
88 simpleTypeNamesToClrTypeNames.Add("long", "long");
89 simpleTypeNamesToClrTypeNames.Add("boolean", "bool");
90 simpleTypeNamesToClrTypeNames.Add("bool", "bool");
91
92 xmlSchema.Compile(null);
93
94 foreach (XmlSchemaAttributeGroup schemaAttributeGroup in xmlSchema.AttributeGroups.Values)
95 {
96 refToAttributeGroups.Add(schemaAttributeGroup.Name, schemaAttributeGroup);
97 }
98
99 foreach (XmlSchemaObject schemaObject in xmlSchema.SchemaTypes.Values)
100 {
101 XmlSchemaSimpleType schemaSimpleType = schemaObject as XmlSchemaSimpleType;
102 if (schemaSimpleType != null)
103 {
104 ProcessSimpleType(schemaSimpleType, codeNamespace);
105 }
106 }
107
108 foreach (XmlSchemaObject schemaObject in xmlSchema.SchemaTypes.Values)
109 {
110 XmlSchemaComplexType schemaComplexType = schemaObject as XmlSchemaComplexType;
111 if (schemaComplexType != null)
112 {
113 ProcessComplexType(schemaComplexType, codeNamespace);
114 }
115 }
116
117 foreach (XmlSchemaObject schemaObject in xmlSchema.Elements.Values)
118 {
119 XmlSchemaElement schemaElement = schemaObject as XmlSchemaElement;
120 if (schemaElement != null)
121 {
122 ProcessElement(schemaElement, codeNamespace);
123 }
124 }
125
126 return codeCompileUnit;
127 }
128
129 /// <summary>
130 /// Processes an XmlSchemaElement into corresponding types.
131 /// </summary>
132 /// <param name="schemaElement">XmlSchemaElement to be processed.</param>
133 /// <param name="codeNamespace">CodeNamespace to be used when outputting code.</param>
134 private static void ProcessElement(XmlSchemaElement schemaElement, CodeNamespace codeNamespace)
135 {
136 string elementType = schemaElement.SchemaTypeName.Name;
137 string elementNamespace = schemaElement.QualifiedName.Namespace;
138 string elementDocumentation = GetDocumentation(schemaElement.Annotation);
139
140 if ((elementType == null || elementType.Length == 0) && schemaElement.SchemaType != null)
141 {
142 ProcessComplexType(schemaElement.Name, elementNamespace, (XmlSchemaComplexType)schemaElement.SchemaType, elementDocumentation, codeNamespace);
143 }
144 else
145 {
146 if (elementType == null || elementType.Length == 0)
147 {
148 elementType = "string";
149 }
150
151 CodeTypeDeclaration typeDeclaration = new CodeTypeDeclaration(schemaElement.Name);
152 typeDeclaration.CustomAttributes.Add(GetGeneratedCodeAttribute());
153 typeDeclaration.Attributes = MemberAttributes.Public;
154 typeDeclaration.IsClass = true;
155
156 if (elementDocumentation != null)
157 {
158 GenerateSummaryComment(typeDeclaration.Comments, elementDocumentation);
159 }
160
161 CodeMemberMethod outputXmlMethod = new CodeMemberMethod();
162 outputXmlMethod.Attributes = MemberAttributes.Public;
163 outputXmlMethod.ImplementationTypes.Add("ISchemaElement");
164 outputXmlMethod.Name = "OutputXml";
165 outputXmlMethod.Parameters.Add(new CodeParameterDeclarationExpression("XmlWriter", "writer"));
166 outputXmlMethod.Statements.Add(GetArgumentNullCheckStatement("writer", false));
167 outputXmlMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteStartElement", new CodeSnippetExpression(String.Concat("\"", schemaElement.Name, "\"")), new CodeSnippetExpression(String.Concat("\"", elementNamespace, "\""))));
168 GenerateSummaryComment(outputXmlMethod.Comments, outputXmlComment);
169
170 if (simpleTypeNamesToClrTypeNames.ContainsKey(elementType))
171 {
172 CodeMemberField parentField = new CodeMemberField("ISchemaElement", "parentElement");
173 typeDeclaration.Members.Add(parentField);
174
175 CodeMemberProperty parentProperty = new CodeMemberProperty();
176 parentProperty.Attributes = MemberAttributes.Public;
177 parentProperty.ImplementationTypes.Add("ISchemaElement");
178 parentProperty.Name = "ParentElement";
179 parentProperty.Type = new CodeTypeReference("ISchemaElement");
180 parentProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "parentElement")));
181 parentProperty.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "parentElement"), new CodeVariableReferenceExpression("value")));
182 typeDeclaration.Members.Add(parentProperty);
183
184 CodeMemberMethod setAttributeMethod = new CodeMemberMethod();
185 setAttributeMethod.Attributes = MemberAttributes.Public;
186 setAttributeMethod.ImplementationTypes.Add("ISetAttributes");
187 setAttributeMethod.Name = "SetAttribute";
188 setAttributeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "name"));
189 setAttributeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "value"));
190 setAttributeMethod.PrivateImplementationType = new CodeTypeReference("ISetAttributes");
191 setAttributeMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes"));
192 setAttributeMethod.Statements.Add(GetArgumentNullCheckStatement("name", true));
193
194 GenerateFieldAndProperty("Content", (string)simpleTypeNamesToClrTypeNames[elementType], typeDeclaration, outputXmlMethod, setAttributeMethod, null, elementDocumentation, true, false);
195
196 typeDeclaration.Members.Add(setAttributeMethod);
197 typeDeclaration.BaseTypes.Add(new CodeTypeReference("ISetAttributes"));
198 }
199 else
200 {
201 typeDeclaration.BaseTypes.Add(elementType);
202 outputXmlMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeBaseReferenceExpression(), "OutputXml", new CodeVariableReferenceExpression("writer")));
203 outputXmlMethod.Attributes |= MemberAttributes.Override;
204 }
205
206 outputXmlMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteEndElement"));
207
208 typeDeclaration.BaseTypes.Add(new CodeTypeReference("ISchemaElement"));
209 typeDeclaration.Members.Add(outputXmlMethod);
210 codeNamespace.Types.Add(typeDeclaration);
211 }
212 }
213
214 /// <summary>
215 /// Processes an XmlSchemaComplexType into corresponding types.
216 /// </summary>
217 /// <param name="complexType">XmlSchemaComplexType to be processed.</param>
218 /// <param name="codeNamespace">CodeNamespace to be used when outputting code.</param>
219 private static void ProcessComplexType(XmlSchemaComplexType complexType, CodeNamespace codeNamespace)
220 {
221 CodeMemberMethod outputXmlMethod = new CodeMemberMethod();
222 outputXmlMethod.Attributes = MemberAttributes.Public;
223 outputXmlMethod.ImplementationTypes.Add("ISchemaElement");
224 outputXmlMethod.Name = "OutputXml";
225 outputXmlMethod.Parameters.Add(new CodeParameterDeclarationExpression("XmlWriter", "writer"));
226 outputXmlMethod.Statements.Add(GetArgumentNullCheckStatement("writer", false));
227 GenerateSummaryComment(outputXmlMethod.Comments, outputXmlComment);
228
229 CodeMemberMethod setAttributeMethod = new CodeMemberMethod();
230 setAttributeMethod.Attributes = MemberAttributes.Public;
231 setAttributeMethod.ImplementationTypes.Add("ISetAttributes");
232 setAttributeMethod.Name = "SetAttribute";
233 setAttributeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "name"));
234 setAttributeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "value"));
235 setAttributeMethod.PrivateImplementationType = new CodeTypeReference("ISetAttributes");
236 setAttributeMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes"));
237 setAttributeMethod.Statements.Add(GetArgumentNullCheckStatement("name", true));
238
239 string documentation = GetDocumentation(complexType.Annotation);
240
241 ProcessSimpleContent(complexType.Name, (XmlSchemaSimpleContentExtension)complexType.ContentModel.Content, documentation, codeNamespace, outputXmlMethod, setAttributeMethod, true);
242 }
243
244 /// <summary>
245 /// Processes an XmlSchemaComplexType into corresponding types.
246 /// </summary>
247 /// <param name="typeName">Name to use for the type being output.</param>
248 /// <param name="elementNamespace">Namespace of the xml element.</param>
249 /// <param name="complexType">XmlSchemaComplexType to be processed.</param>
250 /// <param name="documentation">Documentation for the element.</param>
251 /// <param name="codeNamespace">CodeNamespace to be used when outputting code.</param>
252 private static void ProcessComplexType(string typeName, string elementNamespace, XmlSchemaComplexType complexType, string documentation, CodeNamespace codeNamespace)
253 {
254 CodeMemberMethod outputXmlMethod = new CodeMemberMethod();
255 outputXmlMethod.Attributes = MemberAttributes.Public;
256 outputXmlMethod.ImplementationTypes.Add("ISchemaElement");
257 outputXmlMethod.Name = "OutputXml";
258 outputXmlMethod.Parameters.Add(new CodeParameterDeclarationExpression("XmlWriter", "writer"));
259 outputXmlMethod.Statements.Add(GetArgumentNullCheckStatement("writer", false));
260 outputXmlMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteStartElement", new CodeSnippetExpression(String.Concat("\"", typeName, "\"")), new CodeSnippetExpression(String.Concat("\"", elementNamespace, "\""))));
261 GenerateSummaryComment(outputXmlMethod.Comments, outputXmlComment);
262
263 CodeMemberMethod setAttributeMethod = new CodeMemberMethod();
264 setAttributeMethod.Attributes = MemberAttributes.Public;
265 setAttributeMethod.ImplementationTypes.Add("ISetAttributes");
266 setAttributeMethod.Name = "SetAttribute";
267 setAttributeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "name"));
268 setAttributeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "value"));
269 setAttributeMethod.PrivateImplementationType = new CodeTypeReference("ISetAttributes");
270 setAttributeMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes"));
271 setAttributeMethod.Statements.Add(GetArgumentNullCheckStatement("name", true));
272
273 if (complexType.ContentModel == null)
274 {
275 CodeTypeDeclaration typeDeclaration = new CodeTypeDeclaration(typeName);
276 typeDeclaration.CustomAttributes.Add(GetGeneratedCodeAttribute());
277 typeDeclaration.Attributes = MemberAttributes.Public;
278 typeDeclaration.IsClass = true;
279 CodeIterationStatement childEnumStatement = null;
280
281 if (documentation != null)
282 {
283 GenerateSummaryComment(typeDeclaration.Comments, documentation);
284 }
285
286 if (complexType.Particle != null)
287 {
288 CodeMemberField childrenField = new CodeMemberField("ElementCollection", "children");
289 typeDeclaration.Members.Add(childrenField);
290
291 CodeMemberProperty childrenProperty = new CodeMemberProperty();
292 childrenProperty.Attributes = MemberAttributes.Public;
293 childrenProperty.ImplementationTypes.Add("IParentElement");
294 childrenProperty.Name = "Children";
295 childrenProperty.Type = new CodeTypeReference("IEnumerable");
296 childrenProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "children")));
297 typeDeclaration.Members.Add(childrenProperty);
298
299 CodeMemberProperty filterChildrenProperty = new CodeMemberProperty();
300 filterChildrenProperty.Attributes = MemberAttributes.Public;
301 filterChildrenProperty.ImplementationTypes.Add("IParentElement");
302 filterChildrenProperty.Name = "Item";
303 filterChildrenProperty.Parameters.Add(new CodeParameterDeclarationExpression(typeof(Type), "childType"));
304 filterChildrenProperty.Type = new CodeTypeReference("IEnumerable");
305 filterChildrenProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "children"), "Filter", new CodeVariableReferenceExpression("childType"))));
306 filterChildrenProperty.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Design", "CA1043:UseIntegralOrStringArgumentForIndexers"));
307 typeDeclaration.Members.Add(filterChildrenProperty);
308
309 CodeMemberMethod addChildMethod = new CodeMemberMethod();
310 addChildMethod.Attributes = MemberAttributes.Public;
311 addChildMethod.ImplementationTypes.Add("IParentElement");
312 addChildMethod.Name = "AddChild";
313 addChildMethod.Parameters.Add(new CodeParameterDeclarationExpression("ISchemaElement", "child"));
314 addChildMethod.Statements.Add(GetArgumentNullCheckStatement("child", false));
315 CodeExpressionStatement addChildStatement = new CodeExpressionStatement(new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "children"), "AddElement", new CodeVariableReferenceExpression("child")));
316 addChildMethod.Statements.Add(addChildStatement);
317 CodeAssignStatement setParentStatement = new CodeAssignStatement(new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("child"), "ParentElement"), new CodeThisReferenceExpression());
318 addChildMethod.Statements.Add(setParentStatement);
319 typeDeclaration.Members.Add(addChildMethod);
320
321 CodeMemberMethod removeChildMethod = new CodeMemberMethod();
322 removeChildMethod.Attributes = MemberAttributes.Public;
323 removeChildMethod.ImplementationTypes.Add("IParentElement");
324 removeChildMethod.Name = "RemoveChild";
325 removeChildMethod.Parameters.Add(new CodeParameterDeclarationExpression("ISchemaElement", "child"));
326 removeChildMethod.Statements.Add(GetArgumentNullCheckStatement("child", false));
327 CodeExpressionStatement removeChildStatement = new CodeExpressionStatement(new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "children"), "RemoveElement", new CodeVariableReferenceExpression("child")));
328 removeChildMethod.Statements.Add(removeChildStatement);
329 CodeAssignStatement nullParentStatement = new CodeAssignStatement(new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("child"), "ParentElement"), new CodePrimitiveExpression(null));
330 removeChildMethod.Statements.Add(nullParentStatement);
331 typeDeclaration.Members.Add(removeChildMethod);
332
333 CodeMemberMethod createChildMethod = new CodeMemberMethod();
334 createChildMethod.Attributes = MemberAttributes.Public;
335 createChildMethod.ImplementationTypes.Add("ICreateChildren");
336 createChildMethod.Name = "CreateChild";
337 createChildMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "childName"));
338 createChildMethod.PrivateImplementationType = new CodeTypeReference("ICreateChildren");
339 createChildMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes"));
340 createChildMethod.ReturnType = new CodeTypeReference("ISchemaElement");
341 createChildMethod.Statements.Add(GetArgumentNullCheckStatement("childName", true));
342 createChildMethod.Statements.Add(new CodeVariableDeclarationStatement("ISchemaElement", "childValue", new CodePrimitiveExpression(null)));
343
344 CodeConstructor typeConstructor = new CodeConstructor();
345 typeConstructor.Attributes = MemberAttributes.Public;
346
347 CodeVariableReferenceExpression collectionVariable = null;
348
349 XmlSchemaChoice schemaChoice = complexType.Particle as XmlSchemaChoice;
350 if (schemaChoice != null)
351 {
352 collectionVariable = ProcessSchemaGroup(schemaChoice, typeConstructor, createChildMethod);
353 }
354 else
355 {
356 XmlSchemaSequence schemaSequence = complexType.Particle as XmlSchemaSequence;
357 if (schemaSequence != null)
358 {
359 collectionVariable = ProcessSchemaGroup(schemaSequence, typeConstructor, createChildMethod);
360 }
361 }
362
363 typeConstructor.Statements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "children"), collectionVariable));
364 typeDeclaration.Members.Add(typeConstructor);
365
366 CodeConditionStatement childNameNotFound = new CodeConditionStatement();
367 childNameNotFound.Condition = new CodeBinaryOperatorExpression(new CodePrimitiveExpression(null), CodeBinaryOperatorType.ValueEquality, new CodeVariableReferenceExpression("childValue"));
368 childNameNotFound.TrueStatements.Add(new CodeThrowExceptionStatement(new CodeObjectCreateExpression("InvalidOperationException", new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("String"), "Concat", new CodeVariableReferenceExpression("childName"), new CodeSnippetExpression("\" is not a valid child name.\"")))));
369 createChildMethod.Statements.Add(childNameNotFound);
370
371 if (createChildMethod.Statements.Count > 8)
372 {
373 createChildMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity"));
374 }
375
376 createChildMethod.Statements.Add(new CodeMethodReturnStatement(new CodeVariableReferenceExpression("childValue")));
377 typeDeclaration.Members.Add(createChildMethod);
378
379 childEnumStatement = new CodeIterationStatement();
380 childEnumStatement.InitStatement = new CodeVariableDeclarationStatement("IEnumerator", "enumerator", new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "children"), "GetEnumerator"));
381 childEnumStatement.TestExpression = new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("enumerator"), "MoveNext");
382 childEnumStatement.Statements.Add(new CodeVariableDeclarationStatement("ISchemaElement", "childElement", new CodeCastExpression("ISchemaElement", new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("enumerator"), "Current"))));
383 childEnumStatement.Statements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("childElement"), "OutputXml", new CodeVariableReferenceExpression("writer")));
384 childEnumStatement.IncrementStatement = new CodeExpressionStatement(new CodeSnippetExpression(""));
385
386 typeDeclaration.BaseTypes.Add(new CodeTypeReference("IParentElement"));
387 typeDeclaration.BaseTypes.Add(new CodeTypeReference("ICreateChildren"));
388 }
389
390 // TODO: Handle xs:anyAttribute.
391 ProcessAttributes(complexType.Attributes, typeDeclaration, outputXmlMethod, setAttributeMethod);
392
393 if (childEnumStatement != null)
394 {
395 outputXmlMethod.Statements.Add(childEnumStatement);
396 }
397
398 typeDeclaration.BaseTypes.Add(new CodeTypeReference("ISchemaElement"));
399 typeDeclaration.BaseTypes.Add(new CodeTypeReference("ISetAttributes"));
400
401 CodeMemberField parentField = new CodeMemberField("ISchemaElement", "parentElement");
402 typeDeclaration.Members.Add(parentField);
403
404 CodeMemberProperty parentProperty = new CodeMemberProperty();
405 parentProperty.Attributes = MemberAttributes.Public;
406 parentProperty.ImplementationTypes.Add("ISchemaElement");
407 parentProperty.Name = "ParentElement";
408 parentProperty.Type = new CodeTypeReference("ISchemaElement");
409 parentProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "parentElement")));
410 parentProperty.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "parentElement"), new CodeVariableReferenceExpression("value")));
411 typeDeclaration.Members.Add(parentProperty);
412
413 if (outputXmlMethod.Statements.Count > 8)
414 {
415 outputXmlMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity"));
416 }
417
418 if (setAttributeMethod.Statements.Count > 8)
419 {
420 setAttributeMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity"));
421 }
422
423 typeDeclaration.Members.Add(outputXmlMethod);
424 typeDeclaration.Members.Add(setAttributeMethod);
425 codeNamespace.Types.Add(typeDeclaration);
426 }
427 else
428 {
429 ProcessSimpleContent(typeName, (XmlSchemaSimpleContentExtension)complexType.ContentModel.Content, documentation, codeNamespace, outputXmlMethod, setAttributeMethod, false);
430 }
431
432 outputXmlMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteEndElement"));
433 }
434
435 /// <summary>
436 /// Processes a collection of attributes, generating the required fields and properties.
437 /// </summary>
438 /// <param name="attributes">List of attribute or attributeGroupRef elements being processed.</param>
439 /// <param name="typeDeclaration">CodeTypeDeclaration to be used when outputting code.</param>
440 /// <param name="outputXmlMethod">Member method for the OutputXml method.</param>
441 /// <param name="setAttributeMethod">Member method for the SetAttribute method.</param>
442 private static void ProcessAttributes(XmlSchemaObjectCollection attributes, CodeTypeDeclaration typeDeclaration, CodeMemberMethod outputXmlMethod, CodeMemberMethod setAttributeMethod)
443 {
444 foreach (XmlSchemaObject schemaObject in attributes)
445 {
446 XmlSchemaAttribute schemaAttribute = schemaObject as XmlSchemaAttribute;
447 if (schemaAttribute != null)
448 {
449 ProcessAttribute(schemaAttribute, typeDeclaration, outputXmlMethod, setAttributeMethod);
450 }
451 else
452 {
453 XmlSchemaAttributeGroupRef schemaAttributeGroupRef = schemaObject as XmlSchemaAttributeGroupRef;
454 if (schemaAttributeGroupRef != null)
455 {
456 XmlSchemaAttributeGroup schemaAttributeGroup = refToAttributeGroups[schemaAttributeGroupRef.RefName.Name];
457 // recurse!
458 ProcessAttributes(schemaAttributeGroup.Attributes, typeDeclaration, outputXmlMethod, setAttributeMethod);
459 }
460 }
461 }
462 }
463
464 /// <summary>
465 /// Processes an XmlSchemaGroupBase element.
466 /// </summary>
467 /// <param name="schemaGroup">Element group to process.</param>
468 /// <param name="constructor">Constructor to which statements should be added.</param>
469 /// <param name="createChildMethod">Method used for creating children on read-in.</param>
470 /// <returns>A reference to the local variable containing the collection.</returns>
471 private static CodeVariableReferenceExpression ProcessSchemaGroup(XmlSchemaGroupBase schemaGroup, CodeConstructor constructor, CodeMemberMethod createChildMethod)
472 {
473 return ProcessSchemaGroup(schemaGroup, constructor, createChildMethod, 0);
474 }
475
476 /// <summary>
477 /// Processes an XmlSchemaGroupBase element.
478 /// </summary>
479 /// <param name="schemaGroup">Element group to process.</param>
480 /// <param name="constructor">Constructor to which statements should be added.</param>
481 /// <param name="createChildMethod">Method used for creating children on read-in.</param>
482 /// <param name="depth">Depth to which this collection is nested.</param>
483 /// <returns>A reference to the local variable containing the collection.</returns>
484 private static CodeVariableReferenceExpression ProcessSchemaGroup(XmlSchemaGroupBase schemaGroup, CodeConstructor constructor, CodeMemberMethod createChildMethod, int depth)
485 {
486 string collectionName = String.Format("childCollection{0}", depth);
487 CodeVariableReferenceExpression collectionVariableReference = new CodeVariableReferenceExpression(collectionName);
488 CodeVariableDeclarationStatement collectionStatement = new CodeVariableDeclarationStatement("ElementCollection", collectionName);
489 if (schemaGroup is XmlSchemaChoice)
490 {
491 collectionStatement.InitExpression = new CodeObjectCreateExpression("ElementCollection", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("ElementCollection.CollectionType"), "Choice"));
492 }
493 else
494 {
495 collectionStatement.InitExpression = new CodeObjectCreateExpression("ElementCollection", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("ElementCollection.CollectionType"), "Sequence"));
496 }
497 constructor.Statements.Add(collectionStatement);
498
499 foreach (XmlSchemaObject obj in schemaGroup.Items)
500 {
501 XmlSchemaElement schemaElement = obj as XmlSchemaElement;
502 if (schemaElement != null)
503 {
504 if (schemaGroup is XmlSchemaChoice)
505 {
506 CodeMethodInvokeExpression addItemInvoke = new CodeMethodInvokeExpression(collectionVariableReference, "AddItem", new CodeObjectCreateExpression("ElementCollection.ChoiceItem", new CodeTypeOfExpression(schemaElement.RefName.Name)));
507 constructor.Statements.Add(addItemInvoke);
508 }
509 else
510 {
511 CodeMethodInvokeExpression addItemInvoke = new CodeMethodInvokeExpression(collectionVariableReference, "AddItem", new CodeObjectCreateExpression("ElementCollection.SequenceItem", new CodeTypeOfExpression(schemaElement.RefName.Name)));
512 constructor.Statements.Add(addItemInvoke);
513 }
514
515 CodeConditionStatement createChildIf = new CodeConditionStatement();
516 createChildIf.Condition = new CodeBinaryOperatorExpression(new CodeSnippetExpression(String.Concat("\"", schemaElement.RefName.Name, "\"")), CodeBinaryOperatorType.ValueEquality, new CodeVariableReferenceExpression("childName"));
517 createChildIf.TrueStatements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression("childValue"), new CodeObjectCreateExpression(schemaElement.RefName.Name)));
518 createChildMethod.Statements.Add(createChildIf);
519
520 continue;
521 }
522
523 XmlSchemaAny schemaAny = obj as XmlSchemaAny;
524 if (schemaAny != null)
525 {
526 if (schemaGroup is XmlSchemaChoice)
527 {
528 CodeMethodInvokeExpression addItemInvoke = new CodeMethodInvokeExpression(collectionVariableReference, "AddItem", new CodeObjectCreateExpression("ElementCollection.ChoiceItem", new CodeTypeOfExpression("ISchemaElement")));
529 constructor.Statements.Add(addItemInvoke);
530 }
531 else
532 {
533 CodeMethodInvokeExpression addItemInvoke = new CodeMethodInvokeExpression(collectionVariableReference, "AddItem", new CodeObjectCreateExpression("ElementCollection.SequenceItem", new CodeTypeOfExpression("ISchemaElement"), new CodeSnippetExpression("0"), new CodeSnippetExpression("-1")));
534 constructor.Statements.Add(addItemInvoke);
535 }
536
537 continue;
538 }
539
540 XmlSchemaGroupBase schemaGroupBase = obj as XmlSchemaGroupBase;
541 if (schemaGroupBase != null)
542 {
543 CodeVariableReferenceExpression nestedCollectionReference = ProcessSchemaGroup(schemaGroupBase, constructor, createChildMethod, depth + 1);
544 CodeMethodInvokeExpression addCollectionInvoke = new CodeMethodInvokeExpression(collectionVariableReference, "AddCollection", nestedCollectionReference);
545 constructor.Statements.Add(addCollectionInvoke);
546
547 continue;
548 }
549 }
550
551 return collectionVariableReference;
552 }
553
554 /// <summary>
555 /// Processes an XmlSchemaSimpleContentExtension into corresponding types.
556 /// </summary>
557 /// <param name="typeName">Name of the type being generated.</param>
558 /// <param name="simpleContent">XmlSchemaSimpleContentExtension being processed.</param>
559 /// <param name="documentation">Documentation for the simple content.</param>
560 /// <param name="codeNamespace">CodeNamespace to be used when outputting code.</param>
561 /// <param name="outputXmlMethod">Method to use when outputting Xml.</param>
562 /// <param name="setAttributeMethod">Method to use when setting an attribute.</param>
563 /// <param name="abstractClass">If true, generate an abstract class.</param>
564 private static void ProcessSimpleContent(string typeName, XmlSchemaSimpleContentExtension simpleContent, string documentation, CodeNamespace codeNamespace, CodeMemberMethod outputXmlMethod, CodeMemberMethod setAttributeMethod, bool abstractClass)
565 {
566 CodeTypeDeclaration typeDeclaration = new CodeTypeDeclaration(typeName);
567 typeDeclaration.CustomAttributes.Add(GetGeneratedCodeAttribute());
568 typeDeclaration.Attributes = MemberAttributes.Public;
569 typeDeclaration.IsClass = true;
570
571 if (documentation != null)
572 {
573 GenerateSummaryComment(typeDeclaration.Comments, documentation);
574 }
575
576 if (abstractClass)
577 {
578 typeDeclaration.TypeAttributes = System.Reflection.TypeAttributes.Abstract | System.Reflection.TypeAttributes.Public;
579 }
580
581 // TODO: Handle xs:anyAttribute here.
582 foreach (XmlSchemaAttribute schemaAttribute in simpleContent.Attributes)
583 {
584 ProcessAttribute(schemaAttribute, typeDeclaration, outputXmlMethod, setAttributeMethod);
585 }
586
587 // This needs to come last, so that the generation code generates the inner content after the attributes.
588 string contentDocumentation = GetDocumentation(simpleContent.Annotation);
589 GenerateFieldAndProperty("Content", (string)simpleTypeNamesToClrTypeNames[simpleContent.BaseTypeName.Name], typeDeclaration, outputXmlMethod, setAttributeMethod, null, contentDocumentation, true, false);
590
591 typeDeclaration.BaseTypes.Add(new CodeTypeReference("ISchemaElement"));
592 typeDeclaration.BaseTypes.Add(new CodeTypeReference("ISetAttributes"));
593
594 CodeMemberField parentField = new CodeMemberField("ISchemaElement", "parentElement");
595 typeDeclaration.Members.Add(parentField);
596
597 CodeMemberProperty parentProperty = new CodeMemberProperty();
598 parentProperty.Attributes = MemberAttributes.Public;
599 parentProperty.ImplementationTypes.Add("ISchemaElement");
600 parentProperty.Name = "ParentElement";
601 parentProperty.Type = new CodeTypeReference("ISchemaElement");
602 parentProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "parentElement")));
603 parentProperty.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "parentElement"), new CodeVariableReferenceExpression("value")));
604 typeDeclaration.Members.Add(parentProperty);
605
606 if (outputXmlMethod.Statements.Count > 8)
607 {
608 outputXmlMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity"));
609 }
610
611 if (setAttributeMethod.Statements.Count > 8)
612 {
613 setAttributeMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity"));
614 }
615
616 typeDeclaration.Members.Add(outputXmlMethod);
617 typeDeclaration.Members.Add(setAttributeMethod);
618 codeNamespace.Types.Add(typeDeclaration);
619 }
620
621 /// <summary>
622 /// Processes an attribute, generating the required field and property. Potentially generates
623 /// an enum for an attribute restriction.
624 /// </summary>
625 /// <param name="attribute">Attribute element being processed.</param>
626 /// <param name="typeDeclaration">CodeTypeDeclaration to be used when outputting code.</param>
627 /// <param name="outputXmlMethod">Member method for the OutputXml method.</param>
628 /// <param name="setAttributeMethod">Member method for the SetAttribute method.</param>
629 private static void ProcessAttribute(XmlSchemaAttribute attribute, CodeTypeDeclaration typeDeclaration, CodeMemberMethod outputXmlMethod, CodeMemberMethod setAttributeMethod)
630 {
631 string attributeName = attribute.QualifiedName.Name;
632 string rawAttributeType = attribute.AttributeSchemaType.QualifiedName.Name;
633 string attributeType = null;
634 EnumDeclaration enumDeclaration = null;
635 if (rawAttributeType == null || rawAttributeType.Length == 0)
636 {
637 ProcessSimpleType(attributeName, attribute.AttributeSchemaType, true, out enumDeclaration, out attributeType);
638
639 if (enumDeclaration != null)
640 {
641 typeDeclaration.Members.Add(enumDeclaration.TypeDeclaration);
642 AddEnumHelperMethods(enumDeclaration, typeDeclaration);
643 }
644 }
645 else
646 {
647 attributeType = (string)simpleTypeNamesToClrTypeNames[rawAttributeType];
648 }
649
650 string documentation = GetDocumentation(attribute.Annotation);
651
652 // TODO: Handle required fields.
653 GenerateFieldAndProperty(attributeName, attributeType, typeDeclaration, outputXmlMethod, setAttributeMethod, enumDeclaration, documentation, false, false);
654 }
655
656 /// <summary>
657 /// Gets the first sentence of a documentation element and returns it as a string.
658 /// </summary>
659 /// <param name="annotation">The annotation in which to look for a documentation element.</param>
660 /// <returns>The string representing the first sentence, or null if none found.</returns>
661 private static string GetDocumentation(XmlSchemaAnnotation annotation)
662 {
663 string documentation = null;
664
665 if (annotation != null && annotation.Items != null)
666 {
667 foreach (XmlSchemaObject obj in annotation.Items)
668 {
669 XmlSchemaDocumentation schemaDocumentation = obj as XmlSchemaDocumentation;
670 if (schemaDocumentation != null)
671 {
672 if (schemaDocumentation.Markup.Length > 0)
673 {
674 XmlText text = schemaDocumentation.Markup[0] as XmlText;
675 if (text != null)
676 {
677 documentation = text.Value;
678 }
679 }
680 break;
681 }
682 }
683 }
684
685 if (documentation != null)
686 {
687 documentation = documentation.Trim();
688 }
689 return documentation;
690 }
691
692 /// <summary>
693 /// Makes a valid enum value out of the passed in value. May remove spaces, add 'Item' to the
694 /// start if it begins with an integer, or strip out punctuation.
695 /// </summary>
696 /// <param name="enumValue">Enum value to be processed.</param>
697 /// <returns>Enum value with invalid characters removed.</returns>
698 private static string MakeEnumValue(string enumValue)
699 {
700 if (Char.IsDigit(enumValue[0]))
701 {
702 enumValue = String.Concat("Item", enumValue);
703 }
704
705 StringBuilder newValue = new StringBuilder();
706 for (int i = 0; i < enumValue.Length; ++i)
707 {
708 if (!Char.IsPunctuation(enumValue[i]) && !Char.IsSymbol(enumValue[i]) && !Char.IsWhiteSpace(enumValue[i]))
709 {
710 newValue.Append(enumValue[i]);
711 }
712 }
713
714 return newValue.ToString();
715 }
716
717 /// <summary>
718 /// Generates the private field and public property for a piece of data.
719 /// </summary>
720 /// <param name="propertyName">Name of the property being generated.</param>
721 /// <param name="typeName">Name of the type for the property.</param>
722 /// <param name="typeDeclaration">Type declaration into which the field and property should be placed.</param>
723 /// <param name="outputXmlMethod">Member method for the OutputXml method.</param>
724 /// <param name="setAttributeMethod">Member method for the SetAttribute method.</param>
725 /// <param name="enumDeclaration">EnumDeclaration, which is null unless called from a locally defined enum attribute.</param>
726 /// <param name="documentation">Comment string to be placed on the property.</param>
727 /// <param name="nestedContent">If true, the field will be placed in nested content when outputting to XML.</param>
728 /// <param name="requiredField">If true, the generated serialization code will throw if the field is not set.</param>
729 private static void GenerateFieldAndProperty(string propertyName, string typeName, CodeTypeDeclaration typeDeclaration, CodeMemberMethod outputXmlMethod, CodeMemberMethod setAttributeMethod, EnumDeclaration enumDeclaration, string documentation, bool nestedContent, bool requiredField)
730 {
731 string fieldName = String.Concat(propertyName.Substring(0, 1).ToLower(), propertyName.Substring(1), "Field");
732 string fieldNameSet = String.Concat(fieldName, "Set");
733 Type type = GetClrTypeByXmlName(typeName);
734 CodeMemberField fieldMember;
735 if (type == null)
736 {
737 fieldMember = new CodeMemberField(typeName, fieldName);
738 }
739 else
740 {
741 fieldMember = new CodeMemberField(type, fieldName);
742 }
743 fieldMember.Attributes = MemberAttributes.Private;
744 typeDeclaration.Members.Add(fieldMember);
745 typeDeclaration.Members.Add(new CodeMemberField(typeof(bool), fieldNameSet));
746
747 CodeMemberProperty propertyMember = new CodeMemberProperty();
748 propertyMember.Attributes = MemberAttributes.Public | MemberAttributes.Final;
749 if (documentation != null)
750 {
751 GenerateSummaryComment(propertyMember.Comments, documentation);
752 }
753 propertyMember.Name = propertyName;
754 if (type == null)
755 {
756 propertyMember.Type = new CodeTypeReference(typeName);
757 }
758 else
759 {
760 propertyMember.Type = new CodeTypeReference(type);
761 }
762
763 if (propertyMember.Name.StartsWith("src"))
764 {
765 propertyMember.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly"));
766 }
767 else if (StronglyTypedClasses.multiUppercaseNameRegex.Match(propertyMember.Name).Success)
768 {
769 propertyMember.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Naming", "CA1705:LongAcronymsShouldBePascalCased"));
770 }
771
772 CodeMethodReturnStatement returnStatement = new CodeMethodReturnStatement();
773 returnStatement.Expression = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName);
774 propertyMember.GetStatements.Add(returnStatement);
775
776 CodeAssignStatement assignmentStatement = new CodeAssignStatement();
777 propertyMember.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldNameSet), new CodePrimitiveExpression(true)));
778 assignmentStatement.Left = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName);
779 assignmentStatement.Right = new CodePropertySetValueReferenceExpression();
780 propertyMember.SetStatements.Add(assignmentStatement);
781
782 CodeConditionStatement fieldSetStatement = new CodeConditionStatement();
783 fieldSetStatement.Condition = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldNameSet);
784
785 CodeAssignStatement fieldSetAttrStatement = new CodeAssignStatement();
786 fieldSetAttrStatement.Left = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldNameSet);
787 fieldSetAttrStatement.Right = new CodePrimitiveExpression(true);
788
789 CodeConditionStatement attributeNameMatchStatement = new CodeConditionStatement();
790 attributeNameMatchStatement.Condition = new CodeBinaryOperatorExpression(new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), CodeBinaryOperatorType.IdentityEquality, new CodeVariableReferenceExpression("name"));
791
792 string clrTypeName = (string)simpleTypeNamesToClrTypeNames[typeName];
793 switch (clrTypeName)
794 {
795 case "string":
796 if (nestedContent)
797 {
798 fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteString", new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName)));
799 attributeNameMatchStatement.TrueStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodeVariableReferenceExpression("value")));
800 }
801 else
802 {
803 fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteAttributeString", new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName)));
804 attributeNameMatchStatement.TrueStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodeVariableReferenceExpression("value")));
805 }
806 break;
807 case "bool":
808 if (nestedContent)
809 {
810 fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteString", new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), "ToString", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture"))));
811 }
812 else
813 {
814 fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteAttributeString", new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), "ToString", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture"))));
815 }
816 attributeNameMatchStatement.TrueStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("Convert"), "ToBoolean", new CodeVariableReferenceExpression("value"), new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture"))));
817 break;
818 case "int":
819 case "long":
820 if (nestedContent)
821 {
822 fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteString", new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), "ToString", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture"))));
823 }
824 else
825 {
826 fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteAttributeString", new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), "ToString", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture"))));
827 }
828 attributeNameMatchStatement.TrueStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("Convert"), "ToInt32", new CodeVariableReferenceExpression("value"), new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture"))));
829 break;
830 default:
831 if (typeName == "DateTime")
832 {
833 if (nestedContent)
834 {
835 fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteString", new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), "ToString", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture"))));
836 }
837 else
838 {
839 fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteAttributeString", new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), "ToString", new CodePrimitiveExpression("yyyy-MM-ddTHH:mm:ss"), new CodePropertyReferenceExpression(new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture"), "DateTimeFormat"))));
840 }
841 attributeNameMatchStatement.TrueStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("Convert"), "ToDateTime", new CodeVariableReferenceExpression("value"), new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture"))));
842 break;
843 }
844
845 if (enumDeclaration == null)
846 {
847 GenerateOutputForEnum(fieldSetStatement, attributeNameMatchStatement, typeNamesToEnumDeclarations[typeName], fieldName, propertyName);
848 }
849 else
850 {
851 GenerateOutputForEnum(fieldSetStatement, attributeNameMatchStatement, enumDeclaration, fieldName, propertyName);
852 }
853 break;
854 }
855
856 attributeNameMatchStatement.TrueStatements.Add(fieldSetAttrStatement);
857
858 // TODO: Add throw to falseStatements if required field not set.
859 outputXmlMethod.Statements.Add(fieldSetStatement);
860 setAttributeMethod.Statements.Add(attributeNameMatchStatement);
861
862 typeDeclaration.Members.Add(propertyMember);
863 }
864
865 /// <summary>
866 /// Generates output for an enum type. Will generate a switch statement for normal enums, and if statements
867 /// for a flags enum.
868 /// </summary>
869 /// <param name="fieldSetStatement">If statement to add statements to.</param>
870 /// <param name="attributeNameMatchStatement">If statement to add statements to.</param>
871 /// <param name="enumDeclaration">Enum declaration for this field. Could be locally defined enum or global.</param>
872 /// <param name="fieldName">Name of the private field.</param>
873 /// <param name="propertyName">Name of the property (and XML attribute).</param>
874 private static void GenerateOutputForEnum(CodeConditionStatement fieldSetStatement, CodeConditionStatement attributeNameMatchStatement, EnumDeclaration enumDeclaration, string fieldName, string propertyName)
875 {
876 CodeTypeDeclaration enumParent = enumsToParseMethodClasses[enumDeclaration];
877
878 if (enumDeclaration.Flags)
879 {
880 CodeVariableDeclarationStatement outputValueVariable = new CodeVariableDeclarationStatement(typeof(string), "outputValue", new CodeSnippetExpression("\"\""));
881 fieldSetStatement.TrueStatements.Add(outputValueVariable);
882
883 foreach (string key in enumDeclaration.Values)
884 {
885 CodeConditionStatement enumIfStatement = new CodeConditionStatement();
886 enumIfStatement.Condition = new CodeBinaryOperatorExpression(new CodeBinaryOperatorExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), CodeBinaryOperatorType.BitwiseAnd, new CodePropertyReferenceExpression(new CodeSnippetExpression(enumDeclaration.Name), MakeEnumValue(key))), CodeBinaryOperatorType.IdentityInequality, new CodeSnippetExpression("0"));
887 CodeConditionStatement lengthIfStatement = new CodeConditionStatement();
888 lengthIfStatement.Condition = new CodeBinaryOperatorExpression(new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("outputValue"), "Length"), CodeBinaryOperatorType.IdentityInequality, new CodeSnippetExpression("0"));
889 lengthIfStatement.TrueStatements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression("outputValue"), new CodeBinaryOperatorExpression(new CodeVariableReferenceExpression("outputValue"), CodeBinaryOperatorType.Add, new CodeSnippetExpression("\" \""))));
890 enumIfStatement.TrueStatements.Add(lengthIfStatement);
891 enumIfStatement.TrueStatements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression("outputValue"), new CodeBinaryOperatorExpression(new CodeVariableReferenceExpression("outputValue"), CodeBinaryOperatorType.Add, new CodeSnippetExpression(String.Concat("\"", key, "\"")))));
892 fieldSetStatement.TrueStatements.Add(enumIfStatement);
893 }
894
895 attributeNameMatchStatement.TrueStatements.Add(new CodeMethodInvokeExpression(
896 new CodeTypeReferenceExpression(enumParent.Name),
897 String.Concat("TryParse", enumDeclaration.Name),
898 new CodeVariableReferenceExpression("value"),
899 new CodeDirectionExpression(FieldDirection.Out, new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName))));
900
901 fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteAttributeString", new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), new CodeSnippetExpression(String.Concat("outputValue"))));
902 }
903 else
904 {
905 foreach (string key in enumDeclaration.Values)
906 {
907 CodeConditionStatement enumOutStatement = new CodeConditionStatement();
908 enumOutStatement.Condition = new CodeBinaryOperatorExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), CodeBinaryOperatorType.ValueEquality, new CodePropertyReferenceExpression(new CodeSnippetExpression(enumDeclaration.Name), MakeEnumValue(key)));
909 enumOutStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteAttributeString", new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), new CodeSnippetExpression(String.Concat("\"", key, "\""))));
910 fieldSetStatement.TrueStatements.Add(enumOutStatement);
911 }
912
913 attributeNameMatchStatement.TrueStatements.Add(new CodeAssignStatement(
914 new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName),
915 new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(enumParent.Name),
916 String.Concat("Parse", enumDeclaration.Name),
917 new CodeVariableReferenceExpression("value"))));
918 }
919 }
920
921 /// <summary>
922 /// Generates a summary comment.
923 /// </summary>
924 /// <param name="comments">Comments collection to add the comments to.</param>
925 /// <param name="content">Content of the comment.</param>
926 private static void GenerateSummaryComment(CodeCommentStatementCollection comments, string content)
927 {
928 using (StringWriter sw = new StringWriter())
929 {
930 XmlTextWriter writer = null;
931
932 // create the comment as xml to ensure proper escaping of special xml characters
933 try
934 {
935 writer = new XmlTextWriter(sw);
936 writer.Indentation = 0;
937
938 writer.WriteStartElement("summary");
939 writer.WriteString(Environment.NewLine);
940
941 string nextComment;
942 int newlineIndex = content.IndexOf(Environment.NewLine);
943 int offset = 0;
944 while (newlineIndex != -1)
945 {
946 nextComment = content.Substring(offset, newlineIndex - offset).Trim();
947 writer.WriteString(nextComment);
948 writer.WriteString(Environment.NewLine);
949 offset = newlineIndex + Environment.NewLine.Length;
950 newlineIndex = content.IndexOf(Environment.NewLine, offset);
951 }
952 nextComment = content.Substring(offset).Trim();
953 writer.WriteString(nextComment);
954 writer.WriteString(Environment.NewLine);
955
956 writer.WriteEndElement();
957 }
958 finally
959 {
960 if (null != writer)
961 {
962 writer.Close();
963 }
964 }
965
966 // create the comment statements (one per line of xml)
967 using (StringReader sr = new StringReader(sw.ToString()))
968 {
969 string line;
970
971 while (null != (line = sr.ReadLine()))
972 {
973 comments.Add(new CodeCommentStatement(line, true));
974 }
975 }
976 }
977 }
978
979 /// <summary>
980 /// Gets the CLR type for simple XML type.
981 /// </summary>
982 /// <param name="typeName">Plain text name of type.</param>
983 /// <returns>Type corresponding to parameter.</returns>
984 private static Type GetClrTypeByXmlName(string typeName)
985 {
986 switch (typeName)
987 {
988 case "bool":
989 return typeof(bool);
990 case "int":
991 return typeof(int);
992 case "long":
993 return typeof(long);
994 case "string":
995 return typeof(string);
996 default:
997 return null;
998 }
999 }
1000
1001 /// <summary>
1002 /// Processes an XmlSchemaSimpleType into corresponding types.
1003 /// </summary>
1004 /// <param name="simpleType">XmlSchemaSimpleType to be processed.</param>
1005 /// <param name="codeNamespace">CodeNamespace to be used when outputting code.</param>
1006 private static void ProcessSimpleType(XmlSchemaSimpleType simpleType, CodeNamespace codeNamespace)
1007 {
1008 EnumDeclaration enumDeclaration;
1009 string simpleTypeName = simpleType.Name;
1010 string baseTypeName;
1011
1012 ProcessSimpleType(simpleTypeName, simpleType, false, out enumDeclaration, out baseTypeName);
1013
1014 simpleTypeNamesToClrTypeNames.Add(simpleTypeName, baseTypeName);
1015
1016 if (enumDeclaration != null)
1017 {
1018 codeNamespace.Types.Add(enumDeclaration.TypeDeclaration);
1019 typeNamesToEnumDeclarations.Add(simpleTypeName, enumDeclaration);
1020 AddEnumHelperMethods(enumDeclaration, codeNamespace);
1021 }
1022 }
1023
1024 /// <summary>
1025 /// Processes an XmlSchemaSimpleType into corresponding code.
1026 /// </summary>
1027 /// <param name="simpleTypeName">Name for the type.</param>
1028 /// <param name="simpleType">XmlSchemaSimpleType to be processed.</param>
1029 /// <param name="codeNamespace">CodeNamespace to be used when outputting code for global types.</param>
1030 /// <param name="parentTypeDeclaration">CodeTypeDeclaration to be used when outputting code for nested types.</param>
1031 /// <param name="outputXmlMethod">Member method for the OutputXml method for nested types.</param>
1032 /// <param name="setAttributeMethod">Member method for the SetAttribute method for nested types.</param>
1033 private static void ProcessSimpleType(string simpleTypeName, XmlSchemaSimpleType simpleType, bool nestedType, out EnumDeclaration enumDeclaration, out string baseTypeName)
1034 {
1035 enumDeclaration = null;
1036 baseTypeName = null;
1037
1038 // XSD supports simpleTypes derived by union, list, or restriction; restrictions can have any
1039 // combination of pattern, enumeration, length, and more; lists can contain any other simpleType.
1040 // XsdGen, in contrast, only supports a limited set of values...
1041 // Unions are weakly supported by just using the first member type
1042 // restrictions must either be all enumeration or a single pattern, a list must be of a
1043 // single simpleType which itself is only a restriction of enumeration.
1044 if (simpleType.Content is XmlSchemaSimpleTypeUnion)
1045 {
1046 XmlSchemaSimpleTypeUnion union = simpleType.Content as XmlSchemaSimpleTypeUnion;
1047 if (union.MemberTypes.Length > 0)
1048 {
1049 baseTypeName = union.MemberTypes[0].Name;
1050 return;
1051 }
1052 else
1053 {
1054 baseTypeName = "string";
1055 return;
1056 }
1057 }
1058
1059 bool listType = false; // XSD lists become [Flag]enums in C#...
1060 XmlSchemaSimpleTypeList simpleTypeList = simpleType.Content as XmlSchemaSimpleTypeList;
1061 XmlSchemaSimpleTypeRestriction simpleTypeRestriction = simpleType.Content as XmlSchemaSimpleTypeRestriction;
1062
1063 if (simpleTypeList != null)
1064 {
1065 baseTypeName = simpleTypeList.ItemTypeName.Name;
1066
1067 if (String.IsNullOrEmpty(baseTypeName))
1068 {
1069 simpleTypeRestriction = simpleTypeList.ItemType.Content as XmlSchemaSimpleTypeRestriction;
1070 if (simpleTypeRestriction == null)
1071 {
1072 string appName = typeof(XsdGen).Assembly.GetName().Name;
1073 throw new NotImplementedException(string.Format("{0} does not support a <list> that does not contain a <simpleType>/<restriction>.", appName));
1074 }
1075
1076 listType = true;
1077 }
1078 else
1079 {
1080 // We expect to find an existing enum already declared!
1081 EnumDeclaration existingEnumDeclaration = typeNamesToEnumDeclarations[baseTypeName];
1082 // TODO: do we need to further alter the Flags setter code because of the helper stuff?
1083 // As far as I can tell, this code is never exercised by our existing XSDs!
1084 existingEnumDeclaration.SetFlags();
1085 }
1086 }
1087
1088 if (simpleTypeRestriction == null)
1089 {
1090 string appName = typeof(XsdGen).Assembly.GetName().Name;
1091 throw new NotImplementedException(string.Format("{0} does not understand this simpleType!", appName));
1092 }
1093
1094 bool foundPattern = false;
1095 foreach (XmlSchemaFacet facet in simpleTypeRestriction.Facets)
1096 {
1097 XmlSchemaEnumerationFacet enumFacet = facet as XmlSchemaEnumerationFacet;
1098 XmlSchemaPatternFacet patternFacet = facet as XmlSchemaPatternFacet;
1099
1100 if (enumFacet != null)
1101 {
1102 if (foundPattern)
1103 {
1104 string appName = typeof(XsdGen).Assembly.GetName().Name;
1105 throw new NotImplementedException(string.Format("{0} does not support restrictions containing both <pattern> and <enumeration>.", appName));
1106 }
1107
1108 if (enumDeclaration == null)
1109 {
1110 // For nested types, the simple name comes from the attribute name, with "Type" appended
1111 // to prevent name collision with the attribute member itself.
1112 if (nestedType)
1113 {
1114 simpleTypeName = String.Concat(simpleTypeName, "Type");
1115 }
1116 baseTypeName = simpleTypeName;
1117
1118 string typeDocumentation = GetDocumentation(simpleType.Annotation);
1119 enumDeclaration = new EnumDeclaration(simpleTypeName, typeDocumentation);
1120 }
1121
1122 string documentation = GetDocumentation(enumFacet.Annotation);
1123 enumDeclaration.AddValue(enumFacet.Value, documentation);
1124 }
1125
1126 if (patternFacet != null)
1127 {
1128 if (enumDeclaration != null)
1129 {
1130 string appName = typeof(XsdGen).Assembly.GetName().Name;
1131 throw new NotImplementedException(string.Format("{0} does not support restrictions containing both <pattern> and <enumeration>.", appName));
1132 }
1133
1134 if (foundPattern)
1135 {
1136 string appName = typeof(XsdGen).Assembly.GetName().Name;
1137 throw new NotImplementedException(string.Format("{0} does not support restrictions multiple <pattern> elements.", appName));
1138 }
1139
1140 foundPattern = true;
1141 }
1142 }
1143
1144 if (enumDeclaration != null && listType)
1145 {
1146 enumDeclaration.SetFlags();
1147 }
1148
1149 if (String.IsNullOrEmpty(baseTypeName))
1150 {
1151 baseTypeName = (string)simpleTypeNamesToClrTypeNames[simpleTypeRestriction.BaseTypeName.Name];
1152 }
1153 }
1154
1155 /// <summary>
1156 /// Creates an attribute declaration indicating generated code including the tool name and version.
1157 /// </summary>
1158 /// <returns>GeneratedCodeAttribute declearation.</returns>
1159 private static CodeAttributeDeclaration GetGeneratedCodeAttribute()
1160 {
1161 AssemblyName generatorAssemblyName = typeof(XsdGen).Assembly.GetName();
1162 return new CodeAttributeDeclaration("GeneratedCode",
1163 new CodeAttributeArgument(new CodePrimitiveExpression(generatorAssemblyName.Name)),
1164 new CodeAttributeArgument(new CodePrimitiveExpression(generatorAssemblyName.Version.ToString())));
1165 }
1166
1167 /// <summary>
1168 /// Creates a code statement to throw an exception if an argument is null.
1169 /// </summary>
1170 /// <param name="argumentName">Name of the argument to check.</param>
1171 /// <param name="nullOrEmpty">True to check for null-or-empty instead of just null</param>
1172 /// <returns>Code condition statement.</returns>
1173 private static CodeConditionStatement GetArgumentNullCheckStatement(string argumentName, bool nullOrEmpty)
1174 {
1175 CodeConditionStatement conditionStatement = new CodeConditionStatement();
1176 if (nullOrEmpty)
1177 {
1178 conditionStatement.Condition = new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodeTypeReferenceExpression("String"), "IsNullOrEmpty"), new CodeVariableReferenceExpression(argumentName));
1179 }
1180 else
1181 {
1182 conditionStatement.Condition = new CodeBinaryOperatorExpression(new CodePrimitiveExpression(null), CodeBinaryOperatorType.ValueEquality, new CodeVariableReferenceExpression(argumentName));
1183 }
1184
1185 conditionStatement.TrueStatements.Add(new CodeThrowExceptionStatement(new CodeObjectCreateExpression("ArgumentNullException", new CodeSnippetExpression(String.Concat("\"", argumentName, "\"")))));
1186 return conditionStatement;
1187 }
1188
1189 /// <summary>
1190 /// Creates an attribute declaration to suppress a particular code-analysis message.
1191 /// </summary>
1192 /// <param name="category">Code analysis category, such as "Microsoft.Design"</param>
1193 /// <param name="checkId">Code analysis ID number.</param>
1194 /// <returns>SuppressMessageAttribute declaration.</returns>
1195 private static CodeAttributeDeclaration GetCodeAnalysisSuppressionAttribute(string category, string checkId)
1196 {
1197 return new CodeAttributeDeclaration("SuppressMessage",
1198 new CodeAttributeArgument(new CodePrimitiveExpression(category)),
1199 new CodeAttributeArgument(new CodePrimitiveExpression(checkId)));
1200 }
1201
1202 /// <summary>
1203 /// Class representing an enum declaration.
1204 /// </summary>
1205 internal class EnumDeclaration
1206 {
1207 private string enumTypeName;
1208 private CodeTypeDeclaration declaration;
1209 private bool flags;
1210 private StringCollection enumValues;
1211
1212 /// <summary>
1213 /// Creates a new enum declaration with the given name.
1214 /// </summary>
1215 /// <param name="enumTypeName">Name of the type for the enum.</param>
1216 /// <param name="documentation">Documentation for the enum type.</param>
1217 public EnumDeclaration(string enumTypeName, string documentation)
1218 {
1219 this.enumTypeName = enumTypeName;
1220
1221 this.declaration = new CodeTypeDeclaration(enumTypeName);
1222 this.declaration.CustomAttributes.Add(GetGeneratedCodeAttribute());
1223 this.declaration.Attributes = MemberAttributes.Public;
1224 this.declaration.IsEnum = true;
1225
1226 if (documentation != null)
1227 {
1228 GenerateSummaryComment(this.declaration.Comments, documentation);
1229 }
1230
1231 this.enumValues = new StringCollection();
1232 }
1233
1234 public CodeTypeDeclaration TypeDeclaration
1235 {
1236 get { return this.declaration; }
1237 }
1238
1239 /// <summary>
1240 /// Gets the enumeration values.
1241 /// </summary>
1242 /// <value>The enumeration values.</value>
1243 public ICollection Values
1244 {
1245 get { return this.enumValues; }
1246 }
1247
1248 /// <summary>
1249 /// Gets the enumeration name.
1250 /// </summary>
1251 /// <value>The enumeration name.</value>
1252 public string Name
1253 {
1254 get { return this.enumTypeName; }
1255 }
1256
1257 /// <summary>
1258 /// Gets the enumeration flags property.
1259 /// </summary>
1260 /// <value>Whether the enumeration is a [Flags] type.</value>
1261 public bool Flags
1262 {
1263 get { return this.flags; }
1264 }
1265
1266 /// <summary>
1267 /// Sets the [Flags] property on the enumeration. Once set, this cannot be undone.
1268 /// </summary>
1269 public void SetFlags()
1270 {
1271 if (this.flags)
1272 {
1273 return;
1274 }
1275
1276 this.flags = true;
1277
1278 this.declaration.CustomAttributes.Add(new CodeAttributeDeclaration("Flags"));
1279 SwitchToNoneValue();
1280
1281 int enumValue = 0;
1282 foreach (CodeMemberField enumField in this.declaration.Members)
1283 {
1284 enumField.InitExpression = new CodeSnippetExpression(enumValue.ToString());
1285 if (enumValue == 0)
1286 {
1287 enumValue = 1;
1288 }
1289 else
1290 {
1291 enumValue *= 2;
1292 }
1293 }
1294 }
1295
1296 private void InjectIllegalAndNotSetValues()
1297 {
1298 CodeMemberField memberIllegal = new CodeMemberField(typeof(int), "IllegalValue");
1299 CodeMemberField memberNotSet = new CodeMemberField(typeof(int), "NotSet");
1300
1301 memberIllegal.InitExpression = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(typeof(int)), "MaxValue");
1302 // Using "-1" for "NotSet" ensure that the next value is zero, which is consistent
1303 // with older (3.0) behavior.
1304 memberNotSet.InitExpression = new CodePrimitiveExpression(-1);
1305
1306 this.declaration.Members.Insert(0, memberIllegal);
1307 this.declaration.Members.Insert(1, memberNotSet);
1308 }
1309
1310 private void SwitchToNoneValue()
1311 {
1312 if (this.enumValues.Count > 0)
1313 {
1314 // Remove the "IllegalValue" and "NotSet" values first.
1315 this.declaration.Members.RemoveAt(0);
1316 this.declaration.Members.RemoveAt(0);
1317
1318 CodeMemberField memberNone = new CodeMemberField(typeof(int), "None");
1319 memberNone.InitExpression = new CodePrimitiveExpression(0);
1320
1321 this.declaration.Members.Insert(0, memberNone);
1322 }
1323 }
1324
1325 /// <summary>
1326 /// Add a value to the enumeration.
1327 /// </summary>
1328 /// <param name="enumValue">The value to add.</param>
1329 public void AddValue(string enumValue, string documentation)
1330 {
1331 if (this.enumValues.Count == 0)
1332 {
1333 InjectIllegalAndNotSetValues();
1334 }
1335
1336 this.enumValues.Add(enumValue);
1337 CodeMemberField memberField = new CodeMemberField(typeof(int), MakeEnumValue(enumValue));
1338 //memberField.Attributes
1339 this.declaration.Members.Add(memberField);
1340 if (documentation != null)
1341 {
1342 GenerateSummaryComment(memberField.Comments, documentation);
1343 }
1344 }
1345 }
1346
1347 private static void AddEnumHelperMethods(EnumDeclaration enumDeclaration, CodeNamespace codeNamespace)
1348 {
1349 if (enumHelperClass == null)
1350 {
1351 enumHelperClass = new CodeTypeDeclaration("Enums");
1352 enumHelperClass.CustomAttributes.Add(GetGeneratedCodeAttribute());
1353 // The static and final attributes don't seem to get applied, but we'd prefer if they were.
1354 enumHelperClass.Attributes = MemberAttributes.Public | MemberAttributes.Static | MemberAttributes.Final;
1355 codeNamespace.Types.Add(enumHelperClass);
1356 }
1357
1358 AddEnumHelperMethods(enumDeclaration, enumHelperClass);
1359 }
1360
1361 private static void AddEnumHelperMethods(EnumDeclaration enumDeclaration, CodeTypeDeclaration parentType)
1362 {
1363 CodeTypeReference stringType = new CodeTypeReference(typeof(string));
1364 CodeTypeReference boolType = new CodeTypeReference(typeof(bool));
1365 CodeTypeReference enumType = new CodeTypeReference(typeof(Enum));
1366 CodeTypeReference newEnumType = new CodeTypeReference(enumDeclaration.Name);
1367
1368 CodePrimitiveExpression falseValue = new CodePrimitiveExpression(false);
1369 CodePrimitiveExpression trueValue = new CodePrimitiveExpression(true);
1370 CodeMethodReturnStatement returnFalse = new CodeMethodReturnStatement(falseValue);
1371 CodeMethodReturnStatement returnTrue = new CodeMethodReturnStatement(trueValue);
1372
1373 string parseMethodName = String.Concat("Parse", enumDeclaration.Name);
1374 string tryParseMethodName = String.Concat("TryParse", enumDeclaration.Name);
1375
1376 CodeFieldReferenceExpression defaultEnumValue = null;
1377 CodeFieldReferenceExpression illegalEnumValue = null;
1378 bool addParse = true;
1379 if (enumDeclaration.Flags)
1380 {
1381 defaultEnumValue = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(newEnumType), "None");
1382 illegalEnumValue = defaultEnumValue;
1383 // Because there's no "IllegalValue" for [Flags] enums, we can't create the Parse()
1384 // method. We can still create the TryParse() method, though!
1385 addParse = false;
1386 }
1387 else
1388 {
1389 defaultEnumValue = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(newEnumType), "NotSet");
1390 illegalEnumValue = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(newEnumType), "IllegalValue");
1391 }
1392
1393 if (addParse)
1394 {
1395 CodeMemberMethod parseNewEnum = new CodeMemberMethod();
1396 GenerateSummaryComment(parseNewEnum.Comments, String.Format("Parses a {0} from a string.", enumDeclaration.Name));
1397 parseNewEnum.Attributes = MemberAttributes.Public | MemberAttributes.Static;
1398 parseNewEnum.Name = parseMethodName;
1399 parseNewEnum.ReturnType = newEnumType;
1400 parseNewEnum.Parameters.Add(new CodeParameterDeclarationExpression(stringType, "value"));
1401
1402 parseNewEnum.Statements.Add(new CodeVariableDeclarationStatement(newEnumType, "parsedValue"));
1403
1404 // Just delegate to the TryParse version...
1405 parseNewEnum.Statements.Add(new CodeMethodInvokeExpression(
1406 new CodeMethodReferenceExpression(new CodeTypeReferenceExpression(parentType.Name), tryParseMethodName),
1407 new CodeArgumentReferenceExpression("value"),
1408 new CodeDirectionExpression(FieldDirection.Out, new CodeVariableReferenceExpression("parsedValue"))));
1409
1410 parseNewEnum.Statements.Add(new CodeMethodReturnStatement(new CodeVariableReferenceExpression("parsedValue")));
1411 parentType.Members.Add(parseNewEnum);
1412 }
1413
1414 CodeMemberMethod tryParseNewEnum = new CodeMemberMethod();
1415 GenerateSummaryComment(tryParseNewEnum.Comments, String.Format("Tries to parse a {0} from a string.", enumDeclaration.Name));
1416 tryParseNewEnum.Attributes = MemberAttributes.Public | MemberAttributes.Static;
1417 tryParseNewEnum.Name = tryParseMethodName;
1418 tryParseNewEnum.ReturnType = boolType;
1419 CodeParameterDeclarationExpression valueDeclaration = new CodeParameterDeclarationExpression(stringType, "value");
1420 CodeParameterDeclarationExpression parsedValueDeclaration = new CodeParameterDeclarationExpression(newEnumType, "parsedValue");
1421 parsedValueDeclaration.Direction = FieldDirection.Out;
1422 tryParseNewEnum.Parameters.Add(valueDeclaration);
1423 tryParseNewEnum.Parameters.Add(parsedValueDeclaration);
1424
1425 CodeArgumentReferenceExpression value = new CodeArgumentReferenceExpression(valueDeclaration.Name);
1426 CodeArgumentReferenceExpression parsedValue = new CodeArgumentReferenceExpression(parsedValueDeclaration.Name);
1427
1428 tryParseNewEnum.Statements.Add(new CodeAssignStatement(parsedValue, defaultEnumValue));
1429
1430 tryParseNewEnum.Statements.Add(new CodeConditionStatement(
1431 new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodeTypeReferenceExpression(stringType), "IsNullOrEmpty"), value),
1432 returnFalse));
1433
1434 // The structure is similar, but distinct, for regular and flag-style enums. In particular,
1435 // for a flags-style enum we have to be able to parse multiple values, separated by
1436 // spaces, and each value is bitwise-OR'd together.
1437 CodeStatementCollection nestedIfParent = tryParseNewEnum.Statements;
1438 CodeExpression valueToTest = value;
1439
1440 // For Flags-style enums, we need to loop over the space-separated values...
1441 if (enumDeclaration.Flags)
1442 {
1443 CodeVariableDeclarationStatement split = new CodeVariableDeclarationStatement(typeof(string[]), "splitValue",
1444 new CodeMethodInvokeExpression(value, "Split",
1445 new CodeMethodInvokeExpression(new CodePrimitiveExpression(" \t\r\n"), "ToCharArray"),
1446 new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(typeof(StringSplitOptions)), "RemoveEmptyEntries")));
1447 tryParseNewEnum.Statements.Add(split);
1448
1449 CodeIterationStatement flagLoop = new CodeIterationStatement(
1450 new CodeVariableDeclarationStatement(typeof(IEnumerator), "enumerator",
1451 new CodeMethodInvokeExpression(new CodeVariableReferenceExpression(split.Name), "GetEnumerator")),
1452 new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("enumerator"), "MoveNext"),
1453 new CodeSnippetStatement(""));
1454 tryParseNewEnum.Statements.Add(flagLoop);
1455
1456 CodeVariableDeclarationStatement currentValue = new CodeVariableDeclarationStatement(typeof(string), "currentValue",
1457 new CodeCastExpression(stringType,
1458 new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("enumerator"), "Current")));
1459 flagLoop.Statements.Add(currentValue);
1460 valueToTest = new CodeVariableReferenceExpression(currentValue.Name);
1461
1462 nestedIfParent = flagLoop.Statements;
1463 }
1464
1465 // We can't just Enum.Parse, because some values are also keywords (like 'string', 'int', 'default'),
1466 // and these get generated as '@'-prefixed values. Instead, we 'switch' on the value and do it manually.
1467 // Actually, we if/else, because CodeDom doesn't support 'switch'! Also, we nest the successive 'if's
1468 // in order to short-circuit the parsing as soon as there's a match.
1469 foreach (string enumValue in enumDeclaration.Values)
1470 {
1471 CodeFieldReferenceExpression enumValueReference = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(newEnumType), MakeEnumValue(enumValue));
1472 CodeConditionStatement ifStatement = new CodeConditionStatement(
1473 new CodeBinaryOperatorExpression(new CodePrimitiveExpression(enumValue), CodeBinaryOperatorType.ValueEquality, valueToTest));
1474 if (enumDeclaration.Flags)
1475 {
1476 ifStatement.TrueStatements.Add(new CodeAssignStatement(parsedValue,
1477 new CodeBinaryOperatorExpression(parsedValue, CodeBinaryOperatorType.BitwiseOr, enumValueReference)));
1478 }
1479 else
1480 {
1481 ifStatement.TrueStatements.Add(new CodeAssignStatement(parsedValue, enumValueReference));
1482 }
1483 nestedIfParent.Add(ifStatement);
1484 nestedIfParent = ifStatement.FalseStatements;
1485 }
1486
1487 // Finally, if we didn't find a match, it's illegal (or none, for flags)!
1488 nestedIfParent.Add(new CodeAssignStatement(parsedValue, illegalEnumValue));
1489 nestedIfParent.Add(returnFalse);
1490
1491 tryParseNewEnum.Statements.Add(returnTrue);
1492
1493 parentType.Members.Add(tryParseNewEnum);
1494
1495 enumsToParseMethodClasses.Add(enumDeclaration, parentType);
1496 }
1497 }
1498}
diff --git a/src/internal/WixBuildTools.XsdGen/WixBuildTools.XsdGen.csproj b/src/internal/WixBuildTools.XsdGen/WixBuildTools.XsdGen.csproj
new file mode 100644
index 00000000..2e3a0382
--- /dev/null
+++ b/src/internal/WixBuildTools.XsdGen/WixBuildTools.XsdGen.csproj
@@ -0,0 +1,28 @@
1<Project Sdk="Microsoft.NET.Sdk">
2
3 <PropertyGroup>
4 <OutputType>Exe</OutputType>
5 <!-- <TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks> -->
6 <TargetFrameworks>net461</TargetFrameworks>
7 <IsTool>true</IsTool>
8 <IncludeBuildOutput>false</IncludeBuildOutput>
9 <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
10 <DebugType>embedded</DebugType>
11 <PublishRepositoryUrl>true</PublishRepositoryUrl>
12 <NoWarn>CS0618</NoWarn>
13 </PropertyGroup>
14
15 <ItemGroup>
16 <Content Include="build\WixBuildTools.XsdGen.targets" PackagePath="build\" />
17 <Content Include="buildCrossTargeting\WixBuildTools.XsdGen.targets" PackagePath="buildCrossTargeting\" />
18
19 <Content Include="$(OutputPath)net461\$(TargetFileName)" PackagePath="tools\full" />
20 <!-- <Content Include="$(OutputPath)netcoreapp2.0\$(TargetFileName)" PackagePath="tools\core" /> -->
21 </ItemGroup>
22
23 <ItemGroup>
24 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
25 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
26 </ItemGroup>
27
28</Project>
diff --git a/src/internal/WixBuildTools.XsdGen/XsdGen.cs b/src/internal/WixBuildTools.XsdGen/XsdGen.cs
new file mode 100644
index 00000000..a1374df3
--- /dev/null
+++ b/src/internal/WixBuildTools.XsdGen/XsdGen.cs
@@ -0,0 +1,124 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Tools
4{
5 using System;
6 using System.CodeDom;
7 using System.CodeDom.Compiler;
8 using System.Collections;
9 using System.IO;
10 using System.Xml;
11 using System.Xml.Schema;
12 using Microsoft.CSharp;
13 using WixToolset;
14
15 /// <summary>
16 /// Generates a strongly-typed C# class from an XML schema (XSD).
17 /// </summary>
18 public class XsdGen
19 {
20 private string xsdFile;
21 private string outFile;
22 private string outputNamespace;
23 private string commonNamespace;
24 private bool showHelp;
25
26 /// <summary>
27 /// Constructor for the XsdGen class.
28 /// </summary>
29 /// <param name="args">Command-line arguments passed to the program.</param>
30 private XsdGen(string[] args)
31 {
32 this.ParseCommandlineArgs(args);
33
34 // show usage information
35 if (this.showHelp)
36 {
37 Console.WriteLine("usage: XsdGen.exe <schema>.xsd <outputFile> <namespace> [<commonNamespace>]");
38 return;
39 }
40
41 // ensure that the schema file exists
42 if (!File.Exists(this.xsdFile))
43 {
44 throw new ApplicationException(String.Format("Schema file does not exist: '{0}'.", this.xsdFile));
45 }
46
47 XmlSchema document = null;
48 using (StreamReader xsdFileReader = new StreamReader(this.xsdFile))
49 {
50 document = XmlSchema.Read(xsdFileReader, new ValidationEventHandler(this.ValidationHandler));
51 }
52
53 CodeCompileUnit codeCompileUnit = StronglyTypedClasses.Generate(document, this.outputNamespace, this.commonNamespace);
54
55 using (CSharpCodeProvider codeProvider = new CSharpCodeProvider())
56 {
57 ICodeGenerator generator = codeProvider.CreateGenerator();
58
59 CodeGeneratorOptions options = new CodeGeneratorOptions();
60 options.BlankLinesBetweenMembers = true;
61 options.BracingStyle = "C";
62 options.IndentString = " ";
63
64 using (StreamWriter csharpFileWriter = new StreamWriter(this.outFile))
65 {
66 generator.GenerateCodeFromCompileUnit(codeCompileUnit, csharpFileWriter, options);
67 }
68 }
69 }
70
71 /// <summary>
72 /// The main entry point for the application.
73 /// </summary>
74 /// <param name="args">The command line arguments.</param>
75 /// <returns>The error code.</returns>
76 [STAThread]
77 public static int Main(string[] args)
78 {
79 try
80 {
81 XsdGen xsdGen = new XsdGen(args);
82 }
83 catch (Exception e)
84 {
85 Console.WriteLine("XsdGen.exe : fatal error MSF0000: {0}\r\n\r\nStack Trace:\r\n{1}", e.Message, e.StackTrace);
86 return 1;
87 }
88
89 return 0;
90 }
91
92 /// <summary>
93 /// Validation event handler.
94 /// </summary>
95 /// <param name="sender">Sender for the event.</param>
96 /// <param name="e">Event args.</param>
97 public void ValidationHandler(object sender, ValidationEventArgs e)
98 {
99 }
100
101 /// <summary>
102 /// Parse the command line arguments.
103 /// </summary>
104 /// <param name="args">Command-line arguments.</param>
105 private void ParseCommandlineArgs(string[] args)
106 {
107 if (3 > args.Length)
108 {
109 this.showHelp = true;
110 }
111 else
112 {
113 this.xsdFile = args[0];
114 this.outFile = args[1];
115 this.outputNamespace = args[2];
116
117 if (args.Length >= 4)
118 {
119 this.commonNamespace = args[3];
120 }
121 }
122 }
123 }
124}
diff --git a/src/internal/WixBuildTools.XsdGen/build/WixBuildTools.XsdGen.targets b/src/internal/WixBuildTools.XsdGen/build/WixBuildTools.XsdGen.targets
new file mode 100644
index 00000000..ca1b89f6
--- /dev/null
+++ b/src/internal/WixBuildTools.XsdGen/build/WixBuildTools.XsdGen.targets
@@ -0,0 +1,67 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
5 <PropertyGroup>
6 <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
7 </PropertyGroup>
8
9 <ItemGroup>
10 <!--Provide support for setting type (BuildAction) from VS-->
11 <AvailableItemName Include="XsdGenSource" />
12 </ItemGroup>
13
14 <PropertyGroup>
15 <!-- <XsdGenPath Condition=" '$(XsdGenPath)'=='' and '$(MSBuildRuntimeType)'=='Core' ">$(MSBuildThisFileDirectory)..\tools\core\</XsdGenPath> -->
16 <XsdGenPath Condition=" '$(XsdGenPath)'=='' ">$(MSBuildThisFileDirectory)..\tools\full\</XsdGenPath>
17 </PropertyGroup>
18
19 <!--
20 ================================================================================================
21 XsdGen
22
23 Generates a .cs class file from an .xsd file.
24
25 [IN]
26 @(XsdGenSource) - The items to run through the XsdGen tool.
27
28 [OUT]
29 $(IntermediateOutputPath)%(Filename).cs - The generated .cs files to include in the compilation.
30 ================================================================================================
31 -->
32 <PropertyGroup>
33 <XsdGenDependsOn>
34 </XsdGenDependsOn>
35 <PrepareResourcesDependsOn>
36 XsdGen;
37 $(PrepareResourcesDependsOn)
38 </PrepareResourcesDependsOn>
39 </PropertyGroup>
40 <Target
41 Name="XsdGen"
42 BeforeTargets="PrepareResources"
43 DependsOnTargets="$(XsdGenDependsOn)"
44 Condition=" '@(XsdGenSource)' != '' "
45 Inputs="$(MSBuildAllProjects);@(XsdGenSource)"
46 Outputs="$(IntermediateOutputPath)%(XsdGenSource.Filename).cs">
47
48 <PropertyGroup>
49 <XsdGenCsFile>$(IntermediateOutputPath)%(XsdGenSource.Filename).cs</XsdGenCsFile>
50 <XsdGenCommonNamespace>%(XsdGenSource.CommonNamespace)</XsdGenCommonNamespace>
51 </PropertyGroup>
52
53 <Exec Command="&quot;$(XsdGenPath)WixBuildTools.XsdGen.exe&quot; &quot;%(XsdGenSource.FullPath)&quot; &quot;$(XsdGenCsFile)&quot; %(XsdGenSource.Namespace) $(XsdGenCommonNamespace)"
54 Outputs="$(XsdGenCsFile)" />
55
56 <ItemGroup>
57 <!-- This will tell MSBuild to clean up the .cs file during a Clean build -->
58 <FileWrites Include="$(XsdGenCsFile)" />
59
60 <!-- Add the generated .cs file to the list of source files to compile -->
61 <Compile Include="$(XsdGenCsFile)">
62 <Link>%(XsdGenCsFile.Filename)%(XsdGenCsFile.Extension)</Link>
63 </Compile>
64 </ItemGroup>
65 </Target>
66
67</Project>
diff --git a/src/internal/WixBuildTools.XsdGen/buildCrossTargeting/WixBuildTools.XsdGen.targets b/src/internal/WixBuildTools.XsdGen/buildCrossTargeting/WixBuildTools.XsdGen.targets
new file mode 100644
index 00000000..58692095
--- /dev/null
+++ b/src/internal/WixBuildTools.XsdGen/buildCrossTargeting/WixBuildTools.XsdGen.targets
@@ -0,0 +1,6 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
5 <Import Project="..\build\WixBuildTools.XsdGen.targets" />
6</Project>
diff --git a/src/internal/WixBuildTools.sln b/src/internal/WixBuildTools.sln
new file mode 100644
index 00000000..689832fb
--- /dev/null
+++ b/src/internal/WixBuildTools.sln
@@ -0,0 +1,81 @@
1Microsoft Visual Studio Solution File, Format Version 12.00
2# Visual Studio 15
3VisualStudioVersion = 15.0.27130.2003
4MinimumVisualStudioVersion = 15.0.26124.0
5Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixBuildTools.XsdGen", "src\WixBuildTools.XsdGen\WixBuildTools.XsdGen.csproj", "{E89E52C9-A4A1-4174-A1B1-3B72975E6ED6}"
6EndProject
7Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixBuildTools.MsgGen", "src\WixBuildTools.MsgGen\WixBuildTools.MsgGen.csproj", "{DB6EF6F3-51B1-4214-9A14-D501C23F6FA4}"
8EndProject
9Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixBuildTools.TestSupport", "src\WixBuildTools.TestSupport\WixBuildTools.TestSupport.csproj", "{6C57EF2C-979A-4106-A9E5-FE342810619A}"
10EndProject
11Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WixBuildTools.TestSupport.Native", "src\WixBuildTools.TestSupport.Native\WixBuildTools.TestSupport.Native.vcxproj", "{95BABD97-FBDB-453A-AF8A-FA031A07B599}"
12EndProject
13Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F6660B22-092F-4BC5-A303-E6F696C31E1B}"
14 ProjectSection(SolutionItems) = preProject
15 .editorconfig = .editorconfig
16 EndProjectSection
17EndProject
18Global
19 GlobalSection(SolutionConfigurationPlatforms) = preSolution
20 Debug|Any CPU = Debug|Any CPU
21 Debug|x64 = Debug|x64
22 Debug|x86 = Debug|x86
23 Release|Any CPU = Release|Any CPU
24 Release|x64 = Release|x64
25 Release|x86 = Release|x86
26 EndGlobalSection
27 GlobalSection(ProjectConfigurationPlatforms) = postSolution
28 {E89E52C9-A4A1-4174-A1B1-3B72975E6ED6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 {E89E52C9-A4A1-4174-A1B1-3B72975E6ED6}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 {E89E52C9-A4A1-4174-A1B1-3B72975E6ED6}.Debug|x64.ActiveCfg = Debug|Any CPU
31 {E89E52C9-A4A1-4174-A1B1-3B72975E6ED6}.Debug|x64.Build.0 = Debug|Any CPU
32 {E89E52C9-A4A1-4174-A1B1-3B72975E6ED6}.Debug|x86.ActiveCfg = Debug|Any CPU
33 {E89E52C9-A4A1-4174-A1B1-3B72975E6ED6}.Debug|x86.Build.0 = Debug|Any CPU
34 {E89E52C9-A4A1-4174-A1B1-3B72975E6ED6}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 {E89E52C9-A4A1-4174-A1B1-3B72975E6ED6}.Release|Any CPU.Build.0 = Release|Any CPU
36 {E89E52C9-A4A1-4174-A1B1-3B72975E6ED6}.Release|x64.ActiveCfg = Release|Any CPU
37 {E89E52C9-A4A1-4174-A1B1-3B72975E6ED6}.Release|x64.Build.0 = Release|Any CPU
38 {E89E52C9-A4A1-4174-A1B1-3B72975E6ED6}.Release|x86.ActiveCfg = Release|Any CPU
39 {E89E52C9-A4A1-4174-A1B1-3B72975E6ED6}.Release|x86.Build.0 = Release|Any CPU
40 {DB6EF6F3-51B1-4214-9A14-D501C23F6FA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 {DB6EF6F3-51B1-4214-9A14-D501C23F6FA4}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 {DB6EF6F3-51B1-4214-9A14-D501C23F6FA4}.Debug|x64.ActiveCfg = Debug|Any CPU
43 {DB6EF6F3-51B1-4214-9A14-D501C23F6FA4}.Debug|x64.Build.0 = Debug|Any CPU
44 {DB6EF6F3-51B1-4214-9A14-D501C23F6FA4}.Debug|x86.ActiveCfg = Debug|Any CPU
45 {DB6EF6F3-51B1-4214-9A14-D501C23F6FA4}.Debug|x86.Build.0 = Debug|Any CPU
46 {DB6EF6F3-51B1-4214-9A14-D501C23F6FA4}.Release|Any CPU.ActiveCfg = Release|Any CPU
47 {DB6EF6F3-51B1-4214-9A14-D501C23F6FA4}.Release|Any CPU.Build.0 = Release|Any CPU
48 {DB6EF6F3-51B1-4214-9A14-D501C23F6FA4}.Release|x64.ActiveCfg = Release|Any CPU
49 {DB6EF6F3-51B1-4214-9A14-D501C23F6FA4}.Release|x64.Build.0 = Release|Any CPU
50 {DB6EF6F3-51B1-4214-9A14-D501C23F6FA4}.Release|x86.ActiveCfg = Release|Any CPU
51 {DB6EF6F3-51B1-4214-9A14-D501C23F6FA4}.Release|x86.Build.0 = Release|Any CPU
52 {6C57EF2C-979A-4106-A9E5-FE342810619A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
53 {6C57EF2C-979A-4106-A9E5-FE342810619A}.Debug|Any CPU.Build.0 = Debug|Any CPU
54 {6C57EF2C-979A-4106-A9E5-FE342810619A}.Debug|x64.ActiveCfg = Debug|Any CPU
55 {6C57EF2C-979A-4106-A9E5-FE342810619A}.Debug|x64.Build.0 = Debug|Any CPU
56 {6C57EF2C-979A-4106-A9E5-FE342810619A}.Debug|x86.ActiveCfg = Debug|Any CPU
57 {6C57EF2C-979A-4106-A9E5-FE342810619A}.Debug|x86.Build.0 = Debug|Any CPU
58 {6C57EF2C-979A-4106-A9E5-FE342810619A}.Release|Any CPU.ActiveCfg = Release|Any CPU
59 {6C57EF2C-979A-4106-A9E5-FE342810619A}.Release|Any CPU.Build.0 = Release|Any CPU
60 {6C57EF2C-979A-4106-A9E5-FE342810619A}.Release|x64.ActiveCfg = Release|Any CPU
61 {6C57EF2C-979A-4106-A9E5-FE342810619A}.Release|x64.Build.0 = Release|Any CPU
62 {6C57EF2C-979A-4106-A9E5-FE342810619A}.Release|x86.ActiveCfg = Release|Any CPU
63 {6C57EF2C-979A-4106-A9E5-FE342810619A}.Release|x86.Build.0 = Release|Any CPU
64 {95BABD97-FBDB-453A-AF8A-FA031A07B599}.Debug|Any CPU.ActiveCfg = Debug|Win32
65 {95BABD97-FBDB-453A-AF8A-FA031A07B599}.Debug|Any CPU.Build.0 = Debug|Win32
66 {95BABD97-FBDB-453A-AF8A-FA031A07B599}.Debug|x64.ActiveCfg = Debug|Win32
67 {95BABD97-FBDB-453A-AF8A-FA031A07B599}.Debug|x86.ActiveCfg = Debug|Win32
68 {95BABD97-FBDB-453A-AF8A-FA031A07B599}.Debug|x86.Build.0 = Debug|Win32
69 {95BABD97-FBDB-453A-AF8A-FA031A07B599}.Release|Any CPU.ActiveCfg = Release|Win32
70 {95BABD97-FBDB-453A-AF8A-FA031A07B599}.Release|Any CPU.Build.0 = Release|Win32
71 {95BABD97-FBDB-453A-AF8A-FA031A07B599}.Release|x64.ActiveCfg = Release|Win32
72 {95BABD97-FBDB-453A-AF8A-FA031A07B599}.Release|x86.ActiveCfg = Release|Win32
73 {95BABD97-FBDB-453A-AF8A-FA031A07B599}.Release|x86.Build.0 = Release|Win32
74 EndGlobalSection
75 GlobalSection(SolutionProperties) = preSolution
76 HideSolutionNode = FALSE
77 EndGlobalSection
78 GlobalSection(ExtensibilityGlobals) = postSolution
79 SolutionGuid = {83E9E075-B440-471A-9C37-9D84BA0AE3E0}
80 EndGlobalSection
81EndGlobal
diff --git a/src/internal/WixBuildTools.v3.ncrunchsolution b/src/internal/WixBuildTools.v3.ncrunchsolution
new file mode 100644
index 00000000..10420ac9
--- /dev/null
+++ b/src/internal/WixBuildTools.v3.ncrunchsolution
@@ -0,0 +1,6 @@
1<SolutionConfiguration>
2 <Settings>
3 <AllowParallelTestExecution>True</AllowParallelTestExecution>
4 <SolutionConfigured>True</SolutionConfigured>
5 </Settings>
6</SolutionConfiguration> \ No newline at end of file
diff --git a/src/internal/appveyor.cmd b/src/internal/appveyor.cmd
new file mode 100644
index 00000000..9fa5e330
--- /dev/null
+++ b/src/internal/appveyor.cmd
@@ -0,0 +1,13 @@
1@setlocal
2@pushd %~dp0
3
4nuget restore || exit /b
5
6dotnet pack -c Release src\WixBuildTools.MsgGen\WixBuildTools.MsgGen.csproj || exit /b
7dotnet pack -c Release src\WixBuildTools.TestSupport\WixBuildTools.TestSupport.csproj || exit /b
8dotnet pack -c Release src\WixBuildTools.XsdGen\WixBuildTools.XsdGen.csproj || exit /b
9
10msbuild -p:Configuration=Release -t:PackNativeNuget src\WixBuildTools.TestSupport.Native\WixBuildTools.TestSupport.Native.vcxproj || exit /b
11
12@popd
13@endlocal \ No newline at end of file
diff --git a/src/internal/appveyor.yml b/src/internal/appveyor.yml
new file mode 100644
index 00000000..522e5af3
--- /dev/null
+++ b/src/internal/appveyor.yml
@@ -0,0 +1,42 @@
1# Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2#
3# Do NOT modify this file. Update the canonical version in Home\repo-template\src\appveyor.yml
4# then update all of the repos.
5
6branches:
7 only:
8 - master
9 - develop
10
11image: Visual Studio 2019
12
13version: 0.0.0.{build}
14configuration: Release
15
16environment:
17 DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
18 DOTNET_CLI_TELEMETRY_OPTOUT: 1
19 NUGET_XMLDOC_MODE: skip
20
21build_script:
22 - appveyor.cmd
23
24pull_requests:
25 do_not_increment_build_number: true
26
27nuget:
28 disable_publish_on_pr: true
29
30skip_branch_with_pr: true
31skip_tags: true
32
33artifacts:
34- path: build\Release\**\*.nupkg
35 name: nuget
36- path: build\Release\**\*.msi
37 name: msi
38
39notifications:
40- provider: Slack
41 incoming_webhook:
42 secure: p5xuu+4x2JHfwGDMDe5KcG1k7gZxqYc4jWVwvyNZv5cvkubPD2waJs5yXMAXZNN7Z63/3PWHb7q4KoY/99AjauYa1nZ4c5qYqRPFRBKTHfA=
diff --git a/src/internal/nuget.config b/src/internal/nuget.config
new file mode 100644
index 00000000..fcbac27a
--- /dev/null
+++ b/src/internal/nuget.config
@@ -0,0 +1,8 @@
1<?xml version="1.0" encoding="utf-8"?>
2<configuration>
3 <packageSources>
4 <clear />
5 <add key="wixtoolset-dtf" value="https://ci.appveyor.com/nuget/wixtoolset-dtf" />
6 <add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
7 </packageSources>
8</configuration> \ No newline at end of file
diff --git a/src/libs/dutil/CustomizedNativeRecommendedRules.ruleset b/src/libs/dutil/CustomizedNativeRecommendedRules.ruleset
new file mode 100644
index 00000000..142b141c
--- /dev/null
+++ b/src/libs/dutil/CustomizedNativeRecommendedRules.ruleset
@@ -0,0 +1,8 @@
1<?xml version="1.0" encoding="utf-8"?>
2<RuleSet Name="Customized Microsoft Native Recommended Rules" Description="Microsoft Native Recommended Rules, -C26812" ToolsVersion="16.0">
3 <Include Path="nativerecommendedrules.ruleset" Action="Default" />
4 <Rules AnalyzerId="Microsoft.Analyzers.NativeCodeAnalysis" RuleNamespace="Microsoft.Rules.Native">
5 <!-- We need C style enums since we support BAs written in C -->
6 <Rule Id="C26812" Action="None" />
7 </Rules>
8</RuleSet> \ No newline at end of file
diff --git a/src/libs/dutil/Directory.Build.props b/src/libs/dutil/Directory.Build.props
new file mode 100644
index 00000000..fb34d54e
--- /dev/null
+++ b/src/libs/dutil/Directory.Build.props
@@ -0,0 +1,26 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<!--
4 Do NOT modify this file. Update the canonical version in Home\repo-template\src\Directory.Build.props
5 then update all of the repos.
6-->
7<Project>
8 <PropertyGroup>
9 <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
10 <EnableSourceLink Condition=" '$(NCrunch)' == '1' ">false</EnableSourceLink>
11
12 <ProjectName Condition=" '$(ProjectName)' == '' ">$(MSBuildProjectName)</ProjectName>
13 <BaseOutputPath>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)..\build\))</BaseOutputPath>
14 <BaseIntermediateOutputPath>$(BaseOutputPath)obj\$(ProjectName)\</BaseIntermediateOutputPath>
15 <OutputPath>$(BaseOutputPath)$(Configuration)\</OutputPath>
16
17 <Authors>WiX Toolset Team</Authors>
18 <Company>WiX Toolset</Company>
19 <Copyright>Copyright (c) .NET Foundation and contributors. All rights reserved.</Copyright>
20 <PackageLicenseExpression>MS-RL</PackageLicenseExpression>
21 <Product>WiX Toolset</Product>
22 </PropertyGroup>
23
24 <Import Project="Directory$(MSBuildProjectExtension).props" Condition=" Exists('Directory$(MSBuildProjectExtension).props') " />
25 <Import Project="Custom.Build.props" Condition=" Exists('Custom.Build.props') " />
26</Project>
diff --git a/src/libs/dutil/Directory.Build.targets b/src/libs/dutil/Directory.Build.targets
new file mode 100644
index 00000000..44701fb6
--- /dev/null
+++ b/src/libs/dutil/Directory.Build.targets
@@ -0,0 +1,73 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<!--
4 Do NOT modify this file. Update the canonical version in Home\repo-template\src\Directory.Build.targets
5 then update all of the repos.
6-->
7<Project>
8 <PropertyGroup>
9 <SigningToolFolder>$(BaseOutputPath)obj\.tools</SigningToolFolder>
10 <SigningToolExe>$(SigningToolFolder)\SignClient.exe</SigningToolExe>
11 <SigningFilelist>$(SigningToolFolder)\empty-filelist.txt</SigningFilelist>
12 <SigningConfiguration>$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), signing.json))\signing.json</SigningConfiguration>
13 </PropertyGroup>
14
15 <PropertyGroup>
16 <CreateDocumentation Condition=" '$(CreateDocumentationFile)'!='true' ">false</CreateDocumentation>
17 <DocumentationFile Condition=" '$(CreateDocumentationFile)'=='true' ">$(OutputPath)\$(AssemblyName).xml</DocumentationFile>
18 </PropertyGroup>
19
20 <Target Name="SetNuspecProperties" DependsOnTargets="InitializeSourceControlInformation" AfterTargets="GetBuildVersion"
21 Condition=" Exists('$(MSBuildProjectName).nuspec') ">
22 <PropertyGroup>
23 <ProjectUrl Condition=" '$(ProjectUrl)'=='' and '$(PrivateRepositoryUrl)'!='' ">$(PrivateRepositoryUrl.Replace('.git',''))</ProjectUrl>
24
25 <NuspecFile>$(MSBuildProjectName).nuspec</NuspecFile>
26 <NuspecBasePath Condition=" '$(NuspecBasePath)'=='' ">$(MSBuildProjectDirectory)</NuspecBasePath>
27 <NuspecProperties>$(NuspecProperties);Id=$(PackageId);Authors="$(Authors)";Configuration=$(Configuration);Copyright="$(Copyright)";Description="$(Description)";Title="$(Title)"</NuspecProperties>
28 <NuspecProperties>$(NuspecProperties);Version=$(NPMPackageVersion);RepositoryCommit=$(SourceRevisionId);RepositoryType=$(RepositoryType);RepositoryUrl=$(PrivateRepositoryUrl);ProjectFolder=$(MSBuildProjectDirectory)\;ProjectUrl=$(ProjectUrl)</NuspecProperties>
29 <PublishRepositoryUrl>true</PublishRepositoryUrl>
30 <SymbolPackageFormat>snupkg</SymbolPackageFormat>
31 </PropertyGroup>
32 </Target>
33
34 <Target Name="PackNative" DependsOnTargets="GetBuildVersion;SetNuspecProperties"
35 Condition=" Exists('$(MSBuildProjectName).nuspec') ">
36
37 <Exec Command='nuget pack $(NuspecFile) -OutputDirectory "$(BaseOutputPath)$(Configuration)" -BasePath "$(NuspecBasePath)" -Properties $(NuspecProperties)'
38 WorkingDirectory="$(MSBuildProjectDirectory)" />
39
40 <ItemGroup>
41 <NuGetPackOutput Include="$(BaseOutputPath)$(Configuration)\**\$(PackageId)*.nupkg" />
42 </ItemGroup>
43 </Target>
44
45 <Target Name="_GetSignClient"
46 Condition=" !Exists('$(SigningToolExe)') ">
47
48 <WriteLinesToFile File='$(SigningFilelist)' Lines='do-not-sign-files-in-nupkg' Overwrite='true' />
49
50 <Exec Command='dotnet.exe tool install --tool-path "$(SigningToolFolder)" SignClient' />
51 </Target>
52
53 <Target Name="SignOutput" DependsOnTargets="_GetSignClient" AfterTargets="AfterBuild"
54 Condition=" '$(SigningUser)'!='' and '$(SignOutput)'!='false' and
55 ('$(MSBuildProjectExtension)'=='.csproj' or ('$(MSBuildProjectExtension)'=='.vcxproj' and '$(ConfigurationType)'!='StaticLibrary'))">
56
57 <Exec Command='"$(SigningToolExe)" sign -i $(TargetPath) -c "$(SigningConfiguration)" -n "WiX Toolset" -d "WiX Toolset" -u https://wixtoolset.org/ -r "$(SigningUser)" -s "$(SigningSecret)"'
58 WorkingDirectory="$(MSBuildProjectDirectory)" EchoOff="true" />
59 </Target>
60
61 <Target Name="SignNupkg" DependsOnTargets="_GetSignClient" AfterTargets="Pack;PackNative"
62 Condition=" '$(SigningUser)'!='' and '@(NuGetPackOutput)'!='' and '$(SignNupkg)'!='false' ">
63 <ItemGroup>
64 <SigningNupkgs Include="@(NuGetPackOutput)" Condition=" '%(Extension)'=='.nupkg' " />
65 </ItemGroup>
66
67 <Exec Command='"$(SigningToolExe)" sign -i "@(SigningNupkgs->&apos;%(Identity)&apos;)" -c "$(SigningConfiguration)" -f "$(SigningFilelist)" -n "WiX Toolset" -d "WiX Toolset" -u https://wixtoolset.org/ -r "$(SigningUser)" -s "$(SigningSecret)"'
68 WorkingDirectory="$(MSBuildProjectDirectory)" EchoOff="true" />
69 </Target>
70
71 <Import Project="Directory$(MSBuildProjectExtension).targets" Condition=" Exists('Directory$(MSBuildProjectExtension).targets') " />
72 <Import Project="Custom.Build.targets" Condition=" Exists('Custom.Build.targets') " />
73</Project>
diff --git a/src/libs/dutil/Directory.csproj.props b/src/libs/dutil/Directory.csproj.props
new file mode 100644
index 00000000..81d24ad1
--- /dev/null
+++ b/src/libs/dutil/Directory.csproj.props
@@ -0,0 +1,13 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<!--
3 Do NOT modify this file. Update the canonical version in Home\repo-template\src\CSharp.Build.props
4 then update all of the repos.
5-->
6<Project>
7 <PropertyGroup>
8 <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
9 <SignAssembly>true</SignAssembly>
10 <AssemblyOriginatorKeyFile>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)wix.snk))</AssemblyOriginatorKeyFile>
11 <NBGV_EmitThisAssemblyClass>false</NBGV_EmitThisAssemblyClass>
12 </PropertyGroup>
13</Project>
diff --git a/src/libs/dutil/Directory.vcxproj.props b/src/libs/dutil/Directory.vcxproj.props
new file mode 100644
index 00000000..9ea7071b
--- /dev/null
+++ b/src/libs/dutil/Directory.vcxproj.props
@@ -0,0 +1,115 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project>
5 <PropertyGroup>
6 <Platform Condition=" '$(Platform)' == '' OR '$(Platform)' == 'AnyCPU' ">Win32</Platform>
7 <IntDir>$(BaseIntermediateOutputPath)$(Configuration)\$(Platform)\</IntDir>
8 <OutDir>$(OutputPath)$(Platform)\</OutDir>
9
10 <!-- NBGV properties -->
11 <AssemblyCompany>$(Company)</AssemblyCompany>
12 <AssemblyCopyright>$(Copyright)</AssemblyCopyright>
13
14 <RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
15 <NuGetTargetMoniker>native,Version=v0.0</NuGetTargetMoniker>
16 </PropertyGroup>
17
18 <PropertyGroup Condition="'$(WindowsTargetPlatformVersion)'=='' AND '$(VisualStudioVersion)'>='15.0'">
19 <WindowsTargetPlatformVersion>$([Microsoft.Build.Utilities.ToolLocationHelper]::GetLatestSDKTargetPlatformVersion('Windows', '10.0'))</WindowsTargetPlatformVersion>
20 </PropertyGroup>
21
22 <PropertyGroup>
23 <CodeAnalysisRuleSet Condition=" Exists('$(MSBuildThisFileDirectory)CustomizedNativeRecommendedRules.ruleset') ">$(MSBuildThisFileDirectory)CustomizedNativeRecommendedRules.ruleset</CodeAnalysisRuleSet>
24 </PropertyGroup>
25
26 <ItemDefinitionGroup>
27 <ClCompile>
28 <DisableSpecificWarnings>$(DisableSpecificCompilerWarnings)</DisableSpecificWarnings>
29 <WarningLevel>Level4</WarningLevel>
30 <AdditionalIncludeDirectories>$(ProjectDir)inc;$(MSBuildProjectDirectory);$(IntDir);$(SqlCESdkIncludePath);$(ProjectAdditionalIncludeDirectories);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
31 <PreprocessorDefinitions>WIN32;_WINDOWS;_WIN32_MSI=500;_WIN32_WINNT=0x0501;$(ArmPreprocessorDefinitions);$(UnicodePreprocessorDefinitions);_CRT_STDIO_LEGACY_WIDE_SPECIFIERS;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
32 <PrecompiledHeader>Use</PrecompiledHeader>
33 <PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
34 <CallingConvention Condition="'$(Platform)'=='Win32'">StdCall</CallingConvention>
35 <TreatWarningAsError>true</TreatWarningAsError>
36 <ExceptionHandling>false</ExceptionHandling>
37 <AdditionalOptions>-YlprecompDefine</AdditionalOptions>
38 <AdditionalOptions Condition=" $(PlatformToolset.StartsWith('v14')) ">/Zc:threadSafeInit- %(AdditionalOptions)</AdditionalOptions>
39 <MultiProcessorCompilation Condition=" $(NUMBER_OF_PROCESSORS) &gt; 4 ">true</MultiProcessorCompilation>
40 </ClCompile>
41 <ResourceCompile>
42 <PreprocessorDefinitions>$(ArmPreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
43 <AdditionalIncludeDirectories>$(ProjectAdditionalResourceIncludeDirectories);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
44 </ResourceCompile>
45 <Lib>
46 <AdditionalLibraryDirectories>$(OutDir);$(AdditionalMultiTargetLibraryPath);$(ProjectAdditionalLibraryDirectories);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
47 </Lib>
48 <Link>
49 <SubSystem>$(ProjectSubSystem)</SubSystem>
50 <ModuleDefinitionFile>$(ProjectModuleDefinitionFile)</ModuleDefinitionFile>
51 <NoEntryPoint>$(ResourceOnlyDll)</NoEntryPoint>
52 <GenerateDebugInformation>true</GenerateDebugInformation>
53 <AdditionalDependencies>$(ProjectAdditionalLinkLibraries);advapi32.lib;comdlg32.lib;user32.lib;oleaut32.lib;gdi32.lib;shell32.lib;ole32.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
54 <AdditionalLibraryDirectories>$(OutDir);$(AdditionalMultiTargetLibraryPath);$(ArmLibraryDirectories);$(ProjectAdditionalLinkLibraryDirectories);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
55 <AdditionalOptions Condition=" $(PlatformToolset.StartsWith('v14')) ">/IGNORE:4099 %(AdditionalOptions)</AdditionalOptions>
56 </Link>
57 </ItemDefinitionGroup>
58
59 <ItemDefinitionGroup Condition=" '$(Platform)'=='Win32' and '$(PlatformToolset)'!='v100'">
60 <ClCompile>
61 <EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
62 </ClCompile>
63 </ItemDefinitionGroup>
64 <ItemDefinitionGroup Condition=" '$(Platform)'=='arm' ">
65 <ClCompile>
66 <CallingConvention>CDecl</CallingConvention>
67 </ClCompile>
68 </ItemDefinitionGroup>
69 <ItemDefinitionGroup Condition=" '$(ConfigurationType)'=='StaticLibrary' ">
70 <ClCompile>
71 <DebugInformationFormat>OldStyle</DebugInformationFormat>
72 <OmitDefaultLibName>true</OmitDefaultLibName>
73 <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
74 </ClCompile>
75 </ItemDefinitionGroup>
76 <ItemDefinitionGroup Condition=" '$(Configuration)'=='Debug' ">
77 <ClCompile>
78 <Optimization>Disabled</Optimization>
79 <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
80 <PreprocessorDefinitions>_DEBUG;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
81 <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
82 </ClCompile>
83 </ItemDefinitionGroup>
84 <ItemDefinitionGroup Condition=" '$(Configuration)'=='Debug' and '$(CLRSupport)'=='true' ">
85 <ClCompile>
86 <BasicRuntimeChecks></BasicRuntimeChecks>
87 <RuntimeLibrary>MultiThreadedDebugDll</RuntimeLibrary>
88 </ClCompile>
89 </ItemDefinitionGroup>
90 <ItemDefinitionGroup Condition=" '$(Configuration)'=='Release' ">
91 <ClCompile>
92 <Optimization>MinSpace</Optimization>
93 <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
94 <FunctionLevelLinking>true</FunctionLevelLinking>
95 <IntrinsicFunctions>true</IntrinsicFunctions>
96 <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
97 </ClCompile>
98 <Link>
99 <EnableCOMDATFolding>true</EnableCOMDATFolding>
100 <OptimizeReferences>true</OptimizeReferences>
101 </Link>
102 </ItemDefinitionGroup>
103 <ItemDefinitionGroup Condition=" '$(Configuration)'=='Release' and '$(CLRSupport)'=='true' ">
104 <ClCompile>
105 <BasicRuntimeChecks></BasicRuntimeChecks>
106 <RuntimeLibrary>MultiThreadedDll</RuntimeLibrary>
107 </ClCompile>
108 </ItemDefinitionGroup>
109 <ItemDefinitionGroup Condition=" '$(CLRSupport)'=='true' ">
110 <Link>
111 <KeyFile>$(LinkKeyFile)</KeyFile>
112 <DelaySign>$(LinkDelaySign)</DelaySign>
113 </Link>
114 </ItemDefinitionGroup>
115</Project>
diff --git a/src/libs/dutil/NativeMultiTargeting.Build.props b/src/libs/dutil/NativeMultiTargeting.Build.props
new file mode 100644
index 00000000..1ff46559
--- /dev/null
+++ b/src/libs/dutil/NativeMultiTargeting.Build.props
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project>
5 <!-- Overrides the standard Cpp.Build.props to include the PlatformToolset in the output path. -->
6 <PropertyGroup>
7 <IntDir>$(BaseIntermediateOutputPath)$(Configuration)\$(PlatformToolset)\$(PlatformTarget)\</IntDir>
8 <OutDir>$(OutputPath)$(PlatformToolset)\$(PlatformTarget)\</OutDir>
9 </PropertyGroup>
10</Project>
diff --git a/src/libs/dutil/README.md b/src/libs/dutil/README.md
new file mode 100644
index 00000000..2d6605fe
--- /dev/null
+++ b/src/libs/dutil/README.md
@@ -0,0 +1,2 @@
1# dutil
2dutil.lib - foundation library for all native code in WiX Toolset
diff --git a/src/libs/dutil/WixToolset.DUtil/acl2util.cpp b/src/libs/dutil/WixToolset.DUtil/acl2util.cpp
new file mode 100644
index 00000000..598f12e7
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/acl2util.cpp
@@ -0,0 +1,135 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5// Exit macros
6#define AclExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_ACLUTIL, x, s, __VA_ARGS__)
7#define AclExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_ACLUTIL, x, s, __VA_ARGS__)
8#define AclExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_ACLUTIL, x, s, __VA_ARGS__)
9#define AclExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_ACLUTIL, x, s, __VA_ARGS__)
10#define AclExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_ACLUTIL, x, s, __VA_ARGS__)
11#define AclExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_ACLUTIL, x, s, __VA_ARGS__)
12#define AclExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_ACLUTIL, p, x, e, s, __VA_ARGS__)
13#define AclExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_ACLUTIL, p, x, s, __VA_ARGS__)
14#define AclExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_ACLUTIL, p, x, e, s, __VA_ARGS__)
15#define AclExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_ACLUTIL, p, x, s, __VA_ARGS__)
16#define AclExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_ACLUTIL, e, x, s, __VA_ARGS__)
17#define AclExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_ACLUTIL, g, x, s, __VA_ARGS__)
18
19/********************************************************************
20AclCalculateServiceSidString - gets the SID string for the given service name
21
22NOTE: psczSid should be freed with StrFree()
23********************************************************************/
24extern "C" HRESULT DAPI AclCalculateServiceSidString(
25 __in LPCWSTR wzServiceName,
26 __in SIZE_T cchServiceName,
27 __deref_out_z LPWSTR* psczSid
28 )
29{
30 // TODO: use undocumented RtlCreateServiceSid function?
31 // http://blogs.technet.com/b/voy/archive/2007/03/22/per-service-sid.aspx
32 // Assume little endian.
33 HRESULT hr = S_OK;
34 LPWSTR sczUpperServiceName = NULL;
35 DWORD cbHash = SHA1_HASH_LEN;
36 BYTE* pbHash = NULL;
37
38 Assert(psczSid);
39
40 if (0 == cchServiceName)
41 {
42 hr = ::StringCchLengthW(wzServiceName, STRSAFE_MAX_CCH, reinterpret_cast<size_t*>(&cchServiceName));
43 AclExitOnFailure(hr, "Failed to get the length of the service name.");
44 }
45
46 hr = StrAllocStringToUpperInvariant(&sczUpperServiceName, wzServiceName, cchServiceName);
47 AclExitOnFailure(hr, "Failed to upper case the service name.");
48
49 pbHash = reinterpret_cast<BYTE*>(MemAlloc(cbHash, TRUE));
50 AclExitOnNull(pbHash, hr, E_OUTOFMEMORY, "Failed to allocate hash byte array.");
51
52 hr = CrypHashBuffer(reinterpret_cast<BYTE*>(sczUpperServiceName), cchServiceName * sizeof(WCHAR), PROV_RSA_FULL, CALG_SHA1, pbHash, cbHash);
53 AclExitOnNull(pbHash, hr, E_OUTOFMEMORY, "Failed to hash the service name.");
54
55 hr = StrAllocFormatted(psczSid, L"S-1-5-80-%u-%u-%u-%u-%u",
56 MAKEDWORD(MAKEWORD(pbHash[0], pbHash[1]), MAKEWORD(pbHash[2], pbHash[3])),
57 MAKEDWORD(MAKEWORD(pbHash[4], pbHash[5]), MAKEWORD(pbHash[6], pbHash[7])),
58 MAKEDWORD(MAKEWORD(pbHash[8], pbHash[9]), MAKEWORD(pbHash[10], pbHash[11])),
59 MAKEDWORD(MAKEWORD(pbHash[12], pbHash[13]), MAKEWORD(pbHash[14], pbHash[15])),
60 MAKEDWORD(MAKEWORD(pbHash[16], pbHash[17]), MAKEWORD(pbHash[18], pbHash[19])));
61
62LExit:
63 ReleaseMem(pbHash);
64 ReleaseStr(sczUpperServiceName);
65
66 return hr;
67}
68
69
70/********************************************************************
71AclGetAccountSidStringEx - gets a string version of the account's SID
72 calculates a service's SID if lookup fails
73
74NOTE: psczSid should be freed with StrFree()
75********************************************************************/
76extern "C" HRESULT DAPI AclGetAccountSidStringEx(
77 __in_z LPCWSTR wzSystem,
78 __in_z LPCWSTR wzAccount,
79 __deref_out_z LPWSTR* psczSid
80 )
81{
82 HRESULT hr = S_OK;
83 SIZE_T cchAccount = 0;
84 PSID psid = NULL;
85 LPWSTR pwz = NULL;
86 LPWSTR sczSid = NULL;
87
88 Assert(psczSid);
89
90 hr = AclGetAccountSid(wzSystem, wzAccount, &psid);
91 if (SUCCEEDED(hr))
92 {
93 Assert(::IsValidSid(psid));
94
95 if (!::ConvertSidToStringSidW(psid, &pwz))
96 {
97 AclExitWithLastError(hr, "Failed to convert SID to string for Account: %ls", wzAccount);
98 }
99
100 hr = StrAllocString(psczSid, pwz, 0);
101 }
102 else
103 {
104 if (HRESULT_FROM_WIN32(ERROR_NONE_MAPPED) == hr)
105 {
106 HRESULT hrLength = ::StringCchLengthW(wzAccount, STRSAFE_MAX_CCH, reinterpret_cast<size_t*>(&cchAccount));
107 AclExitOnFailure(hrLength, "Failed to get the length of the account name.");
108
109 if (11 < cchAccount && CSTR_EQUAL == CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, L"NT SERVICE\\", 11, wzAccount, 11))
110 {
111 // If the service is not installed then LookupAccountName doesn't resolve the SID, but we can calculate it.
112 LPCWSTR wzServiceName = &wzAccount[11];
113 hr = AclCalculateServiceSidString(wzServiceName, cchAccount - 11, &sczSid);
114 AclExitOnFailure(hr, "Failed to calculate the service SID for %ls", wzServiceName);
115
116 *psczSid = sczSid;
117 sczSid = NULL;
118 }
119 }
120 AclExitOnFailure(hr, "Failed to get SID for account: %ls", wzAccount);
121 }
122
123LExit:
124 ReleaseStr(sczSid);
125 if (pwz)
126 {
127 ::LocalFree(pwz);
128 }
129 if (psid)
130 {
131 AclFreeSid(psid);
132 }
133
134 return hr;
135}
diff --git a/src/libs/dutil/WixToolset.DUtil/aclutil.cpp b/src/libs/dutil/WixToolset.DUtil/aclutil.cpp
new file mode 100644
index 00000000..c9733033
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/aclutil.cpp
@@ -0,0 +1,1044 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5// Exit macros
6#define AclExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_ACLUTIL, x, s, __VA_ARGS__)
7#define AclExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_ACLUTIL, x, s, __VA_ARGS__)
8#define AclExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_ACLUTIL, x, s, __VA_ARGS__)
9#define AclExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_ACLUTIL, x, s, __VA_ARGS__)
10#define AclExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_ACLUTIL, x, s, __VA_ARGS__)
11#define AclExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_ACLUTIL, x, s, __VA_ARGS__)
12#define AclExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_ACLUTIL, p, x, e, s, __VA_ARGS__)
13#define AclExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_ACLUTIL, p, x, s, __VA_ARGS__)
14#define AclExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_ACLUTIL, p, x, e, s, __VA_ARGS__)
15#define AclExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_ACLUTIL, p, x, s, __VA_ARGS__)
16#define AclExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_ACLUTIL, e, x, s, __VA_ARGS__)
17#define AclExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_ACLUTIL, g, x, s, __VA_ARGS__)
18
19/********************************************************************
20AclCheckAccess - determines if token has appropriate privileges
21
22NOTE: paa->fDenyAccess and paa->dwAccessMask are ignored and must be zero
23if hToken is NULL, the thread will be checked
24if hToken is not NULL the token must be an impersonation token
25********************************************************************/
26extern "C" HRESULT DAPI AclCheckAccess(
27 __in HANDLE hToken,
28 __in ACL_ACCESS* paa
29 )
30{
31 HRESULT hr = S_OK;
32 PSID psid = NULL;
33 BOOL fIsMember = FALSE;
34
35 AclExitOnNull(paa, hr, E_INVALIDARG, "Failed to check ACL access, because no acl access provided to check");
36 Assert(0 == paa->fDenyAccess && 0 == paa->dwAccessMask);
37
38 if (paa->pwzAccountName)
39 {
40 hr = AclGetAccountSid(NULL, paa->pwzAccountName, &psid);
41 AclExitOnFailure(hr, "failed to get SID for account: %ls", paa->pwzAccountName);
42 }
43 else
44 {
45 if (!::AllocateAndInitializeSid(&paa->sia, paa->nSubAuthorityCount, paa->nSubAuthority[0], paa->nSubAuthority[1], paa->nSubAuthority[2], paa->nSubAuthority[3], paa->nSubAuthority[4], paa->nSubAuthority[5], paa->nSubAuthority[6], paa->nSubAuthority[7], &psid))
46 {
47 AclExitWithLastError(hr, "failed to initialize SID");
48 }
49 }
50
51 if (!::CheckTokenMembership(hToken, psid, &fIsMember))
52 {
53 AclExitWithLastError(hr, "failed to check membership");
54 }
55
56 fIsMember ? hr = S_OK : hr = S_FALSE;
57
58LExit:
59 if (psid)
60 {
61 ::FreeSid(psid); // TODO: does this have bad behavior if SID was allocated by Heap from AclGetAccountSid?
62 }
63
64 return hr;
65}
66
67
68/********************************************************************
69AclCheckAdministratorAccess - determines if token has Administrator privileges
70
71NOTE: if hToken is NULL, the thread will be checked
72if hToken is not NULL the token must be an impersonation token
73********************************************************************/
74extern "C" HRESULT DAPI AclCheckAdministratorAccess(
75 __in HANDLE hToken
76 )
77{
78 ACL_ACCESS aa;
79 SID_IDENTIFIER_AUTHORITY siaNt = SECURITY_NT_AUTHORITY;
80
81 memset(&aa, 0, sizeof(aa));
82 aa.sia = siaNt;
83 aa.nSubAuthorityCount = 2;
84 aa.nSubAuthority[0] = SECURITY_BUILTIN_DOMAIN_RID;
85 aa.nSubAuthority[1] = DOMAIN_ALIAS_RID_ADMINS;
86
87 return AclCheckAccess(hToken, &aa);
88}
89
90
91/********************************************************************
92AclCheckLocalSystemAccess - determines if token has LocalSystem privileges
93
94NOTE: if hToken is NULL, the thread will be checked
95if hToken is not NULL the token must be an impersonation token
96********************************************************************/
97extern "C" HRESULT DAPI AclCheckLocalSystemAccess(
98 __in HANDLE hToken
99 )
100{
101 ACL_ACCESS aa;
102 SID_IDENTIFIER_AUTHORITY siaNt = SECURITY_NT_AUTHORITY;
103
104 memset(&aa, 0, sizeof(aa));
105 aa.sia = siaNt;
106 aa.nSubAuthorityCount = 1;
107 aa.nSubAuthority[0] = SECURITY_LOCAL_SYSTEM_RID;
108
109 return AclCheckAccess(hToken, &aa);
110}
111
112
113/********************************************************************
114AclGetWellKnownSid - returns a SID for the specified account
115
116********************************************************************/
117extern "C" HRESULT DAPI AclGetWellKnownSid(
118 __in WELL_KNOWN_SID_TYPE wkst,
119 __deref_out PSID* ppsid
120 )
121{
122 Assert(ppsid);
123
124 HRESULT hr = S_OK;;
125 PSID psid = NULL;
126 DWORD cbSid = SECURITY_MAX_SID_SIZE;
127
128 PSID psidTemp = NULL;
129#if(_WIN32_WINNT < 0x0501)
130 SID_IDENTIFIER_AUTHORITY siaNT = SECURITY_NT_AUTHORITY;
131 SID_IDENTIFIER_AUTHORITY siaWorld = SECURITY_WORLD_SID_AUTHORITY;
132 SID_IDENTIFIER_AUTHORITY siaCreator = SECURITY_CREATOR_SID_AUTHORITY;
133 BOOL fSuccess = FALSE;
134#endif
135
136 //
137 // allocate memory for the SID and get it
138 //
139 psid = static_cast<PSID>(MemAlloc(cbSid, TRUE));
140 AclExitOnNull(psid, hr, E_OUTOFMEMORY, "failed allocate memory for well known SID");
141
142#if(_WIN32_WINNT < 0x0501)
143 switch (wkst)
144 {
145 case WinWorldSid: // Everyone
146 fSuccess = ::AllocateAndInitializeSid(&siaWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &psidTemp);
147 break;
148 case WinAuthenticatedUserSid: // Authenticated Users
149 fSuccess = ::AllocateAndInitializeSid(&siaNT, 1, SECURITY_AUTHENTICATED_USER_RID, 0, 0, 0, 0, 0, 0, 0, &psidTemp);
150 break;
151 case WinLocalSystemSid: // LocalSystem
152 fSuccess = ::AllocateAndInitializeSid(&siaNT, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &psidTemp);
153 break;
154 case WinLocalServiceSid: // LocalService
155 fSuccess = ::AllocateAndInitializeSid(&siaNT, 1, SECURITY_LOCAL_SERVICE_RID, 0, 0, 0, 0, 0, 0, 0, &psidTemp);
156 break;
157 case WinNetworkServiceSid: // NetworkService
158 fSuccess = ::AllocateAndInitializeSid(&siaNT, 1, SECURITY_NETWORK_SERVICE_RID, 0, 0, 0, 0, 0, 0, 0, &psidTemp);
159 break;
160 case WinBuiltinGuestsSid: // Guests
161 fSuccess = ::AllocateAndInitializeSid(&siaNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_GUESTS, 0, 0, 0, 0, 0, 0, &psidTemp);
162 break;
163 case WinBuiltinAdministratorsSid: // Administrators
164 fSuccess = ::AllocateAndInitializeSid(&siaNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &psidTemp);
165 break;
166 case WinBuiltinUsersSid: // Users
167 fSuccess = ::AllocateAndInitializeSid(&siaNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_USERS, 0, 0, 0, 0, 0, 0, &psidTemp);
168 break;
169 case WinCreatorOwnerSid: //CREATOR OWNER
170 fSuccess = ::AllocateAndInitializeSid(&siaCreator, 1, SECURITY_CREATOR_OWNER_RID, 0, 0, 0, 0, 0, 0, 0, &psidTemp);
171 break;
172 case WinInteractiveSid: // INTERACTIVE
173 fSuccess = ::AllocateAndInitializeSid(&siaNT, 1, SECURITY_INTERACTIVE_RID, 0, 0, 0, 0, 0, 0, 0, &psidTemp);
174 break;
175 default:
176 hr = E_INVALIDARG;
177 AclExitOnFailure(hr, "unknown well known SID: %d", wkst);
178 }
179
180 if (!fSuccess)
181 AclExitOnLastError(hr, "failed to allocate well known SID: %d", wkst);
182
183 if (!::CopySid(cbSid, psid, psidTemp))
184 AclExitOnLastError(hr, "failed to create well known SID: %d", wkst);
185#else
186 Assert(NULL == psidTemp);
187 if (!::CreateWellKnownSid(wkst, NULL, psid, &cbSid))
188 {
189 AclExitWithLastError(hr, "failed to create well known SID: %d", wkst);
190 }
191#endif
192
193 *ppsid = psid;
194 psid = NULL; // null it here so it won't be released below
195
196 Assert(S_OK == hr && ::IsValidSid(*ppsid));
197LExit:
198 if (psidTemp)
199 {
200 ::FreeSid(psidTemp);
201 }
202
203 ReleaseMem(psid);
204
205 return hr;
206}
207
208
209/********************************************************************
210AclGetAccountSid - returns a SID for the specified account
211
212********************************************************************/
213extern "C" HRESULT DAPI AclGetAccountSid(
214 __in_opt LPCWSTR wzSystem,
215 __in_z LPCWSTR wzAccount,
216 __deref_out PSID* ppsid
217 )
218{
219 Assert(wzAccount && *wzAccount && ppsid);
220
221 HRESULT hr = S_OK;
222 UINT er = ERROR_SUCCESS;
223 PSID psid = NULL;
224 DWORD cbSid = SECURITY_MAX_SID_SIZE;
225 LPWSTR pwzDomainName = NULL;
226 DWORD cbDomainName = 255;
227 SID_NAME_USE peUse;
228
229 //
230 // allocate memory for the SID and domain name
231 //
232 psid = static_cast<PSID>(MemAlloc(cbSid, TRUE));
233 AclExitOnNull(psid, hr, E_OUTOFMEMORY, "failed to allocate memory for SID");
234 hr = StrAlloc(&pwzDomainName, cbDomainName);
235 AclExitOnFailure(hr, "failed to allocate string for domain name");
236
237 //
238 // try to lookup the account now
239 //
240 if (!::LookupAccountNameW(wzSystem, wzAccount, psid, &cbSid, pwzDomainName, &cbDomainName, &peUse))
241 {
242 // if one of the buffers wasn't large enough
243 er = ::GetLastError();
244 if (ERROR_INSUFFICIENT_BUFFER == er)
245 {
246 if (SECURITY_MAX_SID_SIZE < cbSid)
247 {
248 PSID psidNew = static_cast<PSID>(MemReAlloc(psid, cbSid, TRUE));
249 AclExitOnNullWithLastError(psidNew, hr, "failed to allocate memory for account: %ls", wzAccount);
250
251 psid = psidNew;
252 }
253 if (255 < cbDomainName)
254 {
255 hr = StrAlloc(&pwzDomainName, cbDomainName);
256 AclExitOnFailure(hr, "failed to allocate string for domain name");
257 }
258
259 if (!::LookupAccountNameW(wzSystem, wzAccount, psid, &cbSid, pwzDomainName, &cbDomainName, &peUse))
260 {
261 AclExitWithLastError(hr, "failed to lookup account: %ls", wzAccount);
262 }
263 }
264 else
265 {
266 AclExitOnWin32Error(er, hr, "failed to lookup account: %ls", wzAccount);
267 }
268 }
269
270 *ppsid = psid;
271 psid = NULL;
272
273 hr = S_OK;
274LExit:
275 ReleaseStr(pwzDomainName);
276 ReleaseMem(psid);
277
278 return hr;
279}
280
281
282/********************************************************************
283AclGetAccountSidString - gets a string version of the user's SID
284
285NOTE: ppwzSid should be freed with StrFree()
286********************************************************************/
287extern "C" HRESULT DAPI AclGetAccountSidString(
288 __in_z LPCWSTR wzSystem,
289 __in_z LPCWSTR wzAccount,
290 __deref_out_z LPWSTR* ppwzSid
291 )
292{
293 Assert(ppwzSid);
294 HRESULT hr = S_OK;
295 PSID psid = NULL;
296 LPWSTR pwz = NULL;
297
298 *ppwzSid = NULL;
299
300 hr = AclGetAccountSid(wzSystem, wzAccount, &psid);
301 AclExitOnFailure(hr, "failed to get SID for account: %ls", wzAccount);
302 Assert(::IsValidSid(psid));
303
304 if (!::ConvertSidToStringSidW(psid, &pwz))
305 {
306 AclExitWithLastError(hr, "failed to convert SID to string for Account: %ls", wzAccount);
307 }
308
309 hr = StrAllocString(ppwzSid, pwz, 0);
310
311LExit:
312 if (FAILED(hr))
313 {
314 ReleaseNullStr(*ppwzSid);
315 }
316
317 if (pwz)
318 {
319 ::LocalFree(pwz);
320 }
321
322 if (psid)
323 {
324 AclFreeSid(psid);
325 }
326
327 return hr;
328}
329
330
331/********************************************************************
332AclCreateDacl - creates a DACL from ACL_ACE structures
333
334********************************************************************/
335extern "C" HRESULT DAPI AclCreateDacl(
336 __in_ecount(cDeny) ACL_ACE rgaaDeny[],
337 __in DWORD cDeny,
338 __in_ecount(cAllow) ACL_ACE rgaaAllow[],
339 __in DWORD cAllow,
340 __deref_out ACL** ppAcl
341 )
342{
343 Assert(ppAcl);
344 HRESULT hr = S_OK;
345 ACL* pAcl = NULL;
346 DWORD cbAcl = 0;
347 DWORD i;
348
349 *ppAcl = NULL;
350
351 // initialize the ACL
352 cbAcl = sizeof(ACL);
353 for (i = 0; i < cDeny; ++i)
354 {
355 cbAcl += sizeof(ACCESS_DENIED_ACE) + ::GetLengthSid(rgaaDeny[i].psid) - sizeof(DWORD);
356 }
357
358 for (i = 0; i < cAllow; ++i)
359 {
360 cbAcl += sizeof(ACCESS_ALLOWED_ACE) + ::GetLengthSid(rgaaAllow[i].psid) - sizeof(DWORD);
361 }
362
363 pAcl = static_cast<ACL*>(MemAlloc(cbAcl, TRUE));
364 AclExitOnNull(pAcl, hr, E_OUTOFMEMORY, "failed to allocate ACL");
365
366#pragma prefast(push)
367#pragma prefast(disable:25029)
368 if (!::InitializeAcl(pAcl, cbAcl, ACL_REVISION))
369#pragma prefast(pop)
370 {
371 AclExitWithLastError(hr, "failed to initialize ACL");
372 }
373
374 // add in the ACEs (denied first)
375 for (i = 0; i < cDeny; ++i)
376 {
377#pragma prefast(push)
378#pragma prefast(disable:25029)
379 if (!::AddAccessDeniedAceEx(pAcl, ACL_REVISION, rgaaDeny[i].dwFlags, rgaaDeny[i].dwMask, rgaaDeny[i].psid))
380#pragma prefast(pop)
381 {
382 AclExitWithLastError(hr, "failed to add access denied ACE #%d to ACL", i);
383 }
384 }
385 for (i = 0; i < cAllow; ++i)
386 {
387#pragma prefast(push)
388#pragma prefast(disable:25029)
389 if (!::AddAccessAllowedAceEx(pAcl, ACL_REVISION, rgaaAllow[i].dwFlags, rgaaAllow[i].dwMask, rgaaAllow[i].psid))
390#pragma prefast(pop)
391 {
392 AclExitWithLastError(hr, "failed to add access allowed ACE #%d to ACL", i);
393 }
394 }
395
396 *ppAcl = pAcl;
397 pAcl = NULL;
398 AssertSz(::IsValidAcl(*ppAcl), "AclCreateDacl() - created invalid ACL");
399 Assert(S_OK == hr);
400LExit:
401 if (pAcl)
402 {
403 AclFreeDacl(pAcl);
404 }
405
406 return hr;
407}
408
409
410/********************************************************************
411AclAddToDacl - creates a new DACL from an ACL plus new ACL_ACE structure
412
413********************************************************************/
414extern "C" HRESULT DAPI AclAddToDacl(
415 __in ACL* pAcl,
416 __in_ecount_opt(cDeny) const ACL_ACE rgaaDeny[],
417 __in DWORD cDeny,
418 __in_ecount_opt(cAllow) const ACL_ACE rgaaAllow[],
419 __in DWORD cAllow,
420 __deref_out ACL** ppAclNew
421 )
422{
423 Assert(pAcl && ::IsValidAcl(pAcl) && ppAclNew);
424 HRESULT hr = S_OK;
425
426 ACL_SIZE_INFORMATION asi;
427 ACL_ACE* paaNewDeny = NULL;
428 DWORD cNewDeny = 0;
429 ACL_ACE* paaNewAllow = NULL;
430 DWORD cNewAllow = 0;
431
432 ACCESS_ALLOWED_ACE* paaa;
433 ACCESS_DENIED_ACE* pada;
434 DWORD i;
435
436 // allocate memory for all the new ACEs (NOTE: this over calculates the memory necessary, but that's okay)
437 if (!::GetAclInformation(pAcl, &asi, sizeof(asi), AclSizeInformation))
438 {
439 AclExitWithLastError(hr, "failed to get information about original ACL");
440 }
441
442 if ((asi.AceCount + cDeny) < asi.AceCount || // check for overflow
443 (asi.AceCount + cDeny) < cDeny || // check for overflow
444 (asi.AceCount + cDeny) >= MAXSIZE_T / sizeof(ACL_ACE))
445 {
446 hr = E_OUTOFMEMORY;
447 AclExitOnFailure(hr, "Not enough memory to allocate %d ACEs", (asi.AceCount + cDeny));
448 }
449
450 paaNewDeny = static_cast<ACL_ACE*>(MemAlloc(sizeof(ACL_ACE) * (asi.AceCount + cDeny), TRUE));
451 AclExitOnNull(paaNewDeny, hr, E_OUTOFMEMORY, "failed to allocate memory for new deny ACEs");
452
453 if ((asi.AceCount + cAllow) < asi.AceCount || // check for overflow
454 (asi.AceCount + cAllow) < cAllow || // check for overflow
455 (asi.AceCount + cAllow) >= MAXSIZE_T / sizeof(ACL_ACE))
456 {
457 hr = E_OUTOFMEMORY;
458 AclExitOnFailure(hr, "Not enough memory to allocate %d ACEs", (asi.AceCount + cAllow));
459 }
460
461 paaNewAllow = static_cast<ACL_ACE*>(MemAlloc(sizeof(ACL_ACE) * (asi.AceCount + cAllow), TRUE));
462 AclExitOnNull(paaNewAllow, hr, E_OUTOFMEMORY, "failed to allocate memory for new allow ACEs");
463
464 // fill in the new structures with old data then new data (denied first)
465 for (i = 0; i < asi.AceCount; ++i)
466 {
467 if (!::GetAce(pAcl, i, reinterpret_cast<LPVOID*>(&pada)))
468 {
469 AclExitWithLastError(hr, "failed to get ACE #%d from ACL", i);
470 }
471
472 if (ACCESS_DENIED_ACE_TYPE != pada->Header.AceType)
473 {
474 continue; // skip non-denied aces
475 }
476
477 paaNewDeny[i].dwFlags = pada->Header.AceFlags;
478 paaNewDeny[i].dwMask = pada->Mask;
479 paaNewDeny[i].psid = reinterpret_cast<PSID>(&(pada->SidStart));
480 ++cNewDeny;
481 }
482
483 memcpy(paaNewDeny + cNewDeny, rgaaDeny, sizeof(ACL_ACE) * cDeny);
484 cNewDeny += cDeny;
485
486
487 for (i = 0; i < asi.AceCount; ++i)
488 {
489 if (!::GetAce(pAcl, i, reinterpret_cast<LPVOID*>(&paaa)))
490 {
491 AclExitWithLastError(hr, "failed to get ACE #%d from ACL", i);
492 }
493
494 if (ACCESS_ALLOWED_ACE_TYPE != paaa->Header.AceType)
495 {
496 continue; // skip non-allowed aces
497 }
498
499 paaNewAllow[i].dwFlags = paaa->Header.AceFlags;
500 paaNewAllow[i].dwMask = paaa->Mask;
501 paaNewAllow[i].psid = reinterpret_cast<PSID>(&(paaa->SidStart));
502 ++cNewAllow;
503 }
504
505 memcpy(paaNewAllow + cNewAllow, rgaaAllow, sizeof(ACL_ACE) * cAllow);
506 cNewAllow += cAllow;
507
508 // create the dacl with the new
509 hr = AclCreateDacl(paaNewDeny, cNewDeny, paaNewAllow, cNewAllow, ppAclNew);
510 AclExitOnFailure(hr, "failed to create new ACL from existing ACL");
511
512 AssertSz(::IsValidAcl(*ppAclNew), "AclAddToDacl() - created invalid ACL");
513 Assert(S_OK == hr);
514LExit:
515 ReleaseMem(paaNewAllow);
516 ReleaseMem(paaNewDeny);
517
518 return hr;
519}
520
521
522/********************************************************************
523AclMergeDacls - creates a new DACL from two existing ACLs
524
525********************************************************************/
526extern "C" HRESULT DAPI AclMergeDacls(
527 __in const ACL* pAcl1,
528 __in const ACL* pAcl2,
529 __deref_out ACL** ppAclNew
530 )
531{
532 HRESULT hr = E_NOTIMPL;
533
534 Assert(pAcl1 && pAcl2 && ppAclNew);
535 UNREFERENCED_PARAMETER(pAcl1);
536 UNREFERENCED_PARAMETER(pAcl2);
537 UNREFERENCED_PARAMETER(ppAclNew);
538
539//LExit:
540 return hr;
541}
542
543
544/********************************************************************
545AclCreateDaclOld - creates a DACL from an ACL_ACCESS structure
546
547********************************************************************/
548extern "C" HRESULT DAPI AclCreateDaclOld(
549 __in_ecount(cAclAccesses) ACL_ACCESS* paa,
550 __in DWORD cAclAccesses,
551 __deref_out ACL** ppACL
552 )
553{
554 Assert(ppACL);
555 HRESULT hr = S_OK;
556 DWORD* pdwAccessMask = NULL;
557 PSID* ppsid = NULL;
558
559 DWORD i;
560 int cbAcl;
561
562 *ppACL = NULL;
563
564 //
565 // create the SIDs and calculate the space for the ACL
566 //
567 pdwAccessMask = static_cast<DWORD*>(MemAlloc(sizeof(DWORD) * cAclAccesses, TRUE));
568 AclExitOnNull(pdwAccessMask, hr, E_OUTOFMEMORY, "failed allocate memory for access mask");
569 ppsid = static_cast<PSID*>(MemAlloc(sizeof(PSID) * cAclAccesses, TRUE));
570 AclExitOnNull(ppsid, hr, E_OUTOFMEMORY, "failed allocate memory for sid");
571
572 cbAcl = sizeof (ACL); // start with the size of the header
573 for (i = 0; i < cAclAccesses; ++i)
574 {
575 if (paa[i].pwzAccountName)
576 {
577 hr = AclGetAccountSid(NULL, paa[i].pwzAccountName, ppsid + i);
578 AclExitOnFailure(hr, "failed to get SID for account: %ls", paa[i].pwzAccountName);
579 }
580 else
581 {
582 if ((!::AllocateAndInitializeSid(&paa[i].sia, paa[i].nSubAuthorityCount,
583 paa[i].nSubAuthority[0], paa[i].nSubAuthority[1],
584 paa[i].nSubAuthority[2], paa[i].nSubAuthority[3],
585 paa[i].nSubAuthority[4], paa[i].nSubAuthority[5],
586 paa[i].nSubAuthority[6], paa[i].nSubAuthority[7],
587 (void**)(ppsid + i))))
588 {
589 AclExitWithLastError(hr, "failed to initialize SIDs #%u", i);
590 }
591 }
592
593 // add the newly allocated SID size to the count of bytes for this ACL
594 cbAcl +=::GetLengthSid(*(ppsid + i)) - sizeof(DWORD);
595 if (paa[i].fDenyAccess)
596 {
597 cbAcl += sizeof(ACCESS_DENIED_ACE);
598 }
599 else
600 {
601 cbAcl += sizeof(ACCESS_ALLOWED_ACE);
602 }
603
604 pdwAccessMask[i] = paa[i].dwAccessMask;
605 }
606
607 //
608 // allocate the ACL and set the appropriate ACEs
609 //
610 *ppACL = static_cast<ACL*>(MemAlloc(cbAcl, FALSE));
611 AclExitOnNull(*ppACL, hr, E_OUTOFMEMORY, "failed allocate memory for ACL");
612
613#pragma prefast(push)
614#pragma prefast(disable:25029)
615 if (!::InitializeAcl(*ppACL, cbAcl, ACL_REVISION))
616#pragma prefast(pop)
617 {
618 AclExitWithLastError(hr, "failed to initialize ACLs");
619 }
620
621 // add an access-allowed ACE for each of the SIDs
622 for (i = 0; i < cAclAccesses; ++i)
623 {
624 if (paa[i].fDenyAccess)
625 {
626#pragma prefast(push)
627#pragma prefast(disable:25029)
628 if (!::AddAccessDeniedAceEx(*ppACL, ACL_REVISION, CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, pdwAccessMask[i], *(ppsid + i)))
629#pragma prefast(pop)
630 {
631 AclExitWithLastError(hr, "failed to add access denied for ACE");
632 }
633 }
634 else
635 {
636#pragma prefast(push)
637#pragma prefast(disable:25029)
638 if (!::AddAccessAllowedAceEx(*ppACL, ACL_REVISION, CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, pdwAccessMask[i], *(ppsid + i)))
639#pragma prefast(pop)
640 {
641 AclExitWithLastError(hr, "failed to add access allowed for ACE");
642 }
643 }
644 }
645
646LExit:
647 if (FAILED(hr))
648 {
649 ReleaseNullMem(*ppACL);
650 }
651
652 if (ppsid)
653 {
654 for (i = 0; i < cAclAccesses; ++i)
655 {
656 if (ppsid[i])
657 {
658 ::FreeSid(ppsid[i]);
659 }
660 }
661
662 MemFree(ppsid);
663 }
664
665 ReleaseMem(pdwAccessMask);
666
667 return hr;
668}
669
670
671/********************************************************************
672AclCreateSecurityDescriptorFromDacl - creates a self-relative security
673descriptor from an existing DACL
674
675********************************************************************/
676extern "C" HRESULT DAPI AclCreateSecurityDescriptorFromDacl(
677 __in ACL* pACL,
678 __deref_out SECURITY_DESCRIPTOR** ppsd
679 )
680{
681 HRESULT hr = S_OK;
682
683 SECURITY_DESCRIPTOR sd;
684 DWORD cbSD;
685
686 AclExitOnNull(pACL, hr, E_INVALIDARG, "Failed to create security descriptor from DACL, because no DACL was provided");
687 AclExitOnNull(ppsd, hr, E_INVALIDARG, "Failed to create security descriptor from DACL, because no output object was provided");
688
689 *ppsd = NULL;
690
691 //
692 // create the absolute security descriptor
693 //
694
695 // initialize our security descriptor, throw the ACL into it, and set the owner
696#pragma prefast(push)
697#pragma prefast(disable:25028) // We only call this when pACL isn't NULL, so this call is safe according to the docs
698#pragma prefast(disable:25029)
699 if (!::InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION) ||
700 (!::SetSecurityDescriptorDacl(&sd, TRUE, pACL, FALSE)) ||
701 (!::SetSecurityDescriptorOwner(&sd, NULL, FALSE)))
702#pragma prefast(pop)
703 {
704 AclExitWithLastError(hr, "failed to initialize security descriptor");
705 }
706
707 //
708 // create the self-relative security descriptor
709 //
710 cbSD = ::GetSecurityDescriptorLength(&sd);
711 *ppsd = static_cast<SECURITY_DESCRIPTOR*>(MemAlloc(cbSD, FALSE));
712 AclExitOnNull(*ppsd, hr, E_OUTOFMEMORY, "failed allocate memory for security descriptor");
713
714 ::MakeSelfRelativeSD(&sd, (BYTE*)*ppsd, &cbSD);
715 Assert(::IsValidSecurityDescriptor(*ppsd));
716
717LExit:
718 if (FAILED(hr) && NULL != ppsd && NULL != *ppsd)
719 {
720 MemFree(*ppsd);
721 *ppsd = NULL;
722 }
723
724 return hr;
725}
726
727
728/********************************************************************
729AclCreateSecurityDescriptor - creates a self-relative security descriptor from an
730ACL_ACCESS structure
731
732NOTE: ppsd should be freed with AclFreeSecurityDescriptor()
733********************************************************************/
734extern "C" HRESULT DAPI AclCreateSecurityDescriptor(
735 __in_ecount(cAclAccesses) ACL_ACCESS* paa,
736 __in DWORD cAclAccesses,
737 __deref_out SECURITY_DESCRIPTOR** ppsd
738 )
739{
740 Assert(ppsd);
741 HRESULT hr = S_OK;
742
743 ACL* pACL;
744
745 *ppsd = NULL;
746
747 //
748 // create the DACL
749 //
750 hr = AclCreateDaclOld(paa, cAclAccesses, &pACL);
751 AclExitOnFailure(hr, "failed to create DACL for security descriptor");
752
753 //
754 // create self-relative security descriptor
755 //
756 hr = AclCreateSecurityDescriptorFromDacl(pACL, ppsd);
757
758LExit:
759 return hr;
760}
761
762
763/********************************************************************
764AclCreateSecurityDescriptorFromString - creates a self-relative security
765descriptor from an SDDL string
766
767NOTE: ppsd should be freed with AclFreeSecurityDescriptor()
768********************************************************************/
769extern "C" HRESULT DAPI AclCreateSecurityDescriptorFromString(
770 __deref_out SECURITY_DESCRIPTOR** ppsd,
771 __in_z __format_string LPCWSTR wzSddlFormat,
772 ...
773 )
774{
775 Assert(ppsd);
776 HRESULT hr = S_OK;
777 LPWSTR pwzSddl = NULL;
778 va_list args;
779 PSECURITY_DESCRIPTOR psd = NULL;
780 DWORD cbSD = 0;
781
782 *ppsd = NULL;
783
784 va_start(args, wzSddlFormat);
785 hr = StrAllocFormattedArgs(&pwzSddl, wzSddlFormat, args);
786 va_end(args);
787 AclExitOnFailure(hr, "failed to create SDDL string for format: %ls", wzSddlFormat);
788
789 if (!::ConvertStringSecurityDescriptorToSecurityDescriptorW(pwzSddl, SDDL_REVISION_1, &psd, &cbSD))
790 {
791 AclExitWithLastError(hr, "failed to create security descriptor from SDDL: %ls", pwzSddl);
792 }
793
794 *ppsd = static_cast<SECURITY_DESCRIPTOR*>(MemAlloc(cbSD, FALSE));
795 AclExitOnNull(*ppsd, hr, E_OUTOFMEMORY, "failed to allocate memory for security descriptor");
796
797 memcpy(*ppsd, psd, cbSD);
798 Assert(::IsValidSecurityDescriptor(*ppsd));
799
800 Assert(S_OK == hr);
801
802LExit:
803 if (FAILED(hr) && NULL != ppsd && NULL != *ppsd)
804 {
805 MemFree(*ppsd);
806 *ppsd = NULL;
807 }
808
809 if (psd)
810 {
811 ::LocalFree(psd);
812 }
813
814 ReleaseStr(pwzSddl);
815 return hr;
816}
817
818
819/********************************************************************
820AclDuplicateSecurityDescriptor - creates a copy of a self-relative security descriptor
821
822NOTE: passed in security descriptor must be in self-relative format
823********************************************************************/
824extern "C" HRESULT DAPI AclDuplicateSecurityDescriptor(
825 __in SECURITY_DESCRIPTOR* psd,
826 __deref_out SECURITY_DESCRIPTOR** ppsd
827 )
828{
829 HRESULT hr = S_OK;
830 DWORD cbSD;
831
832 AclExitOnNull(ppsd, hr, E_INVALIDARG, "Failed to get duplicate ACL security descriptor because no place to output was provided");
833 *ppsd = NULL;
834
835 //
836 // create the self-relative security descriptor
837 //
838 cbSD = ::GetSecurityDescriptorLength(psd);
839 *ppsd = static_cast<SECURITY_DESCRIPTOR*>(MemAlloc(cbSD, 0));
840 AclExitOnNull(*ppsd, hr, E_OUTOFMEMORY, "failed allocate memory for security descriptor");
841
842 memcpy(*ppsd, psd, cbSD);
843 Assert(::IsValidSecurityDescriptor(*ppsd));
844
845LExit:
846 if (FAILED(hr) && NULL != ppsd && NULL != *ppsd)
847 {
848 MemFree(*ppsd);
849 *ppsd = NULL;
850 }
851
852 return hr;
853}
854
855
856/********************************************************************
857AclGetSecurityDescriptor - returns self-relative security descriptor for named object
858
859NOTE: free ppsd with AclFreeSecurityDescriptor()
860********************************************************************/
861extern "C" HRESULT DAPI AclGetSecurityDescriptor(
862 __in_z LPCWSTR wzObject,
863 __in SE_OBJECT_TYPE sot,
864 __in SECURITY_INFORMATION securityInformation,
865 __deref_out SECURITY_DESCRIPTOR** ppsd
866 )
867{
868 HRESULT hr = S_OK;
869 DWORD er;
870 PSECURITY_DESCRIPTOR psd = NULL;
871 DWORD cbSD;
872
873 AclExitOnNull(ppsd, hr, E_INVALIDARG, "Failed to get ACL Security Descriptor because no place to output was provided");
874 *ppsd = NULL;
875
876 // get the security descriptor for the object
877 er = ::GetNamedSecurityInfoW(const_cast<LPWSTR>(wzObject), sot, securityInformation, NULL, NULL, NULL, NULL, &psd);
878 AclExitOnWin32Error(er, hr, "failed to get security info from object: %ls", wzObject);
879 Assert(::IsValidSecurityDescriptor(psd));
880
881 // copy the self-relative security descriptor
882 cbSD = ::GetSecurityDescriptorLength(psd);
883 *ppsd = static_cast<SECURITY_DESCRIPTOR*>(MemAlloc(cbSD, 0));
884 AclExitOnNull(*ppsd, hr, E_OUTOFMEMORY, "failed allocate memory for security descriptor");
885
886 memcpy(*ppsd, psd, cbSD);
887 Assert(::IsValidSecurityDescriptor(*ppsd));
888
889LExit:
890 if (FAILED(hr) && NULL != ppsd && NULL != *ppsd)
891 {
892 MemFree(*ppsd);
893 *ppsd = NULL;
894 }
895
896 if (psd)
897 {
898 ::LocalFree(psd);
899 }
900
901 return hr;
902}
903
904
905extern "C" HRESULT DAPI AclSetSecurityWithRetry(
906 __in_z LPCWSTR wzObject,
907 __in SE_OBJECT_TYPE sot,
908 __in SECURITY_INFORMATION securityInformation,
909 __in_opt PSID psidOwner,
910 __in_opt PSID psidGroup,
911 __in_opt PACL pDacl,
912 __in_opt PACL pSacl,
913 __in DWORD cRetry,
914 __in DWORD dwWaitMilliseconds
915 )
916{
917 HRESULT hr = S_OK;
918 LPWSTR sczObject = NULL;
919 DWORD i = 0;
920
921 hr = StrAllocString(&sczObject, wzObject, 0);
922 AclExitOnFailure(hr, "Failed to copy object to secure.");
923
924 hr = E_FAIL;
925 for (i = 0; FAILED(hr) && i <= cRetry; ++i)
926 {
927 if (0 < i)
928 {
929 ::Sleep(dwWaitMilliseconds);
930 }
931
932 DWORD er = ::SetNamedSecurityInfoW(sczObject, sot, securityInformation, psidOwner, psidGroup, pDacl, pSacl);
933 hr = HRESULT_FROM_WIN32(er);
934 }
935 AclExitOnRootFailure(hr, "Failed to set security on object '%ls' after %u retries.", wzObject, i);
936
937LExit:
938 ReleaseStr(sczObject);
939
940 return hr;
941}
942
943
944/********************************************************************
945AclFreeSid - frees a SID created by any Acl* functions
946
947********************************************************************/
948extern "C" HRESULT DAPI AclFreeSid(
949 __in PSID psid
950 )
951{
952 Assert(psid && ::IsValidSid(psid));
953 HRESULT hr = S_OK;
954
955 hr = MemFree(psid);
956
957 return hr;
958}
959
960
961/********************************************************************
962AclFreeDacl - frees a DACL created by any Acl* functions
963
964********************************************************************/
965extern "C" HRESULT DAPI AclFreeDacl(
966 __in ACL* pACL
967 )
968{
969 Assert(pACL);
970 HRESULT hr = S_OK;
971
972 hr = MemFree(pACL);
973
974 return hr;
975}
976
977
978/********************************************************************
979AclFreeSecurityDescriptor - frees a security descriptor created by any Acl* functions
980
981********************************************************************/
982extern "C" HRESULT DAPI AclFreeSecurityDescriptor(
983 __in SECURITY_DESCRIPTOR* psd
984 )
985{
986 Assert(psd && ::IsValidSecurityDescriptor(psd));
987 HRESULT hr = S_OK;
988
989 hr = MemFree(psd);
990
991 return hr;
992}
993
994
995/********************************************************************
996AclAddAdminToSecurityDescriptor - Adds the Administrators group to a security descriptor
997
998********************************************************************/
999extern "C" HRESULT DAPI AclAddAdminToSecurityDescriptor(
1000 __in SECURITY_DESCRIPTOR* pSecurity,
1001 __deref_out SECURITY_DESCRIPTOR** ppSecurityNew
1002 )
1003{
1004 HRESULT hr = S_OK;
1005 PACL pAcl = NULL;
1006 PACL pAclNew = NULL;
1007 BOOL fValid, fDaclDefaulted;
1008 ACL_ACE ace[1];
1009 SECURITY_DESCRIPTOR* pSecurityNew;
1010
1011 if (!::GetSecurityDescriptorDacl(pSecurity, &fValid, &pAcl, &fDaclDefaulted) || !fValid)
1012 {
1013 AclExitOnLastError(hr, "Failed to get acl from security descriptor");
1014 }
1015
1016 hr = AclGetWellKnownSid(WinBuiltinAdministratorsSid, &ace[0].psid);
1017 AclExitOnFailure(hr, "failed to get sid for Administrators group");
1018
1019 ace[0].dwFlags = NO_PROPAGATE_INHERIT_ACE;
1020 ace[0].dwMask = GENERIC_ALL;
1021
1022 hr = AclAddToDacl(pAcl, NULL, 0, ace, 1, &pAclNew);
1023 AclExitOnFailure(hr, "failed to add Administrators ACE to ACL");
1024
1025 hr = AclCreateSecurityDescriptorFromDacl(pAclNew, &pSecurityNew);
1026 AclExitOnLastError(hr, "Failed to create new security descriptor");
1027
1028 // The DACL is referenced by, not copied into, the security descriptor. Make sure not to free it.
1029 pAclNew = NULL;
1030
1031 *ppSecurityNew = pSecurityNew;
1032
1033LExit:
1034 if (pAclNew)
1035 {
1036 AclFreeDacl(pAclNew);
1037 }
1038 if (ace[0].psid)
1039 {
1040 AclFreeSid(ace[0].psid);
1041 }
1042
1043 return hr;
1044}
diff --git a/src/libs/dutil/WixToolset.DUtil/apputil.cpp b/src/libs/dutil/WixToolset.DUtil/apputil.cpp
new file mode 100644
index 00000000..589a09dd
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/apputil.cpp
@@ -0,0 +1,124 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5// Exit macros
6#define AppExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_APPUTIL, x, s, __VA_ARGS__)
7#define AppExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_APPUTIL, x, s, __VA_ARGS__)
8#define AppExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_APPUTIL, x, s, __VA_ARGS__)
9#define AppExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_APPUTIL, x, s, __VA_ARGS__)
10#define AppExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_APPUTIL, x, s, __VA_ARGS__)
11#define AppExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_APPUTIL, x, s, __VA_ARGS__)
12#define AppExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_APPUTIL, p, x, e, s, __VA_ARGS__)
13#define AppExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_APPUTIL, p, x, s, __VA_ARGS__)
14#define AppExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_APPUTIL, p, x, e, s, __VA_ARGS__)
15#define AppExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_APPUTIL, p, x, s, __VA_ARGS__)
16#define AppExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_APPUTIL, e, x, s, __VA_ARGS__)
17#define AppExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_APPUTIL, g, x, s, __VA_ARGS__)
18
19const DWORD PRIVATE_LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800;
20typedef BOOL(WINAPI *LPFN_SETDEFAULTDLLDIRECTORIES)(DWORD);
21typedef BOOL(WINAPI *LPFN_SETDLLDIRECTORYW)(LPCWSTR);
22
23extern "C" void DAPI AppFreeCommandLineArgs(
24 __in LPWSTR* argv
25 )
26{
27 // The "ignored" hack in AppParseCommandLine requires an adjustment.
28 LPWSTR* argvOriginal = argv - 1;
29 ::LocalFree(argvOriginal);
30}
31
32/********************************************************************
33AppInitialize - initializes the standard safety precautions for an
34 installation application.
35
36********************************************************************/
37extern "C" void DAPI AppInitialize(
38 __in_ecount(cSafelyLoadSystemDlls) LPCWSTR rgsczSafelyLoadSystemDlls[],
39 __in DWORD cSafelyLoadSystemDlls
40 )
41{
42 HRESULT hr = S_OK;
43 HMODULE hIgnored = NULL;
44 BOOL fSetDefaultDllDirectories = FALSE;
45
46 ::HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
47
48 // Best effort call to initialize default DLL directories to system only.
49 HMODULE hKernel32 = ::GetModuleHandleW(L"kernel32");
50 Assert(hKernel32);
51 LPFN_SETDEFAULTDLLDIRECTORIES pfnSetDefaultDllDirectories = (LPFN_SETDEFAULTDLLDIRECTORIES)::GetProcAddress(hKernel32, "SetDefaultDllDirectories");
52 if (pfnSetDefaultDllDirectories)
53 {
54 if (pfnSetDefaultDllDirectories(PRIVATE_LOAD_LIBRARY_SEARCH_SYSTEM32))
55 {
56 fSetDefaultDllDirectories = TRUE;
57 }
58 else
59 {
60 hr = HRESULT_FROM_WIN32(::GetLastError());
61 TraceError(hr, "Failed to call SetDefaultDllDirectories.");
62 }
63 }
64
65 // Only need to safely load if the default DLL directories was not
66 // able to be set.
67 if (!fSetDefaultDllDirectories)
68 {
69 // Remove current working directory from search order.
70 LPFN_SETDLLDIRECTORYW pfnSetDllDirectory = (LPFN_SETDLLDIRECTORYW)::GetProcAddress(hKernel32, "SetDllDirectoryW");
71 if (!pfnSetDllDirectory || !pfnSetDllDirectory(L""))
72 {
73 hr = HRESULT_FROM_WIN32(::GetLastError());
74 TraceError(hr, "Failed to call SetDllDirectory.");
75 }
76
77 for (DWORD i = 0; i < cSafelyLoadSystemDlls; ++i)
78 {
79 hr = LoadSystemLibrary(rgsczSafelyLoadSystemDlls[i], &hIgnored);
80 if (FAILED(hr))
81 {
82 TraceError(hr, "Failed to safety load: %ls", rgsczSafelyLoadSystemDlls[i]);
83 }
84 }
85 }
86}
87
88extern "C" void DAPI AppInitializeUnsafe()
89{
90 ::HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
91}
92
93extern "C" DAPI_(HRESULT) AppParseCommandLine(
94 __in LPCWSTR wzCommandLine,
95 __in int* pArgc,
96 __in LPWSTR** pArgv
97 )
98{
99 HRESULT hr = S_OK;
100 LPWSTR sczCommandLine = NULL;
101 LPWSTR* argv = NULL;
102 int argc = 0;
103
104 // CommandLineToArgvW tries to treat the first argument as the path to the process,
105 // which fails pretty miserably if your first argument is something like
106 // FOO="C:\Program Files\My Company". So give it something harmless to play with.
107 hr = StrAllocConcat(&sczCommandLine, L"ignored ", 0);
108 AppExitOnFailure(hr, "Failed to initialize command line.");
109
110 hr = StrAllocConcat(&sczCommandLine, wzCommandLine, 0);
111 AppExitOnFailure(hr, "Failed to copy command line.");
112
113 argv = ::CommandLineToArgvW(sczCommandLine, &argc);
114 AppExitOnNullWithLastError(argv, hr, "Failed to parse command line.");
115
116 // Skip "ignored" argument/hack.
117 *pArgv = argv + 1;
118 *pArgc = argc - 1;
119
120LExit:
121 ReleaseStr(sczCommandLine);
122
123 return hr;
124}
diff --git a/src/libs/dutil/WixToolset.DUtil/apuputil.cpp b/src/libs/dutil/WixToolset.DUtil/apuputil.cpp
new file mode 100644
index 00000000..eb96d515
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/apuputil.cpp
@@ -0,0 +1,700 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5// Exit macros
6#define ApupExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_APUPUTIL, x, s, __VA_ARGS__)
7#define ApupExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_APUPUTIL, x, s, __VA_ARGS__)
8#define ApupExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_APUPUTIL, x, s, __VA_ARGS__)
9#define ApupExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_APUPUTIL, x, s, __VA_ARGS__)
10#define ApupExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_APUPUTIL, x, s, __VA_ARGS__)
11#define ApupExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_APUPUTIL, x, s, __VA_ARGS__)
12#define ApupExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_APUPUTIL, p, x, e, s, __VA_ARGS__)
13#define ApupExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_APUPUTIL, p, x, s, __VA_ARGS__)
14#define ApupExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_APUPUTIL, p, x, e, s, __VA_ARGS__)
15#define ApupExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_APUPUTIL, p, x, s, __VA_ARGS__)
16#define ApupExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_APUPUTIL, e, x, s, __VA_ARGS__)
17#define ApupExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_APUPUTIL, g, x, s, __VA_ARGS__)
18
19// prototypes
20static HRESULT ProcessEntry(
21 __in ATOM_ENTRY* pAtomEntry,
22 __in LPCWSTR wzDefaultAppId,
23 __inout APPLICATION_UPDATE_ENTRY* pApupEntry
24 );
25static HRESULT ParseEnclosure(
26 __in ATOM_LINK* pLink,
27 __in APPLICATION_UPDATE_ENCLOSURE* pEnclosure
28 );
29static __callback int __cdecl CompareEntries(
30 void* pvContext,
31 const void* pvLeft,
32 const void* pvRight
33 );
34static HRESULT FilterEntries(
35 __in APPLICATION_UPDATE_ENTRY* rgEntries,
36 __in DWORD cEntries,
37 __in VERUTIL_VERSION* pCurrentVersion,
38 __inout APPLICATION_UPDATE_ENTRY** prgFilteredEntries,
39 __inout DWORD* pcFilteredEntries
40 );
41static HRESULT CopyEntry(
42 __in const APPLICATION_UPDATE_ENTRY* pSrc,
43 __in APPLICATION_UPDATE_ENTRY* pDest
44 );
45static HRESULT CopyEnclosure(
46 __in const APPLICATION_UPDATE_ENCLOSURE* pSrc,
47 __in APPLICATION_UPDATE_ENCLOSURE* pDest
48 );
49static void FreeEntry(
50 __in APPLICATION_UPDATE_ENTRY* pApupEntry
51 );
52static void FreeEnclosure(
53 __in APPLICATION_UPDATE_ENCLOSURE* pEnclosure
54 );
55
56
57//
58// ApupCalculateChainFromAtom - returns the chain of application updates found in an ATOM feed.
59//
60extern "C" HRESULT DAPI ApupAllocChainFromAtom(
61 __in ATOM_FEED* pFeed,
62 __out APPLICATION_UPDATE_CHAIN** ppChain
63 )
64{
65 HRESULT hr = S_OK;
66 APPLICATION_UPDATE_CHAIN* pChain = NULL;
67
68 pChain = static_cast<APPLICATION_UPDATE_CHAIN*>(MemAlloc(sizeof(APPLICATION_UPDATE_CHAIN), TRUE));
69
70 // First search the ATOM feed's custom elements to try and find the default application identity.
71 for (ATOM_UNKNOWN_ELEMENT* pElement = pFeed->pUnknownElements; pElement; pElement = pElement->pNext)
72 {
73 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pElement->wzNamespace, -1, APPLICATION_SYNDICATION_NAMESPACE, -1))
74 {
75 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pElement->wzElement, -1, L"application", -1))
76 {
77 hr = StrAllocString(&pChain->wzDefaultApplicationId, pElement->wzValue, 0);
78 ApupExitOnFailure(hr, "Failed to allocate default application id.");
79
80 for (ATOM_UNKNOWN_ATTRIBUTE* pAttribute = pElement->pAttributes; pAttribute; pAttribute = pAttribute->pNext)
81 {
82 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pAttribute->wzAttribute, -1, L"type", -1))
83 {
84 hr = StrAllocString(&pChain->wzDefaultApplicationType, pAttribute->wzValue, 0);
85 ApupExitOnFailure(hr, "Failed to allocate default application type.");
86 }
87 }
88 }
89 }
90 }
91
92 // Assume there will be as many application updates entries as their are feed entries.
93 if (pFeed->cEntries)
94 {
95 pChain->rgEntries = static_cast<APPLICATION_UPDATE_ENTRY*>(MemAlloc(sizeof(APPLICATION_UPDATE_ENTRY) * pFeed->cEntries, TRUE));
96 ApupExitOnNull(pChain->rgEntries, hr, E_OUTOFMEMORY, "Failed to allocate memory for update entries.");
97
98 // Process each entry, building up the chain.
99 for (DWORD i = 0; i < pFeed->cEntries; ++i)
100 {
101 hr = ProcessEntry(pFeed->rgEntries + i, pChain->wzDefaultApplicationId, pChain->rgEntries + pChain->cEntries);
102 ApupExitOnFailure(hr, "Failed to process ATOM entry.");
103
104 if (S_FALSE != hr)
105 {
106 ++pChain->cEntries;
107 }
108 }
109
110 // Sort the chain by descending version and ascending total size.
111 qsort_s(pChain->rgEntries, pChain->cEntries, sizeof(APPLICATION_UPDATE_ENTRY), CompareEntries, NULL);
112 }
113
114 // Trim the unused entries from the end, if any of the entries failed to parse or validate
115 if (pChain->cEntries != pFeed->cEntries)
116 {
117 if (pChain->cEntries > 0)
118 {
119 pChain->rgEntries = static_cast<APPLICATION_UPDATE_ENTRY*>(MemReAlloc(pChain->rgEntries, sizeof(APPLICATION_UPDATE_ENTRY) * pChain->cEntries, FALSE));
120 ApupExitOnNull(pChain->rgEntries, hr, E_OUTOFMEMORY, "Failed to reallocate memory for update entries.");
121 }
122 else
123 {
124 ReleaseNullMem(pChain->rgEntries);
125 }
126 }
127
128 *ppChain = pChain;
129 pChain = NULL;
130
131LExit:
132 ReleaseApupChain(pChain);
133
134 return hr;
135}
136
137
138//
139// ApupFilterChain - remove the unneeded update elements from the chain.
140//
141HRESULT DAPI ApupFilterChain(
142 __in APPLICATION_UPDATE_CHAIN* pChain,
143 __in VERUTIL_VERSION* pVersion,
144 __out APPLICATION_UPDATE_CHAIN** ppFilteredChain
145 )
146{
147 HRESULT hr = S_OK;
148 APPLICATION_UPDATE_CHAIN* pNewChain = NULL;
149 APPLICATION_UPDATE_ENTRY* prgEntries = NULL;
150 DWORD cEntries = NULL;
151
152 pNewChain = static_cast<APPLICATION_UPDATE_CHAIN*>(MemAlloc(sizeof(APPLICATION_UPDATE_CHAIN), TRUE));
153 ApupExitOnNull(pNewChain, hr, E_OUTOFMEMORY, "Failed to allocate filtered chain.");
154
155 hr = FilterEntries(pChain->rgEntries, pChain->cEntries, pVersion, &prgEntries, &cEntries);
156 ApupExitOnFailure(hr, "Failed to filter entries by version.");
157
158 if (pChain->wzDefaultApplicationId)
159 {
160 hr = StrAllocString(&pNewChain->wzDefaultApplicationId, pChain->wzDefaultApplicationId, 0);
161 ApupExitOnFailure(hr, "Failed to copy default application id.");
162 }
163
164 if (pChain->wzDefaultApplicationType)
165 {
166 hr = StrAllocString(&pNewChain->wzDefaultApplicationType, pChain->wzDefaultApplicationType, 0);
167 ApupExitOnFailure(hr, "Failed to copy default application type.");
168 }
169
170 pNewChain->rgEntries = prgEntries;
171 pNewChain->cEntries = cEntries;
172
173 *ppFilteredChain = pNewChain;
174 pNewChain = NULL;
175
176LExit:
177 ReleaseApupChain(pNewChain);
178 return hr;
179}
180
181
182//
183// ApupFreeChain - frees a previously allocated application update chain.
184//
185extern "C" void DAPI ApupFreeChain(
186 __in APPLICATION_UPDATE_CHAIN* pChain
187 )
188{
189 if (pChain)
190 {
191 for (DWORD i = 0; i < pChain->cEntries; ++i)
192 {
193 FreeEntry(pChain->rgEntries + i);
194 }
195
196 ReleaseMem(pChain->rgEntries);
197 ReleaseStr(pChain->wzDefaultApplicationType);
198 ReleaseStr(pChain->wzDefaultApplicationId);
199 ReleaseMem(pChain);
200 }
201}
202
203
204static HRESULT ProcessEntry(
205 __in ATOM_ENTRY* pAtomEntry,
206 __in LPCWSTR wzDefaultAppId,
207 __inout APPLICATION_UPDATE_ENTRY* pApupEntry
208 )
209{
210 HRESULT hr = S_OK;
211 int nCompareResult = 0;
212
213 // First search the ATOM entry's custom elements to try and find the application update information.
214 for (ATOM_UNKNOWN_ELEMENT* pElement = pAtomEntry->pUnknownElements; pElement; pElement = pElement->pNext)
215 {
216 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pElement->wzNamespace, -1, APPLICATION_SYNDICATION_NAMESPACE, -1))
217 {
218 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pElement->wzElement, -1, L"application", -1))
219 {
220 hr = StrAllocString(&pApupEntry->wzApplicationId, pElement->wzValue, 0);
221 ApupExitOnFailure(hr, "Failed to allocate application identity.");
222
223 for (ATOM_UNKNOWN_ATTRIBUTE* pAttribute = pElement->pAttributes; pAttribute; pAttribute = pAttribute->pNext)
224 {
225 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pAttribute->wzAttribute, -1, L"type", -1))
226 {
227 hr = StrAllocString(&pApupEntry->wzApplicationType, pAttribute->wzValue, 0);
228 ApupExitOnFailure(hr, "Failed to allocate application type.");
229 }
230 }
231 }
232 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pElement->wzElement, -1, L"upgrade", -1))
233 {
234 hr = StrAllocString(&pApupEntry->wzUpgradeId, pElement->wzValue, 0);
235 ApupExitOnFailure(hr, "Failed to allocate upgrade id.");
236
237 for (ATOM_UNKNOWN_ATTRIBUTE* pAttribute = pElement->pAttributes; pAttribute; pAttribute = pAttribute->pNext)
238 {
239 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pAttribute->wzAttribute, -1, L"version", -1))
240 {
241 hr = VerParseVersion(pAttribute->wzValue, 0, FALSE, &pApupEntry->pUpgradeVersion);
242 ApupExitOnFailure(hr, "Failed to parse upgrade version string '%ls' from ATOM entry.", pAttribute->wzValue);
243 }
244 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pAttribute->wzAttribute, -1, L"exclusive", -1))
245 {
246 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pAttribute->wzValue, -1, L"true", -1))
247 {
248 pApupEntry->fUpgradeExclusive = TRUE;
249 }
250 }
251 }
252 }
253 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pElement->wzElement, -1, L"version", -1))
254 {
255 hr = VerParseVersion(pElement->wzValue, 0, FALSE, &pApupEntry->pVersion);
256 ApupExitOnFailure(hr, "Failed to parse version string '%ls' from ATOM entry.", pElement->wzValue);
257 }
258 }
259 }
260
261 // If there is no application identity or no version, skip the whole thing.
262 if ((!pApupEntry->wzApplicationId && !wzDefaultAppId) || !pApupEntry->pVersion)
263 {
264 ExitFunction1(hr = S_FALSE); // skip this update since it has no application id or version.
265 }
266
267 if (pApupEntry->pUpgradeVersion)
268 {
269 hr = VerCompareParsedVersions(pApupEntry->pUpgradeVersion, pApupEntry->pVersion, &nCompareResult);
270 ApupExitOnFailure(hr, "Failed to compare version to upgrade version.");
271
272 if (nCompareResult >= 0)
273 {
274 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
275 ApupExitOnRootFailure(hr, "Upgrade version is greater than or equal to application version.");
276 }
277 }
278
279 if (pAtomEntry->wzTitle)
280 {
281 hr = StrAllocString(&pApupEntry->wzTitle, pAtomEntry->wzTitle, 0);
282 ApupExitOnFailure(hr, "Failed to allocate application title.");
283 }
284
285 if (pAtomEntry->wzSummary)
286 {
287 hr = StrAllocString(&pApupEntry->wzSummary, pAtomEntry->wzSummary, 0);
288 ApupExitOnFailure(hr, "Failed to allocate application summary.");
289 }
290
291 if (pAtomEntry->pContent)
292 {
293 if (pAtomEntry->pContent->wzType)
294 {
295 hr = StrAllocString(&pApupEntry->wzContentType, pAtomEntry->pContent->wzType, 0);
296 ApupExitOnFailure(hr, "Failed to allocate content type.");
297 }
298
299 if (pAtomEntry->pContent->wzValue)
300 {
301 hr = StrAllocString(&pApupEntry->wzContent, pAtomEntry->pContent->wzValue, 0);
302 ApupExitOnFailure(hr, "Failed to allocate content.");
303 }
304 }
305 // Now process the enclosures. Assume every link in the ATOM entry is an enclosure.
306 pApupEntry->rgEnclosures = static_cast<APPLICATION_UPDATE_ENCLOSURE*>(MemAlloc(sizeof(APPLICATION_UPDATE_ENCLOSURE) * pAtomEntry->cLinks, TRUE));
307 ApupExitOnNull(pApupEntry->rgEnclosures, hr, E_OUTOFMEMORY, "Failed to allocate enclosures for application update entry.");
308
309 for (DWORD i = 0; i < pAtomEntry->cLinks; ++i)
310 {
311 ATOM_LINK* pLink = pAtomEntry->rgLinks + i;
312 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pLink->wzRel, -1, L"enclosure", -1))
313 {
314 hr = ParseEnclosure(pLink, pApupEntry->rgEnclosures + pApupEntry->cEnclosures);
315 ApupExitOnFailure(hr, "Failed to parse enclosure.");
316
317 pApupEntry->dw64TotalSize += pApupEntry->rgEnclosures[pApupEntry->cEnclosures].dw64Size; // total up the size of the enclosures
318
319 ++pApupEntry->cEnclosures;
320 }
321 }
322
323LExit:
324 if (S_OK != hr) // if anything went wrong, free the entry.
325 {
326 FreeEntry(pApupEntry);
327 memset(pApupEntry, 0, sizeof(APPLICATION_UPDATE_ENTRY));
328 }
329
330 return hr;
331}
332
333
334static HRESULT ParseEnclosure(
335 __in ATOM_LINK* pLink,
336 __in APPLICATION_UPDATE_ENCLOSURE* pEnclosure
337 )
338{
339 HRESULT hr = S_OK;
340 DWORD dwDigestLength = 0;
341 DWORD dwDigestStringLength = 0;
342 size_t cchDigestString = 0;
343
344 // First search the ATOM link's custom elements to try and find the application update enclosure information.
345 for (ATOM_UNKNOWN_ELEMENT* pElement = pLink->pUnknownElements; pElement; pElement = pElement->pNext)
346 {
347 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pElement->wzNamespace, -1, APPLICATION_SYNDICATION_NAMESPACE, -1))
348 {
349 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, L"digest", -1, pElement->wzElement, -1))
350 {
351 // Find the digest[@algorithm] which is required. Everything else is ignored.
352 for (ATOM_UNKNOWN_ATTRIBUTE* pAttribute = pElement->pAttributes; pAttribute; pAttribute = pAttribute->pNext)
353 {
354 dwDigestLength = 0;
355 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, L"algorithm", -1, pAttribute->wzAttribute, -1))
356 {
357 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, L"md5", -1, pAttribute->wzValue, -1))
358 {
359 pEnclosure->digestAlgorithm = APUP_HASH_ALGORITHM_MD5;
360 dwDigestLength = MD5_HASH_LEN;
361 }
362 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, L"sha1", -1, pAttribute->wzValue, -1))
363 {
364 pEnclosure->digestAlgorithm = APUP_HASH_ALGORITHM_SHA1;
365 dwDigestLength = SHA1_HASH_LEN;
366 }
367 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, L"sha256", -1, pAttribute->wzValue, -1))
368 {
369 pEnclosure->digestAlgorithm = APUP_HASH_ALGORITHM_SHA256;
370 dwDigestLength = SHA256_HASH_LEN;
371 }
372 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, L"sha512", -1, pAttribute->wzValue, -1))
373 {
374 pEnclosure->digestAlgorithm = APUP_HASH_ALGORITHM_SHA512;
375 dwDigestLength = SHA512_HASH_LEN;
376 }
377 break;
378 }
379 }
380
381 if (dwDigestLength)
382 {
383 dwDigestStringLength = 2 * dwDigestLength;
384
385 hr = ::StringCchLengthW(pElement->wzValue, STRSAFE_MAX_CCH, &cchDigestString);
386 ApupExitOnFailure(hr, "Failed to get string length of digest value.");
387
388 if (dwDigestStringLength != cchDigestString)
389 {
390 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
391 ApupExitOnRootFailure(hr, "Invalid digest length (%Iu) for digest algorithm (%u).", cchDigestString, dwDigestStringLength);
392 }
393
394 pEnclosure->cbDigest = sizeof(BYTE) * dwDigestLength;
395 pEnclosure->rgbDigest = static_cast<BYTE*>(MemAlloc(pEnclosure->cbDigest, TRUE));
396 ApupExitOnNull(pEnclosure->rgbDigest, hr, E_OUTOFMEMORY, "Failed to allocate memory for digest.");
397
398 hr = StrHexDecode(pElement->wzValue, pEnclosure->rgbDigest, pEnclosure->cbDigest);
399 ApupExitOnFailure(hr, "Failed to decode digest value.");
400 }
401 else
402 {
403 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
404 ApupExitOnRootFailure(hr, "Unknown algorithm type for digest.");
405 }
406
407 break;
408 }
409 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, L"name", -1, pElement->wzElement, -1))
410 {
411 hr = StrAllocString(&pEnclosure->wzLocalName, pElement->wzValue, 0);
412 ApupExitOnFailure(hr, "Failed to copy local name.");
413 }
414 }
415 }
416
417 pEnclosure->dw64Size = pLink->dw64Length;
418
419 hr = StrAllocString(&pEnclosure->wzUrl, pLink->wzUrl, 0);
420 ApupExitOnFailure(hr, "Failed to allocate enclosure URL.");
421
422 pEnclosure->fInstaller = FALSE;
423 pEnclosure->wzLocalName = NULL;
424
425LExit:
426 return hr;
427}
428
429
430static __callback int __cdecl CompareEntries(
431 void* /*pvContext*/,
432 const void* pvLeft,
433 const void* pvRight
434 )
435{
436 int ret = 0;
437 const APPLICATION_UPDATE_ENTRY* pEntryLeft = static_cast<const APPLICATION_UPDATE_ENTRY*>(pvLeft);
438 const APPLICATION_UPDATE_ENTRY* pEntryRight = static_cast<const APPLICATION_UPDATE_ENTRY*>(pvRight);
439
440 VerCompareParsedVersions(pEntryLeft->pVersion, pEntryRight->pVersion, &ret);
441 if (0 == ret)
442 {
443 VerCompareParsedVersions(pEntryLeft->pUpgradeVersion, pEntryRight->pUpgradeVersion, &ret);
444 if (0 == ret)
445 {
446 ret = (pEntryLeft->dw64TotalSize < pEntryRight->dw64TotalSize) ? -1 :
447 (pEntryLeft->dw64TotalSize > pEntryRight->dw64TotalSize) ? 1 : 0;
448 }
449 }
450
451 // Sort descending.
452 ret = -ret;
453
454 return ret;
455}
456
457
458static HRESULT FilterEntries(
459 __in APPLICATION_UPDATE_ENTRY* rgEntries,
460 __in DWORD cEntries,
461 __in VERUTIL_VERSION* pCurrentVersion,
462 __inout APPLICATION_UPDATE_ENTRY** prgFilteredEntries,
463 __inout DWORD* pcFilteredEntries
464 )
465{
466 HRESULT hr = S_OK;
467 int nCompareResult = 0;
468 size_t cbAllocSize = 0;
469 const APPLICATION_UPDATE_ENTRY* pRequired = NULL;;
470 LPVOID pv = NULL;
471
472 if (cEntries)
473 {
474 for (DWORD i = 0; i < cEntries; ++i)
475 {
476 const APPLICATION_UPDATE_ENTRY* pEntry = rgEntries + i;
477
478 hr = VerCompareParsedVersions(pCurrentVersion, pEntry->pVersion, &nCompareResult);
479 ApupExitOnFailure(hr, "Failed to compare versions.");
480
481 if (nCompareResult >= 0)
482 {
483 continue;
484 }
485
486 hr = VerCompareParsedVersions(pCurrentVersion, pEntry->pUpgradeVersion, &nCompareResult);
487 ApupExitOnFailure(hr, "Failed to compare upgrade versions.");
488
489 if (nCompareResult > 0 || (!pEntry->fUpgradeExclusive && nCompareResult == 0))
490 {
491 pRequired = pEntry;
492 break;
493 }
494 }
495
496 if (pRequired)
497 {
498 DWORD cNewFilteredEntries = *pcFilteredEntries + 1;
499
500 hr = ::SizeTMult(sizeof(APPLICATION_UPDATE_ENTRY), cNewFilteredEntries, &cbAllocSize);
501 ApupExitOnFailure(hr, "Overflow while calculating alloc size for more entries - number of entries: %u", cNewFilteredEntries);
502
503 if (*prgFilteredEntries)
504 {
505 pv = MemReAlloc(*prgFilteredEntries, cbAllocSize, FALSE);
506 ApupExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to reallocate memory for more entries.");
507 }
508 else
509 {
510 pv = MemAlloc(cbAllocSize, TRUE);
511 ApupExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to allocate memory for entries.");
512 }
513
514 *pcFilteredEntries = cNewFilteredEntries;
515 *prgFilteredEntries = static_cast<APPLICATION_UPDATE_ENTRY*>(pv);
516 pv = NULL;
517
518 hr = CopyEntry(pRequired, *prgFilteredEntries + *pcFilteredEntries - 1);
519 ApupExitOnFailure(hr, "Failed to deep copy entry.");
520
521 hr = VerCompareParsedVersions(pRequired->pVersion, rgEntries[0].pVersion, &nCompareResult);
522 ApupExitOnFailure(hr, "Failed to compare required version.");
523
524 if (nCompareResult < 0)
525 {
526 FilterEntries(rgEntries, cEntries, pRequired->pVersion, prgFilteredEntries, pcFilteredEntries);
527 }
528 }
529 }
530
531LExit:
532 ReleaseMem(pv);
533 return hr;
534}
535
536
537static HRESULT CopyEntry(
538 __in const APPLICATION_UPDATE_ENTRY* pSrc,
539 __in APPLICATION_UPDATE_ENTRY* pDest
540 )
541{
542 HRESULT hr = S_OK;
543 size_t cbAllocSize = 0;
544
545 memset(pDest, 0, sizeof(APPLICATION_UPDATE_ENTRY));
546
547 if (pSrc->wzApplicationId)
548 {
549 hr = StrAllocString(&pDest->wzApplicationId, pSrc->wzApplicationId, 0);
550 ApupExitOnFailure(hr, "Failed to copy application id.");
551 }
552
553 if (pSrc->wzApplicationType)
554 {
555 hr = StrAllocString(&pDest->wzApplicationType, pSrc->wzApplicationType, 0);
556 ApupExitOnFailure(hr, "Failed to copy application type.");
557 }
558
559 if (pSrc->wzUpgradeId)
560 {
561 hr = StrAllocString(&pDest->wzUpgradeId, pSrc->wzUpgradeId, 0);
562 ApupExitOnFailure(hr, "Failed to copy upgrade id.");
563 }
564
565 if (pSrc->wzTitle)
566 {
567 hr = StrAllocString(&pDest->wzTitle, pSrc->wzTitle, 0);
568 ApupExitOnFailure(hr, "Failed to copy title.");
569 }
570
571 if (pSrc->wzSummary)
572 {
573 hr = StrAllocString(&pDest->wzSummary, pSrc->wzSummary, 0);
574 ApupExitOnFailure(hr, "Failed to copy summary.");
575 }
576
577 if (pSrc->wzContentType)
578 {
579 hr = StrAllocString(&pDest->wzContentType, pSrc->wzContentType, 0);
580 ApupExitOnFailure(hr, "Failed to copy content type.");
581 }
582
583 if (pSrc->wzContent)
584 {
585 hr = StrAllocString(&pDest->wzContent, pSrc->wzContent, 0);
586 ApupExitOnFailure(hr, "Failed to copy content.");
587 }
588
589 pDest->dw64TotalSize = pSrc->dw64TotalSize;
590
591 hr = VerCopyVersion(pSrc->pUpgradeVersion, &pDest->pUpgradeVersion);
592 ApupExitOnFailure(hr, "Failed to copy upgrade version.");
593
594 hr = VerCopyVersion(pSrc->pVersion, &pDest->pVersion);
595 ApupExitOnFailure(hr, "Failed to copy version.");
596
597 pDest->fUpgradeExclusive = pSrc->fUpgradeExclusive;
598
599 hr = ::SizeTMult(sizeof(APPLICATION_UPDATE_ENCLOSURE), pSrc->cEnclosures, &cbAllocSize);
600 ApupExitOnRootFailure(hr, "Overflow while calculating memory allocation size");
601
602 pDest->rgEnclosures = static_cast<APPLICATION_UPDATE_ENCLOSURE*>(MemAlloc(cbAllocSize, TRUE));
603 ApupExitOnNull(pDest->rgEnclosures, hr, E_OUTOFMEMORY, "Failed to allocate copy of enclosures.");
604
605 pDest->cEnclosures = pSrc->cEnclosures;
606
607 for (DWORD i = 0; i < pDest->cEnclosures; ++i)
608 {
609 hr = CopyEnclosure(pSrc->rgEnclosures + i, pDest->rgEnclosures + i);
610 ApupExitOnFailure(hr, "Failed to copy enclosure.");
611 }
612
613LExit:
614 if (FAILED(hr))
615 {
616 FreeEntry(pDest);
617 }
618
619 return hr;
620}
621
622
623static HRESULT CopyEnclosure(
624 __in const APPLICATION_UPDATE_ENCLOSURE* pSrc,
625 __in APPLICATION_UPDATE_ENCLOSURE* pDest
626 )
627{
628 HRESULT hr = S_OK;
629
630 memset(pDest, 0, sizeof(APPLICATION_UPDATE_ENCLOSURE));
631
632 if (pSrc->wzUrl)
633 {
634 hr = StrAllocString(&pDest->wzUrl, pSrc->wzUrl, 0);
635 ApupExitOnFailure(hr, "Failed copy url.");
636 }
637
638 if (pSrc->wzLocalName)
639 {
640 hr = StrAllocString(&pDest->wzLocalName, pSrc->wzLocalName, 0);
641 ApupExitOnFailure(hr, "Failed copy url.");
642 }
643
644 pDest->rgbDigest = static_cast<BYTE*>(MemAlloc(sizeof(BYTE) * pSrc->cbDigest, FALSE));
645 ApupExitOnNull(pDest->rgbDigest, hr, E_OUTOFMEMORY, "Failed to allocate memory for copy of digest.");
646
647 pDest->cbDigest = pSrc->cbDigest;
648
649 memcpy_s(pDest->rgbDigest, sizeof(BYTE) * pDest->cbDigest, pSrc->rgbDigest, sizeof(BYTE) * pSrc->cbDigest);
650
651 pDest->digestAlgorithm = pSrc->digestAlgorithm;
652
653 pDest->dw64Size = pSrc->dw64Size;
654 pDest->fInstaller = pSrc->fInstaller;
655
656LExit:
657 if (FAILED(hr))
658 {
659 FreeEnclosure(pDest);
660 }
661
662 return hr;
663}
664
665
666static void FreeEntry(
667 __in APPLICATION_UPDATE_ENTRY* pEntry
668 )
669{
670 if (pEntry)
671 {
672 for (DWORD i = 0; i < pEntry->cEnclosures; ++i)
673 {
674 FreeEnclosure(pEntry->rgEnclosures + i);
675 }
676
677 ReleaseStr(pEntry->wzUpgradeId);
678 ReleaseStr(pEntry->wzApplicationType);
679 ReleaseStr(pEntry->wzApplicationId);
680 ReleaseStr(pEntry->wzTitle);
681 ReleaseStr(pEntry->wzSummary);
682 ReleaseStr(pEntry->wzContentType);
683 ReleaseStr(pEntry->wzContent);
684 ReleaseVerutilVersion(pEntry->pVersion);
685 ReleaseVerutilVersion(pEntry->pUpgradeVersion);
686 }
687}
688
689
690static void FreeEnclosure(
691 __in APPLICATION_UPDATE_ENCLOSURE* pEnclosure
692 )
693{
694 if (pEnclosure)
695 {
696 ReleaseMem(pEnclosure->rgbDigest);
697 ReleaseStr(pEnclosure->wzLocalName);
698 ReleaseStr(pEnclosure->wzUrl);
699 }
700}
diff --git a/src/libs/dutil/WixToolset.DUtil/atomutil.cpp b/src/libs/dutil/WixToolset.DUtil/atomutil.cpp
new file mode 100644
index 00000000..c7c7975a
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/atomutil.cpp
@@ -0,0 +1,1297 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5// Exit macros
6#define AtomExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_ATOMUTIL, x, s, __VA_ARGS__)
7#define AtomExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_ATOMUTIL, x, s, __VA_ARGS__)
8#define AtomExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_ATOMUTIL, x, s, __VA_ARGS__)
9#define AtomExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_ATOMUTIL, x, s, __VA_ARGS__)
10#define AtomExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_ATOMUTIL, x, s, __VA_ARGS__)
11#define AtomExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_ATOMUTIL, x, s, __VA_ARGS__)
12#define AtomExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_ATOMUTIL, p, x, e, s, __VA_ARGS__)
13#define AtomExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_ATOMUTIL, p, x, s, __VA_ARGS__)
14#define AtomExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_ATOMUTIL, p, x, e, s, __VA_ARGS__)
15#define AtomExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_ATOMUTIL, p, x, s, __VA_ARGS__)
16#define AtomExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_ATOMUTIL, e, x, s, __VA_ARGS__)
17#define AtomExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_ATOMUTIL, g, x, s, __VA_ARGS__)
18
19static HRESULT ParseAtomDocument(
20 __in IXMLDOMDocument *pixd,
21 __out ATOM_FEED **ppFeed
22 );
23static HRESULT ParseAtomFeed(
24 __in IXMLDOMNode *pixnFeed,
25 __out ATOM_FEED **ppFeed
26 );
27static HRESULT ParseAtomAuthor(
28 __in IXMLDOMNode* pixnAuthor,
29 __in ATOM_AUTHOR* pAuthor
30 );
31static HRESULT ParseAtomCategory(
32 __in IXMLDOMNode* pixnCategory,
33 __in ATOM_CATEGORY* pCategory
34 );
35static HRESULT ParseAtomEntry(
36 __in IXMLDOMNode* pixnEntry,
37 __in ATOM_ENTRY* pEntry
38 );
39static HRESULT ParseAtomLink(
40 __in IXMLDOMNode* pixnLink,
41 __in ATOM_LINK* pLink
42 );
43static HRESULT ParseAtomUnknownElement(
44 __in IXMLDOMNode *pNode,
45 __inout ATOM_UNKNOWN_ELEMENT** ppUnknownElement
46 );
47static HRESULT ParseAtomUnknownAttribute(
48 __in IXMLDOMNode *pNode,
49 __inout ATOM_UNKNOWN_ATTRIBUTE** ppUnknownAttribute
50 );
51static HRESULT AssignDateTime(
52 __in FILETIME* pft,
53 __in IXMLDOMNode* pNode
54 );
55static HRESULT AssignString(
56 __out_z LPWSTR* pwzValue,
57 __in IXMLDOMNode* pNode
58 );
59static void FreeAtomAuthor(
60 __in_opt ATOM_AUTHOR* pAuthor
61 );
62static void FreeAtomContent(
63 __in_opt ATOM_CONTENT* pContent
64 );
65static void FreeAtomCategory(
66 __in_opt ATOM_CATEGORY* pCategory
67 );
68static void FreeAtomEntry(
69 __in_opt ATOM_ENTRY* pEntry
70 );
71static void FreeAtomLink(
72 __in_opt ATOM_LINK* pLink
73 );
74static void FreeAtomUnknownElementList(
75 __in_opt ATOM_UNKNOWN_ELEMENT* pUnknownElement
76 );
77static void FreeAtomUnknownAttributeList(
78 __in_opt ATOM_UNKNOWN_ATTRIBUTE* pUnknownAttribute
79 );
80
81template<class T> static HRESULT AllocateAtomType(
82 __in IXMLDOMNode* pixnParent,
83 __in LPCWSTR wzT,
84 __out T** pprgT,
85 __out DWORD* pcT
86 );
87
88
89/********************************************************************
90 AtomInitialize - Initialize ATOM utilities.
91
92*********************************************************************/
93extern "C" HRESULT DAPI AtomInitialize()
94{
95 return XmlInitialize();
96}
97
98
99/********************************************************************
100 AtomUninitialize - Uninitialize ATOM utilities.
101
102*********************************************************************/
103extern "C" void DAPI AtomUninitialize()
104{
105 XmlUninitialize();
106}
107
108
109/********************************************************************
110 AtomParseFromString - parses out an ATOM feed from a string.
111
112*********************************************************************/
113extern "C" HRESULT DAPI AtomParseFromString(
114 __in_z LPCWSTR wzAtomString,
115 __out ATOM_FEED **ppFeed
116 )
117{
118 Assert(wzAtomString);
119 Assert(ppFeed);
120
121 HRESULT hr = S_OK;
122 ATOM_FEED *pNewFeed = NULL;
123 IXMLDOMDocument *pixdAtom = NULL;
124
125 hr = XmlLoadDocument(wzAtomString, &pixdAtom);
126 AtomExitOnFailure(hr, "Failed to load ATOM string as XML document.");
127
128 hr = ParseAtomDocument(pixdAtom, &pNewFeed);
129 AtomExitOnFailure(hr, "Failed to parse ATOM document.");
130
131 *ppFeed = pNewFeed;
132 pNewFeed = NULL;
133
134LExit:
135 ReleaseAtomFeed(pNewFeed);
136 ReleaseObject(pixdAtom);
137
138 return hr;
139}
140
141
142/********************************************************************
143 AtomParseFromFile - parses out an ATOM feed from a file path.
144
145*********************************************************************/
146extern "C" HRESULT DAPI AtomParseFromFile(
147 __in_z LPCWSTR wzAtomFile,
148 __out ATOM_FEED **ppFeed
149 )
150{
151 Assert(wzAtomFile);
152 Assert(ppFeed);
153
154 HRESULT hr = S_OK;
155 ATOM_FEED *pNewFeed = NULL;
156 IXMLDOMDocument *pixdAtom = NULL;
157
158 hr = XmlLoadDocumentFromFile(wzAtomFile, &pixdAtom);
159 AtomExitOnFailure(hr, "Failed to load ATOM string as XML document.");
160
161 hr = ParseAtomDocument(pixdAtom, &pNewFeed);
162 AtomExitOnFailure(hr, "Failed to parse ATOM document.");
163
164 *ppFeed = pNewFeed;
165 pNewFeed = NULL;
166
167LExit:
168 ReleaseAtomFeed(pNewFeed);
169 ReleaseObject(pixdAtom);
170
171 return hr;
172}
173
174
175/********************************************************************
176 AtomParseFromDocument - parses out an ATOM feed from an XML document.
177
178*********************************************************************/
179extern "C" HRESULT DAPI AtomParseFromDocument(
180 __in IXMLDOMDocument* pixdDocument,
181 __out ATOM_FEED **ppFeed
182 )
183{
184 Assert(pixdDocument);
185 Assert(ppFeed);
186
187 HRESULT hr = S_OK;
188 ATOM_FEED *pNewFeed = NULL;
189
190 hr = ParseAtomDocument(pixdDocument, &pNewFeed);
191 AtomExitOnFailure(hr, "Failed to parse ATOM document.");
192
193 *ppFeed = pNewFeed;
194 pNewFeed = NULL;
195
196LExit:
197 ReleaseAtomFeed(pNewFeed);
198
199 return hr;
200}
201
202
203/********************************************************************
204 AtomFreeFeed - parses out an ATOM feed from a string.
205
206*********************************************************************/
207extern "C" void DAPI AtomFreeFeed(
208 __in_xcount(pFeed->cItems) ATOM_FEED* pFeed
209 )
210{
211 if (pFeed)
212 {
213 FreeAtomUnknownElementList(pFeed->pUnknownElements);
214 ReleaseObject(pFeed->pixn);
215
216 for (DWORD i = 0; i < pFeed->cLinks; ++i)
217 {
218 FreeAtomLink(pFeed->rgLinks + i);
219 }
220 ReleaseMem(pFeed->rgLinks);
221
222 for (DWORD i = 0; i < pFeed->cEntries; ++i)
223 {
224 FreeAtomEntry(pFeed->rgEntries + i);
225 }
226 ReleaseMem(pFeed->rgEntries);
227
228 for (DWORD i = 0; i < pFeed->cCategories; ++i)
229 {
230 FreeAtomCategory(pFeed->rgCategories + i);
231 }
232 ReleaseMem(pFeed->rgCategories);
233
234 for (DWORD i = 0; i < pFeed->cAuthors; ++i)
235 {
236 FreeAtomAuthor(pFeed->rgAuthors + i);
237 }
238 ReleaseMem(pFeed->rgAuthors);
239
240 ReleaseStr(pFeed->wzGenerator);
241 ReleaseStr(pFeed->wzIcon);
242 ReleaseStr(pFeed->wzId);
243 ReleaseStr(pFeed->wzLogo);
244 ReleaseStr(pFeed->wzSubtitle);
245 ReleaseStr(pFeed->wzTitle);
246
247 MemFree(pFeed);
248 }
249}
250
251
252/********************************************************************
253 ParseAtomDocument - parses out an ATOM feed from a loaded XML DOM document.
254
255*********************************************************************/
256static HRESULT ParseAtomDocument(
257 __in IXMLDOMDocument *pixd,
258 __out ATOM_FEED **ppFeed
259 )
260{
261 Assert(pixd);
262 Assert(ppFeed);
263
264 HRESULT hr = S_OK;
265 IXMLDOMElement *pFeedElement = NULL;
266
267 ATOM_FEED *pNewFeed = NULL;
268
269 //
270 // Get the document element and start processing feeds.
271 //
272 hr = pixd->get_documentElement(&pFeedElement);
273 AtomExitOnFailure(hr, "failed get_documentElement in ParseAtomDocument");
274
275 hr = ParseAtomFeed(pFeedElement, &pNewFeed);
276 AtomExitOnFailure(hr, "Failed to parse ATOM feed.");
277
278 if (S_FALSE == hr)
279 {
280 hr = S_OK;
281 }
282
283 *ppFeed = pNewFeed;
284 pNewFeed = NULL;
285
286LExit:
287 ReleaseObject(pFeedElement);
288
289 ReleaseAtomFeed(pNewFeed);
290
291 return hr;
292}
293
294
295/********************************************************************
296 ParseAtomFeed - parses out an ATOM feed from a loaded XML DOM element.
297
298*********************************************************************/
299static HRESULT ParseAtomFeed(
300 __in IXMLDOMNode *pixnFeed,
301 __out ATOM_FEED **ppFeed
302 )
303{
304 Assert(pixnFeed);
305 Assert(ppFeed);
306
307 HRESULT hr = S_OK;
308 IXMLDOMNodeList *pNodeList = NULL;
309
310 ATOM_FEED *pNewFeed = NULL;
311 DWORD cAuthors = 0;
312 DWORD cCategories = 0;
313 DWORD cEntries = 0;
314 DWORD cLinks = 0;
315
316 IXMLDOMNode *pNode = NULL;
317 BSTR bstrNodeName = NULL;
318
319 // First, allocate the new feed and all the possible sub elements.
320 pNewFeed = (ATOM_FEED*)MemAlloc(sizeof(ATOM_FEED), TRUE);
321 AtomExitOnNull(pNewFeed, hr, E_OUTOFMEMORY, "Failed to allocate ATOM feed structure.");
322
323 pNewFeed->pixn = pixnFeed;
324 pNewFeed->pixn->AddRef();
325
326 hr = AllocateAtomType<ATOM_AUTHOR>(pixnFeed, L"author", &pNewFeed->rgAuthors, &pNewFeed->cAuthors);
327 AtomExitOnFailure(hr, "Failed to allocate ATOM feed authors.");
328
329 hr = AllocateAtomType<ATOM_CATEGORY>(pixnFeed, L"category", &pNewFeed->rgCategories, &pNewFeed->cCategories);
330 AtomExitOnFailure(hr, "Failed to allocate ATOM feed categories.");
331
332 hr = AllocateAtomType<ATOM_ENTRY>(pixnFeed, L"entry", &pNewFeed->rgEntries, &pNewFeed->cEntries);
333 AtomExitOnFailure(hr, "Failed to allocate ATOM feed entries.");
334
335 hr = AllocateAtomType<ATOM_LINK>(pixnFeed, L"link", &pNewFeed->rgLinks, &pNewFeed->cLinks);
336 AtomExitOnFailure(hr, "Failed to allocate ATOM feed links.");
337
338 // Second, process the elements under a feed.
339 hr = pixnFeed->get_childNodes(&pNodeList);
340 AtomExitOnFailure(hr, "Failed to get child nodes of ATOM feed element.");
341
342 while (S_OK == (hr = XmlNextElement(pNodeList, &pNode, &bstrNodeName)))
343 {
344 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"generator", -1))
345 {
346 hr = AssignString(&pNewFeed->wzGenerator, pNode);
347 AtomExitOnFailure(hr, "Failed to allocate ATOM feed generator.");
348 }
349 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"icon", -1))
350 {
351 hr = AssignString(&pNewFeed->wzIcon, pNode);
352 AtomExitOnFailure(hr, "Failed to allocate ATOM feed icon.");
353 }
354 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"id", -1))
355 {
356 hr = AssignString(&pNewFeed->wzId, pNode);
357 AtomExitOnFailure(hr, "Failed to allocate ATOM feed id.");
358 }
359 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"logo", -1))
360 {
361 hr = AssignString(&pNewFeed->wzLogo, pNode);
362 AtomExitOnFailure(hr, "Failed to allocate ATOM feed logo.");
363 }
364 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"subtitle", -1))
365 {
366 hr = AssignString(&pNewFeed->wzSubtitle, pNode);
367 AtomExitOnFailure(hr, "Failed to allocate ATOM feed subtitle.");
368 }
369 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"title", -1))
370 {
371 hr = AssignString(&pNewFeed->wzTitle, pNode);
372 AtomExitOnFailure(hr, "Failed to allocate ATOM feed title.");
373 }
374 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"updated", -1))
375 {
376 hr = AssignDateTime(&pNewFeed->ftUpdated, pNode);
377 AtomExitOnFailure(hr, "Failed to allocate ATOM feed updated.");
378 }
379 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"author", -1))
380 {
381 hr = ParseAtomAuthor(pNode, &pNewFeed->rgAuthors[cAuthors]);
382 AtomExitOnFailure(hr, "Failed to parse ATOM author.");
383
384 ++cAuthors;
385 }
386 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"category", -1))
387 {
388 hr = ParseAtomCategory(pNode, &pNewFeed->rgCategories[cCategories]);
389 AtomExitOnFailure(hr, "Failed to parse ATOM category.");
390
391 ++cCategories;
392 }
393 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"entry", -1))
394 {
395 hr = ParseAtomEntry(pNode, &pNewFeed->rgEntries[cEntries]);
396 AtomExitOnFailure(hr, "Failed to parse ATOM entry.");
397
398 ++cEntries;
399 }
400 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"link", -1))
401 {
402 hr = ParseAtomLink(pNode, &pNewFeed->rgLinks[cLinks]);
403 AtomExitOnFailure(hr, "Failed to parse ATOM link.");
404
405 ++cLinks;
406 }
407 else
408 {
409 hr = ParseAtomUnknownElement(pNode, &pNewFeed->pUnknownElements);
410 AtomExitOnFailure(hr, "Failed to parse unknown ATOM feed element: %ls", bstrNodeName);
411 }
412
413 ReleaseNullBSTR(bstrNodeName);
414 ReleaseNullObject(pNode);
415 }
416
417 if (!pNewFeed->wzId || !*pNewFeed->wzId)
418 {
419 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
420 AtomExitOnRootFailure(hr, "Failed to find required feed/id element.");
421 }
422 else if (!pNewFeed->wzTitle || !*pNewFeed->wzTitle)
423 {
424 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
425 AtomExitOnRootFailure(hr, "Failed to find required feed/title element.");
426 }
427 else if (0 == pNewFeed->ftUpdated.dwHighDateTime && 0 == pNewFeed->ftUpdated.dwLowDateTime)
428 {
429 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
430 AtomExitOnRootFailure(hr, "Failed to find required feed/updated element.");
431 }
432
433 *ppFeed = pNewFeed;
434 pNewFeed = NULL;
435
436LExit:
437 ReleaseBSTR(bstrNodeName);
438 ReleaseObject(pNode);
439 ReleaseObject(pNodeList);
440
441 ReleaseAtomFeed(pNewFeed);
442
443 return hr;
444}
445
446
447/********************************************************************
448 AllocateAtomType - allocates enough space for all of the ATOM elements
449 of a particular type under a particular node.
450
451*********************************************************************/
452template<class T> static HRESULT AllocateAtomType(
453 __in IXMLDOMNode* pixnParent,
454 __in LPCWSTR wzT,
455 __out T** pprgT,
456 __out DWORD* pcT
457 )
458{
459 HRESULT hr = S_OK;
460 IXMLDOMNodeList *pNodeList = NULL;
461
462 long cT = 0;
463 T* prgT = NULL;
464
465 hr = XmlSelectNodes(pixnParent, wzT, &pNodeList);
466 AtomExitOnFailure(hr, "Failed to select all ATOM %ls.", wzT);
467
468 if (S_OK == hr)
469 {
470 hr = pNodeList->get_length(&cT);
471 AtomExitOnFailure(hr, "Failed to count the number of ATOM %ls.", wzT);
472
473 if (cT == 0)
474 {
475 ExitFunction();
476 }
477
478 prgT = static_cast<T*>(MemAlloc(sizeof(T) * cT, TRUE));
479 AtomExitOnNull(prgT, hr, E_OUTOFMEMORY, "Failed to allocate ATOM.");
480
481 *pcT = cT;
482 *pprgT = prgT;
483 prgT = NULL;
484 }
485 else
486 {
487 *pprgT = NULL;
488 *pcT = 0;
489 }
490
491LExit:
492 ReleaseMem(prgT);
493 ReleaseObject(pNodeList);
494
495 return hr;
496}
497
498
499/********************************************************************
500 ParseAtomAuthor - parses out an ATOM author from a loaded XML DOM node.
501
502*********************************************************************/
503static HRESULT ParseAtomAuthor(
504 __in IXMLDOMNode* pixnAuthor,
505 __in ATOM_AUTHOR* pAuthor
506 )
507{
508 HRESULT hr = S_OK;
509
510 IXMLDOMNodeList *pNodeList = NULL;
511 IXMLDOMNode *pNode = NULL;
512 BSTR bstrNodeName = NULL;
513
514 hr = pixnAuthor->get_childNodes(&pNodeList);
515 AtomExitOnFailure(hr, "Failed to get child nodes of ATOM author element.");
516
517 while (S_OK == (hr = XmlNextElement(pNodeList, &pNode, &bstrNodeName)))
518 {
519 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"name", -1))
520 {
521 hr = AssignString(&pAuthor->wzName, pNode);
522 AtomExitOnFailure(hr, "Failed to allocate ATOM author name.");
523 }
524 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"email", -1))
525 {
526 hr = AssignString(&pAuthor->wzEmail, pNode);
527 AtomExitOnFailure(hr, "Failed to allocate ATOM author email.");
528 }
529 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"uri", -1))
530 {
531 hr = AssignString(&pAuthor->wzUrl, pNode);
532 AtomExitOnFailure(hr, "Failed to allocate ATOM author uri.");
533 }
534
535 ReleaseNullBSTR(bstrNodeName);
536 ReleaseNullObject(pNode);
537 }
538 AtomExitOnFailure(hr, "Failed to process all ATOM author elements.");
539
540 hr = S_OK;
541
542LExit:
543 ReleaseBSTR(bstrNodeName);
544 ReleaseObject(pNode);
545 ReleaseObject(pNodeList);
546
547 return hr;
548}
549
550
551/********************************************************************
552 ParseAtomCategory - parses out an ATOM category from a loaded XML DOM node.
553
554*********************************************************************/
555static HRESULT ParseAtomCategory(
556 __in IXMLDOMNode* pixnCategory,
557 __in ATOM_CATEGORY* pCategory
558 )
559{
560 HRESULT hr = S_OK;
561
562 IXMLDOMNamedNodeMap* pixnnmAttributes = NULL;
563 IXMLDOMNodeList *pNodeList = NULL;
564 IXMLDOMNode *pNode = NULL;
565 BSTR bstrNodeName = NULL;
566
567 // Process attributes first.
568 hr = pixnCategory->get_attributes(&pixnnmAttributes);
569 AtomExitOnFailure(hr, "Failed get attributes on ATOM unknown element.");
570
571 while (S_OK == (hr = XmlNextAttribute(pixnnmAttributes, &pNode, &bstrNodeName)))
572 {
573 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"label", -1))
574 {
575 hr = AssignString(&pCategory->wzLabel, pNode);
576 AtomExitOnFailure(hr, "Failed to allocate ATOM category label.");
577 }
578 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"scheme", -1))
579 {
580 hr = AssignString(&pCategory->wzScheme, pNode);
581 AtomExitOnFailure(hr, "Failed to allocate ATOM category scheme.");
582 }
583 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"term", -1))
584 {
585 hr = AssignString(&pCategory->wzTerm, pNode);
586 AtomExitOnFailure(hr, "Failed to allocate ATOM category term.");
587 }
588
589 ReleaseNullBSTR(bstrNodeName);
590 ReleaseNullObject(pNode);
591 }
592 AtomExitOnFailure(hr, "Failed to process all ATOM category attributes.");
593
594 // Process elements second.
595 hr = pixnCategory->get_childNodes(&pNodeList);
596 AtomExitOnFailure(hr, "Failed to get child nodes of ATOM category element.");
597
598 while (S_OK == (hr = XmlNextElement(pNodeList, &pNode, &bstrNodeName)))
599 {
600 hr = ParseAtomUnknownElement(pNode, &pCategory->pUnknownElements);
601 AtomExitOnFailure(hr, "Failed to parse unknown ATOM category element: %ls", bstrNodeName);
602
603 ReleaseNullBSTR(bstrNodeName);
604 ReleaseNullObject(pNode);
605 }
606 AtomExitOnFailure(hr, "Failed to process all ATOM category elements.");
607
608 hr = S_OK;
609
610LExit:
611 ReleaseBSTR(bstrNodeName);
612 ReleaseObject(pNode);
613 ReleaseObject(pNodeList);
614 ReleaseObject(pixnnmAttributes);
615
616 return hr;
617}
618
619
620/********************************************************************
621 ParseAtomContent - parses out an ATOM content from a loaded XML DOM node.
622
623*********************************************************************/
624static HRESULT ParseAtomContent(
625 __in IXMLDOMNode* pixnContent,
626 __in ATOM_CONTENT* pContent
627 )
628{
629 HRESULT hr = S_OK;
630
631 IXMLDOMNamedNodeMap* pixnnmAttributes = NULL;
632 IXMLDOMNodeList *pNodeList = NULL;
633 IXMLDOMNode *pNode = NULL;
634 BSTR bstrNodeName = NULL;
635
636 // Process attributes first.
637 hr = pixnContent->get_attributes(&pixnnmAttributes);
638 AtomExitOnFailure(hr, "Failed get attributes on ATOM unknown element.");
639
640 while (S_OK == (hr = XmlNextAttribute(pixnnmAttributes, &pNode, &bstrNodeName)))
641 {
642 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"type", -1))
643 {
644 hr = AssignString(&pContent->wzType, pNode);
645 AtomExitOnFailure(hr, "Failed to allocate ATOM content type.");
646 }
647 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"url", -1))
648 {
649 hr = AssignString(&pContent->wzUrl, pNode);
650 AtomExitOnFailure(hr, "Failed to allocate ATOM content scheme.");
651 }
652
653 ReleaseNullBSTR(bstrNodeName);
654 ReleaseNullObject(pNode);
655 }
656 AtomExitOnFailure(hr, "Failed to process all ATOM content attributes.");
657
658 // Process elements second.
659 hr = pixnContent->get_childNodes(&pNodeList);
660 AtomExitOnFailure(hr, "Failed to get child nodes of ATOM content element.");
661
662 while (S_OK == (hr = XmlNextElement(pNodeList, &pNode, &bstrNodeName)))
663 {
664 hr = ParseAtomUnknownElement(pNode, &pContent->pUnknownElements);
665 AtomExitOnFailure(hr, "Failed to parse unknown ATOM content element: %ls", bstrNodeName);
666
667 ReleaseNullBSTR(bstrNodeName);
668 ReleaseNullObject(pNode);
669 }
670 AtomExitOnFailure(hr, "Failed to process all ATOM content elements.");
671
672 hr = AssignString(&pContent->wzValue, pixnContent);
673 AtomExitOnFailure(hr, "Failed to allocate ATOM content value.");
674
675LExit:
676 ReleaseBSTR(bstrNodeName);
677 ReleaseObject(pNode);
678 ReleaseObject(pNodeList);
679 ReleaseObject(pixnnmAttributes);
680
681 return hr;
682}
683
684
685/********************************************************************
686 ParseAtomEntry - parses out an ATOM entry from a loaded XML DOM node.
687
688*********************************************************************/
689static HRESULT ParseAtomEntry(
690 __in IXMLDOMNode* pixnEntry,
691 __in ATOM_ENTRY* pEntry
692 )
693{
694 HRESULT hr = S_OK;
695
696 IXMLDOMNamedNodeMap* pixnnmAttributes = NULL;
697 IXMLDOMNodeList *pNodeList = NULL;
698 IXMLDOMNode *pNode = NULL;
699 BSTR bstrNodeName = NULL;
700
701 DWORD cAuthors = 0;
702 DWORD cCategories = 0;
703 DWORD cLinks = 0;
704
705 pEntry->pixn = pixnEntry;
706 pEntry->pixn->AddRef();
707
708 // First, allocate all the possible sub elements.
709 hr = AllocateAtomType<ATOM_AUTHOR>(pixnEntry, L"author", &pEntry->rgAuthors, &pEntry->cAuthors);
710 AtomExitOnFailure(hr, "Failed to allocate ATOM entry authors.");
711
712 hr = AllocateAtomType<ATOM_CATEGORY>(pixnEntry, L"category", &pEntry->rgCategories, &pEntry->cCategories);
713 AtomExitOnFailure(hr, "Failed to allocate ATOM entry categories.");
714
715 hr = AllocateAtomType<ATOM_LINK>(pixnEntry, L"link", &pEntry->rgLinks, &pEntry->cLinks);
716 AtomExitOnFailure(hr, "Failed to allocate ATOM entry links.");
717
718 // Second, process elements.
719 hr = pixnEntry->get_childNodes(&pNodeList);
720 AtomExitOnFailure(hr, "Failed to get child nodes of ATOM entry element.");
721
722 while (S_OK == (hr = XmlNextElement(pNodeList, &pNode, &bstrNodeName)))
723 {
724 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"id", -1))
725 {
726 hr = AssignString(&pEntry->wzId, pNode);
727 AtomExitOnFailure(hr, "Failed to allocate ATOM entry id.");
728 }
729 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"summary", -1))
730 {
731 hr = AssignString(&pEntry->wzSummary, pNode);
732 AtomExitOnFailure(hr, "Failed to allocate ATOM entry summary.");
733 }
734 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"title", -1))
735 {
736 hr = AssignString(&pEntry->wzTitle, pNode);
737 AtomExitOnFailure(hr, "Failed to allocate ATOM entry title.");
738 }
739 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"published", -1))
740 {
741 hr = AssignDateTime(&pEntry->ftPublished, pNode);
742 AtomExitOnFailure(hr, "Failed to allocate ATOM entry published.");
743 }
744 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"updated", -1))
745 {
746 hr = AssignDateTime(&pEntry->ftUpdated, pNode);
747 AtomExitOnFailure(hr, "Failed to allocate ATOM entry updated.");
748 }
749 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"author", -1))
750 {
751 hr = ParseAtomAuthor(pNode, &pEntry->rgAuthors[cAuthors]);
752 AtomExitOnFailure(hr, "Failed to parse ATOM entry author.");
753
754 ++cAuthors;
755 }
756 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"category", -1))
757 {
758 hr = ParseAtomCategory(pNode, &pEntry->rgCategories[cCategories]);
759 AtomExitOnFailure(hr, "Failed to parse ATOM entry category.");
760
761 ++cCategories;
762 }
763 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"content", -1))
764 {
765 if (NULL != pEntry->pContent)
766 {
767 hr = E_UNEXPECTED;
768 AtomExitOnFailure(hr, "Cannot have two content elements in ATOM entry.");
769 }
770
771 pEntry->pContent = static_cast<ATOM_CONTENT*>(MemAlloc(sizeof(ATOM_CONTENT), TRUE));
772 AtomExitOnNull(pEntry->pContent, hr, E_OUTOFMEMORY, "Failed to allocate ATOM entry content.");
773
774 hr = ParseAtomContent(pNode, pEntry->pContent);
775 AtomExitOnFailure(hr, "Failed to parse ATOM entry content.");
776 }
777 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"link", -1))
778 {
779 hr = ParseAtomLink(pNode, &pEntry->rgLinks[cLinks]);
780 AtomExitOnFailure(hr, "Failed to parse ATOM entry link.");
781
782 ++cLinks;
783 }
784 else
785 {
786 hr = ParseAtomUnknownElement(pNode, &pEntry->pUnknownElements);
787 AtomExitOnFailure(hr, "Failed to parse unknown ATOM entry element: %ls", bstrNodeName);
788 }
789
790 ReleaseNullBSTR(bstrNodeName);
791 ReleaseNullObject(pNode);
792 }
793 AtomExitOnFailure(hr, "Failed to process all ATOM entry elements.");
794
795 if (!pEntry->wzId || !*pEntry->wzId)
796 {
797 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
798 AtomExitOnRootFailure(hr, "Failed to find required feed/entry/id element.");
799 }
800 else if (!pEntry->wzTitle || !*pEntry->wzTitle)
801 {
802 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
803 AtomExitOnRootFailure(hr, "Failed to find required feed/entry/title element.");
804 }
805 else if (0 == pEntry->ftUpdated.dwHighDateTime && 0 == pEntry->ftUpdated.dwLowDateTime)
806 {
807 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
808 AtomExitOnRootFailure(hr, "Failed to find required feed/entry/updated element.");
809 }
810
811 hr = S_OK;
812
813LExit:
814 ReleaseBSTR(bstrNodeName);
815 ReleaseObject(pNode);
816 ReleaseObject(pNodeList);
817 ReleaseObject(pixnnmAttributes);
818
819 return hr;
820}
821
822
823/********************************************************************
824 ParseAtomLink - parses out an ATOM link from a loaded XML DOM node.
825
826*********************************************************************/
827static HRESULT ParseAtomLink(
828 __in IXMLDOMNode* pixnLink,
829 __in ATOM_LINK* pLink
830 )
831{
832 HRESULT hr = S_OK;
833
834 IXMLDOMNamedNodeMap* pixnnmAttributes = NULL;
835 IXMLDOMNodeList *pNodeList = NULL;
836 IXMLDOMNode *pNode = NULL;
837 BSTR bstrNodeName = NULL;
838
839 // Process attributes first.
840 hr = pixnLink->get_attributes(&pixnnmAttributes);
841 AtomExitOnFailure(hr, "Failed get attributes for ATOM link.");
842
843 while (S_OK == (hr = XmlNextAttribute(pixnnmAttributes, &pNode, &bstrNodeName)))
844 {
845 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"rel", -1))
846 {
847 hr = AssignString(&pLink->wzRel, pNode);
848 AtomExitOnFailure(hr, "Failed to allocate ATOM link rel.");
849 }
850 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"href", -1))
851 {
852 hr = AssignString(&pLink->wzUrl, pNode);
853 AtomExitOnFailure(hr, "Failed to allocate ATOM link href.");
854 }
855 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"length", -1))
856 {
857 hr = XmlGetAttributeLargeNumber(pixnLink, bstrNodeName, &pLink->dw64Length);
858 if (E_INVALIDARG == hr)
859 {
860 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
861 }
862 AtomExitOnFailure(hr, "Failed to parse ATOM link length.");
863 }
864 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"title", -1))
865 {
866 hr = AssignString(&pLink->wzTitle, pNode);
867 AtomExitOnFailure(hr, "Failed to allocate ATOM link title.");
868 }
869 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"type", -1))
870 {
871 hr = AssignString(&pLink->wzType, pNode);
872 AtomExitOnFailure(hr, "Failed to allocate ATOM link type.");
873 }
874 else
875 {
876 hr = ParseAtomUnknownAttribute(pNode, &pLink->pUnknownAttributes);
877 AtomExitOnFailure(hr, "Failed to parse unknown ATOM link attribute: %ls", bstrNodeName);
878 }
879
880 ReleaseNullBSTR(bstrNodeName);
881 ReleaseNullObject(pNode);
882 }
883 AtomExitOnFailure(hr, "Failed to process all ATOM link attributes.");
884
885 // Process elements second.
886 hr = pixnLink->get_childNodes(&pNodeList);
887 AtomExitOnFailure(hr, "Failed to get child nodes of ATOM link element.");
888
889 while (S_OK == (hr = XmlNextElement(pNodeList, &pNode, &bstrNodeName)))
890 {
891 hr = ParseAtomUnknownElement(pNode, &pLink->pUnknownElements);
892 AtomExitOnFailure(hr, "Failed to parse unknown ATOM link element: %ls", bstrNodeName);
893
894 ReleaseNullBSTR(bstrNodeName);
895 ReleaseNullObject(pNode);
896 }
897 AtomExitOnFailure(hr, "Failed to process all ATOM link elements.");
898
899 hr = AssignString(&pLink->wzValue, pixnLink);
900 AtomExitOnFailure(hr, "Failed to allocate ATOM link value.");
901
902LExit:
903 ReleaseBSTR(bstrNodeName);
904 ReleaseObject(pNode);
905 ReleaseObject(pNodeList);
906 ReleaseObject(pixnnmAttributes);
907
908 return hr;
909}
910
911
912/********************************************************************
913 ParseAtomUnknownElement - parses out an unknown item from the ATOM feed from a loaded XML DOM node.
914
915*********************************************************************/
916static HRESULT ParseAtomUnknownElement(
917 __in IXMLDOMNode *pNode,
918 __inout ATOM_UNKNOWN_ELEMENT** ppUnknownElement
919 )
920{
921 Assert(ppUnknownElement);
922
923 HRESULT hr = S_OK;
924 BSTR bstrNodeNamespace = NULL;
925 BSTR bstrNodeName = NULL;
926 BSTR bstrNodeValue = NULL;
927 IXMLDOMNamedNodeMap* pixnnmAttributes = NULL;
928 IXMLDOMNode* pixnAttribute = NULL;
929 ATOM_UNKNOWN_ELEMENT* pNewUnknownElement;
930
931 pNewUnknownElement = (ATOM_UNKNOWN_ELEMENT*)MemAlloc(sizeof(ATOM_UNKNOWN_ELEMENT), TRUE);
932 AtomExitOnNull(pNewUnknownElement, hr, E_OUTOFMEMORY, "Failed to allocate unknown element.");
933
934 hr = pNode->get_namespaceURI(&bstrNodeNamespace);
935 if (S_OK == hr)
936 {
937 hr = StrAllocString(&pNewUnknownElement->wzNamespace, bstrNodeNamespace, 0);
938 AtomExitOnFailure(hr, "Failed to allocate ATOM unknown element namespace.");
939 }
940 else if (S_FALSE == hr)
941 {
942 hr = S_OK;
943 }
944 AtomExitOnFailure(hr, "Failed to get unknown element namespace.");
945
946 hr = pNode->get_baseName(&bstrNodeName);
947 AtomExitOnFailure(hr, "Failed to get unknown element name.");
948
949 hr = StrAllocString(&pNewUnknownElement->wzElement, bstrNodeName, 0);
950 AtomExitOnFailure(hr, "Failed to allocate ATOM unknown element name.");
951
952 hr = XmlGetText(pNode, &bstrNodeValue);
953 AtomExitOnFailure(hr, "Failed to get unknown element value.");
954
955 hr = StrAllocString(&pNewUnknownElement->wzValue, bstrNodeValue, 0);
956 AtomExitOnFailure(hr, "Failed to allocate ATOM unknown element value.");
957
958 hr = pNode->get_attributes(&pixnnmAttributes);
959 AtomExitOnFailure(hr, "Failed get attributes on ATOM unknown element.");
960
961 while (S_OK == (hr = pixnnmAttributes->nextNode(&pixnAttribute)))
962 {
963 hr = ParseAtomUnknownAttribute(pixnAttribute, &pNewUnknownElement->pAttributes);
964 AtomExitOnFailure(hr, "Failed to parse attribute on ATOM unknown element.");
965
966 ReleaseNullObject(pixnAttribute);
967 }
968
969 if (S_FALSE == hr)
970 {
971 hr = S_OK;
972 }
973 AtomExitOnFailure(hr, "Failed to enumerate all attributes on ATOM unknown element.");
974
975 ATOM_UNKNOWN_ELEMENT** ppTail = ppUnknownElement;
976 while (*ppTail)
977 {
978 ppTail = &(*ppTail)->pNext;
979 }
980
981 *ppTail = pNewUnknownElement;
982 pNewUnknownElement = NULL;
983
984LExit:
985 FreeAtomUnknownElementList(pNewUnknownElement);
986
987 ReleaseBSTR(bstrNodeNamespace);
988 ReleaseBSTR(bstrNodeName);
989 ReleaseBSTR(bstrNodeValue);
990 ReleaseObject(pixnnmAttributes);
991 ReleaseObject(pixnAttribute);
992
993 return hr;
994}
995
996
997/********************************************************************
998 ParseAtomUnknownAttribute - parses out attribute from an unknown element
999
1000*********************************************************************/
1001static HRESULT ParseAtomUnknownAttribute(
1002 __in IXMLDOMNode *pNode,
1003 __inout ATOM_UNKNOWN_ATTRIBUTE** ppUnknownAttribute
1004 )
1005{
1006 Assert(ppUnknownAttribute);
1007
1008 HRESULT hr = S_OK;
1009 BSTR bstrNodeNamespace = NULL;
1010 BSTR bstrNodeName = NULL;
1011 BSTR bstrNodeValue = NULL;
1012 ATOM_UNKNOWN_ATTRIBUTE* pNewUnknownAttribute;
1013
1014 pNewUnknownAttribute = (ATOM_UNKNOWN_ATTRIBUTE*)MemAlloc(sizeof(ATOM_UNKNOWN_ATTRIBUTE), TRUE);
1015 AtomExitOnNull(pNewUnknownAttribute, hr, E_OUTOFMEMORY, "Failed to allocate unknown attribute.");
1016
1017 hr = pNode->get_namespaceURI(&bstrNodeNamespace);
1018 if (S_OK == hr)
1019 {
1020 hr = StrAllocString(&pNewUnknownAttribute->wzNamespace, bstrNodeNamespace, 0);
1021 AtomExitOnFailure(hr, "Failed to allocate ATOM unknown attribute namespace.");
1022 }
1023 else if (S_FALSE == hr)
1024 {
1025 hr = S_OK;
1026 }
1027 AtomExitOnFailure(hr, "Failed to get unknown attribute namespace.");
1028
1029 hr = pNode->get_baseName(&bstrNodeName);
1030 AtomExitOnFailure(hr, "Failed to get unknown attribute name.");
1031
1032 hr = StrAllocString(&pNewUnknownAttribute->wzAttribute, bstrNodeName, 0);
1033 AtomExitOnFailure(hr, "Failed to allocate ATOM unknown attribute name.");
1034
1035 hr = XmlGetText(pNode, &bstrNodeValue);
1036 AtomExitOnFailure(hr, "Failed to get unknown attribute value.");
1037
1038 hr = StrAllocString(&pNewUnknownAttribute->wzValue, bstrNodeValue, 0);
1039 AtomExitOnFailure(hr, "Failed to allocate ATOM unknown attribute value.");
1040
1041 ATOM_UNKNOWN_ATTRIBUTE** ppTail = ppUnknownAttribute;
1042 while (*ppTail)
1043 {
1044 ppTail = &(*ppTail)->pNext;
1045 }
1046
1047 *ppTail = pNewUnknownAttribute;
1048 pNewUnknownAttribute = NULL;
1049
1050LExit:
1051 FreeAtomUnknownAttributeList(pNewUnknownAttribute);
1052
1053 ReleaseBSTR(bstrNodeNamespace);
1054 ReleaseBSTR(bstrNodeName);
1055 ReleaseBSTR(bstrNodeValue);
1056
1057 return hr;
1058}
1059
1060
1061/********************************************************************
1062 AssignDateTime - assigns the value of a node to a FILETIME struct.
1063
1064*********************************************************************/
1065static HRESULT AssignDateTime(
1066 __in FILETIME* pft,
1067 __in IXMLDOMNode* pNode
1068 )
1069{
1070 HRESULT hr = S_OK;
1071 BSTR bstrValue = NULL;
1072
1073 if (0 != pft->dwHighDateTime || 0 != pft->dwLowDateTime)
1074 {
1075 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
1076 AtomExitOnRootFailure(hr, "Already process this datetime value.");
1077 }
1078
1079 hr = XmlGetText(pNode, &bstrValue);
1080 AtomExitOnFailure(hr, "Failed to get value.");
1081
1082 if (S_OK == hr)
1083 {
1084 hr = TimeFromString3339(bstrValue, pft);
1085 AtomExitOnFailure(hr, "Failed to convert value to time.");
1086 }
1087 else
1088 {
1089 ZeroMemory(pft, sizeof(FILETIME));
1090 hr = S_OK;
1091 }
1092
1093LExit:
1094 ReleaseBSTR(bstrValue);
1095
1096 return hr;
1097}
1098
1099
1100/********************************************************************
1101 AssignString - assigns the value of a node to a dynamic string.
1102
1103*********************************************************************/
1104static HRESULT AssignString(
1105 __out_z LPWSTR* pwzValue,
1106 __in IXMLDOMNode* pNode
1107 )
1108{
1109 HRESULT hr = S_OK;
1110 BSTR bstrValue = NULL;
1111
1112 if (pwzValue && *pwzValue)
1113 {
1114 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
1115 AtomExitOnRootFailure(hr, "Already processed this value.");
1116 }
1117
1118 hr = XmlGetText(pNode, &bstrValue);
1119 AtomExitOnFailure(hr, "Failed to get value.");
1120
1121 if (S_OK == hr)
1122 {
1123 hr = StrAllocString(pwzValue, bstrValue, 0);
1124 AtomExitOnFailure(hr, "Failed to allocate value.");
1125 }
1126 else
1127 {
1128 ReleaseNullStr(pwzValue);
1129 hr = S_OK;
1130 }
1131
1132LExit:
1133 ReleaseBSTR(bstrValue);
1134
1135 return hr;
1136}
1137
1138
1139/********************************************************************
1140 FreeAtomAuthor - releases all of the memory used by an ATOM author.
1141
1142*********************************************************************/
1143static void FreeAtomAuthor(
1144 __in_opt ATOM_AUTHOR* pAuthor
1145 )
1146{
1147 if (pAuthor)
1148 {
1149 ReleaseStr(pAuthor->wzUrl);
1150 ReleaseStr(pAuthor->wzEmail);
1151 ReleaseStr(pAuthor->wzName);
1152 }
1153}
1154
1155
1156/********************************************************************
1157 FreeAtomCategory - releases all of the memory used by an ATOM category.
1158
1159*********************************************************************/
1160static void FreeAtomCategory(
1161 __in_opt ATOM_CATEGORY* pCategory
1162 )
1163{
1164 if (pCategory)
1165 {
1166 FreeAtomUnknownElementList(pCategory->pUnknownElements);
1167
1168 ReleaseStr(pCategory->wzTerm);
1169 ReleaseStr(pCategory->wzScheme);
1170 ReleaseStr(pCategory->wzLabel);
1171 }
1172}
1173
1174
1175/********************************************************************
1176 FreeAtomContent - releases all of the memory used by an ATOM content.
1177
1178*********************************************************************/
1179static void FreeAtomContent(
1180 __in_opt ATOM_CONTENT* pContent
1181 )
1182{
1183 if (pContent)
1184 {
1185 FreeAtomUnknownElementList(pContent->pUnknownElements);
1186
1187 ReleaseStr(pContent->wzValue);
1188 ReleaseStr(pContent->wzUrl);
1189 ReleaseStr(pContent->wzType);
1190 }
1191}
1192
1193
1194/********************************************************************
1195 FreeAtomEntry - releases all of the memory used by an ATOM entry.
1196
1197*********************************************************************/
1198static void FreeAtomEntry(
1199 __in_opt ATOM_ENTRY* pEntry
1200 )
1201{
1202 if (pEntry)
1203 {
1204 FreeAtomUnknownElementList(pEntry->pUnknownElements);
1205 ReleaseObject(pEntry->pixn);
1206
1207 for (DWORD i = 0; i < pEntry->cLinks; ++i)
1208 {
1209 FreeAtomLink(pEntry->rgLinks + i);
1210 }
1211 ReleaseMem(pEntry->rgLinks);
1212
1213 for (DWORD i = 0; i < pEntry->cCategories; ++i)
1214 {
1215 FreeAtomCategory(pEntry->rgCategories + i);
1216 }
1217 ReleaseMem(pEntry->rgCategories);
1218
1219 for (DWORD i = 0; i < pEntry->cAuthors; ++i)
1220 {
1221 FreeAtomAuthor(pEntry->rgAuthors + i);
1222 }
1223 ReleaseMem(pEntry->rgAuthors);
1224
1225 FreeAtomContent(pEntry->pContent);
1226 ReleaseMem(pEntry->pContent);
1227
1228 ReleaseStr(pEntry->wzTitle);
1229 ReleaseStr(pEntry->wzSummary);
1230 ReleaseStr(pEntry->wzId);
1231 }
1232}
1233
1234
1235/********************************************************************
1236 FreeAtomLink - releases all of the memory used by an ATOM link.
1237
1238*********************************************************************/
1239static void FreeAtomLink(
1240 __in_opt ATOM_LINK* pLink
1241 )
1242{
1243 if (pLink)
1244 {
1245 FreeAtomUnknownElementList(pLink->pUnknownElements);
1246 FreeAtomUnknownAttributeList(pLink->pUnknownAttributes);
1247
1248 ReleaseStr(pLink->wzValue);
1249 ReleaseStr(pLink->wzUrl);
1250 ReleaseStr(pLink->wzType);
1251 ReleaseStr(pLink->wzTitle);
1252 ReleaseStr(pLink->wzRel);
1253 }
1254}
1255
1256
1257/********************************************************************
1258 FreeAtomUnknownElement - releases all of the memory used by a list of unknown elements
1259
1260*********************************************************************/
1261static void FreeAtomUnknownElementList(
1262 __in_opt ATOM_UNKNOWN_ELEMENT* pUnknownElement
1263 )
1264{
1265 while (pUnknownElement)
1266 {
1267 ATOM_UNKNOWN_ELEMENT* pFree = pUnknownElement;
1268 pUnknownElement = pUnknownElement->pNext;
1269
1270 FreeAtomUnknownAttributeList(pFree->pAttributes);
1271 ReleaseStr(pFree->wzNamespace);
1272 ReleaseStr(pFree->wzElement);
1273 ReleaseStr(pFree->wzValue);
1274 MemFree(pFree);
1275 }
1276}
1277
1278
1279/********************************************************************
1280 FreeAtomUnknownAttribute - releases all of the memory used by a list of unknown attributes
1281
1282*********************************************************************/
1283static void FreeAtomUnknownAttributeList(
1284 __in_opt ATOM_UNKNOWN_ATTRIBUTE* pUnknownAttribute
1285 )
1286{
1287 while (pUnknownAttribute)
1288 {
1289 ATOM_UNKNOWN_ATTRIBUTE* pFree = pUnknownAttribute;
1290 pUnknownAttribute = pUnknownAttribute->pNext;
1291
1292 ReleaseStr(pFree->wzNamespace);
1293 ReleaseStr(pFree->wzAttribute);
1294 ReleaseStr(pFree->wzValue);
1295 MemFree(pFree);
1296 }
1297}
diff --git a/src/libs/dutil/WixToolset.DUtil/buffutil.cpp b/src/libs/dutil/WixToolset.DUtil/buffutil.cpp
new file mode 100644
index 00000000..b6d58cc0
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/buffutil.cpp
@@ -0,0 +1,529 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define BuffExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_BUFFUTIL, x, s, __VA_ARGS__)
8#define BuffExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_BUFFUTIL, x, s, __VA_ARGS__)
9#define BuffExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_BUFFUTIL, x, s, __VA_ARGS__)
10#define BuffExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_BUFFUTIL, x, s, __VA_ARGS__)
11#define BuffExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_BUFFUTIL, x, s, __VA_ARGS__)
12#define BuffExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_BUFFUTIL, x, s, __VA_ARGS__)
13#define BuffExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_BUFFUTIL, p, x, e, s, __VA_ARGS__)
14#define BuffExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_BUFFUTIL, p, x, s, __VA_ARGS__)
15#define BuffExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_BUFFUTIL, p, x, e, s, __VA_ARGS__)
16#define BuffExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_BUFFUTIL, p, x, s, __VA_ARGS__)
17#define BuffExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_BUFFUTIL, e, x, s, __VA_ARGS__)
18#define BuffExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_BUFFUTIL, g, x, s, __VA_ARGS__)
19
20
21// constants
22
23#define BUFFER_INCREMENT 128
24
25
26// helper function declarations
27
28static HRESULT EnsureBufferSize(
29 __deref_inout_bcount(cbSize) BYTE** ppbBuffer,
30 __in SIZE_T cbSize
31 );
32
33
34// functions
35
36extern "C" HRESULT BuffReadNumber(
37 __in_bcount(cbBuffer) const BYTE* pbBuffer,
38 __in SIZE_T cbBuffer,
39 __inout SIZE_T* piBuffer,
40 __out DWORD* pdw
41 )
42{
43 Assert(pbBuffer);
44 Assert(piBuffer);
45 Assert(pdw);
46
47 HRESULT hr = S_OK;
48 SIZE_T cbAvailable = 0;
49
50 // get availiable data size
51 hr = ::SIZETSub(cbBuffer, *piBuffer, &cbAvailable);
52 BuffExitOnRootFailure(hr, "Failed to calculate available data size.");
53
54 // verify buffer size
55 if (sizeof(DWORD) > cbAvailable)
56 {
57 hr = E_INVALIDARG;
58 BuffExitOnRootFailure(hr, "Buffer too small.");
59 }
60
61 *pdw = *(const DWORD*)(pbBuffer + *piBuffer);
62 *piBuffer += sizeof(DWORD);
63
64LExit:
65 return hr;
66}
67
68extern "C" HRESULT BuffReadNumber64(
69 __in_bcount(cbBuffer) const BYTE * pbBuffer,
70 __in SIZE_T cbBuffer,
71 __inout SIZE_T* piBuffer,
72 __out DWORD64* pdw64
73 )
74{
75 Assert(pbBuffer);
76 Assert(piBuffer);
77 Assert(pdw64);
78
79 HRESULT hr = S_OK;
80 SIZE_T cbAvailable = 0;
81
82 // get availiable data size
83 hr = ::SIZETSub(cbBuffer, *piBuffer, &cbAvailable);
84 BuffExitOnRootFailure(hr, "Failed to calculate available data size.");
85
86 // verify buffer size
87 if (sizeof(DWORD64) > cbAvailable)
88 {
89 hr = E_INVALIDARG;
90 BuffExitOnRootFailure(hr, "Buffer too small.");
91 }
92
93 *pdw64 = *(const DWORD64*)(pbBuffer + *piBuffer);
94 *piBuffer += sizeof(DWORD64);
95
96LExit:
97 return hr;
98}
99
100extern "C" HRESULT BuffReadPointer(
101 __in_bcount(cbBuffer) const BYTE* pbBuffer,
102 __in SIZE_T cbBuffer,
103 __inout SIZE_T* piBuffer,
104 __out DWORD_PTR* pdw64
105 )
106{
107 Assert(pbBuffer);
108 Assert(piBuffer);
109 Assert(pdw64);
110
111 HRESULT hr = S_OK;
112 SIZE_T cbAvailable = 0;
113
114 // get availiable data size
115 hr = ::SIZETSub(cbBuffer, *piBuffer, &cbAvailable);
116 BuffExitOnRootFailure(hr, "Failed to calculate available data size.");
117
118 // verify buffer size
119 if (sizeof(DWORD_PTR) > cbAvailable)
120 {
121 hr = E_INVALIDARG;
122 BuffExitOnRootFailure(hr, "Buffer too small.");
123 }
124
125 *pdw64 = *(const DWORD_PTR*)(pbBuffer + *piBuffer);
126 *piBuffer += sizeof(DWORD_PTR);
127
128LExit:
129 return hr;
130}
131
132extern "C" HRESULT BuffReadString(
133 __in_bcount(cbBuffer) const BYTE* pbBuffer,
134 __in SIZE_T cbBuffer,
135 __inout SIZE_T* piBuffer,
136 __deref_out_z LPWSTR* pscz
137 )
138{
139 Assert(pbBuffer);
140 Assert(piBuffer);
141 Assert(pscz);
142
143 HRESULT hr = S_OK;
144 SIZE_T cch = 0;
145 SIZE_T cb = 0;
146 SIZE_T cbAvailable = 0;
147
148 // get availiable data size
149 hr = ::SIZETSub(cbBuffer, *piBuffer, &cbAvailable);
150 BuffExitOnRootFailure(hr, "Failed to calculate available data size for character count.");
151
152 // verify buffer size
153 if (sizeof(SIZE_T) > cbAvailable)
154 {
155 hr = E_INVALIDARG;
156 BuffExitOnRootFailure(hr, "Buffer too small.");
157 }
158
159 // read character count
160 cch = *(const SIZE_T*)(pbBuffer + *piBuffer);
161
162 hr = ::SIZETMult(cch, sizeof(WCHAR), &cb);
163 BuffExitOnRootFailure(hr, "Overflow while multiplying to calculate buffer size");
164
165 hr = ::SIZETAdd(*piBuffer, sizeof(SIZE_T), piBuffer);
166 BuffExitOnRootFailure(hr, "Overflow while adding to calculate buffer size");
167
168 // get availiable data size
169 hr = ::SIZETSub(cbBuffer, *piBuffer, &cbAvailable);
170 BuffExitOnRootFailure(hr, "Failed to calculate available data size for character buffer.");
171
172 // verify buffer size
173 if (cb > cbAvailable)
174 {
175 hr = E_INVALIDARG;
176 BuffExitOnRootFailure(hr, "Buffer too small to hold character data.");
177 }
178
179 // copy character data
180 hr = StrAllocString(pscz, cch ? (LPCWSTR)(pbBuffer + *piBuffer) : L"", cch);
181 BuffExitOnFailure(hr, "Failed to copy character data.");
182
183 *piBuffer += cb;
184
185LExit:
186 return hr;
187}
188
189extern "C" HRESULT BuffReadStringAnsi(
190 __in_bcount(cbBuffer) const BYTE* pbBuffer,
191 __in SIZE_T cbBuffer,
192 __inout SIZE_T* piBuffer,
193 __deref_out_z LPSTR* pscz
194 )
195{
196 Assert(pbBuffer);
197 Assert(piBuffer);
198 Assert(pscz);
199
200 HRESULT hr = S_OK;
201 SIZE_T cch = 0;
202 SIZE_T cb = 0;
203 SIZE_T cbAvailable = 0;
204
205 // get availiable data size
206 hr = ::SIZETSub(cbBuffer, *piBuffer, &cbAvailable);
207 BuffExitOnRootFailure(hr, "Failed to calculate available data size for character count.");
208
209 // verify buffer size
210 if (sizeof(SIZE_T) > cbAvailable)
211 {
212 hr = E_INVALIDARG;
213 BuffExitOnRootFailure(hr, "Buffer too small.");
214 }
215
216 // read character count
217 cch = *(const SIZE_T*)(pbBuffer + *piBuffer);
218
219 hr = ::SIZETMult(cch, sizeof(CHAR), &cb);
220 BuffExitOnRootFailure(hr, "Overflow while multiplying to calculate buffer size");
221
222 hr = ::SIZETAdd(*piBuffer, sizeof(SIZE_T), piBuffer);
223 BuffExitOnRootFailure(hr, "Overflow while adding to calculate buffer size");
224
225 // get availiable data size
226 hr = ::SIZETSub(cbBuffer, *piBuffer, &cbAvailable);
227 BuffExitOnRootFailure(hr, "Failed to calculate available data size for character buffer.");
228
229 // verify buffer size
230 if (cb > cbAvailable)
231 {
232 hr = E_INVALIDARG;
233 BuffExitOnRootFailure(hr, "Buffer too small to hold character count.");
234 }
235
236 // copy character data
237 hr = StrAnsiAllocStringAnsi(pscz, cch ? (LPCSTR)(pbBuffer + *piBuffer) : "", cch);
238 BuffExitOnFailure(hr, "Failed to copy character data.");
239
240 *piBuffer += cb;
241
242LExit:
243 return hr;
244}
245
246extern "C" HRESULT BuffReadStream(
247 __in_bcount(cbBuffer) const BYTE* pbBuffer,
248 __in SIZE_T cbBuffer,
249 __inout SIZE_T* piBuffer,
250 __deref_inout_bcount(*pcbStream) BYTE** ppbStream,
251 __out SIZE_T* pcbStream
252 )
253{
254 Assert(pbBuffer);
255 Assert(piBuffer);
256 Assert(ppbStream);
257 Assert(pcbStream);
258
259 HRESULT hr = S_OK;
260 SIZE_T cb = 0;
261 SIZE_T cbAvailable = 0;
262 errno_t err = 0;
263
264 // get availiable data size
265 hr = ::SIZETSub(cbBuffer, *piBuffer, &cbAvailable);
266 BuffExitOnRootFailure(hr, "Failed to calculate available data size for stream size.");
267
268 // verify buffer size
269 if (sizeof(SIZE_T) > cbAvailable)
270 {
271 hr = E_INVALIDARG;
272 BuffExitOnRootFailure(hr, "Buffer too small.");
273 }
274
275 // read stream size
276 cb = *(const SIZE_T*)(pbBuffer + *piBuffer);
277 *piBuffer += sizeof(SIZE_T);
278
279 // get availiable data size
280 hr = ::SIZETSub(cbBuffer, *piBuffer, &cbAvailable);
281 BuffExitOnRootFailure(hr, "Failed to calculate available data size for stream buffer.");
282
283 // verify buffer size
284 if (cb > cbAvailable)
285 {
286 hr = E_INVALIDARG;
287 BuffExitOnRootFailure(hr, "Buffer too small to hold byte count.");
288 }
289
290 // allocate buffer
291 *ppbStream = (BYTE*)MemAlloc(cb, TRUE);
292 BuffExitOnNull(*ppbStream, hr, E_OUTOFMEMORY, "Failed to allocate stream.");
293
294 // read stream data
295 err = memcpy_s(*ppbStream, cbBuffer - *piBuffer, pbBuffer + *piBuffer, cb);
296 if (err)
297 {
298 BuffExitOnRootFailure(hr = E_INVALIDARG, "Failed to read stream from buffer, error: %d", err);
299 }
300
301 *piBuffer += cb;
302
303 // return stream size
304 *pcbStream = cb;
305
306LExit:
307 return hr;
308}
309
310extern "C" HRESULT BuffWriteNumber(
311 __deref_inout_bcount(*piBuffer) BYTE** ppbBuffer,
312 __inout SIZE_T* piBuffer,
313 __in DWORD dw
314 )
315{
316 Assert(ppbBuffer);
317 Assert(piBuffer);
318
319 HRESULT hr = S_OK;
320
321 // make sure we have a buffer with sufficient space
322 hr = EnsureBufferSize(ppbBuffer, *piBuffer + sizeof(DWORD));
323 BuffExitOnFailure(hr, "Failed to ensure buffer size.");
324
325 // copy data to buffer
326 *(DWORD*)(*ppbBuffer + *piBuffer) = dw;
327 *piBuffer += sizeof(DWORD);
328
329LExit:
330 return hr;
331}
332
333extern "C" HRESULT BuffWriteNumber64(
334 __deref_inout_bcount(*piBuffer) BYTE** ppbBuffer,
335 __inout SIZE_T* piBuffer,
336 __in DWORD64 dw64
337 )
338{
339 Assert(ppbBuffer);
340 Assert(piBuffer);
341
342 HRESULT hr = S_OK;
343
344 // make sure we have a buffer with sufficient space
345 hr = EnsureBufferSize(ppbBuffer, *piBuffer + sizeof(DWORD64));
346 BuffExitOnFailure(hr, "Failed to ensure buffer size.");
347
348 // copy data to buffer
349 *(DWORD64*)(*ppbBuffer + *piBuffer) = dw64;
350 *piBuffer += sizeof(DWORD64);
351
352LExit:
353 return hr;
354}
355
356extern "C" HRESULT BuffWritePointer(
357 __deref_inout_bcount(*piBuffer) BYTE** ppbBuffer,
358 __inout SIZE_T* piBuffer,
359 __in DWORD_PTR dw
360 )
361{
362 Assert(ppbBuffer);
363 Assert(piBuffer);
364
365 HRESULT hr = S_OK;
366
367 // make sure we have a buffer with sufficient space
368 hr = EnsureBufferSize(ppbBuffer, *piBuffer + sizeof(DWORD_PTR));
369 BuffExitOnFailure(hr, "Failed to ensure buffer size.");
370
371 // copy data to buffer
372 *(DWORD_PTR*)(*ppbBuffer + *piBuffer) = dw;
373 *piBuffer += sizeof(DWORD_PTR);
374
375LExit:
376 return hr;
377}
378
379extern "C" HRESULT BuffWriteString(
380 __deref_inout_bcount(*piBuffer) BYTE** ppbBuffer,
381 __inout SIZE_T* piBuffer,
382 __in_z_opt LPCWSTR scz
383 )
384{
385 Assert(ppbBuffer);
386 Assert(piBuffer);
387
388 HRESULT hr = S_OK;
389 SIZE_T cch = 0;
390 SIZE_T cb = 0;
391 errno_t err = 0;
392
393 if (scz)
394 {
395 hr = ::StringCchLengthW(scz, STRSAFE_MAX_CCH, reinterpret_cast<size_t*>(&cch));
396 BuffExitOnRootFailure(hr, "Failed to get string size.")
397 }
398
399 cb = cch * sizeof(WCHAR);
400
401 // make sure we have a buffer with sufficient space
402 hr = EnsureBufferSize(ppbBuffer, *piBuffer + (sizeof(SIZE_T) + cb));
403 BuffExitOnFailure(hr, "Failed to ensure buffer size.");
404
405 // copy character count to buffer
406 *(SIZE_T*)(*ppbBuffer + *piBuffer) = cch;
407 *piBuffer += sizeof(SIZE_T);
408
409 // copy data to buffer
410 err = memcpy_s(*ppbBuffer + *piBuffer, cb, scz, cb);
411 if (err)
412 {
413 BuffExitOnRootFailure(hr = E_INVALIDARG, "Failed to write string to buffer: '%ls', error: %d", scz, err);
414 }
415
416 *piBuffer += cb;
417
418LExit:
419 return hr;
420}
421
422extern "C" HRESULT BuffWriteStringAnsi(
423 __deref_inout_bcount(*piBuffer) BYTE** ppbBuffer,
424 __inout SIZE_T* piBuffer,
425 __in_z_opt LPCSTR scz
426 )
427{
428 Assert(ppbBuffer);
429 Assert(piBuffer);
430
431 HRESULT hr = S_OK;
432 SIZE_T cch = 0;
433 SIZE_T cb = 0;
434 errno_t err = 0;
435
436 if (scz)
437 {
438 hr = ::StringCchLengthA(scz, STRSAFE_MAX_CCH, reinterpret_cast<size_t*>(&cch));
439 BuffExitOnRootFailure(hr, "Failed to get string size.")
440 }
441
442 cb = cch * sizeof(CHAR);
443
444 // make sure we have a buffer with sufficient space
445 hr = EnsureBufferSize(ppbBuffer, *piBuffer + (sizeof(SIZE_T) + cb));
446 BuffExitOnFailure(hr, "Failed to ensure buffer size.");
447
448 // copy character count to buffer
449 *(SIZE_T*)(*ppbBuffer + *piBuffer) = cch;
450 *piBuffer += sizeof(SIZE_T);
451
452 // copy data to buffer
453 err = memcpy_s(*ppbBuffer + *piBuffer, cb, scz, cb);
454 if (err)
455 {
456 BuffExitOnRootFailure(hr = E_INVALIDARG, "Failed to write string to buffer: '%hs', error: %d", scz, err);
457 }
458
459 *piBuffer += cb;
460
461LExit:
462 return hr;
463}
464
465extern "C" HRESULT BuffWriteStream(
466 __deref_inout_bcount(*piBuffer) BYTE** ppbBuffer,
467 __inout SIZE_T* piBuffer,
468 __in_bcount(cbStream) const BYTE* pbStream,
469 __in SIZE_T cbStream
470 )
471{
472 Assert(ppbBuffer);
473 Assert(piBuffer);
474 Assert(pbStream);
475
476 HRESULT hr = S_OK;
477 SIZE_T cb = cbStream;
478 errno_t err = 0;
479
480 // make sure we have a buffer with sufficient space
481 hr = EnsureBufferSize(ppbBuffer, *piBuffer + cbStream + sizeof(SIZE_T));
482 BuffExitOnFailure(hr, "Failed to ensure buffer size.");
483
484 // copy byte count to buffer
485 *(SIZE_T*)(*ppbBuffer + *piBuffer) = cb;
486 *piBuffer += sizeof(SIZE_T);
487
488 // copy data to buffer
489 err = memcpy_s(*ppbBuffer + *piBuffer, cbStream, pbStream, cbStream);
490 if (err)
491 {
492 BuffExitOnRootFailure(hr = E_INVALIDARG, "Failed to write stream to buffer, error: %d", err);
493 }
494
495 *piBuffer += cbStream;
496
497LExit:
498 return hr;
499}
500
501
502// helper functions
503
504static HRESULT EnsureBufferSize(
505 __deref_inout_bcount(cbSize) BYTE** ppbBuffer,
506 __in SIZE_T cbSize
507 )
508{
509 HRESULT hr = S_OK;
510 SIZE_T cbTarget = ((cbSize / BUFFER_INCREMENT) + 1) * BUFFER_INCREMENT;
511
512 if (*ppbBuffer)
513 {
514 if (MemSize(*ppbBuffer) < cbTarget)
515 {
516 LPVOID pv = MemReAlloc(*ppbBuffer, cbTarget, TRUE);
517 BuffExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to reallocate buffer.");
518 *ppbBuffer = (BYTE*)pv;
519 }
520 }
521 else
522 {
523 *ppbBuffer = (BYTE*)MemAlloc(cbTarget, TRUE);
524 BuffExitOnNull(*ppbBuffer, hr, E_OUTOFMEMORY, "Failed to allocate buffer.");
525 }
526
527LExit:
528 return hr;
529}
diff --git a/src/libs/dutil/WixToolset.DUtil/build/WixToolset.DUtil.props b/src/libs/dutil/WixToolset.DUtil/build/WixToolset.DUtil.props
new file mode 100644
index 00000000..35749be8
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/build/WixToolset.DUtil.props
@@ -0,0 +1,28 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
5 <ItemDefinitionGroup>
6 <ClCompile>
7 <AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)native\include\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
8 </ClCompile>
9 <ResourceCompile>
10 <AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)native\include\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
11 </ResourceCompile>
12 </ItemDefinitionGroup>
13 <ItemDefinitionGroup Condition=" $(PlatformToolset.ToLower().StartsWith('v140')) ">
14 <Link>
15 <AdditionalDependencies>$(MSBuildThisFileDirectory)native\v140\$(PlatformTarget)\dutil.lib;%(AdditionalDependencies)</AdditionalDependencies>
16 </Link>
17 </ItemDefinitionGroup>
18 <ItemDefinitionGroup Condition=" $(PlatformToolset.ToLower().StartsWith('v141')) ">
19 <Link>
20 <AdditionalDependencies>$(MSBuildThisFileDirectory)native\v141\$(PlatformTarget)\dutil.lib;%(AdditionalDependencies)</AdditionalDependencies>
21 </Link>
22 </ItemDefinitionGroup>
23 <ItemDefinitionGroup Condition=" $(PlatformToolset.ToLower().StartsWith('v142')) ">
24 <Link>
25 <AdditionalDependencies>$(MSBuildThisFileDirectory)native\v142\$(PlatformTarget)\dutil.lib;%(AdditionalDependencies)</AdditionalDependencies>
26 </Link>
27 </ItemDefinitionGroup>
28</Project>
diff --git a/src/libs/dutil/WixToolset.DUtil/butil.cpp b/src/libs/dutil/WixToolset.DUtil/butil.cpp
new file mode 100644
index 00000000..e04b52e9
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/butil.cpp
@@ -0,0 +1,257 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5// Exit macros
6#define ButilExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_BUTIL, x, s, __VA_ARGS__)
7#define ButilExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_BUTIL, x, s, __VA_ARGS__)
8#define ButilExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_BUTIL, x, s, __VA_ARGS__)
9#define ButilExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_BUTIL, x, s, __VA_ARGS__)
10#define ButilExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_BUTIL, x, s, __VA_ARGS__)
11#define ButilExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_BUTIL, x, s, __VA_ARGS__)
12#define ButilExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_BUTIL, p, x, e, s, __VA_ARGS__)
13#define ButilExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_BUTIL, p, x, s, __VA_ARGS__)
14#define ButilExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_BUTIL, p, x, e, s, __VA_ARGS__)
15#define ButilExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_BUTIL, p, x, s, __VA_ARGS__)
16#define ButilExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_BUTIL, e, x, s, __VA_ARGS__)
17
18// constants
19// From engine/registration.h
20const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_UNINSTALL_KEY = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
21const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE = L"BundleUpgradeCode";
22const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_BUNDLE_PROVIDER_KEY = L"BundleProviderKey";
23
24// Forward declarations.
25/********************************************************************
26OpenBundleKey - Opens the bundle uninstallation key for a given bundle
27
28NOTE: caller is responsible for closing key
29********************************************************************/
30static HRESULT OpenBundleKey(
31 __in_z LPCWSTR wzBundleId,
32 __in BUNDLE_INSTALL_CONTEXT context,
33 __inout HKEY *key);
34
35
36extern "C" HRESULT DAPI BundleGetBundleInfo(
37 __in_z LPCWSTR wzBundleId,
38 __in_z LPCWSTR wzAttribute,
39 __out_ecount_opt(*pcchValueBuf) LPWSTR lpValueBuf,
40 __inout_opt LPDWORD pcchValueBuf
41 )
42{
43 Assert(wzBundleId && wzAttribute);
44
45 HRESULT hr = S_OK;
46 BUNDLE_INSTALL_CONTEXT context = BUNDLE_INSTALL_CONTEXT_MACHINE;
47 LPWSTR sczValue = NULL;
48 HKEY hkBundle = NULL;
49 DWORD cchSource = 0;
50 DWORD dwType = 0;
51 DWORD dwValue = 0;
52
53 if ((lpValueBuf && !pcchValueBuf) || !wzBundleId || !wzAttribute)
54 {
55 ButilExitOnFailure(hr = E_INVALIDARG, "An invalid parameter was passed to the function.");
56 }
57
58 if (FAILED(hr = OpenBundleKey(wzBundleId, context = BUNDLE_INSTALL_CONTEXT_MACHINE, &hkBundle)) &&
59 FAILED(hr = OpenBundleKey(wzBundleId, context = BUNDLE_INSTALL_CONTEXT_USER, &hkBundle)))
60 {
61 ButilExitOnFailure(E_FILENOTFOUND == hr ? HRESULT_FROM_WIN32(ERROR_UNKNOWN_PRODUCT) : hr, "Failed to locate bundle uninstall key path.");
62 }
63
64 // If the bundle doesn't have the property defined, return ERROR_UNKNOWN_PROPERTY
65 hr = RegGetType(hkBundle, wzAttribute, &dwType);
66 ButilExitOnFailure(E_FILENOTFOUND == hr ? HRESULT_FROM_WIN32(ERROR_UNKNOWN_PROPERTY) : hr, "Failed to locate bundle property.");
67
68 switch (dwType)
69 {
70 case REG_SZ:
71 hr = RegReadString(hkBundle, wzAttribute, &sczValue);
72 ButilExitOnFailure(hr, "Failed to read string property.");
73 break;
74 case REG_DWORD:
75 hr = RegReadNumber(hkBundle, wzAttribute, &dwValue);
76 ButilExitOnFailure(hr, "Failed to read dword property.");
77
78 hr = StrAllocFormatted(&sczValue, L"%d", dwValue);
79 ButilExitOnFailure(hr, "Failed to format dword property as string.");
80 break;
81 default:
82 ButilExitOnFailure(hr = E_NOTIMPL, "Reading bundle info of type 0x%x not implemented.", dwType);
83
84 }
85
86 hr = ::StringCchLengthW(sczValue, STRSAFE_MAX_CCH, reinterpret_cast<UINT_PTR*>(&cchSource));
87 ButilExitOnFailure(hr, "Failed to calculate length of string");
88
89 if (lpValueBuf)
90 {
91 // cchSource is the length of the string not including the terminating null character
92 if (*pcchValueBuf <= cchSource)
93 {
94 *pcchValueBuf = ++cchSource;
95 ButilExitOnFailure(hr = HRESULT_FROM_WIN32(ERROR_MORE_DATA), "A buffer is too small to hold the requested data.");
96 }
97
98 hr = ::StringCchCatNExW(lpValueBuf, *pcchValueBuf, sczValue, cchSource, NULL, NULL, STRSAFE_FILL_BEHIND_NULL);
99 ButilExitOnFailure(hr, "Failed to copy the property value to the output buffer.");
100
101 *pcchValueBuf = cchSource++;
102 }
103
104LExit:
105 ReleaseRegKey(hkBundle);
106 ReleaseStr(sczValue);
107
108 return hr;
109}
110
111HRESULT DAPI BundleEnumRelatedBundle(
112 __in_z LPCWSTR wzUpgradeCode,
113 __in BUNDLE_INSTALL_CONTEXT context,
114 __inout PDWORD pdwStartIndex,
115 __out_ecount(MAX_GUID_CHARS+1) LPWSTR lpBundleIdBuf
116 )
117{
118 HRESULT hr = S_OK;
119 HKEY hkRoot = BUNDLE_INSTALL_CONTEXT_USER == context ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
120 HKEY hkUninstall = NULL;
121 HKEY hkBundle = NULL;
122 LPWSTR sczUninstallSubKey = NULL;
123 DWORD cchUninstallSubKey = 0;
124 LPWSTR sczUninstallSubKeyPath = NULL;
125 LPWSTR sczValue = NULL;
126 DWORD dwType = 0;
127
128 LPWSTR* rgsczBundleUpgradeCodes = NULL;
129 DWORD cBundleUpgradeCodes = 0;
130 BOOL fUpgradeCodeFound = FALSE;
131
132 if (!wzUpgradeCode || !lpBundleIdBuf || !pdwStartIndex)
133 {
134 ButilExitOnFailure(hr = E_INVALIDARG, "An invalid parameter was passed to the function.");
135 }
136
137 hr = RegOpen(hkRoot, BUNDLE_REGISTRATION_REGISTRY_UNINSTALL_KEY, KEY_READ, &hkUninstall);
138 ButilExitOnFailure(hr, "Failed to open bundle uninstall key path.");
139
140 for (DWORD dwIndex = *pdwStartIndex; !fUpgradeCodeFound; dwIndex++)
141 {
142 hr = RegKeyEnum(hkUninstall, dwIndex, &sczUninstallSubKey);
143 ButilExitOnFailure(hr, "Failed to enumerate bundle uninstall key path.");
144
145 hr = StrAllocFormatted(&sczUninstallSubKeyPath, L"%ls\\%ls", BUNDLE_REGISTRATION_REGISTRY_UNINSTALL_KEY, sczUninstallSubKey);
146 ButilExitOnFailure(hr, "Failed to allocate bundle uninstall key path.");
147
148 hr = RegOpen(hkRoot, sczUninstallSubKeyPath, KEY_READ, &hkBundle);
149 ButilExitOnFailure(hr, "Failed to open uninstall key path.");
150
151 // If it's a bundle, it should have a BundleUpgradeCode value of type REG_SZ (old) or REG_MULTI_SZ
152 hr = RegGetType(hkBundle, BUNDLE_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, &dwType);
153 if (FAILED(hr))
154 {
155 ReleaseRegKey(hkBundle);
156 ReleaseNullStr(sczUninstallSubKey);
157 ReleaseNullStr(sczUninstallSubKeyPath);
158 // Not a bundle
159 continue;
160 }
161
162 switch (dwType)
163 {
164 case REG_SZ:
165 hr = RegReadString(hkBundle, BUNDLE_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, &sczValue);
166 ButilExitOnFailure(hr, "Failed to read BundleUpgradeCode string property.");
167 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, sczValue, -1, wzUpgradeCode, -1))
168 {
169 *pdwStartIndex = dwIndex;
170 fUpgradeCodeFound = TRUE;
171 break;
172 }
173
174 ReleaseNullStr(sczValue);
175
176 break;
177 case REG_MULTI_SZ:
178 hr = RegReadStringArray(hkBundle, BUNDLE_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, &rgsczBundleUpgradeCodes, &cBundleUpgradeCodes);
179 ButilExitOnFailure(hr, "Failed to read BundleUpgradeCode multi-string property.");
180
181 for (DWORD i = 0; i < cBundleUpgradeCodes; i++)
182 {
183 LPWSTR wzBundleUpgradeCode = rgsczBundleUpgradeCodes[i];
184 if (wzBundleUpgradeCode && *wzBundleUpgradeCode)
185 {
186 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, wzBundleUpgradeCode, -1, wzUpgradeCode, -1))
187 {
188 *pdwStartIndex = dwIndex;
189 fUpgradeCodeFound = TRUE;
190 break;
191 }
192 }
193 }
194 ReleaseNullStrArray(rgsczBundleUpgradeCodes, cBundleUpgradeCodes);
195
196 break;
197
198 default:
199 ButilExitOnFailure(hr = E_NOTIMPL, "BundleUpgradeCode of type 0x%x not implemented.", dwType);
200
201 }
202
203 if (fUpgradeCodeFound)
204 {
205 if (lpBundleIdBuf)
206 {
207 hr = ::StringCchLengthW(sczUninstallSubKey, STRSAFE_MAX_CCH, reinterpret_cast<UINT_PTR*>(&cchUninstallSubKey));
208 ButilExitOnFailure(hr, "Failed to calculate length of string");
209
210 hr = ::StringCchCopyNExW(lpBundleIdBuf, MAX_GUID_CHARS + 1, sczUninstallSubKey, cchUninstallSubKey, NULL, NULL, STRSAFE_FILL_BEHIND_NULL);
211 ButilExitOnFailure(hr, "Failed to copy the property value to the output buffer.");
212 }
213
214 break;
215 }
216
217 // Cleanup before next iteration
218 ReleaseRegKey(hkBundle);
219 ReleaseNullStr(sczUninstallSubKey);
220 ReleaseNullStr(sczUninstallSubKeyPath);
221 }
222
223LExit:
224 ReleaseStr(sczValue);
225 ReleaseStr(sczUninstallSubKey);
226 ReleaseStr(sczUninstallSubKeyPath);
227 ReleaseRegKey(hkBundle);
228 ReleaseRegKey(hkUninstall);
229 ReleaseStrArray(rgsczBundleUpgradeCodes, cBundleUpgradeCodes);
230
231 return hr;
232}
233
234
235HRESULT OpenBundleKey(
236 __in_z LPCWSTR wzBundleId,
237 __in BUNDLE_INSTALL_CONTEXT context,
238 __inout HKEY *key)
239{
240 Assert(key && wzBundleId);
241 AssertSz(NULL == *key, "*key should be null");
242
243 HRESULT hr = S_OK;
244 HKEY hkRoot = BUNDLE_INSTALL_CONTEXT_USER == context ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
245 LPWSTR sczKeypath = NULL;
246
247 hr = StrAllocFormatted(&sczKeypath, L"%ls\\%ls", BUNDLE_REGISTRATION_REGISTRY_UNINSTALL_KEY, wzBundleId);
248 ButilExitOnFailure(hr, "Failed to allocate bundle uninstall key path.");
249
250 hr = RegOpen(hkRoot, sczKeypath, KEY_READ, key);
251 ButilExitOnFailure(hr, "Failed to open bundle uninstall key path.");
252
253LExit:
254 ReleaseStr(sczKeypath);
255
256 return hr;
257}
diff --git a/src/libs/dutil/WixToolset.DUtil/cabcutil.cpp b/src/libs/dutil/WixToolset.DUtil/cabcutil.cpp
new file mode 100644
index 00000000..93a9b7e1
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/cabcutil.cpp
@@ -0,0 +1,1539 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define CabcExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_CABCUTIL, x, s, __VA_ARGS__)
8#define CabcExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_CABCUTIL, x, s, __VA_ARGS__)
9#define CabcExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_CABCUTIL, x, s, __VA_ARGS__)
10#define CabcExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_CABCUTIL, x, s, __VA_ARGS__)
11#define CabcExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_CABCUTIL, x, s, __VA_ARGS__)
12#define CabcExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_CABCUTIL, x, s, __VA_ARGS__)
13#define CabcExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_CABCUTIL, p, x, e, s, __VA_ARGS__)
14#define CabcExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_CABCUTIL, p, x, s, __VA_ARGS__)
15#define CabcExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_CABCUTIL, p, x, e, s, __VA_ARGS__)
16#define CabcExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_CABCUTIL, p, x, s, __VA_ARGS__)
17#define CabcExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_CABCUTIL, e, x, s, __VA_ARGS__)
18#define CabcExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_CABCUTIL, g, x, s, __VA_ARGS__)
19
20
21static const WCHAR CABC_MAGIC_UNICODE_STRING_MARKER = '?';
22static const DWORD MAX_CABINET_HEADER_SIZE = 16 * 1024 * 1024;
23
24// The minimum number of uncompressed bytes between FciFlushFolder() calls - if we call FciFlushFolder()
25// too often (because of duplicates too close together) we theoretically ruin our compression ratio -
26// left at zero to maximize install-time performance, because even a small minimum threshhold seems to
27// have a high install-time performance cost for little or no size benefit. The value is left here for
28// tweaking though - possible suggested values are 524288 for 512K, or 2097152 for 2MB.
29static const DWORD MINFLUSHTHRESHHOLD = 0;
30
31// structs
32struct MS_CABINET_HEADER
33{
34 DWORD sig;
35 DWORD csumHeader;
36 DWORD cbCabinet;
37 DWORD csumFolders;
38 DWORD coffFiles;
39 DWORD csumFiles;
40 WORD version;
41 WORD cFolders;
42 WORD cFiles;
43 WORD flags;
44 WORD setID;
45 WORD iCabinet;
46};
47
48
49struct MS_CABINET_ITEM
50{
51 DWORD cbFile;
52 DWORD uoffFolderStart;
53 WORD iFolder;
54 WORD date;
55 WORD time;
56 WORD attribs;
57};
58
59struct CABC_INTERNAL_ADDFILEINFO
60{
61 LPCWSTR wzSourcePath;
62 LPCWSTR wzEmptyPath;
63};
64
65struct CABC_DUPLICATEFILE
66{
67 DWORD dwFileArrayIndex;
68 DWORD dwDuplicateCabFileIndex;
69 LPWSTR pwzSourcePath;
70 LPWSTR pwzToken;
71};
72
73
74struct CABC_FILE
75{
76 DWORD dwCabFileIndex;
77 LPWSTR pwzSourcePath;
78 LPWSTR pwzToken;
79 PMSIFILEHASHINFO pmfHash;
80 LONGLONG llFileSize;
81 BOOL fHasDuplicates;
82};
83
84
85struct CABC_DATA
86{
87 LONGLONG llBytesSinceLastFlush;
88 LONGLONG llFlushThreshhold;
89
90 STRINGDICT_HANDLE shDictHandle;
91
92 WCHAR wzCabinetPath[MAX_PATH];
93 WCHAR wzEmptyFile[MAX_PATH];
94 HANDLE hEmptyFile;
95 DWORD dwLastFileIndex;
96
97 DWORD cFilePaths;
98 DWORD cMaxFilePaths;
99 CABC_FILE *prgFiles;
100
101 DWORD cDuplicates;
102 DWORD cMaxDuplicates;
103 CABC_DUPLICATEFILE *prgDuplicates;
104
105 HRESULT hrLastError;
106 BOOL fGoodCab;
107
108 HFCI hfci;
109 ERF erf;
110 CCAB ccab;
111 TCOMP tc;
112
113 // Below Field are used for Cabinet Splitting
114 BOOL fCabinetSplittingEnabled;
115 FileSplitCabNamesCallback fileSplitCabNamesCallback;
116 WCHAR wzFirstCabinetName[MAX_PATH]; // Stores Name of First Cabinet excluding ".cab" extention to help generate other names by Splitting
117};
118
119const int CABC_HANDLE_BYTES = sizeof(CABC_DATA);
120
121//
122// prototypes
123//
124static void FreeCabCData(
125 __in CABC_DATA* pcd
126 );
127static HRESULT CheckForDuplicateFile(
128 __in CABC_DATA *pcd,
129 __out CABC_FILE **ppcf,
130 __in LPCWSTR wzFileName,
131 __in PMSIFILEHASHINFO *ppmfHash,
132 __in LONGLONG llFileSize
133 );
134static HRESULT AddDuplicateFile(
135 __in CABC_DATA *pcd,
136 __in DWORD dwFileArrayIndex,
137 __in_z LPCWSTR wzSourcePath,
138 __in_opt LPCWSTR wzToken,
139 __in DWORD dwDuplicateCabFileIndex
140 );
141static HRESULT AddNonDuplicateFile(
142 __in CABC_DATA *pcd,
143 __in LPCWSTR wzFile,
144 __in_opt LPCWSTR wzToken,
145 __in_opt const MSIFILEHASHINFO* pmfHash,
146 __in LONGLONG llFileSize,
147 __in DWORD dwCabFileIndex
148 );
149static HRESULT UpdateDuplicateFiles(
150 __in const CABC_DATA *pcd
151 );
152static HRESULT DuplicateFile(
153 __in MS_CABINET_HEADER *pHeader,
154 __in const CABC_DATA *pcd,
155 __in const CABC_DUPLICATEFILE *pDuplicate
156 );
157static HRESULT UtcFileTimeToLocalDosDateTime(
158 __in const FILETIME* pFileTime,
159 __out USHORT* pDate,
160 __out USHORT* pTime
161 );
162
163static __callback int DIAMONDAPI CabCFilePlaced(__in PCCAB pccab, __in_z PSTR szFile, __in long cbFile, __in BOOL fContinuation, __inout_bcount(CABC_HANDLE_BYTES) void *pv);
164static __callback void * DIAMONDAPI CabCAlloc(__in ULONG cb);
165static __callback void DIAMONDAPI CabCFree(__out_bcount(CABC_HANDLE_BYTES) void *pv);
166static __callback INT_PTR DIAMONDAPI CabCOpen(__in_z PSTR pszFile, __in int oflag, __in int pmode, __out int *err, __inout_bcount(CABC_HANDLE_BYTES) void *pv);
167static __callback UINT FAR DIAMONDAPI CabCRead(__in INT_PTR hf, __out_bcount(cb) void FAR *memory, __in UINT cb, __out int *err, __inout_bcount(CABC_HANDLE_BYTES) void *pv);
168static __callback UINT FAR DIAMONDAPI CabCWrite(__in INT_PTR hf, __in_bcount(cb) void FAR *memory, __in UINT cb, __out int *err, __inout_bcount(CABC_HANDLE_BYTES) void *pv);
169static __callback long FAR DIAMONDAPI CabCSeek(__in INT_PTR hf, __in long dist, __in int seektype, __out int *err, __inout_bcount(CABC_HANDLE_BYTES) void *pv);
170static __callback int FAR DIAMONDAPI CabCClose(__in INT_PTR hf, __out int *err, __inout_bcount(CABC_HANDLE_BYTES) void *pv);
171static __callback int DIAMONDAPI CabCDelete(__in_z PSTR szFile, __out int *err, __inout_bcount(CABC_HANDLE_BYTES) void *pv);
172__success(return != FALSE) static __callback BOOL DIAMONDAPI CabCGetTempFile(__out_bcount_z(cbFile) char *szFile, __in int cbFile, __inout_bcount(CABC_HANDLE_BYTES) void *pv);
173__success(return != FALSE) static __callback BOOL DIAMONDAPI CabCGetNextCabinet(__in PCCAB pccab, __in ULONG ul, __out_bcount(CABC_HANDLE_BYTES) void *pv);
174static __callback INT_PTR DIAMONDAPI CabCGetOpenInfo(__in_z PSTR pszName, __out USHORT *pdate, __out USHORT *ptime, __out USHORT *pattribs, __out int *err, __out_bcount(CABC_HANDLE_BYTES) void *pv);
175static __callback long DIAMONDAPI CabCStatus(__in UINT uiTypeStatus, __in ULONG cb1, __in ULONG cb2, __inout_bcount(CABC_HANDLE_BYTES) void *pv);
176
177
178/********************************************************************
179CabcBegin - begins creating a cabinet
180
181NOTE: phContext must be the same handle used in AddFile and Finish.
182 wzCabDir can be L"", but not NULL.
183 dwMaxSize and dwMaxThresh can be 0. A large default value will be used in that case.
184
185********************************************************************/
186extern "C" HRESULT DAPI CabCBegin(
187 __in_z LPCWSTR wzCab,
188 __in_z LPCWSTR wzCabDir,
189 __in DWORD dwMaxFiles,
190 __in DWORD dwMaxSize,
191 __in DWORD dwMaxThresh,
192 __in COMPRESSION_TYPE ct,
193 __out_bcount(CABC_HANDLE_BYTES) HANDLE *phContext
194 )
195{
196 Assert(wzCab && *wzCab && phContext);
197
198 HRESULT hr = S_OK;
199 CABC_DATA *pcd = NULL;
200 WCHAR wzTempPath[MAX_PATH] = { };
201
202 C_ASSERT(sizeof(MSIFILEHASHINFO) == 20);
203
204 WCHAR wzPathBuffer [MAX_PATH] = L"";
205 size_t cchPathBuffer;
206 if (wzCabDir)
207 {
208 hr = ::StringCchLengthW(wzCabDir, MAX_PATH, &cchPathBuffer);
209 CabcExitOnFailure(hr, "Failed to get length of cab directory");
210
211 // Need room to terminate with L'\\' and L'\0'
212 if((MAX_PATH - 1) <= cchPathBuffer || 0 == cchPathBuffer)
213 {
214 hr = E_INVALIDARG;
215 CabcExitOnFailure(hr, "Cab directory had invalid length: %u", cchPathBuffer);
216 }
217
218 hr = ::StringCchCopyW(wzPathBuffer, countof(wzPathBuffer), wzCabDir);
219 CabcExitOnFailure(hr, "Failed to copy cab directory to buffer");
220
221 if (L'\\' != wzPathBuffer[cchPathBuffer - 1])
222 {
223 hr = ::StringCchCatW(wzPathBuffer, countof(wzPathBuffer), L"\\");
224 CabcExitOnFailure(hr, "Failed to cat \\ to end of buffer");
225 ++cchPathBuffer;
226 }
227 }
228
229 pcd = static_cast<CABC_DATA*>(MemAlloc(sizeof(CABC_DATA), TRUE));
230 CabcExitOnNull(pcd, hr, E_OUTOFMEMORY, "failed to allocate cab creation data structure");
231
232 pcd->hrLastError = S_OK;
233 pcd->fGoodCab = TRUE;
234 pcd->llFlushThreshhold = MINFLUSHTHRESHHOLD;
235
236 pcd->hEmptyFile = INVALID_HANDLE_VALUE;
237
238 pcd->fileSplitCabNamesCallback = NULL;
239
240 if (NULL == dwMaxSize)
241 {
242 pcd->ccab.cb = CAB_MAX_SIZE;
243 pcd->fCabinetSplittingEnabled = FALSE; // If no max cab size is supplied, cabinet splitting is not desired
244 }
245 else
246 {
247 pcd->ccab.cb = dwMaxSize * 1024 * 1024;
248 pcd->fCabinetSplittingEnabled = TRUE;
249 }
250
251 if (0 == dwMaxThresh)
252 {
253 // Subtract 16 to magically make cabbing of uncompressed data larger than 2GB work.
254 pcd->ccab.cbFolderThresh = CAB_MAX_SIZE - 16;
255 }
256 else
257 {
258 pcd->ccab.cbFolderThresh = dwMaxThresh;
259 }
260
261 // Translate the compression type
262 if (COMPRESSION_TYPE_NONE == ct)
263 {
264 pcd->tc = tcompTYPE_NONE;
265 }
266 else if (COMPRESSION_TYPE_LOW == ct)
267 {
268 pcd->tc = tcompTYPE_LZX | tcompLZX_WINDOW_LO;
269 }
270 else if (COMPRESSION_TYPE_MEDIUM == ct)
271 {
272 pcd->tc = TCOMPfromLZXWindow(18);
273 }
274 else if (COMPRESSION_TYPE_HIGH == ct)
275 {
276 pcd->tc = tcompTYPE_LZX | tcompLZX_WINDOW_HI;
277 }
278 else if (COMPRESSION_TYPE_MSZIP == ct)
279 {
280 pcd->tc = tcompTYPE_MSZIP;
281 }
282 else
283 {
284 hr = E_INVALIDARG;
285 CabcExitOnFailure(hr, "Invalid compression type specified.");
286 }
287
288 if (0 == ::WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, wzCab, -1, pcd->ccab.szCab, sizeof(pcd->ccab.szCab), NULL, NULL))
289 {
290 CabcExitWithLastError(hr, "failed to convert cab name to multi-byte");
291 }
292
293 if (0 == ::WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, wzPathBuffer, -1, pcd->ccab.szCabPath, sizeof(pcd->ccab.szCab), NULL, NULL))
294 {
295 CabcExitWithLastError(hr, "failed to convert cab dir to multi-byte");
296 }
297
298 // Remember the path to the cabinet.
299 hr= ::StringCchCopyW(pcd->wzCabinetPath, countof(pcd->wzCabinetPath), wzPathBuffer);
300 CabcExitOnFailure(hr, "Failed to copy cabinet path from path: %ls", wzPathBuffer);
301
302 hr = ::StringCchCatW(pcd->wzCabinetPath, countof(pcd->wzCabinetPath), wzCab);
303 CabcExitOnFailure(hr, "Failed to concat to cabinet path cabinet name: %ls", wzCab);
304
305 // Get the empty file to use as the blank marker for duplicates.
306 if (!::GetTempPathW(countof(wzTempPath), wzTempPath))
307 {
308 CabcExitWithLastError(hr, "Failed to get temp path.");
309 }
310
311 if (!::GetTempFileNameW(wzTempPath, L"WSC", 0, pcd->wzEmptyFile))
312 {
313 CabcExitWithLastError(hr, "Failed to create a temp file name.");
314 }
315
316 // Try to open the newly created empty file (remember, GetTempFileName() is kind enough to create a file for us)
317 // with a handle to automatically delete the file on close. Ignore any failure that might happen, since the worst
318 // case is we'll leave a zero byte file behind in the temp folder.
319 pcd->hEmptyFile = ::CreateFileW(pcd->wzEmptyFile, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL);
320
321 hr = DictCreateWithEmbeddedKey(&pcd->shDictHandle, dwMaxFiles, reinterpret_cast<void **>(&pcd->prgFiles), offsetof(CABC_FILE, pwzSourcePath), DICT_FLAG_CASEINSENSITIVE);
322 CabcExitOnFailure(hr, "Failed to create dictionary to keep track of duplicate files");
323
324 // Make sure to allocate at least some space, or we won't be able to realloc later if they "lied" about having zero files
325 if (1 > dwMaxFiles)
326 {
327 dwMaxFiles = 1;
328 }
329
330 pcd->cMaxFilePaths = dwMaxFiles;
331 size_t cbFileAllocSize = 0;
332
333 hr = ::SizeTMult(pcd->cMaxFilePaths, sizeof(CABC_FILE), &(cbFileAllocSize));
334 CabcExitOnFailure(hr, "Maximum allocation exceeded on initialization.");
335
336 pcd->prgFiles = static_cast<CABC_FILE*>(MemAlloc(cbFileAllocSize, TRUE));
337 CabcExitOnNull(pcd->prgFiles, hr, E_OUTOFMEMORY, "Failed to allocate memory for files.");
338
339 // Tell cabinet API about our configuration.
340 pcd->hfci = ::FCICreate(&(pcd->erf), CabCFilePlaced, CabCAlloc, CabCFree, CabCOpen, CabCRead, CabCWrite, CabCClose, CabCSeek, CabCDelete, CabCGetTempFile, &(pcd->ccab), pcd);
341 if (NULL == pcd->hfci || pcd->erf.fError)
342 {
343 // Prefer our recorded last error, then ::GetLastError(), finally fallback to the useless "E_FAIL" error
344 if (FAILED(pcd->hrLastError))
345 {
346 hr = pcd->hrLastError;
347 }
348 else
349 {
350 CabcExitWithLastError(hr, "failed to create FCI object Oper: 0x%x Type: 0x%x", pcd->erf.erfOper, pcd->erf.erfType);
351 }
352
353 pcd->fGoodCab = FALSE;
354
355 CabcExitOnFailure(hr, "failed to create FCI object Oper: 0x%x Type: 0x%x", pcd->erf.erfOper, pcd->erf.erfType); // TODO: can these be converted to HRESULTS?
356 }
357
358 *phContext = pcd;
359
360LExit:
361 if (FAILED(hr) && pcd && pcd->hfci)
362 {
363 ::FCIDestroy(pcd->hfci);
364 }
365
366 return hr;
367}
368
369
370/********************************************************************
371CabCNextCab - This will be useful when creating multiple cabs.
372Haven't needed it yet.
373********************************************************************/
374extern "C" HRESULT DAPI CabCNextCab(
375 __in_bcount(CABC_HANDLE_BYTES) HANDLE hContext
376 )
377{
378 UNREFERENCED_PARAMETER(hContext);
379 // TODO: Make the appropriate FCIFlushCabinet and FCIFlushFolder calls
380 return E_NOTIMPL;
381}
382
383
384/********************************************************************
385CabcAddFile - adds a file to a cabinet
386
387NOTE: hContext must be the same used in Begin and Finish
388if wzToken is null, the file's original name is used within the cab
389********************************************************************/
390extern "C" HRESULT DAPI CabCAddFile(
391 __in_z LPCWSTR wzFile,
392 __in_z_opt LPCWSTR wzToken,
393 __in_opt PMSIFILEHASHINFO pmfHash,
394 __in_bcount(CABC_HANDLE_BYTES) HANDLE hContext
395 )
396{
397 Assert(wzFile && *wzFile && hContext);
398
399 HRESULT hr = S_OK;
400 CABC_DATA *pcd = reinterpret_cast<CABC_DATA*>(hContext);
401 CABC_FILE *pcfDuplicate = NULL;
402 LONGLONG llFileSize = 0;
403 PMSIFILEHASHINFO pmfLocalHash = pmfHash;
404
405 // Use Smart Cabbing if there are duplicates and if Cabinet Splitting is not desired
406 // For Cabinet Spliting avoid hashing as Smart Cabbing is disabled
407 if(!pcd->fCabinetSplittingEnabled)
408 {
409 // Store file size, primarily used to determine which files to hash for duplicates
410 hr = FileSize(wzFile, &llFileSize);
411 CabcExitOnFailure(hr, "Failed to check size of file %ls", wzFile);
412
413 hr = CheckForDuplicateFile(pcd, &pcfDuplicate, wzFile, &pmfLocalHash, llFileSize);
414 CabcExitOnFailure(hr, "Failed while checking for duplicate of file: %ls", wzFile);
415 }
416
417 if (pcfDuplicate) // This will be null for smart cabbing case
418 {
419 DWORD index;
420 hr = ::PtrdiffTToDWord(pcfDuplicate - pcd->prgFiles, &index);
421 CabcExitOnFailure(hr, "Failed to calculate index of file name: %ls", pcfDuplicate->pwzSourcePath);
422
423 hr = AddDuplicateFile(pcd, index, wzFile, wzToken, pcd->dwLastFileIndex);
424 CabcExitOnFailure(hr, "Failed to add duplicate of file name: %ls", pcfDuplicate->pwzSourcePath);
425 }
426 else
427 {
428 hr = AddNonDuplicateFile(pcd, wzFile, wzToken, pmfLocalHash, llFileSize, pcd->dwLastFileIndex);
429 CabcExitOnFailure(hr, "Failed to add non-duplicated file: %ls", wzFile);
430 }
431
432 ++pcd->dwLastFileIndex;
433
434LExit:
435 // If we allocated a hash struct ourselves, free it
436 if (pmfHash != pmfLocalHash)
437 {
438 ReleaseMem(pmfLocalHash);
439 }
440
441 return hr;
442}
443
444
445/********************************************************************
446CabcFinish - finishes making a cabinet
447
448NOTE: hContext must be the same used in Begin and AddFile
449*********************************************************************/
450extern "C" HRESULT DAPI CabCFinish(
451 __in_bcount(CABC_HANDLE_BYTES) HANDLE hContext,
452 __in_opt FileSplitCabNamesCallback fileSplitCabNamesCallback
453 )
454{
455 Assert(hContext);
456
457 HRESULT hr = S_OK;
458 CABC_DATA *pcd = reinterpret_cast<CABC_DATA*>(hContext);
459 CABC_INTERNAL_ADDFILEINFO fileInfo = { };
460 DWORD dwCabFileIndex; // Total file index, counts up to pcd->dwLastFileIndex
461 DWORD dwArrayFileIndex = 0; // Index into pcd->prgFiles[] array
462 DWORD dwDupeArrayFileIndex = 0; // Index into pcd->prgDuplicates[] array
463 LPSTR pszFileToken = NULL;
464 LONGLONG llFileSize = 0;
465
466 pcd->fileSplitCabNamesCallback = fileSplitCabNamesCallback;
467
468 // These are used to determine whether to call FciFlushFolder() before or after the next call to FciAddFile()
469 // doing so at appropriate times results in install-time performance benefits in the case of duplicate files.
470 // Basically, when MSI has to extract files out of order (as it does due to our smart cabbing), it can't just jump
471 // exactly to the out of order file, it must begin extracting all over again, starting from that file's CAB folder
472 // (this is not the same as a regular folder, and is a concept unique to CABs).
473
474 // This means MSI spends a lot of time extracting the same files twice, especially if the duplicate file has many files
475 // before it in the CAB folder. To avoid this, we want to make sure whenever MSI jumps to another file in the CAB, that
476 // file is at the beginning of its own folder, so no extra files need to be extracted. FciFlushFolder() causes the CAB
477 // to close the current folder, and create a new folder for the next file to be added.
478
479 // So to maximize our performance benefit, we must call FciFlushFolder() at every place MSI will jump "to" in the CAB sequence.
480 // So, we call FciFlushFolder() before adding the original version of a duplicated file (as this will be jumped "to")
481 // And we call FciFlushFolder() after adding the duplicate versions of files (as this will be jumped back "to" to get back in the regular sequence)
482 BOOL fFlushBefore = FALSE;
483 BOOL fFlushAfter = FALSE;
484
485 ReleaseDict(pcd->shDictHandle);
486
487 // We need to go through all the files, duplicates and non-duplicates, sequentially in the order they were added
488 for (dwCabFileIndex = 0; dwCabFileIndex < pcd->dwLastFileIndex; ++dwCabFileIndex)
489 {
490 if (dwArrayFileIndex < pcd->cMaxFilePaths && pcd->prgFiles[dwArrayFileIndex].dwCabFileIndex == dwCabFileIndex) // If it's a non-duplicate file
491 {
492 // Just a normal, non-duplicated file. We'll add it to the list for later checking of
493 // duplicates.
494 fileInfo.wzSourcePath = pcd->prgFiles[dwArrayFileIndex].pwzSourcePath;
495 fileInfo.wzEmptyPath = NULL;
496
497 // Use the provided token, otherwise default to the source file name.
498 if (pcd->prgFiles[dwArrayFileIndex].pwzToken)
499 {
500 LPCWSTR pwzTemp = pcd->prgFiles[dwArrayFileIndex].pwzToken;
501 hr = StrAnsiAllocString(&pszFileToken, pwzTemp, 0, CP_ACP);
502 CabcExitOnFailure(hr, "failed to convert file token to ANSI: %ls", pwzTemp);
503 }
504 else
505 {
506 LPCWSTR pwzTemp = FileFromPath(fileInfo.wzSourcePath);
507 hr = StrAnsiAllocString(&pszFileToken, pwzTemp, 0, CP_ACP);
508 CabcExitOnFailure(hr, "failed to convert file name to ANSI: %ls", pwzTemp);
509 }
510
511 if (pcd->prgFiles[dwArrayFileIndex].fHasDuplicates)
512 {
513 fFlushBefore = TRUE;
514 }
515
516 llFileSize = pcd->prgFiles[dwArrayFileIndex].llFileSize;
517
518 ++dwArrayFileIndex; // Increment into the non-duplicate array
519 }
520 else if (dwDupeArrayFileIndex < pcd->cMaxDuplicates && pcd->prgDuplicates[dwDupeArrayFileIndex].dwDuplicateCabFileIndex == dwCabFileIndex) // If it's a duplicate file
521 {
522 // For duplicate files, we point them at our empty (zero-byte) file so it takes up no space
523 // in the resultant cabinet. Later on (CabCFinish) we'll go through and change all the zero
524 // byte files to point at their duplicated file index.
525 //
526 // Notice that duplicate files are not added to the list of file paths because all duplicate
527 // files point at the same path (the empty file) so there is no point in tracking them with
528 // their path.
529 fileInfo.wzSourcePath = pcd->prgDuplicates[dwDupeArrayFileIndex].pwzSourcePath;
530 fileInfo.wzEmptyPath = pcd->wzEmptyFile;
531
532 // Use the provided token, otherwise default to the source file name.
533 if (pcd->prgDuplicates[dwDupeArrayFileIndex].pwzToken)
534 {
535 LPCWSTR pwzTemp = pcd->prgDuplicates[dwDupeArrayFileIndex].pwzToken;
536 hr = StrAnsiAllocString(&pszFileToken, pwzTemp, 0, CP_ACP);
537 CabcExitOnFailure(hr, "failed to convert duplicate file token to ANSI: %ls", pwzTemp);
538 }
539 else
540 {
541 LPCWSTR pwzTemp = FileFromPath(fileInfo.wzSourcePath);
542 hr = StrAnsiAllocString(&pszFileToken, pwzTemp, 0, CP_ACP);
543 CabcExitOnFailure(hr, "failed to convert duplicate file name to ANSI: %ls", pwzTemp);
544 }
545
546 // Flush afterward only if this isn't a duplicate of the previous file, and at least one non-duplicate file remains to be added to the cab
547 if (!(dwCabFileIndex - 1 == pcd->prgFiles[pcd->prgDuplicates[dwDupeArrayFileIndex].dwFileArrayIndex].dwCabFileIndex) &&
548 !(dwDupeArrayFileIndex > 0 && dwCabFileIndex - 1 == pcd->prgDuplicates[dwDupeArrayFileIndex - 1].dwDuplicateCabFileIndex) &&
549 dwArrayFileIndex < pcd->cFilePaths)
550 {
551 fFlushAfter = TRUE;
552 }
553
554 // We're just adding a 0-byte file, so set it appropriately
555 llFileSize = 0;
556
557 ++dwDupeArrayFileIndex; // Increment into the duplicate array
558 }
559 else // If it's neither duplicate nor non-duplicate, throw an error
560 {
561 hr = HRESULT_FROM_WIN32(ERROR_EA_LIST_INCONSISTENT);
562 CabcExitOnRootFailure(hr, "Internal inconsistency in data structures while creating CAB file - a non-standard, non-duplicate file was encountered");
563 }
564
565 if (fFlushBefore && pcd->llBytesSinceLastFlush > pcd->llFlushThreshhold)
566 {
567 if (!::FCIFlushFolder(pcd->hfci, CabCGetNextCabinet, CabCStatus))
568 {
569 CabcExitWithLastError(hr, "failed to flush FCI folder before adding file, Oper: 0x%x Type: 0x%x", pcd->erf.erfOper, pcd->erf.erfType);
570 }
571 pcd->llBytesSinceLastFlush = 0;
572 }
573
574 pcd->llBytesSinceLastFlush += llFileSize;
575
576 // Add the file to the cab. Notice that we are passing our CABC_INTERNAL_ADDFILEINFO struct
577 // through the pointer to an ANSI string. This is neccessary so we can smuggle through the
578 // path to the empty file (should this be a duplicate file).
579#pragma prefast(push)
580#pragma prefast(disable:6387) // OACR is silly, pszFileToken can't be false here
581 if (!::FCIAddFile(pcd->hfci, reinterpret_cast<LPSTR>(&fileInfo), pszFileToken, FALSE, CabCGetNextCabinet, CabCStatus, CabCGetOpenInfo, pcd->tc))
582#pragma prefast(pop)
583 {
584 pcd->fGoodCab = FALSE;
585
586 // Prefer our recorded last error, then ::GetLastError(), finally fallback to the useless "E_FAIL" error
587 if (FAILED(pcd->hrLastError))
588 {
589 hr = pcd->hrLastError;
590 }
591 else
592 {
593 CabcExitWithLastError(hr, "failed to add file to FCI object Oper: 0x%x Type: 0x%x File: %ls", pcd->erf.erfOper, pcd->erf.erfType, fileInfo.wzSourcePath);
594 }
595
596 CabcExitOnFailure(hr, "failed to add file to FCI object Oper: 0x%x Type: 0x%x File: %ls", pcd->erf.erfOper, pcd->erf.erfType, fileInfo.wzSourcePath); // TODO: can these be converted to HRESULTS?
597 }
598
599 // For Cabinet Splitting case, check for pcd->hrLastError that may be set as result of Error in CabCGetNextCabinet
600 // This is required as returning False in CabCGetNextCabinet is not aborting cabinet creation and is reporting success instead
601 if (pcd->fCabinetSplittingEnabled && FAILED(pcd->hrLastError))
602 {
603 hr = pcd->hrLastError;
604 CabcExitOnFailure(hr, "Failed to create next cabinet name while splitting cabinet.");
605 }
606
607 if (fFlushAfter && pcd->llBytesSinceLastFlush > pcd->llFlushThreshhold)
608 {
609 if (!::FCIFlushFolder(pcd->hfci, CabCGetNextCabinet, CabCStatus))
610 {
611 CabcExitWithLastError(hr, "failed to flush FCI folder after adding file, Oper: 0x%x Type: 0x%x", pcd->erf.erfOper, pcd->erf.erfType);
612 }
613 pcd->llBytesSinceLastFlush = 0;
614 }
615
616 fFlushAfter = FALSE;
617 fFlushBefore = FALSE;
618 }
619
620 if (!pcd->fGoodCab)
621 {
622 // Prefer our recorded last error, then ::GetLastError(), finally fallback to the useless "E_FAIL" error
623 if (FAILED(pcd->hrLastError))
624 {
625 hr = pcd->hrLastError;
626 }
627 else
628 {
629 CabcExitWithLastError(hr, "failed while creating CAB FCI object Oper: 0x%x Type: 0x%x File: %ls", pcd->erf.erfOper, pcd->erf.erfType, fileInfo.wzSourcePath);
630 }
631
632 CabcExitOnFailure(hr, "failed while creating CAB FCI object Oper: 0x%x Type: 0x%x File: %ls", pcd->erf.erfOper, pcd->erf.erfType, fileInfo.wzSourcePath); // TODO: can these be converted to HRESULTS?
633 }
634
635 // Only flush the cabinet if we actually succeeded in previous calls - otherwise we just waste time (a lot on big cabs)
636 if (!::FCIFlushCabinet(pcd->hfci, FALSE, CabCGetNextCabinet, CabCStatus))
637 {
638 // If we have a last error, use that, otherwise return the useless error
639 hr = FAILED(pcd->hrLastError) ? pcd->hrLastError : E_FAIL;
640 CabcExitOnFailure(hr, "failed to flush FCI object Oper: 0x%x Type: 0x%x", pcd->erf.erfOper, pcd->erf.erfType); // TODO: can these be converted to HRESULTS?
641 }
642
643 if (pcd->fGoodCab && pcd->cDuplicates)
644 {
645 hr = UpdateDuplicateFiles(pcd);
646 CabcExitOnFailure(hr, "Failed to update duplicates in cabinet: %ls", pcd->wzCabinetPath);
647 }
648
649LExit:
650 ::FCIDestroy(pcd->hfci);
651 FreeCabCData(pcd);
652 ReleaseNullStr(pszFileToken);
653
654 return hr;
655}
656
657
658/********************************************************************
659CabCCancel - cancels making a cabinet
660
661NOTE: hContext must be the same used in Begin and AddFile
662*********************************************************************/
663extern "C" void DAPI CabCCancel(
664 __in_bcount(CABC_HANDLE_BYTES) HANDLE hContext
665 )
666{
667 Assert(hContext);
668
669 CABC_DATA* pcd = reinterpret_cast<CABC_DATA*>(hContext);
670 ::FCIDestroy(pcd->hfci);
671 FreeCabCData(pcd);
672}
673
674
675//
676// private
677//
678
679static void FreeCabCData(
680 __in CABC_DATA* pcd
681 )
682{
683 if (pcd)
684 {
685 ReleaseFileHandle(pcd->hEmptyFile);
686
687 for (DWORD i = 0; i < pcd->cFilePaths; ++i)
688 {
689 ReleaseStr(pcd->prgFiles[i].pwzSourcePath);
690 ReleaseMem(pcd->prgFiles[i].pmfHash);
691 }
692 ReleaseMem(pcd->prgFiles);
693 ReleaseMem(pcd->prgDuplicates);
694
695 ReleaseMem(pcd);
696 }
697}
698
699/********************************************************************
700 SmartCab functions
701
702********************************************************************/
703
704static HRESULT CheckForDuplicateFile(
705 __in CABC_DATA *pcd,
706 __out CABC_FILE **ppcf,
707 __in LPCWSTR wzFileName,
708 __in PMSIFILEHASHINFO *ppmfHash,
709 __in LONGLONG llFileSize
710 )
711{
712 DWORD i;
713 HRESULT hr = S_OK;
714 UINT er = ERROR_SUCCESS;
715
716 CabcExitOnNull(ppcf, hr, E_INVALIDARG, "No file structure sent while checking for duplicate file");
717 CabcExitOnNull(ppmfHash, hr, E_INVALIDARG, "No file hash structure pointer sent while checking for duplicate file");
718
719 *ppcf = NULL; // By default, we'll set our output to NULL
720
721 hr = DictGetValue(pcd->shDictHandle, wzFileName, reinterpret_cast<void **>(ppcf));
722 // If we found it in the hash of previously added source paths, return our match immediately
723 if (SUCCEEDED(hr))
724 {
725 ExitFunction1(hr = S_OK);
726 }
727 else if (E_NOTFOUND == hr)
728 {
729 hr = S_OK;
730 }
731 CabcExitOnFailure(hr, "Failed while searching for file in dictionary of previously added files");
732
733 for (i = 0; i < pcd->cFilePaths; ++i)
734 {
735 // If two files have the same size, use hashing to check if they're a match
736 if (llFileSize == pcd->prgFiles[i].llFileSize)
737 {
738 // If pcd->prgFiles[i], our potential match, hasn't been hashed yet, hash it
739 if (pcd->prgFiles[i].pmfHash == NULL)
740 {
741 pcd->prgFiles[i].pmfHash = (PMSIFILEHASHINFO)MemAlloc(sizeof(MSIFILEHASHINFO), FALSE);
742 CabcExitOnNull(pcd->prgFiles[i].pmfHash, hr, E_OUTOFMEMORY, "Failed to allocate memory for candidate duplicate file's MSI file hash");
743
744 pcd->prgFiles[i].pmfHash->dwFileHashInfoSize = sizeof(MSIFILEHASHINFO);
745 er = ::MsiGetFileHashW(pcd->prgFiles[i].pwzSourcePath, 0, pcd->prgFiles[i].pmfHash);
746 CabcExitOnWin32Error(er, hr, "Failed while getting MSI file hash of candidate duplicate file: %ls", pcd->prgFiles[i].pwzSourcePath);
747 }
748
749 // If our own file hasn't yet been hashed, hash it
750 if (NULL == *ppmfHash)
751 {
752 *ppmfHash = (PMSIFILEHASHINFO)MemAlloc(sizeof(MSIFILEHASHINFO), FALSE);
753 CabcExitOnNull(*ppmfHash, hr, E_OUTOFMEMORY, "Failed to allocate memory for file's MSI file hash");
754
755 (*ppmfHash)->dwFileHashInfoSize = sizeof(MSIFILEHASHINFO);
756 er = ::MsiGetFileHashW(wzFileName, 0, *ppmfHash);
757 CabcExitOnWin32Error(er, hr, "Failed while getting MSI file hash of file: %ls", pcd->prgFiles[i].pwzSourcePath);
758 }
759
760 // If the two file hashes are both of the expected size, and they match, we've got a match, so return it!
761 if (pcd->prgFiles[i].pmfHash->dwFileHashInfoSize == (*ppmfHash)->dwFileHashInfoSize &&
762 sizeof(MSIFILEHASHINFO) == (*ppmfHash)->dwFileHashInfoSize &&
763 pcd->prgFiles[i].pmfHash->dwData[0] == (*ppmfHash)->dwData[0] &&
764 pcd->prgFiles[i].pmfHash->dwData[1] == (*ppmfHash)->dwData[1] &&
765 pcd->prgFiles[i].pmfHash->dwData[2] == (*ppmfHash)->dwData[2] &&
766 pcd->prgFiles[i].pmfHash->dwData[3] == (*ppmfHash)->dwData[3])
767 {
768 *ppcf = pcd->prgFiles + i;
769 ExitFunction1(hr = S_OK);
770 }
771 }
772 }
773
774LExit:
775
776 return hr;
777}
778
779
780static HRESULT AddDuplicateFile(
781 __in CABC_DATA *pcd,
782 __in DWORD dwFileArrayIndex,
783 __in_z LPCWSTR wzSourcePath,
784 __in_opt LPCWSTR wzToken,
785 __in DWORD dwDuplicateCabFileIndex
786 )
787{
788 HRESULT hr = S_OK;
789 LPVOID pv = NULL;
790
791 // Ensure there is enough memory to store this duplicate file index.
792 if (pcd->cDuplicates == pcd->cMaxDuplicates)
793 {
794 pcd->cMaxDuplicates += 20; // grow by a reasonable number (20 is reasonable, right?)
795 size_t cbDuplicates = 0;
796
797 hr = ::SizeTMult(pcd->cMaxDuplicates, sizeof(CABC_DUPLICATEFILE), &cbDuplicates);
798 CabcExitOnFailure(hr, "Maximum allocation exceeded.");
799
800 if (pcd->cDuplicates)
801 {
802 pv = MemReAlloc(pcd->prgDuplicates, cbDuplicates, FALSE);
803 CabcExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to reallocate memory for duplicate file.");
804 }
805 else
806 {
807 pv = MemAlloc(cbDuplicates, FALSE);
808 CabcExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to allocate memory for duplicate file.");
809 }
810
811 ZeroMemory(reinterpret_cast<BYTE*>(pv) + (pcd->cDuplicates * sizeof(CABC_DUPLICATEFILE)), (pcd->cMaxDuplicates - pcd->cDuplicates) * sizeof(CABC_DUPLICATEFILE));
812
813 pcd->prgDuplicates = static_cast<CABC_DUPLICATEFILE*>(pv);
814 pv = NULL;
815 }
816
817 // Store the duplicate file index.
818 pcd->prgDuplicates[pcd->cDuplicates].dwFileArrayIndex = dwFileArrayIndex;
819 pcd->prgDuplicates[pcd->cDuplicates].dwDuplicateCabFileIndex = dwDuplicateCabFileIndex;
820 pcd->prgFiles[dwFileArrayIndex].fHasDuplicates = TRUE; // Mark original file as having duplicates
821
822 hr = StrAllocString(&pcd->prgDuplicates[pcd->cDuplicates].pwzSourcePath, wzSourcePath, 0);
823 CabcExitOnFailure(hr, "Failed to copy duplicate file path: %ls", wzSourcePath);
824
825 if (wzToken && *wzToken)
826 {
827 hr = StrAllocString(&pcd->prgDuplicates[pcd->cDuplicates].pwzToken, wzToken, 0);
828 CabcExitOnFailure(hr, "Failed to copy duplicate file token: %ls", wzToken);
829 }
830
831 ++pcd->cDuplicates;
832
833LExit:
834 ReleaseMem(pv);
835 return hr;
836}
837
838
839static HRESULT AddNonDuplicateFile(
840 __in CABC_DATA *pcd,
841 __in LPCWSTR wzFile,
842 __in_opt LPCWSTR wzToken,
843 __in_opt const MSIFILEHASHINFO* pmfHash,
844 __in LONGLONG llFileSize,
845 __in DWORD dwCabFileIndex
846 )
847{
848 HRESULT hr = S_OK;
849 LPVOID pv = NULL;
850
851 // Ensure there is enough memory to store this file index.
852 if (pcd->cFilePaths == pcd->cMaxFilePaths)
853 {
854 pcd->cMaxFilePaths += 100; // grow by a reasonable number (100 is reasonable, right?)
855 size_t cbFilePaths = 0;
856
857 hr = ::SizeTMult(pcd->cMaxFilePaths, sizeof(CABC_FILE), &cbFilePaths);
858 CabcExitOnFailure(hr, "Maximum allocation exceeded.");
859
860 pv = MemReAlloc(pcd->prgFiles, cbFilePaths, FALSE);
861 CabcExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to reallocate memory for file.");
862
863 ZeroMemory(reinterpret_cast<BYTE*>(pv) + (pcd->cFilePaths * sizeof(CABC_FILE)), (pcd->cMaxFilePaths - pcd->cFilePaths) * sizeof(CABC_FILE));
864
865 pcd->prgFiles = static_cast<CABC_FILE*>(pv);
866 pv = NULL;
867 }
868
869 // Store the file index information.
870 // TODO: add this to a sorted list so we can do a binary search later.
871 CABC_FILE *pcf = pcd->prgFiles + pcd->cFilePaths;
872 pcf->dwCabFileIndex = dwCabFileIndex;
873 pcf->llFileSize = llFileSize;
874
875 if (pmfHash && sizeof(MSIFILEHASHINFO) == pmfHash->dwFileHashInfoSize)
876 {
877 pcf->pmfHash = (PMSIFILEHASHINFO)MemAlloc(sizeof(MSIFILEHASHINFO), FALSE);
878 CabcExitOnNull(pcf->pmfHash, hr, E_OUTOFMEMORY, "Failed to allocate memory for individual file's MSI file hash");
879
880 pcf->pmfHash->dwFileHashInfoSize = sizeof(MSIFILEHASHINFO);
881 pcf->pmfHash->dwData[0] = pmfHash->dwData[0];
882 pcf->pmfHash->dwData[1] = pmfHash->dwData[1];
883 pcf->pmfHash->dwData[2] = pmfHash->dwData[2];
884 pcf->pmfHash->dwData[3] = pmfHash->dwData[3];
885 }
886
887 hr = StrAllocString(&pcf->pwzSourcePath, wzFile, 0);
888 CabcExitOnFailure(hr, "Failed to copy file path: %ls", wzFile);
889
890 if (wzToken && *wzToken)
891 {
892 hr = StrAllocString(&pcf->pwzToken, wzToken, 0);
893 CabcExitOnFailure(hr, "Failed to copy file token: %ls", wzToken);
894 }
895
896 ++pcd->cFilePaths;
897
898 hr = DictAddValue(pcd->shDictHandle, pcf);
899 CabcExitOnFailure(hr, "Failed to add file to dictionary of added files");
900
901LExit:
902 ReleaseMem(pv);
903 return hr;
904}
905
906
907static HRESULT UpdateDuplicateFiles(
908 __in const CABC_DATA *pcd
909 )
910{
911 HRESULT hr = S_OK;
912 DWORD cbCabinet = 0;
913 LARGE_INTEGER liCabinetSize = { };
914 HANDLE hCabinet = INVALID_HANDLE_VALUE;
915 HANDLE hCabinetMapping = NULL;
916 LPVOID pv = NULL;
917 MS_CABINET_HEADER *pCabinetHeader = NULL;
918
919 hCabinet = ::CreateFileW(pcd->wzCabinetPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
920 if (INVALID_HANDLE_VALUE == hCabinet)
921 {
922 CabcExitWithLastError(hr, "Failed to open cabinet: %ls", pcd->wzCabinetPath);
923 }
924
925 // Shouldn't need more than 16 MB to get the whole cabinet header into memory so use that as
926 // the upper bound for the memory map.
927 if (!::GetFileSizeEx(hCabinet, &liCabinetSize))
928 {
929 CabcExitWithLastError(hr, "Failed to get size of cabinet: %ls", pcd->wzCabinetPath);
930 }
931
932 if (0 == liCabinetSize.HighPart && liCabinetSize.LowPart < MAX_CABINET_HEADER_SIZE)
933 {
934 cbCabinet = liCabinetSize.LowPart;
935 }
936 else
937 {
938 cbCabinet = MAX_CABINET_HEADER_SIZE;
939 }
940
941 // CreateFileMapping() returns NULL on failure, not INVALID_HANDLE_VALUE
942 hCabinetMapping = ::CreateFileMappingW(hCabinet, NULL, PAGE_READWRITE | SEC_COMMIT, 0, cbCabinet, NULL);
943 if (NULL == hCabinetMapping || INVALID_HANDLE_VALUE == hCabinetMapping)
944 {
945 CabcExitWithLastError(hr, "Failed to memory map cabinet file: %ls", pcd->wzCabinetPath);
946 }
947
948 pv = ::MapViewOfFile(hCabinetMapping, FILE_MAP_WRITE, 0, 0, 0);
949 CabcExitOnNullWithLastError(pv, hr, "Failed to map view of cabinet file: %ls", pcd->wzCabinetPath);
950
951 pCabinetHeader = static_cast<MS_CABINET_HEADER*>(pv);
952
953 for (DWORD i = 0; i < pcd->cDuplicates; ++i)
954 {
955 const CABC_DUPLICATEFILE *pDuplicateFile = pcd->prgDuplicates + i;
956
957 hr = DuplicateFile(pCabinetHeader, pcd, pDuplicateFile);
958 CabcExitOnFailure(hr, "Failed to find cabinet file items at index: %d and %d", pDuplicateFile->dwFileArrayIndex, pDuplicateFile->dwDuplicateCabFileIndex);
959 }
960
961LExit:
962 if (pv)
963 {
964 ::UnmapViewOfFile(pv);
965 }
966 if (hCabinetMapping)
967 {
968 ::CloseHandle(hCabinetMapping);
969 }
970 ReleaseFileHandle(hCabinet);
971
972 return hr;
973}
974
975
976static HRESULT DuplicateFile(
977 __in MS_CABINET_HEADER *pHeader,
978 __in const CABC_DATA *pcd,
979 __in const CABC_DUPLICATEFILE *pDuplicate
980 )
981{
982 HRESULT hr = S_OK;
983 BYTE *pbHeader = reinterpret_cast<BYTE*>(pHeader);
984 BYTE* pbItem = pbHeader + pHeader->coffFiles;
985 const MS_CABINET_ITEM *pOriginalItem = NULL;
986 MS_CABINET_ITEM *pDuplicateItem = NULL;
987
988 if (pHeader->cFiles <= pcd->prgFiles[pDuplicate->dwFileArrayIndex].dwCabFileIndex ||
989 pHeader->cFiles <= pDuplicate->dwDuplicateCabFileIndex ||
990 pDuplicate->dwDuplicateCabFileIndex <= pcd->prgFiles[pDuplicate->dwFileArrayIndex].dwCabFileIndex)
991 {
992 hr = E_UNEXPECTED;
993 CabcExitOnFailure(hr, "Unexpected duplicate file indices, header cFiles: %d, file index: %d, duplicate index: %d", pHeader->cFiles, pcd->prgFiles[pDuplicate->dwFileArrayIndex].dwCabFileIndex, pDuplicate->dwDuplicateCabFileIndex);
994 }
995
996 // Step through each cabinet items until we get to the original
997 // file's index. Notice that the name of the cabinet item is
998 // appended to the end of the MS_CABINET_INFO, that's why we can't
999 // index straight to the data we want.
1000 for (DWORD i = 0; i < pcd->prgFiles[pDuplicate->dwFileArrayIndex].dwCabFileIndex; ++i)
1001 {
1002 LPCSTR szItemName = reinterpret_cast<LPCSTR>(pbItem + sizeof(MS_CABINET_ITEM));
1003 pbItem = pbItem + sizeof(MS_CABINET_ITEM) + lstrlenA(szItemName) + 1;
1004 }
1005
1006 pOriginalItem = reinterpret_cast<const MS_CABINET_ITEM*>(pbItem);
1007
1008 // Now pick up where we left off after the original file's index
1009 // was found and loop until we find the duplicate file's index.
1010 for (DWORD i = pcd->prgFiles[pDuplicate->dwFileArrayIndex].dwCabFileIndex; i < pDuplicate->dwDuplicateCabFileIndex; ++i)
1011 {
1012 LPCSTR szItemName = reinterpret_cast<LPCSTR>(pbItem + sizeof(MS_CABINET_ITEM));
1013 pbItem = pbItem + sizeof(MS_CABINET_ITEM) + lstrlenA(szItemName) + 1;
1014 }
1015
1016 pDuplicateItem = reinterpret_cast<MS_CABINET_ITEM*>(pbItem);
1017
1018 if (0 != pDuplicateItem->cbFile)
1019 {
1020 hr = E_UNEXPECTED;
1021 CabcExitOnFailure(hr, "Failed because duplicate file does not have a file size of zero: %d", pDuplicateItem->cbFile);
1022 }
1023
1024 pDuplicateItem->cbFile = pOriginalItem->cbFile;
1025 pDuplicateItem->uoffFolderStart = pOriginalItem->uoffFolderStart;
1026 pDuplicateItem->iFolder = pOriginalItem->iFolder;
1027 // Note: we do *not* duplicate the date/time and attributes metadata from
1028 // the original item to the duplicate. The following lines are commented
1029 // so people are not tempted to put them back.
1030 //pDuplicateItem->date = pOriginalItem->date;
1031 //pDuplicateItem->time = pOriginalItem->time;
1032 //pDuplicateItem->attribs = pOriginalItem->attribs;
1033
1034LExit:
1035 return hr;
1036}
1037
1038
1039static HRESULT UtcFileTimeToLocalDosDateTime(
1040 __in const FILETIME* pFileTime,
1041 __out USHORT* pDate,
1042 __out USHORT* pTime
1043 )
1044{
1045 HRESULT hr = S_OK;
1046 FILETIME ftLocal = { };
1047
1048 if (!::FileTimeToLocalFileTime(pFileTime, &ftLocal))
1049 {
1050 CabcExitWithLastError(hr, "Filed to convert file time to local file time.");
1051 }
1052
1053 if (!::FileTimeToDosDateTime(&ftLocal, pDate, pTime))
1054 {
1055 CabcExitWithLastError(hr, "Filed to convert file time to DOS date time.");
1056 }
1057
1058LExit:
1059 return hr;
1060}
1061
1062
1063/********************************************************************
1064 FCI callback functions
1065
1066*********************************************************************/
1067static __callback int DIAMONDAPI CabCFilePlaced(
1068 __in PCCAB pccab,
1069 __in_z PSTR szFile,
1070 __in long cbFile,
1071 __in BOOL fContinuation,
1072 __inout_bcount(CABC_HANDLE_BYTES) void *pv
1073 )
1074{
1075 UNREFERENCED_PARAMETER(pccab);
1076 UNREFERENCED_PARAMETER(szFile);
1077 UNREFERENCED_PARAMETER(cbFile);
1078 UNREFERENCED_PARAMETER(fContinuation);
1079 UNREFERENCED_PARAMETER(pv);
1080 return 0;
1081}
1082
1083
1084static __callback void * DIAMONDAPI CabCAlloc(
1085 __in ULONG cb
1086 )
1087{
1088 return MemAlloc(cb, FALSE);
1089}
1090
1091
1092static __callback void DIAMONDAPI CabCFree(
1093 __out_bcount(CABC_HANDLE_BYTES) void *pv
1094 )
1095{
1096 MemFree(pv);
1097}
1098
1099static __callback INT_PTR DIAMONDAPI CabCOpen(
1100 __in_z PSTR pszFile,
1101 __in int oflag,
1102 __in int pmode,
1103 __out int *err,
1104 __inout_bcount(CABC_HANDLE_BYTES) void *pv
1105 )
1106{
1107 CABC_DATA *pcd = reinterpret_cast<CABC_DATA*>(pv);
1108 HRESULT hr = S_OK;
1109 INT_PTR pFile = -1;
1110 DWORD dwAccess = 0;
1111 DWORD dwDisposition = 0;
1112 DWORD dwAttributes = 0;
1113
1114 //
1115 // Translate flags for CreateFile
1116 //
1117 if (oflag & _O_CREAT)
1118 {
1119 if (pmode == _S_IREAD)
1120 dwAccess |= GENERIC_READ;
1121 else if (pmode == _S_IWRITE)
1122 dwAccess |= GENERIC_WRITE;
1123 else if (pmode == (_S_IWRITE | _S_IREAD))
1124 dwAccess |= GENERIC_READ | GENERIC_WRITE;
1125
1126 if (oflag & _O_SHORT_LIVED)
1127 dwDisposition = FILE_ATTRIBUTE_TEMPORARY;
1128 else if (oflag & _O_TEMPORARY)
1129 dwAttributes |= FILE_FLAG_DELETE_ON_CLOSE;
1130 else if (oflag & _O_EXCL)
1131 dwDisposition = CREATE_NEW;
1132 }
1133 if (oflag & _O_TRUNC)
1134 dwDisposition = CREATE_ALWAYS;
1135
1136 if (!dwAccess)
1137 dwAccess = GENERIC_READ;
1138 if (!dwDisposition)
1139 dwDisposition = OPEN_EXISTING;
1140 if (!dwAttributes)
1141 dwAttributes = FILE_ATTRIBUTE_NORMAL;
1142
1143 // Check to see if we were passed the magic character that says 'Unicode string follows'.
1144 if (pszFile && CABC_MAGIC_UNICODE_STRING_MARKER == *pszFile)
1145 {
1146 pFile = reinterpret_cast<INT_PTR>(::CreateFileW(reinterpret_cast<LPCWSTR>(pszFile + 1), dwAccess, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, dwDisposition, dwAttributes, NULL));
1147 }
1148 else
1149 {
1150#pragma prefast(push)
1151#pragma prefast(disable:25068) // We intentionally don't use the unicode API here
1152 pFile = reinterpret_cast<INT_PTR>(::CreateFileA(pszFile, dwAccess, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, dwDisposition, dwAttributes, NULL));
1153#pragma prefast(pop)
1154 }
1155
1156 if (INVALID_HANDLE_VALUE == reinterpret_cast<HANDLE>(pFile))
1157 {
1158 CabcExitOnLastError(hr, "failed to open file: %s", pszFile);
1159 }
1160
1161LExit:
1162 if (FAILED(hr))
1163 pcd->hrLastError = *err = hr;
1164
1165 return FAILED(hr) ? -1 : pFile;
1166}
1167
1168
1169static __callback UINT FAR DIAMONDAPI CabCRead(
1170 __in INT_PTR hf,
1171 __out_bcount(cb) void FAR *memory,
1172 __in UINT cb,
1173 __out int *err,
1174 __inout_bcount(CABC_HANDLE_BYTES) void *pv
1175 )
1176{
1177 CABC_DATA *pcd = reinterpret_cast<CABC_DATA*>(pv);
1178 HRESULT hr = S_OK;
1179 DWORD cbRead = 0;
1180
1181 CabcExitOnNull(hf, *err, E_INVALIDARG, "Failed to read during cabinet extraction because no file handle was provided");
1182 if (!::ReadFile(reinterpret_cast<HANDLE>(hf), memory, cb, &cbRead, NULL))
1183 {
1184 *err = ::GetLastError();
1185 CabcExitOnLastError(hr, "failed to read during cabinet extraction");
1186 }
1187
1188LExit:
1189 if (FAILED(hr))
1190 {
1191 pcd->hrLastError = *err = hr;
1192 }
1193
1194 return FAILED(hr) ? -1 : cbRead;
1195}
1196
1197
1198static __callback UINT FAR DIAMONDAPI CabCWrite(
1199 __in INT_PTR hf,
1200 __in_bcount(cb) void FAR *memory,
1201 __in UINT cb,
1202 __out int *err,
1203 __inout_bcount(CABC_HANDLE_BYTES) void *pv
1204 )
1205{
1206 CABC_DATA *pcd = reinterpret_cast<CABC_DATA*>(pv);
1207 HRESULT hr = S_OK;
1208 DWORD cbWrite = 0;
1209
1210 CabcExitOnNull(hf, *err, E_INVALIDARG, "Failed to write during cabinet extraction because no file handle was provided");
1211 if (!::WriteFile(reinterpret_cast<HANDLE>(hf), memory, cb, &cbWrite, NULL))
1212 {
1213 *err = ::GetLastError();
1214 CabcExitOnLastError(hr, "failed to write during cabinet extraction");
1215 }
1216
1217LExit:
1218 if (FAILED(hr))
1219 pcd->hrLastError = *err = hr;
1220
1221 return FAILED(hr) ? -1 : cbWrite;
1222}
1223
1224
1225static __callback long FAR DIAMONDAPI CabCSeek(
1226 __in INT_PTR hf,
1227 __in long dist,
1228 __in int seektype,
1229 __out int *err,
1230 __inout_bcount(CABC_HANDLE_BYTES) void *pv
1231 )
1232{
1233 CABC_DATA *pcd = reinterpret_cast<CABC_DATA*>(pv);
1234 HRESULT hr = S_OK;
1235 DWORD dwMoveMethod;
1236 LONG lMove = 0;
1237
1238 switch (seektype)
1239 {
1240 case 0: // SEEK_SET
1241 dwMoveMethod = FILE_BEGIN;
1242 break;
1243 case 1: /// SEEK_CUR
1244 dwMoveMethod = FILE_CURRENT;
1245 break;
1246 case 2: // SEEK_END
1247 dwMoveMethod = FILE_END;
1248 break;
1249 default :
1250 dwMoveMethod = 0;
1251 hr = E_UNEXPECTED;
1252 CabcExitOnFailure(hr, "unexpected seektype in FCISeek(): %d", seektype);
1253 }
1254
1255 // SetFilePointer returns -1 if it fails (this will cause FDI to quit with an FDIERROR_USER_ABORT error.
1256 // (Unless this happens while working on a cabinet, in which case FDI returns FDIERROR_CORRUPT_CABINET)
1257 // Todo: update these comments for FCI (are they accurate for FCI as well?)
1258 lMove = ::SetFilePointer(reinterpret_cast<HANDLE>(hf), dist, NULL, dwMoveMethod);
1259 if (DWORD_MAX == lMove)
1260 {
1261 *err = ::GetLastError();
1262 CabcExitOnLastError(hr, "failed to move file pointer %d bytes", dist);
1263 }
1264
1265LExit:
1266 if (FAILED(hr))
1267 {
1268 pcd->hrLastError = *err = hr;
1269 }
1270
1271 return FAILED(hr) ? -1 : lMove;
1272}
1273
1274
1275static __callback int FAR DIAMONDAPI CabCClose(
1276 __in INT_PTR hf,
1277 __out int *err,
1278 __inout_bcount(CABC_HANDLE_BYTES) void *pv
1279 )
1280{
1281 CABC_DATA *pcd = reinterpret_cast<CABC_DATA*>(pv);
1282 HRESULT hr = S_OK;
1283
1284 if (!::CloseHandle(reinterpret_cast<HANDLE>(hf)))
1285 {
1286 *err = ::GetLastError();
1287 CabcExitOnLastError(hr, "failed to close file during cabinet extraction");
1288 }
1289
1290LExit:
1291 if (FAILED(hr))
1292 {
1293 pcd->hrLastError = *err = hr;
1294 }
1295
1296 return FAILED(hr) ? -1 : 0;
1297}
1298
1299static __callback int DIAMONDAPI CabCDelete(
1300 __in_z PSTR szFile,
1301 __out int *err,
1302 __inout_bcount(CABC_HANDLE_BYTES) void *pv
1303 )
1304{
1305 UNREFERENCED_PARAMETER(err);
1306 UNREFERENCED_PARAMETER(pv);
1307
1308#pragma prefast(push)
1309#pragma prefast(disable:25068) // We intentionally don't use the unicode API here
1310 ::DeleteFileA(szFile);
1311#pragma prefast(pop)
1312
1313 return 0;
1314}
1315
1316
1317__success(return != FALSE)
1318static __callback BOOL DIAMONDAPI CabCGetTempFile(
1319 __out_bcount_z(cbFile) char *szFile,
1320 __in int cbFile,
1321 __inout_bcount(CABC_HANDLE_BYTES) void *pv
1322 )
1323{
1324 CABC_DATA *pcd = reinterpret_cast<CABC_DATA*>(pv);
1325 static volatile DWORD dwIndex = 0;
1326
1327 HRESULT hr = S_OK;
1328 char szTempPath[MAX_PATH] = { };
1329 DWORD cchTempPath = MAX_PATH;
1330 DWORD dwProcessId = ::GetCurrentProcessId();
1331 HANDLE hTempFile = INVALID_HANDLE_VALUE;
1332
1333 if (MAX_PATH < ::GetTempPathA(cchTempPath, szTempPath))
1334 {
1335 CabcExitWithLastError(hr, "Failed to get temp path during cabinet creation.");
1336 }
1337
1338 for (DWORD i = 0; i < DWORD_MAX; ++i)
1339 {
1340 LONG dwTempIndex = ::InterlockedIncrement(reinterpret_cast<volatile LONG*>(&dwIndex));
1341
1342 hr = ::StringCbPrintfA(szFile, cbFile, "%s\\%08x.%03x", szTempPath, dwTempIndex, dwProcessId);
1343 CabcExitOnFailure(hr, "failed to format log file path.");
1344
1345 hTempFile = ::CreateFileA(szFile, 0, FILE_SHARE_DELETE, NULL, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1346 if (INVALID_HANDLE_VALUE != hTempFile)
1347 {
1348 // we found one that doesn't exist
1349 hr = S_OK;
1350 break;
1351 }
1352 else
1353 {
1354 hr = E_FAIL; // this file was taken so be pessimistic and assume we're not going to find one.
1355 }
1356 }
1357 CabcExitOnFailure(hr, "failed to find temporary file.");
1358
1359LExit:
1360 ReleaseFileHandle(hTempFile);
1361
1362 if (FAILED(hr))
1363 {
1364 pcd->hrLastError = hr;
1365 }
1366
1367 return FAILED(hr)? FALSE : TRUE;
1368}
1369
1370
1371__success(return != FALSE)
1372static __callback BOOL DIAMONDAPI CabCGetNextCabinet(
1373 __in PCCAB pccab,
1374 __in ULONG ul,
1375 __out_bcount(CABC_HANDLE_BYTES) void *pv
1376 )
1377{
1378 UNREFERENCED_PARAMETER(ul);
1379
1380 // Construct next cab names like cab1a.cab, cab1b.cab, cab1c.cab, ........
1381 CABC_DATA *pcd = reinterpret_cast<CABC_DATA*>(pv);
1382 HRESULT hr = S_OK;
1383 LPWSTR pwzFileToken = NULL;
1384 WCHAR wzNewCabName[MAX_PATH] = L"";
1385
1386 if (pccab->iCab == 1)
1387 {
1388 pcd->wzFirstCabinetName[0] = '\0';
1389 LPCWSTR pwzCabinetName = FileFromPath(pcd->wzCabinetPath);
1390 size_t len = wcsnlen(pwzCabinetName, sizeof(pwzCabinetName));
1391 if (len > 4)
1392 {
1393 len -= 4; // remove Extention ".cab" of 8.3 Format
1394 }
1395 hr = ::StringCchCatNW(pcd->wzFirstCabinetName, countof(pcd->wzFirstCabinetName), pwzCabinetName, len);
1396 CabcExitOnFailure(hr, "Failed to remove extension to create next Cabinet File Name");
1397 }
1398
1399 const int nAlphabets = 26; // Number of Alphabets from a to z
1400 if (pccab->iCab <= nAlphabets)
1401 {
1402 // Construct next cab names like cab1a.cab, cab1b.cab, cab1c.cab, ........
1403 hr = ::StringCchPrintfA(pccab->szCab, sizeof(pccab->szCab), "%ls%c.cab", pcd->wzFirstCabinetName, char(((int)('a') - 1) + pccab->iCab));
1404 CabcExitOnFailure(hr, "Failed to create next Cabinet File Name");
1405 hr = ::StringCchPrintfW(wzNewCabName, countof(wzNewCabName), L"%ls%c.cab", pcd->wzFirstCabinetName, WCHAR(((int)('a') - 1) + pccab->iCab));
1406 CabcExitOnFailure(hr, "Failed to create next Cabinet File Name");
1407 }
1408 else if (pccab->iCab <= nAlphabets*nAlphabets)
1409 {
1410 // Construct next cab names like cab1aa.cab, cab1ab.cab, cab1ac.cab, ......, cabaz.cab, cabaa.cab, cabab.cab, cabac.cab, ......
1411 int char2 = (pccab->iCab) % nAlphabets;
1412 int char1 = (pccab->iCab - char2)/nAlphabets;
1413 if (char2 == 0)
1414 {
1415 // e.g. when iCab = 52, we want az
1416 char2 = nAlphabets; // Second char must be 'z' in this case
1417 char1--; // First Char must be decremented by 1
1418 }
1419 hr = ::StringCchPrintfA(pccab->szCab, sizeof(pccab->szCab), "%ls%c%c.cab", pcd->wzFirstCabinetName, char(((int)('a') - 1) + char1), char(((int)('a') - 1) + char2));
1420 CabcExitOnFailure(hr, "Failed to create next Cabinet File Name");
1421 hr = ::StringCchPrintfW(wzNewCabName, countof(wzNewCabName), L"%ls%c%c.cab", pcd->wzFirstCabinetName, WCHAR(((int)('a') - 1) + char1), WCHAR(((int)('a') - 1) + char2));
1422 CabcExitOnFailure(hr, "Failed to create next Cabinet File Name");
1423 }
1424 else
1425 {
1426 hr = DISP_E_BADINDEX; // Value 0x8002000B stands for Invalid index.
1427 CabcExitOnFailure(hr, "Cannot Split Cabinet more than 26*26 = 676 times. Failed to create next Cabinet File Name");
1428 }
1429
1430 // Callback from PFNFCIGETNEXTCABINET CabCGetNextCabinet method
1431 if(pcd->fileSplitCabNamesCallback != 0)
1432 {
1433 // In following if/else block, getting the Token for the First File in the Cabinets that are getting Split
1434 // This code will need updation if we need to send all file tokens for the splitting Cabinets
1435 if (pcd->prgFiles[0].pwzToken)
1436 {
1437 pwzFileToken = pcd->prgFiles[0].pwzToken;
1438 }
1439 else
1440 {
1441 LPCWSTR wzSourcePath = pcd->prgFiles[0].pwzSourcePath;
1442 pwzFileToken = FileFromPath(wzSourcePath);
1443 }
1444
1445 // The call back to Binder to Add File Transfer for new Cab and add new Cab to Media table
1446 pcd->fileSplitCabNamesCallback(pcd->wzFirstCabinetName, wzNewCabName, pwzFileToken);
1447 }
1448
1449LExit:
1450 if (FAILED(hr))
1451 {
1452 // Returning False in case of error here as stated by Documentation, However It fails to Abort Cab Creation!!!
1453 // So Using separate check for pcd->hrLastError after ::FCIAddFile for Cabinet Splitting
1454 pcd->hrLastError = hr;
1455 return FALSE;
1456 }
1457 else
1458 {
1459 return TRUE;
1460 }
1461}
1462
1463
1464static __callback INT_PTR DIAMONDAPI CabCGetOpenInfo(
1465 __in_z PSTR pszName,
1466 __out USHORT *pdate,
1467 __out USHORT *ptime,
1468 __out USHORT *pattribs,
1469 __out int *err,
1470 __out_bcount(CABC_HANDLE_BYTES) void *pv
1471 )
1472{
1473 HRESULT hr = S_OK;
1474 CABC_INTERNAL_ADDFILEINFO* pFileInfo = reinterpret_cast<CABC_INTERNAL_ADDFILEINFO*>(pszName);
1475 LPCWSTR wzFile = NULL;
1476 DWORD cbFile = 0;
1477 LPSTR pszFilePlusMagic = NULL;
1478 DWORD cbFilePlusMagic = 0;
1479 WIN32_FILE_ATTRIBUTE_DATA fad = { };
1480 INT_PTR iResult = -1;
1481
1482 // If there is an empty file provided, use that as the source path to cab (since we
1483 // must be dealing with a duplicate file). Otherwise, use the source path you'd expect.
1484 wzFile = pFileInfo->wzEmptyPath ? pFileInfo->wzEmptyPath : pFileInfo->wzSourcePath;
1485 cbFile = (lstrlenW(wzFile) + 1) * sizeof(WCHAR);
1486
1487 // Convert the source file path into an Ansi string that our APIs will recognize as
1488 // a Unicode string (due to the magic character).
1489 cbFilePlusMagic = cbFile + 1; // add one for the magic.
1490 pszFilePlusMagic = reinterpret_cast<LPSTR>(MemAlloc(cbFilePlusMagic, TRUE));
1491
1492 *pszFilePlusMagic = CABC_MAGIC_UNICODE_STRING_MARKER;
1493 memcpy_s(pszFilePlusMagic + 1, cbFilePlusMagic - 1, wzFile, cbFile);
1494
1495 if (!::GetFileAttributesExW(pFileInfo->wzSourcePath, GetFileExInfoStandard, &fad))
1496 {
1497 CabcExitWithLastError(hr, "Failed to get file attributes on '%ls'.", pFileInfo->wzSourcePath);
1498 }
1499
1500 // Set the attributes but only allow the few attributes that CAB supports.
1501 *pattribs = static_cast<USHORT>(fad.dwFileAttributes) & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE);
1502
1503 hr = UtcFileTimeToLocalDosDateTime(&fad.ftLastWriteTime, pdate, ptime);
1504 if (FAILED(hr))
1505 {
1506 // NOTE: Changed this from ftLastWriteTime to ftCreationTime because of issues around how different OSs were
1507 // handling the access of the FILETIME structure and how it would fail conversion to DOS time if it wasn't
1508 // found. This would create further problems if the file was written to the CAB without this value. Windows
1509 // Installer would then fail to extract the file.
1510 hr = UtcFileTimeToLocalDosDateTime(&fad.ftCreationTime, pdate, ptime);
1511 CabcExitOnFailure(hr, "Filed to read a valid file time stucture on file '%s'.", pszName);
1512 }
1513
1514 iResult = CabCOpen(pszFilePlusMagic, _O_BINARY|_O_RDONLY, 0, err, pv);
1515
1516LExit:
1517 ReleaseMem(pszFilePlusMagic);
1518 if (FAILED(hr))
1519 {
1520 *err = (int)hr;
1521 }
1522
1523 return FAILED(hr) ? -1 : iResult;
1524}
1525
1526
1527static __callback long DIAMONDAPI CabCStatus(
1528 __in UINT ui,
1529 __in ULONG cb1,
1530 __in ULONG cb2,
1531 __inout_bcount(CABC_HANDLE_BYTES) void *pv
1532 )
1533{
1534 UNREFERENCED_PARAMETER(ui);
1535 UNREFERENCED_PARAMETER(cb1);
1536 UNREFERENCED_PARAMETER(cb2);
1537 UNREFERENCED_PARAMETER(pv);
1538 return 0;
1539}
diff --git a/src/libs/dutil/WixToolset.DUtil/cabutil.cpp b/src/libs/dutil/WixToolset.DUtil/cabutil.cpp
new file mode 100644
index 00000000..5d77e483
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/cabutil.cpp
@@ -0,0 +1,617 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define CabExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_CABUTIL, x, s, __VA_ARGS__)
8#define CabExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_CABUTIL, x, s, __VA_ARGS__)
9#define CabExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_CABUTIL, x, s, __VA_ARGS__)
10#define CabExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_CABUTIL, x, s, __VA_ARGS__)
11#define CabExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_CABUTIL, x, s, __VA_ARGS__)
12#define CabExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_CABUTIL, x, s, __VA_ARGS__)
13#define CabExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_CABUTIL, p, x, e, s, __VA_ARGS__)
14#define CabExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_CABUTIL, p, x, s, __VA_ARGS__)
15#define CabExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_CABUTIL, p, x, e, s, __VA_ARGS__)
16#define CabExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_CABUTIL, p, x, s, __VA_ARGS__)
17#define CabExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_CABUTIL, e, x, s, __VA_ARGS__)
18#define CabExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_CABUTIL, g, x, s, __VA_ARGS__)
19
20
21// external prototypes
22typedef BOOL (FAR DIAMONDAPI *PFNFDIDESTROY)(VOID*);
23typedef HFDI (FAR DIAMONDAPI *PFNFDICREATE)(PFNALLOC, PFNFREE, PFNOPEN, PFNREAD, PFNWRITE, PFNCLOSE, PFNSEEK, int, PERF);
24typedef BOOL (FAR DIAMONDAPI *PFNFDIISCABINET)(HFDI, INT_PTR, PFDICABINETINFO);
25typedef BOOL (FAR DIAMONDAPI *PFNFDICOPY)(HFDI, char *, char *, int, PFNFDINOTIFY, PFNFDIDECRYPT, void *);
26
27
28//
29// static globals
30//
31static HMODULE vhCabinetDll = NULL;
32
33static HFDI vhfdi = NULL;
34static PFNFDICREATE vpfnFDICreate = NULL;
35static PFNFDICOPY vpfnFDICopy = NULL;
36static PFNFDIISCABINET vpfnFDIIsCabinet = NULL;
37static PFNFDIDESTROY vpfnFDIDestroy = NULL;
38static ERF verf;
39
40static DWORD64 vdw64EmbeddedOffset = 0;
41
42//
43// structs
44//
45struct CAB_CALLBACK_STRUCT
46{
47 BOOL fStopExtracting; // flag set when no more files are needed
48 LPCWSTR pwzExtract; // file to extract ("*" means extract all)
49 LPCWSTR pwzExtractDir; // directory to extract files to
50
51 // possible user data
52 CAB_CALLBACK_PROGRESS pfnProgress;
53 LPVOID pvContext;
54};
55
56//
57// prototypes
58//
59static __callback LPVOID DIAMONDAPI CabExtractAlloc(__in DWORD dwSize);
60static __callback void DIAMONDAPI CabExtractFree(__in LPVOID pvData);
61static __callback INT_PTR FAR DIAMONDAPI CabExtractOpen(__in_z PSTR pszFile, __in int oflag, __in int pmode);
62static __callback UINT FAR DIAMONDAPI CabExtractRead(__in INT_PTR hf, __out void FAR *pv, __in UINT cb);
63static __callback UINT FAR DIAMONDAPI CabExtractWrite(__in INT_PTR hf, __in void FAR *pv, __in UINT cb);
64static __callback int FAR DIAMONDAPI CabExtractClose(__in INT_PTR hf);
65static __callback long FAR DIAMONDAPI CabExtractSeek(__in INT_PTR hf, __in long dist, __in int seektype);
66static __callback INT_PTR DIAMONDAPI CabExtractCallback(__in FDINOTIFICATIONTYPE iNotification, __inout FDINOTIFICATION *pFDINotify);
67static HRESULT DAPI CabOperation(__in LPCWSTR wzCabinet, __in LPCWSTR wzExtractFile, __in_opt LPCWSTR wzExtractDir, __in_opt CAB_CALLBACK_PROGRESS pfnProgress, __in_opt LPVOID pvContext, __in_opt STDCALL_PFNFDINOTIFY pfnNotify, __in DWORD64 dw64EmbeddedOffset);
68
69static STDCALL_PFNFDINOTIFY v_pfnNetFx11Notify = NULL;
70
71
72inline HRESULT LoadCabinetDll()
73{
74 HRESULT hr = S_OK;
75 if (!vhCabinetDll)
76 {
77 hr = LoadSystemLibrary(L"cabinet.dll", &vhCabinetDll);
78 CabExitOnFailure(hr, "failed to load cabinet.dll");
79
80 // retrieve all address functions
81 vpfnFDICreate = reinterpret_cast<PFNFDICREATE>(::GetProcAddress(vhCabinetDll, "FDICreate"));
82 CabExitOnNullWithLastError(vpfnFDICreate, hr, "failed to import FDICreate from CABINET.DLL");
83 vpfnFDICopy = reinterpret_cast<PFNFDICOPY>(::GetProcAddress(vhCabinetDll, "FDICopy"));
84 CabExitOnNullWithLastError(vpfnFDICopy, hr, "failed to import FDICopy from CABINET.DLL");
85 vpfnFDIIsCabinet = reinterpret_cast<PFNFDIISCABINET>(::GetProcAddress(vhCabinetDll, "FDIIsCabinet"));
86 CabExitOnNullWithLastError(vpfnFDIIsCabinet, hr, "failed to import FDIIsCabinetfrom CABINET.DLL");
87 vpfnFDIDestroy = reinterpret_cast<PFNFDIDESTROY>(::GetProcAddress(vhCabinetDll, "FDIDestroy"));
88 CabExitOnNullWithLastError(vpfnFDIDestroy, hr, "failed to import FDIDestroyfrom CABINET.DLL");
89
90 vhfdi = vpfnFDICreate(CabExtractAlloc, CabExtractFree, CabExtractOpen, CabExtractRead, CabExtractWrite, CabExtractClose, CabExtractSeek, cpuUNKNOWN, &verf);
91 CabExitOnNull(vhfdi, hr, E_FAIL, "failed to initialize cabinet.dll");
92 }
93
94LExit:
95 if (FAILED(hr) && vhCabinetDll)
96 {
97 ::FreeLibrary(vhCabinetDll);
98 vhCabinetDll = NULL;
99 }
100
101 return hr;
102}
103
104
105static HANDLE OpenFileWithRetry(
106 __in LPCWSTR wzPath,
107 __in DWORD dwDesiredAccess,
108 __in DWORD dwCreationDisposition
109)
110{
111 HANDLE hFile = INVALID_HANDLE_VALUE;
112
113 for (DWORD i = 0; i < 30; ++i)
114 {
115 hFile = ::CreateFileW(wzPath, dwDesiredAccess, FILE_SHARE_READ, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
116 if (INVALID_HANDLE_VALUE != hFile)
117 {
118 break;
119 }
120
121 ::Sleep(100);
122 }
123
124 return hFile;
125}
126
127
128/********************************************************************
129 CabInitialize - initializes internal static variables
130
131********************************************************************/
132extern "C" HRESULT DAPI CabInitialize(
133 __in BOOL fDelayLoad
134 )
135{
136 HRESULT hr = S_OK;
137
138 if (!fDelayLoad)
139 {
140 hr = LoadCabinetDll();
141 CabExitOnFailure(hr, "failed to load CABINET.DLL");
142 }
143
144LExit:
145 return hr;
146}
147
148
149/********************************************************************
150 CabUninitialize - initializes internal static variables
151
152********************************************************************/
153extern "C" void DAPI CabUninitialize(
154 )
155{
156 if (vhfdi)
157 {
158 if (vpfnFDIDestroy)
159 {
160 vpfnFDIDestroy(vhfdi);
161 }
162 vhfdi = NULL;
163 }
164
165 vpfnFDICreate = NULL;
166 vpfnFDICopy =NULL;
167 vpfnFDIIsCabinet = NULL;
168 vpfnFDIDestroy = NULL;
169
170 if (vhCabinetDll)
171 {
172 ::FreeLibrary(vhCabinetDll);
173 vhCabinetDll = NULL;
174 }
175}
176
177/********************************************************************
178 CabEnumerate - list files inside cabinet
179
180 NOTE: wzCabinet must be full path to cabinet file
181 pfnNotify is callback function to get notified for each file
182 in the cabinet
183********************************************************************/
184extern "C" HRESULT DAPI CabEnumerate(
185 __in_z LPCWSTR wzCabinet,
186 __in_z LPCWSTR wzEnumerateFile,
187 __in STDCALL_PFNFDINOTIFY pfnNotify,
188 __in DWORD64 dw64EmbeddedOffset
189 )
190{
191 return CabOperation(wzCabinet, wzEnumerateFile, NULL, NULL, NULL, pfnNotify, dw64EmbeddedOffset);
192}
193
194/********************************************************************
195 CabExtract - extracts one or all files from a cabinet
196
197 NOTE: wzCabinet must be full path to cabinet file
198 wzExtractFile can be a single file id or "*" to extract all files
199 wzExttractDir must be normalized (end in a "\")
200 if pfnBeginFile is NULL pfnEndFile must be NULL and vice versa
201********************************************************************/
202extern "C" HRESULT DAPI CabExtract(
203 __in_z LPCWSTR wzCabinet,
204 __in_z LPCWSTR wzExtractFile,
205 __in_z LPCWSTR wzExtractDir,
206 __in_opt CAB_CALLBACK_PROGRESS pfnProgress,
207 __in_opt LPVOID pvContext,
208 __in DWORD64 dw64EmbeddedOffset
209 )
210{
211 return CabOperation(wzCabinet, wzExtractFile, wzExtractDir, pfnProgress, pvContext, NULL, dw64EmbeddedOffset);
212}
213
214//
215// private
216//
217/********************************************************************
218 FDINotify -- wrapper that converts call convention from __cdecl to __stdcall.
219
220 NOTE: Since netfx 1.1 supports only function pointers (delegates)
221 with __stdcall calling convention and cabinet api uses
222 __cdecl calling convention, we need this wrapper function.
223 netfx 2.0 will work with [UnmanagedFunctionPointer(CallingConvention.Cdecl)] attribute on the delegate.
224 TODO: remove this when upgrading to netfx 2.0.
225********************************************************************/
226static __callback INT_PTR DIAMONDAPI FDINotify(
227 __in FDINOTIFICATIONTYPE iNotification,
228 __inout FDINOTIFICATION *pFDINotify
229 )
230{
231 if (NULL != v_pfnNetFx11Notify)
232 {
233 return v_pfnNetFx11Notify(iNotification, pFDINotify);
234 }
235 else
236 {
237 return (INT_PTR)0;
238 }
239}
240
241
242/********************************************************************
243 CabOperation - helper function that enumerates or extracts files
244 from cabinet
245
246 NOTE: wzCabinet must be full path to cabinet file
247 wzExtractFile can be a single file id or "*" to extract all files
248 wzExttractDir must be normalized (end in a "\")
249 if pfnBeginFile is NULL pfnEndFile must be NULL and vice versa
250 pfnNotify is callback function to get notified for each file
251 in the cabinet. If it's NULL, files will be extracted.
252********************************************************************/
253static HRESULT DAPI CabOperation(
254 __in LPCWSTR wzCabinet,
255 __in LPCWSTR wzExtractFile,
256 __in_opt LPCWSTR wzExtractDir,
257 __in_opt CAB_CALLBACK_PROGRESS pfnProgress,
258 __in_opt LPVOID pvContext,
259 __in_opt STDCALL_PFNFDINOTIFY pfnNotify,
260 __in DWORD64 dw64EmbeddedOffset
261 )
262{
263 HRESULT hr = S_OK;
264 BOOL fResult;
265
266 LPWSTR sczCabinet = NULL;
267 LPWSTR pwz = NULL;
268 CHAR szCabDirectory[MAX_PATH * 4]; // Make sure these are big enough for UTF-8 strings
269 CHAR szCabFile[MAX_PATH * 4];
270
271 CAB_CALLBACK_STRUCT ccs;
272 PFNFDINOTIFY pfnFdiNotify;
273
274 //
275 // ensure the cabinet.dll is loaded
276 //
277 if (!vhfdi)
278 {
279 hr = LoadCabinetDll();
280 CabExitOnFailure(hr, "failed to load CABINET.DLL");
281 }
282
283 hr = StrAllocString(&sczCabinet, wzCabinet, 0);
284 CabExitOnFailure(hr, "Failed to make copy of cabinet name:%ls", wzCabinet);
285
286 //
287 // split the cabinet full path into directory and filename and convert to multi-byte (ick!)
288 //
289 pwz = FileFromPath(sczCabinet);
290 CabExitOnNull(pwz, hr, E_INVALIDARG, "failed to process cabinet path: %ls", wzCabinet);
291
292 if (!::WideCharToMultiByte(CP_UTF8, 0, pwz, -1, szCabFile, countof(szCabFile), NULL, NULL))
293 {
294 CabExitWithLastError(hr, "failed to convert cabinet filename to ASCII: %ls", pwz);
295 }
296
297 *pwz = '\0';
298
299 // If a full path was not provided, use the relative current directory.
300 if (wzCabinet == pwz)
301 {
302 hr = ::StringCchCopyA(szCabDirectory, countof(szCabDirectory), ".\\");
303 CabExitOnFailure(hr, "Failed to copy relative current directory as cabinet directory.");
304 }
305 else
306 {
307 if (!::WideCharToMultiByte(CP_UTF8, 0, sczCabinet, -1, szCabDirectory, countof(szCabDirectory), NULL, NULL))
308 {
309 CabExitWithLastError(hr, "failed to convert cabinet directory to ASCII: %ls", sczCabinet);
310 }
311 }
312
313 //
314 // iterate through files in cabinet extracting them to the callback function
315 //
316 ccs.fStopExtracting = FALSE;
317 ccs.pwzExtract = wzExtractFile;
318 ccs.pwzExtractDir = wzExtractDir;
319 ccs.pfnProgress = pfnProgress;
320 ccs.pvContext = pvContext;
321
322 vdw64EmbeddedOffset = dw64EmbeddedOffset;
323
324 // if pfnNotify is given, use it, otherwise use default callback
325 if (NULL == pfnNotify)
326 {
327 pfnFdiNotify = CabExtractCallback;
328 }
329 else
330 {
331 v_pfnNetFx11Notify = pfnNotify;
332 pfnFdiNotify = FDINotify;
333 }
334 fResult = vpfnFDICopy(vhfdi, szCabFile, szCabDirectory, 0, pfnFdiNotify, NULL, static_cast<void*>(&ccs));
335 if (!fResult && !ccs.fStopExtracting) // if something went wrong and it wasn't us just stopping the extraction, then return a failure
336 {
337 CabExitWithLastError(hr, "failed to extract cabinet file: %ls", sczCabinet);
338 }
339
340LExit:
341 ReleaseStr(sczCabinet);
342 v_pfnNetFx11Notify = NULL;
343
344 return hr;
345}
346
347/****************************************************************************
348 default extract routines
349
350****************************************************************************/
351static __callback LPVOID DIAMONDAPI CabExtractAlloc(__in DWORD dwSize)
352{
353 return MemAlloc(dwSize, FALSE);
354}
355
356
357static __callback void DIAMONDAPI CabExtractFree(__in LPVOID pvData)
358{
359 MemFree(pvData);
360}
361
362
363static __callback INT_PTR FAR DIAMONDAPI CabExtractOpen(__in_z PSTR pszFile, __in int oflag, __in int pmode)
364{
365 HRESULT hr = S_OK;
366 HANDLE hFile = INVALID_HANDLE_VALUE;
367 INT_PTR pFile = -1;
368 LPWSTR sczCabFile = NULL;
369
370 // if FDI asks for some unusual mode (in low memory situation it could ask for a scratch file) fail
371 if ((oflag != (/*_O_BINARY*/ 0x8000 | /*_O_RDONLY*/ 0x0000)) || (pmode != (_S_IREAD | _S_IWRITE)))
372 {
373 hr = E_OUTOFMEMORY;
374 CabExitOnFailure(hr, "FDI asked for a scratch file to be created, which is unsupported");
375 }
376
377 hr = StrAllocStringAnsi(&sczCabFile, pszFile, 0, CP_UTF8);
378 CabExitOnFailure(hr, "Failed to convert UTF8 cab file name to wide character string");
379
380 hFile = OpenFileWithRetry(sczCabFile, GENERIC_READ, OPEN_EXISTING);
381 if (INVALID_HANDLE_VALUE == hFile)
382 {
383 CabExitWithLastError(hr, "failed to open file: %ls", sczCabFile);
384 }
385
386 pFile = reinterpret_cast<INT_PTR>(hFile);
387
388 if (vdw64EmbeddedOffset)
389 {
390 hr = CabExtractSeek(pFile, 0, 0);
391 CabExitOnFailure(hr, "Failed to seek to embedded offset %I64d", vdw64EmbeddedOffset);
392 }
393
394 hFile = INVALID_HANDLE_VALUE;
395
396LExit:
397 ReleaseFileHandle(hFile);
398 ReleaseStr(sczCabFile);
399
400 return FAILED(hr) ? -1 : pFile;
401}
402
403
404static __callback UINT FAR DIAMONDAPI CabExtractRead(__in INT_PTR hf, __out void FAR *pv, __in UINT cb)
405{
406 HRESULT hr = S_OK;
407 DWORD cbRead = 0;
408
409 CabExitOnNull(hf, hr, E_INVALIDARG, "Failed to read file during cabinet extraction - no file given to read");
410 if (!::ReadFile(reinterpret_cast<HANDLE>(hf), pv, cb, &cbRead, NULL))
411 {
412 CabExitWithLastError(hr, "failed to read during cabinet extraction");
413 }
414
415LExit:
416 return FAILED(hr) ? -1 : cbRead;
417}
418
419
420static __callback UINT FAR DIAMONDAPI CabExtractWrite(__in INT_PTR hf, __in void FAR *pv, __in UINT cb)
421{
422 HRESULT hr = S_OK;
423 DWORD cbWrite = 0;
424
425 CabExitOnNull(hf, hr, E_INVALIDARG, "Failed to write file during cabinet extraction - no file given to write");
426 if (!::WriteFile(reinterpret_cast<HANDLE>(hf), pv, cb, &cbWrite, NULL))
427 {
428 CabExitWithLastError(hr, "failed to write during cabinet extraction");
429 }
430
431LExit:
432 return FAILED(hr) ? -1 : cbWrite;
433}
434
435
436static __callback long FAR DIAMONDAPI CabExtractSeek(__in INT_PTR hf, __in long dist, __in int seektype)
437{
438 HRESULT hr = S_OK;
439 DWORD dwMoveMethod;
440 LONG lMove = 0;
441
442 switch (seektype)
443 {
444 case 0: // SEEK_SET
445 dwMoveMethod = FILE_BEGIN;
446 dist += static_cast<long>(vdw64EmbeddedOffset);
447 break;
448 case 1: /// SEEK_CUR
449 dwMoveMethod = FILE_CURRENT;
450 break;
451 case 2: // SEEK_END
452 dwMoveMethod = FILE_END;
453 break;
454 default :
455 dwMoveMethod = 0;
456 hr = E_UNEXPECTED;
457 CabExitOnFailure(hr, "unexpected seektype in FDISeek(): %d", seektype);
458 }
459
460 // SetFilePointer returns -1 if it fails (this will cause FDI to quit with an FDIERROR_USER_ABORT error.
461 // (Unless this happens while working on a cabinet, in which case FDI returns FDIERROR_CORRUPT_CABINET)
462 lMove = ::SetFilePointer(reinterpret_cast<HANDLE>(hf), dist, NULL, dwMoveMethod);
463 if (0xFFFFFFFF == lMove)
464 {
465 CabExitWithLastError(hr, "failed to move file pointer %d bytes", dist);
466 }
467
468LExit:
469 return FAILED(hr) ? -1 : lMove - static_cast<long>(vdw64EmbeddedOffset);
470}
471
472
473static __callback int FAR DIAMONDAPI CabExtractClose(__in INT_PTR hf)
474{
475 HRESULT hr = S_OK;
476
477 if (!::CloseHandle(reinterpret_cast<HANDLE>(hf)))
478 {
479 CabExitWithLastError(hr, "failed to close file during cabinet extraction");
480 }
481
482LExit:
483 return FAILED(hr) ? -1 : 0;
484}
485
486
487static __callback INT_PTR DIAMONDAPI CabExtractCallback(__in FDINOTIFICATIONTYPE iNotification, __inout FDINOTIFICATION *pFDINotify)
488{
489 Assert(pFDINotify->pv);
490
491 HRESULT hr = S_OK;
492 HANDLE hFile = INVALID_HANDLE_VALUE;
493 INT_PTR ipResult = 0; // result to return on success
494
495 CAB_CALLBACK_STRUCT* pccs = static_cast<CAB_CALLBACK_STRUCT*>(pFDINotify->pv);
496 LPCSTR sz;
497 WCHAR wz[MAX_PATH];
498 FILETIME ft;
499
500 switch (iNotification)
501 {
502 case fdintCOPY_FILE: // begin extracting a resource from cabinet
503 CabExitOnNull(pFDINotify->psz1, hr, E_INVALIDARG, "No cabinet file ID given to convert");
504 CabExitOnNull(pccs, hr, E_INVALIDARG, "Failed to call cabextract callback, because no callback struct was provided");
505
506 if (pccs->fStopExtracting)
507 {
508 ExitFunction1(hr = S_FALSE); // no more extracting
509 }
510
511 // convert params to useful variables
512 sz = static_cast<LPCSTR>(pFDINotify->psz1);
513 if (!::MultiByteToWideChar(CP_ACP, 0, sz, -1, wz, countof(wz)))
514 {
515 CabExitWithLastError(hr, "failed to convert cabinet file id to unicode: %s", sz);
516 }
517
518 if (pccs->pfnProgress)
519 {
520 hr = pccs->pfnProgress(TRUE, wz, pccs->pvContext);
521 if (S_OK != hr)
522 {
523 ExitFunction();
524 }
525 }
526
527 if (L'*' == *pccs->pwzExtract || 0 == lstrcmpW(pccs->pwzExtract, wz))
528 {
529 // get the created date for the resource in the cabinet
530 FILETIME ftLocal;
531 if (!::DosDateTimeToFileTime(pFDINotify->date, pFDINotify->time, &ftLocal))
532 {
533 CabExitWithLastError(hr, "failed to get time for resource: %ls", wz);
534 }
535 ::LocalFileTimeToFileTime(&ftLocal, &ft);
536
537 WCHAR wzPath[MAX_PATH];
538 hr = ::StringCchCopyW(wzPath, countof(wzPath), pccs->pwzExtractDir);
539 CabExitOnFailure(hr, "failed to copy in extract directory: %ls for file: %ls", pccs->pwzExtractDir, wz);
540 hr = ::StringCchCatW(wzPath, countof(wzPath), wz);
541 CabExitOnFailure(hr, "failed to concat onto path: %ls file: %ls", wzPath, wz);
542
543 hFile = OpenFileWithRetry(wzPath, GENERIC_WRITE, CREATE_ALWAYS);
544 if (INVALID_HANDLE_VALUE == hFile)
545 {
546 CabExitWithLastError(hr, "failed to create file: %ls", wzPath);
547 }
548
549 ::SetFileTime(hFile, &ft, &ft, &ft); // try to set the file time (who cares if it fails)
550
551 if (::SetFilePointer(hFile, pFDINotify->cb, NULL, FILE_BEGIN)) // try to set the end of the file (don't worry if this fails)
552 {
553 if (::SetEndOfFile(hFile))
554 {
555 ::SetFilePointer(hFile, 0, NULL, FILE_BEGIN); // reset the file pointer
556 }
557 }
558
559 ipResult = reinterpret_cast<INT_PTR>(hFile);
560 hFile = INVALID_HANDLE_VALUE;
561 }
562 else // resource wasn't requested, skip it
563 {
564 hr = S_OK;
565 ipResult = 0;
566 }
567
568 break;
569 case fdintCLOSE_FILE_INFO: // resource extraction complete
570 Assert(pFDINotify->hf && pFDINotify->psz1);
571 CabExitOnNull(pccs, hr, E_INVALIDARG, "Failed to call cabextract callback, because no callback struct was provided");
572
573 // convert params to useful variables
574 sz = static_cast<LPCSTR>(pFDINotify->psz1);
575 CabExitOnNull(sz, hr, E_INVALIDARG, "Failed to convert cabinet file id, because no cabinet file id was provided");
576
577 if (!::MultiByteToWideChar(CP_ACP, 0, sz, -1, wz, countof(wz)))
578 {
579 CabExitWithLastError(hr, "failed to convert cabinet file id to unicode: %s", sz);
580 }
581
582 if (NULL != pFDINotify->hf) // just close the file
583 {
584 ::CloseHandle(reinterpret_cast<HANDLE>(pFDINotify->hf));
585 }
586
587 if (pccs->pfnProgress)
588 {
589 hr = pccs->pfnProgress(FALSE, wz, pccs->pvContext);
590 }
591
592 if (S_OK == hr && L'*' == *pccs->pwzExtract) // if everything is okay and we're extracting all files, keep going
593 {
594 ipResult = TRUE;
595 }
596 else // something went wrong or we only needed to extract one file
597 {
598 hr = S_OK;
599 ipResult = FALSE;
600 pccs->fStopExtracting = TRUE;
601 }
602
603 break;
604 case fdintPARTIAL_FILE: __fallthrough; // no action needed for these messages, fall through
605 case fdintNEXT_CABINET: __fallthrough;
606 case fdintENUMERATE: __fallthrough;
607 case fdintCABINET_INFO:
608 break;
609 default:
610 AssertSz(FALSE, "CabExtractCallback() - unknown FDI notification command");
611 };
612
613LExit:
614 ReleaseFileHandle(hFile);
615
616 return (S_OK == hr) ? ipResult : -1;
617}
diff --git a/src/libs/dutil/WixToolset.DUtil/certutil.cpp b/src/libs/dutil/WixToolset.DUtil/certutil.cpp
new file mode 100644
index 00000000..69897b9e
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/certutil.cpp
@@ -0,0 +1,342 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define CertExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_CERTUTIL, x, s, __VA_ARGS__)
8#define CertExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_CERTUTIL, x, s, __VA_ARGS__)
9#define CertExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_CERTUTIL, x, s, __VA_ARGS__)
10#define CertExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_CERTUTIL, x, s, __VA_ARGS__)
11#define CertExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_CERTUTIL, x, s, __VA_ARGS__)
12#define CertExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_CERTUTIL, x, s, __VA_ARGS__)
13#define CertExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_CERTUTIL, p, x, e, s, __VA_ARGS__)
14#define CertExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_CERTUTIL, p, x, s, __VA_ARGS__)
15#define CertExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_CERTUTIL, p, x, e, s, __VA_ARGS__)
16#define CertExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_CERTUTIL, p, x, s, __VA_ARGS__)
17#define CertExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_CERTUTIL, e, x, s, __VA_ARGS__)
18#define CertExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_CERTUTIL, g, x, s, __VA_ARGS__)
19
20/********************************************************************
21CertReadProperty - reads a property from the certificate.
22
23NOTE: call MemFree() on the returned pvValue.
24********************************************************************/
25extern "C" HRESULT DAPI CertReadProperty(
26 __in PCCERT_CONTEXT pCertContext,
27 __in DWORD dwProperty,
28 __deref_out_bound LPVOID* ppvValue,
29 __out_opt DWORD* pcbValue
30 )
31{
32 HRESULT hr = S_OK;
33 LPVOID pv = NULL;
34 DWORD cb = 0;
35
36 if (!::CertGetCertificateContextProperty(pCertContext, dwProperty, NULL, &cb))
37 {
38 CertExitWithLastError(hr, "Failed to get size of certificate property.");
39 }
40
41 pv = MemAlloc(cb, TRUE);
42 CertExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to allocate memory for certificate property.");
43
44 if (!::CertGetCertificateContextProperty(pCertContext, dwProperty, pv, &cb))
45 {
46 CertExitWithLastError(hr, "Failed to get certificate property.");
47 }
48
49 *ppvValue = pv;
50 pv = NULL;
51
52 if (pcbValue)
53 {
54 *pcbValue = cb;
55 }
56
57LExit:
58 ReleaseMem(pv);
59 return hr;
60}
61
62
63extern "C" HRESULT DAPI CertGetAuthenticodeSigningTimestamp(
64 __in CMSG_SIGNER_INFO* pSignerInfo,
65 __out FILETIME* pftSigningTimestamp
66 )
67{
68 HRESULT hr = S_OK;
69 CRYPT_INTEGER_BLOB* pBlob = NULL;
70 PCMSG_SIGNER_INFO pCounterSignerInfo = NULL;
71 DWORD cbSigningTimestamp = sizeof(FILETIME);
72
73 // Find the countersigner blob. The countersigner in Authenticode contains the time
74 // that signing took place. It's a "countersigner" because the signing time was sent
75 // off to the certificate authority in the sky to return the verified time signed.
76 for (DWORD i = 0; i < pSignerInfo->UnauthAttrs.cAttr; ++i)
77 {
78 if (CSTR_EQUAL == ::CompareStringA(LOCALE_NEUTRAL, 0, szOID_RSA_counterSign, -1, pSignerInfo->UnauthAttrs.rgAttr[i].pszObjId, -1))
79 {
80 pBlob = pSignerInfo->UnauthAttrs.rgAttr[i].rgValue;
81 break;
82 }
83 }
84
85 if (!pBlob)
86 {
87 hr = TRUST_E_FAIL;
88 CertExitOnFailure(hr, "Failed to find countersigner in signer information.");
89 }
90
91 hr = CrypDecodeObject(PKCS7_SIGNER_INFO, pBlob->pbData, pBlob->cbData, 0, reinterpret_cast<LPVOID*>(&pCounterSignerInfo), NULL);
92 CertExitOnFailure(hr, "Failed to decode countersigner information.");
93
94 pBlob = NULL; // reset the blob before searching for the signing time.
95
96 // Find the signing time blob in the countersigner.
97 for (DWORD i = 0; i < pCounterSignerInfo->AuthAttrs.cAttr; ++i)
98 {
99 if (CSTR_EQUAL == ::CompareStringA(LOCALE_NEUTRAL, 0, szOID_RSA_signingTime, -1, pCounterSignerInfo->AuthAttrs.rgAttr[i].pszObjId, -1))
100 {
101 pBlob = pCounterSignerInfo->AuthAttrs.rgAttr[i].rgValue;
102 break;
103 }
104 }
105
106 if (!pBlob)
107 {
108 hr = TRUST_E_FAIL;
109 CertExitOnFailure(hr, "Failed to find signing time in countersigner information.");
110 }
111
112 if (!::CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, szOID_RSA_signingTime, pBlob->pbData, pBlob->cbData, 0, pftSigningTimestamp, &cbSigningTimestamp))
113 {
114 CertExitWithLastError(hr, "Failed to decode countersigner signing timestamp.");
115 }
116
117LExit:
118 ReleaseMem(pCounterSignerInfo);
119
120 return hr;
121}
122
123
124extern "C" HRESULT DAPI GetCryptProvFromCert(
125 __in_opt HWND hwnd,
126 __in PCCERT_CONTEXT pCert,
127 __out HCRYPTPROV *phCryptProv,
128 __out DWORD *pdwKeySpec,
129 __in BOOL *pfDidCryptAcquire,
130 __deref_opt_out LPWSTR *ppwszTmpContainer,
131 __deref_opt_out LPWSTR *ppwszProviderName,
132 __out DWORD *pdwProviderType
133 )
134{
135 HRESULT hr = S_OK;
136 HMODULE hMsSign32 = NULL;
137
138 typedef BOOL (WINAPI *GETCRYPTPROVFROMCERTPTR)(HWND, PCCERT_CONTEXT, HCRYPTPROV*, DWORD*,BOOL*,LPWSTR*,LPWSTR*,DWORD*);
139 GETCRYPTPROVFROMCERTPTR pGetCryptProvFromCert = NULL;
140
141 hr = LoadSystemLibrary(L"MsSign32.dll", &hMsSign32);
142 CertExitOnFailure(hr, "Failed to get handle to MsSign32.dll");
143
144 pGetCryptProvFromCert = (GETCRYPTPROVFROMCERTPTR)::GetProcAddress(hMsSign32, "GetCryptProvFromCert");
145 CertExitOnNullWithLastError(hMsSign32, hr, "Failed to get handle to MsSign32.dll");
146
147 if (!pGetCryptProvFromCert(hwnd,
148 pCert,
149 phCryptProv,
150 pdwKeySpec,
151 pfDidCryptAcquire,
152 ppwszTmpContainer,
153 ppwszProviderName,
154 pdwProviderType))
155 {
156 CertExitWithLastError(hr, "Failed to get CSP from cert.");
157 }
158LExit:
159 return hr;
160}
161
162extern "C" HRESULT DAPI FreeCryptProvFromCert(
163 __in BOOL fAcquired,
164 __in HCRYPTPROV hProv,
165 __in_opt LPWSTR pwszCapiProvider,
166 __in DWORD dwProviderType,
167 __in_opt LPWSTR pwszTmpContainer
168 )
169{
170 HRESULT hr = S_OK;
171 HMODULE hMsSign32 = NULL;
172
173 typedef void (WINAPI *FREECRYPTPROVFROMCERT)(BOOL, HCRYPTPROV, LPWSTR, DWORD, LPWSTR);
174 FREECRYPTPROVFROMCERT pFreeCryptProvFromCert = NULL;
175
176 hr = LoadSystemLibrary(L"MsSign32.dll", &hMsSign32);
177 CertExitOnFailure(hr, "Failed to get handle to MsSign32.dll");
178
179 pFreeCryptProvFromCert = (FREECRYPTPROVFROMCERT)::GetProcAddress(hMsSign32, "FreeCryptProvFromCert");
180 CertExitOnNullWithLastError(hMsSign32, hr, "Failed to get handle to MsSign32.dll");
181
182 pFreeCryptProvFromCert(fAcquired, hProv, pwszCapiProvider, dwProviderType, pwszTmpContainer);
183LExit:
184 return hr;
185}
186
187extern "C" HRESULT DAPI GetProvSecurityDesc(
188 __in HCRYPTPROV hProv,
189 __deref_out SECURITY_DESCRIPTOR** ppSecurity)
190{
191 HRESULT hr = S_OK;
192 ULONG ulSize = 0;
193 SECURITY_DESCRIPTOR* pSecurity = NULL;
194
195 // Get the size of the security descriptor.
196 if (!::CryptGetProvParam(
197 hProv,
198 PP_KEYSET_SEC_DESCR,
199 NULL,
200 &ulSize,
201 DACL_SECURITY_INFORMATION))
202 {
203 CertExitWithLastError(hr, "Error getting security descriptor size for CSP.");
204 }
205
206 // Allocate the memory for the security descriptor.
207 pSecurity = static_cast<SECURITY_DESCRIPTOR *>(MemAlloc(ulSize, TRUE));
208 CertExitOnNullWithLastError(pSecurity, hr, "Error allocating memory for CSP DACL");
209
210 // Get the security descriptor.
211 if (!::CryptGetProvParam(
212 hProv,
213 PP_KEYSET_SEC_DESCR,
214 (BYTE*)pSecurity,
215 &ulSize,
216 DACL_SECURITY_INFORMATION))
217 {
218 MemFree(pSecurity);
219 CertExitWithLastError(hr, "Error getting security descriptor for CSP.");
220 }
221 *ppSecurity = pSecurity;
222
223LExit:
224 return hr;
225}
226
227
228extern "C" HRESULT DAPI SetProvSecurityDesc(
229 __in HCRYPTPROV hProv,
230 __in SECURITY_DESCRIPTOR* pSecurity)
231{
232 HRESULT hr = S_OK;
233
234 // Set the new security descriptor.
235 if (!::CryptSetProvParam(
236 hProv,
237 PP_KEYSET_SEC_DESCR,
238 (BYTE*)pSecurity,
239 DACL_SECURITY_INFORMATION))
240 {
241 CertExitWithLastError(hr, "Error setting security descriptor for CSP.");
242 }
243LExit:
244 return hr;
245}
246
247extern "C" BOOL DAPI CertHasPrivateKey(
248 __in PCCERT_CONTEXT pCertContext,
249 __out_opt DWORD* pdwKeySpec)
250{
251 HCRYPTPROV_OR_NCRYPT_KEY_HANDLE hPrivateKey = NULL;
252 DWORD dwKeySpec = 0;
253 // set CRYPT_ACQUIRE_CACHE_FLAG so that we don't have to release the private key handle
254 BOOL fResult = ::CryptAcquireCertificatePrivateKey(
255 pCertContext,
256 CRYPT_ACQUIRE_SILENT_FLAG | CRYPT_ACQUIRE_CACHE_FLAG,
257 0, //pvReserved
258 &hPrivateKey,
259 &dwKeySpec,
260 NULL
261 );
262 if (pdwKeySpec)
263 {
264 *pdwKeySpec = dwKeySpec;
265 }
266 return fResult;
267}
268
269
270extern "C" HRESULT DAPI CertInstallSingleCertificate(
271 __in HCERTSTORE hStore,
272 __in PCCERT_CONTEXT pCertContext,
273 __in LPCWSTR wzName
274 )
275{
276 HRESULT hr = S_OK;
277 CERT_BLOB blob = { };
278
279 DWORD dwKeySpec = 0;
280
281 HCRYPTPROV hCsp = NULL;
282 LPWSTR pwszTmpContainer = NULL;
283 LPWSTR pwszProviderName = NULL;
284 DWORD dwProviderType = 0;
285 BOOL fAcquired = TRUE;
286
287 SECURITY_DESCRIPTOR* pSecurity = NULL;
288 SECURITY_DESCRIPTOR* pSecurityNew = NULL;
289
290 // Update the friendly name of the certificate to be configured.
291 blob.pbData = (BYTE*)wzName;
292 blob.cbData = (lstrlenW(wzName) + 1) * sizeof(WCHAR); // including terminating null
293
294 if (!::CertSetCertificateContextProperty(pCertContext, CERT_FRIENDLY_NAME_PROP_ID, 0, &blob))
295 {
296 CertExitWithLastError(hr, "Failed to set the friendly name of the certificate: %ls", wzName);
297 }
298
299 if (!::CertAddCertificateContextToStore(hStore, pCertContext, CERT_STORE_ADD_REPLACE_EXISTING, NULL))
300 {
301 CertExitWithLastError(hr, "Failed to add certificate to the store.");
302 }
303
304 // if the certificate has a private key, grant Administrators access
305 if (CertHasPrivateKey(pCertContext, &dwKeySpec))
306 {
307 if (AT_KEYEXCHANGE == dwKeySpec || AT_SIGNATURE == dwKeySpec)
308 {
309 // We added a CSP key
310 hr = GetCryptProvFromCert(NULL, pCertContext, &hCsp, &dwKeySpec, &fAcquired, &pwszTmpContainer, &pwszProviderName, &dwProviderType);
311 CertExitOnFailure(hr, "Failed to get handle to CSP");
312
313 hr = GetProvSecurityDesc(hCsp, &pSecurity);
314 CertExitOnFailure(hr, "Failed to get security descriptor of CSP");
315
316 hr = AclAddAdminToSecurityDescriptor(pSecurity, &pSecurityNew);
317 CertExitOnFailure(hr, "Failed to create new security descriptor");
318
319 hr = SetProvSecurityDesc(hCsp, pSecurityNew);
320 CertExitOnFailure(hr, "Failed to set Admin ACL on CSP");
321 }
322
323 if (CERT_NCRYPT_KEY_SPEC == dwKeySpec)
324 {
325 // We added a CNG key
326 // TODO change ACL on CNG key
327 }
328 }
329LExit:
330 if (hCsp)
331 {
332 FreeCryptProvFromCert(fAcquired, hCsp, NULL, dwProviderType, NULL);
333 }
334
335 ReleaseMem(pSecurity);
336
337 if (pSecurityNew)
338 {
339 AclFreeSecurityDescriptor(pSecurityNew);
340 }
341 return hr;
342}
diff --git a/src/libs/dutil/WixToolset.DUtil/conutil.cpp b/src/libs/dutil/WixToolset.DUtil/conutil.cpp
new file mode 100644
index 00000000..33e1b59a
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/conutil.cpp
@@ -0,0 +1,673 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define ConExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_CONUTIL, x, s, __VA_ARGS__)
8#define ConExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_CONUTIL, x, s, __VA_ARGS__)
9#define ConExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_CONUTIL, x, s, __VA_ARGS__)
10#define ConExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_CONUTIL, x, s, __VA_ARGS__)
11#define ConExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_CONUTIL, x, s, __VA_ARGS__)
12#define ConExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_CONUTIL, x, s, __VA_ARGS__)
13#define ConExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_CONUTIL, p, x, e, s, __VA_ARGS__)
14#define ConExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_CONUTIL, p, x, s, __VA_ARGS__)
15#define ConExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_CONUTIL, p, x, e, s, __VA_ARGS__)
16#define ConExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_CONUTIL, p, x, s, __VA_ARGS__)
17#define ConExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_CONUTIL, e, x, s, __VA_ARGS__)
18#define ConExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_CONUTIL, g, x, s, __VA_ARGS__)
19
20
21static HANDLE vhStdIn = INVALID_HANDLE_VALUE;
22static HANDLE vhStdOut = INVALID_HANDLE_VALUE;
23static BOOL vfConsoleIn = FALSE;
24static BOOL vfConsoleOut = FALSE;
25static CONSOLE_SCREEN_BUFFER_INFO vcsbiInfo;
26
27
28extern "C" HRESULT DAPI ConsoleInitialize()
29{
30 Assert(INVALID_HANDLE_VALUE == vhStdOut);
31 HRESULT hr = S_OK;
32 UINT er;
33
34 vhStdIn = ::GetStdHandle(STD_INPUT_HANDLE);
35 if (INVALID_HANDLE_VALUE == vhStdIn)
36 {
37 ConExitOnLastError(hr, "failed to open stdin");
38 }
39
40 vhStdOut = ::GetStdHandle(STD_OUTPUT_HANDLE);
41 if (INVALID_HANDLE_VALUE == vhStdOut)
42 {
43 ConExitOnLastError(hr, "failed to open stdout");
44 }
45
46 // check if we have a std in on the console
47 if (::GetConsoleScreenBufferInfo(vhStdIn, &vcsbiInfo))
48 {
49 vfConsoleIn = TRUE;
50 }
51 else
52 {
53 er = ::GetLastError();
54 if (ERROR_INVALID_HANDLE == er)
55 {
56 vfConsoleIn= FALSE;
57 hr = S_OK;
58 }
59 else
60 {
61 ConExitOnWin32Error(er, hr, "failed to get input console screen buffer info");
62 }
63 }
64
65 if (::GetConsoleScreenBufferInfo(vhStdOut, &vcsbiInfo))
66 {
67 vfConsoleOut = TRUE;
68 }
69 else // no console
70 {
71 memset(&vcsbiInfo, 0, sizeof(vcsbiInfo));
72 er = ::GetLastError();
73 if (ERROR_INVALID_HANDLE == er)
74 {
75 vfConsoleOut = FALSE;
76 hr = S_OK;
77 }
78 else
79 {
80 ConExitOnWin32Error(er, hr, "failed to get output console screen buffer info");
81 }
82 }
83
84LExit:
85 if (FAILED(hr))
86 {
87 if (INVALID_HANDLE_VALUE != vhStdOut)
88 {
89 ::CloseHandle(vhStdOut);
90 }
91
92 if (INVALID_HANDLE_VALUE != vhStdIn && vhStdOut != vhStdIn)
93 {
94 ::CloseHandle(vhStdIn);
95 }
96
97 vhStdOut = INVALID_HANDLE_VALUE;
98 vhStdIn = INVALID_HANDLE_VALUE;
99 }
100
101 return hr;
102}
103
104
105extern "C" void DAPI ConsoleUninitialize()
106{
107 BOOL fOutEqualsIn = vhStdOut == vhStdIn;
108
109 memset(&vcsbiInfo, 0, sizeof(vcsbiInfo));
110
111 if (INVALID_HANDLE_VALUE != vhStdOut)
112 {
113 ::CloseHandle(vhStdOut);
114 }
115
116 if (INVALID_HANDLE_VALUE != vhStdIn && !fOutEqualsIn)
117 {
118 ::CloseHandle(vhStdIn);
119 }
120
121 vhStdOut = INVALID_HANDLE_VALUE;
122 vhStdIn = INVALID_HANDLE_VALUE;
123}
124
125
126extern "C" void DAPI ConsoleGreen()
127{
128 AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called");
129 if (vfConsoleOut)
130 {
131 ::SetConsoleTextAttribute(vhStdOut, FOREGROUND_GREEN | FOREGROUND_INTENSITY);
132 }
133}
134
135
136extern "C" void DAPI ConsoleRed()
137{
138 AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called");
139 if (vfConsoleOut)
140 {
141 ::SetConsoleTextAttribute(vhStdOut, FOREGROUND_RED | FOREGROUND_INTENSITY);
142 }
143}
144
145
146extern "C" void DAPI ConsoleYellow()
147{
148 AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called");
149 if (vfConsoleOut)
150 {
151 ::SetConsoleTextAttribute(vhStdOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
152 }
153}
154
155
156extern "C" void DAPI ConsoleNormal()
157{
158 AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called");
159 if (vfConsoleOut)
160 {
161 ::SetConsoleTextAttribute(vhStdOut, vcsbiInfo.wAttributes);
162 }
163}
164
165
166/********************************************************************
167 ConsoleWrite - full color printfA without libc
168
169 NOTE: use FormatMessage formatting ("%1" or "%1!d!") not plain printf formatting ("%ls" or "%d")
170 assumes already in normal color and resets the screen to normal color
171********************************************************************/
172extern "C" HRESULT DAPI ConsoleWrite(
173 CONSOLE_COLOR cc,
174 __in_z __format_string LPCSTR szFormat,
175 ...
176 )
177{
178 AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called");
179 HRESULT hr = S_OK;
180 LPSTR pszOutput = NULL;
181 DWORD cchOutput = 0;
182 DWORD cbWrote = 0;
183 DWORD cbTotal = 0;
184
185 // set the color
186 switch (cc)
187 {
188 case CONSOLE_COLOR_NORMAL: break; // do nothing
189 case CONSOLE_COLOR_RED: ConsoleRed(); break;
190 case CONSOLE_COLOR_YELLOW: ConsoleYellow(); break;
191 case CONSOLE_COLOR_GREEN: ConsoleGreen(); break;
192 }
193
194 va_list args;
195 va_start(args, szFormat);
196 hr = StrAnsiAllocFormattedArgs(&pszOutput, szFormat, args);
197 va_end(args);
198 ConExitOnFailure(hr, "failed to format message: \"%s\"", szFormat);
199
200 cchOutput = lstrlenA(pszOutput);
201 while (cbTotal < (sizeof(*pszOutput) * cchOutput))
202 {
203 if (!::WriteFile(vhStdOut, reinterpret_cast<BYTE*>(pszOutput) + cbTotal, cchOutput * sizeof(*pszOutput) - cbTotal, &cbWrote, NULL))
204 {
205 ConExitOnLastError(hr, "failed to write output to console: %s", pszOutput);
206 }
207
208 cbTotal += cbWrote;
209 }
210
211 // reset the color to normal
212 if (CONSOLE_COLOR_NORMAL != cc)
213 {
214 ConsoleNormal();
215 }
216
217LExit:
218 ReleaseStr(pszOutput);
219 return hr;
220}
221
222
223/********************************************************************
224 ConsoleWriteLine - full color printfA plus newline without libc
225
226 NOTE: use FormatMessage formatting ("%1" or "%1!d!") not plain printf formatting ("%ls" or "%d")
227 assumes already in normal color and resets the screen to normal color
228********************************************************************/
229extern "C" HRESULT DAPI ConsoleWriteLine(
230 CONSOLE_COLOR cc,
231 __in_z __format_string LPCSTR szFormat,
232 ...
233 )
234{
235 AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called");
236 HRESULT hr = S_OK;
237 LPSTR pszOutput = NULL;
238 DWORD cchOutput = 0;
239 DWORD cbWrote = 0;
240 DWORD cbTotal = 0;
241 LPCSTR szNewLine = "\r\n";
242
243 // set the color
244 switch (cc)
245 {
246 case CONSOLE_COLOR_NORMAL: break; // do nothing
247 case CONSOLE_COLOR_RED: ConsoleRed(); break;
248 case CONSOLE_COLOR_YELLOW: ConsoleYellow(); break;
249 case CONSOLE_COLOR_GREEN: ConsoleGreen(); break;
250 }
251
252 va_list args;
253 va_start(args, szFormat);
254 hr = StrAnsiAllocFormattedArgs(&pszOutput, szFormat, args);
255 va_end(args);
256 ConExitOnFailure(hr, "failed to format message: \"%s\"", szFormat);
257
258 //
259 // write the string
260 //
261 cchOutput = lstrlenA(pszOutput);
262 while (cbTotal < (sizeof(*pszOutput) * cchOutput))
263 {
264 if (!::WriteFile(vhStdOut, reinterpret_cast<BYTE*>(pszOutput) + cbTotal, cchOutput * sizeof(*pszOutput) - cbTotal, &cbWrote, NULL))
265 ConExitOnLastError(hr, "failed to write output to console: %s", pszOutput);
266
267 cbTotal += cbWrote;
268 }
269
270 //
271 // write the newline
272 //
273 if (!::WriteFile(vhStdOut, reinterpret_cast<const BYTE*>(szNewLine), 2, &cbWrote, NULL))
274 {
275 ConExitOnLastError(hr, "failed to write newline to console");
276 }
277
278 // reset the color to normal
279 if (CONSOLE_COLOR_NORMAL != cc)
280 {
281 ConsoleNormal();
282 }
283
284LExit:
285 ReleaseStr(pszOutput);
286 return hr;
287}
288
289
290/********************************************************************
291 ConsoleWriteError - display an error to the screen
292
293 NOTE: use FormatMessage formatting ("%1" or "%1!d!") not plain printf formatting ("%s" or "%d")
294********************************************************************/
295HRESULT ConsoleWriteError(
296 HRESULT hrError,
297 CONSOLE_COLOR cc,
298 __in_z __format_string LPCSTR szFormat,
299 ...
300 )
301{
302 HRESULT hr = S_OK;
303 LPSTR pszMessage = NULL;
304
305 va_list args;
306 va_start(args, szFormat);
307 hr = StrAnsiAllocFormattedArgs(&pszMessage, szFormat, args);
308 va_end(args);
309 ConExitOnFailure(hr, "failed to format error message: \"%s\"", szFormat);
310
311 if (FAILED(hrError))
312 {
313 hr = ConsoleWriteLine(cc, "Error 0x%x: %s", hrError, pszMessage);
314 }
315 else
316 {
317 hr = ConsoleWriteLine(cc, "Error: %s", pszMessage);
318 }
319
320LExit:
321 ReleaseStr(pszMessage);
322 return hr;
323}
324
325
326/********************************************************************
327 ConsoleReadW - get console input without libc
328
329 NOTE: only supports reading ANSI characters
330********************************************************************/
331extern "C" HRESULT DAPI ConsoleReadW(
332 __deref_out_z LPWSTR* ppwzBuffer
333 )
334{
335 AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called");
336 Assert(ppwzBuffer);
337
338 HRESULT hr = S_OK;
339 LPSTR psz = NULL;
340 DWORD cch = 0;
341 DWORD cchRead = 0;
342 DWORD cchTotalRead = 0;
343
344 cch = 64;
345 hr = StrAnsiAlloc(&psz, cch);
346 ConExitOnFailure(hr, "failed to allocate memory to read from console");
347
348 // loop until we read the \r\n from the console
349 for (;;)
350 {
351 // read one character at a time, since that seems to be the only way to make this work
352 if (!::ReadFile(vhStdIn, psz + cchTotalRead, 1, &cchRead, NULL))
353 ConExitOnLastError(hr, "failed to read string from console");
354
355 cchTotalRead += cchRead;
356 if (1 < cchTotalRead && '\r' == psz[cchTotalRead - 2] || '\n' == psz[cchTotalRead - 1])
357 {
358 psz[cchTotalRead - 2] = '\0'; // chop off the \r\n
359 break;
360 }
361 else if (0 == cchRead) // nothing more was read
362 {
363 psz[cchTotalRead] = '\0'; // null termintate and bail
364 break;
365 }
366
367 if (cchTotalRead == cch)
368 {
369 cch *= 2; // double everytime we run out of space
370 hr = StrAnsiAlloc(&psz, cch);
371 ConExitOnFailure(hr, "failed to allocate memory to read from console");
372 }
373 }
374
375 hr = StrAllocStringAnsi(ppwzBuffer, psz, 0, CP_ACP);
376
377LExit:
378 ReleaseStr(psz);
379 return hr;
380}
381
382
383/********************************************************************
384 ConsoleReadNonBlockingW - Read from the console without blocking
385 Won't work for redirected files (exe < txtfile), but will work for stdin redirected to
386 an anonymous or named pipe
387
388 if (fReadLine), stop reading immediately when \r\n is found
389*********************************************************************/
390extern "C" HRESULT DAPI ConsoleReadNonBlockingW(
391 __deref_out_ecount_opt(*pcchSize) LPWSTR* ppwzBuffer,
392 __out DWORD* pcchSize,
393 BOOL fReadLine
394 )
395{
396 Assert(INVALID_HANDLE_VALUE != vhStdIn && pcchSize);
397 HRESULT hr = S_OK;
398
399 LPSTR psz = NULL;
400
401 ConExitOnNull(ppwzBuffer, hr, E_INVALIDARG, "Failed to read from console because buffer was not provided");
402
403 DWORD dwRead;
404 DWORD dwNumInput;
405
406 DWORD cchTotal = 0;
407 DWORD cch = 8;
408
409 DWORD cchRead = 0;
410 DWORD cchTotalRead = 0;
411
412 DWORD dwIndex = 0;
413 DWORD er;
414
415 INPUT_RECORD ir;
416 WCHAR chIn;
417
418 *ppwzBuffer = NULL;
419 *pcchSize = 0;
420
421 // If we really have a handle to stdin, and not the end of a pipe
422 if (!PeekNamedPipe(vhStdIn, NULL, 0, NULL, &dwRead, NULL))
423 {
424 er = ::GetLastError();
425 if (ERROR_INVALID_HANDLE != er)
426 {
427 ExitFunction1(hr = HRESULT_FROM_WIN32(er));
428 }
429
430 if (!GetNumberOfConsoleInputEvents(vhStdIn, &dwRead))
431 {
432 ConExitOnLastError(hr, "failed to peek at console input");
433 }
434
435 if (0 == dwRead)
436 {
437 ExitFunction1(hr = S_FALSE);
438 }
439
440 for (/* dwRead from num of input events */; dwRead > 0; dwRead--)
441 {
442 if (!ReadConsoleInputW(vhStdIn, &ir, 1, &dwNumInput))
443 {
444 ConExitOnLastError(hr, "Failed to read input from console");
445 }
446
447 // If what we have is a KEY_EVENT, and that event signifies keyUp, we're interested
448 if (KEY_EVENT == ir.EventType && FALSE == ir.Event.KeyEvent.bKeyDown)
449 {
450 chIn = ir.Event.KeyEvent.uChar.UnicodeChar;
451
452 if (0 == cchTotal)
453 {
454 cchTotal = cch;
455 cch *= 2;
456 StrAlloc(ppwzBuffer, cch);
457 }
458
459 (*ppwzBuffer)[dwIndex] = chIn;
460
461 if (fReadLine && (L'\r' == (*ppwzBuffer)[dwIndex - 1] && L'\n' == (*ppwzBuffer)[dwIndex]))
462 {
463 *ppwzBuffer[dwIndex - 1] = L'\0';
464 dwIndex -= 1;
465 break;
466 }
467
468 ++dwIndex;
469 cchTotal--;
470 }
471 }
472
473 *pcchSize = dwIndex;
474 }
475 else
476 {
477 // otherwise, the peek worked, and we have the end of a pipe
478 if (0 == dwRead)
479 ExitFunction1(hr = S_FALSE);
480
481 cch = 8;
482 hr = StrAnsiAlloc(&psz, cch);
483 ConExitOnFailure(hr, "failed to allocate memory to read from console");
484
485 for (/*dwRead from PeekNamedPipe*/; dwRead > 0; dwRead--)
486 {
487 // read one character at a time, since that seems to be the only way to make this work
488 if (!::ReadFile(vhStdIn, psz + cchTotalRead, 1, &cchRead, NULL))
489 {
490 ConExitOnLastError(hr, "failed to read string from console");
491 }
492
493 cchTotalRead += cchRead;
494 if (fReadLine && '\r' == psz[cchTotalRead - 1] && '\n' == psz[cchTotalRead])
495 {
496 psz[cchTotalRead - 1] = '\0'; // chop off the \r\n
497 cchTotalRead -= 1;
498 break;
499 }
500 else if (0 == cchRead) // nothing more was read
501 {
502 psz[cchTotalRead] = '\0'; // null termintate and bail
503 break;
504 }
505
506 if (cchTotalRead == cch)
507 {
508 cch *= 2; // double everytime we run out of space
509 hr = StrAnsiAlloc(&psz, cch);
510 ConExitOnFailure(hr, "failed to allocate memory to read from console");
511 }
512 }
513
514 *pcchSize = cchTotalRead;
515 hr = StrAllocStringAnsi(ppwzBuffer, psz, cchTotalRead, CP_ACP);
516 }
517
518LExit:
519 ReleaseStr(psz);
520
521 return hr;
522}
523
524
525/********************************************************************
526 ConsoleReadStringA - get console input without libc
527
528*********************************************************************/
529extern "C" HRESULT DAPI ConsoleReadStringA(
530 __deref_inout_ecount_part(cchCharBuffer,*pcchNumCharReturn) LPSTR* ppszCharBuffer,
531 CONST DWORD cchCharBuffer,
532 __out DWORD* pcchNumCharReturn
533 )
534{
535 AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called");
536 HRESULT hr = S_OK;
537 if (ppszCharBuffer && (pcchNumCharReturn || cchCharBuffer < 2))
538 {
539 DWORD iRead = 1;
540 DWORD iReadCharTotal = 0;
541 if (ppszCharBuffer && *ppszCharBuffer == NULL)
542 {
543 do
544 {
545 hr = StrAnsiAlloc(ppszCharBuffer, cchCharBuffer * iRead);
546 ConExitOnFailure(hr, "failed to allocate memory for ConsoleReadStringW");
547 // ReadConsoleW will not return until <Return>, the last two chars are 13 and 10.
548 if (!::ReadConsoleA(vhStdIn, *ppszCharBuffer + iReadCharTotal, cchCharBuffer, pcchNumCharReturn, NULL) || *pcchNumCharReturn == 0)
549 {
550 ConExitOnLastError(hr, "failed to read string from console");
551 }
552 iReadCharTotal += *pcchNumCharReturn;
553 iRead += 1;
554 }
555 while((*ppszCharBuffer)[iReadCharTotal - 1] != 10 || (*ppszCharBuffer)[iReadCharTotal - 2] != 13);
556 *pcchNumCharReturn = iReadCharTotal;
557 }
558 else
559 {
560 if (!::ReadConsoleA(vhStdIn, *ppszCharBuffer, cchCharBuffer, pcchNumCharReturn, NULL) ||
561 *pcchNumCharReturn > cchCharBuffer || *pcchNumCharReturn == 0)
562 {
563 ConExitOnLastError(hr, "failed to read string from console");
564 }
565 if ((*ppszCharBuffer)[*pcchNumCharReturn - 1] != 10 ||
566 (*ppszCharBuffer)[*pcchNumCharReturn - 2] != 13)
567 {
568 // need read more
569 hr = ERROR_MORE_DATA;
570 }
571 }
572 }
573 else
574 {
575 hr = E_INVALIDARG;
576 }
577
578LExit:
579 return hr;
580}
581
582/********************************************************************
583 ConsoleReadStringW - get console input without libc
584
585*********************************************************************/
586extern "C" HRESULT DAPI ConsoleReadStringW(
587 __deref_inout_ecount_part(cchCharBuffer,*pcchNumCharReturn) LPWSTR* ppwzCharBuffer,
588 const DWORD cchCharBuffer,
589 __out DWORD* pcchNumCharReturn
590 )
591{
592 AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called");
593 HRESULT hr = S_OK;
594 if (ppwzCharBuffer && (pcchNumCharReturn || cchCharBuffer < 2))
595 {
596 DWORD iRead = 1;
597 DWORD iReadCharTotal = 0;
598 if (*ppwzCharBuffer == NULL)
599 {
600 do
601 {
602 hr = StrAlloc(ppwzCharBuffer, cchCharBuffer * iRead);
603 ConExitOnFailure(hr, "failed to allocate memory for ConsoleReadStringW");
604 // ReadConsoleW will not return until <Return>, the last two chars are 13 and 10.
605 if (!::ReadConsoleW(vhStdIn, *ppwzCharBuffer + iReadCharTotal, cchCharBuffer, pcchNumCharReturn, NULL) || *pcchNumCharReturn == 0)
606 {
607 ConExitOnLastError(hr, "failed to read string from console");
608 }
609 iReadCharTotal += *pcchNumCharReturn;
610 iRead += 1;
611 }
612 while((*ppwzCharBuffer)[iReadCharTotal - 1] != 10 || (*ppwzCharBuffer)[iReadCharTotal - 2] != 13);
613 *pcchNumCharReturn = iReadCharTotal;
614 }
615 else
616 {
617 if (!::ReadConsoleW(vhStdIn, *ppwzCharBuffer, cchCharBuffer, pcchNumCharReturn, NULL) ||
618 *pcchNumCharReturn > cchCharBuffer || *pcchNumCharReturn == 0)
619 {
620 ConExitOnLastError(hr, "failed to read string from console");
621 }
622 if ((*ppwzCharBuffer)[*pcchNumCharReturn - 1] != 10 ||
623 (*ppwzCharBuffer)[*pcchNumCharReturn - 2] != 13)
624 {
625 // need read more
626 hr = ERROR_MORE_DATA;
627 }
628 }
629 }
630 else
631 {
632 hr = E_INVALIDARG;
633 }
634
635LExit:
636 return hr;
637}
638
639/********************************************************************
640 ConsoleSetReadHidden - set console input no echo
641
642*********************************************************************/
643extern "C" HRESULT DAPI ConsoleSetReadHidden(void)
644{
645 AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called");
646 HRESULT hr = S_OK;
647 ::FlushConsoleInputBuffer(vhStdIn);
648 if (!::SetConsoleMode(vhStdIn, ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT))
649 {
650 ConExitOnLastError(hr, "failed to set console input mode to be hidden");
651 }
652
653LExit:
654 return hr;
655}
656
657/********************************************************************
658 ConsoleSetReadNormal - reset to echo
659
660*********************************************************************/
661extern "C" HRESULT DAPI ConsoleSetReadNormal(void)
662{
663 AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called");
664 HRESULT hr = S_OK;
665 if (!::SetConsoleMode(vhStdIn, ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_MOUSE_INPUT))
666 {
667 ConExitOnLastError(hr, "failed to set console input mode to be normal");
668 }
669
670LExit:
671 return hr;
672}
673
diff --git a/src/libs/dutil/WixToolset.DUtil/cryputil.cpp b/src/libs/dutil/WixToolset.DUtil/cryputil.cpp
new file mode 100644
index 00000000..24bb83f1
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/cryputil.cpp
@@ -0,0 +1,404 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define CrypExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_CRYPUTIL, x, s, __VA_ARGS__)
8#define CrypExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_CRYPUTIL, x, s, __VA_ARGS__)
9#define CrypExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_CRYPUTIL, x, s, __VA_ARGS__)
10#define CrypExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_CRYPUTIL, x, s, __VA_ARGS__)
11#define CrypExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_CRYPUTIL, x, s, __VA_ARGS__)
12#define CrypExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_CRYPUTIL, x, s, __VA_ARGS__)
13#define CrypExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_CRYPUTIL, p, x, e, s, __VA_ARGS__)
14#define CrypExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_CRYPUTIL, p, x, s, __VA_ARGS__)
15#define CrypExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_CRYPUTIL, p, x, e, s, __VA_ARGS__)
16#define CrypExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_CRYPUTIL, p, x, s, __VA_ARGS__)
17#define CrypExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_CRYPUTIL, e, x, s, __VA_ARGS__)
18#define CrypExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_CRYPUTIL, g, x, s, __VA_ARGS__)
19
20static PFN_RTLENCRYPTMEMORY vpfnRtlEncryptMemory = NULL;
21static PFN_RTLDECRYPTMEMORY vpfnRtlDecryptMemory = NULL;
22static PFN_CRYPTPROTECTMEMORY vpfnCryptProtectMemory = NULL;
23static PFN_CRYPTUNPROTECTMEMORY vpfnCryptUnprotectMemory = NULL;
24
25static HMODULE vhAdvApi32Dll = NULL;
26static HMODULE vhCrypt32Dll = NULL;
27static BOOL vfCrypInitialized = FALSE;
28
29// function definitions
30
31/********************************************************************
32 CrypInitialize - initializes cryputil
33
34*********************************************************************/
35extern "C" HRESULT DAPI CrypInitialize(
36 )
37{
38 HRESULT hr = S_OK;
39
40 hr = LoadSystemLibrary(L"AdvApi32.dll", &vhAdvApi32Dll);
41 if (SUCCEEDED(hr))
42 {
43 // Ignore failures - if these don't exist, we'll try the Crypt methods.
44 vpfnRtlEncryptMemory = reinterpret_cast<PFN_RTLENCRYPTMEMORY>(::GetProcAddress(vhAdvApi32Dll, "SystemFunction040"));
45 vpfnRtlDecryptMemory = reinterpret_cast<PFN_RTLDECRYPTMEMORY>(::GetProcAddress(vhAdvApi32Dll, "SystemFunction041"));
46 }
47 if (!vpfnRtlEncryptMemory || !vpfnRtlDecryptMemory)
48 {
49 hr = LoadSystemLibrary(L"Crypt32.dll", &vhCrypt32Dll);
50 CrypExitOnFailure(hr, "Failed to load Crypt32.dll");
51
52 vpfnCryptProtectMemory = reinterpret_cast<PFN_CRYPTPROTECTMEMORY>(::GetProcAddress(vhCrypt32Dll, "CryptProtectMemory"));
53 if (!vpfnRtlEncryptMemory && !vpfnCryptProtectMemory)
54 {
55 CrypExitWithLastError(hr, "Failed to load an encryption method");
56 }
57 vpfnCryptUnprotectMemory = reinterpret_cast<PFN_CRYPTUNPROTECTMEMORY>(::GetProcAddress(vhCrypt32Dll, "CryptUnprotectMemory"));
58 if (!vpfnRtlDecryptMemory && !vpfnCryptUnprotectMemory)
59 {
60 CrypExitWithLastError(hr, "Failed to load a decryption method");
61 }
62 }
63
64 vfCrypInitialized = TRUE;
65
66LExit:
67 return hr;
68}
69
70
71/********************************************************************
72 CrypUninitialize - uninitializes cryputil
73
74*********************************************************************/
75extern "C" void DAPI CrypUninitialize(
76 )
77{
78 if (vhAdvApi32Dll)
79 {
80 ::FreeLibrary(vhAdvApi32Dll);
81 vhAdvApi32Dll = NULL;
82 vpfnRtlEncryptMemory = NULL;
83 vpfnRtlDecryptMemory = NULL;
84 }
85
86 if (vhCrypt32Dll)
87 {
88 ::FreeLibrary(vhCrypt32Dll);
89 vhCrypt32Dll = NULL;
90 vpfnCryptProtectMemory = NULL;
91 vpfnCryptUnprotectMemory = NULL;
92 }
93
94 vfCrypInitialized = FALSE;
95}
96
97extern "C" HRESULT DAPI CrypDecodeObject(
98 __in_z LPCSTR szStructType,
99 __in_ecount(cbData) const BYTE* pbData,
100 __in DWORD cbData,
101 __in DWORD dwFlags,
102 __out LPVOID* ppvObject,
103 __out_opt DWORD* pcbObject
104 )
105{
106 HRESULT hr = S_OK;
107 LPVOID pvObject = NULL;
108 DWORD cbObject = 0;
109
110 if (!::CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, szStructType, pbData, cbData, dwFlags, NULL, &cbObject))
111 {
112 CrypExitWithLastError(hr, "Failed to decode object to determine size.");
113 }
114
115 pvObject = MemAlloc(cbObject, TRUE);
116 CrypExitOnNull(pvObject, hr, E_OUTOFMEMORY, "Failed to allocate memory for decoded object.");
117
118 if (!::CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, szStructType, pbData, cbData, dwFlags, pvObject, &cbObject))
119 {
120 CrypExitWithLastError(hr, "Failed to decode object.");
121 }
122
123 *ppvObject = pvObject;
124 pvObject = NULL;
125
126 if (pcbObject)
127 {
128 *pcbObject = cbObject;
129 }
130
131LExit:
132 ReleaseMem(pvObject);
133
134 return hr;
135}
136
137
138extern "C" HRESULT DAPI CrypMsgGetParam(
139 __in HCRYPTMSG hCryptMsg,
140 __in DWORD dwType,
141 __in DWORD dwIndex,
142 __out LPVOID* ppvData,
143 __out_opt DWORD* pcbData
144 )
145{
146 HRESULT hr = S_OK;
147 LPVOID pv = NULL;
148 DWORD cb = 0;
149
150 if (!::CryptMsgGetParam(hCryptMsg, dwType, dwIndex, NULL, &cb))
151 {
152 CrypExitWithLastError(hr, "Failed to get crypt message parameter data size.");
153 }
154
155 pv = MemAlloc(cb, TRUE);
156 CrypExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to allocate memory for crypt message parameter.");
157
158 if (!::CryptMsgGetParam(hCryptMsg, dwType, dwIndex, pv, &cb))
159 {
160 CrypExitWithLastError(hr, "Failed to get crypt message parameter.");
161 }
162
163 *ppvData = pv;
164 pv = NULL;
165
166 if (pcbData)
167 {
168 *pcbData = cb;
169 }
170
171LExit:
172 ReleaseMem(pv);
173
174 return hr;
175}
176
177
178extern "C" HRESULT DAPI CrypHashFile(
179 __in_z LPCWSTR wzFilePath,
180 __in DWORD dwProvType,
181 __in ALG_ID algid,
182 __out_bcount(cbHash) BYTE* pbHash,
183 __in DWORD cbHash,
184 __out_opt DWORD64* pqwBytesHashed
185 )
186{
187 HRESULT hr = S_OK;
188 HANDLE hFile = INVALID_HANDLE_VALUE;
189
190 // open input file
191 hFile = ::CreateFileW(wzFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
192 if (INVALID_HANDLE_VALUE == hFile)
193 {
194 CrypExitWithLastError(hr, "Failed to open input file.");
195 }
196
197 hr = CrypHashFileHandle(hFile, dwProvType, algid, pbHash, cbHash, pqwBytesHashed);
198 CrypExitOnFailure(hr, "Failed to hash file: %ls", wzFilePath);
199
200LExit:
201 ReleaseFileHandle(hFile);
202
203 return hr;
204}
205
206
207extern "C" HRESULT DAPI CrypHashFileHandle(
208 __in HANDLE hFile,
209 __in DWORD dwProvType,
210 __in ALG_ID algid,
211 __out_bcount(cbHash) BYTE* pbHash,
212 __in DWORD cbHash,
213 __out_opt DWORD64* pqwBytesHashed
214 )
215{
216 HRESULT hr = S_OK;
217 HCRYPTPROV hProv = NULL;
218 HCRYPTHASH hHash = NULL;
219 DWORD cbRead = 0;
220 BYTE rgbBuffer[4096] = { };
221 const LARGE_INTEGER liZero = { };
222
223 // get handle to the crypto provider
224 if (!::CryptAcquireContextW(&hProv, NULL, NULL, dwProvType, CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
225 {
226 CrypExitWithLastError(hr, "Failed to acquire crypto context.");
227 }
228
229 // initiate hash
230 if (!::CryptCreateHash(hProv, algid, 0, 0, &hHash))
231 {
232 CrypExitWithLastError(hr, "Failed to initiate hash.");
233 }
234
235 for (;;)
236 {
237 // read data block
238 if (!::ReadFile(hFile, rgbBuffer, sizeof(rgbBuffer), &cbRead, NULL))
239 {
240 CrypExitWithLastError(hr, "Failed to read data block.");
241 }
242
243 if (!cbRead)
244 {
245 break; // end of file
246 }
247
248 // hash data block
249 if (!::CryptHashData(hHash, rgbBuffer, cbRead, 0))
250 {
251 CrypExitWithLastError(hr, "Failed to hash data block.");
252 }
253 }
254
255 // get hash value
256 if (!::CryptGetHashParam(hHash, HP_HASHVAL, pbHash, &cbHash, 0))
257 {
258 CrypExitWithLastError(hr, "Failed to get hash value.");
259 }
260
261 if (pqwBytesHashed)
262 {
263 if (!::SetFilePointerEx(hFile, liZero, (LARGE_INTEGER*)pqwBytesHashed, FILE_CURRENT))
264 {
265 CrypExitWithLastError(hr, "Failed to get file pointer.");
266 }
267 }
268
269LExit:
270 if (hHash)
271 {
272 ::CryptDestroyHash(hHash);
273 }
274 if (hProv)
275 {
276 ::CryptReleaseContext(hProv, 0);
277 }
278
279 return hr;
280}
281
282HRESULT DAPI CrypHashBuffer(
283 __in_bcount(cbBuffer) const BYTE* pbBuffer,
284 __in SIZE_T cbBuffer,
285 __in DWORD dwProvType,
286 __in ALG_ID algid,
287 __out_bcount(cbHash) BYTE* pbHash,
288 __in DWORD cbHash
289 )
290{
291 HRESULT hr = S_OK;
292 HCRYPTPROV hProv = NULL;
293 HCRYPTHASH hHash = NULL;
294 DWORD cbDataHashed = 0;
295 SIZE_T cbTotal = 0;
296 SIZE_T cbRemaining = 0;
297
298 // get handle to the crypto provider
299 if (!::CryptAcquireContextW(&hProv, NULL, NULL, dwProvType, CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
300 {
301 CrypExitWithLastError(hr, "Failed to acquire crypto context.");
302 }
303
304 // initiate hash
305 if (!::CryptCreateHash(hProv, algid, 0, 0, &hHash))
306 {
307 CrypExitWithLastError(hr, "Failed to initiate hash.");
308 }
309
310 do
311 {
312 cbRemaining = cbBuffer - cbTotal;
313 cbDataHashed = (DWORD)min(DWORD_MAX, cbRemaining);
314 if (!::CryptHashData(hHash, pbBuffer + cbTotal, cbDataHashed, 0))
315 {
316 CrypExitWithLastError(hr, "Failed to hash data.");
317 }
318
319 cbTotal += cbDataHashed;
320 } while (cbTotal < cbBuffer);
321
322 // get hash value
323 if (!::CryptGetHashParam(hHash, HP_HASHVAL, pbHash, &cbHash, 0))
324 {
325 CrypExitWithLastError(hr, "Failed to get hash value.");
326 }
327
328LExit:
329 if (hHash)
330 {
331 ::CryptDestroyHash(hHash);
332 }
333 if (hProv)
334 {
335 ::CryptReleaseContext(hProv, 0);
336 }
337
338 return hr;
339}
340
341HRESULT DAPI CrypEncryptMemory(
342 __inout LPVOID pData,
343 __in DWORD cbData,
344 __in DWORD dwFlags
345 )
346{
347 HRESULT hr = E_FAIL;
348
349 if (0 != cbData % CRYP_ENCRYPT_MEMORY_SIZE)
350 {
351 hr = E_INVALIDARG;
352 }
353 else if (vpfnRtlEncryptMemory)
354 {
355 hr = static_cast<HRESULT>(vpfnRtlEncryptMemory(pData, cbData, dwFlags));
356 }
357 else if (vpfnCryptProtectMemory)
358 {
359 if (vpfnCryptProtectMemory(pData, cbData, dwFlags))
360 {
361 hr = S_OK;
362 }
363 else
364 {
365 hr = HRESULT_FROM_WIN32(::GetLastError());
366 }
367 }
368 CrypExitOnFailure(hr, "Failed to encrypt memory");
369LExit:
370 return hr;
371}
372
373HRESULT DAPI CrypDecryptMemory(
374 __inout LPVOID pData,
375 __in DWORD cbData,
376 __in DWORD dwFlags
377 )
378{
379 HRESULT hr = E_FAIL;
380
381 if (0 != cbData % CRYP_ENCRYPT_MEMORY_SIZE)
382 {
383 hr = E_INVALIDARG;
384 }
385 else if (vpfnRtlDecryptMemory)
386 {
387 hr = static_cast<HRESULT>(vpfnRtlDecryptMemory(pData, cbData, dwFlags));
388 }
389 else if (vpfnCryptUnprotectMemory)
390 {
391 if (vpfnCryptUnprotectMemory(pData, cbData, dwFlags))
392 {
393 hr = S_OK;
394 }
395 else
396 {
397 hr = HRESULT_FROM_WIN32(::GetLastError());
398 }
399 }
400 CrypExitOnFailure(hr, "Failed to decrypt memory");
401LExit:
402 return hr;
403}
404
diff --git a/src/libs/dutil/WixToolset.DUtil/deputil.cpp b/src/libs/dutil/WixToolset.DUtil/deputil.cpp
new file mode 100644
index 00000000..2e6d6a6c
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/deputil.cpp
@@ -0,0 +1,699 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define DepExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_DEPUTIL, x, s, __VA_ARGS__)
8#define DepExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_DEPUTIL, x, s, __VA_ARGS__)
9#define DepExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_DEPUTIL, x, s, __VA_ARGS__)
10#define DepExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_DEPUTIL, x, s, __VA_ARGS__)
11#define DepExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_DEPUTIL, x, s, __VA_ARGS__)
12#define DepExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_DEPUTIL, x, s, __VA_ARGS__)
13#define DepExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_DEPUTIL, p, x, e, s, __VA_ARGS__)
14#define DepExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_DEPUTIL, p, x, s, __VA_ARGS__)
15#define DepExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_DEPUTIL, p, x, e, s, __VA_ARGS__)
16#define DepExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_DEPUTIL, p, x, s, __VA_ARGS__)
17#define DepExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_DEPUTIL, e, x, s, __VA_ARGS__)
18#define DepExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_DEPUTIL, g, x, s, __VA_ARGS__)
19
20#define ARRAY_GROWTH_SIZE 5
21
22static LPCWSTR vcszVersionValue = L"Version";
23static LPCWSTR vcszDisplayNameValue = L"DisplayName";
24static LPCWSTR vcszMinVersionValue = L"MinVersion";
25static LPCWSTR vcszMaxVersionValue = L"MaxVersion";
26static LPCWSTR vcszAttributesValue = L"Attributes";
27
28enum eRequiresAttributes { RequiresAttributesMinVersionInclusive = 256, RequiresAttributesMaxVersionInclusive = 512 };
29
30// We write to Software\Classes explicitly based on the current security context instead of HKCR.
31// See http://msdn.microsoft.com/en-us/library/ms724475(VS.85).aspx for more information.
32static LPCWSTR vsczRegistryRoot = L"Software\\Classes\\Installer\\Dependencies\\";
33static LPCWSTR vsczRegistryDependents = L"Dependents";
34
35static HRESULT AllocDependencyKeyName(
36 __in_z LPCWSTR wzName,
37 __deref_out_z LPWSTR* psczKeyName
38 );
39
40static HRESULT GetDependencyNameFromKey(
41 __in HKEY hkHive,
42 __in LPCWSTR wzKey,
43 __deref_out_z LPWSTR* psczName
44 );
45
46DAPI_(HRESULT) DepGetProviderInformation(
47 __in HKEY hkHive,
48 __in_z LPCWSTR wzProviderKey,
49 __deref_out_z_opt LPWSTR* psczId,
50 __deref_out_z_opt LPWSTR* psczName,
51 __deref_out_z_opt LPWSTR* psczVersion
52 )
53{
54 HRESULT hr = S_OK;
55 LPWSTR sczKey = NULL;
56 HKEY hkKey = NULL;
57
58 // Format the provider dependency registry key.
59 hr = AllocDependencyKeyName(wzProviderKey, &sczKey);
60 DepExitOnFailure(hr, "Failed to allocate the registry key for dependency \"%ls\".", wzProviderKey);
61
62 // Try to open the dependency key.
63 hr = RegOpen(hkHive, sczKey, KEY_READ, &hkKey);
64 if (E_FILENOTFOUND == hr)
65 {
66 ExitFunction1(hr = E_NOTFOUND);
67 }
68 DepExitOnFailure(hr, "Failed to open the registry key for the dependency \"%ls\".", wzProviderKey);
69
70 // Get the Id if requested and available.
71 if (psczId)
72 {
73 hr = RegReadString(hkKey, NULL, psczId);
74 if (E_FILENOTFOUND == hr)
75 {
76 hr = S_OK;
77 }
78 DepExitOnFailure(hr, "Failed to get the id for the dependency \"%ls\".", wzProviderKey);
79 }
80
81 // Get the DisplayName if requested and available.
82 if (psczName)
83 {
84 hr = RegReadString(hkKey, vcszDisplayNameValue, psczName);
85 if (E_FILENOTFOUND == hr)
86 {
87 hr = S_OK;
88 }
89 DepExitOnFailure(hr, "Failed to get the name for the dependency \"%ls\".", wzProviderKey);
90 }
91
92 // Get the Version if requested and available.
93 if (psczVersion)
94 {
95 hr = RegReadString(hkKey, vcszVersionValue, psczVersion);
96 if (E_FILENOTFOUND == hr)
97 {
98 hr = S_OK;
99 }
100 DepExitOnFailure(hr, "Failed to get the version for the dependency \"%ls\".", wzProviderKey);
101 }
102
103LExit:
104 ReleaseRegKey(hkKey);
105 ReleaseStr(sczKey);
106
107 return hr;
108}
109
110DAPI_(HRESULT) DepCheckDependency(
111 __in HKEY hkHive,
112 __in_z LPCWSTR wzProviderKey,
113 __in_z_opt LPCWSTR wzMinVersion,
114 __in_z_opt LPCWSTR wzMaxVersion,
115 __in int iAttributes,
116 __in STRINGDICT_HANDLE sdDependencies,
117 __deref_inout_ecount_opt(*pcDependencies) DEPENDENCY** prgDependencies,
118 __inout LPUINT pcDependencies
119 )
120{
121 HRESULT hr = S_OK;
122 LPWSTR sczKey = NULL;
123 HKEY hkKey = NULL;
124 DWORD64 dw64Version = 0;
125 DWORD64 dw64MinVersion = 0;
126 DWORD64 dw64MaxVersion = 0;
127 BOOL fAllowEqual = FALSE;
128 LPWSTR sczName = NULL;
129
130 // Format the provider dependency registry key.
131 hr = AllocDependencyKeyName(wzProviderKey, &sczKey);
132 DepExitOnFailure(hr, "Failed to allocate the registry key for dependency \"%ls\".", wzProviderKey);
133
134 // Try to open the key. If that fails, add the missing dependency key to the dependency array if it doesn't already exist.
135 hr = RegOpen(hkHive, sczKey, KEY_READ, &hkKey);
136 if (E_FILENOTFOUND != hr)
137 {
138 DepExitOnFailure(hr, "Failed to open the registry key for dependency \"%ls\".", wzProviderKey);
139
140 // If there are no registry values, consider the key orphaned and treat it as missing.
141 hr = RegReadVersion(hkKey, vcszVersionValue, &dw64Version);
142 if (E_FILENOTFOUND != hr)
143 {
144 DepExitOnFailure(hr, "Failed to read the %ls registry value for dependency \"%ls\".", vcszVersionValue, wzProviderKey);
145 }
146 }
147
148 // If the key was not found or the Version value was not found, add the missing dependency key to the dependency array.
149 if (E_FILENOTFOUND == hr)
150 {
151 hr = DictKeyExists(sdDependencies, wzProviderKey);
152 if (E_NOTFOUND != hr)
153 {
154 DepExitOnFailure(hr, "Failed to check the dictionary for missing dependency \"%ls\".", wzProviderKey);
155 }
156 else
157 {
158 hr = DepDependencyArrayAlloc(prgDependencies, pcDependencies, wzProviderKey, NULL);
159 DepExitOnFailure(hr, "Failed to add the missing dependency \"%ls\" to the array.", wzProviderKey);
160
161 hr = DictAddKey(sdDependencies, wzProviderKey);
162 DepExitOnFailure(hr, "Failed to add the missing dependency \"%ls\" to the dictionary.", wzProviderKey);
163 }
164
165 // Exit since the check already failed.
166 ExitFunction1(hr = E_NOTFOUND);
167 }
168
169 // Check MinVersion if provided.
170 if (wzMinVersion)
171 {
172 if (*wzMinVersion)
173 {
174 hr = FileVersionFromStringEx(wzMinVersion, 0, &dw64MinVersion);
175 DepExitOnFailure(hr, "Failed to get the 64-bit version number from \"%ls\".", wzMinVersion);
176
177 fAllowEqual = iAttributes & RequiresAttributesMinVersionInclusive;
178 if (!(fAllowEqual && dw64MinVersion <= dw64Version || dw64MinVersion < dw64Version))
179 {
180 hr = DictKeyExists(sdDependencies, wzProviderKey);
181 if (E_NOTFOUND != hr)
182 {
183 DepExitOnFailure(hr, "Failed to check the dictionary for the older dependency \"%ls\".", wzProviderKey);
184 }
185 else
186 {
187 hr = RegReadString(hkKey, vcszDisplayNameValue, &sczName);
188 DepExitOnFailure(hr, "Failed to get the display name of the older dependency \"%ls\".", wzProviderKey);
189
190 hr = DepDependencyArrayAlloc(prgDependencies, pcDependencies, wzProviderKey, sczName);
191 DepExitOnFailure(hr, "Failed to add the older dependency \"%ls\" to the dependencies array.", wzProviderKey);
192
193 hr = DictAddKey(sdDependencies, wzProviderKey);
194 DepExitOnFailure(hr, "Failed to add the older dependency \"%ls\" to the unique dependency string list.", wzProviderKey);
195 }
196
197 // Exit since the check already failed.
198 ExitFunction1(hr = E_NOTFOUND);
199 }
200 }
201 }
202
203 // Check MaxVersion if provided.
204 if (wzMaxVersion)
205 {
206 if (*wzMaxVersion)
207 {
208 hr = FileVersionFromStringEx(wzMaxVersion, 0, &dw64MaxVersion);
209 DepExitOnFailure(hr, "Failed to get the 64-bit version number from \"%ls\".", wzMaxVersion);
210
211 fAllowEqual = iAttributes & RequiresAttributesMaxVersionInclusive;
212 if (!(fAllowEqual && dw64Version <= dw64MaxVersion || dw64Version < dw64MaxVersion))
213 {
214 hr = DictKeyExists(sdDependencies, wzProviderKey);
215 if (E_NOTFOUND != hr)
216 {
217 DepExitOnFailure(hr, "Failed to check the dictionary for the newer dependency \"%ls\".", wzProviderKey);
218 }
219 else
220 {
221 hr = RegReadString(hkKey, vcszDisplayNameValue, &sczName);
222 DepExitOnFailure(hr, "Failed to get the display name of the newer dependency \"%ls\".", wzProviderKey);
223
224 hr = DepDependencyArrayAlloc(prgDependencies, pcDependencies, wzProviderKey, sczName);
225 DepExitOnFailure(hr, "Failed to add the newer dependency \"%ls\" to the dependencies array.", wzProviderKey);
226
227 hr = DictAddKey(sdDependencies, wzProviderKey);
228 DepExitOnFailure(hr, "Failed to add the newer dependency \"%ls\" to the unique dependency string list.", wzProviderKey);
229 }
230
231 // Exit since the check already failed.
232 ExitFunction1(hr = E_NOTFOUND);
233 }
234 }
235 }
236
237LExit:
238 ReleaseStr(sczName);
239 ReleaseRegKey(hkKey);
240 ReleaseStr(sczKey);
241
242 return hr;
243}
244
245DAPI_(HRESULT) DepCheckDependents(
246 __in HKEY hkHive,
247 __in_z LPCWSTR wzProviderKey,
248 __reserved int /*iAttributes*/,
249 __in C_STRINGDICT_HANDLE sdIgnoredDependents,
250 __deref_inout_ecount_opt(*pcDependents) DEPENDENCY** prgDependents,
251 __inout LPUINT pcDependents
252 )
253{
254 HRESULT hr = S_OK;
255 LPWSTR sczKey = NULL;
256 HKEY hkProviderKey = NULL;
257 HKEY hkDependentsKey = NULL;
258 LPWSTR sczDependentKey = NULL;
259 LPWSTR sczDependentName = NULL;
260
261 // Format the provider dependency registry key.
262 hr = AllocDependencyKeyName(wzProviderKey, &sczKey);
263 DepExitOnFailure(hr, "Failed to allocate the registry key for dependency \"%ls\".", wzProviderKey);
264
265 // Try to open the key. If that fails, the dependency information is corrupt.
266 hr = RegOpen(hkHive, sczKey, KEY_READ, &hkProviderKey);
267 DepExitOnFailure(hr, "Failed to open the registry key \"%ls\". The dependency store is corrupt.", sczKey);
268
269 // Try to open the dependencies key. If that does not exist, there are no dependents.
270 hr = RegOpen(hkProviderKey, vsczRegistryDependents, KEY_READ, &hkDependentsKey);
271 if (E_FILENOTFOUND != hr)
272 {
273 DepExitOnFailure(hr, "Failed to open the registry key for dependents of \"%ls\".", wzProviderKey);
274 }
275 else
276 {
277 ExitFunction1(hr = S_OK);
278 }
279
280 // Now enumerate the dependent keys. If they are not defined in the ignored list, add them to the array.
281 for (DWORD dwIndex = 0; ; ++dwIndex)
282 {
283 hr = RegKeyEnum(hkDependentsKey, dwIndex, &sczDependentKey);
284 if (E_NOMOREITEMS != hr)
285 {
286 DepExitOnFailure(hr, "Failed to enumerate the dependents key of \"%ls\".", wzProviderKey);
287 }
288 else
289 {
290 hr = S_OK;
291 break;
292 }
293
294 // If the key isn't ignored, add it to the dependent array.
295 hr = DictKeyExists(sdIgnoredDependents, sczDependentKey);
296 if (E_NOTFOUND != hr)
297 {
298 DepExitOnFailure(hr, "Failed to check the dictionary of ignored dependents.");
299 }
300 else
301 {
302 // Get the name of the dependent from the key.
303 hr = GetDependencyNameFromKey(hkHive, sczDependentKey, &sczDependentName);
304 DepExitOnFailure(hr, "Failed to get the name of the dependent from the key \"%ls\".", sczDependentKey);
305
306 hr = DepDependencyArrayAlloc(prgDependents, pcDependents, sczDependentKey, sczDependentName);
307 DepExitOnFailure(hr, "Failed to add the dependent key \"%ls\" to the string array.", sczDependentKey);
308 }
309 }
310
311LExit:
312 ReleaseStr(sczDependentName);
313 ReleaseStr(sczDependentKey);
314 ReleaseRegKey(hkDependentsKey);
315 ReleaseRegKey(hkProviderKey);
316 ReleaseStr(sczKey);
317
318 return hr;
319}
320
321DAPI_(HRESULT) DepRegisterDependency(
322 __in HKEY hkHive,
323 __in_z LPCWSTR wzProviderKey,
324 __in_z LPCWSTR wzVersion,
325 __in_z LPCWSTR wzDisplayName,
326 __in_z_opt LPCWSTR wzId,
327 __in int iAttributes
328 )
329{
330 HRESULT hr = S_OK;
331 LPWSTR sczKey = NULL;
332 HKEY hkKey = NULL;
333 BOOL fCreated = FALSE;
334
335 // Format the provider dependency registry key.
336 hr = AllocDependencyKeyName(wzProviderKey, &sczKey);
337 DepExitOnFailure(hr, "Failed to allocate the registry key for dependency \"%ls\".", wzProviderKey);
338
339 // Create the dependency key (or open it if it already exists).
340 hr = RegCreateEx(hkHive, sczKey, KEY_WRITE, FALSE, NULL, &hkKey, &fCreated);
341 DepExitOnFailure(hr, "Failed to create the dependency registry key \"%ls\".", sczKey);
342
343 // Set the id if it was provided.
344 if (wzId)
345 {
346 hr = RegWriteString(hkKey, NULL, wzId);
347 DepExitOnFailure(hr, "Failed to set the %ls registry value to \"%ls\".", L"default", wzId);
348 }
349
350 // Set the version.
351 hr = RegWriteString(hkKey, vcszVersionValue, wzVersion);
352 DepExitOnFailure(hr, "Failed to set the %ls registry value to \"%ls\".", vcszVersionValue, wzVersion);
353
354 // Set the display name.
355 hr = RegWriteString(hkKey, vcszDisplayNameValue, wzDisplayName);
356 DepExitOnFailure(hr, "Failed to set the %ls registry value to \"%ls\".", vcszDisplayNameValue, wzDisplayName);
357
358 // Set the attributes if non-zero.
359 if (0 != iAttributes)
360 {
361 hr = RegWriteNumber(hkKey, vcszAttributesValue, static_cast<DWORD>(iAttributes));
362 DepExitOnFailure(hr, "Failed to set the %ls registry value to %d.", vcszAttributesValue, iAttributes);
363 }
364
365LExit:
366 ReleaseRegKey(hkKey);
367 ReleaseStr(sczKey);
368
369 return hr;
370}
371
372DAPI_(HRESULT) DepDependentExists(
373 __in HKEY hkHive,
374 __in_z LPCWSTR wzDependencyProviderKey,
375 __in_z LPCWSTR wzProviderKey
376 )
377{
378 HRESULT hr = S_OK;
379 LPWSTR sczDependentKey = NULL;
380 HKEY hkDependentKey = NULL;
381
382 // Format the provider dependents registry key.
383 hr = StrAllocFormatted(&sczDependentKey, L"%ls%ls\\%ls\\%ls", vsczRegistryRoot, wzDependencyProviderKey, vsczRegistryDependents, wzProviderKey);
384 DepExitOnFailure(hr, "Failed to format registry key to dependent.");
385
386 hr = RegOpen(hkHive, sczDependentKey, KEY_READ, &hkDependentKey);
387 if (E_FILENOTFOUND != hr)
388 {
389 DepExitOnFailure(hr, "Failed to open the dependent registry key at: \"%ls\".", sczDependentKey);
390 }
391
392LExit:
393 ReleaseRegKey(hkDependentKey);
394 ReleaseStr(sczDependentKey);
395
396 return hr;
397}
398
399DAPI_(HRESULT) DepRegisterDependent(
400 __in HKEY hkHive,
401 __in_z LPCWSTR wzDependencyProviderKey,
402 __in_z LPCWSTR wzProviderKey,
403 __in_z_opt LPCWSTR wzMinVersion,
404 __in_z_opt LPCWSTR wzMaxVersion,
405 __in int iAttributes
406 )
407{
408 HRESULT hr = S_OK;
409 LPWSTR sczDependencyKey = NULL;
410 HKEY hkDependencyKey = NULL;
411 LPWSTR sczKey = NULL;
412 HKEY hkKey = NULL;
413 BOOL fCreated = FALSE;
414
415 // Format the provider dependency registry key.
416 hr = AllocDependencyKeyName(wzDependencyProviderKey, &sczDependencyKey);
417 DepExitOnFailure(hr, "Failed to allocate the registry key for dependency \"%ls\".", wzDependencyProviderKey);
418
419 // Create the dependency key (or open it if it already exists).
420 hr = RegCreateEx(hkHive, sczDependencyKey, KEY_WRITE, FALSE, NULL, &hkDependencyKey, &fCreated);
421 DepExitOnFailure(hr, "Failed to create the dependency registry key \"%ls\".", sczDependencyKey);
422
423 // Create the subkey to register the dependent.
424 hr = StrAllocFormatted(&sczKey, L"%ls\\%ls", vsczRegistryDependents, wzProviderKey);
425 DepExitOnFailure(hr, "Failed to allocate dependent subkey \"%ls\" under dependency \"%ls\".", wzProviderKey, wzDependencyProviderKey);
426
427 hr = RegCreateEx(hkDependencyKey, sczKey, KEY_WRITE, FALSE, NULL, &hkKey, &fCreated);
428 DepExitOnFailure(hr, "Failed to create the dependency subkey \"%ls\".", sczKey);
429
430 // Set the minimum version if not NULL.
431 hr = RegWriteString(hkKey, vcszMinVersionValue, wzMinVersion);
432 DepExitOnFailure(hr, "Failed to set the %ls registry value to \"%ls\".", vcszMinVersionValue, wzMinVersion);
433
434 // Set the maximum version if not NULL.
435 hr = RegWriteString(hkKey, vcszMaxVersionValue, wzMaxVersion);
436 DepExitOnFailure(hr, "Failed to set the %ls registry value to \"%ls\".", vcszMaxVersionValue, wzMaxVersion);
437
438 // Set the attributes if non-zero.
439 if (0 != iAttributes)
440 {
441 hr = RegWriteNumber(hkKey, vcszAttributesValue, static_cast<DWORD>(iAttributes));
442 DepExitOnFailure(hr, "Failed to set the %ls registry value to %d.", vcszAttributesValue, iAttributes);
443 }
444
445LExit:
446 ReleaseRegKey(hkKey);
447 ReleaseStr(sczKey);
448 ReleaseRegKey(hkDependencyKey);
449 ReleaseStr(sczDependencyKey);
450
451 return hr;
452}
453
454DAPI_(HRESULT) DepUnregisterDependency(
455 __in HKEY hkHive,
456 __in_z LPCWSTR wzProviderKey
457 )
458{
459 HRESULT hr = S_OK;
460 LPWSTR sczKey = NULL;
461 HKEY hkKey = NULL;
462
463 // Format the provider dependency registry key.
464 hr = AllocDependencyKeyName(wzProviderKey, &sczKey);
465 DepExitOnFailure(hr, "Failed to allocate the registry key for dependency \"%ls\".", wzProviderKey);
466
467 // Delete the entire key including all sub-keys.
468 hr = RegDelete(hkHive, sczKey, REG_KEY_DEFAULT, TRUE);
469 if (E_FILENOTFOUND != hr)
470 {
471 DepExitOnFailure(hr, "Failed to delete the key \"%ls\".", sczKey);
472 }
473
474LExit:
475 ReleaseRegKey(hkKey);
476 ReleaseStr(sczKey);
477
478 return hr;
479}
480
481DAPI_(HRESULT) DepUnregisterDependent(
482 __in HKEY hkHive,
483 __in_z LPCWSTR wzDependencyProviderKey,
484 __in_z LPCWSTR wzProviderKey
485 )
486{
487 HRESULT hr = S_OK;
488 HKEY hkRegistryRoot = NULL;
489 HKEY hkDependencyProviderKey = NULL;
490 HKEY hkRegistryDependents = NULL;
491 DWORD cSubKeys = 0;
492 DWORD cValues = 0;
493
494 // Open the root key. We may delete the wzDependencyProviderKey during clean up.
495 hr = RegOpen(hkHive, vsczRegistryRoot, KEY_READ, &hkRegistryRoot);
496 if (E_FILENOTFOUND != hr)
497 {
498 DepExitOnFailure(hr, "Failed to open root registry key \"%ls\".", vsczRegistryRoot);
499 }
500 else
501 {
502 ExitFunction();
503 }
504
505 // Try to open the dependency key. If that does not exist, simply return.
506 hr = RegOpen(hkRegistryRoot, wzDependencyProviderKey, KEY_READ, &hkDependencyProviderKey);
507 if (E_FILENOTFOUND != hr)
508 {
509 DepExitOnFailure(hr, "Failed to open the registry key for the dependency \"%ls\".", wzDependencyProviderKey);
510 }
511 else
512 {
513 ExitFunction();
514 }
515
516 // Try to open the dependents subkey to enumerate.
517 hr = RegOpen(hkDependencyProviderKey, vsczRegistryDependents, KEY_READ, &hkRegistryDependents);
518 if (E_FILENOTFOUND != hr)
519 {
520 DepExitOnFailure(hr, "Failed to open the dependents subkey under the dependency \"%ls\".", wzDependencyProviderKey);
521 }
522 else
523 {
524 ExitFunction();
525 }
526
527 // Delete the wzProviderKey dependent sub-key.
528 hr = RegDelete(hkRegistryDependents, wzProviderKey, REG_KEY_DEFAULT, TRUE);
529 DepExitOnFailure(hr, "Failed to delete the dependent \"%ls\" under the dependency \"%ls\".", wzProviderKey, wzDependencyProviderKey);
530
531 // If there are no remaining dependents, delete the Dependents subkey.
532 hr = RegQueryKey(hkRegistryDependents, &cSubKeys, NULL);
533 DepExitOnFailure(hr, "Failed to get the number of dependent subkeys under the dependency \"%ls\".", wzDependencyProviderKey);
534
535 if (0 < cSubKeys)
536 {
537 ExitFunction();
538 }
539
540 // Release the handle to make sure it's deleted immediately.
541 ReleaseRegKey(hkRegistryDependents);
542
543 // Fail if there are any subkeys since we just checked.
544 hr = RegDelete(hkDependencyProviderKey, vsczRegistryDependents, REG_KEY_DEFAULT, FALSE);
545 DepExitOnFailure(hr, "Failed to delete the dependents subkey under the dependency \"%ls\".", wzDependencyProviderKey);
546
547 // If there are no values, delete the provider dependency key.
548 hr = RegQueryKey(hkDependencyProviderKey, NULL, &cValues);
549 DepExitOnFailure(hr, "Failed to get the number of values under the dependency \"%ls\".", wzDependencyProviderKey);
550
551 if (0 == cValues)
552 {
553 // Release the handle to make sure it's deleted immediately.
554 ReleaseRegKey(hkDependencyProviderKey);
555
556 // Fail if there are any subkeys since we just checked.
557 hr = RegDelete(hkRegistryRoot, wzDependencyProviderKey, REG_KEY_DEFAULT, FALSE);
558 DepExitOnFailure(hr, "Failed to delete the dependency \"%ls\".", wzDependencyProviderKey);
559 }
560
561LExit:
562 ReleaseRegKey(hkRegistryDependents);
563 ReleaseRegKey(hkDependencyProviderKey);
564 ReleaseRegKey(hkRegistryRoot);
565
566 return hr;
567}
568
569DAPI_(HRESULT) DepDependencyArrayAlloc(
570 __deref_inout_ecount_opt(*pcDependencies) DEPENDENCY** prgDependencies,
571 __inout LPUINT pcDependencies,
572 __in_z LPCWSTR wzKey,
573 __in_z_opt LPCWSTR wzName
574 )
575{
576 HRESULT hr = S_OK;
577 UINT cRequired = 0;
578 DEPENDENCY* pDependency = NULL;
579
580 hr = ::UIntAdd(*pcDependencies, 1, &cRequired);
581 DepExitOnFailure(hr, "Failed to increment the number of elements required in the dependency array.");
582
583 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(prgDependencies), cRequired, sizeof(DEPENDENCY), ARRAY_GROWTH_SIZE);
584 DepExitOnFailure(hr, "Failed to allocate memory for the dependency array.");
585
586 pDependency = static_cast<DEPENDENCY*>(&(*prgDependencies)[*pcDependencies]);
587 DepExitOnNull(pDependency, hr, E_POINTER, "The dependency element in the array is invalid.");
588
589 hr = StrAllocString(&(pDependency->sczKey), wzKey, 0);
590 DepExitOnFailure(hr, "Failed to allocate the string key in the dependency array.");
591
592 if (wzName)
593 {
594 hr = StrAllocString(&(pDependency->sczName), wzName, 0);
595 DepExitOnFailure(hr, "Failed to allocate the string name in the dependency array.");
596 }
597
598 // Update the number of current elements in the dependency array.
599 *pcDependencies = cRequired;
600
601LExit:
602 return hr;
603}
604
605DAPI_(void) DepDependencyArrayFree(
606 __in_ecount(cDependencies) DEPENDENCY* rgDependencies,
607 __in UINT cDependencies
608 )
609{
610 for (UINT i = 0; i < cDependencies; ++i)
611 {
612 ReleaseStr(rgDependencies[i].sczKey);
613 ReleaseStr(rgDependencies[i].sczName);
614 }
615
616 ReleaseMem(rgDependencies);
617}
618
619/***************************************************************************
620 AllocDependencyKeyName - Allocates and formats the root registry key name.
621
622***************************************************************************/
623static HRESULT AllocDependencyKeyName(
624 __in_z LPCWSTR wzName,
625 __deref_out_z LPWSTR* psczKeyName
626 )
627{
628 HRESULT hr = S_OK;
629 size_t cchName = 0;
630 size_t cchKeyName = 0;
631
632 // Get the length of the static registry root once.
633 static size_t cchRegistryRoot = ::lstrlenW(vsczRegistryRoot);
634
635 // Get the length of the dependency, and add to the length of the root.
636 hr = ::StringCchLengthW(wzName, STRSAFE_MAX_CCH, &cchName);
637 DepExitOnFailure(hr, "Failed to get string length of dependency name.");
638
639 // Add the sizes together to allocate memory once (callee will add space for nul).
640 hr = ::SizeTAdd(cchRegistryRoot, cchName, &cchKeyName);
641 DepExitOnFailure(hr, "Failed to add the string lengths together.");
642
643 // Allocate and concat the strings together.
644 hr = StrAllocString(psczKeyName, vsczRegistryRoot, cchKeyName);
645 DepExitOnFailure(hr, "Failed to allocate string for dependency registry root.");
646
647 hr = StrAllocConcat(psczKeyName, wzName, cchName);
648 DepExitOnFailure(hr, "Failed to concatenate the dependency key name.");
649
650LExit:
651 return hr;
652}
653
654/***************************************************************************
655 GetDependencyNameFromKey - Attempts to name of the dependency from the key.
656
657***************************************************************************/
658static HRESULT GetDependencyNameFromKey(
659 __in HKEY hkHive,
660 __in LPCWSTR wzProviderKey,
661 __deref_out_z LPWSTR* psczName
662 )
663{
664 HRESULT hr = S_OK;
665 LPWSTR sczKey = NULL;
666 HKEY hkKey = NULL;
667
668 // Format the provider dependency registry key.
669 hr = AllocDependencyKeyName(wzProviderKey, &sczKey);
670 DepExitOnFailure(hr, "Failed to allocate the registry key for dependency \"%ls\".", wzProviderKey);
671
672 // Try to open the dependency key.
673 hr = RegOpen(hkHive, sczKey, KEY_READ, &hkKey);
674 if (E_FILENOTFOUND != hr)
675 {
676 DepExitOnFailure(hr, "Failed to open the registry key for the dependency \"%ls\".", wzProviderKey);
677 }
678 else
679 {
680 ExitFunction1(hr = S_OK);
681 }
682
683 // Get the DisplayName if available.
684 hr = RegReadString(hkKey, vcszDisplayNameValue, psczName);
685 if (E_FILENOTFOUND != hr)
686 {
687 DepExitOnFailure(hr, "Failed to get the dependency name for the dependency \"%ls\".", wzProviderKey);
688 }
689 else
690 {
691 ExitFunction1(hr = S_OK);
692 }
693
694LExit:
695 ReleaseRegKey(hkKey);
696 ReleaseStr(sczKey);
697
698 return hr;
699}
diff --git a/src/libs/dutil/WixToolset.DUtil/dictutil.cpp b/src/libs/dutil/WixToolset.DUtil/dictutil.cpp
new file mode 100644
index 00000000..0d0743eb
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/dictutil.cpp
@@ -0,0 +1,784 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define DictExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_DICTUTIL, x, s, __VA_ARGS__)
8#define DictExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_DICTUTIL, x, s, __VA_ARGS__)
9#define DictExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_DICTUTIL, x, s, __VA_ARGS__)
10#define DictExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_DICTUTIL, x, s, __VA_ARGS__)
11#define DictExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_DICTUTIL, x, s, __VA_ARGS__)
12#define DictExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_DICTUTIL, x, s, __VA_ARGS__)
13#define DictExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_DICTUTIL, p, x, e, s, __VA_ARGS__)
14#define DictExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_DICTUTIL, p, x, s, __VA_ARGS__)
15#define DictExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_DICTUTIL, p, x, e, s, __VA_ARGS__)
16#define DictExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_DICTUTIL, p, x, s, __VA_ARGS__)
17#define DictExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_DICTUTIL, e, x, s, __VA_ARGS__)
18#define DictExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_DICTUTIL, g, x, s, __VA_ARGS__)
19
20// These should all be primes, and spaced reasonably apart (currently each is about 4x the last)
21const DWORD MAX_BUCKET_SIZES[] = {
22 503,
23 2017,
24 7937,
25 32779,
26 131111,
27 524341,
28 2097709,
29 8390857,
30 33563437,
31 134253719,
32 537014927,
33 2148059509
34 };
35
36// However many items are in the cab, let's keep the buckets at least 8 times that to avoid collisions
37#define MAX_BUCKETS_TO_ITEMS_RATIO 8
38
39enum DICT_TYPE
40{
41 DICT_INVALID = 0,
42 DICT_EMBEDDED_KEY = 1,
43 DICT_STRING_LIST = 2
44};
45
46struct STRINGDICT_STRUCT
47{
48 DICT_TYPE dtType;
49
50 // Optional flags to control the behavior of the dictionary.
51 DICT_FLAG dfFlags;
52
53 // Index into MAX_BUCKET_SIZES (array of primes), representing number of buckets we've allocated
54 DWORD dwBucketSizeIndex;
55
56 // Number of items currently stored in the dict buckets
57 DWORD dwNumItems;
58
59 // Byte offset of key within bucket value, for collision checking - see
60 // comments above DictCreateEmbeddedKey() implementation for further details
61 size_t cByteOffset;
62
63 // The actual stored buckets
64 void **ppvBuckets;
65
66 // The actual stored items in the order they were added (used for auto freeing or enumerating)
67 void **ppvItemList;
68
69 // Pointer to the array of items, so the caller is free to resize the array of values out from under us without harm
70 void **ppvValueArray;
71};
72
73const int STRINGDICT_HANDLE_BYTES = sizeof(STRINGDICT_STRUCT);
74
75static HRESULT StringHash(
76 __in const STRINGDICT_STRUCT *psd,
77 __in DWORD dwNumBuckets,
78 __in_z LPCWSTR pszString,
79 __out DWORD *pdwHash
80 );
81static BOOL IsMatchExact(
82 __in const STRINGDICT_STRUCT *psd,
83 __in DWORD dwMatchIndex,
84 __in_z LPCWSTR wzOriginalString
85 );
86static HRESULT GetValue(
87 __in const STRINGDICT_STRUCT *psd,
88 __in_z LPCWSTR pszString,
89 __out_opt void **ppvValue
90 );
91static HRESULT GetInsertIndex(
92 __in const STRINGDICT_STRUCT *psd,
93 __in DWORD dwBucketCount,
94 __in void **ppvBuckets,
95 __in_z LPCWSTR pszString,
96 __out DWORD *pdwOutput
97 );
98static HRESULT GetIndex(
99 __in const STRINGDICT_STRUCT *psd,
100 __in_z LPCWSTR pszString,
101 __out DWORD *pdwOutput
102 );
103static LPCWSTR GetKey(
104 __in const STRINGDICT_STRUCT *psd,
105 __in void *pvValue
106 );
107static HRESULT GrowDictionary(
108 __inout STRINGDICT_STRUCT *psd
109 );
110// These 2 helper functions allow us to safely handle dictutil consumers resizing
111// the value array by storing "offsets" instead of raw void *'s in our buckets.
112static void * TranslateOffsetToValue(
113 __in const STRINGDICT_STRUCT *psd,
114 __in void *pvValue
115 );
116static void * TranslateValueToOffset(
117 __in const STRINGDICT_STRUCT *psd,
118 __in void *pvValue
119 );
120
121// The dict will store a set of keys (as wide-char strings) and a set of values associated with those keys (as void *'s).
122// However, to support collision checking, the key needs to be represented in the "value" object (pointed to
123// by the void *). The "stByteOffset" parameter tells this dict the byte offset of the "key" string pointer
124// within the "value" object. Use the offsetof() macro to fill this out.
125// The "ppvArray" parameter gives dictutil the address of your value array. If you provide this parameter,
126// dictutil will remember all pointer values provided as "offsets" against this array. It is only necessary to provide
127// this parameter to dictutil if it is possible you will realloc the array.
128//
129// Use DictAddValue() and DictGetValue() with this dictionary type.
130extern "C" HRESULT DAPI DictCreateWithEmbeddedKey(
131 __out_bcount(STRINGDICT_HANDLE_BYTES) STRINGDICT_HANDLE* psdHandle,
132 __in DWORD dwNumExpectedItems,
133 __in_opt void **ppvArray,
134 __in size_t cByteOffset,
135 __in DICT_FLAG dfFlags
136 )
137{
138 HRESULT hr = S_OK;
139
140 DictExitOnNull(psdHandle, hr, E_INVALIDARG, "Handle not specified while creating dict");
141
142 // Allocate the handle
143 *psdHandle = static_cast<STRINGDICT_HANDLE>(MemAlloc(sizeof(STRINGDICT_STRUCT), FALSE));
144 DictExitOnNull(*psdHandle, hr, E_OUTOFMEMORY, "Failed to allocate dictionary object");
145
146 STRINGDICT_STRUCT *psd = static_cast<STRINGDICT_STRUCT *>(*psdHandle);
147
148 // Fill out the new handle's values
149 psd->dtType = DICT_EMBEDDED_KEY;
150 psd->dfFlags = dfFlags;
151 psd->cByteOffset = cByteOffset;
152 psd->dwBucketSizeIndex = 0;
153 psd->dwNumItems = 0;
154 psd->ppvItemList = NULL;
155 psd->ppvValueArray = ppvArray;
156
157 // Make psd->dwBucketSizeIndex point to the appropriate spot in the prime
158 // array based on expected number of items and items to buckets ratio
159 // Careful: the "-1" in "countof(MAX_BUCKET_SIZES)-1" ensures we don't end
160 // this loop past the end of the array!
161 while (psd->dwBucketSizeIndex < (countof(MAX_BUCKET_SIZES)-1) &&
162 MAX_BUCKET_SIZES[psd->dwBucketSizeIndex] < dwNumExpectedItems * MAX_BUCKETS_TO_ITEMS_RATIO)
163 {
164 ++psd->dwBucketSizeIndex;
165 }
166
167 // Finally, allocate our initial buckets
168 psd->ppvBuckets = static_cast<void**>(MemAlloc(sizeof(void *) * MAX_BUCKET_SIZES[psd->dwBucketSizeIndex], TRUE));
169 DictExitOnNull(psd->ppvBuckets, hr, E_OUTOFMEMORY, "Failed to allocate buckets for dictionary");
170
171LExit:
172 return hr;
173}
174
175// The dict will store a set of keys, with no values associated with them. Use DictAddKey() and DictKeyExists() with this dictionary type.
176extern "C" HRESULT DAPI DictCreateStringList(
177 __out_bcount(STRINGDICT_HANDLE_BYTES) STRINGDICT_HANDLE* psdHandle,
178 __in DWORD dwNumExpectedItems,
179 __in DICT_FLAG dfFlags
180 )
181{
182 HRESULT hr = S_OK;
183
184 DictExitOnNull(psdHandle, hr, E_INVALIDARG, "Handle not specified while creating dict");
185
186 // Allocate the handle
187 *psdHandle = static_cast<STRINGDICT_HANDLE>(MemAlloc(sizeof(STRINGDICT_STRUCT), FALSE));
188 DictExitOnNull(*psdHandle, hr, E_OUTOFMEMORY, "Failed to allocate dictionary object");
189
190 STRINGDICT_STRUCT *psd = static_cast<STRINGDICT_STRUCT *>(*psdHandle);
191
192 // Fill out the new handle's values
193 psd->dtType = DICT_STRING_LIST;
194 psd->dfFlags = dfFlags;
195 psd->cByteOffset = 0;
196 psd->dwBucketSizeIndex = 0;
197 psd->dwNumItems = 0;
198 psd->ppvItemList = NULL;
199 psd->ppvValueArray = NULL;
200
201 // Make psd->dwBucketSizeIndex point to the appropriate spot in the prime
202 // array based on expected number of items and items to buckets ratio
203 // Careful: the "-1" in "countof(MAX_BUCKET_SIZES)-1" ensures we don't end
204 // this loop past the end of the array!
205 while (psd->dwBucketSizeIndex < (countof(MAX_BUCKET_SIZES)-1) &&
206 MAX_BUCKET_SIZES[psd->dwBucketSizeIndex] < dwNumExpectedItems * MAX_BUCKETS_TO_ITEMS_RATIO)
207 {
208 ++psd->dwBucketSizeIndex;
209 }
210
211 // Finally, allocate our initial buckets
212 psd->ppvBuckets = static_cast<void**>(MemAlloc(sizeof(void *) * MAX_BUCKET_SIZES[psd->dwBucketSizeIndex], TRUE));
213 DictExitOnNull(psd->ppvBuckets, hr, E_OUTOFMEMORY, "Failed to allocate buckets for dictionary");
214
215LExit:
216 return hr;
217}
218
219extern "C" HRESULT DAPI DictCreateStringListFromArray(
220 __out_bcount(STRINGDICT_HANDLE_BYTES) STRINGDICT_HANDLE* psdHandle,
221 __in_ecount(cStringArray) const LPCWSTR* rgwzStringArray,
222 __in const DWORD cStringArray,
223 __in DICT_FLAG dfFlags
224 )
225{
226 HRESULT hr = S_OK;
227 STRINGDICT_HANDLE sd = NULL;
228
229 hr = DictCreateStringList(&sd, cStringArray, dfFlags);
230 DictExitOnFailure(hr, "Failed to create the string dictionary.");
231
232 for (DWORD i = 0; i < cStringArray; ++i)
233 {
234 const LPCWSTR wzKey = rgwzStringArray[i];
235
236 hr = DictKeyExists(sd, wzKey);
237 if (E_NOTFOUND != hr)
238 {
239 DictExitOnFailure(hr, "Failed to check the string dictionary.");
240 }
241 else
242 {
243 hr = DictAddKey(sd, wzKey);
244 DictExitOnFailure(hr, "Failed to add \"%ls\" to the string dictionary.", wzKey);
245 }
246 }
247
248 *psdHandle = sd;
249 sd = NULL;
250
251LExit:
252 ReleaseDict(sd);
253
254 return hr;
255}
256
257extern "C" HRESULT DAPI DictCompareStringListToArray(
258 __in_bcount(STRINGDICT_HANDLE_BYTES) STRINGDICT_HANDLE sdStringList,
259 __in_ecount(cStringArray) const LPCWSTR* rgwzStringArray,
260 __in const DWORD cStringArray
261 )
262{
263 HRESULT hr = S_OK;
264
265 for (DWORD i = 0; i < cStringArray; ++i)
266 {
267 hr = DictKeyExists(sdStringList, rgwzStringArray[i]);
268 if (E_NOTFOUND != hr)
269 {
270 DictExitOnFailure(hr, "Failed to check the string dictionary.");
271 ExitFunction1(hr = S_OK);
272 }
273 }
274
275 ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_NO_MATCH));
276
277LExit:
278 return hr;
279}
280
281// Todo: Dict should resize itself when (number of items) exceeds (number of buckets / MAX_BUCKETS_TO_ITEMS_RATIO)
282extern "C" HRESULT DAPI DictAddKey(
283 __in_bcount(STRINGDICT_HANDLE_BYTES) STRINGDICT_HANDLE sdHandle,
284 __in_z LPCWSTR pszString
285 )
286{
287 HRESULT hr = S_OK;
288 DWORD dwIndex = 0;
289 STRINGDICT_STRUCT *psd = static_cast<STRINGDICT_STRUCT *>(sdHandle);
290
291 DictExitOnNull(sdHandle, hr, E_INVALIDARG, "Handle not specified while adding value to dict");
292 DictExitOnNull(pszString, hr, E_INVALIDARG, "String not specified while adding value to dict");
293
294 if (psd->dwBucketSizeIndex >= countof(MAX_BUCKET_SIZES))
295 {
296 hr = E_INVALIDARG;
297 DictExitOnFailure(hr, "Invalid dictionary - bucket size index is out of range");
298 }
299
300 if (DICT_STRING_LIST != psd->dtType)
301 {
302 hr = E_INVALIDARG;
303 DictExitOnFailure(hr, "Tried to add key without value to wrong dictionary type! This dictionary type is: %d", psd->dtType);
304 }
305
306 if ((psd->dwNumItems + 1) >= MAX_BUCKET_SIZES[psd->dwBucketSizeIndex] / MAX_BUCKETS_TO_ITEMS_RATIO)
307 {
308 hr = GrowDictionary(psd);
309 if (HRESULT_FROM_WIN32(ERROR_DATABASE_FULL) == hr)
310 {
311 // If we fail to proactively grow the dictionary, don't fail unless the dictionary is completely full
312 if (psd->dwNumItems < MAX_BUCKET_SIZES[psd->dwBucketSizeIndex])
313 {
314 hr = S_OK;
315 }
316 }
317 DictExitOnFailure(hr, "Failed to grow dictionary");
318 }
319
320 hr = GetInsertIndex(psd, MAX_BUCKET_SIZES[psd->dwBucketSizeIndex], psd->ppvBuckets, pszString, &dwIndex);
321 DictExitOnFailure(hr, "Failed to get index to insert into");
322
323 hr = MemEnsureArraySize(reinterpret_cast<void **>(&(psd->ppvItemList)), psd->dwNumItems + 1, sizeof(void *), 1000);
324 DictExitOnFailure(hr, "Failed to resize list of items in dictionary");
325 ++psd->dwNumItems;
326
327 hr = StrAllocString(reinterpret_cast<LPWSTR *>(&(psd->ppvBuckets[dwIndex])), pszString, 0);
328 DictExitOnFailure(hr, "Failed to allocate copy of string");
329
330 psd->ppvItemList[psd->dwNumItems-1] = psd->ppvBuckets[dwIndex];
331
332LExit:
333 return hr;
334}
335
336// Todo: Dict should resize itself when (number of items) exceeds (number of buckets / MAX_BUCKETS_TO_ITEMS_RATIO)
337extern "C" HRESULT DAPI DictAddValue(
338 __in_bcount(STRINGDICT_HANDLE_BYTES) STRINGDICT_HANDLE sdHandle,
339 __in void *pvValue
340 )
341{
342 HRESULT hr = S_OK;
343 void *pvOffset = NULL;
344 LPCWSTR wzKey = NULL;
345 DWORD dwIndex = 0;
346 STRINGDICT_STRUCT *psd = static_cast<STRINGDICT_STRUCT *>(sdHandle);
347
348 DictExitOnNull(sdHandle, hr, E_INVALIDARG, "Handle not specified while adding value to dict");
349 DictExitOnNull(pvValue, hr, E_INVALIDARG, "Value not specified while adding value to dict");
350
351 if (psd->dwBucketSizeIndex >= countof(MAX_BUCKET_SIZES))
352 {
353 hr = E_INVALIDARG;
354 DictExitOnFailure(hr, "Invalid dictionary - bucket size index is out of range");
355 }
356
357 if (DICT_EMBEDDED_KEY != psd->dtType)
358 {
359 hr = E_INVALIDARG;
360 DictExitOnFailure(hr, "Tried to add key/value pair to wrong dictionary type! This dictionary type is: %d", psd->dtType);
361 }
362
363 wzKey = GetKey(psd, pvValue);
364 DictExitOnNull(wzKey, hr, E_INVALIDARG, "String not specified while adding value to dict");
365
366 if ((psd->dwNumItems + 1) >= MAX_BUCKET_SIZES[psd->dwBucketSizeIndex] / MAX_BUCKETS_TO_ITEMS_RATIO)
367 {
368 hr = GrowDictionary(psd);
369 if (HRESULT_FROM_WIN32(ERROR_DATABASE_FULL) == hr && psd->dwNumItems + 1 )
370 {
371 // If we fail to proactively grow the dictionary, don't fail unless the dictionary is completely full
372 if (psd->dwNumItems < MAX_BUCKET_SIZES[psd->dwBucketSizeIndex])
373 {
374 hr = S_OK;
375 }
376 }
377 DictExitOnFailure(hr, "Failed to grow dictionary");
378 }
379
380 hr = GetInsertIndex(psd, MAX_BUCKET_SIZES[psd->dwBucketSizeIndex], psd->ppvBuckets, wzKey, &dwIndex);
381 DictExitOnFailure(hr, "Failed to get index to insert into");
382
383 hr = MemEnsureArraySize(reinterpret_cast<void **>(&(psd->ppvItemList)), psd->dwNumItems + 1, sizeof(void *), 1000);
384 DictExitOnFailure(hr, "Failed to resize list of items in dictionary");
385 ++psd->dwNumItems;
386
387 pvOffset = TranslateValueToOffset(psd, pvValue);
388 psd->ppvBuckets[dwIndex] = pvOffset;
389 psd->ppvItemList[psd->dwNumItems-1] = pvOffset;
390
391LExit:
392 return hr;
393}
394
395extern "C" HRESULT DAPI DictGetValue(
396 __in_bcount(STRINGDICT_HANDLE_BYTES) C_STRINGDICT_HANDLE sdHandle,
397 __in_z LPCWSTR pszString,
398 __out void **ppvValue
399 )
400{
401 HRESULT hr = S_OK;
402
403 DictExitOnNull(sdHandle, hr, E_INVALIDARG, "Handle not specified while searching dict");
404 DictExitOnNull(pszString, hr, E_INVALIDARG, "String not specified while searching dict");
405
406 const STRINGDICT_STRUCT *psd = static_cast<const STRINGDICT_STRUCT *>(sdHandle);
407
408 if (DICT_EMBEDDED_KEY != psd->dtType)
409 {
410 hr = E_INVALIDARG;
411 DictExitOnFailure(hr, "Tried to lookup value in wrong dictionary type! This dictionary type is: %d", psd->dtType);
412 }
413
414 hr = GetValue(psd, pszString, ppvValue);
415 if (E_NOTFOUND == hr)
416 {
417 ExitFunction();
418 }
419 DictExitOnFailure(hr, "Failed to call internal GetValue()");
420
421LExit:
422 return hr;
423}
424
425extern "C" HRESULT DAPI DictKeyExists(
426 __in_bcount(STRINGDICT_HANDLE_BYTES) C_STRINGDICT_HANDLE sdHandle,
427 __in_z LPCWSTR pszString
428 )
429{
430 HRESULT hr = S_OK;
431
432 DictExitOnNull(sdHandle, hr, E_INVALIDARG, "Handle not specified while searching dict");
433 DictExitOnNull(pszString, hr, E_INVALIDARG, "String not specified while searching dict");
434
435 const STRINGDICT_STRUCT *psd = static_cast<const STRINGDICT_STRUCT *>(sdHandle);
436
437 // This works with either type of dictionary
438 hr = GetValue(psd, pszString, NULL);
439 if (E_NOTFOUND == hr)
440 {
441 ExitFunction();
442 }
443 DictExitOnFailure(hr, "Failed to call internal GetValue()");
444
445LExit:
446 return hr;
447}
448
449extern "C" void DAPI DictDestroy(
450 __in_bcount(STRINGDICT_HANDLE_BYTES) STRINGDICT_HANDLE sdHandle
451 )
452{
453 DWORD i;
454
455 STRINGDICT_STRUCT *psd = static_cast<STRINGDICT_STRUCT *>(sdHandle);
456
457 if (DICT_STRING_LIST == psd->dtType)
458 {
459 for (i = 0; i < psd->dwNumItems; ++i)
460 {
461 ReleaseStr(reinterpret_cast<LPWSTR>(psd->ppvItemList[i]));
462 }
463 }
464
465 ReleaseMem(psd->ppvItemList);
466 ReleaseMem(psd->ppvBuckets);
467 ReleaseMem(psd);
468}
469
470static HRESULT StringHash(
471 __in const STRINGDICT_STRUCT *psd,
472 __in DWORD dwNumBuckets,
473 __in_z LPCWSTR pszString,
474 __out DWORD *pdwHash
475 )
476{
477 HRESULT hr = S_OK;
478 LPCWSTR wzKey = NULL;
479 LPWSTR sczNewKey = NULL;
480 DWORD result = 0;
481
482 if (DICT_FLAG_CASEINSENSITIVE & psd->dfFlags)
483 {
484 hr = StrAllocStringToUpperInvariant(&sczNewKey, pszString, 0);
485 DictExitOnFailure(hr, "Failed to convert the string to upper-case.");
486
487 wzKey = sczNewKey;
488 }
489 else
490 {
491 wzKey = pszString;
492 }
493
494 while (*wzKey)
495 {
496 result = ~(*wzKey++ * 509) + result * 65599;
497 }
498
499 *pdwHash = result % dwNumBuckets;
500
501LExit:
502 ReleaseStr(sczNewKey);
503
504 return hr;
505}
506
507static BOOL IsMatchExact(
508 __in const STRINGDICT_STRUCT *psd,
509 __in DWORD dwMatchIndex,
510 __in_z LPCWSTR wzOriginalString
511 )
512{
513 LPCWSTR wzMatchString = GetKey(psd, TranslateOffsetToValue(psd, psd->ppvBuckets[dwMatchIndex]));
514 DWORD dwFlags = 0;
515
516 if (DICT_FLAG_CASEINSENSITIVE & psd->dfFlags)
517 {
518 dwFlags |= NORM_IGNORECASE;
519 }
520
521 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, dwFlags, wzOriginalString, -1, wzMatchString, -1))
522 {
523 return TRUE;
524 }
525
526 return FALSE;
527}
528
529static HRESULT GetValue(
530 __in const STRINGDICT_STRUCT *psd,
531 __in_z LPCWSTR pszString,
532 __out_opt void **ppvValue
533 )
534{
535 HRESULT hr = S_OK;
536 DWORD dwOriginalIndexCandidate = 0;
537 void *pvCandidateValue = NULL;
538 DWORD dwIndex = 0;
539
540 DictExitOnNull(psd, hr, E_INVALIDARG, "Handle not specified while searching dict");
541 DictExitOnNull(pszString, hr, E_INVALIDARG, "String not specified while searching dict");
542
543 if (psd->dwBucketSizeIndex >= countof(MAX_BUCKET_SIZES))
544 {
545 hr = E_INVALIDARG;
546 DictExitOnFailure(hr, "Invalid dictionary - bucket size index is out of range");
547 }
548
549 hr = StringHash(psd, MAX_BUCKET_SIZES[psd->dwBucketSizeIndex], pszString, &dwOriginalIndexCandidate);
550 DictExitOnFailure(hr, "Failed to hash the string.");
551
552 DWORD dwIndexCandidate = dwOriginalIndexCandidate;
553
554 pvCandidateValue = TranslateOffsetToValue(psd, psd->ppvBuckets[dwIndexCandidate]);
555
556 // If no match exists in the dict
557 if (NULL == pvCandidateValue)
558 {
559 if (NULL != ppvValue)
560 {
561 *ppvValue = NULL;
562 }
563 ExitFunction1(hr = E_NOTFOUND);
564 }
565
566 hr = GetIndex(psd, pszString, &dwIndex);
567 if (E_NOTFOUND == hr)
568 {
569 ExitFunction();
570 }
571 DictExitOnFailure(hr, "Failed to find index to get");
572
573 if (NULL != ppvValue)
574 {
575 *ppvValue = TranslateOffsetToValue(psd, psd->ppvBuckets[dwIndex]);
576 }
577
578LExit:
579 if (FAILED(hr) && NULL != ppvValue)
580 {
581 *ppvValue = NULL;
582 }
583
584 return hr;
585}
586
587static HRESULT GetInsertIndex(
588 __in const STRINGDICT_STRUCT *psd,
589 __in DWORD dwBucketCount,
590 __in void **ppvBuckets,
591 __in_z LPCWSTR pszString,
592 __out DWORD *pdwOutput
593 )
594{
595 HRESULT hr = S_OK;
596 DWORD dwOriginalIndexCandidate = 0;
597
598 hr = StringHash(psd, dwBucketCount, pszString, &dwOriginalIndexCandidate);
599 DictExitOnFailure(hr, "Failed to hash the string.");
600
601 DWORD dwIndexCandidate = dwOriginalIndexCandidate;
602
603 // If we collide, keep iterating forward from our intended position, even wrapping around to zero, until we find an empty bucket
604#pragma prefast(push)
605#pragma prefast(disable:26007)
606 while (NULL != ppvBuckets[dwIndexCandidate])
607#pragma prefast(pop)
608 {
609 ++dwIndexCandidate;
610
611 // If we got to the end of the array, wrap around to zero index
612 if (dwIndexCandidate >= dwBucketCount)
613 {
614 dwIndexCandidate = 0;
615 }
616
617 // If we wrapped all the way back around to our original index, the dict is full - throw an error
618 if (dwIndexCandidate == dwOriginalIndexCandidate)
619 {
620 // The dict table is full - this error seems to be a reasonably close match
621 hr = HRESULT_FROM_WIN32(ERROR_DATABASE_FULL);
622 DictExitOnRootFailure(hr, "Failed to add item '%ls' to dict table because dict table is full of items", pszString);
623 }
624 }
625
626 *pdwOutput = dwIndexCandidate;
627
628LExit:
629 return hr;
630}
631
632static HRESULT GetIndex(
633 __in const STRINGDICT_STRUCT *psd,
634 __in_z LPCWSTR pszString,
635 __out DWORD *pdwOutput
636 )
637{
638 HRESULT hr = S_OK;
639 DWORD dwOriginalIndexCandidate = 0;
640
641 if (psd->dwBucketSizeIndex >= countof(MAX_BUCKET_SIZES))
642 {
643 hr = E_INVALIDARG;
644 DictExitOnFailure(hr, "Invalid dictionary - bucket size index is out of range");
645 }
646
647 hr = StringHash(psd, MAX_BUCKET_SIZES[psd->dwBucketSizeIndex], pszString, &dwOriginalIndexCandidate);
648 DictExitOnFailure(hr, "Failed to hash the string.");
649
650 DWORD dwIndexCandidate = dwOriginalIndexCandidate;
651
652 while (!IsMatchExact(psd, dwIndexCandidate, pszString))
653 {
654 ++dwIndexCandidate;
655
656 // If we got to the end of the array, wrap around to zero index
657 if (dwIndexCandidate >= MAX_BUCKET_SIZES[psd->dwBucketSizeIndex])
658 {
659 dwIndexCandidate = 0;
660 }
661
662 // If no match exists in the dict
663 if (NULL == psd->ppvBuckets[dwIndexCandidate])
664 {
665 ExitFunction1(hr = E_NOTFOUND);
666 }
667
668 // If we wrapped all the way back around to our original index, the dict is full and we found nothing, so return as such
669 if (dwIndexCandidate == dwOriginalIndexCandidate)
670 {
671 ExitFunction1(hr = E_NOTFOUND);
672 }
673 }
674
675 *pdwOutput = dwIndexCandidate;
676
677LExit:
678 return hr;
679}
680
681static LPCWSTR GetKey(
682 __in const STRINGDICT_STRUCT *psd,
683 __in void *pvValue
684 )
685{
686 const BYTE *lpByte = reinterpret_cast<BYTE *>(pvValue);
687
688 if (DICT_EMBEDDED_KEY == psd->dtType)
689 {
690 void *pvKey = reinterpret_cast<void *>(reinterpret_cast<BYTE *>(pvValue) + psd->cByteOffset);
691
692#pragma prefast(push)
693#pragma prefast(disable:26010)
694 return *(reinterpret_cast<LPCWSTR *>(pvKey));
695#pragma prefast(pop)
696 }
697 else
698 {
699 return (reinterpret_cast<LPCWSTR>(lpByte));
700 }
701}
702
703static HRESULT GrowDictionary(
704 __inout STRINGDICT_STRUCT *psd
705 )
706{
707 HRESULT hr = S_OK;
708 DWORD dwInsertIndex = 0;
709 LPCWSTR wzKey = NULL;
710 DWORD dwNewBucketSizeIndex = 0;
711 size_t cbAllocSize = 0;
712 void **ppvNewBuckets = NULL;
713
714 dwNewBucketSizeIndex = psd->dwBucketSizeIndex + 1;
715
716 if (dwNewBucketSizeIndex >= countof(MAX_BUCKET_SIZES))
717 {
718 ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_DATABASE_FULL));
719 }
720
721 hr = ::SizeTMult(sizeof(void *), MAX_BUCKET_SIZES[dwNewBucketSizeIndex], &cbAllocSize);
722 DictExitOnFailure(hr, "Overflow while calculating allocation size to grow dictionary");
723
724 ppvNewBuckets = static_cast<void**>(MemAlloc(cbAllocSize, TRUE));
725 DictExitOnNull(ppvNewBuckets, hr, E_OUTOFMEMORY, "Failed to allocate %u buckets while growing dictionary", MAX_BUCKET_SIZES[dwNewBucketSizeIndex]);
726
727 for (DWORD i = 0; i < psd->dwNumItems; ++i)
728 {
729 wzKey = GetKey(psd, TranslateOffsetToValue(psd, psd->ppvItemList[i]));
730 DictExitOnNull(wzKey, hr, E_INVALIDARG, "String not specified in existing dict value");
731
732 hr = GetInsertIndex(psd, MAX_BUCKET_SIZES[dwNewBucketSizeIndex], ppvNewBuckets, wzKey, &dwInsertIndex);
733 DictExitOnFailure(hr, "Failed to get index to insert into");
734
735 ppvNewBuckets[dwInsertIndex] = psd->ppvItemList[i];
736 }
737
738 psd->dwBucketSizeIndex = dwNewBucketSizeIndex;
739 ReleaseMem(psd->ppvBuckets);
740 psd->ppvBuckets = ppvNewBuckets;
741 ppvNewBuckets = NULL;
742
743LExit:
744 ReleaseMem(ppvNewBuckets);
745
746 return hr;
747}
748
749static void * TranslateOffsetToValue(
750 __in const STRINGDICT_STRUCT *psd,
751 __in void *pvValue
752 )
753{
754 if (NULL == pvValue)
755 {
756 return NULL;
757 }
758
759 // All offsets are stored as (real offset + 1), so subtract 1 to get back to the real value
760 if (NULL != psd->ppvValueArray)
761 {
762 return reinterpret_cast<void *>(reinterpret_cast<DWORD_PTR>(pvValue) + reinterpret_cast<DWORD_PTR>(*psd->ppvValueArray) - 1);
763 }
764 else
765 {
766 return pvValue;
767 }
768}
769
770static void * TranslateValueToOffset(
771 __in const STRINGDICT_STRUCT *psd,
772 __in void *pvValue
773 )
774{
775 if (NULL != psd->ppvValueArray)
776 {
777 // 0 has a special meaning - we don't want offset 0 into the array to have NULL for the offset - so add 1 to avoid this issue
778 return reinterpret_cast<void *>(reinterpret_cast<DWORD_PTR>(pvValue) - reinterpret_cast<DWORD_PTR>(*psd->ppvValueArray) + 1);
779 }
780 else
781 {
782 return pvValue;
783 }
784}
diff --git a/src/libs/dutil/WixToolset.DUtil/dirutil.cpp b/src/libs/dutil/WixToolset.DUtil/dirutil.cpp
new file mode 100644
index 00000000..ae2c5e1c
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/dirutil.cpp
@@ -0,0 +1,441 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define DirExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_DIRUTIL, x, s, __VA_ARGS__)
8#define DirExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_DIRUTIL, x, s, __VA_ARGS__)
9#define DirExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_DIRUTIL, x, s, __VA_ARGS__)
10#define DirExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_DIRUTIL, x, s, __VA_ARGS__)
11#define DirExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_DIRUTIL, x, s, __VA_ARGS__)
12#define DirExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_DIRUTIL, x, s, __VA_ARGS__)
13#define DirExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_DIRUTIL, p, x, e, s, __VA_ARGS__)
14#define DirExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_DIRUTIL, p, x, s, __VA_ARGS__)
15#define DirExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_DIRUTIL, p, x, e, s, __VA_ARGS__)
16#define DirExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_DIRUTIL, p, x, s, __VA_ARGS__)
17#define DirExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_DIRUTIL, e, x, s, __VA_ARGS__)
18#define DirExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_DIRUTIL, g, x, s, __VA_ARGS__)
19
20
21/*******************************************************************
22 DirExists
23
24*******************************************************************/
25extern "C" BOOL DAPI DirExists(
26 __in_z LPCWSTR wzPath,
27 __out_opt DWORD *pdwAttributes
28 )
29{
30 Assert(wzPath);
31
32 BOOL fExists = FALSE;
33
34 DWORD dwAttributes = ::GetFileAttributesW(wzPath);
35 if (0xFFFFFFFF == dwAttributes) // TODO: figure out why "INVALID_FILE_ATTRIBUTES" can't be used here
36 {
37 ExitFunction();
38 }
39
40 if (dwAttributes & FILE_ATTRIBUTE_DIRECTORY)
41 {
42 if (pdwAttributes)
43 {
44 *pdwAttributes = dwAttributes;
45 }
46
47 fExists = TRUE;
48 }
49
50LExit:
51 return fExists;
52}
53
54
55/*******************************************************************
56 DirCreateTempPath
57
58 *******************************************************************/
59extern "C" HRESULT DAPI DirCreateTempPath(
60 __in_z LPCWSTR wzPrefix,
61 __out_ecount_z(cchPath) LPWSTR wzPath,
62 __in DWORD cchPath
63 )
64{
65 Assert(wzPrefix);
66 Assert(wzPath);
67
68 HRESULT hr = S_OK;
69
70 WCHAR wzDir[MAX_PATH];
71 WCHAR wzFile[MAX_PATH];
72 DWORD cch = 0;
73
74 cch = ::GetTempPathW(countof(wzDir), wzDir);
75 if (!cch || cch >= countof(wzDir))
76 {
77 DirExitWithLastError(hr, "Failed to GetTempPath.");
78 }
79
80 if (!::GetTempFileNameW(wzDir, wzPrefix, 0, wzFile))
81 {
82 DirExitWithLastError(hr, "Failed to GetTempFileName.");
83 }
84
85 hr = ::StringCchCopyW(wzPath, cchPath, wzFile);
86
87LExit:
88 return hr;
89}
90
91
92/*******************************************************************
93 DirEnsureExists
94
95*******************************************************************/
96extern "C" HRESULT DAPI DirEnsureExists(
97 __in_z LPCWSTR wzPath,
98 __in_opt LPSECURITY_ATTRIBUTES psa
99 )
100{
101 HRESULT hr = S_OK;
102 UINT er;
103
104 // try to create this directory
105 if (!::CreateDirectoryW(wzPath, psa))
106 {
107 // if the directory already exists, bail
108 er = ::GetLastError();
109 if (ERROR_ALREADY_EXISTS == er)
110 {
111 ExitFunction1(hr = S_OK);
112 }
113 else if (ERROR_PATH_NOT_FOUND != er && DirExists(wzPath, NULL)) // if the directory happens to exist (don't check if CreateDirectory said it doesn't), declare success.
114 {
115 ExitFunction1(hr = S_OK);
116 }
117
118 // get the parent path and try to create it
119 LPWSTR pwzLastSlash = NULL;
120 for (LPWSTR pwz = const_cast<LPWSTR>(wzPath); *pwz; ++pwz)
121 {
122 if (*pwz == L'\\')
123 {
124 pwzLastSlash = pwz;
125 }
126 }
127
128 // if there is no parent directory fail
129 DirExitOnNullDebugTrace(pwzLastSlash, hr, HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND), "cannot find parent path");
130
131 *pwzLastSlash = L'\0'; // null terminate the parent path
132 hr = DirEnsureExists(wzPath, psa); // recurse!
133 *pwzLastSlash = L'\\'; // put the slash back
134 DirExitOnFailureDebugTrace(hr, "failed to create path: %ls", wzPath);
135
136 // try to create the directory now that all parents are created
137 if (!::CreateDirectoryW(wzPath, psa))
138 {
139 // if the directory already exists for some reason no error
140 er = ::GetLastError();
141 if (ERROR_ALREADY_EXISTS == er)
142 {
143 hr = S_FALSE;
144 }
145 else
146 {
147 hr = HRESULT_FROM_WIN32(er);
148 }
149 }
150 else
151 {
152 hr = S_OK;
153 }
154 }
155
156LExit:
157 return hr;
158}
159
160
161/*******************************************************************
162 DirEnsureDelete - removes an entire directory structure
163
164*******************************************************************/
165extern "C" HRESULT DAPI DirEnsureDelete(
166 __in_z LPCWSTR wzPath,
167 __in BOOL fDeleteFiles,
168 __in BOOL fRecurse
169 )
170{
171 HRESULT hr = S_OK;
172 DWORD dwDeleteFlags = 0;
173
174 dwDeleteFlags |= fDeleteFiles ? DIR_DELETE_FILES : 0;
175 dwDeleteFlags |= fRecurse ? DIR_DELETE_RECURSE : 0;
176
177 hr = DirEnsureDeleteEx(wzPath, dwDeleteFlags);
178 return hr;
179}
180
181
182/*******************************************************************
183 DirEnsureDeleteEx - removes an entire directory structure
184
185*******************************************************************/
186extern "C" HRESULT DAPI DirEnsureDeleteEx(
187 __in_z LPCWSTR wzPath,
188 __in DWORD dwFlags
189 )
190{
191 Assert(wzPath && *wzPath);
192
193 HRESULT hr = S_OK;
194 DWORD er;
195
196 DWORD dwAttrib;
197 HANDLE hFind = INVALID_HANDLE_VALUE;
198 LPWSTR sczDelete = NULL;
199 WIN32_FIND_DATAW wfd;
200
201 BOOL fDeleteFiles = (DIR_DELETE_FILES == (dwFlags & DIR_DELETE_FILES));
202 BOOL fRecurse = (DIR_DELETE_RECURSE == (dwFlags & DIR_DELETE_RECURSE));
203 BOOL fScheduleDelete = (DIR_DELETE_SCHEDULE == (dwFlags & DIR_DELETE_SCHEDULE));
204 WCHAR wzTempDirectory[MAX_PATH] = { };
205 WCHAR wzTempPath[MAX_PATH] = { };
206
207 if (-1 == (dwAttrib = ::GetFileAttributesW(wzPath)))
208 {
209 er = ::GetLastError();
210 if (ERROR_FILE_NOT_FOUND == er) // change "file not found" to "path not found" since we were looking for a directory.
211 {
212 er = ERROR_PATH_NOT_FOUND;
213 }
214 hr = HRESULT_FROM_WIN32(er);
215 DirExitOnRootFailure(hr, "Failed to get attributes for path: %ls", wzPath);
216 }
217
218 if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)
219 {
220 if (dwAttrib & FILE_ATTRIBUTE_READONLY)
221 {
222 if (!::SetFileAttributesW(wzPath, FILE_ATTRIBUTE_NORMAL))
223 {
224 DirExitWithLastError(hr, "Failed to remove read-only attribute from path: %ls", wzPath);
225 }
226 }
227
228 // If we're deleting files and/or child directories loop through the contents of the directory.
229 if (fDeleteFiles || fRecurse)
230 {
231 if (fScheduleDelete)
232 {
233 if (!::GetTempPathW(countof(wzTempDirectory), wzTempDirectory))
234 {
235 DirExitWithLastError(hr, "Failed to get temp directory.");
236 }
237 }
238
239 // Delete everything in this directory.
240 hr = PathConcat(wzPath, L"*.*", &sczDelete);
241 DirExitOnFailure(hr, "Failed to concat wild cards to string: %ls", wzPath);
242
243 hFind = ::FindFirstFileW(sczDelete, &wfd);
244 if (INVALID_HANDLE_VALUE == hFind)
245 {
246 DirExitWithLastError(hr, "failed to get first file in directory: %ls", wzPath);
247 }
248
249 do
250 {
251 // Skip the dot directories.
252 if (L'.' == wfd.cFileName[0] && (L'\0' == wfd.cFileName[1] || (L'.' == wfd.cFileName[1] && L'\0' == wfd.cFileName[2])))
253 {
254 continue;
255 }
256
257 // For extra safety and to silence OACR.
258 wfd.cFileName[MAX_PATH - 1] = L'\0';
259
260 hr = PathConcat(wzPath, wfd.cFileName, &sczDelete);
261 DirExitOnFailure(hr, "Failed to concat filename '%ls' to directory: %ls", wfd.cFileName, wzPath);
262
263 if (fRecurse && wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
264 {
265 hr = PathBackslashTerminate(&sczDelete);
266 DirExitOnFailure(hr, "Failed to ensure path is backslash terminated: %ls", sczDelete);
267
268 hr = DirEnsureDeleteEx(sczDelete, dwFlags); // recursive call
269 if (FAILED(hr))
270 {
271 // if we failed to delete a subdirectory, keep trying to finish any remaining files
272 ExitTraceSource(DUTIL_SOURCE_DIRUTIL, hr, "Failed to delete subdirectory; continuing: %ls", sczDelete);
273 hr = S_OK;
274 }
275 }
276 else if (fDeleteFiles) // this is a file, just delete it
277 {
278 if (wfd.dwFileAttributes & FILE_ATTRIBUTE_READONLY || wfd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN || wfd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
279 {
280 if (!::SetFileAttributesW(sczDelete, FILE_ATTRIBUTE_NORMAL))
281 {
282 DirExitWithLastError(hr, "Failed to remove attributes from file: %ls", sczDelete);
283 }
284 }
285
286 if (!::DeleteFileW(sczDelete))
287 {
288 if (fScheduleDelete)
289 {
290 if (!::GetTempFileNameW(wzTempDirectory, L"DEL", 0, wzTempPath))
291 {
292 DirExitWithLastError(hr, "Failed to get temp file to move to.");
293 }
294
295 // Try to move the file to the temp directory then schedule for delete,
296 // otherwise just schedule for delete.
297 if (::MoveFileExW(sczDelete, wzTempPath, MOVEFILE_REPLACE_EXISTING))
298 {
299 ::MoveFileExW(wzTempPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
300 }
301 else
302 {
303 ::MoveFileExW(sczDelete, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
304 }
305 }
306 else
307 {
308 DirExitWithLastError(hr, "Failed to delete file: %ls", sczDelete);
309 }
310 }
311 }
312 } while (::FindNextFileW(hFind, &wfd));
313
314 er = ::GetLastError();
315 if (ERROR_NO_MORE_FILES == er)
316 {
317 hr = S_OK;
318 }
319 else
320 {
321 DirExitWithLastError(hr, "Failed while looping through files in directory: %ls", wzPath);
322 }
323 }
324
325 if (!::RemoveDirectoryW(wzPath))
326 {
327 hr = HRESULT_FROM_WIN32(::GetLastError());
328 if (HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION) == hr && fScheduleDelete)
329 {
330 if (::MoveFileExW(wzPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT))
331 {
332 hr = S_OK;
333 }
334 }
335
336 DirExitOnRootFailure(hr, "Failed to remove directory: %ls", wzPath);
337 }
338 }
339 else
340 {
341 hr = E_UNEXPECTED;
342 DirExitOnFailure(hr, "Directory delete cannot delete file: %ls", wzPath);
343 }
344
345 Assert(S_OK == hr);
346
347LExit:
348 ReleaseFileFindHandle(hFind);
349 ReleaseStr(sczDelete);
350
351 return hr;
352}
353
354
355/*******************************************************************
356DirDeleteEmptyDirectoriesToRoot - removes an empty directory and as many
357 of its parents as possible.
358
359 Returns: count of directories deleted.
360*******************************************************************/
361extern "C" DWORD DAPI DirDeleteEmptyDirectoriesToRoot(
362 __in_z LPCWSTR wzPath,
363 __in DWORD /*dwFlags*/
364 )
365{
366 DWORD cDeletedDirs = 0;
367 LPWSTR sczPath = NULL;
368
369 while (wzPath && *wzPath && ::RemoveDirectoryW(wzPath))
370 {
371 ++cDeletedDirs;
372
373 HRESULT hr = PathGetParentPath(wzPath, &sczPath);
374 DirExitOnFailure(hr, "Failed to get parent directory for path: %ls", wzPath);
375
376 wzPath = sczPath;
377 }
378
379LExit:
380 ReleaseStr(sczPath);
381
382 return cDeletedDirs;
383}
384
385
386/*******************************************************************
387 DirGetCurrent - gets the current directory.
388
389*******************************************************************/
390extern "C" HRESULT DAPI DirGetCurrent(
391 __deref_out_z LPWSTR* psczCurrentDirectory
392 )
393{
394 HRESULT hr = S_OK;
395 SIZE_T cch = 0;
396
397 if (psczCurrentDirectory && *psczCurrentDirectory)
398 {
399 hr = StrMaxLength(*psczCurrentDirectory, &cch);
400 DirExitOnFailure(hr, "Failed to determine size of current directory.");
401 }
402
403 DWORD cchRequired = ::GetCurrentDirectoryW((DWORD)min(DWORD_MAX, cch), 0 == cch ? NULL : *psczCurrentDirectory);
404 if (0 == cchRequired)
405 {
406 DirExitWithLastError(hr, "Failed to get current directory.");
407 }
408 else if (cch < cchRequired)
409 {
410 hr = StrAlloc(psczCurrentDirectory, cchRequired);
411 DirExitOnFailure(hr, "Failed to allocate string for current directory.");
412
413 if (!::GetCurrentDirectoryW(cchRequired, *psczCurrentDirectory))
414 {
415 DirExitWithLastError(hr, "Failed to get current directory using allocated string.");
416 }
417 }
418
419LExit:
420 return hr;
421}
422
423
424/*******************************************************************
425 DirSetCurrent - sets the current directory.
426
427*******************************************************************/
428extern "C" HRESULT DAPI DirSetCurrent(
429 __in_z LPCWSTR wzDirectory
430 )
431{
432 HRESULT hr = S_OK;
433
434 if (!::SetCurrentDirectoryW(wzDirectory))
435 {
436 DirExitWithLastError(hr, "Failed to set current directory to: %ls", wzDirectory);
437 }
438
439LExit:
440 return hr;
441}
diff --git a/src/libs/dutil/WixToolset.DUtil/dlutil.cpp b/src/libs/dutil/WixToolset.DUtil/dlutil.cpp
new file mode 100644
index 00000000..70155e6f
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/dlutil.cpp
@@ -0,0 +1,802 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5#include <inetutil.h>
6#include <uriutil.h>
7
8
9// Exit macros
10#define DlExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_DLUTIL, x, s, __VA_ARGS__)
11#define DlExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_DLUTIL, x, s, __VA_ARGS__)
12#define DlExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_DLUTIL, x, s, __VA_ARGS__)
13#define DlExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_DLUTIL, x, s, __VA_ARGS__)
14#define DlExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_DLUTIL, x, s, __VA_ARGS__)
15#define DlExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_DLUTIL, x, s, __VA_ARGS__)
16#define DlExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_DLUTIL, p, x, e, s, __VA_ARGS__)
17#define DlExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_DLUTIL, p, x, s, __VA_ARGS__)
18#define DlExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_DLUTIL, p, x, e, s, __VA_ARGS__)
19#define DlExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_DLUTIL, p, x, s, __VA_ARGS__)
20#define DlExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_DLUTIL, e, x, s, __VA_ARGS__)
21#define DlExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_DLUTIL, g, x, s, __VA_ARGS__)
22
23static const DWORD64 DOWNLOAD_ENGINE_TWO_GIGABYTES = DWORD64(2) * 1024 * 1024 * 1024;
24static LPCWSTR DOWNLOAD_ENGINE_ACCEPT_TYPES[] = { L"*/*", NULL };
25
26// internal function declarations
27
28static HRESULT InitializeResume(
29 __in LPCWSTR wzDestinationPath,
30 __out LPWSTR* psczResumePath,
31 __out HANDLE* phResumeFile,
32 __out DWORD64* pdw64ResumeOffset
33 );
34static HRESULT GetResourceMetadata(
35 __in HINTERNET hSession,
36 __inout_z LPWSTR* psczUrl,
37 __in_z_opt LPCWSTR wzUser,
38 __in_z_opt LPCWSTR wzPassword,
39 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate,
40 __out DWORD64* pdw64ResourceSize,
41 __out FILETIME* pftResourceCreated
42 );
43static HRESULT DownloadResource(
44 __in HINTERNET hSession,
45 __inout_z LPWSTR* psczUrl,
46 __in_z_opt LPCWSTR wzUser,
47 __in_z_opt LPCWSTR wzPassword,
48 __in_z LPCWSTR wzDestinationPath,
49 __in DWORD64 dw64AuthoredResourceLength,
50 __in DWORD64 dw64ResourceLength,
51 __in DWORD64 dw64ResumeOffset,
52 __in HANDLE hResumeFile,
53 __in_opt DOWNLOAD_CACHE_CALLBACK* pCache,
54 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate
55 );
56static HRESULT AllocateRangeRequestHeader(
57 __in DWORD64 dw64ResumeOffset,
58 __in DWORD64 dw64ResourceLength,
59 __deref_inout_z LPWSTR* psczHeader
60 );
61static HRESULT WriteToFile(
62 __in HINTERNET hUrl,
63 __in HANDLE hPayloadFile,
64 __inout DWORD64* pdw64ResumeOffset,
65 __in HANDLE hResumeFile,
66 __in DWORD64 dw64ResourceLength,
67 __in LPBYTE pbData,
68 __in DWORD cbData,
69 __in_opt DOWNLOAD_CACHE_CALLBACK* pCallback
70 );
71static HRESULT UpdateResumeOffset(
72 __inout DWORD64* pdw64ResumeOffset,
73 __in HANDLE hResumeFile,
74 __in DWORD cbData
75 );
76static HRESULT MakeRequest(
77 __in HINTERNET hSession,
78 __inout_z LPWSTR* psczSourceUrl,
79 __in_z_opt LPCWSTR wzMethod,
80 __in_z_opt LPCWSTR wzHeaders,
81 __in_z_opt LPCWSTR wzUser,
82 __in_z_opt LPCWSTR wzPassword,
83 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate,
84 __out HINTERNET* phConnect,
85 __out HINTERNET* phUrl,
86 __out BOOL* pfRangeRequestsAccepted
87 );
88static HRESULT OpenRequest(
89 __in HINTERNET hConnect,
90 __in_z_opt LPCWSTR wzMethod,
91 __in INTERNET_SCHEME scheme,
92 __in_z LPCWSTR wzResource,
93 __in_z_opt LPCWSTR wzQueryString,
94 __in_z_opt LPCWSTR wzHeader,
95 __out HINTERNET* phUrl
96 );
97static HRESULT SendRequest(
98 __in HINTERNET hUrl,
99 __inout_z LPWSTR* psczUrl,
100 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate,
101 __out BOOL* pfRetry,
102 __out BOOL* pfRangesAccepted
103 );
104static HRESULT AuthenticationRequired(
105 __in HINTERNET hUrl,
106 __in long lHttpCode,
107 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate,
108 __out BOOL* pfRetrySend,
109 __out BOOL* pfRetry
110 );
111static HRESULT DownloadGetResumePath(
112 __in_z LPCWSTR wzPayloadWorkingPath,
113 __deref_out_z LPWSTR* psczResumePath
114 );
115static HRESULT DownloadSendProgressCallback(
116 __in DOWNLOAD_CACHE_CALLBACK* pCallback,
117 __in DWORD64 dw64Progress,
118 __in DWORD64 dw64Total,
119 __in HANDLE hDestinationFile
120 );
121// function definitions
122
123extern "C" HRESULT DAPI DownloadUrl(
124 __in DOWNLOAD_SOURCE* pDownloadSource,
125 __in DWORD64 dw64AuthoredDownloadSize,
126 __in LPCWSTR wzDestinationPath,
127 __in_opt DOWNLOAD_CACHE_CALLBACK* pCache,
128 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate
129 )
130{
131 HRESULT hr = S_OK;
132 LPWSTR sczUrl = NULL;
133 HINTERNET hSession = NULL;
134 DWORD dwTimeout = 0;
135 LPWSTR sczResumePath = NULL;
136 HANDLE hResumeFile = INVALID_HANDLE_VALUE;
137 DWORD64 dw64ResumeOffset = 0;
138 DWORD64 dw64Size = 0;
139 FILETIME ftCreated = { };
140
141 // Copy the download source into a working variable to handle redirects then
142 // open the internet session.
143 hr = StrAllocString(&sczUrl, pDownloadSource->sczUrl, 0);
144 DlExitOnFailure(hr, "Failed to copy download source URL.");
145
146 hSession = ::InternetOpenW(L"Burn", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
147 DlExitOnNullWithLastError(hSession, hr, "Failed to open internet session");
148
149 // Make a best effort to set the download timeouts to 2 minutes or whatever policy says.
150 PolcReadNumber(POLICY_BURN_REGISTRY_PATH, L"DownloadTimeout", 2 * 60, &dwTimeout);
151 if (0 < dwTimeout)
152 {
153 dwTimeout *= 1000; // convert to milliseconds.
154 ::InternetSetOptionW(hSession, INTERNET_OPTION_CONNECT_TIMEOUT, &dwTimeout, sizeof(dwTimeout));
155 ::InternetSetOptionW(hSession, INTERNET_OPTION_RECEIVE_TIMEOUT, &dwTimeout, sizeof(dwTimeout));
156 ::InternetSetOptionW(hSession, INTERNET_OPTION_SEND_TIMEOUT, &dwTimeout, sizeof(dwTimeout));
157 }
158
159 // Get the resource size and creation time from the internet.
160 hr = GetResourceMetadata(hSession, &sczUrl, pDownloadSource->sczUser, pDownloadSource->sczPassword, pAuthenticate, &dw64Size, &ftCreated);
161 DlExitOnFailure(hr, "Failed to get size and time for URL: %ls", sczUrl);
162
163 // Ignore failure to initialize resume because we will fall back to full download then
164 // download.
165 InitializeResume(wzDestinationPath, &sczResumePath, &hResumeFile, &dw64ResumeOffset);
166
167 hr = DownloadResource(hSession, &sczUrl, pDownloadSource->sczUser, pDownloadSource->sczPassword, wzDestinationPath, dw64AuthoredDownloadSize, dw64Size, dw64ResumeOffset, hResumeFile, pCache, pAuthenticate);
168 DlExitOnFailure(hr, "Failed to download URL: %ls", sczUrl);
169
170 // Cleanup the resume file because we successfully downloaded the whole file.
171 if (sczResumePath && *sczResumePath)
172 {
173 ::DeleteFileW(sczResumePath);
174 }
175
176LExit:
177 ReleaseFileHandle(hResumeFile);
178 ReleaseStr(sczResumePath);
179 ReleaseInternet(hSession);
180 ReleaseStr(sczUrl);
181
182 return hr;
183}
184
185
186// internal helper functions
187
188static HRESULT InitializeResume(
189 __in LPCWSTR wzDestinationPath,
190 __out LPWSTR* psczResumePath,
191 __out HANDLE* phResumeFile,
192 __out DWORD64* pdw64ResumeOffset
193 )
194{
195 HRESULT hr = S_OK;
196 HANDLE hResumeFile = INVALID_HANDLE_VALUE;
197 DWORD cbTotalReadResumeData = 0;
198 DWORD cbReadData = 0;
199
200 *pdw64ResumeOffset = 0;
201
202 hr = DownloadGetResumePath(wzDestinationPath, psczResumePath);
203 DlExitOnFailure(hr, "Failed to calculate resume path from working path: %ls", wzDestinationPath);
204
205 hResumeFile = ::CreateFileW(*psczResumePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_DELETE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
206 if (INVALID_HANDLE_VALUE == hResumeFile)
207 {
208 DlExitWithLastError(hr, "Failed to create resume file: %ls", *psczResumePath);
209 }
210
211 do
212 {
213 if (!::ReadFile(hResumeFile, reinterpret_cast<BYTE*>(pdw64ResumeOffset) + cbTotalReadResumeData, sizeof(DWORD64) - cbTotalReadResumeData, &cbReadData, NULL))
214 {
215 DlExitWithLastError(hr, "Failed to read resume file: %ls", *psczResumePath);
216 }
217 cbTotalReadResumeData += cbReadData;
218 } while (cbReadData && sizeof(DWORD64) > cbTotalReadResumeData);
219
220 // Start over if we couldn't get a resume offset.
221 if (cbTotalReadResumeData != sizeof(DWORD64))
222 {
223 *pdw64ResumeOffset = 0;
224 }
225
226 *phResumeFile = hResumeFile;
227 hResumeFile = INVALID_HANDLE_VALUE;
228
229LExit:
230 ReleaseFileHandle(hResumeFile);
231 return hr;
232}
233
234static HRESULT GetResourceMetadata(
235 __in HINTERNET hSession,
236 __inout_z LPWSTR* psczUrl,
237 __in_z_opt LPCWSTR wzUser,
238 __in_z_opt LPCWSTR wzPassword,
239 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate,
240 __out DWORD64* pdw64ResourceSize,
241 __out FILETIME* pftResourceCreated
242 )
243{
244 HRESULT hr = S_OK;
245 BOOL fRangeRequestsAccepted = TRUE;
246 HINTERNET hConnect = NULL;
247 HINTERNET hUrl = NULL;
248 LONGLONG llLength = 0;
249
250 hr = MakeRequest(hSession, psczUrl, L"HEAD", NULL, wzUser, wzPassword, pAuthenticate, &hConnect, &hUrl, &fRangeRequestsAccepted);
251 DlExitOnFailure(hr, "Failed to connect to URL: %ls", *psczUrl);
252
253 hr = InternetGetSizeByHandle(hUrl, &llLength);
254 if (FAILED(hr))
255 {
256 llLength = 0;
257 hr = S_OK;
258 }
259
260 *pdw64ResourceSize = llLength;
261
262 // Get the last modified time from the server, we'll use that as our downloaded time here. If
263 // the server time isn't available then use the local system time.
264 hr = InternetGetCreateTimeByHandle(hUrl, pftResourceCreated);
265 if (FAILED(hr))
266 {
267 ::GetSystemTimeAsFileTime(pftResourceCreated);
268 hr = S_OK;
269 }
270
271LExit:
272 ReleaseInternet(hUrl);
273 ReleaseInternet(hConnect);
274 return hr;
275}
276
277static HRESULT DownloadResource(
278 __in HINTERNET hSession,
279 __inout_z LPWSTR* psczUrl,
280 __in_z_opt LPCWSTR wzUser,
281 __in_z_opt LPCWSTR wzPassword,
282 __in_z LPCWSTR wzDestinationPath,
283 __in DWORD64 dw64AuthoredResourceLength,
284 __in DWORD64 dw64ResourceLength,
285 __in DWORD64 dw64ResumeOffset,
286 __in HANDLE hResumeFile,
287 __in_opt DOWNLOAD_CACHE_CALLBACK* pCache,
288 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate
289 )
290{
291 HRESULT hr = S_OK;
292 HANDLE hPayloadFile = INVALID_HANDLE_VALUE;
293 DWORD cbMaxData = 64 * 1024; // 64 KB
294 BYTE* pbData = NULL;
295 BOOL fRangeRequestsAccepted = TRUE;
296 LPWSTR sczRangeRequestHeader = NULL;
297 HINTERNET hConnect = NULL;
298 HINTERNET hUrl = NULL;
299 LONGLONG llLength = 0;
300
301 hPayloadFile = ::CreateFileW(wzDestinationPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_DELETE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
302 if (INVALID_HANDLE_VALUE == hPayloadFile)
303 {
304 DlExitWithLastError(hr, "Failed to create download destination file: %ls", wzDestinationPath);
305 }
306
307 // Allocate a memory block on a page boundary in case we want to do optimal writing.
308 pbData = static_cast<BYTE*>(::VirtualAlloc(NULL, cbMaxData, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
309 DlExitOnNullWithLastError(pbData, hr, "Failed to allocate buffer to download files into.");
310
311 // Let's try downloading the file assuming that range requests are accepted. If range requests
312 // are not supported we'll have to start over and accept the fact that we only get one shot
313 // downloading the file however big it is. Hopefully, not more than 2 GB since wininet doesn't
314 // like files that big.
315 while (fRangeRequestsAccepted && (0 == dw64ResourceLength || dw64ResumeOffset < dw64ResourceLength))
316 {
317 hr = AllocateRangeRequestHeader(dw64ResumeOffset, 0 == dw64ResourceLength ? dw64AuthoredResourceLength : dw64ResourceLength, &sczRangeRequestHeader);
318 DlExitOnFailure(hr, "Failed to allocate range request header.");
319
320 ReleaseNullInternet(hConnect);
321 ReleaseNullInternet(hUrl);
322
323 hr = MakeRequest(hSession, psczUrl, L"GET", sczRangeRequestHeader, wzUser, wzPassword, pAuthenticate, &hConnect, &hUrl, &fRangeRequestsAccepted);
324 DlExitOnFailure(hr, "Failed to request URL for download: %ls", *psczUrl);
325
326 // If we didn't get the size of the resource from the initial "HEAD" request
327 // then let's try to get the size from this "GET" request.
328 if (0 == dw64ResourceLength)
329 {
330 hr = InternetGetSizeByHandle(hUrl, &llLength);
331 if (SUCCEEDED(hr))
332 {
333 dw64ResourceLength = llLength;
334 }
335 else // server didn't tell us the resource length.
336 {
337 // Fallback to the authored size of the resource. However, since we
338 // don't really know the size on the server, don't try to use
339 // range requests either.
340 dw64ResourceLength = dw64AuthoredResourceLength;
341 fRangeRequestsAccepted = FALSE;
342 }
343 }
344
345 // If we just tried to do a range request and found out that it isn't supported, start over.
346 if (!fRangeRequestsAccepted)
347 {
348 // TODO: log a message that the server did not accept range requests.
349 dw64ResumeOffset = 0;
350 }
351
352 hr = WriteToFile(hUrl, hPayloadFile, &dw64ResumeOffset, hResumeFile, dw64ResourceLength, pbData, cbMaxData, pCache);
353 DlExitOnFailure(hr, "Failed while reading from internet and writing to: %ls", wzDestinationPath);
354 }
355
356LExit:
357 ReleaseInternet(hUrl);
358 ReleaseInternet(hConnect);
359 ReleaseStr(sczRangeRequestHeader);
360 if (pbData)
361 {
362 ::VirtualFree(pbData, 0, MEM_RELEASE);
363 }
364 ReleaseFileHandle(hPayloadFile);
365
366 return hr;
367}
368
369static HRESULT AllocateRangeRequestHeader(
370 __in DWORD64 dw64ResumeOffset,
371 __in DWORD64 dw64ResourceLength,
372 __deref_inout_z LPWSTR* psczHeader
373 )
374{
375 HRESULT hr = S_OK;
376
377 // If the remaining length is less that 2GB we'll be able to ask for everything.
378 DWORD64 dw64RemainingLength = dw64ResourceLength - dw64ResumeOffset;
379 if (DOWNLOAD_ENGINE_TWO_GIGABYTES > dw64RemainingLength)
380 {
381 // If we have a resume offset, let's download everything from there. Otherwise, we'll
382 // just get everything with no headers in the way.
383 if (0 < dw64ResumeOffset)
384 {
385 hr = StrAllocFormatted(psczHeader, L"Range: bytes=%I64u-", dw64ResumeOffset);
386 DlExitOnFailure(hr, "Failed to add range read header.");
387 }
388 else
389 {
390 ReleaseNullStr(*psczHeader);
391 }
392 }
393 else // we'll have to download in chunks.
394 {
395 hr = StrAllocFormatted(psczHeader, L"Range: bytes=%I64u-%I64u", dw64ResumeOffset, dw64ResumeOffset + dw64RemainingLength - 1);
396 DlExitOnFailure(hr, "Failed to add range read header.");
397 }
398
399LExit:
400 return hr;
401}
402
403static HRESULT WriteToFile(
404 __in HINTERNET hUrl,
405 __in HANDLE hPayloadFile,
406 __inout DWORD64* pdw64ResumeOffset,
407 __in HANDLE hResumeFile,
408 __in DWORD64 dw64ResourceLength,
409 __in LPBYTE pbData,
410 __in DWORD cbData,
411 __in_opt DOWNLOAD_CACHE_CALLBACK* pCallback
412 )
413{
414 HRESULT hr = S_OK;
415 DWORD cbReadData = 0;
416
417 hr = FileSetPointer(hPayloadFile, *pdw64ResumeOffset, NULL, FILE_BEGIN);
418 DlExitOnFailure(hr, "Failed to seek to start point in file.");
419
420 do
421 {
422 // Read bits from the internet.
423 if (!::InternetReadFile(hUrl, static_cast<void*>(pbData), cbData, &cbReadData))
424 {
425 DlExitWithLastError(hr, "Failed while reading from internet.");
426 }
427
428 // Write bits to disk (if there are any).
429 if (cbReadData)
430 {
431 DWORD cbTotalWritten = 0;
432 DWORD cbWritten = 0;
433 do
434 {
435 if (!::WriteFile(hPayloadFile, pbData + cbTotalWritten, cbReadData - cbTotalWritten, &cbWritten, NULL))
436 {
437 DlExitWithLastError(hr, "Failed to write data from internet.");
438 }
439
440 cbTotalWritten += cbWritten;
441 } while (cbWritten && cbTotalWritten < cbReadData);
442
443 // Ignore failure from updating resume file as this doesn't mean the download cannot succeed.
444 UpdateResumeOffset(pdw64ResumeOffset, hResumeFile, cbTotalWritten);
445
446 if (pCallback && pCallback->pfnProgress)
447 {
448 hr = DownloadSendProgressCallback(pCallback, *pdw64ResumeOffset, dw64ResourceLength, hPayloadFile);
449 DlExitOnFailure(hr, "UX aborted on cache progress.");
450 }
451 }
452 } while (cbReadData);
453
454LExit:
455 return hr;
456}
457
458static HRESULT UpdateResumeOffset(
459 __inout DWORD64* pdw64ResumeOffset,
460 __in HANDLE hResumeFile,
461 __in DWORD cbData
462 )
463{
464 HRESULT hr = S_OK;
465
466 *pdw64ResumeOffset += cbData;
467
468 if (INVALID_HANDLE_VALUE != hResumeFile)
469 {
470 DWORD cbTotalWrittenResumeData = 0;
471 DWORD cbWrittenResumeData = 0;
472
473 hr = FileSetPointer(hResumeFile, 0, NULL, FILE_BEGIN);
474 DlExitOnFailure(hr, "Failed to seek to start point in file.");
475
476 do
477 {
478 // Ignore failure to write to the resume file as that should not prevent the download from happening.
479 if (!::WriteFile(hResumeFile, pdw64ResumeOffset + cbTotalWrittenResumeData, sizeof(DWORD64) - cbTotalWrittenResumeData, &cbWrittenResumeData, NULL))
480 {
481 DlExitOnFailure(hr, "Failed to seek to write to file.");
482 }
483
484 cbTotalWrittenResumeData += cbWrittenResumeData;
485 } while (cbWrittenResumeData && sizeof(DWORD64) > cbTotalWrittenResumeData);
486 }
487
488LExit:
489 return hr;
490}
491
492static HRESULT MakeRequest(
493 __in HINTERNET hSession,
494 __inout_z LPWSTR* psczSourceUrl,
495 __in_z_opt LPCWSTR wzMethod,
496 __in_z_opt LPCWSTR wzHeaders,
497 __in_z_opt LPCWSTR wzUser,
498 __in_z_opt LPCWSTR wzPassword,
499 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate,
500 __out HINTERNET* phConnect,
501 __out HINTERNET* phUrl,
502 __out BOOL* pfRangeRequestsAccepted
503 )
504{
505 HRESULT hr = S_OK;
506 HINTERNET hConnect = NULL;
507 HINTERNET hUrl = NULL;
508 URI_INFO uri = { };
509
510 // Try to open the URL.
511 BOOL fRetry;
512 do
513 {
514 fRetry = FALSE;
515
516 // If the URL was opened close it, so we can reopen it again.
517 ReleaseInternet(hUrl);
518 ReleaseInternet(hConnect);
519
520 // Open the url.
521 hr = UriCrackEx(*psczSourceUrl, &uri);
522 DlExitOnFailure(hr, "Failed to break URL into server and resource parts.");
523
524 hConnect = ::InternetConnectW(hSession, uri.sczHostName, uri.port, (wzUser && *wzUser) ? wzUser : uri.sczUser, (wzPassword && *wzPassword) ? wzPassword : uri.sczPassword, INTERNET_SCHEME_FTP == uri.scheme ? INTERNET_SERVICE_FTP : INTERNET_SERVICE_HTTP, 0, 0);
525 DlExitOnNullWithLastError(hConnect, hr, "Failed to connect to URL: %ls", *psczSourceUrl);
526
527 // Best effort set the proxy username and password, if they were provided.
528 if ((wzUser && *wzUser) && (wzPassword && *wzPassword))
529 {
530 if (::InternetSetOptionW(hConnect, INTERNET_OPTION_PROXY_USERNAME, (LPVOID)wzUser, lstrlenW(wzUser)))
531 {
532 ::InternetSetOptionW(hConnect, INTERNET_OPTION_PROXY_PASSWORD, (LPVOID)wzPassword, lstrlenW(wzPassword));
533 }
534 }
535
536 hr = OpenRequest(hConnect, wzMethod, uri.scheme, uri.sczPath, uri.sczQueryString, wzHeaders, &hUrl);
537 DlExitOnFailure(hr, "Failed to open internet URL: %ls", *psczSourceUrl);
538
539 hr = SendRequest(hUrl, psczSourceUrl, pAuthenticate, &fRetry, pfRangeRequestsAccepted);
540 DlExitOnFailure(hr, "Failed to send request to URL: %ls", *psczSourceUrl);
541 } while (fRetry);
542
543 // Okay, we're all ready to start downloading. Update the connection information.
544 *phConnect = hConnect;
545 hConnect = NULL;
546 *phUrl = hUrl;
547 hUrl = NULL;
548
549LExit:
550 UriInfoUninitialize(&uri);
551 ReleaseInternet(hUrl);
552 ReleaseInternet(hConnect);
553
554 return hr;
555}
556
557static HRESULT OpenRequest(
558 __in HINTERNET hConnect,
559 __in_z_opt LPCWSTR wzMethod,
560 __in INTERNET_SCHEME scheme,
561 __in_z LPCWSTR wzResource,
562 __in_z_opt LPCWSTR wzQueryString,
563 __in_z_opt LPCWSTR wzHeader,
564 __out HINTERNET* phUrl
565 )
566{
567 HRESULT hr = S_OK;
568 DWORD dwRequestFlags = INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_UI | INTERNET_FLAG_RELOAD;
569 LPWSTR sczResource = NULL;
570 HINTERNET hUrl = NULL;
571
572 if (INTERNET_SCHEME_HTTPS == scheme)
573 {
574 dwRequestFlags |= INTERNET_FLAG_SECURE;
575 }
576 else if (INTERNET_SCHEME_HTTP == scheme)
577 {
578 dwRequestFlags |= INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS;
579 }
580
581 // Allocate the resource name.
582 hr = StrAllocString(&sczResource, wzResource, 0);
583 DlExitOnFailure(hr, "Failed to allocate string for resource URI.");
584
585 if (wzQueryString && *wzQueryString)
586 {
587 hr = StrAllocConcat(&sczResource, wzQueryString, 0);
588 DlExitOnFailure(hr, "Failed to append query strong to resource from URI.");
589 }
590
591 // Open the request and add the header if provided.
592 hUrl = ::HttpOpenRequestW(hConnect, wzMethod, sczResource, NULL, NULL, DOWNLOAD_ENGINE_ACCEPT_TYPES, dwRequestFlags, NULL);
593 DlExitOnNullWithLastError(hUrl, hr, "Failed to open internet request.");
594
595 if (wzHeader && *wzHeader)
596 {
597 if (!::HttpAddRequestHeadersW(hUrl, wzHeader, static_cast<DWORD>(-1), HTTP_ADDREQ_FLAG_COALESCE))
598 {
599 DlExitWithLastError(hr, "Failed to add header to HTTP request.");
600 }
601 }
602
603 *phUrl = hUrl;
604 hUrl = NULL;
605
606LExit:
607 ReleaseInternet(hUrl);
608 ReleaseStr(sczResource);
609 return hr;
610}
611
612static HRESULT SendRequest(
613 __in HINTERNET hUrl,
614 __inout_z LPWSTR* psczUrl,
615 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate,
616 __out BOOL* pfRetry,
617 __out BOOL* pfRangesAccepted
618 )
619{
620 HRESULT hr = S_OK;
621 BOOL fRetrySend = FALSE;
622 LONG lCode = 0;
623
624 do
625 {
626 fRetrySend = FALSE;
627
628 if (!::HttpSendRequestW(hUrl, NULL, 0, NULL, 0))
629 {
630 hr = HRESULT_FROM_WIN32(::GetLastError()); // remember the error that occurred and log it.
631 LogErrorString(hr, "Failed to send request to URL: %ls, trying to process HTTP status code anyway.", *psczUrl);
632
633 // Try to get the HTTP status code and, if good, handle via the switch statement below but if it
634 // fails return the error code from the send request above as the result of the function.
635 HRESULT hrQueryStatusCode = InternetQueryInfoNumber(hUrl, HTTP_QUERY_STATUS_CODE, &lCode);
636 DlExitOnFailure(hrQueryStatusCode, "Failed to get HTTP status code for failed request to URL: %ls", *psczUrl);
637 }
638 else // get the http status code.
639 {
640 hr = InternetQueryInfoNumber(hUrl, HTTP_QUERY_STATUS_CODE, &lCode);
641 DlExitOnFailure(hr, "Failed to get HTTP status code for request to URL: %ls", *psczUrl);
642 }
643
644 switch (lCode)
645 {
646 case 200: // OK but range requests don't work.
647 *pfRangesAccepted = FALSE;
648 hr = S_OK;
649 break;
650
651 case 206: // Partial content means that range requests work!
652 *pfRangesAccepted = TRUE;
653 hr = S_OK;
654 break;
655
656 // redirection cases
657 case 301: __fallthrough; // file moved
658 case 302: __fallthrough; // temporary
659 case 303: // redirect method
660 hr = InternetQueryInfoString(hUrl, HTTP_QUERY_CONTENT_LOCATION, psczUrl);
661 DlExitOnFailure(hr, "Failed to get redirect url: %ls", *psczUrl);
662
663 *pfRetry = TRUE;
664 break;
665
666 // error cases
667 case 400: // bad request
668 hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME);
669 break;
670
671 case 401: __fallthrough; // unauthorized
672 case 407: __fallthrough; // proxy unauthorized
673 hr = AuthenticationRequired(hUrl, lCode, pAuthenticate, &fRetrySend, pfRetry);
674 break;
675
676 case 403: // forbidden
677 hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
678 break;
679
680 case 404: // file not found
681 case 410: // gone
682 hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
683 break;
684
685 case 405: // method not allowed
686 hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
687 break;
688
689 case 408: __fallthrough; // request timedout
690 case 504: // gateway timeout
691 hr = HRESULT_FROM_WIN32(WAIT_TIMEOUT);
692 break;
693
694 case 414: // request URI too long
695 hr = CO_E_PATHTOOLONG;
696 break;
697
698 case 502: __fallthrough; // server (through a gateway) was not found
699 case 503: // server unavailable
700 hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
701 break;
702
703 case 418: // I'm a teapot.
704 default:
705 // If the request failed and the HTTP status code was invalid (but wininet gave us a number anyway)
706 // do not overwrite the error code from the failed request. Otherwise, the error was unexpected.
707 if (SUCCEEDED(hr))
708 {
709 hr = E_UNEXPECTED;
710 }
711
712 LogErrorString(hr, "Unknown HTTP status code %d, returned from URL: %ls", lCode, *psczUrl);
713 break;
714 }
715 } while (fRetrySend);
716
717LExit:
718 return hr;
719}
720
721static HRESULT AuthenticationRequired(
722 __in HINTERNET hUrl,
723 __in long lHttpCode,
724 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate,
725 __out BOOL* pfRetrySend,
726 __out BOOL* pfRetry
727 )
728{
729 Assert(401 == lHttpCode || 407 == lHttpCode);
730
731 HRESULT hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
732 *pfRetrySend = FALSE;
733 *pfRetry = FALSE;
734
735 if (pAuthenticate && pAuthenticate->pfnAuthenticate)
736 {
737 hr = (*pAuthenticate->pfnAuthenticate)(pAuthenticate->pv, hUrl, lHttpCode, pfRetrySend, pfRetry);
738 }
739
740 return hr;
741}
742
743
744static HRESULT DownloadGetResumePath(
745 __in_z LPCWSTR wzPayloadWorkingPath,
746 __deref_out_z LPWSTR* psczResumePath
747 )
748{
749 HRESULT hr = S_OK;
750
751 hr = StrAllocFormatted(psczResumePath, L"%ls.R", wzPayloadWorkingPath);
752 DlExitOnFailure(hr, "Failed to create resume path.");
753
754LExit:
755 return hr;
756}
757
758static HRESULT DownloadSendProgressCallback(
759 __in DOWNLOAD_CACHE_CALLBACK* pCallback,
760 __in DWORD64 dw64Progress,
761 __in DWORD64 dw64Total,
762 __in HANDLE hDestinationFile
763 )
764{
765 static LARGE_INTEGER LARGE_INTEGER_ZERO = { };
766
767 HRESULT hr = S_OK;
768 DWORD dwResult = PROGRESS_CONTINUE;
769 LARGE_INTEGER liTotalSize = { };
770 LARGE_INTEGER liTotalTransferred = { };
771
772 if (pCallback->pfnProgress)
773 {
774 liTotalSize.QuadPart = dw64Total;
775 liTotalTransferred.QuadPart = dw64Progress;
776
777 dwResult = (*pCallback->pfnProgress)(liTotalSize, liTotalTransferred, LARGE_INTEGER_ZERO, LARGE_INTEGER_ZERO, 1, CALLBACK_CHUNK_FINISHED, INVALID_HANDLE_VALUE, hDestinationFile, pCallback->pv);
778 switch (dwResult)
779 {
780 case PROGRESS_CONTINUE:
781 hr = S_OK;
782 break;
783
784 case PROGRESS_CANCEL: __fallthrough; // TODO: should cancel and stop be treated differently?
785 case PROGRESS_STOP:
786 hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT);
787 DlExitOnRootFailure(hr, "UX aborted on download progress.");
788
789 case PROGRESS_QUIET: // Not actually an error, just an indication to the caller to stop requesting progress.
790 pCallback->pfnProgress = NULL;
791 hr = S_OK;
792 break;
793
794 default:
795 hr = E_UNEXPECTED;
796 DlExitOnRootFailure(hr, "Invalid return code from progress routine.");
797 }
798 }
799
800LExit:
801 return hr;
802}
diff --git a/src/libs/dutil/WixToolset.DUtil/dpiutil.cpp b/src/libs/dutil/WixToolset.DUtil/dpiutil.cpp
new file mode 100644
index 00000000..4096c8d3
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/dpiutil.cpp
@@ -0,0 +1,274 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5// Exit macros
6#define DpiuExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_DPIUTIL, x, s, __VA_ARGS__)
7#define DpiuExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_DPIUTIL, x, s, __VA_ARGS__)
8#define DpiuExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_DPIUTIL, x, s, __VA_ARGS__)
9#define DpiuExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_DPIUTIL, x, s, __VA_ARGS__)
10#define DpiuExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_DPIUTIL, x, s, __VA_ARGS__)
11#define DpiuExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_DPIUTIL, x, s, __VA_ARGS__)
12#define DpiuExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_DPIUTIL, p, x, e, s, __VA_ARGS__)
13#define DpiuExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_DPIUTIL, p, x, s, __VA_ARGS__)
14#define DpiuExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_DPIUTIL, p, x, e, s, __VA_ARGS__)
15#define DpiuExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_DPIUTIL, p, x, s, __VA_ARGS__)
16#define DpiuExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_DPIUTIL, e, x, s, __VA_ARGS__)
17
18static PFN_ADJUSTWINDOWRECTEXFORDPI vpfnAdjustWindowRectExForDpi = NULL;
19static PFN_GETDPIFORMONITOR vpfnGetDpiForMonitor = NULL;
20static PFN_GETDPIFORWINDOW vpfnGetDpiForWindow = NULL;
21static PFN_SETPROCESSDPIAWARE vpfnSetProcessDPIAware = NULL;
22static PFN_SETPROCESSDPIAWARENESS vpfnSetProcessDpiAwareness = NULL;
23static PFN_SETPROCESSDPIAWARENESSCONTEXT vpfnSetProcessDpiAwarenessContext = NULL;
24
25static HMODULE vhShcoreDll = NULL;
26static HMODULE vhUser32Dll = NULL;
27static BOOL vfDpiuInitialized = FALSE;
28
29DAPI_(void) DpiuInitialize()
30{
31 HRESULT hr = S_OK;
32
33 hr = LoadSystemLibrary(L"Shcore.dll", &vhShcoreDll);
34 if (SUCCEEDED(hr))
35 {
36 // Ignore failures.
37 vpfnGetDpiForMonitor = reinterpret_cast<PFN_GETDPIFORMONITOR>(::GetProcAddress(vhShcoreDll, "GetDpiForMonitor"));
38 vpfnSetProcessDpiAwareness = reinterpret_cast<PFN_SETPROCESSDPIAWARENESS>(::GetProcAddress(vhShcoreDll, "SetProcessDpiAwareness"));
39 }
40
41 hr = LoadSystemLibrary(L"User32.dll", &vhUser32Dll);
42 if (SUCCEEDED(hr))
43 {
44 // Ignore failures.
45 vpfnAdjustWindowRectExForDpi = reinterpret_cast<PFN_ADJUSTWINDOWRECTEXFORDPI>(::GetProcAddress(vhUser32Dll, "AdjustWindowRectExForDpi"));
46 vpfnGetDpiForWindow = reinterpret_cast<PFN_GETDPIFORWINDOW>(::GetProcAddress(vhUser32Dll, "GetDpiForWindow"));
47 vpfnSetProcessDPIAware = reinterpret_cast<PFN_SETPROCESSDPIAWARE>(::GetProcAddress(vhUser32Dll, "SetProcessDPIAware"));
48 vpfnSetProcessDpiAwarenessContext = reinterpret_cast<PFN_SETPROCESSDPIAWARENESSCONTEXT>(::GetProcAddress(vhUser32Dll, "SetProcessDpiAwarenessContext"));
49 }
50
51 vfDpiuInitialized = TRUE;
52}
53
54DAPI_(void) DpiuUninitialize()
55{
56 if (vhShcoreDll)
57 {
58 ::FreeLibrary(vhShcoreDll);
59 }
60
61 if (vhUser32Dll)
62 {
63 ::FreeLibrary(vhUser32Dll);
64 }
65
66 vhShcoreDll = NULL;
67 vhUser32Dll = NULL;
68 vpfnAdjustWindowRectExForDpi = NULL;
69 vpfnGetDpiForMonitor = NULL;
70 vpfnGetDpiForWindow = NULL;
71 vfDpiuInitialized = FALSE;
72}
73
74DAPI_(void) DpiuAdjustWindowRect(
75 __in RECT* pWindowRect,
76 __in DWORD dwStyle,
77 __in BOOL fMenu,
78 __in DWORD dwExStyle,
79 __in UINT nDpi
80 )
81{
82 if (WS_SYSMENU & dwStyle)
83 {
84 dwStyle |= WS_CAPTION; // WS_CAPTION is required with WS_SYSMENU, AdjustWindowRect* won't work properly when it's not specified.
85 }
86
87 if (vpfnAdjustWindowRectExForDpi)
88 {
89 vpfnAdjustWindowRectExForDpi(pWindowRect, dwStyle, fMenu, dwExStyle, nDpi);
90 }
91 else
92 {
93 ::AdjustWindowRectEx(pWindowRect, dwStyle, fMenu, dwExStyle);
94 }
95}
96
97DAPI_(HRESULT) DpiuGetMonitorContextFromPoint(
98 __in const POINT* pt,
99 __out DPIU_MONITOR_CONTEXT** ppMonitorContext
100 )
101{
102 HRESULT hr = S_OK;
103 DPIU_MONITOR_CONTEXT* pMonitorContext = NULL;
104 HMONITOR hMonitor = NULL;
105 UINT dpiX = 0;
106 UINT dpiY = 0;
107 HDC hdc = NULL;
108
109 pMonitorContext = reinterpret_cast<DPIU_MONITOR_CONTEXT*>(MemAlloc(sizeof(DPIU_MONITOR_CONTEXT), TRUE));
110 DpiuExitOnNull(pMonitorContext, hr, E_OUTOFMEMORY, "Failed to allocate memory for DpiuMonitorContext.");
111
112 hMonitor = ::MonitorFromPoint(*pt, MONITOR_DEFAULTTONEAREST);
113 DpiuExitOnNull(hMonitor, hr, E_FAIL, "Failed to get monitor from point.");
114
115 pMonitorContext->mi.cbSize = sizeof(pMonitorContext->mi);
116 if (!::GetMonitorInfoW(hMonitor, &pMonitorContext->mi))
117 {
118 DpiuExitOnFailure(hr = E_OUTOFMEMORY, "Failed to get monitor info for point.");
119 }
120
121 if (vpfnGetDpiForMonitor)
122 {
123 hr = vpfnGetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY);
124 DpiuExitOnFailure(hr, "Failed to get DPI for monitor.");
125
126 pMonitorContext->nDpi = dpiX;
127 }
128 else
129 {
130 hdc = ::CreateDCW(L"DISPLAY", pMonitorContext->mi.szDevice, NULL, NULL);
131 DpiuExitOnNull(hdc, hr, E_OUTOFMEMORY, "Failed to get device context for monitor.");
132
133 pMonitorContext->nDpi = ::GetDeviceCaps(hdc, LOGPIXELSX);
134 }
135
136 *ppMonitorContext = pMonitorContext;
137 pMonitorContext = NULL;
138
139LExit:
140 if (hdc)
141 {
142 ::ReleaseDC(NULL, hdc);
143 }
144
145 MemFree(pMonitorContext);
146
147 return hr;
148}
149
150DAPI_(void) DpiuGetWindowContext(
151 __in HWND hWnd,
152 __in DPIU_WINDOW_CONTEXT* pWindowContext
153 )
154{
155 HRESULT hr = S_OK;
156 HMONITOR hMonitor = NULL;
157 UINT dpiX = 0;
158 UINT dpiY = 0;
159 HDC hdc = NULL;
160
161 pWindowContext->nDpi = USER_DEFAULT_SCREEN_DPI;
162
163 if (vpfnGetDpiForWindow)
164 {
165 pWindowContext->nDpi = vpfnGetDpiForWindow(hWnd);
166 ExitFunction();
167 }
168
169 if (vpfnGetDpiForMonitor)
170 {
171 hMonitor = ::MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
172 if (hMonitor)
173 {
174 hr = vpfnGetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY);
175 if (SUCCEEDED(hr))
176 {
177 pWindowContext->nDpi = dpiX;
178 ExitFunction();
179 }
180 }
181 }
182
183 hdc = ::GetDC(hWnd);
184 if (hdc)
185 {
186 pWindowContext->nDpi = ::GetDeviceCaps(hdc, LOGPIXELSX);
187 }
188
189LExit:
190 if (hdc)
191 {
192 ::ReleaseDC(hWnd, hdc);
193 }
194}
195
196DAPI_(int) DpiuScaleValue(
197 __in int nDefaultDpiValue,
198 __in UINT nTargetDpi
199 )
200{
201 return ::MulDiv(nDefaultDpiValue, nTargetDpi, USER_DEFAULT_SCREEN_DPI);
202}
203
204DAPI_(HRESULT) DpiuSetProcessDpiAwareness(
205 __in DPIU_AWARENESS supportedAwareness,
206 __in_opt DPIU_AWARENESS* pSelectedAwareness
207 )
208{
209 HRESULT hr = S_OK;
210 DPIU_AWARENESS selectedAwareness = DPIU_AWARENESS_NONE;
211 DPI_AWARENESS_CONTEXT awarenessContext = DPI_AWARENESS_CONTEXT_UNAWARE;
212 PROCESS_DPI_AWARENESS awareness = PROCESS_DPI_UNAWARE;
213
214 if (vpfnSetProcessDpiAwarenessContext)
215 {
216 if (DPIU_AWARENESS_PERMONITORV2 & supportedAwareness)
217 {
218 awarenessContext = DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
219 selectedAwareness = DPIU_AWARENESS_PERMONITORV2;
220 }
221 else if (DPIU_AWARENESS_PERMONITOR & supportedAwareness)
222 {
223 awarenessContext = DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE;
224 selectedAwareness = DPIU_AWARENESS_PERMONITOR;
225 }
226 else if (DPIU_AWARENESS_SYSTEM & supportedAwareness)
227 {
228 awarenessContext = DPI_AWARENESS_CONTEXT_SYSTEM_AWARE;
229 selectedAwareness = DPIU_AWARENESS_SYSTEM;
230 }
231 else if (DPIU_AWARENESS_GDISCALED & supportedAwareness)
232 {
233 awarenessContext = DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED;
234 selectedAwareness = DPIU_AWARENESS_GDISCALED;
235 }
236
237 if (!vpfnSetProcessDpiAwarenessContext(awarenessContext))
238 {
239 DpiuExitOnLastError(hr, "Failed to set process DPI awareness context.");
240 }
241 }
242 else if (vpfnSetProcessDpiAwareness)
243 {
244 if (DPIU_AWARENESS_PERMONITOR & supportedAwareness)
245 {
246 awareness = PROCESS_PER_MONITOR_DPI_AWARE;
247 selectedAwareness = DPIU_AWARENESS_PERMONITOR;
248 }
249 else if (DPIU_AWARENESS_SYSTEM & supportedAwareness)
250 {
251 awareness = PROCESS_SYSTEM_DPI_AWARE;
252 selectedAwareness = DPIU_AWARENESS_SYSTEM;
253 }
254
255 hr = vpfnSetProcessDpiAwareness(awareness);
256 DpiuExitOnFailure(hr, "Failed to set process DPI awareness.");
257 }
258 else if (vpfnSetProcessDPIAware && (DPIU_AWARENESS_SYSTEM & supportedAwareness))
259 {
260 selectedAwareness = DPIU_AWARENESS_SYSTEM;
261 if (!vpfnSetProcessDPIAware())
262 {
263 DpiuExitOnLastError(hr, "Failed to set process DPI aware.");
264 }
265 }
266
267LExit:
268 if (pSelectedAwareness)
269 {
270 *pSelectedAwareness = selectedAwareness;
271 }
272
273 return hr;
274}
diff --git a/src/libs/dutil/WixToolset.DUtil/dutil.cpp b/src/libs/dutil/WixToolset.DUtil/dutil.cpp
new file mode 100644
index 00000000..56b85207
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/dutil.cpp
@@ -0,0 +1,524 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define DExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_DUTIL, x, s, __VA_ARGS__)
8#define DExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_DUTIL, x, s, __VA_ARGS__)
9#define DExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_DUTIL, x, s, __VA_ARGS__)
10#define DExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_DUTIL, x, s, __VA_ARGS__)
11#define DExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_DUTIL, x, s, __VA_ARGS__)
12#define DExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_DUTIL, x, s, __VA_ARGS__)
13#define DExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_DUTIL, p, x, e, s, __VA_ARGS__)
14#define DExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_DUTIL, p, x, s, __VA_ARGS__)
15#define DExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_DUTIL, p, x, e, s, __VA_ARGS__)
16#define DExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_DUTIL, p, x, s, __VA_ARGS__)
17#define DExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_DUTIL, e, x, s, __VA_ARGS__)
18#define DExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_DUTIL, g, x, s, __VA_ARGS__)
19
20// No need for OACR to warn us about using non-unicode APIs in this file.
21#pragma prefast(disable:25068)
22
23// Asserts & Tracing
24
25const int DUTIL_STRING_BUFFER = 1024;
26static HMODULE Dutil_hAssertModule = NULL;
27static DUTIL_ASSERTDISPLAYFUNCTION Dutil_pfnDisplayAssert = NULL;
28static BOOL Dutil_fNoAsserts = FALSE;
29static REPORT_LEVEL Dutil_rlCurrentTrace = REPORT_STANDARD;
30static BOOL Dutil_fTraceFilenames = FALSE;
31static DUTIL_CALLBACK_TRACEERROR vpfnTraceErrorCallback = NULL;
32
33
34DAPI_(HRESULT) DutilInitialize(
35 __in_opt DUTIL_CALLBACK_TRACEERROR pfnTraceErrorCallback
36 )
37{
38 HRESULT hr = S_OK;
39
40 vpfnTraceErrorCallback = pfnTraceErrorCallback;
41
42 return hr;
43}
44
45
46DAPI_(void) DutilUninitialize()
47{
48 vpfnTraceErrorCallback = NULL;
49}
50
51/*******************************************************************
52Dutil_SetAssertModule
53
54*******************************************************************/
55extern "C" void DAPI Dutil_SetAssertModule(
56 __in HMODULE hAssertModule
57 )
58{
59 Dutil_hAssertModule = hAssertModule;
60}
61
62
63/*******************************************************************
64Dutil_SetAssertDisplayFunction
65
66*******************************************************************/
67extern "C" void DAPI Dutil_SetAssertDisplayFunction(
68 __in DUTIL_ASSERTDISPLAYFUNCTION pfn
69 )
70{
71 Dutil_pfnDisplayAssert = pfn;
72}
73
74
75/*******************************************************************
76Dutil_AssertMsg
77
78*******************************************************************/
79extern "C" void DAPI Dutil_AssertMsg(
80 __in_z LPCSTR szMessage
81 )
82{
83 static BOOL fInAssert = FALSE; // TODO: make this thread safe (this is a cheap hack to prevent re-entrant Asserts)
84
85 HRESULT hr = S_OK;
86 DWORD er = ERROR_SUCCESS;
87
88 int id = IDRETRY;
89 HKEY hkDebug = NULL;
90 HANDLE hAssertFile = INVALID_HANDLE_VALUE;
91 char szPath[MAX_PATH] = { };
92 DWORD cch = 0;
93
94 if (fInAssert)
95 {
96 return;
97 }
98 fInAssert = TRUE;
99
100 char szMsg[DUTIL_STRING_BUFFER];
101 hr = ::StringCchCopyA(szMsg, countof(szMsg), szMessage);
102 DExitOnFailure(hr, "failed to copy message while building assert message");
103
104 if (Dutil_pfnDisplayAssert)
105 {
106 // call custom function to display the assert string
107 if (!Dutil_pfnDisplayAssert(szMsg))
108 {
109 ExitFunction();
110 }
111 }
112 else
113 {
114 OutputDebugStringA(szMsg);
115 }
116
117 if (!Dutil_fNoAsserts)
118 {
119 er = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Delivery\\Debug", 0, KEY_QUERY_VALUE, &hkDebug);
120 if (ERROR_SUCCESS == er)
121 {
122 cch = countof(szPath);
123 er = ::RegQueryValueExA(hkDebug, "DeliveryAssertsLog", NULL, NULL, reinterpret_cast<BYTE*>(szPath), &cch);
124 szPath[countof(szPath) - 1] = '\0'; // ensure string is null terminated since registry won't guarantee that.
125 if (ERROR_SUCCESS == er)
126 {
127 hAssertFile = ::CreateFileA(szPath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
128 if (INVALID_HANDLE_VALUE != hAssertFile)
129 {
130 if (INVALID_SET_FILE_POINTER != ::SetFilePointer(hAssertFile, 0, 0, FILE_END))
131 {
132 if (SUCCEEDED(::StringCchCatA(szMsg, countof(szMsg), "\r\n")))
133 {
134 ::WriteFile(hAssertFile, szMsg, lstrlenA(szMsg), &cch, NULL);
135 }
136 }
137 }
138 }
139 }
140
141 // if anything went wrong while fooling around with the registry, just show the usual assert dialog box
142 if (ERROR_SUCCESS != er)
143 {
144 hr = ::StringCchCatA(szMsg, countof(szMsg), "\nAbort=Debug, Retry=Skip, Ignore=Skip all");
145 DExitOnFailure(hr, "failed to concat string while building assert message");
146
147 id = ::MessageBoxA(0, szMsg, "Debug Assert Message",
148 MB_SERVICE_NOTIFICATION | MB_TOPMOST |
149 MB_DEFBUTTON2 | MB_ABORTRETRYIGNORE);
150 }
151 }
152
153 if (id == IDABORT)
154 {
155 if (Dutil_hAssertModule)
156 {
157 ::GetModuleFileNameA(Dutil_hAssertModule, szPath, countof(szPath));
158
159 hr = ::StringCchPrintfA(szMsg, countof(szMsg), "Module is running from: %s\nIf you are not using pdb-stamping, place your PDB near the module and attach to process id: %d (0x%x)", szPath, ::GetCurrentProcessId(), ::GetCurrentProcessId());
160 if (SUCCEEDED(hr))
161 {
162 ::MessageBoxA(0, szMsg, "Debug Assert Message", MB_SERVICE_NOTIFICATION | MB_TOPMOST | MB_OK);
163 }
164 }
165
166 ::DebugBreak();
167 }
168 else if (id == IDIGNORE)
169 {
170 Dutil_fNoAsserts = TRUE;
171 }
172
173LExit:
174 ReleaseFileHandle(hAssertFile);
175 ReleaseRegKey(hkDebug);
176 fInAssert = FALSE;
177}
178
179
180/*******************************************************************
181Dutil_Assert
182
183*******************************************************************/
184extern "C" void DAPI Dutil_Assert(
185 __in_z LPCSTR szFile,
186 __in int iLine
187 )
188{
189 HRESULT hr = S_OK;
190 char szMessage[DUTIL_STRING_BUFFER] = { };
191 hr = ::StringCchPrintfA(szMessage, countof(szMessage), "Assertion failed in %s, %i", szFile, iLine);
192 if (SUCCEEDED(hr))
193 {
194 Dutil_AssertMsg(szMessage);
195 }
196 else
197 {
198 Dutil_AssertMsg("Assert failed to build string");
199 }
200}
201
202
203/*******************************************************************
204Dutil_AssertSz
205
206*******************************************************************/
207extern "C" void DAPI Dutil_AssertSz(
208 __in_z LPCSTR szFile,
209 __in int iLine,
210 __in_z __format_string LPCSTR szMsg
211 )
212{
213 HRESULT hr = S_OK;
214 char szMessage[DUTIL_STRING_BUFFER] = { };
215
216 hr = ::StringCchPrintfA(szMessage, countof(szMessage), "Assertion failed in %s, %i\n%s", szFile, iLine, szMsg);
217 if (SUCCEEDED(hr))
218 {
219 Dutil_AssertMsg(szMessage);
220 }
221 else
222 {
223 Dutil_AssertMsg("Assert failed to build string");
224 }
225}
226
227
228/*******************************************************************
229Dutil_TraceSetLevel
230
231*******************************************************************/
232extern "C" void DAPI Dutil_TraceSetLevel(
233 __in REPORT_LEVEL rl,
234 __in BOOL fTraceFilenames
235 )
236{
237 Dutil_rlCurrentTrace = rl;
238 Dutil_fTraceFilenames = fTraceFilenames;
239}
240
241
242/*******************************************************************
243Dutil_TraceGetLevel
244
245*******************************************************************/
246extern "C" REPORT_LEVEL DAPI Dutil_TraceGetLevel()
247{
248 return Dutil_rlCurrentTrace;
249}
250
251
252/*******************************************************************
253Dutil_Trace
254
255*******************************************************************/
256extern "C" void DAPIV Dutil_Trace(
257 __in_z LPCSTR szFile,
258 __in int iLine,
259 __in REPORT_LEVEL rl,
260 __in_z __format_string LPCSTR szFormat,
261 ...
262 )
263{
264 AssertSz(REPORT_NONE != rl, "REPORT_NONE is not a valid tracing level");
265
266 HRESULT hr = S_OK;
267 char szOutput[DUTIL_STRING_BUFFER] = { };
268 char szMsg[DUTIL_STRING_BUFFER] = { };
269
270 if (Dutil_rlCurrentTrace < rl)
271 {
272 return;
273 }
274
275 va_list args;
276 va_start(args, szFormat);
277 hr = ::StringCchVPrintfA(szOutput, countof(szOutput), szFormat, args);
278 va_end(args);
279
280 if (SUCCEEDED(hr))
281 {
282 LPCSTR szPrefix = "Trace/u";
283 switch (rl)
284 {
285 case REPORT_STANDARD:
286 szPrefix = "Trace/s";
287 break;
288 case REPORT_VERBOSE:
289 szPrefix = "Trace/v";
290 break;
291 case REPORT_DEBUG:
292 szPrefix = "Trace/d";
293 break;
294 }
295
296 if (Dutil_fTraceFilenames)
297 {
298 hr = ::StringCchPrintfA(szMsg, countof(szMsg), "%s [%s,%d]: %s\r\n", szPrefix, szFile, iLine, szOutput);
299 }
300 else
301 {
302 hr = ::StringCchPrintfA(szMsg, countof(szMsg), "%s: %s\r\n", szPrefix, szOutput);
303 }
304
305 if (SUCCEEDED(hr))
306 {
307 OutputDebugStringA(szMsg);
308 }
309 // else fall through to the case below
310 }
311
312 if (FAILED(hr))
313 {
314 if (Dutil_fTraceFilenames)
315 {
316 ::StringCchPrintfA(szMsg, countof(szMsg), "Trace [%s,%d]: message too long, skipping\r\n", szFile, iLine);
317 }
318 else
319 {
320 ::StringCchPrintfA(szMsg, countof(szMsg), "Trace: message too long, skipping\r\n");
321 }
322
323 szMsg[countof(szMsg)-1] = '\0';
324 OutputDebugStringA(szMsg);
325 }
326}
327
328
329/*******************************************************************
330Dutil_TraceError
331
332*******************************************************************/
333extern "C" void DAPIV Dutil_TraceError(
334 __in_z LPCSTR szFile,
335 __in int iLine,
336 __in REPORT_LEVEL rl,
337 __in HRESULT hrError,
338 __in_z __format_string LPCSTR szFormat,
339 ...
340 )
341{
342 HRESULT hr = S_OK;
343 char szOutput[DUTIL_STRING_BUFFER] = { };
344 char szMsg[DUTIL_STRING_BUFFER] = { };
345
346 // if this is NOT an error report and we're not logging at this level, bail
347 if (REPORT_ERROR != rl && Dutil_rlCurrentTrace < rl)
348 {
349 return;
350 }
351
352 va_list args;
353 va_start(args, szFormat);
354 hr = ::StringCchVPrintfA(szOutput, countof(szOutput), szFormat, args);
355 va_end(args);
356
357 if (SUCCEEDED(hr))
358 {
359 if (Dutil_fTraceFilenames)
360 {
361 if (FAILED(hrError))
362 {
363 hr = ::StringCchPrintfA(szMsg, countof(szMsg), "TraceError 0x%x [%s,%d]: %s\r\n", hrError, szFile, iLine, szOutput);
364 }
365 else
366 {
367 hr = ::StringCchPrintfA(szMsg, countof(szMsg), "TraceError [%s,%d]: %s\r\n", szFile, iLine, szOutput);
368 }
369 }
370 else
371 {
372 if (FAILED(hrError))
373 {
374 hr = ::StringCchPrintfA(szMsg, countof(szMsg), "TraceError 0x%x: %s\r\n", hrError, szOutput);
375 }
376 else
377 {
378 hr = ::StringCchPrintfA(szMsg, countof(szMsg), "TraceError: %s\r\n", szOutput);
379 }
380 }
381
382 if (SUCCEEDED(hr))
383 {
384 OutputDebugStringA(szMsg);
385 }
386 // else fall through to the failure case below
387 }
388
389 if (FAILED(hr))
390 {
391 if (Dutil_fTraceFilenames)
392 {
393 if (FAILED(hrError))
394 {
395 ::StringCchPrintfA(szMsg, countof(szMsg), "TraceError 0x%x [%s,%d]: message too long, skipping\r\n", hrError, szFile, iLine);
396 }
397 else
398 {
399 ::StringCchPrintfA(szMsg, countof(szMsg), "TraceError [%s,%d]: message too long, skipping\r\n", szFile, iLine);
400 }
401 }
402 else
403 {
404 if (FAILED(hrError))
405 {
406 ::StringCchPrintfA(szMsg, countof(szMsg), "TraceError 0x%x: message too long, skipping\r\n", hrError);
407 }
408 else
409 {
410 ::StringCchPrintfA(szMsg, countof(szMsg), "TraceError: message too long, skipping\r\n");
411 }
412 }
413
414 szMsg[countof(szMsg)-1] = '\0';
415 OutputDebugStringA(szMsg);
416 }
417}
418
419
420DAPIV_(void) Dutil_TraceErrorSource(
421 __in_z LPCSTR szFile,
422 __in int iLine,
423 __in REPORT_LEVEL rl,
424 __in UINT source,
425 __in HRESULT hr,
426 __in_z __format_string LPCSTR szFormat,
427 ...
428 )
429{
430 // if this is NOT an error report and we're not logging at this level, bail
431 if (REPORT_ERROR != rl && Dutil_rlCurrentTrace < rl)
432 {
433 return;
434 }
435
436 if (DUTIL_SOURCE_UNKNOWN != source && vpfnTraceErrorCallback)
437 {
438 va_list args;
439 va_start(args, szFormat);
440 vpfnTraceErrorCallback(szFile, iLine, rl, source, hr, szFormat, args);
441 va_end(args);
442 }
443}
444
445
446/*******************************************************************
447Dutil_RootFailure
448
449*******************************************************************/
450extern "C" void DAPI Dutil_RootFailure(
451 __in_z LPCSTR szFile,
452 __in int iLine,
453 __in HRESULT hrError
454 )
455{
456#ifndef DEBUG
457 UNREFERENCED_PARAMETER(szFile);
458 UNREFERENCED_PARAMETER(iLine);
459 UNREFERENCED_PARAMETER(hrError);
460#endif // DEBUG
461
462 TraceError(hrError, "Root failure at %s:%d", szFile, iLine);
463}
464
465/*******************************************************************
466 LoadSystemLibrary - Fully qualifies the path to a module in the
467 Windows system directory and loads it.
468
469 Returns
470 E_MODNOTFOUND - The module could not be found.
471 * - Another error occured.
472********************************************************************/
473extern "C" HRESULT DAPI LoadSystemLibrary(
474 __in_z LPCWSTR wzModuleName,
475 __out HMODULE *phModule
476 )
477{
478 HRESULT hr = LoadSystemLibraryWithPath(wzModuleName, phModule, NULL);
479 return hr;
480}
481
482/*******************************************************************
483 LoadSystemLibraryWithPath - Fully qualifies the path to a module in
484 the Windows system directory and loads it
485 and returns the path
486
487 Returns
488 E_MODNOTFOUND - The module could not be found.
489 * - Another error occured.
490********************************************************************/
491extern "C" HRESULT DAPI LoadSystemLibraryWithPath(
492 __in_z LPCWSTR wzModuleName,
493 __out HMODULE *phModule,
494 __deref_out_z_opt LPWSTR* psczPath
495 )
496{
497 HRESULT hr = S_OK;
498 DWORD cch = 0;
499 WCHAR wzPath[MAX_PATH] = { };
500
501 cch = ::GetSystemDirectoryW(wzPath, MAX_PATH);
502 DExitOnNullWithLastError(cch, hr, "Failed to get the Windows system directory.");
503
504 if (L'\\' != wzPath[cch - 1])
505 {
506 hr = ::StringCchCatNW(wzPath, MAX_PATH, L"\\", 1);
507 DExitOnRootFailure(hr, "Failed to terminate the string with a backslash.");
508 }
509
510 hr = ::StringCchCatW(wzPath, MAX_PATH, wzModuleName);
511 DExitOnRootFailure(hr, "Failed to create the fully-qualified path to %ls.", wzModuleName);
512
513 *phModule = ::LoadLibraryW(wzPath);
514 DExitOnNullWithLastError(*phModule, hr, "Failed to load the library %ls.", wzModuleName);
515
516 if (psczPath)
517 {
518 hr = StrAllocString(psczPath, wzPath, MAX_PATH);
519 DExitOnFailure(hr, "Failed to copy the path to library.");
520 }
521
522LExit:
523 return hr;
524}
diff --git a/src/libs/dutil/WixToolset.DUtil/dutil.nuspec b/src/libs/dutil/WixToolset.DUtil/dutil.nuspec
new file mode 100644
index 00000000..3499a2d5
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/dutil.nuspec
@@ -0,0 +1,27 @@
1<?xml version="1.0"?>
2<package >
3 <metadata>
4 <id>$id$</id>
5 <version>$version$</version>
6 <authors>$authors$</authors>
7 <owners>$authors$</owners>
8 <license type="expression">MS-RL</license>
9 <projectUrl>https://github.com/wixtoolset/dutil</projectUrl>
10 <requireLicenseAcceptance>false</requireLicenseAcceptance>
11 <description>$description$</description>
12 <copyright>$copyright$</copyright>
13 </metadata>
14
15 <files>
16 <file src="build\$id$.props" target="build\" />
17 <file src="inc\*" target="build\native\include" />
18 <file src="..\..\build\$configuration$\v140\x64\dutil.lib" target="build\native\v140\x64" />
19 <file src="..\..\build\$configuration$\v140\x86\dutil.lib" target="build\native\v140\x86" />
20 <file src="..\..\build\$configuration$\v141\x64\dutil.lib" target="build\native\v141\x64" />
21 <file src="..\..\build\$configuration$\v141\x86\dutil.lib" target="build\native\v141\x86" />
22 <file src="..\..\build\$configuration$\v141\ARM64\dutil.lib" target="build\native\v141\ARM64" />
23 <file src="..\..\build\$configuration$\v142\x64\dutil.lib" target="build\native\v142\x64" />
24 <file src="..\..\build\$configuration$\v142\x86\dutil.lib" target="build\native\v142\x86" />
25 <file src="..\..\build\$configuration$\v142\ARM64\dutil.lib" target="build\native\v142\ARM64" />
26 </files>
27</package>
diff --git a/src/libs/dutil/WixToolset.DUtil/dutil.vcxproj b/src/libs/dutil/WixToolset.DUtil/dutil.vcxproj
new file mode 100644
index 00000000..4e341438
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/dutil.vcxproj
@@ -0,0 +1,183 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
5 <ItemGroup Label="ProjectConfigurations">
6 <ProjectConfiguration Include="Debug|ARM64">
7 <Configuration>Debug</Configuration>
8 <Platform>ARM64</Platform>
9 </ProjectConfiguration>
10 <ProjectConfiguration Include="Debug|Win32">
11 <Configuration>Debug</Configuration>
12 <Platform>Win32</Platform>
13 </ProjectConfiguration>
14 <ProjectConfiguration Include="Release|ARM64">
15 <Configuration>Release</Configuration>
16 <Platform>ARM64</Platform>
17 </ProjectConfiguration>
18 <ProjectConfiguration Include="Release|Win32">
19 <Configuration>Release</Configuration>
20 <Platform>Win32</Platform>
21 </ProjectConfiguration>
22 <ProjectConfiguration Include="Debug|x64">
23 <Configuration>Debug</Configuration>
24 <Platform>x64</Platform>
25 </ProjectConfiguration>
26 <ProjectConfiguration Include="Release|x64">
27 <Configuration>Release</Configuration>
28 <Platform>x64</Platform>
29 </ProjectConfiguration>
30 </ItemGroup>
31
32 <PropertyGroup Label="Globals">
33 <ProjectGuid>{1244E671-F108-4334-BA52-8A7517F26ECD}</ProjectGuid>
34 <ConfigurationType>StaticLibrary</ConfigurationType>
35 <TargetName>dutil</TargetName>
36 <MultiTargetLibrary>true</MultiTargetLibrary>
37 <PlatformToolset>v142</PlatformToolset>
38 <CharacterSet>MultiByte</CharacterSet>
39 <Description>WiX Toolset native library foundation</Description>
40 <PackageId>WixToolset.DUtil</PackageId>
41 </PropertyGroup>
42
43 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
44 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
45
46 <Import Project="..\NativeMultiTargeting.Build.props" />
47
48 <ItemGroup>
49 <ClCompile Include="acl2util.cpp" />
50 <ClCompile Include="aclutil.cpp" />
51 <ClCompile Include="apputil.cpp" />
52 <ClCompile Include="apuputil.cpp" />
53 <ClCompile Include="atomutil.cpp" />
54 <ClCompile Include="butil.cpp" />
55 <ClCompile Include="buffutil.cpp" />
56 <ClCompile Include="cabcutil.cpp" />
57 <ClCompile Include="cabutil.cpp" />
58 <ClCompile Include="certutil.cpp" />
59 <ClCompile Include="conutil.cpp" />
60 <ClCompile Include="cryputil.cpp" />
61 <ClCompile Include="deputil.cpp" />
62 <ClCompile Include="dictutil.cpp" />
63 <ClCompile Include="dirutil.cpp" />
64 <ClCompile Include="dlutil.cpp" />
65 <ClCompile Include="dpiutil.cpp" />
66 <ClCompile Include="dutil.cpp">
67 <PrecompiledHeader>Create</PrecompiledHeader>
68 <DisableSpecificWarnings>4091;4458</DisableSpecificWarnings>
69 </ClCompile>
70 <ClCompile Include="eseutil.cpp" />
71 <ClCompile Include="fileutil.cpp" />
72 <ClCompile Include="gdiputil.cpp" />
73 <ClCompile Include="guidutil.cpp" />
74 <ClCompile Include="iis7util.cpp" />
75 <ClCompile Include="inetutil.cpp" />
76 <ClCompile Include="iniutil.cpp" />
77 <ClCompile Include="jsonutil.cpp" />
78 <ClCompile Include="locutil.cpp" />
79 <ClCompile Include="logutil.cpp" />
80 <ClCompile Include="memutil.cpp" />
81 <ClCompile Include="metautil.cpp" />
82 <ClCompile Include="monutil.cpp" />
83 <ClCompile Include="osutil.cpp" />
84 <ClCompile Include="path2utl.cpp" />
85 <ClCompile Include="pathutil.cpp" />
86 <ClCompile Include="perfutil.cpp" />
87 <ClCompile Include="polcutil.cpp" />
88 <ClCompile Include="proc2utl.cpp" />
89 <ClCompile Include="proc3utl.cpp" />
90 <ClCompile Include="procutil.cpp" />
91 <ClCompile Include="regutil.cpp" />
92 <ClCompile Include="resrutil.cpp" />
93 <ClCompile Include="reswutil.cpp" />
94 <ClCompile Include="rexutil.cpp" />
95 <ClCompile Include="rmutil.cpp" />
96 <ClCompile Include="rssutil.cpp" />
97 <ClCompile Include="sceutil.cpp" Condition=" Exists('$(SqlCESdkIncludePath)') " />
98 <ClCompile Include="shelutil.cpp" />
99 <ClCompile Include="sqlutil.cpp" />
100 <ClCompile Include="srputil.cpp" />
101 <ClCompile Include="strutil.cpp" />
102 <ClCompile Include="svcutil.cpp" />
103 <ClCompile Include="thmutil.cpp" />
104 <ClCompile Include="timeutil.cpp" />
105 <ClCompile Include="uncutil.cpp" />
106 <ClCompile Include="uriutil.cpp" />
107 <ClCompile Include="userutil.cpp" />
108 <ClCompile Include="verutil.cpp" />
109 <ClCompile Include="wiutil.cpp" />
110 <ClCompile Include="wuautil.cpp" />
111 <ClCompile Include="xmlutil.cpp" />
112 </ItemGroup>
113
114 <ItemGroup>
115 <ClInclude Include="inc\aclutil.h" />
116 <ClInclude Include="inc\apputil.h" />
117 <ClInclude Include="inc\apuputil.h" />
118 <ClInclude Include="inc\atomutil.h" />
119 <ClInclude Include="inc\buffutil.h" />
120 <ClInclude Include="inc\butil.h" />
121 <ClInclude Include="inc\cabcutil.h" />
122 <ClInclude Include="inc\cabutil.h" />
123 <ClInclude Include="inc\certutil.h" />
124 <ClInclude Include="inc\conutil.h" />
125 <ClInclude Include="inc\cryputil.h" />
126 <ClInclude Include="inc\deputil.h" />
127 <ClInclude Include="inc\dictutil.h" />
128 <ClInclude Include="inc\dirutil.h" />
129 <ClInclude Include="inc\dlutil.h" />
130 <ClInclude Include="inc\dpiutil.h" />
131 <ClInclude Include="inc\dutil.h" />
132 <ClInclude Include="inc\dutilsources.h" />
133 <ClInclude Include="inc\eseutil.h" />
134 <ClInclude Include="inc\fileutil.h" />
135 <ClInclude Include="inc\gdiputil.h" />
136 <ClInclude Include="inc\guidutil.h" />
137 <ClInclude Include="inc\inetutil.h" />
138 <ClInclude Include="inc\iniutil.h" />
139 <ClInclude Include="inc\jsonutil.h" />
140 <ClInclude Include="inc\locutil.h" />
141 <ClInclude Include="inc\logutil.h" />
142 <ClInclude Include="inc\memutil.h" />
143 <ClInclude Include="inc\metautil.h" />
144 <ClInclude Include="inc\monutil.h" />
145 <ClInclude Include="inc\osutil.h" />
146 <ClInclude Include="inc\pathutil.h" />
147 <ClInclude Include="inc\perfutil.h" />
148 <ClInclude Include="inc\polcutil.h" />
149 <ClInclude Include="inc\procutil.h" />
150 <ClInclude Include="inc\regutil.h" />
151 <ClInclude Include="inc\resrutil.h" />
152 <ClInclude Include="inc\reswutil.h" />
153 <ClInclude Include="inc\rexutil.h" />
154 <ClInclude Include="inc\rssutil.h" />
155 <ClInclude Include="inc\sceutil.h" />
156 <ClInclude Include="inc\shelutil.h" />
157 <ClInclude Include="inc\sqlutil.h" />
158 <ClInclude Include="inc\srputil.h" />
159 <ClInclude Include="inc\strutil.h" />
160 <ClInclude Include="inc\svcutil.h" />
161 <ClInclude Include="inc\thmutil.h" />
162 <ClInclude Include="inc\timeutil.h" />
163 <ClInclude Include="inc\uriutil.h" />
164 <ClInclude Include="inc\userutil.h" />
165 <ClInclude Include="inc\verutil.h" />
166 <ClInclude Include="inc\wiutil.h" />
167 <ClInclude Include="inc\wuautil.h" />
168 <ClInclude Include="inc\xmlutil.h" />
169 <ClInclude Include="precomp.h" />
170 </ItemGroup>
171
172 <ItemGroup>
173 <None Include="packages.config" />
174 <None Include="xsd\thmutil.xsd" />
175 </ItemGroup>
176
177 <ItemGroup>
178 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" />
179 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" />
180 </ItemGroup>
181
182 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
183</Project>
diff --git a/src/libs/dutil/WixToolset.DUtil/dutil.vcxproj.filters b/src/libs/dutil/WixToolset.DUtil/dutil.vcxproj.filters
new file mode 100644
index 00000000..b93d166b
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/dutil.vcxproj.filters
@@ -0,0 +1,372 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 <ItemGroup>
4 <Filter Include="Source Files">
5 <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
6 <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
7 </Filter>
8 <Filter Include="Header Files">
9 <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
10 <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
11 </Filter>
12 <Filter Include="Resource Files">
13 <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
14 <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
15 </Filter>
16 </ItemGroup>
17 <ItemGroup>
18 <ClCompile Include="acl2util.cpp">
19 <Filter>Source Files</Filter>
20 </ClCompile>
21 <ClCompile Include="aclutil.cpp">
22 <Filter>Source Files</Filter>
23 </ClCompile>
24 <ClCompile Include="apputil.cpp">
25 <Filter>Source Files</Filter>
26 </ClCompile>
27 <ClCompile Include="apuputil.cpp">
28 <Filter>Source Files</Filter>
29 </ClCompile>
30 <ClCompile Include="atomutil.cpp">
31 <Filter>Source Files</Filter>
32 </ClCompile>
33 <ClCompile Include="buffutil.cpp">
34 <Filter>Source Files</Filter>
35 </ClCompile>
36 <ClCompile Include="butil.cpp">
37 <Filter>Source Files</Filter>
38 </ClCompile>
39 <ClCompile Include="cabcutil.cpp">
40 <Filter>Source Files</Filter>
41 </ClCompile>
42 <ClCompile Include="cabutil.cpp">
43 <Filter>Source Files</Filter>
44 </ClCompile>
45 <ClCompile Include="certutil.cpp">
46 <Filter>Source Files</Filter>
47 </ClCompile>
48 <ClCompile Include="conutil.cpp">
49 <Filter>Source Files</Filter>
50 </ClCompile>
51 <ClCompile Include="cryputil.cpp">
52 <Filter>Source Files</Filter>
53 </ClCompile>
54 <ClCompile Include="dictutil.cpp">
55 <Filter>Source Files</Filter>
56 </ClCompile>
57 <ClCompile Include="dirutil.cpp">
58 <Filter>Source Files</Filter>
59 </ClCompile>
60 <ClCompile Include="dlutil.cpp">
61 <Filter>Source Files</Filter>
62 </ClCompile>
63 <ClCompile Include="dpiutil.cpp">
64 <Filter>Source Files</Filter>
65 </ClCompile>
66 <ClCompile Include="dutil.cpp">
67 <Filter>Source Files</Filter>
68 </ClCompile>
69 <ClCompile Include="eseutil.cpp">
70 <Filter>Source Files</Filter>
71 </ClCompile>
72 <ClCompile Include="fileutil.cpp">
73 <Filter>Source Files</Filter>
74 </ClCompile>
75 <ClCompile Include="gdiputil.cpp">
76 <Filter>Source Files</Filter>
77 </ClCompile>
78 <ClCompile Include="guidutil.cpp">
79 <Filter>Source Files</Filter>
80 </ClCompile>
81 <ClCompile Include="iis7util.cpp">
82 <Filter>Source Files</Filter>
83 </ClCompile>
84 <ClCompile Include="inetutil.cpp">
85 <Filter>Source Files</Filter>
86 </ClCompile>
87 <ClCompile Include="jsonutil.cpp">
88 <Filter>Source Files</Filter>
89 </ClCompile>
90 <ClCompile Include="locutil.cpp">
91 <Filter>Source Files</Filter>
92 </ClCompile>
93 <ClCompile Include="logutil.cpp">
94 <Filter>Source Files</Filter>
95 </ClCompile>
96 <ClCompile Include="memutil.cpp">
97 <Filter>Source Files</Filter>
98 </ClCompile>
99 <ClCompile Include="metautil.cpp">
100 <Filter>Source Files</Filter>
101 </ClCompile>
102 <ClCompile Include="monutil.cpp">
103 <Filter>Source Files</Filter>
104 </ClCompile>
105 <ClCompile Include="osutil.cpp">
106 <Filter>Source Files</Filter>
107 </ClCompile>
108 <ClCompile Include="path2utl.cpp">
109 <Filter>Source Files</Filter>
110 </ClCompile>
111 <ClCompile Include="pathutil.cpp">
112 <Filter>Source Files</Filter>
113 </ClCompile>
114 <ClCompile Include="perfutil.cpp">
115 <Filter>Source Files</Filter>
116 </ClCompile>
117 <ClCompile Include="proc2utl.cpp">
118 <Filter>Source Files</Filter>
119 </ClCompile>
120 <ClCompile Include="procutil.cpp">
121 <Filter>Source Files</Filter>
122 </ClCompile>
123 <ClCompile Include="resrutil.cpp">
124 <Filter>Source Files</Filter>
125 </ClCompile>
126 <ClCompile Include="reswutil.cpp">
127 <Filter>Source Files</Filter>
128 </ClCompile>
129 <ClCompile Include="rexutil.cpp">
130 <Filter>Source Files</Filter>
131 </ClCompile>
132 <ClCompile Include="rmutil.cpp">
133 <Filter>Source Files</Filter>
134 </ClCompile>
135 <ClCompile Include="rssutil.cpp">
136 <Filter>Source Files</Filter>
137 </ClCompile>
138 <ClCompile Include="shelutil.cpp">
139 <Filter>Source Files</Filter>
140 </ClCompile>
141 <ClCompile Include="sqlutil.cpp">
142 <Filter>Source Files</Filter>
143 </ClCompile>
144 <ClCompile Include="strutil.cpp">
145 <Filter>Source Files</Filter>
146 </ClCompile>
147 <ClCompile Include="thmutil.cpp">
148 <Filter>Source Files</Filter>
149 </ClCompile>
150 <ClCompile Include="timeutil.cpp">
151 <Filter>Source Files</Filter>
152 </ClCompile>
153 <ClCompile Include="uncutil.cpp">
154 <Filter>Source Files</Filter>
155 </ClCompile>
156 <ClCompile Include="uriutil.cpp">
157 <Filter>Source Files</Filter>
158 </ClCompile>
159 <ClCompile Include="userutil.cpp">
160 <Filter>Source Files</Filter>
161 </ClCompile>
162 <ClCompile Include="verutil.cpp">
163 <Filter>Source Files</Filter>
164 </ClCompile>
165 <ClCompile Include="wiutil.cpp">
166 <Filter>Source Files</Filter>
167 </ClCompile>
168 <ClCompile Include="xmlutil.cpp">
169 <Filter>Source Files</Filter>
170 </ClCompile>
171 <ClCompile Include="svcutil.cpp">
172 <Filter>Source Files</Filter>
173 </ClCompile>
174 <ClCompile Include="regutil.cpp">
175 <Filter>Source Files</Filter>
176 </ClCompile>
177 <ClCompile Include="iniutil.cpp">
178 <Filter>Source Files</Filter>
179 </ClCompile>
180 <ClCompile Include="proc3utl.cpp">
181 <Filter>Source Files</Filter>
182 </ClCompile>
183 <ClCompile Include="wuautil.cpp">
184 <Filter>Source Files</Filter>
185 </ClCompile>
186 <ClCompile Include="srputil.cpp">
187 <Filter>Source Files</Filter>
188 </ClCompile>
189 <ClCompile Include="polcutil.cpp">
190 <Filter>Source Files</Filter>
191 </ClCompile>
192 <ClCompile Include="deputil.cpp">
193 <Filter>Source Files</Filter>
194 </ClCompile>
195 </ItemGroup>
196 <ItemGroup>
197 <ClInclude Include="inc\aclutil.h">
198 <Filter>Header Files</Filter>
199 </ClInclude>
200 <ClInclude Include="inc\apputil.h">
201 <Filter>Header Files</Filter>
202 </ClInclude>
203 <ClInclude Include="inc\apuputil.h">
204 <Filter>Header Files</Filter>
205 </ClInclude>
206 <ClInclude Include="inc\atomutil.h">
207 <Filter>Header Files</Filter>
208 </ClInclude>
209 <ClInclude Include="inc\buffutil.h">
210 <Filter>Header Files</Filter>
211 </ClInclude>
212 <ClInclude Include="inc\butil.h">
213 <Filter>Header Files</Filter>
214 </ClInclude>
215 <ClInclude Include="inc\cabcutil.h">
216 <Filter>Header Files</Filter>
217 </ClInclude>
218 <ClInclude Include="inc\cabutil.h">
219 <Filter>Header Files</Filter>
220 </ClInclude>
221 <ClInclude Include="inc\certutil.h">
222 <Filter>Header Files</Filter>
223 </ClInclude>
224 <ClInclude Include="inc\conutil.h">
225 <Filter>Header Files</Filter>
226 </ClInclude>
227 <ClInclude Include="inc\cryputil.h">
228 <Filter>Header Files</Filter>
229 </ClInclude>
230 <ClInclude Include="inc\dictutil.h">
231 <Filter>Header Files</Filter>
232 </ClInclude>
233 <ClInclude Include="inc\dirutil.h">
234 <Filter>Header Files</Filter>
235 </ClInclude>
236 <ClInclude Include="inc\dlutil.h">
237 <Filter>Header Files</Filter>
238 </ClInclude>
239 <ClInclude Include="inc\dpiutil.h">
240 <Filter>Header Files</Filter>
241 </ClInclude>
242 <ClInclude Include="inc\dutil.h">
243 <Filter>Header Files</Filter>
244 </ClInclude>
245 <ClInclude Include="inc\dutilsources.h">
246 <Filter>Header Files</Filter>
247 </ClInclude>
248 <ClInclude Include="inc\eseutil.h">
249 <Filter>Header Files</Filter>
250 </ClInclude>
251 <ClInclude Include="inc\fileutil.h">
252 <Filter>Header Files</Filter>
253 </ClInclude>
254 <ClInclude Include="inc\gdiputil.h">
255 <Filter>Header Files</Filter>
256 </ClInclude>
257 <ClInclude Include="inc\guidutil.h">
258 <Filter>Header Files</Filter>
259 </ClInclude>
260 <ClInclude Include="inc\inetutil.h">
261 <Filter>Header Files</Filter>
262 </ClInclude>
263 <ClInclude Include="inc\jsonutil.h">
264 <Filter>Header Files</Filter>
265 </ClInclude>
266 <ClInclude Include="inc\locutil.h">
267 <Filter>Header Files</Filter>
268 </ClInclude>
269 <ClInclude Include="inc\logutil.h">
270 <Filter>Header Files</Filter>
271 </ClInclude>
272 <ClInclude Include="inc\memutil.h">
273 <Filter>Header Files</Filter>
274 </ClInclude>
275 <ClInclude Include="inc\metautil.h">
276 <Filter>Header Files</Filter>
277 </ClInclude>
278 <ClInclude Include="inc\monutil.h">
279 <Filter>Header Files</Filter>
280 </ClInclude>
281 <ClInclude Include="inc\osutil.h">
282 <Filter>Header Files</Filter>
283 </ClInclude>
284 <ClInclude Include="inc\pathutil.h">
285 <Filter>Header Files</Filter>
286 </ClInclude>
287 <ClInclude Include="inc\perfutil.h">
288 <Filter>Header Files</Filter>
289 </ClInclude>
290 <ClInclude Include="inc\procutil.h">
291 <Filter>Header Files</Filter>
292 </ClInclude>
293 <ClInclude Include="inc\regutil.h">
294 <Filter>Header Files</Filter>
295 </ClInclude>
296 <ClInclude Include="inc\resrutil.h">
297 <Filter>Header Files</Filter>
298 </ClInclude>
299 <ClInclude Include="inc\reswutil.h">
300 <Filter>Header Files</Filter>
301 </ClInclude>
302 <ClInclude Include="inc\rmutil.h">
303 <Filter>Header Files</Filter>
304 </ClInclude>
305 <ClInclude Include="inc\rexutil.h">
306 <Filter>Header Files</Filter>
307 </ClInclude>
308 <ClInclude Include="inc\rssutil.h">
309 <Filter>Header Files</Filter>
310 </ClInclude>
311 <ClInclude Include="inc\sceutil.h">
312 <Filter>Header Files</Filter>
313 </ClInclude>
314 <ClInclude Include="inc\shelutil.h">
315 <Filter>Header Files</Filter>
316 </ClInclude>
317 <ClInclude Include="inc\sqlutil.h">
318 <Filter>Header Files</Filter>
319 </ClInclude>
320 <ClInclude Include="inc\strutil.h">
321 <Filter>Header Files</Filter>
322 </ClInclude>
323 <ClInclude Include="inc\thmutil.h">
324 <Filter>Header Files</Filter>
325 </ClInclude>
326 <ClInclude Include="inc\timeutil.h">
327 <Filter>Header Files</Filter>
328 </ClInclude>
329 <ClInclude Include="inc\uriutil.h">
330 <Filter>Header Files</Filter>
331 </ClInclude>
332 <ClInclude Include="inc\userutil.h">
333 <Filter>Header Files</Filter>
334 </ClInclude>
335 <ClInclude Include="inc\verutil.h">
336 <Filter>Header Files</Filter>
337 </ClInclude>
338 <ClInclude Include="inc\wiutil.h">
339 <Filter>Header Files</Filter>
340 </ClInclude>
341 <ClInclude Include="inc\xmlutil.h">
342 <Filter>Header Files</Filter>
343 </ClInclude>
344 <ClInclude Include="precomp.h">
345 <Filter>Header Files</Filter>
346 </ClInclude>
347 <ClInclude Include="inc\svcutil.h">
348 <Filter>Header Files</Filter>
349 </ClInclude>
350 <ClInclude Include="inc\iniutil.h">
351 <Filter>Header Files</Filter>
352 </ClInclude>
353 <ClInclude Include="inc\wuautil.h">
354 <Filter>Header Files</Filter>
355 </ClInclude>
356 <ClInclude Include="inc\srputil.h">
357 <Filter>Header Files</Filter>
358 </ClInclude>
359 <ClInclude Include="inc\polcutil.h">
360 <Filter>Header Files</Filter>
361 </ClInclude>
362 <ClInclude Include="inc\deputil.h">
363 <Filter>Header Files</Filter>
364 </ClInclude>
365 </ItemGroup>
366 <ItemGroup>
367 <None Include="xsd\thmutil.xsd">
368 <Filter>Header Files</Filter>
369 </None>
370 <None Include="packages.config" />
371 </ItemGroup>
372</Project> \ No newline at end of file
diff --git a/src/libs/dutil/WixToolset.DUtil/eseutil.cpp b/src/libs/dutil/WixToolset.DUtil/eseutil.cpp
new file mode 100644
index 00000000..b9455d4b
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/eseutil.cpp
@@ -0,0 +1,1340 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define EseExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_ESEUTIL, x, s, __VA_ARGS__)
8#define EseExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_ESEUTIL, x, s, __VA_ARGS__)
9#define EseExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_ESEUTIL, x, s, __VA_ARGS__)
10#define EseExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_ESEUTIL, x, s, __VA_ARGS__)
11#define EseExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_ESEUTIL, x, s, __VA_ARGS__)
12#define EseExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_ESEUTIL, x, s, __VA_ARGS__)
13#define EseExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_ESEUTIL, p, x, e, s, __VA_ARGS__)
14#define EseExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_ESEUTIL, p, x, s, __VA_ARGS__)
15#define EseExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_ESEUTIL, p, x, e, s, __VA_ARGS__)
16#define EseExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_ESEUTIL, p, x, s, __VA_ARGS__)
17#define EseExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_ESEUTIL, e, x, s, __VA_ARGS__)
18#define EseExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_ESEUTIL, g, x, s, __VA_ARGS__)
19
20struct ESE_QUERY
21{
22 ESE_QUERY_TYPE qtQueryType;
23 BOOL fIndexRangeSet;
24
25 JET_SESID jsSession;
26 JET_TABLEID jtTable;
27
28 DWORD dwColumns;
29 void *pvData[10]; // The data queried for for this column
30 DWORD cbData[10]; // One for each column, describes the size of the corresponding entry in ppvData
31};
32
33// Todo: convert more JET_ERR to HRESULTS here
34HRESULT HresultFromJetError(JET_ERR jEr)
35{
36 HRESULT hr = S_OK;
37
38 switch (jEr)
39 {
40 case JET_errSuccess:
41 return S_OK;
42
43 case JET_wrnNyi:
44 return E_NOTIMPL;
45 break;
46
47 case JET_errOutOfMemory:
48 hr = E_OUTOFMEMORY;
49 break;
50
51 case JET_errInvalidParameter: __fallthrough;
52 case JET_errInvalidInstance:
53 hr = E_INVALIDARG;
54 break;
55
56 case JET_errDatabaseInUse:
57 hr = HRESULT_FROM_WIN32(ERROR_DEVICE_IN_USE);
58 break;
59
60 case JET_errKeyDuplicate:
61 hr = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS);
62 break;
63
64 case JET_errInvalidSystemPath: __fallthrough;
65 case JET_errInvalidLogDirectory: __fallthrough;
66 case JET_errInvalidPath: __fallthrough;
67 case JET_errDatabaseInvalidPath:
68 hr = HRESULT_FROM_WIN32(ERROR_INVALID_NAME);
69 break;
70
71 case JET_errDatabaseLocked:
72 hr = HRESULT_FROM_WIN32(ERROR_FILE_CHECKED_OUT);
73 break;
74
75 case JET_errInvalidDatabase:
76 hr = HRESULT_FROM_WIN32(ERROR_INTERNAL_DB_CORRUPTION);
77 break;
78
79 case JET_errCallbackNotResolved:
80 hr = HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION);
81 break;
82
83 case JET_errNoCurrentRecord: __fallthrough;
84 case JET_errRecordNotFound: __fallthrough;
85 case JET_errFileNotFound: __fallthrough;
86 case JET_errObjectNotFound:
87 hr = E_NOTFOUND;
88 break;
89
90 case JET_wrnBufferTruncated:
91 hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
92 break;
93
94 case JET_errFileAccessDenied:
95 hr = E_ACCESSDENIED;
96 break;
97
98 default:
99 hr = E_FAIL;
100 }
101
102 // Log the actual Jet error code so we have record of it before it's morphed into an HRESULT to be compatible with the rest of our code
103 ExitTraceSource(DUTIL_SOURCE_ESEUTIL, hr, "Encountered Jet Error: 0x%08x", jEr);
104
105 return hr;
106}
107
108#define ExitOnJetFailure(e, x, s, ...) { x = HresultFromJetError(e); if (S_OK != x) { ExitTraceSource(DUTIL_SOURCE_ESEUTIL, x, s, __VA_ARGS__); goto LExit; }}
109#define ExitOnRootJetFailure(e, x, s, ...) { x = HresultFromJetError(e); if (S_OK != x) { Dutil_RootFailure(__FILE__, __LINE__, x); ExitTraceSource(DUTIL_SOURCE_ESEUTIL, x, s, __VA_ARGS__); goto LExit; }}
110
111HRESULT DAPI EseBeginSession(
112 __out JET_INSTANCE *pjiInstance,
113 __out JET_SESID *pjsSession,
114 __in_z LPCWSTR pszInstance,
115 __in_z LPCWSTR pszPath
116 )
117{
118 HRESULT hr = S_OK;
119 JET_ERR jEr = JET_errSuccess;
120 LPSTR pszAnsiInstance = NULL;
121 LPSTR pszAnsiPath = NULL;
122
123 hr = DirEnsureExists(pszPath, NULL);
124 EseExitOnFailure(hr, "Failed to ensure database directory exists");
125
126 // Sigh. JETblue requires Vista and up for the wide character version of this function, so we'll convert to ANSI before calling,
127 // likely breaking everyone with unicode characters in their path.
128 hr = StrAnsiAllocString(&pszAnsiInstance, pszInstance, 0, CP_ACP);
129 EseExitOnFailure(hr, "Failed converting instance name to ansi");
130
131 hr = StrAnsiAllocString(&pszAnsiPath, pszPath, 0, CP_ACP);
132 EseExitOnFailure(hr, "Failed converting session path name to ansi");
133
134 jEr = JetCreateInstanceA(pjiInstance, pszAnsiInstance);
135 ExitOnJetFailure(jEr, hr, "Failed to create instance");
136
137 jEr = JetSetSystemParameter(pjiInstance, NULL, JET_paramSystemPath, NULL, pszAnsiPath);
138 ExitOnJetFailure(jEr, hr, "Failed to set jet system path to: %s", pszAnsiPath);
139
140 // This makes sure log files that are created are created next to the database, not next to our EXE (note they last after execution)
141 jEr = JetSetSystemParameter(pjiInstance, NULL, JET_paramLogFilePath, NULL, pszAnsiPath);
142 ExitOnJetFailure(jEr, hr, "Failed to set jet log file path to: %s", pszAnsiPath);
143
144 jEr = JetSetSystemParameter(pjiInstance, NULL, JET_paramMaxOpenTables, 10, NULL);
145 ExitOnJetFailure(jEr, hr, "Failed to set jet max open tables parameter");
146
147 // TODO: Use callback hooks so that Jet Engine uses our memory allocation methods, etc.? (search docs for "JET_PFNREALLOC" - there are other callbacks too)
148
149 jEr = JetInit(pjiInstance);
150 ExitOnJetFailure(jEr, hr, "Failed to initialize jet engine instance");
151
152 jEr = JetBeginSession(*pjiInstance, pjsSession, NULL, NULL);
153 ExitOnJetFailure(jEr, hr, "Failed to begin jet session");
154
155LExit:
156 ReleaseStr(pszAnsiInstance);
157 ReleaseStr(pszAnsiPath);
158
159 return hr;
160}
161
162HRESULT DAPI EseEndSession(
163 __in JET_INSTANCE jiInstance,
164 __in JET_SESID jsSession
165 )
166{
167 HRESULT hr = S_OK;
168 JET_ERR jEr = JET_errSuccess;
169
170 jEr = JetEndSession(jsSession, 0);
171 ExitOnJetFailure(jEr, hr, "Failed to end jet session");
172
173 jEr = JetTerm(jiInstance);
174 ExitOnJetFailure(jEr, hr, "Failed to uninitialize jet engine instance");
175
176LExit:
177 return hr;
178}
179
180// Utility function used by EnsureSchema()
181HRESULT AllocColumnCreateStruct(
182 __in const ESE_TABLE_SCHEMA *ptsSchema,
183 __deref_out JET_COLUMNCREATE **ppjccColumnCreate
184 )
185{
186 HRESULT hr = S_OK;
187 DWORD_PTR i;
188 size_t cbAllocSize = 0;
189
190 hr = ::SizeTMult(ptsSchema->dwColumns, sizeof(JET_COLUMNCREATE), &(cbAllocSize));
191 EseExitOnFailure(hr, "Maximum allocation exceeded.");
192
193 *ppjccColumnCreate = static_cast<JET_COLUMNCREATE*>(MemAlloc(cbAllocSize, TRUE));
194 EseExitOnNull(*ppjccColumnCreate, hr, E_OUTOFMEMORY, "Failed to allocate column create structure for database");
195
196 for (i = 0; i < ptsSchema->dwColumns; ++i)
197 {
198 (*ppjccColumnCreate)[i].cbStruct = sizeof(JET_COLUMNCREATE);
199
200 hr = StrAnsiAllocString(&(*ppjccColumnCreate)[i].szColumnName, ptsSchema->pcsColumns[i].pszName, 0, CP_ACP);
201 EseExitOnFailure(hr, "Failed to allocate ansi column name: %ls", ptsSchema->pcsColumns[i].pszName);
202
203 (*ppjccColumnCreate)[i].coltyp = ptsSchema->pcsColumns[i].jcColumnType;
204
205 if (JET_coltypText == (*ppjccColumnCreate)[i].coltyp)
206 {
207 (*ppjccColumnCreate)[i].cbMax = 256;
208 }
209 else if (JET_coltypLongText == (*ppjccColumnCreate)[i].coltyp)
210 {
211 (*ppjccColumnCreate)[i].cbMax = 2147483648;
212 (*ppjccColumnCreate)[i].grbit = JET_bitColumnTagged; // LongText columns must be tagged
213 ptsSchema->pcsColumns[i].fNullable = TRUE;
214 }
215 else if (JET_coltypLong == (*ppjccColumnCreate)[i].coltyp)
216 {
217 (*ppjccColumnCreate)[i].cbMax = 4;
218
219 if (ptsSchema->pcsColumns[i].fAutoIncrement)
220 {
221 (*ppjccColumnCreate)[i].grbit |= JET_bitColumnAutoincrement;
222 }
223 }
224
225 if (!(ptsSchema->pcsColumns[i].fNullable))
226 {
227 (*ppjccColumnCreate)[i].grbit |= JET_bitColumnNotNULL;
228 }
229
230 (*ppjccColumnCreate)[i].pvDefault = NULL;
231 (*ppjccColumnCreate)[i].cbDefault = 0;
232 (*ppjccColumnCreate)[i].cp = 1200;
233 (*ppjccColumnCreate)[i].columnid = 0;
234 (*ppjccColumnCreate)[i].err = 0;
235 }
236
237LExit:
238 return hr;
239}
240
241HRESULT FreeColumnCreateStruct(
242 __in_ecount(dwColumns) JET_COLUMNCREATE *pjccColumnCreate,
243 __in DWORD dwColumns
244 )
245{
246 HRESULT hr = S_OK;
247 DWORD i;
248
249 for (i = 0; i < dwColumns; ++i)
250 {
251 ReleaseStr((pjccColumnCreate[i]).szColumnName);
252 }
253
254 hr = MemFree(pjccColumnCreate);
255 EseExitOnFailure(hr, "Failed to release core column create struct");
256
257LExit:
258 return hr;
259}
260
261// Utility function used by EnsureSchema()
262HRESULT AllocIndexCreateStruct(
263 __in const ESE_TABLE_SCHEMA *ptsSchema,
264 __deref_out JET_INDEXCREATE **ppjicIndexCreate
265 )
266{
267 HRESULT hr = S_OK;
268 LPSTR pszMultiSzKeys = NULL;
269 LPSTR pszIndexName = NULL;
270 LPSTR pszTempString = NULL;
271 BOOL fKeyColumns = FALSE;
272 DWORD_PTR i;
273
274 for (i=0; i < ptsSchema->dwColumns; ++i)
275 {
276 if (ptsSchema->pcsColumns[i].fKey)
277 {
278 hr = StrAnsiAllocString(&pszTempString, ptsSchema->pcsColumns[i].pszName, 0, CP_ACP);
279 EseExitOnFailure(hr, "Failed to convert string to ansi: %ls", ptsSchema->pcsColumns[i].pszName);
280
281 hr = StrAnsiAllocConcat(&pszMultiSzKeys, "+", 0);
282 EseExitOnFailure(hr, "Failed to append plus sign to multisz string: %s", pszTempString);
283
284 hr = StrAnsiAllocConcat(&pszMultiSzKeys, pszTempString, 0);
285 EseExitOnFailure(hr, "Failed to append column name to multisz string: %s", pszTempString);
286
287 ReleaseNullStr(pszTempString);
288
289 // All question marks will be converted to null characters later; this is just to trick dutil
290 // into letting us create an ansi, double-null-terminated list of single-null-terminated strings
291 hr = StrAnsiAllocConcat(&pszMultiSzKeys, "?", 0);
292 EseExitOnFailure(hr, "Failed to append placeholder character to multisz string: %hs", pszMultiSzKeys);
293
294 // Record that at least one key column was found
295 fKeyColumns = TRUE;
296 }
297 }
298
299 // If no key columns were found, don't create an index - just return
300 if (!fKeyColumns)
301 {
302 ExitFunction1(hr = S_OK);
303 }
304
305 hr = StrAnsiAllocString(&pszIndexName, ptsSchema->pszName, 0, CP_ACP);
306 EseExitOnFailure(hr, "Failed to allocate ansi string version of %ls", ptsSchema->pszName);
307
308 hr = StrAnsiAllocConcat(&pszIndexName, "_Index", 0);
309 EseExitOnFailure(hr, "Failed to append table name string version of %ls", ptsSchema->pszName);
310
311 *ppjicIndexCreate = static_cast<JET_INDEXCREATE*>(MemAlloc(sizeof(JET_INDEXCREATE), TRUE));
312 EseExitOnNull(*ppjicIndexCreate, hr, E_OUTOFMEMORY, "Failed to allocate index create structure for database");
313
314 // Record the size including both null terminators - the struct requires this
315 size_t cchSize = 0;
316 hr = ::StringCchLengthA(pszMultiSzKeys, STRSAFE_MAX_LENGTH, &cchSize);
317 EseExitOnRootFailure(hr, "Failed to get size of keys string");
318
319 ++cchSize; // add 1 to include null character at the end
320
321 // At this point convert all question marks to null characters
322 for (i = 0; i < cchSize; ++i)
323 {
324 if ('?' == pszMultiSzKeys[i])
325 {
326 pszMultiSzKeys[i] = '\0';
327 }
328 }
329
330 (*ppjicIndexCreate)->cbStruct = sizeof(JET_INDEXCREATE);
331 (*ppjicIndexCreate)->szIndexName = pszIndexName;
332 (*ppjicIndexCreate)->szKey = pszMultiSzKeys;
333 (*ppjicIndexCreate)->cbKey = (DWORD)cchSize;
334 (*ppjicIndexCreate)->grbit = JET_bitIndexUnique | JET_bitIndexPrimary;
335 (*ppjicIndexCreate)->ulDensity = 80;
336 (*ppjicIndexCreate)->lcid = 1033;
337 (*ppjicIndexCreate)->pidxunicode = NULL;
338 (*ppjicIndexCreate)->cbVarSegMac = 0;
339 (*ppjicIndexCreate)->rgconditionalcolumn = NULL;
340 (*ppjicIndexCreate)->cConditionalColumn = 0;
341 (*ppjicIndexCreate)->err = 0;
342
343LExit:
344 ReleaseStr(pszTempString);
345
346 return hr;
347}
348
349HRESULT EnsureSchema(
350 __in JET_DBID jdbDb,
351 __in JET_SESID jsSession,
352 __in ESE_DATABASE_SCHEMA *pdsSchema
353 )
354{
355 HRESULT hr = S_OK;
356 JET_ERR jEr = JET_errSuccess;
357 BOOL fTransaction = FALSE;
358 DWORD dwTable;
359 DWORD dwColumn;
360 JET_TABLECREATE jtTableCreate = { };
361
362 // Set parameters which apply to all tables here
363 jtTableCreate.cbStruct = sizeof(jtTableCreate);
364 jtTableCreate.ulPages = 100;
365 jtTableCreate.ulDensity = 0; // per the docs, 0 means "use the default value"
366 jtTableCreate.cIndexes = 1;
367
368 hr = EseBeginTransaction(jsSession);
369 EseExitOnFailure(hr, "Failed to begin transaction to create tables");
370 fTransaction = TRUE;
371
372 for (dwTable = 0;dwTable < pdsSchema->dwTables; ++dwTable)
373 {
374 // Don't free this pointer - it's just a shortcut to the current table's name within the struct
375 LPCWSTR pwzTableName = pdsSchema->ptsTables[dwTable].pszName;
376
377 // Ensure table exists
378 hr = EseOpenTable(jsSession, jdbDb, pwzTableName, &pdsSchema->ptsTables[dwTable].jtTable);
379 if (E_NOTFOUND == hr) // if the table is missing, create it
380 {
381 // Fill out the JET_TABLECREATE struct
382 hr = StrAnsiAllocString(&jtTableCreate.szTableName, pdsSchema->ptsTables[dwTable].pszName, 0, CP_ACP);
383 EseExitOnFailure(hr, "Failed converting table name to ansi");
384
385 hr = AllocColumnCreateStruct(&(pdsSchema->ptsTables[dwTable]), &jtTableCreate.rgcolumncreate);
386 EseExitOnFailure(hr, "Failed to allocate column create struct");
387
388 hr = AllocIndexCreateStruct(&(pdsSchema->ptsTables[dwTable]), &jtTableCreate.rgindexcreate);
389 EseExitOnFailure(hr, "Failed to allocate index create struct");
390
391 jtTableCreate.cColumns = pdsSchema->ptsTables[dwTable].dwColumns;
392 jtTableCreate.tableid = NULL;
393
394 // TODO: Investigate why we can't create a table without a key column?
395 // Actually create the table using our JET_TABLECREATE struct
396 jEr = JetCreateTableColumnIndex(jsSession, jdbDb, &jtTableCreate);
397 ExitOnJetFailure(jEr, hr, "Failed to create %ls table", pwzTableName);
398
399 // Record the table ID in our cache
400 pdsSchema->ptsTables[dwTable].jtTable = jtTableCreate.tableid;
401
402 // Record the column IDs in our cache
403 for (dwColumn = 0; dwColumn < pdsSchema->ptsTables[dwTable].dwColumns; ++dwColumn)
404 {
405 pdsSchema->ptsTables[dwTable].pcsColumns[dwColumn].jcColumn = jtTableCreate.rgcolumncreate[dwColumn].columnid;
406 }
407
408 // Free and NULL things we allocated in this struct
409 ReleaseNullStr(jtTableCreate.szTableName);
410
411 hr = FreeColumnCreateStruct(jtTableCreate.rgcolumncreate, jtTableCreate.cColumns);
412 EseExitOnFailure(hr, "Failed to free column create struct");
413 jtTableCreate.rgcolumncreate = NULL;
414 }
415 else
416 {
417 // If the table already exists, grab the column ids and put them into our cache
418 for (dwColumn = 0;dwColumn < pdsSchema->ptsTables[dwTable].dwColumns; ++dwColumn)
419 {
420 // Don't free this - it's just a shortcut to the current column within the struct
421 ESE_COLUMN_SCHEMA *pcsColumn = &(pdsSchema->ptsTables[dwTable].pcsColumns[dwColumn]);
422 ULONG ulColumnSize = 0;
423 BOOL fNullable = pcsColumn->fNullable;
424
425 // Todo: this code is nearly duplicated from AllocColumnCreateStruct - factor it out!
426 if (JET_coltypText == pcsColumn->jcColumnType)
427 {
428 ulColumnSize = 256;
429 }
430 else if (JET_coltypLongText == pcsColumn->jcColumnType)
431 {
432 ulColumnSize = 2147483648;
433 fNullable = TRUE;
434 }
435 else if (JET_coltypLong == pcsColumn->jcColumnType)
436 {
437 ulColumnSize = 4;
438 fNullable = TRUE;
439 }
440
441 hr = EseEnsureColumn(jsSession, pdsSchema->ptsTables[dwTable].jtTable, pcsColumn->pszName, pcsColumn->jcColumnType, ulColumnSize, pcsColumn->fFixed, fNullable, &pcsColumn->jcColumn);
442 EseExitOnFailure(hr, "Failed to create column %u of %ls table", dwColumn, pwzTableName);
443 }
444 }
445 }
446
447LExit:
448 ReleaseStr(jtTableCreate.szTableName);
449
450 if (NULL != jtTableCreate.rgcolumncreate)
451 {
452 // Don't record the HRESULT here or it will override the return value of this function
453 FreeColumnCreateStruct(jtTableCreate.rgcolumncreate, jtTableCreate.cColumns);
454 }
455
456 if (fTransaction)
457 {
458 EseCommitTransaction(jsSession);
459 }
460
461 return hr;
462}
463
464// Todo: support overwrite flag? Unfortunately, requires WinXP and up
465// Todo: Add version parameter, and a built-in dutil table that stores the version of the database schema on disk - then allow overriding the "migrate to new schema" functionality with a callback
466HRESULT DAPI EseEnsureDatabase(
467 __in JET_SESID jsSession,
468 __in_z LPCWSTR pszFile,
469 __in ESE_DATABASE_SCHEMA *pdsSchema,
470 __out JET_DBID* pjdbDb,
471 __in BOOL fExclusive,
472 __in BOOL fReadonly
473 )
474{
475 HRESULT hr = S_OK;
476 JET_ERR jEr = JET_errSuccess;
477 JET_GRBIT jgrOptions = 0;
478 LPWSTR pszDir = NULL;
479 LPSTR pszAnsiFile = NULL;
480
481 // Sigh. JETblue requires Vista and up for the wide character version of this function, so we'll convert to ANSI before calling,
482 // likely breaking all those with unicode characters in their path.
483 hr = StrAnsiAllocString(&pszAnsiFile, pszFile, 0, CP_ACP);
484 EseExitOnFailure(hr, "Failed converting database name to ansi");
485
486 hr = PathGetDirectory(pszFile, &pszDir);
487 EseExitOnFailure(hr, "Failed to get directory that will contain database file");
488
489 hr = DirEnsureExists(pszDir, NULL);
490 EseExitOnFailure(hr, "Failed to ensure directory exists for database: %ls", pszDir);
491
492 if (FileExistsEx(pszFile, NULL))
493 {
494 if (fReadonly)
495 {
496 jgrOptions = jgrOptions | JET_bitDbReadOnly;
497 }
498
499 jEr = JetAttachDatabaseA(jsSession, pszAnsiFile, jgrOptions);
500 ExitOnJetFailure(jEr, hr, "Failed to attach to database %s", pszAnsiFile);
501
502 // This flag doesn't apply to attach, only applies to Open, so only set it after the attach
503 if (fExclusive)
504 {
505 jgrOptions = jgrOptions | JET_bitDbExclusive;
506 }
507
508 jEr = JetOpenDatabaseA(jsSession, pszAnsiFile, NULL, pjdbDb, jgrOptions);
509 ExitOnJetFailure(jEr, hr, "Failed to open database %s", pszAnsiFile);
510 }
511 else
512 {
513 jEr = JetCreateDatabase2A(jsSession, pszAnsiFile, 0, pjdbDb, 0);
514 ExitOnJetFailure(jEr, hr, "Failed to create database %ls", pszFile);
515 }
516
517 hr = EnsureSchema(*pjdbDb, jsSession, pdsSchema);
518 EseExitOnFailure(hr, "Failed to ensure database schema matches expectations");
519
520LExit:
521 ReleaseStr(pszDir);
522 ReleaseStr(pszAnsiFile);
523
524 return hr;
525}
526
527HRESULT DAPI EseCloseDatabase(
528 __in JET_SESID jsSession,
529 __in JET_DBID jdbDb
530 )
531{
532 HRESULT hr = S_OK;
533 JET_ERR jEr = JET_errSuccess;
534 JET_GRBIT jgrOptions = 0;
535
536 jEr = JetCloseDatabase(jsSession, jdbDb, jgrOptions);
537 ExitOnJetFailure(jEr, hr, "Failed to close database");
538
539LExit:
540 return hr;
541}
542
543HRESULT DAPI EseCreateTable(
544 __in JET_SESID jsSession,
545 __in JET_DBID jdbDb,
546 __in_z LPCWSTR pszTable,
547 __out JET_TABLEID *pjtTable
548 )
549{
550 HRESULT hr = S_OK;
551 JET_ERR jEr = JET_errSuccess;
552 LPSTR pszAnsiTable = NULL;
553
554 hr = StrAnsiAllocString(&pszAnsiTable, pszTable, 0, CP_ACP);
555 EseExitOnFailure(hr, "Failed converting table name to ansi");
556
557 jEr = JetCreateTableA(jsSession, jdbDb, pszAnsiTable, 100, 0, pjtTable);
558 ExitOnJetFailure(jEr, hr, "Failed to create table %s", pszAnsiTable);
559
560LExit:
561 ReleaseStr(pszAnsiTable);
562
563 return hr;
564}
565
566HRESULT DAPI EseOpenTable(
567 __in JET_SESID jsSession,
568 __in JET_DBID jdbDb,
569 __in_z LPCWSTR pszTable,
570 __out JET_TABLEID *pjtTable
571 )
572{
573 HRESULT hr = S_OK;
574 JET_ERR jEr = JET_errSuccess;
575 LPSTR pszAnsiTable = NULL;
576
577 hr = StrAnsiAllocString(&pszAnsiTable, pszTable, 0, CP_ACP);
578 EseExitOnFailure(hr, "Failed converting table name to ansi");
579
580 jEr = JetOpenTableA(jsSession, jdbDb, pszAnsiTable, NULL, 0, 0, pjtTable);
581 ExitOnJetFailure(jEr, hr, "Failed to open table %s", pszAnsiTable);
582
583LExit:
584 ReleaseStr(pszAnsiTable);
585
586 return hr;
587}
588
589HRESULT DAPI EseCloseTable(
590 __in JET_SESID jsSession,
591 __in JET_TABLEID jtTable
592 )
593{
594 HRESULT hr = S_OK;
595 JET_ERR jEr = JET_errSuccess;
596
597 jEr = JetCloseTable(jsSession, jtTable);
598 ExitOnJetFailure(jEr, hr, "Failed to close table");
599
600LExit:
601 return hr;
602}
603
604HRESULT DAPI EseEnsureColumn(
605 __in JET_SESID jsSession,
606 __in JET_TABLEID jtTable,
607 __in_z LPCWSTR pszColumnName,
608 __in JET_COLTYP jcColumnType,
609 __in ULONG ulColumnSize,
610 __in BOOL fFixed,
611 __in BOOL fNullable,
612 __out_opt JET_COLUMNID *pjcColumn
613 )
614{
615 HRESULT hr = S_OK;
616 JET_ERR jEr = JET_errSuccess;
617 LPSTR pszAnsiColumnName = NULL;
618 JET_COLUMNDEF jcdColumnDef = { sizeof(JET_COLUMNDEF) };
619 JET_COLUMNBASE jcdTempBase = { sizeof(JET_COLUMNBASE) };
620
621 hr = StrAnsiAllocString(&pszAnsiColumnName, pszColumnName, 0, CP_ACP);
622 EseExitOnFailure(hr, "Failed converting column name to ansi");
623
624 jEr = JetGetTableColumnInfoA(jsSession, jtTable, pszAnsiColumnName, &jcdTempBase, sizeof(JET_COLUMNBASE), JET_ColInfoBase);
625 if (JET_errSuccess == jEr)
626 {
627 // Return the found columnID
628 if (NULL != pjcColumn)
629 {
630 *pjcColumn = jcdTempBase.columnid;
631 }
632
633 ExitFunction1(hr = S_OK);
634 }
635 else if (JET_errColumnNotFound == jEr)
636 {
637 jEr = JET_errSuccess;
638 }
639 ExitOnJetFailure(jEr, hr, "Failed to check if column exists: %s", pszAnsiColumnName);
640
641 jcdColumnDef.columnid = 0;
642 jcdColumnDef.coltyp = jcColumnType;
643 jcdColumnDef.wCountry = 0;
644 jcdColumnDef.langid = 0;
645 jcdColumnDef.cp = 1200;
646 jcdColumnDef.wCollate = 0;
647 jcdColumnDef.cbMax = ulColumnSize;
648 jcdColumnDef.grbit = 0;
649
650 if (fFixed)
651 {
652 jcdColumnDef.grbit = jcdColumnDef.grbit | JET_bitColumnFixed;
653 }
654 if (!fNullable)
655 {
656 jcdColumnDef.grbit = jcdColumnDef.grbit | JET_bitColumnNotNULL;
657 }
658
659 jEr = JetAddColumnA(jsSession, jtTable, pszAnsiColumnName, &jcdColumnDef, NULL, 0, pjcColumn);
660 ExitOnJetFailure(jEr, hr, "Failed to add column %ls", pszColumnName);
661
662LExit:
663 ReleaseStr(pszAnsiColumnName);
664
665 return hr;
666}
667
668HRESULT DAPI EseGetColumn(
669 __in JET_SESID jsSession,
670 __in JET_TABLEID jtTable,
671 __in_z LPCWSTR pszColumnName,
672 __out JET_COLUMNID *pjcColumn
673 )
674{
675 HRESULT hr = S_OK;
676 JET_ERR jEr = JET_errSuccess;
677 LPSTR pszAnsiColumnName = NULL;
678 JET_COLUMNBASE jcdTempBase = { sizeof(JET_COLUMNBASE) };
679
680 hr = StrAnsiAllocString(&pszAnsiColumnName, pszColumnName, 0, CP_ACP);
681 EseExitOnFailure(hr, "Failed converting column name to ansi");
682
683 jEr = JetGetTableColumnInfoA(jsSession, jtTable, pszAnsiColumnName, &jcdTempBase, sizeof(JET_COLUMNBASE), JET_ColInfoBase);
684 if (JET_errSuccess == jEr)
685 {
686 // Return the found columnID
687 if (NULL != pjcColumn)
688 {
689 *pjcColumn = jcdTempBase.columnid;
690 }
691
692 ExitFunction1(hr = S_OK);
693 }
694 ExitOnJetFailure(jEr, hr, "Failed to check if column exists: %s", pszAnsiColumnName);
695
696LExit:
697 ReleaseStr(pszAnsiColumnName);
698
699 return hr;
700}
701
702HRESULT DAPI EseMoveCursor(
703 __in JET_SESID jsSession,
704 __in JET_TABLEID jtTable,
705 __in LONG lRow
706 )
707{
708 HRESULT hr = S_OK;
709 JET_ERR jEr = JET_errSuccess;
710
711 jEr = JetMove(jsSession, jtTable, lRow, 0);
712 ExitOnJetFailure(jEr, hr, "Failed to move jet cursor by amount: %d", lRow);
713
714LExit:
715 return hr;
716}
717
718HRESULT DAPI EseDeleteRow(
719 __in JET_SESID jsSession,
720 __in JET_TABLEID jtTable
721 )
722{
723 HRESULT hr = S_OK;
724 JET_ERR jEr = JET_errSuccess;
725
726 jEr = JetDelete(jsSession, jtTable);
727 ExitOnJetFailure(jEr, hr, "Failed to delete row");
728
729LExit:
730 return hr;
731}
732
733HRESULT DAPI EseBeginTransaction(
734 __in JET_SESID jsSession
735 )
736{
737 HRESULT hr = S_OK;
738 JET_ERR jEr = JET_errSuccess;
739
740 jEr = JetBeginTransaction(jsSession);
741 ExitOnJetFailure(jEr, hr, "Failed to begin transaction");
742
743LExit:
744 return hr;
745}
746
747HRESULT DAPI EseRollbackTransaction(
748 __in JET_SESID jsSession,
749 __in BOOL fAll
750 )
751{
752 HRESULT hr = S_OK;
753 JET_ERR jEr = JET_errSuccess;
754
755 jEr = JetRollback(jsSession, fAll ? JET_bitRollbackAll : 0);
756 ExitOnJetFailure(jEr, hr, "Failed to rollback transaction");
757
758LExit:
759 return hr;
760}
761
762HRESULT DAPI EseCommitTransaction(
763 __in JET_SESID jsSession
764 )
765{
766 HRESULT hr = S_OK;
767 JET_ERR jEr = JET_errSuccess;
768
769 jEr = JetCommitTransaction(jsSession, 0);
770 ExitOnJetFailure(jEr, hr, "Failed to commit transaction");
771
772LExit:
773 return hr;
774}
775
776HRESULT DAPI EsePrepareUpdate(
777 __in JET_SESID jsSession,
778 __in JET_TABLEID jtTable,
779 __in ULONG ulPrep
780 )
781{
782 HRESULT hr = S_OK;
783 JET_ERR jEr = JET_errSuccess;
784
785 jEr = JetPrepareUpdate(jsSession, jtTable, ulPrep);
786 ExitOnJetFailure(jEr, hr, "Failed to prepare for update of type: %ul", ulPrep);
787
788LExit:
789 return hr;
790}
791
792HRESULT DAPI EseFinishUpdate(
793 __in JET_SESID jsSession,
794 __in JET_TABLEID jtTable,
795 __in BOOL fSeekToInsertedRecord
796 )
797{
798 HRESULT hr = S_OK;
799 JET_ERR jEr = JET_errSuccess;
800 unsigned char rgbBookmark[JET_cbBookmarkMost + 1];
801 DWORD cbBookmark;
802
803 if (fSeekToInsertedRecord)
804 {
805 jEr = JetUpdate(jsSession, jtTable, rgbBookmark, sizeof(rgbBookmark), &cbBookmark);
806 ExitOnJetFailure(jEr, hr, "Failed to run update and retrieve bookmark");
807
808 jEr = JetGotoBookmark(jsSession, jtTable, rgbBookmark, cbBookmark);
809 ExitOnJetFailure(jEr, hr, "Failed to seek to recently updated record using bookmark");
810 }
811 else
812 {
813 jEr = JetUpdate(jsSession, jtTable, NULL, 0, NULL);
814 ExitOnJetFailure(jEr, hr, "Failed to run update (without retrieving bookmark)");
815 }
816
817LExit:
818 // If we fail, the caller won't expect that the update wasn't finished, so we'll cancel their entire update to leave them in a good state
819 if (FAILED(hr))
820 {
821 JetPrepareUpdate(jsSession, jtTable, JET_prepCancel);
822 }
823
824 return hr;
825}
826
827HRESULT DAPI EseSetColumnBinary(
828 __in JET_SESID jsSession,
829 __in ESE_TABLE_SCHEMA tsTable,
830 __in DWORD dwColumn,
831 __in_bcount(cbBuffer) const BYTE* pbBuffer,
832 __in SIZE_T cbBuffer
833 )
834{
835 HRESULT hr = S_OK;
836 JET_ERR jEr = JET_errSuccess;
837
838 jEr = JetSetColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, pbBuffer, static_cast<unsigned long>(cbBuffer), 0, NULL);
839 ExitOnJetFailure(jEr, hr, "Failed to set binary value into column of database");
840
841LExit:
842 return hr;
843}
844
845HRESULT DAPI EseSetColumnDword(
846 __in JET_SESID jsSession,
847 __in ESE_TABLE_SCHEMA tsTable,
848 __in DWORD dwColumn,
849 __in DWORD dwValue
850 )
851{
852 HRESULT hr = S_OK;
853 JET_ERR jEr = JET_errSuccess;
854
855 jEr = JetSetColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, &dwValue, sizeof(DWORD), 0, NULL);
856 ExitOnJetFailure(jEr, hr, "Failed to set dword value into column of database: %u", dwValue);
857
858LExit:
859 return hr;
860}
861
862HRESULT DAPI EseSetColumnBool(
863 __in JET_SESID jsSession,
864 __in ESE_TABLE_SCHEMA tsTable,
865 __in DWORD dwColumn,
866 __in BOOL fValue
867 )
868{
869 HRESULT hr = S_OK;
870 JET_ERR jEr = JET_errSuccess;
871 BYTE bValue = fValue ? 0xFF : 0x00;
872
873 jEr = JetSetColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, &bValue, 1, 0, NULL);
874 ExitOnJetFailure(jEr, hr, "Failed to set bool value into column of database");
875
876LExit:
877 return hr;
878}
879
880HRESULT DAPI EseSetColumnString(
881 __in JET_SESID jsSession,
882 __in ESE_TABLE_SCHEMA tsTable,
883 __in DWORD dwColumn,
884 __in_z LPCWSTR pwzValue
885 )
886{
887 HRESULT hr = S_OK;
888 JET_ERR jEr = JET_errSuccess;
889 size_t cchValue = 0;
890 ULONG cbValueSize = 0;
891
892 if (pwzValue)
893 {
894 hr = ::StringCchLengthW(pwzValue, STRSAFE_MAX_LENGTH, &cchValue);
895 EseExitOnRootFailure(hr, "Failed to get string length: %ls", pwzValue);
896 }
897
898 cbValueSize = static_cast<ULONG>((cchValue + 1) * sizeof(WCHAR)); // add 1 for null character, then multiply by size of WCHAR to get bytes
899
900 jEr = JetSetColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, pwzValue, cbValueSize, 0, NULL);
901 ExitOnJetFailure(jEr, hr, "Failed to set string value into column of database: %ls", pwzValue);
902
903LExit:
904 return hr;
905}
906
907HRESULT DAPI EseSetColumnEmpty(
908 __in JET_SESID jsSession,
909 __in ESE_TABLE_SCHEMA tsTable,
910 __in DWORD dwColumn
911 )
912{
913 HRESULT hr = S_OK;
914 JET_ERR jEr = JET_errSuccess;
915
916 jEr = JetSetColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, NULL, 0, 0, NULL);
917 ExitOnJetFailure(jEr, hr, "Failed to set empty value into column of database");
918
919LExit:
920 return hr;
921}
922
923HRESULT DAPI EseGetColumnBinary(
924 __in JET_SESID jsSession,
925 __in ESE_TABLE_SCHEMA tsTable,
926 __in DWORD dwColumn,
927 __deref_inout_bcount(*piBuffer) BYTE** ppbBuffer,
928 __inout SIZE_T* piBuffer
929 )
930{
931 HRESULT hr = S_OK;
932 JET_ERR jEr = JET_errSuccess;
933 ULONG ulActualSize = 0;
934
935 jEr = JetRetrieveColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, NULL, 0, &ulActualSize, 0, NULL);
936 if (JET_wrnBufferTruncated == jEr)
937 {
938 jEr = JET_errSuccess;
939 }
940 ExitOnJetFailure(jEr, hr, "Failed to check size of binary value from record");
941
942 if (NULL == *ppbBuffer)
943 {
944 *ppbBuffer = reinterpret_cast<BYTE *>(MemAlloc(ulActualSize, FALSE));
945 EseExitOnNull(*ppbBuffer, hr, E_OUTOFMEMORY, "Failed to allocate memory for reading binary value column");
946 }
947 else
948 {
949 *ppbBuffer = reinterpret_cast<BYTE *>(MemReAlloc(*ppbBuffer, ulActualSize, FALSE));
950 EseExitOnNull(*ppbBuffer, hr, E_OUTOFMEMORY, "Failed to reallocate memory for reading binary value column");
951 }
952
953 jEr = JetRetrieveColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, *ppbBuffer, ulActualSize, NULL, 0, NULL);
954 ExitOnJetFailure(jEr, hr, "Failed to retrieve binary value from record");
955
956 *piBuffer = static_cast<SIZE_T>(ulActualSize);
957
958LExit:
959 if (FAILED(hr))
960 {
961 ReleaseNullMem(*ppbBuffer);
962 }
963
964 return hr;
965}
966
967HRESULT DAPI EseGetColumnDword(
968 __in JET_SESID jsSession,
969 __in ESE_TABLE_SCHEMA tsTable,
970 __in DWORD dwColumn,
971 __out DWORD *pdwValue
972 )
973{
974 HRESULT hr = S_OK;
975 JET_ERR jEr = JET_errSuccess;
976
977 jEr = JetRetrieveColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, pdwValue, sizeof(DWORD), NULL, 0, NULL);
978 ExitOnJetFailure(jEr, hr, "Failed to retrieve dword value from record");
979
980LExit:
981 return hr;
982}
983
984HRESULT DAPI EseGetColumnBool(
985 __in JET_SESID jsSession,
986 __in ESE_TABLE_SCHEMA tsTable,
987 __in DWORD dwColumn,
988 __out BOOL *pfValue
989 )
990{
991 HRESULT hr = S_OK;
992 JET_ERR jEr = JET_errSuccess;
993 BYTE bValue = 0;
994
995 jEr = JetRetrieveColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, &bValue, 1, NULL, 0, NULL);
996 ExitOnJetFailure(jEr, hr, "Failed to retrieve bool value from record");
997
998 if (bValue == 0)
999 {
1000 *pfValue = FALSE;
1001 }
1002 else
1003 {
1004 *pfValue = TRUE;
1005 }
1006
1007LExit:
1008 return hr;
1009}
1010
1011HRESULT DAPI EseGetColumnString(
1012 __in JET_SESID jsSession,
1013 __in ESE_TABLE_SCHEMA tsTable,
1014 __in DWORD dwColumn,
1015 __out LPWSTR *ppszValue
1016 )
1017{
1018 HRESULT hr = S_OK;
1019 JET_ERR jEr = JET_errSuccess;
1020 ULONG ulActualSize = 0;
1021
1022 jEr = JetRetrieveColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, NULL, 0, &ulActualSize, 0, NULL);
1023 if (JET_wrnBufferTruncated == jEr)
1024 {
1025 jEr = JET_errSuccess;
1026 }
1027 ExitOnJetFailure(jEr, hr, "Failed to check size of string value from record");
1028
1029 hr = StrAlloc(ppszValue, ulActualSize);
1030 EseExitOnFailure(hr, "Failed to allocate string while retrieving column value");
1031
1032 jEr = JetRetrieveColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, *ppszValue, ulActualSize, NULL, 0, NULL);
1033 ExitOnJetFailure(jEr, hr, "Failed to retrieve string value from record");
1034
1035LExit:
1036 return hr;
1037}
1038
1039HRESULT DAPI EseBeginQuery(
1040 __in JET_SESID jsSession,
1041 __in JET_TABLEID jtTable,
1042 __in ESE_QUERY_TYPE qtQueryType,
1043 __out ESE_QUERY_HANDLE *peqhHandle
1044 )
1045{
1046 UNREFERENCED_PARAMETER(jsSession);
1047 UNREFERENCED_PARAMETER(jtTable);
1048
1049 HRESULT hr = S_OK;
1050
1051 *peqhHandle = static_cast<ESE_QUERY*>(MemAlloc(sizeof(ESE_QUERY), TRUE));
1052 EseExitOnNull(*peqhHandle, hr, E_OUTOFMEMORY, "Failed to allocate new query");
1053
1054 ESE_QUERY *peqHandle = static_cast<ESE_QUERY *>(*peqhHandle);
1055 peqHandle->qtQueryType = qtQueryType;
1056 peqHandle->jsSession = jsSession;
1057 peqHandle->jtTable = jtTable;
1058
1059LExit:
1060 return hr;
1061}
1062
1063// Utility function used by other functions to set a query column
1064HRESULT DAPI SetQueryColumn(
1065 __in ESE_QUERY_HANDLE eqhHandle,
1066 __in_bcount(cbData) const void *pvData,
1067 __in DWORD cbData,
1068 __in JET_GRBIT jGrb
1069 )
1070{
1071 HRESULT hr = S_OK;
1072 JET_ERR jEr = JET_errSuccess;
1073
1074 ESE_QUERY *peqHandle = static_cast<ESE_QUERY *>(eqhHandle);
1075
1076 if (peqHandle->dwColumns == countof(peqHandle->pvData))
1077 {
1078 hr = E_NOTIMPL;
1079 EseExitOnFailure(hr, "Dutil hasn't implemented support for queries of more than %d columns", countof(peqHandle->pvData));
1080 }
1081
1082 if (0 == peqHandle->dwColumns) // If it's the first column, start a new key
1083 {
1084 jGrb = jGrb | JET_bitNewKey;
1085 }
1086
1087 jEr = JetMakeKey(peqHandle->jsSession, peqHandle->jtTable, pvData, cbData, jGrb);
1088 ExitOnJetFailure(jEr, hr, "Failed to begin new query");
1089
1090 // If the query is wildcard, setup the cached copy of pvData
1091 if (ESE_QUERY_EXACT != peqHandle->qtQueryType)
1092 {
1093 peqHandle->pvData[peqHandle->dwColumns] = MemAlloc(cbData, FALSE);
1094 EseExitOnNull(peqHandle->pvData[peqHandle->dwColumns], hr, E_OUTOFMEMORY, "Failed to allocate memory");
1095
1096 memcpy(peqHandle->pvData[peqHandle->dwColumns], pvData, cbData);
1097
1098 peqHandle->cbData[peqHandle->dwColumns] = cbData;
1099 }
1100
1101 // Increment the number of total columns
1102 ++peqHandle->dwColumns;
1103
1104LExit:
1105 return hr;
1106}
1107
1108HRESULT DAPI EseSetQueryColumnBinary(
1109 __in ESE_QUERY_HANDLE eqhHandle,
1110 __in_bcount(cbBuffer) const BYTE* pbBuffer,
1111 __in SIZE_T cbBuffer,
1112 __in BOOL fFinal // If this is true, all other key columns in the query will be set to "*"
1113 )
1114{
1115 HRESULT hr = S_OK;
1116 ESE_QUERY *peqHandle = static_cast<ESE_QUERY *>(eqhHandle);
1117 JET_GRBIT jGrb = 0;
1118
1119 if (cbBuffer > DWORD_MAX)
1120 {
1121 ExitFunction1(hr = E_INVALIDARG);
1122 }
1123
1124 if (fFinal)
1125 {
1126 if (ESE_QUERY_FROM_TOP == peqHandle->qtQueryType)
1127 {
1128 jGrb = jGrb | JET_bitFullColumnStartLimit;
1129 }
1130 else if (ESE_QUERY_FROM_BOTTOM == peqHandle->qtQueryType)
1131 {
1132 jGrb = jGrb | JET_bitFullColumnEndLimit;
1133 }
1134 }
1135
1136 hr = SetQueryColumn(eqhHandle, reinterpret_cast<const void *>(pbBuffer), static_cast<DWORD>(cbBuffer), jGrb);
1137 EseExitOnFailure(hr, "Failed to set value of query colum (as binary) to:");
1138
1139LExit:
1140 return hr;
1141}
1142
1143HRESULT DAPI EseSetQueryColumnDword(
1144 __in ESE_QUERY_HANDLE eqhHandle,
1145 __in DWORD dwData,
1146 __in BOOL fFinal
1147 )
1148{
1149 HRESULT hr = S_OK;
1150 ESE_QUERY *peqHandle = static_cast<ESE_QUERY *>(eqhHandle);
1151 JET_GRBIT jGrb = 0;
1152
1153 if (fFinal)
1154 {
1155 if (ESE_QUERY_FROM_TOP == peqHandle->qtQueryType)
1156 {
1157 jGrb = jGrb | JET_bitFullColumnStartLimit;
1158 }
1159 else if (ESE_QUERY_FROM_BOTTOM == peqHandle->qtQueryType)
1160 {
1161 jGrb = jGrb | JET_bitFullColumnEndLimit;
1162 }
1163 }
1164
1165 hr = SetQueryColumn(eqhHandle, (const void *)&dwData, sizeof(DWORD), jGrb);
1166 EseExitOnFailure(hr, "Failed to set value of query colum (as dword) to: %u", dwData);
1167
1168LExit:
1169 return hr;
1170}
1171
1172HRESULT DAPI EseSetQueryColumnBool(
1173 __in ESE_QUERY_HANDLE eqhHandle,
1174 __in BOOL fValue,
1175 __in BOOL fFinal
1176 )
1177{
1178 HRESULT hr = S_OK;
1179 BYTE bByte = fValue ? 0xFF : 0x00;
1180 ESE_QUERY *peqHandle = static_cast<ESE_QUERY *>(eqhHandle);
1181 JET_GRBIT jGrb = 0;
1182
1183 if (fFinal)
1184 {
1185 if (ESE_QUERY_FROM_TOP == peqHandle->qtQueryType)
1186 {
1187 jGrb = jGrb | JET_bitFullColumnStartLimit;
1188 }
1189 else if (ESE_QUERY_FROM_BOTTOM == peqHandle->qtQueryType)
1190 {
1191 jGrb = jGrb | JET_bitFullColumnEndLimit;
1192 }
1193 }
1194
1195 hr = SetQueryColumn(eqhHandle, (const void *)&bByte, 1, jGrb);
1196 EseExitOnFailure(hr, "Failed to set value of query colum (as bool) to: %s", fValue ? "TRUE" : "FALSE");
1197
1198LExit:
1199 return hr;
1200}
1201
1202HRESULT DAPI EseSetQueryColumnString(
1203 __in ESE_QUERY_HANDLE eqhHandle,
1204 __in_z LPCWSTR pszString,
1205 __in BOOL fFinal
1206 )
1207{
1208 HRESULT hr = S_OK;
1209 DWORD dwStringSize = 0;
1210 size_t cchString = 0;
1211 ESE_QUERY *peqHandle = static_cast<ESE_QUERY *>(eqhHandle);
1212 JET_GRBIT jGrb = 0;
1213
1214 if (pszString)
1215 {
1216 hr = ::StringCchLengthW(pszString, STRSAFE_MAX_LENGTH, &cchString);
1217 EseExitOnRootFailure(hr, "Failed to get size of column string");
1218 }
1219
1220 dwStringSize = static_cast<DWORD>(sizeof(WCHAR) * (cchString + 1)); // Add 1 for null terminator
1221
1222 if (fFinal)
1223 {
1224 if (ESE_QUERY_FROM_TOP == peqHandle->qtQueryType)
1225 {
1226 jGrb = jGrb | JET_bitFullColumnStartLimit;
1227 }
1228 else if (ESE_QUERY_FROM_BOTTOM == peqHandle->qtQueryType)
1229 {
1230 jGrb = jGrb | JET_bitFullColumnEndLimit;
1231 }
1232 }
1233
1234 hr = SetQueryColumn(eqhHandle, (const void *)pszString, dwStringSize, jGrb);
1235 EseExitOnFailure(hr, "Failed to set value of query colum (as string) to: %ls", pszString);
1236
1237LExit:
1238 return hr;
1239}
1240
1241HRESULT DAPI EseFinishQuery(
1242 __in ESE_QUERY_HANDLE eqhHandle
1243 )
1244{
1245 HRESULT hr = S_OK;
1246 JET_ERR jEr = JET_errSuccess;
1247
1248 ESE_QUERY *peqHandle = static_cast<ESE_QUERY *>(eqhHandle);
1249
1250 if (peqHandle->fIndexRangeSet)
1251 {
1252 jEr = JetSetIndexRange(peqHandle->jsSession, peqHandle->jtTable, JET_bitRangeRemove);
1253 ExitOnJetFailure(jEr, hr, "Failed to release index range");
1254
1255 peqHandle->fIndexRangeSet = FALSE;
1256 }
1257
1258 for (int i=0; i < countof(peqHandle->pvData); ++i)
1259 {
1260 ReleaseMem(peqHandle->pvData[i]);
1261 }
1262
1263 ReleaseMem(peqHandle);
1264
1265LExit:
1266 return hr;
1267}
1268
1269HRESULT DAPI EseRunQuery(
1270 __in ESE_QUERY_HANDLE eqhHandle
1271 )
1272{
1273 HRESULT hr = S_OK;
1274 JET_ERR jEr = JET_errSuccess;
1275 JET_GRBIT jGrb = 0;
1276 JET_GRBIT jGrbSeekType = 0;
1277 DWORD i;
1278
1279 ESE_QUERY *peqHandle = static_cast<ESE_QUERY *>(eqhHandle);
1280
1281 if (ESE_QUERY_EXACT == peqHandle->qtQueryType)
1282 {
1283 jEr = JetSeek(peqHandle->jsSession, peqHandle->jtTable, JET_bitSeekEQ);
1284 ExitOnJetFailure(jEr, hr, "Failed to seek EQ within jet table");
1285 }
1286 else
1287 {
1288 if (ESE_QUERY_FROM_TOP == peqHandle->qtQueryType)
1289 {
1290 jGrbSeekType = JET_bitSeekGE;
1291 }
1292 else if (ESE_QUERY_FROM_BOTTOM == peqHandle->qtQueryType)
1293 {
1294 jGrbSeekType = JET_bitSeekLE;
1295 }
1296
1297 jEr = JetSeek(peqHandle->jsSession, peqHandle->jtTable, jGrbSeekType);
1298 if (jEr == JET_wrnSeekNotEqual)
1299 {
1300 jEr = JET_errSuccess;
1301 }
1302
1303 // At this point we've already set our cursor to the beginning of the range of records to select.
1304 // Now we'll make a key pointing to the end of the range of records to select, so we can call JetSetIndexRange()
1305 // For a semi-explanation, see this doc page: http://msdn.microsoft.com/en-us/library/aa964799%28EXCHG.10%29.aspx
1306 for (i = 0; i < peqHandle->dwColumns; ++i)
1307 {
1308 if (i == 0)
1309 {
1310 jGrb = JET_bitNewKey;
1311 }
1312 else
1313 {
1314 jGrb = 0;
1315 }
1316
1317 // On the last iteration
1318 if (i == peqHandle->dwColumns - 1)
1319 {
1320 jGrb |= JET_bitFullColumnEndLimit;
1321 }
1322
1323 jEr = JetMakeKey(peqHandle->jsSession, peqHandle->jtTable, peqHandle->pvData[i], peqHandle->cbData[i], jGrb);
1324 ExitOnJetFailure(jEr, hr, "Failed to begin new query");
1325 }
1326
1327 jEr = JetSetIndexRange(peqHandle->jsSession, peqHandle->jtTable, JET_bitRangeUpperLimit);
1328 ExitOnJetFailure(jEr, hr, "Failed to set index range");
1329
1330 peqHandle->fIndexRangeSet = TRUE;
1331
1332 // Sometimes JetBlue doesn't check if there is a current record when calling the above function (and sometimes it does)
1333 // So, let's check if there is a current record before returning (by reading the first byte of one).
1334 jEr = JetMove(peqHandle->jsSession, peqHandle->jtTable, 0, 0);
1335 ExitOnJetFailure(jEr, hr, "Failed to check if there is a current record after query");
1336 }
1337
1338LExit:
1339 return hr;
1340}
diff --git a/src/libs/dutil/WixToolset.DUtil/fileutil.cpp b/src/libs/dutil/WixToolset.DUtil/fileutil.cpp
new file mode 100644
index 00000000..1822727a
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/fileutil.cpp
@@ -0,0 +1,2032 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define FileExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_FILEUTIL, x, s, __VA_ARGS__)
8#define FileExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_FILEUTIL, x, s, __VA_ARGS__)
9#define FileExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_FILEUTIL, x, s, __VA_ARGS__)
10#define FileExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_FILEUTIL, x, s, __VA_ARGS__)
11#define FileExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_FILEUTIL, x, s, __VA_ARGS__)
12#define FileExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_FILEUTIL, x, s, __VA_ARGS__)
13#define FileExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_FILEUTIL, p, x, e, s, __VA_ARGS__)
14#define FileExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_FILEUTIL, p, x, s, __VA_ARGS__)
15#define FileExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_FILEUTIL, p, x, e, s, __VA_ARGS__)
16#define FileExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_FILEUTIL, p, x, s, __VA_ARGS__)
17#define FileExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_FILEUTIL, e, x, s, __VA_ARGS__)
18#define FileExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_FILEUTIL, g, x, s, __VA_ARGS__)
19
20// constants
21
22const BYTE UTF8BOM[] = {0xEF, 0xBB, 0xBF};
23const BYTE UTF16BOM[] = {0xFF, 0xFE};
24
25const LPCWSTR REGISTRY_PENDING_FILE_RENAME_KEY = L"SYSTEM\\CurrentControlSet\\Control\\Session Manager";
26const LPCWSTR REGISTRY_PENDING_FILE_RENAME_VALUE = L"PendingFileRenameOperations";
27
28/*******************************************************************
29 FileFromPath - returns a pointer to the file part of the path
30
31********************************************************************/
32extern "C" LPWSTR DAPI FileFromPath(
33 __in_z LPCWSTR wzPath
34 )
35{
36 if (!wzPath)
37 return NULL;
38
39 LPWSTR wzFile = const_cast<LPWSTR>(wzPath);
40 for (LPWSTR wz = wzFile; *wz; ++wz)
41 {
42 // valid delineators
43 // \ => Windows path
44 // / => unix and URL path
45 // : => relative path from mapped root
46 if (L'\\' == *wz || L'/' == *wz || L':' == *wz)
47 wzFile = wz + 1;
48 }
49
50 return wzFile;
51}
52
53
54/*******************************************************************
55 FileResolvePath - gets the full path to a file resolving environment
56 variables along the way.
57
58********************************************************************/
59extern "C" HRESULT DAPI FileResolvePath(
60 __in_z LPCWSTR wzRelativePath,
61 __out LPWSTR *ppwzFullPath
62 )
63{
64 Assert(wzRelativePath && *wzRelativePath);
65
66 HRESULT hr = S_OK;
67 DWORD cch = 0;
68 LPWSTR pwzExpandedPath = NULL;
69 DWORD cchExpandedPath = 0;
70
71 LPWSTR pwzFullPath = NULL;
72 DWORD cchFullPath = 0;
73
74 LPWSTR wzFileName = NULL;
75
76 //
77 // First, expand any environment variables.
78 //
79 cchExpandedPath = MAX_PATH;
80 hr = StrAlloc(&pwzExpandedPath, cchExpandedPath);
81 FileExitOnFailure(hr, "Failed to allocate space for expanded path.");
82
83 cch = ::ExpandEnvironmentStringsW(wzRelativePath, pwzExpandedPath, cchExpandedPath);
84 if (0 == cch)
85 {
86 FileExitWithLastError(hr, "Failed to expand environment variables in string: %ls", wzRelativePath);
87 }
88 else if (cchExpandedPath < cch)
89 {
90 cchExpandedPath = cch;
91 hr = StrAlloc(&pwzExpandedPath, cchExpandedPath);
92 FileExitOnFailure(hr, "Failed to re-allocate more space for expanded path.");
93
94 cch = ::ExpandEnvironmentStringsW(wzRelativePath, pwzExpandedPath, cchExpandedPath);
95 if (0 == cch)
96 {
97 FileExitWithLastError(hr, "Failed to expand environment variables in string: %ls", wzRelativePath);
98 }
99 else if (cchExpandedPath < cch)
100 {
101 hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
102 FileExitOnRootFailure(hr, "Failed to allocate buffer for expanded path.");
103 }
104 }
105
106 //
107 // Second, get the full path.
108 //
109 cchFullPath = MAX_PATH;
110 hr = StrAlloc(&pwzFullPath, cchFullPath);
111 FileExitOnFailure(hr, "Failed to allocate space for full path.");
112
113 cch = ::GetFullPathNameW(pwzExpandedPath, cchFullPath, pwzFullPath, &wzFileName);
114 if (0 == cch)
115 {
116 FileExitWithLastError(hr, "Failed to get full path for string: %ls", pwzExpandedPath);
117 }
118 else if (cchFullPath < cch)
119 {
120 cchFullPath = cch;
121 hr = StrAlloc(&pwzFullPath, cchFullPath);
122 FileExitOnFailure(hr, "Failed to re-allocate more space for full path.");
123
124 cch = ::GetFullPathNameW(pwzExpandedPath, cchFullPath, pwzFullPath, &wzFileName);
125 if (0 == cch)
126 {
127 FileExitWithLastError(hr, "Failed to get full path for string: %ls", pwzExpandedPath);
128 }
129 else if (cchFullPath < cch)
130 {
131 hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
132 FileExitOnRootFailure(hr, "Failed to allocate buffer for full path.");
133 }
134 }
135
136 *ppwzFullPath = pwzFullPath;
137 pwzFullPath = NULL;
138
139LExit:
140 ReleaseStr(pwzFullPath);
141 ReleaseStr(pwzExpandedPath);
142
143 return hr;
144}
145
146
147/*******************************************************************
148FileStripExtension - Strip extension from filename
149********************************************************************/
150extern "C" HRESULT DAPI FileStripExtension(
151__in_z LPCWSTR wzFileName,
152__out LPWSTR *ppwzFileNameNoExtension
153)
154{
155 Assert(wzFileName && *wzFileName);
156
157 HRESULT hr = S_OK;
158 size_t cchFileName = 0;
159 LPWSTR pwzFileNameNoExtension = NULL;
160 size_t cchFileNameNoExtension = 0;
161 errno_t err = 0;
162
163 hr = ::StringCchLengthW(wzFileName, STRSAFE_MAX_LENGTH, &cchFileName);
164 FileExitOnRootFailure(hr, "failed to get length of file name: %ls", wzFileName);
165
166 cchFileNameNoExtension = cchFileName + 1;
167
168 hr = StrAlloc(&pwzFileNameNoExtension, cchFileNameNoExtension);
169 FileExitOnFailure(hr, "failed to allocate space for File Name without extension");
170
171 // _wsplitpath_s can handle drive/path/filename/extension
172 err = _wsplitpath_s(wzFileName, NULL, NULL, NULL, NULL, pwzFileNameNoExtension, cchFileNameNoExtension, NULL, NULL);
173 if (err)
174 {
175 hr = E_INVALIDARG;
176 FileExitOnRootFailure(hr, "failed to parse filename: '%ls', error: %d", wzFileName, err);
177 }
178
179 *ppwzFileNameNoExtension = pwzFileNameNoExtension;
180 pwzFileNameNoExtension = NULL;
181
182LExit:
183 ReleaseStr(pwzFileNameNoExtension);
184
185 return hr;
186}
187
188
189/*******************************************************************
190FileChangeExtension - Changes the extension of a filename
191********************************************************************/
192extern "C" HRESULT DAPI FileChangeExtension(
193 __in_z LPCWSTR wzFileName,
194 __in_z LPCWSTR wzNewExtension,
195 __out LPWSTR *ppwzFileNameNewExtension
196 )
197{
198 Assert(wzFileName && *wzFileName);
199
200 HRESULT hr = S_OK;
201 LPWSTR sczFileName = NULL;
202
203 hr = FileStripExtension(wzFileName, &sczFileName);
204 FileExitOnFailure(hr, "Failed to strip extension from file name: %ls", wzFileName);
205
206 hr = StrAllocConcat(&sczFileName, wzNewExtension, 0);
207 FileExitOnFailure(hr, "Failed to add new extension.");
208
209 *ppwzFileNameNewExtension = sczFileName;
210 sczFileName = NULL;
211
212LExit:
213 ReleaseStr(sczFileName);
214
215 return hr;
216}
217
218
219/*******************************************************************
220FileAddSuffixToBaseName - Adds a suffix the base portion of a file
221name; e.g., file.ext to fileSuffix.ext.
222********************************************************************/
223extern "C" HRESULT DAPI FileAddSuffixToBaseName(
224 __in_z LPCWSTR wzFileName,
225 __in_z LPCWSTR wzSuffix,
226 __out_z LPWSTR* psczNewFileName
227 )
228{
229 Assert(wzFileName && *wzFileName);
230
231 HRESULT hr = S_OK;
232 LPWSTR sczNewFileName = NULL;
233 size_t cchFileName = 0;
234
235 hr = ::StringCchLengthW(wzFileName, STRSAFE_MAX_CCH, &cchFileName);
236 FileExitOnRootFailure(hr, "Failed to get length of file name: %ls", wzFileName);
237
238 LPCWSTR wzExtension = wzFileName + cchFileName;
239 while (wzFileName < wzExtension && L'.' != *wzExtension)
240 {
241 --wzExtension;
242 }
243
244 if (wzFileName < wzExtension)
245 {
246 // found an extension so add the suffix before it
247 hr = StrAllocFormatted(&sczNewFileName, L"%.*ls%ls%ls", static_cast<int>(wzExtension - wzFileName), wzFileName, wzSuffix, wzExtension);
248 }
249 else
250 {
251 // no extension, so add the suffix at the end of the whole name
252 hr = StrAllocString(&sczNewFileName, wzFileName, 0);
253 FileExitOnFailure(hr, "Failed to allocate new file name.");
254
255 hr = StrAllocConcat(&sczNewFileName, wzSuffix, 0);
256 }
257 FileExitOnFailure(hr, "Failed to allocate new file name with suffix.");
258
259 *psczNewFileName = sczNewFileName;
260 sczNewFileName = NULL;
261
262LExit:
263 ReleaseStr(sczNewFileName);
264
265 return hr;
266}
267
268
269/*******************************************************************
270 FileVersion
271
272********************************************************************/
273extern "C" HRESULT DAPI FileVersion(
274 __in_z LPCWSTR wzFilename,
275 __out DWORD *pdwVerMajor,
276 __out DWORD* pdwVerMinor
277 )
278{
279 HRESULT hr = S_OK;
280
281 DWORD dwHandle = 0;
282 UINT cbVerBuffer = 0;
283 LPVOID pVerBuffer = NULL;
284 VS_FIXEDFILEINFO* pvsFileInfo = NULL;
285 UINT cbFileInfo = 0;
286
287 if (0 == (cbVerBuffer = ::GetFileVersionInfoSizeW(wzFilename, &dwHandle)))
288 {
289 FileExitOnLastErrorDebugTrace(hr, "failed to get version info for file: %ls", wzFilename);
290 }
291
292 pVerBuffer = ::GlobalAlloc(GMEM_FIXED, cbVerBuffer);
293 FileExitOnNullDebugTrace(pVerBuffer, hr, E_OUTOFMEMORY, "failed to allocate version info for file: %ls", wzFilename);
294
295 if (!::GetFileVersionInfoW(wzFilename, dwHandle, cbVerBuffer, pVerBuffer))
296 {
297 FileExitOnLastErrorDebugTrace(hr, "failed to get version info for file: %ls", wzFilename);
298 }
299
300 if (!::VerQueryValueW(pVerBuffer, L"\\", (void**)&pvsFileInfo, &cbFileInfo))
301 {
302 FileExitOnLastErrorDebugTrace(hr, "failed to get version value for file: %ls", wzFilename);
303 }
304
305 *pdwVerMajor = pvsFileInfo->dwFileVersionMS;
306 *pdwVerMinor = pvsFileInfo->dwFileVersionLS;
307
308LExit:
309 if (pVerBuffer)
310 {
311 ::GlobalFree(pVerBuffer);
312 }
313 return hr;
314}
315
316
317/*******************************************************************
318 FileVersionFromString
319
320*******************************************************************/
321extern "C" HRESULT DAPI FileVersionFromString(
322 __in_z LPCWSTR wzVersion,
323 __out DWORD* pdwVerMajor,
324 __out DWORD* pdwVerMinor
325 )
326{
327 Assert(pdwVerMajor && pdwVerMinor);
328
329 HRESULT hr = S_OK;
330 LPCWSTR pwz = wzVersion;
331 DWORD dw;
332
333 *pdwVerMajor = 0;
334 *pdwVerMinor = 0;
335
336 if ((L'v' == *pwz) || (L'V' == *pwz))
337 {
338 ++pwz;
339 }
340
341 dw = wcstoul(pwz, (WCHAR**)&pwz, 10);
342 if (pwz && (L'.' == *pwz && dw < 0x10000) || !*pwz)
343 {
344 *pdwVerMajor = dw << 16;
345
346 if (!*pwz)
347 {
348 ExitFunction1(hr = S_OK);
349 }
350 ++pwz;
351 }
352 else
353 {
354 ExitFunction1(hr = S_FALSE);
355 }
356
357 dw = wcstoul(pwz, (WCHAR**)&pwz, 10);
358 if (pwz && (L'.' == *pwz && dw < 0x10000) || !*pwz)
359 {
360 *pdwVerMajor |= dw;
361
362 if (!*pwz)
363 {
364 ExitFunction1(hr = S_OK);
365 }
366 ++pwz;
367 }
368 else
369 {
370 ExitFunction1(hr = S_FALSE);
371 }
372
373 dw = wcstoul(pwz, (WCHAR**)&pwz, 10);
374 if (pwz && (L'.' == *pwz && dw < 0x10000) || !*pwz)
375 {
376 *pdwVerMinor = dw << 16;
377
378 if (!*pwz)
379 {
380 ExitFunction1(hr = S_OK);
381 }
382 ++pwz;
383 }
384 else
385 {
386 ExitFunction1(hr = S_FALSE);
387 }
388
389 dw = wcstoul(pwz, (WCHAR**)&pwz, 10);
390 if (pwz && L'\0' == *pwz && dw < 0x10000)
391 {
392 *pdwVerMinor |= dw;
393 }
394 else
395 {
396 ExitFunction1(hr = S_FALSE);
397 }
398
399LExit:
400 return hr;
401}
402
403
404/*******************************************************************
405 FileVersionFromStringEx
406
407*******************************************************************/
408extern "C" HRESULT DAPI FileVersionFromStringEx(
409 __in_z LPCWSTR wzVersion,
410 __in SIZE_T cchVersion,
411 __out DWORD64* pqwVersion
412 )
413{
414 Assert(wzVersion);
415 Assert(pqwVersion);
416
417 HRESULT hr = S_OK;
418 LPCWSTR wzEnd = NULL;
419 LPCWSTR wzPartBegin = wzVersion;
420 LPCWSTR wzPartEnd = wzVersion;
421 DWORD iPart = 0;
422 USHORT us = 0;
423 DWORD64 qwVersion = 0;
424
425 // get string length if not provided
426 if (0 >= cchVersion)
427 {
428 hr = ::StringCchLengthW(wzVersion, STRSAFE_MAX_CCH, reinterpret_cast<size_t*>(&cchVersion));
429 FileExitOnRootFailure(hr, "Failed to get length of file version string: %ls", wzVersion);
430
431 if (0 >= cchVersion)
432 {
433 ExitFunction1(hr = E_INVALIDARG);
434 }
435 }
436
437 if ((L'v' == *wzVersion) || (L'V' == *wzVersion))
438 {
439 ++wzVersion;
440 --cchVersion;
441 wzPartBegin = wzVersion;
442 wzPartEnd = wzVersion;
443 }
444
445 // save end pointer
446 wzEnd = wzVersion + cchVersion;
447
448 // loop through parts
449 for (;;)
450 {
451 if (4 <= iPart)
452 {
453 // error, too many parts
454 ExitFunction1(hr = E_INVALIDARG);
455 }
456
457 // find end of part
458 while (wzPartEnd < wzEnd && L'.' != *wzPartEnd)
459 {
460 ++wzPartEnd;
461 }
462 if (wzPartBegin == wzPartEnd)
463 {
464 // error, empty part
465 ExitFunction1(hr = E_INVALIDARG);
466 }
467
468 DWORD cchPart;
469 hr = ::PtrdiffTToDWord(wzPartEnd - wzPartBegin, &cchPart);
470 FileExitOnFailure(hr, "Version number part was too long.");
471
472 // parse version part
473 hr = StrStringToUInt16(wzPartBegin, cchPart, &us);
474 FileExitOnFailure(hr, "Failed to parse version number part.");
475
476 // add part to qword version
477 qwVersion |= (DWORD64)us << ((3 - iPart) * 16);
478
479 if (wzPartEnd >= wzEnd)
480 {
481 // end of string
482 break;
483 }
484
485 wzPartBegin = ++wzPartEnd; // skip over separator
486 ++iPart;
487 }
488
489 *pqwVersion = qwVersion;
490
491LExit:
492 return hr;
493}
494
495/*******************************************************************
496 FileVersionFromStringEx - Formats the DWORD64 as a string version.
497
498*******************************************************************/
499extern "C" HRESULT DAPI FileVersionToStringEx(
500 __in DWORD64 qwVersion,
501 __out LPWSTR* psczVersion
502 )
503{
504 HRESULT hr = S_OK;
505 WORD wMajor = 0;
506 WORD wMinor = 0;
507 WORD wBuild = 0;
508 WORD wRevision = 0;
509
510 // Mask and shift each WORD for each field.
511 wMajor = (WORD)(qwVersion >> 48 & 0xffff);
512 wMinor = (WORD)(qwVersion >> 32 & 0xffff);
513 wBuild = (WORD)(qwVersion >> 16 & 0xffff);
514 wRevision = (WORD)(qwVersion & 0xffff);
515
516 // Format and return the version string.
517 hr = StrAllocFormatted(psczVersion, L"%u.%u.%u.%u", wMajor, wMinor, wBuild, wRevision);
518 FileExitOnFailure(hr, "Failed to allocate and format the version number.");
519
520LExit:
521 return hr;
522}
523
524/*******************************************************************
525 FileSetPointer - sets the file pointer.
526
527********************************************************************/
528extern "C" HRESULT DAPI FileSetPointer(
529 __in HANDLE hFile,
530 __in DWORD64 dw64Move,
531 __out_opt DWORD64* pdw64NewPosition,
532 __in DWORD dwMoveMethod
533 )
534{
535 Assert(INVALID_HANDLE_VALUE != hFile);
536
537 HRESULT hr = S_OK;
538 LARGE_INTEGER liMove;
539 LARGE_INTEGER liNewPosition;
540
541 liMove.QuadPart = dw64Move;
542 if (!::SetFilePointerEx(hFile, liMove, &liNewPosition, dwMoveMethod))
543 {
544 FileExitWithLastError(hr, "Failed to set file pointer.");
545 }
546
547 if (pdw64NewPosition)
548 {
549 *pdw64NewPosition = liNewPosition.QuadPart;
550 }
551
552LExit:
553 return hr;
554}
555
556
557/*******************************************************************
558 FileSize
559
560********************************************************************/
561extern "C" HRESULT DAPI FileSize(
562 __in_z LPCWSTR pwzFileName,
563 __out LONGLONG* pllSize
564 )
565{
566 HRESULT hr = S_OK;
567 HANDLE hFile = INVALID_HANDLE_VALUE;
568
569 FileExitOnNull(pwzFileName, hr, E_INVALIDARG, "Attempted to check filename, but no filename was provided");
570
571 hFile = ::CreateFileW(pwzFileName, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
572 if (INVALID_HANDLE_VALUE == hFile)
573 {
574 FileExitWithLastError(hr, "Failed to open file %ls while checking file size", pwzFileName);
575 }
576
577 hr = FileSizeByHandle(hFile, pllSize);
578 FileExitOnFailure(hr, "Failed to check size of file %ls by handle", pwzFileName);
579
580LExit:
581 ReleaseFileHandle(hFile);
582
583 return hr;
584}
585
586
587/*******************************************************************
588 FileSizeByHandle
589
590********************************************************************/
591extern "C" HRESULT DAPI FileSizeByHandle(
592 __in HANDLE hFile,
593 __out LONGLONG* pllSize
594 )
595{
596 Assert(INVALID_HANDLE_VALUE != hFile && pllSize);
597 HRESULT hr = S_OK;
598 LARGE_INTEGER li;
599
600 *pllSize = 0;
601
602 if (!::GetFileSizeEx(hFile, &li))
603 {
604 FileExitWithLastError(hr, "Failed to get size of file.");
605 }
606
607 *pllSize = li.QuadPart;
608
609LExit:
610 return hr;
611}
612
613
614/*******************************************************************
615 FileExistsEx
616
617********************************************************************/
618extern "C" BOOL DAPI FileExistsEx(
619 __in_z LPCWSTR wzPath,
620 __out_opt DWORD *pdwAttributes
621 )
622{
623 Assert(wzPath && *wzPath);
624 BOOL fExists = FALSE;
625
626 WIN32_FIND_DATAW fd = { };
627 HANDLE hff;
628
629 if (INVALID_HANDLE_VALUE != (hff = ::FindFirstFileW(wzPath, &fd)))
630 {
631 ::FindClose(hff);
632 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
633 {
634 if (pdwAttributes)
635 {
636 *pdwAttributes = fd.dwFileAttributes;
637 }
638
639 fExists = TRUE;
640 }
641 }
642
643 return fExists;
644}
645
646
647/*******************************************************************
648 FileExistsAfterRestart - checks that a file exists and will continue
649 to exist after restart.
650
651********************************************************************/
652extern "C" BOOL DAPI FileExistsAfterRestart(
653 __in_z LPCWSTR wzPath,
654 __out_opt DWORD *pdwAttributes
655 )
656{
657 HRESULT hr = S_OK;
658 BOOL fExists = FALSE;
659 HKEY hkPendingFileRename = NULL;
660 LPWSTR* rgsczRenames = NULL;
661 DWORD cRenames = 0;
662 int nCompare = 0;
663
664 fExists = FileExistsEx(wzPath, pdwAttributes);
665 if (fExists)
666 {
667 hr = RegOpen(HKEY_LOCAL_MACHINE, REGISTRY_PENDING_FILE_RENAME_KEY, KEY_QUERY_VALUE, &hkPendingFileRename);
668 if (E_FILENOTFOUND == hr)
669 {
670 ExitFunction1(hr = S_OK);
671 }
672 FileExitOnFailure(hr, "Failed to open pending file rename registry key.");
673
674 hr = RegReadStringArray(hkPendingFileRename, REGISTRY_PENDING_FILE_RENAME_VALUE, &rgsczRenames, &cRenames);
675 if (E_FILENOTFOUND == hr)
676 {
677 ExitFunction1(hr = S_OK);
678 }
679 FileExitOnFailure(hr, "Failed to read pending file renames.");
680
681 // The pending file renames array is pairs of source and target paths. We only care
682 // about checking the source paths so skip the target paths (i += 2).
683 for (DWORD i = 0; i < cRenames; i += 2)
684 {
685 LPWSTR wzRename = rgsczRenames[i];
686 if (wzRename && *wzRename)
687 {
688 // Skip the long path designator if present.
689 if (L'\\' == wzRename[0] && L'?' == wzRename[1] && L'?' == wzRename[2] && L'\\' == wzRename[3])
690 {
691 wzRename += 4;
692 }
693
694 hr = PathCompare(wzPath, wzRename, &nCompare);
695 FileExitOnFailure(hr, "Failed to compare path from pending file rename to check path.");
696
697 if (CSTR_EQUAL == nCompare)
698 {
699 fExists = FALSE;
700 break;
701 }
702 }
703 }
704 }
705
706LExit:
707 ReleaseStrArray(rgsczRenames, cRenames);
708 ReleaseRegKey(hkPendingFileRename);
709
710 return fExists;
711}
712
713
714/*******************************************************************
715 FileRemoveFromPendingRename - removes the file path from the pending
716 file rename list.
717
718********************************************************************/
719extern "C" HRESULT DAPI FileRemoveFromPendingRename(
720 __in_z LPCWSTR wzPath
721 )
722{
723 HRESULT hr = S_OK;
724 HKEY hkPendingFileRename = NULL;
725 LPWSTR* rgsczRenames = NULL;
726 DWORD cRenames = 0;
727 int nCompare = 0;
728 BOOL fRemoved = FALSE;
729 DWORD cNewRenames = 0;
730
731 hr = RegOpen(HKEY_LOCAL_MACHINE, REGISTRY_PENDING_FILE_RENAME_KEY, KEY_QUERY_VALUE | KEY_SET_VALUE, &hkPendingFileRename);
732 if (E_FILENOTFOUND == hr)
733 {
734 ExitFunction1(hr = S_OK);
735 }
736 FileExitOnFailure(hr, "Failed to open pending file rename registry key.");
737
738 hr = RegReadStringArray(hkPendingFileRename, REGISTRY_PENDING_FILE_RENAME_VALUE, &rgsczRenames, &cRenames);
739 if (E_FILENOTFOUND == hr)
740 {
741 ExitFunction1(hr = S_OK);
742 }
743 FileExitOnFailure(hr, "Failed to read pending file renames.");
744
745 // The pending file renames array is pairs of source and target paths. We only care
746 // about checking the source paths so skip the target paths (i += 2).
747 for (DWORD i = 0; i < cRenames; i += 2)
748 {
749 LPWSTR wzRename = rgsczRenames[i];
750 if (wzRename && *wzRename)
751 {
752 // Skip the long path designator if present.
753 if (L'\\' == wzRename[0] && L'?' == wzRename[1] && L'?' == wzRename[2] && L'\\' == wzRename[3])
754 {
755 wzRename += 4;
756 }
757
758 hr = PathCompare(wzPath, wzRename, &nCompare);
759 FileExitOnFailure(hr, "Failed to compare path from pending file rename to check path.");
760
761 // If we find our path in the list, null out the source and target slot and
762 // we'll compact the array next.
763 if (CSTR_EQUAL == nCompare)
764 {
765 ReleaseNullStr(rgsczRenames[i]);
766 ReleaseNullStr(rgsczRenames[i + 1]);
767 fRemoved = TRUE;
768 }
769 }
770 }
771
772 if (fRemoved)
773 {
774 // Compact the array by removing any nulls.
775 for (DWORD i = 0; i < cRenames; ++i)
776 {
777 LPWSTR wzRename = rgsczRenames[i];
778 if (wzRename)
779 {
780 rgsczRenames[cNewRenames] = wzRename;
781 ++cNewRenames;
782 }
783 }
784
785 cRenames = cNewRenames; // ignore the pointers on the end of the array since an early index points to them already.
786
787 // Write the new array back to the pending file rename key.
788 hr = RegWriteStringArray(hkPendingFileRename, REGISTRY_PENDING_FILE_RENAME_VALUE, rgsczRenames, cRenames);
789 FileExitOnFailure(hr, "Failed to update pending file renames.");
790 }
791
792LExit:
793 ReleaseStrArray(rgsczRenames, cRenames);
794 ReleaseRegKey(hkPendingFileRename);
795
796 return hr;
797}
798
799
800/*******************************************************************
801 FileRead - read a file into memory
802
803********************************************************************/
804extern "C" HRESULT DAPI FileRead(
805 __deref_out_bcount_full(*pcbDest) LPBYTE* ppbDest,
806 __out SIZE_T* pcbDest,
807 __in_z LPCWSTR wzSrcPath
808 )
809{
810 HRESULT hr = FileReadPartial(ppbDest, pcbDest, wzSrcPath, FALSE, 0, 0xFFFFFFFF, FALSE);
811 return hr;
812}
813
814/*******************************************************************
815 FileRead - read a file into memory with specified share mode
816
817********************************************************************/
818extern "C" HRESULT DAPI FileReadEx(
819 __deref_out_bcount_full(*pcbDest) LPBYTE* ppbDest,
820 __out SIZE_T* pcbDest,
821 __in_z LPCWSTR wzSrcPath,
822 __in DWORD dwShareMode
823 )
824{
825 HRESULT hr = FileReadPartialEx(ppbDest, pcbDest, wzSrcPath, FALSE, 0, 0xFFFFFFFF, FALSE, dwShareMode);
826 return hr;
827}
828
829/*******************************************************************
830 FileReadUntil - read a file into memory with a maximum size
831
832********************************************************************/
833extern "C" HRESULT DAPI FileReadUntil(
834 __deref_out_bcount_full(*pcbDest) LPBYTE* ppbDest,
835 __out_range(<=, cbMaxRead) SIZE_T* pcbDest,
836 __in_z LPCWSTR wzSrcPath,
837 __in DWORD cbMaxRead
838 )
839{
840 HRESULT hr = FileReadPartial(ppbDest, pcbDest, wzSrcPath, FALSE, 0, cbMaxRead, FALSE);
841 return hr;
842}
843
844
845/*******************************************************************
846 FileReadPartial - read a portion of a file into memory
847
848********************************************************************/
849extern "C" HRESULT DAPI FileReadPartial(
850 __deref_out_bcount_full(*pcbDest) LPBYTE* ppbDest,
851 __out_range(<=, cbMaxRead) SIZE_T* pcbDest,
852 __in_z LPCWSTR wzSrcPath,
853 __in BOOL fSeek,
854 __in DWORD cbStartPosition,
855 __in DWORD cbMaxRead,
856 __in BOOL fPartialOK
857 )
858{
859 return FileReadPartialEx(ppbDest, pcbDest, wzSrcPath, fSeek, cbStartPosition, cbMaxRead, fPartialOK, FILE_SHARE_READ | FILE_SHARE_DELETE);
860}
861
862/*******************************************************************
863 FileReadPartial - read a portion of a file into memory
864 (with specified share mode)
865********************************************************************/
866extern "C" HRESULT DAPI FileReadPartialEx(
867 __deref_inout_bcount_full(*pcbDest) LPBYTE* ppbDest,
868 __out_range(<=, cbMaxRead) SIZE_T* pcbDest,
869 __in_z LPCWSTR wzSrcPath,
870 __in BOOL fSeek,
871 __in DWORD cbStartPosition,
872 __in DWORD cbMaxRead,
873 __in BOOL fPartialOK,
874 __in DWORD dwShareMode
875 )
876{
877 HRESULT hr = S_OK;
878
879 UINT er = ERROR_SUCCESS;
880 HANDLE hFile = INVALID_HANDLE_VALUE;
881 LARGE_INTEGER liFileSize = { };
882 DWORD cbData = 0;
883 BYTE* pbData = NULL;
884
885 FileExitOnNull(pcbDest, hr, E_INVALIDARG, "Invalid argument pcbDest");
886 FileExitOnNull(ppbDest, hr, E_INVALIDARG, "Invalid argument ppbDest");
887 FileExitOnNull(wzSrcPath, hr, E_INVALIDARG, "Invalid argument wzSrcPath");
888 FileExitOnNull(*wzSrcPath, hr, E_INVALIDARG, "*wzSrcPath is null");
889
890 hFile = ::CreateFileW(wzSrcPath, GENERIC_READ, dwShareMode, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
891 if (INVALID_HANDLE_VALUE == hFile)
892 {
893 er = ::GetLastError();
894 if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er))
895 {
896 ExitFunction1(hr = E_FILENOTFOUND);
897 }
898 FileExitOnWin32Error(er, hr, "Failed to open file: %ls", wzSrcPath);
899 }
900
901 if (!::GetFileSizeEx(hFile, &liFileSize))
902 {
903 FileExitWithLastError(hr, "Failed to get size of file: %ls", wzSrcPath);
904 }
905
906 if (fSeek)
907 {
908 if (cbStartPosition > liFileSize.QuadPart)
909 {
910 hr = E_INVALIDARG;
911 FileExitOnFailure(hr, "Start position %d bigger than file '%ls' size %llu", cbStartPosition, wzSrcPath, liFileSize.QuadPart);
912 }
913
914 DWORD dwErr = ::SetFilePointer(hFile, cbStartPosition, NULL, FILE_CURRENT);
915 if (INVALID_SET_FILE_POINTER == dwErr)
916 {
917 FileExitOnLastError(hr, "Failed to seek position %d", cbStartPosition);
918 }
919 }
920 else
921 {
922 cbStartPosition = 0;
923 }
924
925 if (fPartialOK)
926 {
927 cbData = cbMaxRead;
928 }
929 else
930 {
931 cbData = liFileSize.LowPart - cbStartPosition; // should only need the low part because we cap at DWORD
932 if (cbMaxRead < liFileSize.QuadPart - cbStartPosition)
933 {
934 hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
935 FileExitOnRootFailure(hr, "Failed to load file: %ls, too large.", wzSrcPath);
936 }
937 }
938
939 if (*ppbDest)
940 {
941 if (0 == cbData)
942 {
943 ReleaseNullMem(*ppbDest);
944 *pcbDest = 0;
945 ExitFunction1(hr = S_OK);
946 }
947
948 LPVOID pv = MemReAlloc(*ppbDest, cbData, TRUE);
949 FileExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to re-allocate memory to read in file: %ls", wzSrcPath);
950
951 pbData = static_cast<BYTE*>(pv);
952 }
953 else
954 {
955 if (0 == cbData)
956 {
957 *pcbDest = 0;
958 ExitFunction1(hr = S_OK);
959 }
960
961 pbData = static_cast<BYTE*>(MemAlloc(cbData, TRUE));
962 FileExitOnNull(pbData, hr, E_OUTOFMEMORY, "Failed to allocate memory to read in file: %ls", wzSrcPath);
963 }
964
965 DWORD cbTotalRead = 0;
966 DWORD cbRead = 0;
967 do
968 {
969 DWORD cbRemaining = 0;
970 hr = ::ULongSub(cbData, cbTotalRead, &cbRemaining);
971 FileExitOnFailure(hr, "Underflow calculating remaining buffer size.");
972
973 if (!::ReadFile(hFile, pbData + cbTotalRead, cbRemaining, &cbRead, NULL))
974 {
975 FileExitWithLastError(hr, "Failed to read from file: %ls", wzSrcPath);
976 }
977
978 cbTotalRead += cbRead;
979 } while (cbRead);
980
981 if (cbTotalRead != cbData)
982 {
983 hr = E_UNEXPECTED;
984 FileExitOnFailure(hr, "Failed to completely read file: %ls", wzSrcPath);
985 }
986
987 *ppbDest = pbData;
988 pbData = NULL;
989 *pcbDest = cbData;
990
991LExit:
992 ReleaseMem(pbData);
993 ReleaseFile(hFile);
994
995 return hr;
996}
997
998extern "C" HRESULT DAPI FileReadHandle(
999 __in HANDLE hFile,
1000 __in_bcount(cbDest) LPBYTE pbDest,
1001 __in SIZE_T cbDest
1002 )
1003{
1004 HRESULT hr = 0;
1005 DWORD cbDataRead = 0;
1006 SIZE_T cbRemaining = cbDest;
1007 SIZE_T cbTotal = 0;
1008
1009 while (0 < cbRemaining)
1010 {
1011 if (!::ReadFile(hFile, pbDest + cbTotal, (DWORD)min(DWORD_MAX, cbRemaining), &cbDataRead, NULL))
1012 {
1013 DWORD er = ::GetLastError();
1014 if (ERROR_MORE_DATA == er)
1015 {
1016 hr = S_OK;
1017 }
1018 else
1019 {
1020 hr = HRESULT_FROM_WIN32(er);
1021 }
1022 FileExitOnRootFailure(hr, "Failed to read data from file handle.");
1023 }
1024
1025 cbRemaining -= cbDataRead;
1026 cbTotal += cbDataRead;
1027 }
1028
1029LExit:
1030 return hr;
1031}
1032
1033
1034/*******************************************************************
1035 FileWrite - write a file from memory
1036
1037********************************************************************/
1038extern "C" HRESULT DAPI FileWrite(
1039 __in_z LPCWSTR pwzFileName,
1040 __in DWORD dwFlagsAndAttributes,
1041 __in_bcount_opt(cbData) LPCBYTE pbData,
1042 __in SIZE_T cbData,
1043 __out_opt HANDLE* pHandle
1044 )
1045{
1046 HRESULT hr = S_OK;
1047 HANDLE hFile = INVALID_HANDLE_VALUE;
1048
1049 // Open the file
1050 hFile = ::CreateFileW(pwzFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, dwFlagsAndAttributes, NULL);
1051 FileExitOnInvalidHandleWithLastError(hFile, hr, "Failed to open file: %ls", pwzFileName);
1052
1053 hr = FileWriteHandle(hFile, pbData, cbData);
1054 FileExitOnFailure(hr, "Failed to write to file: %ls", pwzFileName);
1055
1056 if (pHandle)
1057 {
1058 *pHandle = hFile;
1059 hFile = INVALID_HANDLE_VALUE;
1060 }
1061
1062LExit:
1063 ReleaseFile(hFile);
1064
1065 return hr;
1066}
1067
1068
1069/*******************************************************************
1070 FileWriteHandle - write to a file handle from memory
1071
1072********************************************************************/
1073extern "C" HRESULT DAPI FileWriteHandle(
1074 __in HANDLE hFile,
1075 __in_bcount_opt(cbData) LPCBYTE pbData,
1076 __in SIZE_T cbData
1077 )
1078{
1079 HRESULT hr = S_OK;
1080 DWORD cbDataWritten = 0;
1081 SIZE_T cbTotal = 0;
1082 SIZE_T cbRemaining = cbData;
1083
1084 // Write out all of the data.
1085 while (0 < cbRemaining)
1086 {
1087 if (!::WriteFile(hFile, pbData + cbTotal, (DWORD)min(DWORD_MAX, cbRemaining), &cbDataWritten, NULL))
1088 {
1089 FileExitOnLastError(hr, "Failed to write data to file handle.");
1090 }
1091
1092 cbRemaining -= cbDataWritten;
1093 cbTotal += cbDataWritten;
1094 }
1095
1096LExit:
1097 return hr;
1098}
1099
1100
1101/*******************************************************************
1102 FileCopyUsingHandles
1103
1104*******************************************************************/
1105extern "C" HRESULT DAPI FileCopyUsingHandles(
1106 __in HANDLE hSource,
1107 __in HANDLE hTarget,
1108 __in DWORD64 cbCopy,
1109 __out_opt DWORD64* pcbCopied
1110 )
1111{
1112 HRESULT hr = S_OK;
1113 DWORD64 cbTotalCopied = 0;
1114 BYTE rgbData[4 * 1024];
1115 DWORD cbRead = 0;
1116
1117 do
1118 {
1119 cbRead = static_cast<DWORD>((0 == cbCopy) ? countof(rgbData) : min(countof(rgbData), cbCopy - cbTotalCopied));
1120 if (!::ReadFile(hSource, rgbData, cbRead, &cbRead, NULL))
1121 {
1122 FileExitWithLastError(hr, "Failed to read from source.");
1123 }
1124
1125 if (cbRead)
1126 {
1127 hr = FileWriteHandle(hTarget, rgbData, cbRead);
1128 FileExitOnFailure(hr, "Failed to write to target.");
1129 }
1130
1131 cbTotalCopied += cbRead;
1132 } while (cbTotalCopied < cbCopy && 0 != cbRead);
1133
1134 if (pcbCopied)
1135 {
1136 *pcbCopied = cbTotalCopied;
1137 }
1138
1139LExit:
1140 return hr;
1141}
1142
1143
1144/*******************************************************************
1145 FileCopyUsingHandlesWithProgress
1146
1147*******************************************************************/
1148extern "C" HRESULT DAPI FileCopyUsingHandlesWithProgress(
1149 __in HANDLE hSource,
1150 __in HANDLE hTarget,
1151 __in DWORD64 cbCopy,
1152 __in_opt LPPROGRESS_ROUTINE lpProgressRoutine,
1153 __in_opt LPVOID lpData
1154 )
1155{
1156 HRESULT hr = S_OK;
1157 DWORD64 cbTotalCopied = 0;
1158 BYTE rgbData[64 * 1024];
1159 DWORD cbRead = 0;
1160
1161 LARGE_INTEGER liSourceSize = { };
1162 LARGE_INTEGER liTotalCopied = { };
1163 LARGE_INTEGER liZero = { };
1164 DWORD dwResult = 0;
1165
1166 hr = FileSizeByHandle(hSource, &liSourceSize.QuadPart);
1167 FileExitOnFailure(hr, "Failed to get size of source.");
1168
1169 if (0 < cbCopy && cbCopy < (DWORD64)liSourceSize.QuadPart)
1170 {
1171 liSourceSize.QuadPart = cbCopy;
1172 }
1173
1174 if (lpProgressRoutine)
1175 {
1176 dwResult = lpProgressRoutine(liSourceSize, liTotalCopied, liZero, liZero, 0, CALLBACK_STREAM_SWITCH, hSource, hTarget, lpData);
1177 switch (dwResult)
1178 {
1179 case PROGRESS_CONTINUE:
1180 break;
1181
1182 case PROGRESS_CANCEL:
1183 ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_REQUEST_ABORTED));
1184
1185 case PROGRESS_STOP:
1186 ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_REQUEST_ABORTED));
1187
1188 case PROGRESS_QUIET:
1189 lpProgressRoutine = NULL;
1190 break;
1191 }
1192 }
1193
1194 // Set size of the target file.
1195 ::SetFilePointerEx(hTarget, liSourceSize, NULL, FILE_BEGIN);
1196
1197 if (!::SetEndOfFile(hTarget))
1198 {
1199 FileExitWithLastError(hr, "Failed to set end of target file.");
1200 }
1201
1202 if (!::SetFilePointerEx(hTarget, liZero, NULL, FILE_BEGIN))
1203 {
1204 FileExitWithLastError(hr, "Failed to reset target file pointer.");
1205 }
1206
1207 // Copy with progress.
1208 while (0 == cbCopy || cbTotalCopied < cbCopy)
1209 {
1210 cbRead = static_cast<DWORD>((0 == cbCopy) ? countof(rgbData) : min(countof(rgbData), cbCopy - cbTotalCopied));
1211 if (!::ReadFile(hSource, rgbData, cbRead, &cbRead, NULL))
1212 {
1213 FileExitWithLastError(hr, "Failed to read from source.");
1214 }
1215
1216 if (cbRead)
1217 {
1218 hr = FileWriteHandle(hTarget, rgbData, cbRead);
1219 FileExitOnFailure(hr, "Failed to write to target.");
1220
1221 cbTotalCopied += cbRead;
1222
1223 if (lpProgressRoutine)
1224 {
1225 liTotalCopied.QuadPart = cbTotalCopied;
1226 dwResult = lpProgressRoutine(liSourceSize, liTotalCopied, liZero, liZero, 0, CALLBACK_CHUNK_FINISHED, hSource, hTarget, lpData);
1227 switch (dwResult)
1228 {
1229 case PROGRESS_CONTINUE:
1230 break;
1231
1232 case PROGRESS_CANCEL:
1233 ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_REQUEST_ABORTED));
1234
1235 case PROGRESS_STOP:
1236 ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_REQUEST_ABORTED));
1237
1238 case PROGRESS_QUIET:
1239 lpProgressRoutine = NULL;
1240 break;
1241 }
1242 }
1243 }
1244 else
1245 {
1246 break;
1247 }
1248 }
1249
1250LExit:
1251 return hr;
1252}
1253
1254
1255/*******************************************************************
1256 FileEnsureCopy
1257
1258*******************************************************************/
1259extern "C" HRESULT DAPI FileEnsureCopy(
1260 __in_z LPCWSTR wzSource,
1261 __in_z LPCWSTR wzTarget,
1262 __in BOOL fOverwrite
1263 )
1264{
1265 HRESULT hr = S_OK;
1266 DWORD er;
1267
1268 // try to copy the file first
1269 if (::CopyFileW(wzSource, wzTarget, !fOverwrite))
1270 {
1271 ExitFunction(); // we're done
1272 }
1273
1274 er = ::GetLastError(); // check the error and do the right thing below
1275 if (!fOverwrite && (ERROR_FILE_EXISTS == er || ERROR_ALREADY_EXISTS == er))
1276 {
1277 // if not overwriting this is an expected error
1278 ExitFunction1(hr = S_FALSE);
1279 }
1280 else if (ERROR_PATH_NOT_FOUND == er) // if the path doesn't exist
1281 {
1282 // try to create the directory then do the copy
1283 LPWSTR pwzLastSlash = NULL;
1284 for (LPWSTR pwz = const_cast<LPWSTR>(wzTarget); *pwz; ++pwz)
1285 {
1286 if (*pwz == L'\\')
1287 {
1288 pwzLastSlash = pwz;
1289 }
1290 }
1291
1292 if (pwzLastSlash)
1293 {
1294 *pwzLastSlash = L'\0'; // null terminate
1295 hr = DirEnsureExists(wzTarget, NULL);
1296 *pwzLastSlash = L'\\'; // now put the slash back
1297 FileExitOnFailureDebugTrace(hr, "failed to create directory while copying file: '%ls' to: '%ls'", wzSource, wzTarget);
1298
1299 // try to copy again
1300 if (!::CopyFileW(wzSource, wzTarget, fOverwrite))
1301 {
1302 FileExitOnLastErrorDebugTrace(hr, "failed to copy file: '%ls' to: '%ls'", wzSource, wzTarget);
1303 }
1304 }
1305 else // no path was specified so just return the error
1306 {
1307 hr = HRESULT_FROM_WIN32(er);
1308 }
1309 }
1310 else // unexpected error
1311 {
1312 hr = HRESULT_FROM_WIN32(er);
1313 }
1314
1315LExit:
1316 return hr;
1317}
1318
1319
1320/*******************************************************************
1321 FileEnsureCopyWithRetry
1322
1323*******************************************************************/
1324extern "C" HRESULT DAPI FileEnsureCopyWithRetry(
1325 __in LPCWSTR wzSource,
1326 __in LPCWSTR wzTarget,
1327 __in BOOL fOverwrite,
1328 __in DWORD cRetry,
1329 __in DWORD dwWaitMilliseconds
1330 )
1331{
1332 AssertSz(cRetry != DWORD_MAX, "Cannot pass DWORD_MAX for retry.");
1333
1334 HRESULT hr = E_FAIL;
1335 DWORD i = 0;
1336
1337 for (i = 0; FAILED(hr) && i <= cRetry; ++i)
1338 {
1339 if (0 < i)
1340 {
1341 ::Sleep(dwWaitMilliseconds);
1342 }
1343
1344 hr = FileEnsureCopy(wzSource, wzTarget, fOverwrite);
1345 if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr || HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr
1346 || HRESULT_FROM_WIN32(ERROR_FILE_EXISTS) == hr || HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS) == hr)
1347 {
1348 break; // no reason to retry these errors.
1349 }
1350 }
1351 FileExitOnFailure(hr, "Failed to copy file: '%ls' to: '%ls' after %u retries.", wzSource, wzTarget, i);
1352
1353LExit:
1354 return hr;
1355}
1356
1357
1358/*******************************************************************
1359 FileEnsureMove
1360
1361*******************************************************************/
1362extern "C" HRESULT DAPI FileEnsureMove(
1363 __in_z LPCWSTR wzSource,
1364 __in_z LPCWSTR wzTarget,
1365 __in BOOL fOverwrite,
1366 __in BOOL fAllowCopy
1367 )
1368{
1369 HRESULT hr = S_OK;
1370 DWORD er;
1371
1372 DWORD dwFlags = 0;
1373
1374 if (fOverwrite)
1375 {
1376 dwFlags |= MOVEFILE_REPLACE_EXISTING;
1377 }
1378 if (fAllowCopy)
1379 {
1380 dwFlags |= MOVEFILE_COPY_ALLOWED;
1381 }
1382
1383 // try to move the file first
1384 if (::MoveFileExW(wzSource, wzTarget, dwFlags))
1385 {
1386 ExitFunction(); // we're done
1387 }
1388
1389 er = ::GetLastError(); // check the error and do the right thing below
1390 if (!fOverwrite && (ERROR_FILE_EXISTS == er || ERROR_ALREADY_EXISTS == er))
1391 {
1392 // if not overwriting this is an expected error
1393 ExitFunction1(hr = S_FALSE);
1394 }
1395 else if (ERROR_FILE_NOT_FOUND == er)
1396 {
1397 // We are seeing some cases where ::MoveFileEx() says a file was not found
1398 // but the source file is actually present. In that case, return path not
1399 // found so we try to create the target path since that is most likely
1400 // what is missing. Otherwise, the source file is missing and we're obviously
1401 // not going to be recovering from that.
1402 if (FileExistsEx(wzSource, NULL))
1403 {
1404 er = ERROR_PATH_NOT_FOUND;
1405 }
1406 }
1407
1408 // If the path doesn't exist, try to create the directory tree then do the move.
1409 if (ERROR_PATH_NOT_FOUND == er)
1410 {
1411 LPWSTR pwzLastSlash = NULL;
1412 for (LPWSTR pwz = const_cast<LPWSTR>(wzTarget); *pwz; ++pwz)
1413 {
1414 if (*pwz == L'\\')
1415 {
1416 pwzLastSlash = pwz;
1417 }
1418 }
1419
1420 if (pwzLastSlash)
1421 {
1422 *pwzLastSlash = L'\0'; // null terminate
1423 hr = DirEnsureExists(wzTarget, NULL);
1424 *pwzLastSlash = L'\\'; // now put the slash back
1425 FileExitOnFailureDebugTrace(hr, "failed to create directory while moving file: '%ls' to: '%ls'", wzSource, wzTarget);
1426
1427 // try to move again
1428 if (!::MoveFileExW(wzSource, wzTarget, dwFlags))
1429 {
1430 FileExitOnLastErrorDebugTrace(hr, "failed to move file: '%ls' to: '%ls'", wzSource, wzTarget);
1431 }
1432 }
1433 else // no path was specified so just return the error
1434 {
1435 hr = HRESULT_FROM_WIN32(er);
1436 }
1437 }
1438 else // unexpected error
1439 {
1440 hr = HRESULT_FROM_WIN32(er);
1441 }
1442
1443LExit:
1444 return hr;
1445}
1446
1447
1448/*******************************************************************
1449 FileEnsureMoveWithRetry
1450
1451*******************************************************************/
1452extern "C" HRESULT DAPI FileEnsureMoveWithRetry(
1453 __in LPCWSTR wzSource,
1454 __in LPCWSTR wzTarget,
1455 __in BOOL fOverwrite,
1456 __in BOOL fAllowCopy,
1457 __in DWORD cRetry,
1458 __in DWORD dwWaitMilliseconds
1459 )
1460{
1461 AssertSz(cRetry != DWORD_MAX, "Cannot pass DWORD_MAX for retry.");
1462
1463 HRESULT hr = E_FAIL;
1464 DWORD i = 0;
1465
1466 for (i = 0; FAILED(hr) && i < cRetry + 1; ++i)
1467 {
1468 if (0 < i)
1469 {
1470 ::Sleep(dwWaitMilliseconds);
1471 }
1472
1473 hr = FileEnsureMove(wzSource, wzTarget, fOverwrite, fAllowCopy);
1474 }
1475 FileExitOnFailure(hr, "Failed to move file: '%ls' to: '%ls' after %u retries.", wzSource, wzTarget, i);
1476
1477LExit:
1478 return hr;
1479}
1480
1481
1482/*******************************************************************
1483 FileCreateTemp - creates an empty temp file
1484
1485 NOTE: uses ANSI functions internally so it is Win9x safe
1486********************************************************************/
1487extern "C" HRESULT DAPI FileCreateTemp(
1488 __in_z LPCWSTR wzPrefix,
1489 __in_z LPCWSTR wzExtension,
1490 __deref_opt_out_z LPWSTR* ppwzTempFile,
1491 __out_opt HANDLE* phTempFile
1492 )
1493{
1494 Assert(wzPrefix && *wzPrefix);
1495 HRESULT hr = S_OK;
1496 LPSTR pszTempPath = NULL;
1497 DWORD cchTempPath = MAX_PATH;
1498
1499 HANDLE hTempFile = INVALID_HANDLE_VALUE;
1500 LPSTR pszTempFile = NULL;
1501
1502 int i = 0;
1503
1504 hr = StrAnsiAlloc(&pszTempPath, cchTempPath);
1505 FileExitOnFailure(hr, "failed to allocate memory for the temp path");
1506 ::GetTempPathA(cchTempPath, pszTempPath);
1507
1508 for (i = 0; i < 1000 && INVALID_HANDLE_VALUE == hTempFile; ++i)
1509 {
1510 hr = StrAnsiAllocFormatted(&pszTempFile, "%s%ls%05d.%ls", pszTempPath, wzPrefix, i, wzExtension);
1511 FileExitOnFailure(hr, "failed to allocate memory for log file");
1512
1513 hTempFile = ::CreateFileA(pszTempFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
1514 if (INVALID_HANDLE_VALUE == hTempFile)
1515 {
1516 // if the file already exists, just try again
1517 hr = HRESULT_FROM_WIN32(::GetLastError());
1518 if (HRESULT_FROM_WIN32(ERROR_FILE_EXISTS) == hr)
1519 {
1520 hr = S_OK;
1521 continue;
1522 }
1523 FileExitOnFailureDebugTrace(hr, "failed to create file: %hs", pszTempFile);
1524 }
1525 }
1526
1527 if (ppwzTempFile)
1528 {
1529 hr = StrAllocStringAnsi(ppwzTempFile, pszTempFile, 0, CP_UTF8);
1530 }
1531
1532 if (phTempFile)
1533 {
1534 *phTempFile = hTempFile;
1535 hTempFile = INVALID_HANDLE_VALUE;
1536 }
1537
1538LExit:
1539 ReleaseFile(hTempFile);
1540 ReleaseStr(pszTempFile);
1541 ReleaseStr(pszTempPath);
1542
1543 return hr;
1544}
1545
1546
1547/*******************************************************************
1548 FileCreateTempW - creates an empty temp file
1549
1550*******************************************************************/
1551extern "C" HRESULT DAPI FileCreateTempW(
1552 __in_z LPCWSTR wzPrefix,
1553 __in_z LPCWSTR wzExtension,
1554 __deref_opt_out_z LPWSTR* ppwzTempFile,
1555 __out_opt HANDLE* phTempFile
1556 )
1557{
1558 Assert(wzPrefix && *wzPrefix);
1559 HRESULT hr = E_FAIL;
1560
1561 WCHAR wzTempPath[MAX_PATH];
1562 DWORD cchTempPath = countof(wzTempPath);
1563 LPWSTR pwzTempFile = NULL;
1564
1565 HANDLE hTempFile = INVALID_HANDLE_VALUE;
1566 int i = 0;
1567
1568 if (!::GetTempPathW(cchTempPath, wzTempPath))
1569 {
1570 FileExitOnLastError(hr, "failed to get temp path");
1571 }
1572
1573 for (i = 0; i < 1000 && INVALID_HANDLE_VALUE == hTempFile; ++i)
1574 {
1575 hr = StrAllocFormatted(&pwzTempFile, L"%s%s%05d.%s", wzTempPath, wzPrefix, i, wzExtension);
1576 FileExitOnFailure(hr, "failed to allocate memory for temp filename");
1577
1578 hTempFile = ::CreateFileW(pwzTempFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
1579 if (INVALID_HANDLE_VALUE == hTempFile)
1580 {
1581 // if the file already exists, just try again
1582 hr = HRESULT_FROM_WIN32(::GetLastError());
1583 if (HRESULT_FROM_WIN32(ERROR_FILE_EXISTS) == hr)
1584 {
1585 hr = S_OK;
1586 continue;
1587 }
1588 FileExitOnFailureDebugTrace(hr, "failed to create file: %ls", pwzTempFile);
1589 }
1590 }
1591
1592 if (phTempFile)
1593 {
1594 *phTempFile = hTempFile;
1595 hTempFile = INVALID_HANDLE_VALUE;
1596 }
1597
1598 if (ppwzTempFile)
1599 {
1600 *ppwzTempFile = pwzTempFile;
1601 pwzTempFile = NULL;
1602 }
1603
1604LExit:
1605 ReleaseFile(hTempFile);
1606 ReleaseStr(pwzTempFile);
1607
1608 return hr;
1609}
1610
1611
1612/*******************************************************************
1613 FileIsSame
1614
1615********************************************************************/
1616extern "C" HRESULT DAPI FileIsSame(
1617 __in_z LPCWSTR wzFile1,
1618 __in_z LPCWSTR wzFile2,
1619 __out LPBOOL lpfSameFile
1620 )
1621{
1622 HRESULT hr = S_OK;
1623 HANDLE hFile1 = NULL;
1624 HANDLE hFile2 = NULL;
1625 BY_HANDLE_FILE_INFORMATION fileInfo1 = { };
1626 BY_HANDLE_FILE_INFORMATION fileInfo2 = { };
1627
1628 hFile1 = ::CreateFileW(wzFile1, FILE_READ_ATTRIBUTES, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
1629 FileExitOnInvalidHandleWithLastError(hFile1, hr, "Failed to open file 1. File = '%ls'", wzFile1);
1630
1631 hFile2 = ::CreateFileW(wzFile2, FILE_READ_ATTRIBUTES, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
1632 FileExitOnInvalidHandleWithLastError(hFile2, hr, "Failed to open file 2. File = '%ls'", wzFile2);
1633
1634 if (!::GetFileInformationByHandle(hFile1, &fileInfo1))
1635 {
1636 FileExitWithLastError(hr, "Failed to get information for file 1. File = '%ls'", wzFile1);
1637 }
1638
1639 if (!::GetFileInformationByHandle(hFile2, &fileInfo2))
1640 {
1641 FileExitWithLastError(hr, "Failed to get information for file 2. File = '%ls'", wzFile2);
1642 }
1643
1644 *lpfSameFile = fileInfo1.dwVolumeSerialNumber == fileInfo2.dwVolumeSerialNumber &&
1645 fileInfo1.nFileIndexHigh == fileInfo2.nFileIndexHigh &&
1646 fileInfo1.nFileIndexLow == fileInfo2.nFileIndexLow ? TRUE : FALSE;
1647
1648LExit:
1649 ReleaseFile(hFile1);
1650 ReleaseFile(hFile2);
1651
1652 return hr;
1653}
1654
1655/*******************************************************************
1656 FileEnsureDelete - deletes a file, first removing read-only,
1657 hidden, or system attributes if necessary.
1658********************************************************************/
1659extern "C" HRESULT DAPI FileEnsureDelete(
1660 __in_z LPCWSTR wzFile
1661 )
1662{
1663 HRESULT hr = S_OK;
1664
1665 DWORD dwAttrib = INVALID_FILE_ATTRIBUTES;
1666 if (FileExistsEx(wzFile, &dwAttrib))
1667 {
1668 if (dwAttrib & FILE_ATTRIBUTE_READONLY || dwAttrib & FILE_ATTRIBUTE_HIDDEN || dwAttrib & FILE_ATTRIBUTE_SYSTEM)
1669 {
1670 if (!::SetFileAttributesW(wzFile, FILE_ATTRIBUTE_NORMAL))
1671 {
1672 FileExitOnLastError(hr, "Failed to remove attributes from file: %ls", wzFile);
1673 }
1674 }
1675
1676 if (!::DeleteFileW(wzFile))
1677 {
1678 FileExitOnLastError(hr, "Failed to delete file: %ls", wzFile);
1679 }
1680 }
1681
1682LExit:
1683 return hr;
1684}
1685
1686/*******************************************************************
1687 FileGetTime - Gets the file time of a specified file
1688********************************************************************/
1689extern "C" HRESULT DAPI FileGetTime(
1690 __in_z LPCWSTR wzFile,
1691 __out_opt LPFILETIME lpCreationTime,
1692 __out_opt LPFILETIME lpLastAccessTime,
1693 __out_opt LPFILETIME lpLastWriteTime
1694 )
1695{
1696 HRESULT hr = S_OK;
1697 HANDLE hFile = NULL;
1698
1699 hFile = ::CreateFileW(wzFile, FILE_READ_ATTRIBUTES, FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL);
1700 FileExitOnInvalidHandleWithLastError(hFile, hr, "Failed to open file. File = '%ls'", wzFile);
1701
1702 if (!::GetFileTime(hFile, lpCreationTime, lpLastAccessTime, lpLastWriteTime))
1703 {
1704 FileExitWithLastError(hr, "Failed to get file time for file. File = '%ls'", wzFile);
1705 }
1706
1707LExit:
1708 ReleaseFile(hFile);
1709 return hr;
1710}
1711
1712/*******************************************************************
1713 FileSetTime - Sets the file time of a specified file
1714********************************************************************/
1715extern "C" HRESULT DAPI FileSetTime(
1716 __in_z LPCWSTR wzFile,
1717 __in_opt const FILETIME *lpCreationTime,
1718 __in_opt const FILETIME *lpLastAccessTime,
1719 __in_opt const FILETIME *lpLastWriteTime
1720 )
1721{
1722 HRESULT hr = S_OK;
1723 HANDLE hFile = NULL;
1724
1725 hFile = ::CreateFileW(wzFile, FILE_WRITE_ATTRIBUTES, FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL);
1726 FileExitOnInvalidHandleWithLastError(hFile, hr, "Failed to open file. File = '%ls'", wzFile);
1727
1728 if (!::SetFileTime(hFile, lpCreationTime, lpLastAccessTime, lpLastWriteTime))
1729 {
1730 FileExitWithLastError(hr, "Failed to set file time for file. File = '%ls'", wzFile);
1731 }
1732
1733LExit:
1734 ReleaseFile(hFile);
1735 return hr;
1736}
1737
1738/*******************************************************************
1739 FileReSetTime - ReSets a file's last acess and modified time to the
1740 creation time of the file
1741********************************************************************/
1742extern "C" HRESULT DAPI FileResetTime(
1743 __in_z LPCWSTR wzFile
1744 )
1745{
1746 HRESULT hr = S_OK;
1747 HANDLE hFile = NULL;
1748 FILETIME ftCreateTime;
1749
1750 hFile = ::CreateFileW(wzFile, FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
1751 FileExitOnInvalidHandleWithLastError(hFile, hr, "Failed to open file. File = '%ls'", wzFile);
1752
1753 if (!::GetFileTime(hFile, &ftCreateTime, NULL, NULL))
1754 {
1755 FileExitWithLastError(hr, "Failed to get file time for file. File = '%ls'", wzFile);
1756 }
1757
1758 if (!::SetFileTime(hFile, NULL, NULL, &ftCreateTime))
1759 {
1760 FileExitWithLastError(hr, "Failed to reset file time for file. File = '%ls'", wzFile);
1761 }
1762
1763LExit:
1764 ReleaseFile(hFile);
1765 return hr;
1766}
1767
1768
1769/*******************************************************************
1770 FileExecutableArchitecture
1771
1772*******************************************************************/
1773extern "C" HRESULT DAPI FileExecutableArchitecture(
1774 __in_z LPCWSTR wzFile,
1775 __out FILE_ARCHITECTURE *pArchitecture
1776 )
1777{
1778 HRESULT hr = S_OK;
1779
1780 HANDLE hFile = INVALID_HANDLE_VALUE;
1781 DWORD cbRead = 0;
1782 IMAGE_DOS_HEADER DosImageHeader = { };
1783 IMAGE_NT_HEADERS NtImageHeader = { };
1784
1785 hFile = ::CreateFileW(wzFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
1786 if (hFile == INVALID_HANDLE_VALUE)
1787 {
1788 FileExitWithLastError(hr, "Failed to open file: %ls", wzFile);
1789 }
1790
1791 if (!::ReadFile(hFile, &DosImageHeader, sizeof(DosImageHeader), &cbRead, NULL))
1792 {
1793 FileExitWithLastError(hr, "Failed to read DOS header from file: %ls", wzFile);
1794 }
1795
1796 if (DosImageHeader.e_magic != IMAGE_DOS_SIGNATURE)
1797 {
1798 hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT);
1799 FileExitOnRootFailure(hr, "Read invalid DOS header from file: %ls", wzFile);
1800 }
1801
1802 if (INVALID_SET_FILE_POINTER == ::SetFilePointer(hFile, DosImageHeader.e_lfanew, NULL, FILE_BEGIN))
1803 {
1804 FileExitWithLastError(hr, "Failed to seek the NT header in file: %ls", wzFile);
1805 }
1806
1807 if (!::ReadFile(hFile, &NtImageHeader, sizeof(NtImageHeader), &cbRead, NULL))
1808 {
1809 FileExitWithLastError(hr, "Failed to read NT header from file: %ls", wzFile);
1810 }
1811
1812 if (NtImageHeader.Signature != IMAGE_NT_SIGNATURE)
1813 {
1814 hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT);
1815 FileExitOnRootFailure(hr, "Read invalid NT header from file: %ls", wzFile);
1816 }
1817
1818 if (IMAGE_SUBSYSTEM_NATIVE == NtImageHeader.OptionalHeader.Subsystem ||
1819 IMAGE_SUBSYSTEM_WINDOWS_GUI == NtImageHeader.OptionalHeader.Subsystem ||
1820 IMAGE_SUBSYSTEM_WINDOWS_CUI == NtImageHeader.OptionalHeader.Subsystem)
1821 {
1822 switch (NtImageHeader.FileHeader.Machine)
1823 {
1824 case IMAGE_FILE_MACHINE_I386:
1825 *pArchitecture = FILE_ARCHITECTURE_X86;
1826 break;
1827 case IMAGE_FILE_MACHINE_IA64:
1828 *pArchitecture = FILE_ARCHITECTURE_IA64;
1829 break;
1830 case IMAGE_FILE_MACHINE_AMD64:
1831 *pArchitecture = FILE_ARCHITECTURE_X64;
1832 break;
1833 default:
1834 hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT);
1835 break;
1836 }
1837 }
1838 else
1839 {
1840 hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT);
1841 }
1842 FileExitOnFailure(hr, "Unexpected subsystem: %d machine type: %d specified in NT header from file: %ls", NtImageHeader.OptionalHeader.Subsystem, NtImageHeader.FileHeader.Machine, wzFile);
1843
1844LExit:
1845 if (hFile != INVALID_HANDLE_VALUE)
1846 {
1847 ::CloseHandle(hFile);
1848 }
1849
1850 return hr;
1851}
1852
1853/*******************************************************************
1854 FileToString
1855
1856*******************************************************************/
1857extern "C" HRESULT DAPI FileToString(
1858 __in_z LPCWSTR wzFile,
1859 __out LPWSTR *psczString,
1860 __out_opt FILE_ENCODING *pfeEncoding
1861 )
1862{
1863 HRESULT hr = S_OK;
1864 BYTE *pbFullFileBuffer = NULL;
1865 SIZE_T cbFullFileBuffer = 0;
1866 BOOL fNullCharFound = FALSE;
1867 LPWSTR sczFileText = NULL;
1868
1869 // Check if the file is ANSI
1870 hr = FileRead(&pbFullFileBuffer, &cbFullFileBuffer, wzFile);
1871 FileExitOnFailure(hr, "Failed to read file: %ls", wzFile);
1872
1873 if (0 == cbFullFileBuffer)
1874 {
1875 *psczString = NULL;
1876 ExitFunction1(hr = S_OK);
1877 }
1878
1879 // UTF-8 BOM
1880 if (cbFullFileBuffer > sizeof(UTF8BOM) && 0 == memcmp(pbFullFileBuffer, UTF8BOM, sizeof(UTF8BOM)))
1881 {
1882 if (pfeEncoding)
1883 {
1884 *pfeEncoding = FILE_ENCODING_UTF8_WITH_BOM;
1885 }
1886
1887 hr = StrAllocStringAnsi(&sczFileText, reinterpret_cast<LPCSTR>(pbFullFileBuffer + 3), cbFullFileBuffer - 3, CP_UTF8);
1888 FileExitOnFailure(hr, "Failed to convert file %ls from UTF-8 as its BOM indicated", wzFile);
1889
1890 *psczString = sczFileText;
1891 sczFileText = NULL;
1892 }
1893 // UTF-16 BOM, little endian (windows regular UTF-16)
1894 else if (cbFullFileBuffer > sizeof(UTF16BOM) && 0 == memcmp(pbFullFileBuffer, UTF16BOM, sizeof(UTF16BOM)))
1895 {
1896 if (pfeEncoding)
1897 {
1898 *pfeEncoding = FILE_ENCODING_UTF16_WITH_BOM;
1899 }
1900
1901 hr = StrAllocString(psczString, reinterpret_cast<LPWSTR>(pbFullFileBuffer + 2), (cbFullFileBuffer - 2) / sizeof(WCHAR));
1902 FileExitOnFailure(hr, "Failed to allocate copy of string");
1903 }
1904 // No BOM, let's try to detect
1905 else
1906 {
1907 for (DWORD i = 0; i < cbFullFileBuffer; ++i)
1908 {
1909 if (pbFullFileBuffer[i] == '\0')
1910 {
1911 fNullCharFound = TRUE;
1912 break;
1913 }
1914 }
1915
1916 if (!fNullCharFound)
1917 {
1918 if (pfeEncoding)
1919 {
1920 *pfeEncoding = FILE_ENCODING_UTF8;
1921 }
1922
1923 hr = StrAllocStringAnsi(&sczFileText, reinterpret_cast<LPCSTR>(pbFullFileBuffer), cbFullFileBuffer, CP_UTF8);
1924 if (FAILED(hr))
1925 {
1926 if (E_OUTOFMEMORY == hr)
1927 {
1928 FileExitOnFailure(hr, "Failed to convert file %ls from UTF-8", wzFile);
1929 }
1930 }
1931 else
1932 {
1933 *psczString = sczFileText;
1934 sczFileText = NULL;
1935 }
1936 }
1937 else if (NULL == *psczString)
1938 {
1939 if (pfeEncoding)
1940 {
1941 *pfeEncoding = FILE_ENCODING_UTF16;
1942 }
1943
1944 hr = StrAllocString(psczString, reinterpret_cast<LPWSTR>(pbFullFileBuffer), cbFullFileBuffer / sizeof(WCHAR));
1945 FileExitOnFailure(hr, "Failed to allocate copy of string");
1946 }
1947 }
1948
1949LExit:
1950 ReleaseStr(sczFileText);
1951 ReleaseMem(pbFullFileBuffer);
1952
1953 return hr;
1954}
1955
1956/*******************************************************************
1957 FileFromString
1958
1959*******************************************************************/
1960extern "C" HRESULT DAPI FileFromString(
1961 __in_z LPCWSTR wzFile,
1962 __in DWORD dwFlagsAndAttributes,
1963 __in_z LPCWSTR sczString,
1964 __in FILE_ENCODING feEncoding
1965 )
1966{
1967 HRESULT hr = S_OK;
1968 LPSTR sczUtf8String = NULL;
1969 BYTE *pbFullFileBuffer = NULL;
1970 const BYTE *pcbFullFileBuffer = NULL;
1971 SIZE_T cbFullFileBuffer = 0;
1972 SIZE_T cbStrLen = 0;
1973
1974 switch (feEncoding)
1975 {
1976 case FILE_ENCODING_UTF8:
1977 hr = StrAnsiAllocString(&sczUtf8String, sczString, 0, CP_UTF8);
1978 FileExitOnFailure(hr, "Failed to convert string to UTF-8 to write UTF-8 file");
1979
1980 hr = ::StringCchLengthA(sczUtf8String, STRSAFE_MAX_CCH, reinterpret_cast<size_t*>(&cbFullFileBuffer));
1981 FileExitOnRootFailure(hr, "Failed to get length of UTF-8 string");
1982
1983 pcbFullFileBuffer = reinterpret_cast<BYTE *>(sczUtf8String);
1984 break;
1985 case FILE_ENCODING_UTF8_WITH_BOM:
1986 hr = StrAnsiAllocString(&sczUtf8String, sczString, 0, CP_UTF8);
1987 FileExitOnFailure(hr, "Failed to convert string to UTF-8 to write UTF-8 file");
1988
1989 hr = ::StringCchLengthA(sczUtf8String, STRSAFE_MAX_CCH, reinterpret_cast<size_t*>(&cbStrLen));
1990 FileExitOnRootFailure(hr, "Failed to get length of UTF-8 string");
1991
1992 cbFullFileBuffer = sizeof(UTF8BOM) + cbStrLen;
1993
1994 pbFullFileBuffer = reinterpret_cast<BYTE *>(MemAlloc(cbFullFileBuffer, TRUE));
1995 FileExitOnNull(pbFullFileBuffer, hr, E_OUTOFMEMORY, "Failed to allocate memory for output file buffer");
1996
1997 memcpy_s(pbFullFileBuffer, sizeof(UTF8BOM), UTF8BOM, sizeof(UTF8BOM));
1998 memcpy_s(pbFullFileBuffer + sizeof(UTF8BOM), cbStrLen, sczUtf8String, cbStrLen);
1999 pcbFullFileBuffer = pbFullFileBuffer;
2000 break;
2001 case FILE_ENCODING_UTF16:
2002 hr = ::StringCchLengthW(sczString, STRSAFE_MAX_CCH, reinterpret_cast<size_t*>(&cbStrLen));
2003 FileExitOnRootFailure(hr, "Failed to get length of string");
2004
2005 cbFullFileBuffer = cbStrLen * sizeof(WCHAR);
2006 pcbFullFileBuffer = reinterpret_cast<const BYTE *>(sczString);
2007 break;
2008 case FILE_ENCODING_UTF16_WITH_BOM:
2009 hr = ::StringCchLengthW(sczString, STRSAFE_MAX_CCH, reinterpret_cast<size_t*>(&cbStrLen));
2010 FileExitOnRootFailure(hr, "Failed to get length of string");
2011
2012 cbStrLen *= sizeof(WCHAR);
2013 cbFullFileBuffer = sizeof(UTF16BOM) + cbStrLen;
2014
2015 pbFullFileBuffer = reinterpret_cast<BYTE *>(MemAlloc(cbFullFileBuffer, TRUE));
2016 FileExitOnNull(pbFullFileBuffer, hr, E_OUTOFMEMORY, "Failed to allocate memory for output file buffer");
2017
2018 memcpy_s(pbFullFileBuffer, sizeof(UTF16BOM), UTF16BOM, sizeof(UTF16BOM));
2019 memcpy_s(pbFullFileBuffer + sizeof(UTF16BOM), cbStrLen, sczString, cbStrLen);
2020 pcbFullFileBuffer = pbFullFileBuffer;
2021 break;
2022 }
2023
2024 hr = FileWrite(wzFile, dwFlagsAndAttributes, pcbFullFileBuffer, cbFullFileBuffer, NULL);
2025 FileExitOnFailure(hr, "Failed to write file from string to: %ls", wzFile);
2026
2027LExit:
2028 ReleaseStr(sczUtf8String);
2029 ReleaseMem(pbFullFileBuffer);
2030
2031 return hr;
2032}
diff --git a/src/libs/dutil/WixToolset.DUtil/gdiputil.cpp b/src/libs/dutil/WixToolset.DUtil/gdiputil.cpp
new file mode 100644
index 00000000..b5a0087c
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/gdiputil.cpp
@@ -0,0 +1,227 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5using namespace Gdiplus;
6
7
8// Exit macros
9#define GdipExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_GDIPUTIL, x, s, __VA_ARGS__)
10#define GdipExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_GDIPUTIL, x, s, __VA_ARGS__)
11#define GdipExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_GDIPUTIL, x, s, __VA_ARGS__)
12#define GdipExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_GDIPUTIL, x, s, __VA_ARGS__)
13#define GdipExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_GDIPUTIL, x, s, __VA_ARGS__)
14#define GdipExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_GDIPUTIL, x, s, __VA_ARGS__)
15#define GdipExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_GDIPUTIL, p, x, e, s, __VA_ARGS__)
16#define GdipExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_GDIPUTIL, p, x, s, __VA_ARGS__)
17#define GdipExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_GDIPUTIL, p, x, e, s, __VA_ARGS__)
18#define GdipExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_GDIPUTIL, p, x, s, __VA_ARGS__)
19#define GdipExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_GDIPUTIL, e, x, s, __VA_ARGS__)
20#define GdipExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_GDIPUTIL, g, x, s, __VA_ARGS__)
21
22/********************************************************************
23 GdipInitialize - initializes GDI+.
24
25 Note: pOutput must be non-NULL if pInput->SuppressBackgroundThread
26 is TRUE. See GdiplusStartup() for more information.
27********************************************************************/
28extern "C" HRESULT DAPI GdipInitialize(
29 __in const Gdiplus::GdiplusStartupInput* pInput,
30 __out ULONG_PTR* pToken,
31 __out_opt Gdiplus::GdiplusStartupOutput *pOutput
32 )
33{
34 AssertSz(!pInput->SuppressBackgroundThread || pOutput, "pOutput required if background thread suppressed.");
35
36 HRESULT hr = S_OK;
37 Status status = Ok;
38
39 status = GdiplusStartup(pToken, pInput, pOutput);
40 GdipExitOnGdipFailure(status, hr, "Failed to initialize GDI+.");
41
42LExit:
43 return hr;
44}
45
46/********************************************************************
47 GdipUninitialize - uninitializes GDI+.
48
49********************************************************************/
50extern "C" void DAPI GdipUninitialize(
51 __in ULONG_PTR token
52 )
53{
54 GdiplusShutdown(token);
55}
56
57/********************************************************************
58 GdipBitmapFromResource - read a GDI+ image out of a resource stream
59
60********************************************************************/
61extern "C" HRESULT DAPI GdipBitmapFromResource(
62 __in_opt HINSTANCE hinst,
63 __in_z LPCSTR szId,
64 __out Bitmap **ppBitmap
65 )
66{
67 HRESULT hr = S_OK;
68 LPVOID pvData = NULL;
69 DWORD cbData = 0;
70 HGLOBAL hGlobal = NULL;;
71 LPVOID pv = NULL;
72 IStream *pStream = NULL;
73 Bitmap *pBitmap = NULL;
74 Status gs = Ok;
75
76 hr = ResReadData(hinst, szId, &pvData, &cbData);
77 GdipExitOnFailure(hr, "Failed to load GDI+ bitmap from resource.");
78
79 // Have to copy the fixed resource data into moveable (heap) memory
80 // since that's what GDI+ expects.
81 hGlobal = ::GlobalAlloc(GMEM_MOVEABLE, cbData);
82 GdipExitOnNullWithLastError(hGlobal, hr, "Failed to allocate global memory.");
83
84 pv = ::GlobalLock(hGlobal);
85 GdipExitOnNullWithLastError(pv, hr, "Failed to lock global memory.");
86
87 memcpy(pv, pvData, cbData);
88
89 ::GlobalUnlock(pv); // no point taking any more memory than we have already
90 pv = NULL;
91
92 hr = ::CreateStreamOnHGlobal(hGlobal, TRUE, &pStream);
93 GdipExitOnFailure(hr, "Failed to allocate stream from global memory.");
94
95 hGlobal = NULL; // we gave the global memory to the stream object so it will close it
96
97 pBitmap = Bitmap::FromStream(pStream);
98 GdipExitOnNull(pBitmap, hr, E_OUTOFMEMORY, "Failed to allocate bitmap from stream.");
99
100 gs = pBitmap->GetLastStatus();
101 GdipExitOnGdipFailure(gs, hr, "Failed to load bitmap from stream.");
102
103 *ppBitmap = pBitmap;
104 pBitmap = NULL;
105
106LExit:
107 if (pBitmap)
108 {
109 delete pBitmap;
110 }
111
112 ReleaseObject(pStream);
113
114 if (pv)
115 {
116 ::GlobalUnlock(pv);
117 }
118
119 if (hGlobal)
120 {
121 ::GlobalFree(hGlobal);
122 }
123
124 return hr;
125}
126
127
128/********************************************************************
129 GdipBitmapFromFile - read a GDI+ image from a file.
130
131********************************************************************/
132extern "C" HRESULT DAPI GdipBitmapFromFile(
133 __in_z LPCWSTR wzFileName,
134 __out Bitmap **ppBitmap
135 )
136{
137 HRESULT hr = S_OK;
138 Bitmap *pBitmap = NULL;
139 Status gs = Ok;
140
141 GdipExitOnNull(ppBitmap, hr, E_INVALIDARG, "Invalid null wzFileName");
142
143 pBitmap = Bitmap::FromFile(wzFileName);
144 GdipExitOnNull(pBitmap, hr, E_OUTOFMEMORY, "Failed to allocate bitmap from file.");
145
146 gs = pBitmap->GetLastStatus();
147 GdipExitOnGdipFailure(gs, hr, "Failed to load bitmap from file: %ls", wzFileName);
148
149 *ppBitmap = pBitmap;
150 pBitmap = NULL;
151
152LExit:
153 if (pBitmap)
154 {
155 delete pBitmap;
156 }
157
158 return hr;
159}
160
161
162HRESULT DAPI GdipHresultFromStatus(
163 __in Gdiplus::Status gs
164 )
165{
166 switch (gs)
167 {
168 case Ok:
169 return S_OK;
170
171 case GenericError:
172 return E_FAIL;
173
174 case InvalidParameter:
175 return E_INVALIDARG;
176
177 case OutOfMemory:
178 return E_OUTOFMEMORY;
179
180 case ObjectBusy:
181 return HRESULT_FROM_WIN32(ERROR_BUSY);
182
183 case InsufficientBuffer:
184 return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
185
186 case NotImplemented:
187 return E_NOTIMPL;
188
189 case Win32Error:
190 return E_FAIL;
191
192 case WrongState:
193 return E_FAIL;
194
195 case Aborted:
196 return E_ABORT;
197
198 case FileNotFound:
199 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
200
201 case ValueOverflow:
202 return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
203
204 case AccessDenied:
205 return E_ACCESSDENIED;
206
207 case UnknownImageFormat:
208 return HRESULT_FROM_WIN32(ERROR_BAD_FORMAT);
209
210 case FontFamilyNotFound: __fallthrough;
211 case FontStyleNotFound: __fallthrough;
212 case NotTrueTypeFont:
213 return E_UNEXPECTED;
214
215 case UnsupportedGdiplusVersion:
216 return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
217
218 case GdiplusNotInitialized:
219 return E_UNEXPECTED;
220
221 case PropertyNotFound: __fallthrough;
222 case PropertyNotSupported:
223 return E_FAIL;
224 }
225
226 return E_UNEXPECTED;
227}
diff --git a/src/libs/dutil/WixToolset.DUtil/guidutil.cpp b/src/libs/dutil/WixToolset.DUtil/guidutil.cpp
new file mode 100644
index 00000000..204c9af2
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/guidutil.cpp
@@ -0,0 +1,54 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define GuidExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_GUIDUTIL, x, s, __VA_ARGS__)
8#define GuidExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_GUIDUTIL, x, s, __VA_ARGS__)
9#define GuidExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_GUIDUTIL, x, s, __VA_ARGS__)
10#define GuidExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_GUIDUTIL, x, s, __VA_ARGS__)
11#define GuidExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_GUIDUTIL, x, s, __VA_ARGS__)
12#define GuidExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_GUIDUTIL, x, s, __VA_ARGS__)
13#define GuidExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_GUIDUTIL, p, x, e, s, __VA_ARGS__)
14#define GuidExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_GUIDUTIL, p, x, s, __VA_ARGS__)
15#define GuidExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_GUIDUTIL, p, x, e, s, __VA_ARGS__)
16#define GuidExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_GUIDUTIL, p, x, s, __VA_ARGS__)
17#define GuidExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_GUIDUTIL, e, x, s, __VA_ARGS__)
18#define GuidExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_GUIDUTIL, g, x, s, __VA_ARGS__)
19
20extern "C" HRESULT DAPI GuidFixedCreate(
21 _Out_z_cap_c_(GUID_STRING_LENGTH) WCHAR* wzGuid
22 )
23{
24 HRESULT hr = S_OK;
25 UUID guid = { };
26
27 hr = HRESULT_FROM_RPC(::UuidCreate(&guid));
28 GuidExitOnFailure(hr, "UuidCreate failed.");
29
30 if (!::StringFromGUID2(guid, wzGuid, GUID_STRING_LENGTH))
31 {
32 hr = E_OUTOFMEMORY;
33 GuidExitOnRootFailure(hr, "Failed to convert guid into string.");
34 }
35
36LExit:
37 return hr;
38}
39
40extern "C" HRESULT DAPI GuidCreate(
41 __deref_out_z LPWSTR* psczGuid
42 )
43{
44 HRESULT hr = S_OK;
45
46 hr = StrAlloc(psczGuid, GUID_STRING_LENGTH);
47 GuidExitOnFailure(hr, "Failed to allocate space for guid");
48
49 hr = GuidFixedCreate(*psczGuid);
50 GuidExitOnFailure(hr, "Failed to create new guid.");
51
52LExit:
53 return hr;
54}
diff --git a/src/libs/dutil/WixToolset.DUtil/iis7util.cpp b/src/libs/dutil/WixToolset.DUtil/iis7util.cpp
new file mode 100644
index 00000000..d0a0b000
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/iis7util.cpp
@@ -0,0 +1,535 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4#include "iis7util.h"
5
6
7// Exit macros
8#define IisExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_IIS7UTIL, x, s, __VA_ARGS__)
9#define IisExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_IIS7UTIL, x, s, __VA_ARGS__)
10#define IisExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_IIS7UTIL, x, s, __VA_ARGS__)
11#define IisExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_IIS7UTIL, x, s, __VA_ARGS__)
12#define IisExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_IIS7UTIL, x, s, __VA_ARGS__)
13#define IisExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_IIS7UTIL, x, s, __VA_ARGS__)
14#define IisExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_IIS7UTIL, p, x, e, s, __VA_ARGS__)
15#define IisExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_IIS7UTIL, p, x, s, __VA_ARGS__)
16#define IisExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_IIS7UTIL, p, x, e, s, __VA_ARGS__)
17#define IisExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_IIS7UTIL, p, x, s, __VA_ARGS__)
18#define IisExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_IIS7UTIL, e, x, s, __VA_ARGS__)
19#define IisExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_IIS7UTIL, g, x, s, __VA_ARGS__)
20
21#define ISSTRINGVARIANT(vt) (VT_BSTR == vt || VT_LPWSTR == vt)
22
23extern "C" HRESULT DAPI Iis7PutPropertyVariant(
24 __in IAppHostElement *pElement,
25 __in LPCWSTR wzPropName,
26 __in VARIANT vtPut
27 )
28{
29 HRESULT hr = S_OK;
30 IAppHostProperty *pProperty = NULL;
31 BSTR bstrPropName = NULL;
32
33 bstrPropName = ::SysAllocString(wzPropName);
34 IisExitOnNull(bstrPropName, hr, E_OUTOFMEMORY, "failed SysAllocString");
35
36 hr = pElement->GetPropertyByName(bstrPropName, &pProperty);
37 IisExitOnFailure(hr, "Failed to get property object for %ls", wzPropName);
38
39 hr = pProperty->put_Value(vtPut);
40 IisExitOnFailure(hr, "Failed to set property value for %ls", wzPropName);
41
42LExit:
43 ReleaseBSTR(bstrPropName);
44 // caller responsible for cleaning up variant vtPut
45 ReleaseObject(pProperty);
46
47 return hr;
48}
49
50extern "C" HRESULT DAPI Iis7PutPropertyString(
51 __in IAppHostElement *pElement,
52 __in LPCWSTR wzPropName,
53 __in LPCWSTR wzString
54 )
55{
56 HRESULT hr = S_OK;
57 VARIANT vtPut;
58
59 ::VariantInit(&vtPut);
60 vtPut.vt = VT_BSTR;
61 vtPut.bstrVal = ::SysAllocString(wzString);
62 IisExitOnNull(vtPut.bstrVal, hr, E_OUTOFMEMORY, "failed SysAllocString");
63
64 hr = Iis7PutPropertyVariant(pElement, wzPropName, vtPut);
65
66LExit:
67 ReleaseVariant(vtPut);
68
69 return hr;
70}
71
72extern "C" HRESULT DAPI Iis7PutPropertyInteger(
73 __in IAppHostElement *pElement,
74 __in LPCWSTR wzPropName,
75 __in DWORD dValue
76 )
77{
78 VARIANT vtPut;
79
80 ::VariantInit(&vtPut);
81 vtPut.vt = VT_I4;
82 vtPut.lVal = dValue;
83 return Iis7PutPropertyVariant(pElement, wzPropName, vtPut);
84}
85
86extern "C" HRESULT DAPI Iis7PutPropertyBool(
87 __in IAppHostElement *pElement,
88 __in LPCWSTR wzPropName,
89 __in BOOL fValue)
90{
91 VARIANT vtPut;
92
93 ::VariantInit(&vtPut);
94 vtPut.vt = VT_BOOL;
95 vtPut.boolVal = (fValue == FALSE) ? VARIANT_FALSE : VARIANT_TRUE;
96 return Iis7PutPropertyVariant(pElement, wzPropName, vtPut);
97}
98
99extern "C" HRESULT DAPI Iis7GetPropertyVariant(
100 __in IAppHostElement *pElement,
101 __in LPCWSTR wzPropName,
102 __in VARIANT* vtGet
103 )
104{
105 HRESULT hr = S_OK;
106 IAppHostProperty *pProperty = NULL;
107 BSTR bstrPropName = NULL;
108
109 bstrPropName = ::SysAllocString(wzPropName);
110 IisExitOnNull(bstrPropName, hr, E_OUTOFMEMORY, "failed SysAllocString");
111
112 hr = pElement->GetPropertyByName(bstrPropName, &pProperty);
113 IisExitOnFailure(hr, "Failed to get property object for %ls", wzPropName);
114
115 hr = pProperty->get_Value(vtGet);
116 IisExitOnFailure(hr, "Failed to get property value for %ls", wzPropName);
117
118LExit:
119 ReleaseBSTR(bstrPropName);
120 // caller responsible for cleaning up variant vtGet
121 ReleaseObject(pProperty);
122
123 return hr;
124}
125
126extern "C" HRESULT DAPI Iis7GetPropertyString(
127 __in IAppHostElement *pElement,
128 __in LPCWSTR wzPropName,
129 __in LPWSTR* psczGet
130 )
131{
132 HRESULT hr = S_OK;
133 VARIANT vtGet;
134
135 ::VariantInit(&vtGet);
136 hr = Iis7GetPropertyVariant(pElement, wzPropName, &vtGet);
137 IisExitOnFailure(hr, "Failed to get iis7 property variant with name: %ls", wzPropName);
138
139 if (!ISSTRINGVARIANT(vtGet.vt))
140 {
141 hr = E_UNEXPECTED;
142 IisExitOnFailure(hr, "Tried to get property as a string, but type was %d instead.", vtGet.vt);
143 }
144
145 hr = StrAllocString(psczGet, vtGet.bstrVal, 0);
146
147LExit:
148 ReleaseVariant(vtGet);
149
150 return hr;
151}
152
153BOOL DAPI CompareVariantDefault(
154 __in VARIANT* pVariant1,
155 __in VARIANT* pVariant2
156 )
157{
158 BOOL fEqual = FALSE;
159
160 switch (pVariant1->vt)
161 {
162 // VarCmp doesn't work for unsigned ints
163 // We'd like to allow signed/unsigned comparison as well since
164 // IIS doesn't document variant type for integer fields
165 case VT_I1:
166 case VT_UI1:
167 if (VT_I1 == pVariant2->vt || VT_UI1 == pVariant2->vt)
168 {
169 fEqual = pVariant1->bVal == pVariant2->bVal;
170 }
171 break;
172 case VT_I2:
173 case VT_UI2:
174 if (VT_I2 == pVariant2->vt || VT_UI2 == pVariant2->vt)
175 {
176 fEqual = pVariant1->uiVal == pVariant2->uiVal;
177 }
178 break;
179 case VT_UI4:
180 case VT_I4:
181 if (VT_I4 == pVariant2->vt || VT_UI4 == pVariant2->vt)
182 {
183 fEqual = pVariant1->ulVal == pVariant2->ulVal;
184 }
185 break;
186 case VT_UI8:
187 case VT_I8:
188 if (VT_I8 == pVariant2->vt || VT_UI8 == pVariant2->vt)
189 {
190 fEqual = pVariant1->ullVal == pVariant2->ullVal;
191 }
192 break;
193 default:
194 fEqual = VARCMP_EQ == ::VarCmp(pVariant1,
195 pVariant2,
196 LOCALE_INVARIANT,
197 NORM_IGNORECASE);
198 }
199
200 return fEqual;
201}
202
203BOOL DAPI CompareVariantPath(
204 __in VARIANT* pVariant1,
205 __in VARIANT* pVariant2
206 )
207{
208 HRESULT hr = S_OK;
209 BOOL fEqual = FALSE;
210 LPWSTR wzValue1 = NULL;
211 LPWSTR wzValue2 = NULL;
212
213 if (ISSTRINGVARIANT(pVariant1->vt))
214 {
215 hr = PathExpand(&wzValue1, pVariant1->bstrVal, PATH_EXPAND_ENVIRONMENT | PATH_EXPAND_FULLPATH);
216 IisExitOnFailure(hr, "Failed to expand path %ls", pVariant1->bstrVal);
217 }
218
219 if (ISSTRINGVARIANT(pVariant2->vt))
220 {
221 hr = PathExpand(&wzValue2, pVariant2->bstrVal, PATH_EXPAND_ENVIRONMENT | PATH_EXPAND_FULLPATH);
222 IisExitOnFailure(hr, "Failed to expand path %ls", pVariant2->bstrVal);
223 }
224
225 fEqual = CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, wzValue1, -1, wzValue2, -1);
226
227LExit:
228 ReleaseNullStr(wzValue1);
229 ReleaseNullStr(wzValue2);
230 return fEqual;
231}
232
233BOOL DAPI IsMatchingAppHostElementCallback(
234 __in IAppHostElement *pElement,
235 __in_bcount(sizeof(IIS7_APPHOSTELEMENTCOMPARISON)) LPVOID pContext
236 )
237{
238 IIS7_APPHOSTELEMENTCOMPARISON* pComparison = (IIS7_APPHOSTELEMENTCOMPARISON*) pContext;
239
240 return Iis7IsMatchingAppHostElement(pElement, pComparison);
241}
242
243extern "C" BOOL DAPI Iis7IsMatchingAppHostElement(
244 __in IAppHostElement *pElement,
245 __in IIS7_APPHOSTELEMENTCOMPARISON* pComparison
246 )
247{
248 HRESULT hr = S_OK;
249 BOOL fResult = FALSE;
250 IAppHostProperty *pProperty = NULL;
251 BSTR bstrElementName = NULL;
252
253 VARIANT vPropValue;
254 ::VariantInit(&vPropValue);
255
256 // Use the default comparator if a comparator is not specified
257 VARIANTCOMPARATORPROC pComparator = pComparison->pComparator ? pComparison->pComparator : CompareVariantDefault;
258
259 hr = pElement->get_Name(&bstrElementName);
260 IisExitOnFailure(hr, "Failed to get name of element");
261 if (CSTR_EQUAL != ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, pComparison->sczElementName, -1, bstrElementName, -1))
262 {
263 ExitFunction();
264 }
265
266 hr = Iis7GetPropertyVariant(pElement, pComparison->sczAttributeName, &vPropValue);
267 IisExitOnFailure(hr, "Failed to get value of %ls attribute of %ls element", pComparison->sczAttributeName, pComparison->sczElementName);
268
269 if (TRUE == pComparator(pComparison->pvAttributeValue, &vPropValue))
270 {
271 fResult = TRUE;
272 }
273
274LExit:
275 ReleaseBSTR(bstrElementName);
276 ReleaseVariant(vPropValue);
277 ReleaseObject(pProperty);
278
279 return fResult;
280}
281
282BOOL DAPI IsMatchingAppHostMethod(
283 __in IAppHostMethod *pMethod,
284 __in LPCWSTR wzMethodName
285 )
286{
287 HRESULT hr = S_OK;
288 BOOL fResult = FALSE;
289 BSTR bstrName = NULL;
290
291 hr = pMethod->get_Name(&bstrName);
292 IisExitOnFailure(hr, "Failed to get name of element");
293
294 Assert(bstrName);
295
296 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, wzMethodName, -1, bstrName, -1))
297 {
298 fResult = TRUE;
299 }
300
301LExit:
302 ReleaseBSTR(bstrName);
303
304 return fResult;
305}
306
307extern "C" HRESULT DAPI Iis7FindAppHostElementPath(
308 __in IAppHostElementCollection *pCollection,
309 __in LPCWSTR wzElementName,
310 __in LPCWSTR wzAttributeName,
311 __in LPCWSTR wzAttributeValue,
312 __out IAppHostElement** ppElement,
313 __out DWORD* pdwIndex
314 )
315{
316 HRESULT hr = S_OK;
317 IIS7_APPHOSTELEMENTCOMPARISON comparison = { };
318 VARIANT vtValue = { };
319 ::VariantInit(&vtValue);
320
321 vtValue.vt = VT_BSTR;
322 vtValue.bstrVal = ::SysAllocString(wzAttributeValue);
323 IisExitOnNull(vtValue.bstrVal, hr, E_OUTOFMEMORY, "failed SysAllocString");
324
325 comparison.sczElementName = wzElementName;
326 comparison.sczAttributeName = wzAttributeName;
327 comparison.pvAttributeValue = &vtValue;
328 comparison.pComparator = CompareVariantPath;
329
330 hr = Iis7EnumAppHostElements(pCollection,
331 IsMatchingAppHostElementCallback,
332 &comparison,
333 ppElement,
334 pdwIndex);
335
336LExit:
337 ReleaseVariant(vtValue);
338
339 return hr;
340}
341
342extern "C" HRESULT DAPI Iis7FindAppHostElementString(
343 __in IAppHostElementCollection *pCollection,
344 __in LPCWSTR wzElementName,
345 __in LPCWSTR wzAttributeName,
346 __in LPCWSTR wzAttributeValue,
347 __out IAppHostElement** ppElement,
348 __out DWORD* pdwIndex
349 )
350{
351 HRESULT hr = S_OK;
352 VARIANT vtValue;
353 ::VariantInit(&vtValue);
354
355 vtValue.vt = VT_BSTR;
356 vtValue.bstrVal = ::SysAllocString(wzAttributeValue);
357 IisExitOnNull(vtValue.bstrVal, hr, E_OUTOFMEMORY, "failed SysAllocString");
358
359 hr = Iis7FindAppHostElementVariant(pCollection,
360 wzElementName,
361 wzAttributeName,
362 &vtValue,
363 ppElement,
364 pdwIndex);
365
366LExit:
367 ReleaseVariant(vtValue);
368
369 return hr;
370}
371
372extern "C" HRESULT DAPI Iis7FindAppHostElementInteger(
373 __in IAppHostElementCollection *pCollection,
374 __in LPCWSTR wzElementName,
375 __in LPCWSTR wzAttributeName,
376 __in DWORD dwAttributeValue,
377 __out IAppHostElement** ppElement,
378 __out DWORD* pdwIndex
379 )
380{
381 HRESULT hr = S_OK;
382 VARIANT vtValue;
383 ::VariantInit(&vtValue);
384
385 vtValue.vt = VT_UI4;
386 vtValue.ulVal = dwAttributeValue;
387
388 hr = Iis7FindAppHostElementVariant(pCollection,
389 wzElementName,
390 wzAttributeName,
391 &vtValue,
392 ppElement,
393 pdwIndex);
394
395 ReleaseVariant(vtValue);
396
397 return hr;
398}
399
400extern "C" HRESULT DAPI Iis7FindAppHostElementVariant(
401 __in IAppHostElementCollection *pCollection,
402 __in LPCWSTR wzElementName,
403 __in LPCWSTR wzAttributeName,
404 __in VARIANT* pvAttributeValue,
405 __out IAppHostElement** ppElement,
406 __out DWORD* pdwIndex
407 )
408{
409 IIS7_APPHOSTELEMENTCOMPARISON comparison = { };
410 comparison.sczElementName = wzElementName;
411 comparison.sczAttributeName = wzAttributeName;
412 comparison.pvAttributeValue = pvAttributeValue;
413 comparison.pComparator = CompareVariantDefault;
414
415 return Iis7EnumAppHostElements(pCollection,
416 IsMatchingAppHostElementCallback,
417 &comparison,
418 ppElement,
419 pdwIndex);
420}
421
422extern "C" HRESULT DAPI Iis7EnumAppHostElements(
423 __in IAppHostElementCollection *pCollection,
424 __in ENUMAPHOSTELEMENTPROC pCallback,
425 __in LPVOID pContext,
426 __out IAppHostElement** ppElement,
427 __out DWORD* pdwIndex
428 )
429{
430 HRESULT hr = S_OK;
431 IAppHostElement *pElement = NULL;
432 DWORD dwElements = 0;
433
434 VARIANT vtIndex;
435 ::VariantInit(&vtIndex);
436
437 if (NULL != ppElement)
438 {
439 *ppElement = NULL;
440 }
441 if (NULL != pdwIndex)
442 {
443 *pdwIndex = MAXDWORD;
444 }
445
446 hr = pCollection->get_Count(&dwElements);
447 IisExitOnFailure(hr, "Failed get application IAppHostElementCollection count");
448
449 vtIndex.vt = VT_UI4;
450 for (DWORD i = 0; i < dwElements; ++i)
451 {
452 vtIndex.ulVal = i;
453 hr = pCollection->get_Item(vtIndex , &pElement);
454 IisExitOnFailure(hr, "Failed get IAppHostElement element");
455
456 if (pCallback(pElement, pContext))
457 {
458 if (NULL != ppElement)
459 {
460 *ppElement = pElement;
461 pElement = NULL;
462 }
463 if (NULL != pdwIndex)
464 {
465 *pdwIndex = i;
466 }
467 break;
468 }
469
470 ReleaseNullObject(pElement);
471 }
472
473LExit:
474 ReleaseObject(pElement);
475 ReleaseVariant(vtIndex);
476
477 return hr;
478}
479
480extern "C" HRESULT DAPI Iis7FindAppHostMethod(
481 __in IAppHostMethodCollection *pCollection,
482 __in LPCWSTR wzMethodName,
483 __out IAppHostMethod** ppMethod,
484 __out DWORD* pdwIndex
485 )
486{
487 HRESULT hr = S_OK;
488 IAppHostMethod *pMethod = NULL;
489 DWORD dwMethods = 0;
490
491 VARIANT vtIndex;
492 ::VariantInit(&vtIndex);
493
494 if (NULL != ppMethod)
495 {
496 *ppMethod = NULL;
497 }
498 if (NULL != pdwIndex)
499 {
500 *pdwIndex = MAXDWORD;
501 }
502
503 hr = pCollection->get_Count(&dwMethods);
504 IisExitOnFailure(hr, "Failed get application IAppHostMethodCollection count");
505
506 vtIndex.vt = VT_UI4;
507 for (DWORD i = 0; i < dwMethods; ++i)
508 {
509 vtIndex.ulVal = i;
510 hr = pCollection->get_Item(vtIndex , &pMethod);
511 IisExitOnFailure(hr, "Failed get IAppHostMethod element");
512
513 if (IsMatchingAppHostMethod(pMethod, wzMethodName))
514 {
515 if (NULL != ppMethod)
516 {
517 *ppMethod = pMethod;
518 pMethod = NULL;
519 }
520 if (NULL != pdwIndex)
521 {
522 *pdwIndex = i;
523 }
524 break;
525 }
526
527 ReleaseNullObject(pMethod);
528 }
529
530LExit:
531 ReleaseObject(pMethod);
532 ReleaseVariant(vtIndex);
533
534 return hr;
535}
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/aclutil.h b/src/libs/dutil/WixToolset.DUtil/inc/aclutil.h
new file mode 100644
index 00000000..ac03f9a8
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/aclutil.h
@@ -0,0 +1,154 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#include <aclapi.h>
6#include <sddl.h>
7
8#define ReleaseSid(x) if (x) { AclFreeSid(x); }
9#define ReleaseNullSid(x) if (x) { AclFreeSid(x); x = NULL; }
10
11#ifdef __cplusplus
12extern "C" {
13#endif
14
15// structs
16struct ACL_ACCESS
17{
18 BOOL fDenyAccess;
19 DWORD dwAccessMask;
20
21 // TODO: consider using a union
22 LPCWSTR pwzAccountName; // NOTE: the last three items in this structure are ignored if this is not NULL
23
24 SID_IDENTIFIER_AUTHORITY sia; // used if pwzAccountName is NULL
25 BYTE nSubAuthorityCount;
26 DWORD nSubAuthority[8];
27};
28
29struct ACL_ACE
30{
31 DWORD dwFlags;
32 DWORD dwMask;
33 PSID psid;
34};
35
36
37// functions
38HRESULT DAPI AclCheckAccess(
39 __in HANDLE hToken,
40 __in ACL_ACCESS* paa
41 );
42HRESULT DAPI AclCheckAdministratorAccess(
43 __in HANDLE hToken
44 );
45HRESULT DAPI AclCheckLocalSystemAccess(
46 __in HANDLE hToken
47 );
48
49HRESULT DAPI AclGetWellKnownSid(
50 __in WELL_KNOWN_SID_TYPE wkst,
51 __deref_out PSID* ppsid
52 );
53HRESULT DAPI AclGetAccountSid(
54 __in_opt LPCWSTR wzSystem,
55 __in_z LPCWSTR wzAccount,
56 __deref_out PSID* ppsid
57 );
58HRESULT DAPI AclGetAccountSidString(
59 __in_z LPCWSTR wzSystem,
60 __in_z LPCWSTR wzAccount,
61 __deref_out_z LPWSTR* ppwzSid
62 );
63
64HRESULT DAPI AclCreateDacl(
65 __in_ecount(cDeny) ACL_ACE rgaaDeny[],
66 __in DWORD cDeny,
67 __in_ecount(cAllow) ACL_ACE rgaaAllow[],
68 __in DWORD cAllow,
69 __deref_out ACL** ppAcl
70 );
71HRESULT DAPI AclAddToDacl(
72 __in ACL* pAcl,
73 __in_ecount_opt(cDeny) const ACL_ACE rgaaDeny[],
74 __in DWORD cDeny,
75 __in_ecount_opt(cAllow) const ACL_ACE rgaaAllow[],
76 __in DWORD cAllow,
77 __deref_out ACL** ppAclNew
78 );
79HRESULT DAPI AclMergeDacls(
80 __in const ACL* pAcl1,
81 __in const ACL* pAcl2,
82 __deref_out ACL** ppAclNew
83 );
84HRESULT DAPI AclCreateDaclOld(
85 __in_ecount(cAclAccesses) ACL_ACCESS* paa,
86 __in DWORD cAclAccesses,
87 __deref_out ACL** ppAcl
88 );
89HRESULT DAPI AclCreateSecurityDescriptor(
90 __in_ecount(cAclAccesses) ACL_ACCESS* paa,
91 __in DWORD cAclAccesses,
92 __deref_out SECURITY_DESCRIPTOR** ppsd
93 );
94HRESULT DAPI AclCreateSecurityDescriptorFromDacl(
95 __in ACL* pACL,
96 __deref_out SECURITY_DESCRIPTOR** ppsd
97 );
98HRESULT __cdecl AclCreateSecurityDescriptorFromString(
99 __deref_out SECURITY_DESCRIPTOR** ppsd,
100 __in_z __format_string LPCWSTR wzSddlFormat,
101 ...
102 );
103HRESULT DAPI AclDuplicateSecurityDescriptor(
104 __in SECURITY_DESCRIPTOR* psd,
105 __deref_out SECURITY_DESCRIPTOR** ppsd
106 );
107HRESULT DAPI AclGetSecurityDescriptor(
108 __in_z LPCWSTR wzObject,
109 __in SE_OBJECT_TYPE sot,
110 __in SECURITY_INFORMATION securityInformation,
111 __deref_out SECURITY_DESCRIPTOR** ppsd
112 );
113HRESULT DAPI AclSetSecurityWithRetry(
114 __in_z LPCWSTR wzObject,
115 __in SE_OBJECT_TYPE sot,
116 __in SECURITY_INFORMATION securityInformation,
117 __in_opt PSID psidOwner,
118 __in_opt PSID psidGroup,
119 __in_opt PACL pDacl,
120 __in_opt PACL pSacl,
121 __in DWORD cRetry,
122 __in DWORD dwWaitMilliseconds
123 );
124
125HRESULT DAPI AclFreeSid(
126 __in PSID psid
127 );
128HRESULT DAPI AclFreeDacl(
129 __in ACL* pACL
130 );
131HRESULT DAPI AclFreeSecurityDescriptor(
132 __in SECURITY_DESCRIPTOR* psd
133 );
134
135HRESULT DAPI AclAddAdminToSecurityDescriptor(
136 __in SECURITY_DESCRIPTOR* pSecurity,
137 __deref_out SECURITY_DESCRIPTOR** ppSecurityNew
138 );
139
140// Following code in acl2util.cpp due to dependency on crypt32.dll.
141HRESULT DAPI AclCalculateServiceSidString(
142 __in LPCWSTR wzServiceName,
143 __in SIZE_T cchServiceName,
144 __deref_out_z LPWSTR* psczSid
145 );
146HRESULT DAPI AclGetAccountSidStringEx(
147 __in_z LPCWSTR wzSystem,
148 __in_z LPCWSTR wzAccount,
149 __deref_out_z LPWSTR* psczSid
150 );
151
152#ifdef __cplusplus
153}
154#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/apputil.h b/src/libs/dutil/WixToolset.DUtil/inc/apputil.h
new file mode 100644
index 00000000..1a1e14f7
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/apputil.h
@@ -0,0 +1,45 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9// functions
10
11/********************************************************************
12AppFreeCommandLineArgs - frees argv from AppParseCommandLine.
13
14********************************************************************/
15void DAPI AppFreeCommandLineArgs(
16 __in LPWSTR* argv
17 );
18
19void DAPI AppInitialize(
20 __in_ecount(cSafelyLoadSystemDlls) LPCWSTR rgsczSafelyLoadSystemDlls[],
21 __in DWORD cSafelyLoadSystemDlls
22 );
23
24/********************************************************************
25AppInitializeUnsafe - initializes without the full standard safety
26 precautions for an application.
27
28********************************************************************/
29void DAPI AppInitializeUnsafe();
30
31/********************************************************************
32AppParseCommandLine - parses the command line using CommandLineToArgvW.
33 The caller must free the value of pArgv on success
34 by calling AppFreeCommandLineArgs.
35
36********************************************************************/
37DAPI_(HRESULT) AppParseCommandLine(
38 __in LPCWSTR wzCommandLine,
39 __in int* argc,
40 __in LPWSTR** pArgv
41 );
42
43#ifdef __cplusplus
44}
45#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/apuputil.h b/src/libs/dutil/WixToolset.DUtil/inc/apuputil.h
new file mode 100644
index 00000000..f26a12b7
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/apuputil.h
@@ -0,0 +1,87 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9#define ReleaseApupChain(p) if (p) { ApupFreeChain(p); p = NULL; }
10#define ReleaseNullApupChain(p) if (p) { ApupFreeChain(p); p = NULL; }
11
12
13const LPCWSTR APPLICATION_SYNDICATION_NAMESPACE = L"http://appsyndication.org/2006/appsyn";
14
15typedef enum APUP_HASH_ALGORITHM
16{
17 APUP_HASH_ALGORITHM_UNKNOWN,
18 APUP_HASH_ALGORITHM_MD5,
19 APUP_HASH_ALGORITHM_SHA1,
20 APUP_HASH_ALGORITHM_SHA256,
21 APUP_HASH_ALGORITHM_SHA512,
22} APUP_HASH_ALGORITHM;
23
24
25struct APPLICATION_UPDATE_ENCLOSURE
26{
27 LPWSTR wzUrl;
28 LPWSTR wzLocalName;
29 DWORD64 dw64Size;
30
31 BYTE* rgbDigest;
32 DWORD cbDigest;
33 APUP_HASH_ALGORITHM digestAlgorithm;
34
35 BOOL fInstaller;
36};
37
38
39struct APPLICATION_UPDATE_ENTRY
40{
41 LPWSTR wzApplicationId;
42 LPWSTR wzApplicationType;
43 LPWSTR wzTitle;
44 LPWSTR wzSummary;
45 LPWSTR wzContentType;
46 LPWSTR wzContent;
47
48 LPWSTR wzUpgradeId;
49 BOOL fUpgradeExclusive;
50 VERUTIL_VERSION* pVersion;
51 VERUTIL_VERSION* pUpgradeVersion;
52
53 DWORD64 dw64TotalSize;
54
55 DWORD cEnclosures;
56 APPLICATION_UPDATE_ENCLOSURE* rgEnclosures;
57};
58
59
60struct APPLICATION_UPDATE_CHAIN
61{
62 LPWSTR wzDefaultApplicationId;
63 LPWSTR wzDefaultApplicationType;
64
65 DWORD cEntries;
66 APPLICATION_UPDATE_ENTRY* rgEntries;
67};
68
69
70HRESULT DAPI ApupAllocChainFromAtom(
71 __in ATOM_FEED* pFeed,
72 __out APPLICATION_UPDATE_CHAIN** ppChain
73 );
74
75HRESULT DAPI ApupFilterChain(
76 __in APPLICATION_UPDATE_CHAIN* pChain,
77 __in VERUTIL_VERSION* pVersion,
78 __out APPLICATION_UPDATE_CHAIN** ppFilteredChain
79 );
80
81void DAPI ApupFreeChain(
82 __in APPLICATION_UPDATE_CHAIN* pChain
83 );
84
85#ifdef __cplusplus
86}
87#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/atomutil.h b/src/libs/dutil/WixToolset.DUtil/inc/atomutil.h
new file mode 100644
index 00000000..9acfc1d5
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/atomutil.h
@@ -0,0 +1,146 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9#define ReleaseAtomFeed(p) if (p) { AtomFreeFeed(p); }
10#define ReleaseNullAtomFeed(p) if (p) { AtomFreeFeed(p); p = NULL; }
11
12
13struct ATOM_UNKNOWN_ATTRIBUTE
14{
15 LPWSTR wzNamespace;
16 LPWSTR wzAttribute;
17 LPWSTR wzValue;
18
19 ATOM_UNKNOWN_ATTRIBUTE* pNext;
20};
21
22struct ATOM_UNKNOWN_ELEMENT
23{
24 LPWSTR wzNamespace;
25 LPWSTR wzElement;
26 LPWSTR wzValue;
27
28 ATOM_UNKNOWN_ATTRIBUTE* pAttributes;
29 ATOM_UNKNOWN_ELEMENT* pNext;
30};
31
32struct ATOM_LINK
33{
34 LPWSTR wzRel;
35 LPWSTR wzTitle;
36 LPWSTR wzType;
37 LPWSTR wzUrl;
38 LPWSTR wzValue;
39 DWORD64 dw64Length;
40
41 ATOM_UNKNOWN_ATTRIBUTE* pUnknownAttributes;
42 ATOM_UNKNOWN_ELEMENT* pUnknownElements;
43};
44
45struct ATOM_CONTENT
46{
47 LPWSTR wzType;
48 LPWSTR wzUrl;
49 LPWSTR wzValue;
50
51 ATOM_UNKNOWN_ELEMENT* pUnknownElements;
52};
53
54struct ATOM_AUTHOR
55{
56 LPWSTR wzName;
57 LPWSTR wzEmail;
58 LPWSTR wzUrl;
59};
60
61struct ATOM_CATEGORY
62{
63 LPWSTR wzLabel;
64 LPWSTR wzScheme;
65 LPWSTR wzTerm;
66
67 ATOM_UNKNOWN_ELEMENT* pUnknownElements;
68};
69
70struct ATOM_ENTRY
71{
72 LPWSTR wzId;
73 LPWSTR wzSummary;
74 LPWSTR wzTitle;
75 FILETIME ftPublished;
76 FILETIME ftUpdated;
77
78 ATOM_CONTENT* pContent;
79
80 DWORD cAuthors;
81 ATOM_AUTHOR* rgAuthors;
82
83 DWORD cCategories;
84 ATOM_CATEGORY* rgCategories;
85
86 DWORD cLinks;
87 ATOM_LINK* rgLinks;
88
89 IXMLDOMNode* pixn;
90 ATOM_UNKNOWN_ELEMENT* pUnknownElements;
91};
92
93struct ATOM_FEED
94{
95 LPWSTR wzGenerator;
96 LPWSTR wzIcon;
97 LPWSTR wzId;
98 LPWSTR wzLogo;
99 LPWSTR wzSubtitle;
100 LPWSTR wzTitle;
101 FILETIME ftUpdated;
102
103 DWORD cAuthors;
104 ATOM_AUTHOR* rgAuthors;
105
106 DWORD cCategories;
107 ATOM_CATEGORY* rgCategories;
108
109 DWORD cEntries;
110 ATOM_ENTRY* rgEntries;
111
112 DWORD cLinks;
113 ATOM_LINK* rgLinks;
114
115 IXMLDOMNode* pixn;
116 ATOM_UNKNOWN_ELEMENT* pUnknownElements;
117};
118
119HRESULT DAPI AtomInitialize(
120 );
121
122void DAPI AtomUninitialize(
123 );
124
125HRESULT DAPI AtomParseFromString(
126 __in_z LPCWSTR wzAtomString,
127 __out ATOM_FEED **ppFeed
128 );
129
130HRESULT DAPI AtomParseFromFile(
131 __in_z LPCWSTR wzAtomFile,
132 __out ATOM_FEED **ppFeed
133 );
134
135HRESULT DAPI AtomParseFromDocument(
136 __in IXMLDOMDocument* pixdDocument,
137 __out ATOM_FEED **ppFeed
138 );
139
140void DAPI AtomFreeFeed(
141 __in_xcount(pFeed->cItems) ATOM_FEED* pFeed
142 );
143
144#ifdef __cplusplus
145}
146#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/buffutil.h b/src/libs/dutil/WixToolset.DUtil/inc/buffutil.h
new file mode 100644
index 00000000..322209e6
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/buffutil.h
@@ -0,0 +1,91 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9
10// macro definitions
11
12#define ReleaseBuffer ReleaseMem
13#define ReleaseNullBuffer ReleaseNullMem
14#define BuffFree MemFree
15
16
17// function declarations
18
19HRESULT BuffReadNumber(
20 __in_bcount(cbBuffer) const BYTE* pbBuffer,
21 __in SIZE_T cbBuffer,
22 __inout SIZE_T* piBuffer,
23 __out DWORD* pdw
24 );
25HRESULT BuffReadNumber64(
26 __in_bcount(cbBuffer) const BYTE* pbBuffer,
27 __in SIZE_T cbBuffer,
28 __inout SIZE_T* piBuffer,
29 __out DWORD64* pdw64
30 );
31HRESULT BuffReadPointer(
32 __in_bcount(cbBuffer) const BYTE* pbBuffer,
33 __in SIZE_T cbBuffer,
34 __inout SIZE_T* piBuffer,
35 __out DWORD_PTR* pdw
36);
37HRESULT BuffReadString(
38 __in_bcount(cbBuffer) const BYTE* pbBuffer,
39 __in SIZE_T cbBuffer,
40 __inout SIZE_T* piBuffer,
41 __deref_out_z LPWSTR* pscz
42 );
43HRESULT BuffReadStringAnsi(
44 __in_bcount(cbBuffer) const BYTE* pbBuffer,
45 __in SIZE_T cbBuffer,
46 __inout SIZE_T* piBuffer,
47 __deref_out_z LPSTR* pscz
48 );
49HRESULT BuffReadStream(
50 __in_bcount(cbBuffer) const BYTE* pbBuffer,
51 __in SIZE_T cbBuffer,
52 __inout SIZE_T* piBuffer,
53 __deref_inout_bcount(*pcbStream) BYTE** ppbStream,
54 __out SIZE_T* pcbStream
55 );
56
57HRESULT BuffWriteNumber(
58 __deref_inout_bcount(*piBuffer) BYTE** ppbBuffer,
59 __inout SIZE_T* piBuffer,
60 __in DWORD dw
61 );
62HRESULT BuffWriteNumber64(
63 __deref_inout_bcount(*piBuffer) BYTE** ppbBuffer,
64 __inout SIZE_T* piBuffer,
65 __in DWORD64 dw64
66 );
67HRESULT BuffWritePointer(
68 __deref_inout_bcount(*piBuffer) BYTE** ppbBuffer,
69 __inout SIZE_T* piBuffer,
70 __in DWORD_PTR dw
71);
72HRESULT BuffWriteString(
73 __deref_inout_bcount(*piBuffer) BYTE** ppbBuffer,
74 __inout SIZE_T* piBuffer,
75 __in_z_opt LPCWSTR scz
76 );
77HRESULT BuffWriteStringAnsi(
78 __deref_inout_bcount(*piBuffer) BYTE** ppbBuffer,
79 __inout SIZE_T* piBuffer,
80 __in_z_opt LPCSTR scz
81 );
82HRESULT BuffWriteStream(
83 __deref_inout_bcount(*piBuffer) BYTE** ppbBuffer,
84 __inout SIZE_T* piBuffer,
85 __in_bcount(cbStream) const BYTE* pbStream,
86 __in SIZE_T cbStream
87 );
88
89#ifdef __cplusplus
90}
91#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/butil.h b/src/libs/dutil/WixToolset.DUtil/inc/butil.h
new file mode 100644
index 00000000..d1ec73bc
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/butil.h
@@ -0,0 +1,60 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9enum BUNDLE_INSTALL_CONTEXT
10{
11 BUNDLE_INSTALL_CONTEXT_MACHINE,
12 BUNDLE_INSTALL_CONTEXT_USER,
13};
14
15
16/********************************************************************
17BundleGetBundleInfo - Queries the bundle installation metadata for a given property
18
19RETURNS:
20 E_INVALIDARG
21 An invalid parameter was passed to the function.
22 HRESULT_FROM_WIN32(ERROR_UNKNOWN_PRODUCT)
23 The bundle is not installed
24 HRESULT_FROM_WIN32(ERROR_UNKNOWN_PROPERTY)
25 The property is unrecognized
26 HRESULT_FROM_WIN32(ERROR_MORE_DATA)
27 A buffer is too small to hold the requested data.
28 E_NOTIMPL:
29 Tried to read a bundle attribute for a type which has not been implemented
30
31 All other returns are unexpected returns from other dutil methods.
32********************************************************************/
33HRESULT DAPI BundleGetBundleInfo(
34 __in_z LPCWSTR szBundleId, // Bundle code
35 __in_z LPCWSTR szAttribute, // attribute name
36 __out_ecount_opt(*pcchValueBuf) LPWSTR lpValueBuf, // returned value, NULL if not desired
37 __inout_opt LPDWORD pcchValueBuf // in/out buffer character count
38 );
39
40/********************************************************************
41BundleEnumRelatedBundle - Queries the bundle installation metadata for installs with the given upgrade code
42
43NOTE: lpBundleIdBuff is a buffer to receive the bundle GUID. This buffer must be 39 characters long.
44 The first 38 characters are for the GUID, and the last character is for the terminating null character.
45RETURNS:
46 E_INVALIDARG
47 An invalid parameter was passed to the function.
48
49 All other returns are unexpected returns from other dutil methods.
50********************************************************************/
51HRESULT DAPI BundleEnumRelatedBundle(
52 __in_z LPCWSTR lpUpgradeCode,
53 __in BUNDLE_INSTALL_CONTEXT context,
54 __inout PDWORD pdwStartIndex,
55 __out_ecount(MAX_GUID_CHARS+1) LPWSTR lpBundleIdBuf
56 );
57
58#ifdef __cplusplus
59}
60#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/cabcutil.h b/src/libs/dutil/WixToolset.DUtil/inc/cabcutil.h
new file mode 100644
index 00000000..4f0c7b13
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/cabcutil.h
@@ -0,0 +1,62 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#include <fci.h>
6#include <fcntl.h>
7#include <msi.h>
8
9// Callback from PFNFCIGETNEXTCABINET CabCGetNextCabinet method
10// First argument is the name of splitting cabinet without extension e.g. "cab1"
11// Second argument is name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"
12// Third argument is the file token of the first file present in the splitting cabinet
13typedef void (__stdcall * FileSplitCabNamesCallback)(LPWSTR, LPWSTR, LPWSTR);
14
15#define CAB_MAX_SIZE 0x7FFFFFFF // (see KB: Q174866)
16
17#ifdef __cplusplus
18extern "C" {
19#endif
20
21extern const int CABC_HANDLE_BYTES;
22
23// time vs. space trade-off
24typedef enum COMPRESSION_TYPE
25{
26 COMPRESSION_TYPE_NONE, // fastest
27 COMPRESSION_TYPE_LOW,
28 COMPRESSION_TYPE_MEDIUM,
29 COMPRESSION_TYPE_HIGH, // smallest
30 COMPRESSION_TYPE_MSZIP
31} COMPRESSION_TYPE;
32
33// functions
34HRESULT DAPI CabCBegin(
35 __in_z LPCWSTR wzCab,
36 __in_z LPCWSTR wzCabDir,
37 __in DWORD dwMaxFiles,
38 __in DWORD dwMaxSize,
39 __in DWORD dwMaxThresh,
40 __in COMPRESSION_TYPE ct,
41 __out_bcount(CABC_HANDLE_BYTES) HANDLE *phContext
42 );
43HRESULT DAPI CabCNextCab(
44 __in_bcount(CABC_HANDLE_BYTES) HANDLE hContext
45 );
46HRESULT DAPI CabCAddFile(
47 __in_z LPCWSTR wzFile,
48 __in_z_opt LPCWSTR wzToken,
49 __in_opt PMSIFILEHASHINFO pmfHash,
50 __in_bcount(CABC_HANDLE_BYTES) HANDLE hContext
51 );
52HRESULT DAPI CabCFinish(
53 __in_bcount(CABC_HANDLE_BYTES) HANDLE hContext,
54 __in_opt FileSplitCabNamesCallback fileSplitCabNamesCallback
55 );
56void DAPI CabCCancel(
57 __in_bcount(CABC_HANDLE_BYTES) HANDLE hContext
58 );
59
60#ifdef __cplusplus
61}
62#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/cabutil.h b/src/libs/dutil/WixToolset.DUtil/inc/cabutil.h
new file mode 100644
index 00000000..0bedba80
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/cabutil.h
@@ -0,0 +1,56 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#include <fdi.h>
6#include <sys\stat.h>
7
8#ifdef __cplusplus
9extern "C" {
10#endif
11
12// structs
13
14
15// callback function prototypes
16typedef HRESULT (*CAB_CALLBACK_OPEN_FILE)(LPCWSTR wzFile, INT_PTR* ppFile);
17typedef HRESULT (*CAB_CALLBACK_READ_FILE)(INT_PTR pFile, LPVOID pvData, DWORD cbData, DWORD* pcbRead);
18typedef HRESULT (*CAB_CALLBACK_WRITE_FILE)(INT_PTR pFile, LPVOID pvData, DWORD cbData, DWORD* pcbRead);
19typedef LONG (*CAB_CALLBACK_SEEK_FILE)(INT_PTR pFile, DWORD dwMove, DWORD dwMoveMethod);
20typedef HRESULT (*CAB_CALLBACK_CLOSE_FILE)(INT_PTR pFile);
21
22typedef HRESULT (*CAB_CALLBACK_BEGIN_FILE)(LPCWSTR wzFileId, FILETIME* pftFileTime, DWORD cbFileSize, LPVOID pvContext, INT_PTR* ppFile);
23typedef HRESULT (*CAB_CALLBACK_END_FILE)(LPCWSTR wzFileId, LPVOID pvContext, INT_PTR pFile);
24typedef HRESULT (*CAB_CALLBACK_PROGRESS)(BOOL fBeginFile, LPCWSTR wzFileId, LPVOID pvContext);
25
26// function type with calling convention of __stdcall that .NET 1.1 understands only
27// .NET 2.0 will not need this
28typedef INT_PTR (FAR __stdcall *STDCALL_PFNFDINOTIFY)(FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin);
29
30
31// functions
32HRESULT DAPI CabInitialize(
33 __in BOOL fDelayLoad
34 );
35void DAPI CabUninitialize(
36 );
37
38HRESULT DAPI CabExtract(
39 __in_z LPCWSTR wzCabinet,
40 __in_z LPCWSTR wzExtractFile,
41 __in_z LPCWSTR wzExtractDir,
42 __in_opt CAB_CALLBACK_PROGRESS pfnProgress,
43 __in_opt LPVOID pvContext,
44 __in DWORD64 dw64EmbeddedOffset
45 );
46
47HRESULT DAPI CabEnumerate(
48 __in_z LPCWSTR wzCabinet,
49 __in_z LPCWSTR wzEnumerateFile,
50 __in STDCALL_PFNFDINOTIFY pfnNotify,
51 __in DWORD64 dw64EmbeddedOffset
52 );
53
54#ifdef __cplusplus
55}
56#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/certutil.h b/src/libs/dutil/WixToolset.DUtil/inc/certutil.h
new file mode 100644
index 00000000..8565c1cf
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/certutil.h
@@ -0,0 +1,66 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#define ReleaseCertStore(p) if (p) { ::CertCloseStore(p, 0); p = NULL; }
6#define ReleaseCertContext(p) if (p) { ::CertFreeCertificateContext(p); p = NULL; }
7#define ReleaseCertChain(p) if (p) { ::CertFreeCertificateChain(p); p = NULL; }
8
9#ifdef __cplusplus
10extern "C" {
11#endif
12
13HRESULT DAPI CertReadProperty(
14 __in PCCERT_CONTEXT pCertContext,
15 __in DWORD dwProperty,
16 __out_bcount(*pcbValue) LPVOID pvValue,
17 __out_opt DWORD* pcbValue
18 );
19
20HRESULT DAPI CertGetAuthenticodeSigningTimestamp(
21 __in CMSG_SIGNER_INFO* pSignerInfo,
22 __out FILETIME* pft
23 );
24
25HRESULT DAPI GetCryptProvFromCert(
26 __in_opt HWND hwnd,
27 __in PCCERT_CONTEXT pCert,
28 __out HCRYPTPROV *phCryptProv,
29 __out DWORD *pdwKeySpec,
30 __in BOOL *pfDidCryptAcquire,
31 __deref_opt_out LPWSTR *ppwszTmpContainer,
32 __deref_opt_out LPWSTR *ppwszProviderName,
33 __out DWORD *pdwProviderType
34 );
35
36HRESULT DAPI FreeCryptProvFromCert(
37 __in BOOL fAcquired,
38 __in HCRYPTPROV hProv,
39 __in_opt LPWSTR pwszCapiProvider,
40 __in DWORD dwProviderType,
41 __in_opt LPWSTR pwszTmpContainer
42 );
43
44HRESULT DAPI GetProvSecurityDesc(
45 __in HCRYPTPROV hProv,
46 __deref_out SECURITY_DESCRIPTOR** pSecurity
47 );
48
49HRESULT DAPI SetProvSecurityDesc(
50 __in HCRYPTPROV hProv,
51 __in SECURITY_DESCRIPTOR* pSecurity
52 );
53
54BOOL DAPI CertHasPrivateKey(
55 __in PCCERT_CONTEXT pCertContext,
56 __out_opt DWORD* pdwKeySpec
57 );
58
59HRESULT DAPI CertInstallSingleCertificate(
60 __in HCERTSTORE hStore,
61 __in PCCERT_CONTEXT pCertContext,
62 __in LPCWSTR wzName
63 );
64#ifdef __cplusplus
65}
66#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/conutil.h b/src/libs/dutil/WixToolset.DUtil/inc/conutil.h
new file mode 100644
index 00000000..38aaea84
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/conutil.h
@@ -0,0 +1,80 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9#define ConsoleExitOnFailureSource(d, x, c, f, ...) if (FAILED(x)) { ConsoleWriteError(x, c, f, __VA_ARGS__); ExitTraceSource(d, x, f, __VA_ARGS__); goto LExit; }
10#define ConsoleExitOnLastErrorSource(d, x, c, f, ...) { x = ::GetLastError(); x = HRESULT_FROM_WIN32(x); if (FAILED(x)) { ConsoleWriteError(x, c, f, __VA_ARGS__); ExitTraceSource(d, x, f, __VA_ARGS__); goto LExit; } }
11#define ConsoleExitOnNullSource(d, p, x, e, c, f, ...) if (NULL == p) { x = e; ConsoleWriteError(x, c, f, __VA_ARGS__); ExitTraceSource(d, x, f, __VA_ARGS__); goto LExit; }
12#define ConsoleExitOnNullWithLastErrorSource(d, p, x, c, f, ...) if (NULL == p) { DWORD Dutil_er = ::GetLastError(); x = HRESULT_FROM_WIN32(Dutil_er); if (!FAILED(x)) { x = E_FAIL; } ConsoleWriteError(x, c, f, __VA_ARGS__); ExitTraceSource(d, x, f, __VA_ARGS__); goto LExit; }
13#define ConsoleExitWithLastErrorSource(d, x, c, f, ...) { DWORD Dutil_er = ::GetLastError(); x = HRESULT_FROM_WIN32(Dutil_er); if (!FAILED(x)) { x = E_FAIL; } ConsoleWriteError(x, c, f, __VA_ARGS__); ExitTraceSource(d, x, f, __VA_ARGS__); goto LExit; }
14
15
16#define ConsoleExitOnFailure(x, c, f, ...) ConsoleExitOnFailureSource(DUTIL_SOURCE_DEFAULT, x, c, f, __VA_ARGS__)
17#define ConsoleExitOnLastError(x, c, f, ...) ConsoleExitOnLastErrorSource(DUTIL_SOURCE_DEFAULT, x, c, f, __VA_ARGS__)
18#define ConsoleExitOnNull(p, x, e, c, f, ...) ConsoleExitOnNullSource(DUTIL_SOURCE_DEFAULT, p, x, e, c, f, __VA_ARGS__)
19#define ConsoleExitOnNullWithLastError(p, x, c, f, ...) ConsoleExitOnNullWithLastErrorSource(DUTIL_SOURCE_DEFAULT, p, x, c, f, __VA_ARGS__)
20#define ConsoleExitWithLastError(x, c, f, ...) ConsoleExitWithLastErrorSource(DUTIL_SOURCE_DEFAULT, x, c, f, __VA_ARGS__)
21
22// enums
23typedef enum CONSOLE_COLOR { CONSOLE_COLOR_NORMAL, CONSOLE_COLOR_RED, CONSOLE_COLOR_YELLOW, CONSOLE_COLOR_GREEN } CONSOLE_COLOR;
24
25// structs
26
27// functions
28HRESULT DAPI ConsoleInitialize();
29void DAPI ConsoleUninitialize();
30
31void DAPI ConsoleGreen();
32void DAPI ConsoleRed();
33void DAPI ConsoleYellow();
34void DAPI ConsoleNormal();
35
36HRESULT DAPI ConsoleWrite(
37 CONSOLE_COLOR cc,
38 __in_z __format_string LPCSTR szFormat,
39 ...
40 );
41HRESULT DAPI ConsoleWriteLine(
42 CONSOLE_COLOR cc,
43 __in_z __format_string LPCSTR szFormat,
44 ...
45 );
46HRESULT DAPI ConsoleWriteError(
47 HRESULT hrError,
48 CONSOLE_COLOR cc,
49 __in_z __format_string LPCSTR szFormat,
50 ...
51 );
52
53HRESULT DAPI ConsoleReadW(
54 __deref_out_z LPWSTR* ppwzBuffer
55 );
56
57HRESULT DAPI ConsoleReadStringA(
58 __deref_inout_ecount_part(cchCharBuffer,*pcchNumCharReturn) LPSTR* szCharBuffer,
59 CONST DWORD cchCharBuffer,
60 __out DWORD* pcchNumCharReturn
61 );
62HRESULT DAPI ConsoleReadStringW(
63 __deref_inout_ecount_part(cchCharBuffer,*pcchNumCharReturn) LPWSTR* szCharBuffer,
64 CONST DWORD cchCharBuffer,
65 __out DWORD* pcchNumCharReturn
66 );
67
68HRESULT DAPI ConsoleReadNonBlockingW(
69 __deref_out_ecount_opt(*pcchSize) LPWSTR* ppwzBuffer,
70 __out DWORD* pcchSize,
71 BOOL fReadLine
72 );
73
74HRESULT DAPI ConsoleSetReadHidden(void);
75HRESULT DAPI ConsoleSetReadNormal(void);
76
77#ifdef __cplusplus
78}
79#endif
80
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/cryputil.h b/src/libs/dutil/WixToolset.DUtil/inc/cryputil.h
new file mode 100644
index 00000000..02492d8a
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/cryputil.h
@@ -0,0 +1,106 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#define ReleaseCryptMsg(p) if (p) { ::CryptMsgClose(p); p = NULL; }
6
7#ifdef __cplusplus
8extern "C" {
9#endif
10
11
12// Use CRYPTPROTECTMEMORY_BLOCK_SIZE, because it's larger and thus more restrictive than RTL_ENCRYPT_MEMORY_SIZE.
13#define CRYP_ENCRYPT_MEMORY_SIZE CRYPTPROTECTMEMORY_BLOCK_SIZE
14#define MD5_HASH_LEN 16
15#define SHA1_HASH_LEN 20
16#define SHA256_HASH_LEN 32
17#define SHA512_HASH_LEN 64
18
19typedef NTSTATUS (APIENTRY *PFN_RTLENCRYPTMEMORY)(
20 __inout PVOID Memory,
21 __in ULONG MemoryLength,
22 __in ULONG OptionFlags
23 );
24
25typedef NTSTATUS (APIENTRY *PFN_RTLDECRYPTMEMORY)(
26 __inout PVOID Memory,
27 __in ULONG MemoryLength,
28 __in ULONG OptionFlags
29 );
30
31typedef BOOL (APIENTRY *PFN_CRYPTPROTECTMEMORY)(
32 __inout LPVOID pData,
33 __in DWORD cbData,
34 __in DWORD dwFlags
35 );
36
37typedef BOOL (APIENTRY *PFN_CRYPTUNPROTECTMEMORY)(
38 __inout LPVOID pData,
39 __in DWORD cbData,
40 __in DWORD dwFlags
41 );
42
43// function declarations
44
45HRESULT DAPI CrypInitialize();
46void DAPI CrypUninitialize();
47
48HRESULT DAPI CrypDecodeObject(
49 __in_z LPCSTR szStructType,
50 __in_ecount(cbData) const BYTE* pbData,
51 __in DWORD cbData,
52 __in DWORD dwFlags,
53 __out LPVOID* ppvObject,
54 __out_opt DWORD* pcbObject
55 );
56
57HRESULT DAPI CrypMsgGetParam(
58 __in HCRYPTMSG hCryptMsg,
59 __in DWORD dwType,
60 __in DWORD dwIndex,
61 __out LPVOID* ppvData,
62 __out_opt DWORD* pcbData
63 );
64
65HRESULT DAPI CrypHashFile(
66 __in_z LPCWSTR wzFilePath,
67 __in DWORD dwProvType,
68 __in ALG_ID algid,
69 __out_bcount(cbHash) BYTE* pbHash,
70 __in DWORD cbHash,
71 __out_opt DWORD64* pqwBytesHashed
72 );
73
74HRESULT DAPI CrypHashFileHandle(
75 __in HANDLE hFile,
76 __in DWORD dwProvType,
77 __in ALG_ID algid,
78 __out_bcount(cbHash) BYTE* pbHash,
79 __in DWORD cbHash,
80 __out_opt DWORD64* pqwBytesHashed
81 );
82
83HRESULT DAPI CrypHashBuffer(
84 __in_bcount(cbBuffer) const BYTE* pbBuffer,
85 __in SIZE_T cbBuffer,
86 __in DWORD dwProvType,
87 __in ALG_ID algid,
88 __out_bcount(cbHash) BYTE* pbHash,
89 __in DWORD cbHash
90 );
91
92HRESULT DAPI CrypEncryptMemory(
93 __inout LPVOID pData,
94 __in DWORD cbData,
95 __in DWORD dwFlags
96 );
97
98HRESULT DAPI CrypDecryptMemory(
99 __inout LPVOID pData,
100 __in DWORD cbData,
101 __in DWORD dwFlags
102 );
103
104#ifdef __cplusplus
105}
106#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/deputil.h b/src/libs/dutil/WixToolset.DUtil/inc/deputil.h
new file mode 100644
index 00000000..bfe235f3
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/deputil.h
@@ -0,0 +1,147 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9#define ReleaseDependencyArray(rg, c) if (rg) { DepDependencyArrayFree(rg, c); }
10#define ReleaseNullDependencyArray(rg, c) if (rg) { DepDependencyArrayFree(rg, c); rg = NULL; }
11
12typedef struct _DEPENDENCY
13{
14 LPWSTR sczKey;
15 LPWSTR sczName;
16} DEPENDENCY;
17
18
19/***************************************************************************
20 DepGetProviderInformation - gets the various pieces of data registered
21 with a dependency.
22
23 Note: Returns E_NOTFOUND if the dependency was not found.
24***************************************************************************/
25DAPI_(HRESULT) DepGetProviderInformation(
26 __in HKEY hkHive,
27 __in_z LPCWSTR wzProviderKey,
28 __deref_out_z_opt LPWSTR* psczId,
29 __deref_out_z_opt LPWSTR* psczName,
30 __deref_out_z_opt LPWSTR* psczVersion
31 );
32
33/***************************************************************************
34 DepCheckDependency - Checks that the dependency is registered and within
35 the proper version range.
36
37 Note: Returns E_NOTFOUND if the dependency was not found.
38***************************************************************************/
39DAPI_(HRESULT) DepCheckDependency(
40 __in HKEY hkHive,
41 __in_z LPCWSTR wzProviderKey,
42 __in_z_opt LPCWSTR wzMinVersion,
43 __in_z_opt LPCWSTR wzMaxVersion,
44 __in int iAttributes,
45 __in STRINGDICT_HANDLE sdDependencies,
46 __deref_inout_ecount_opt(*pcDependencies) DEPENDENCY** prgDependencies,
47 __inout LPUINT pcDependencies
48 );
49
50/***************************************************************************
51 DepCheckDependents - Checks if any dependents are still installed for the
52 given provider key.
53
54***************************************************************************/
55DAPI_(HRESULT) DepCheckDependents(
56 __in HKEY hkHive,
57 __in_z LPCWSTR wzProviderKey,
58 __reserved int iAttributes,
59 __in C_STRINGDICT_HANDLE sdIgnoredDependents,
60 __deref_inout_ecount_opt(*pcDependents) DEPENDENCY** prgDependents,
61 __inout LPUINT pcDependents
62 );
63
64/***************************************************************************
65 DepRegisterDependency - Registers the dependency provider.
66
67***************************************************************************/
68DAPI_(HRESULT) DepRegisterDependency(
69 __in HKEY hkHive,
70 __in_z LPCWSTR wzProviderKey,
71 __in_z LPCWSTR wzVersion,
72 __in_z LPCWSTR wzDisplayName,
73 __in_z_opt LPCWSTR wzId,
74 __in int iAttributes
75 );
76
77/***************************************************************************
78 DepDependentExists - Determines if a dependent is registered.
79
80 Note: Returns S_OK if dependent is registered.
81 Returns E_FILENOTFOUND if dependent is not registered
82***************************************************************************/
83DAPI_(HRESULT) DepDependentExists(
84 __in HKEY hkHive,
85 __in_z LPCWSTR wzDependencyProviderKey,
86 __in_z LPCWSTR wzProviderKey
87 );
88
89/***************************************************************************
90 DepRegisterDependent - Registers a dependent under the dependency provider.
91
92***************************************************************************/
93DAPI_(HRESULT) DepRegisterDependent(
94 __in HKEY hkHive,
95 __in_z LPCWSTR wzDependencyProviderKey,
96 __in_z LPCWSTR wzProviderKey,
97 __in_z_opt LPCWSTR wzMinVersion,
98 __in_z_opt LPCWSTR wzMaxVersion,
99 __in int iAttributes
100 );
101
102/***************************************************************************
103 DepUnregisterDependency - Removes the dependency provider.
104
105 Note: Caller should call CheckDependents prior to remove a dependency.
106 Returns E_FILENOTFOUND if the dependency is not registered.
107***************************************************************************/
108DAPI_(HRESULT) DepUnregisterDependency(
109 __in HKEY hkHive,
110 __in_z LPCWSTR wzProviderKey
111 );
112
113/***************************************************************************
114 DepUnregisterDependent - Removes a dependent under the dependency provider.
115
116 Note: Returns E_FILENOTFOUND if neither the dependency or dependent are
117 registered.
118 ***************************************************************************/
119DAPI_(HRESULT) DepUnregisterDependent(
120 __in HKEY hkHive,
121 __in_z LPCWSTR wzDependencyProviderKey,
122 __in_z LPCWSTR wzProviderKey
123 );
124
125/***************************************************************************
126 DependencyArrayAlloc - Allocates or expands an array of DEPENDENCY structs.
127
128***************************************************************************/
129DAPI_(HRESULT) DepDependencyArrayAlloc(
130 __deref_inout_ecount_opt(*pcDependencies) DEPENDENCY** prgDependencies,
131 __inout LPUINT pcDependencies,
132 __in_z LPCWSTR wzKey,
133 __in_z_opt LPCWSTR wzName
134 );
135
136/***************************************************************************
137 DepDependencyArrayFree - Frees an array of DEPENDENCY structs.
138
139***************************************************************************/
140DAPI_(void) DepDependencyArrayFree(
141 __in_ecount(cDependencies) DEPENDENCY* rgDependencies,
142 __in UINT cDependencies
143 );
144
145#ifdef __cplusplus
146}
147#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/dictutil.h b/src/libs/dutil/WixToolset.DUtil/inc/dictutil.h
new file mode 100644
index 00000000..f0a3bb5a
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/dictutil.h
@@ -0,0 +1,69 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9#define ReleaseDict(sdh) if (sdh) { DictDestroy(sdh); }
10#define ReleaseNullDict(sdh) if (sdh) { DictDestroy(sdh); sdh = NULL; }
11
12typedef void* STRINGDICT_HANDLE;
13typedef const void* C_STRINGDICT_HANDLE;
14
15extern const int STRINGDICT_HANDLE_BYTES;
16
17typedef enum DICT_FLAG
18{
19 DICT_FLAG_NONE = 0,
20 DICT_FLAG_CASEINSENSITIVE = 1
21} DICT_FLAG;
22
23HRESULT DAPI DictCreateWithEmbeddedKey(
24 __out_bcount(STRINGDICT_HANDLE_BYTES) STRINGDICT_HANDLE* psdHandle,
25 __in DWORD dwNumExpectedItems,
26 __in_opt void **ppvArray,
27 __in size_t cByteOffset,
28 __in DICT_FLAG dfFlags
29 );
30HRESULT DAPI DictCreateStringList(
31 __out_bcount(STRINGDICT_HANDLE_BYTES) STRINGDICT_HANDLE* psdHandle,
32 __in DWORD dwNumExpectedItems,
33 __in DICT_FLAG dfFlags
34 );
35HRESULT DAPI DictCreateStringListFromArray(
36 __out_bcount(STRINGDICT_HANDLE_BYTES) STRINGDICT_HANDLE* psdHandle,
37 __in_ecount(cStringArray) const LPCWSTR* rgwzStringArray,
38 __in const DWORD cStringArray,
39 __in DICT_FLAG dfFlags
40 );
41HRESULT DAPI DictCompareStringListToArray(
42 __in_bcount(STRINGDICT_HANDLE_BYTES) STRINGDICT_HANDLE sdStringList,
43 __in_ecount(cStringArray) const LPCWSTR* rgwzStringArray,
44 __in const DWORD cStringArray
45 );
46HRESULT DAPI DictAddKey(
47 __in_bcount(STRINGDICT_HANDLE_BYTES) STRINGDICT_HANDLE sdHandle,
48 __in_z LPCWSTR szString
49 );
50HRESULT DAPI DictAddValue(
51 __in_bcount(STRINGDICT_HANDLE_BYTES) STRINGDICT_HANDLE sdHandle,
52 __in void *pvValue
53 );
54HRESULT DAPI DictKeyExists(
55 __in_bcount(STRINGDICT_HANDLE_BYTES) C_STRINGDICT_HANDLE sdHandle,
56 __in_z LPCWSTR szString
57 );
58HRESULT DAPI DictGetValue(
59 __in_bcount(STRINGDICT_HANDLE_BYTES) C_STRINGDICT_HANDLE sdHandle,
60 __in_z LPCWSTR szString,
61 __out void **ppvValue
62 );
63void DAPI DictDestroy(
64 __in_bcount(STRINGDICT_HANDLE_BYTES) STRINGDICT_HANDLE sdHandle
65 );
66
67#ifdef __cplusplus
68}
69#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/dirutil.h b/src/libs/dutil/WixToolset.DUtil/inc/dirutil.h
new file mode 100644
index 00000000..539b3a73
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/dirutil.h
@@ -0,0 +1,59 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5typedef enum DIR_DELETE
6{
7 DIR_DELETE_FILES = 1,
8 DIR_DELETE_RECURSE = 2,
9 DIR_DELETE_SCHEDULE = 4,
10} DIR_DELETE;
11
12#ifdef __cplusplus
13extern "C" {
14#endif
15
16BOOL DAPI DirExists(
17 __in_z LPCWSTR wzPath,
18 __out_opt DWORD *pdwAttributes
19 );
20
21HRESULT DAPI DirCreateTempPath(
22 __in_z LPCWSTR wzPrefix,
23 __out_ecount_z(cchPath) LPWSTR wzPath,
24 __in DWORD cchPath
25 );
26
27HRESULT DAPI DirEnsureExists(
28 __in_z LPCWSTR wzPath,
29 __in_opt LPSECURITY_ATTRIBUTES psa
30 );
31
32HRESULT DAPI DirEnsureDelete(
33 __in_z LPCWSTR wzPath,
34 __in BOOL fDeleteFiles,
35 __in BOOL fRecurse
36 );
37
38HRESULT DAPI DirEnsureDeleteEx(
39 __in_z LPCWSTR wzPath,
40 __in DWORD dwFlags
41 );
42
43DWORD DAPI DirDeleteEmptyDirectoriesToRoot(
44 __in_z LPCWSTR wzPath,
45 __in DWORD dwFlags
46 );
47
48HRESULT DAPI DirGetCurrent(
49 __deref_out_z LPWSTR* psczCurrentDirectory
50 );
51
52HRESULT DAPI DirSetCurrent(
53 __in_z LPCWSTR wzDirectory
54 );
55
56#ifdef __cplusplus
57}
58#endif
59
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/dlutil.h b/src/libs/dutil/WixToolset.DUtil/inc/dlutil.h
new file mode 100644
index 00000000..3e95103a
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/dlutil.h
@@ -0,0 +1,59 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9typedef HRESULT (WINAPI *LPAUTHENTICATION_ROUTINE)(
10 __in LPVOID pVoid,
11 __in HINTERNET hUrl,
12 __in long lHttpCode,
13 __out BOOL* pfRetrySend,
14 __out BOOL* pfRetry
15 );
16
17typedef int (WINAPI *LPCANCEL_ROUTINE)(
18 __in HRESULT hrError,
19 __in_z_opt LPCWSTR wzError,
20 __in BOOL fAllowRetry,
21 __in_opt LPVOID pvContext
22 );
23
24// structs
25typedef struct _DOWNLOAD_SOURCE
26{
27 LPWSTR sczUrl;
28 LPWSTR sczUser;
29 LPWSTR sczPassword;
30} DOWNLOAD_SOURCE;
31
32typedef struct _DOWNLOAD_CACHE_CALLBACK
33{
34 LPPROGRESS_ROUTINE pfnProgress;
35 LPCANCEL_ROUTINE pfnCancel;
36 LPVOID pv;
37} DOWNLOAD_CACHE_CALLBACK;
38
39typedef struct _DOWNLOAD_AUTHENTICATION_CALLBACK
40{
41 LPAUTHENTICATION_ROUTINE pfnAuthenticate;
42 LPVOID pv;
43} DOWNLOAD_AUTHENTICATION_CALLBACK;
44
45
46// functions
47
48HRESULT DAPI DownloadUrl(
49 __in DOWNLOAD_SOURCE* pDownloadSource,
50 __in DWORD64 dw64AuthoredDownloadSize,
51 __in LPCWSTR wzDestinationPath,
52 __in_opt DOWNLOAD_CACHE_CALLBACK* pCache,
53 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate
54 );
55
56
57#ifdef __cplusplus
58}
59#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/dpiutil.h b/src/libs/dutil/WixToolset.DUtil/inc/dpiutil.h
new file mode 100644
index 00000000..b30e2332
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/dpiutil.h
@@ -0,0 +1,120 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9// from WinUser.h
10#ifndef WM_DPICHANGED
11#define WM_DPICHANGED 0x02E0
12#endif
13#ifndef USER_DEFAULT_SCREEN_DPI
14#define USER_DEFAULT_SCREEN_DPI 96
15#endif
16
17typedef enum DPIU_AWARENESS
18{
19 DPIU_AWARENESS_NONE = 0x0,
20 DPIU_AWARENESS_SYSTEM = 0x1,
21 DPIU_AWARENESS_PERMONITOR = 0x2,
22 DPIU_AWARENESS_PERMONITORV2 = 0x4,
23 DPIU_AWARENESS_GDISCALED = 0x8,
24} DPIU_PROCESS_AWARENESS;
25
26typedef struct _DPIU_MONITOR_CONTEXT
27{
28 UINT nDpi;
29 MONITORINFOEXW mi;
30} DPIU_MONITOR_CONTEXT;
31
32typedef struct _DPIU_WINDOW_CONTEXT
33{
34 UINT nDpi;
35} DPIU_WINDOW_CONTEXT;
36
37typedef BOOL (APIENTRY* PFN_ADJUSTWINDOWRECTEXFORDPI)(
38 __in LPRECT lpRect,
39 __in DWORD dwStyle,
40 __in BOOL bMenu,
41 __in DWORD dwExStyle,
42 __in UINT dpi
43 );
44typedef UINT (APIENTRY *PFN_GETDPIFORWINDOW)(
45 __in HWND hwnd
46 );
47typedef BOOL (APIENTRY* PFN_SETPROCESSDPIAWARE)();
48typedef BOOL (APIENTRY* PFN_SETPROCESSDPIAWARENESSCONTEXT)(
49 __in DPI_AWARENESS_CONTEXT value
50 );
51
52#ifdef DPI_ENUMS_DECLARED
53typedef HRESULT(APIENTRY* PFN_GETDPIFORMONITOR)(
54 __in HMONITOR hmonitor,
55 __in MONITOR_DPI_TYPE dpiType,
56 __in UINT* dpiX,
57 __in UINT* dpiY
58 );
59typedef HRESULT(APIENTRY* PFN_SETPROCESSDPIAWARENESS)(
60 __in PROCESS_DPI_AWARENESS value
61 );
62#endif
63
64void DAPI DpiuInitialize();
65void DAPI DpiuUninitialize();
66
67/********************************************************************
68 DpiuAdjustWindowRect - calculate the required size of the window rectangle,
69 based on the desired size of the client rectangle
70 and the provided DPI.
71
72*******************************************************************/
73void DAPI DpiuAdjustWindowRect(
74 __in RECT* pWindowRect,
75 __in DWORD dwStyle,
76 __in BOOL fMenu,
77 __in DWORD dwExStyle,
78 __in UINT nDpi
79 );
80
81/********************************************************************
82 DpiuGetMonitorContextFromPoint - get the DPI context of the monitor from the given point.
83
84*******************************************************************/
85HRESULT DAPI DpiuGetMonitorContextFromPoint(
86 __in const POINT* pt,
87 __out DPIU_MONITOR_CONTEXT** ppMonitorContext
88 );
89
90/********************************************************************
91 DpiuGetWindowContext - get the DPI context of the given window.
92
93*******************************************************************/
94void DAPI DpiuGetWindowContext(
95 __in HWND hWnd,
96 __in DPIU_WINDOW_CONTEXT* pWindowContext
97 );
98
99/********************************************************************
100 DpiuScaleValue - scale the value to the target DPI.
101
102*******************************************************************/
103int DAPI DpiuScaleValue(
104 __in int nDefaultDpiValue,
105 __in UINT nTargetDpi
106 );
107
108/********************************************************************
109 DpiuSetProcessDpiAwareness - set the process DPI awareness. The ranking is
110 PERMONITORV2 > PERMONITOR > SYSTEM > GDISCALED > NONE.
111
112*******************************************************************/
113HRESULT DAPI DpiuSetProcessDpiAwareness(
114 __in DPIU_AWARENESS supportedAwareness,
115 __in_opt DPIU_AWARENESS* pSelectedAwareness
116 );
117
118#ifdef __cplusplus
119}
120#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/dutil.h b/src/libs/dutil/WixToolset.DUtil/inc/dutil.h
new file mode 100644
index 00000000..fc9ec0f4
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/dutil.h
@@ -0,0 +1,190 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4#include "dutilsources.h"
5
6#define DAPI __stdcall
7#define DAPIV __cdecl // used only for functions taking variable length arguments
8
9#define DAPI_(type) EXTERN_C type DAPI
10#define DAPIV_(type) EXTERN_C type DAPIV
11
12
13// asserts and traces
14typedef BOOL (DAPI *DUTIL_ASSERTDISPLAYFUNCTION)(__in_z LPCSTR sz);
15
16typedef void (CALLBACK *DUTIL_CALLBACK_TRACEERROR)(
17 __in_z LPCSTR szFile,
18 __in int iLine,
19 __in REPORT_LEVEL rl,
20 __in UINT source,
21 __in HRESULT hrError,
22 __in_z __format_string LPCSTR szFormat,
23 __in va_list args
24 );
25
26#ifdef __cplusplus
27extern "C" {
28#endif
29
30/********************************************************************
31 DutilInitialize - initialize dutil.
32
33*******************************************************************/
34HRESULT DAPI DutilInitialize(
35 __in_opt DUTIL_CALLBACK_TRACEERROR pfnTraceErrorCallback
36 );
37
38/********************************************************************
39 DutilUninitialize - uninitialize dutil.
40
41*******************************************************************/
42void DAPI DutilUninitialize();
43
44void DAPI Dutil_SetAssertModule(__in HMODULE hAssertModule);
45void DAPI Dutil_SetAssertDisplayFunction(__in DUTIL_ASSERTDISPLAYFUNCTION pfn);
46void DAPI Dutil_Assert(__in_z LPCSTR szFile, __in int iLine);
47void DAPI Dutil_AssertSz(__in_z LPCSTR szFile, __in int iLine, __in_z __format_string LPCSTR szMessage);
48
49void DAPI Dutil_TraceSetLevel(__in REPORT_LEVEL ll, __in BOOL fTraceFilenames);
50REPORT_LEVEL DAPI Dutil_TraceGetLevel();
51void DAPIV Dutil_Trace(__in_z LPCSTR szFile, __in int iLine, __in REPORT_LEVEL rl, __in_z __format_string LPCSTR szMessage, ...);
52void DAPIV Dutil_TraceError(__in_z LPCSTR szFile, __in int iLine, __in REPORT_LEVEL rl, __in HRESULT hr, __in_z __format_string LPCSTR szMessage, ...);
53void DAPIV Dutil_TraceErrorSource(__in_z LPCSTR szFile, __in int iLine, __in REPORT_LEVEL rl, __in UINT source, __in HRESULT hr, __in_z __format_string LPCSTR szMessage, ...);
54void DAPI Dutil_RootFailure(__in_z LPCSTR szFile, __in int iLine, __in HRESULT hrError);
55
56#ifdef __cplusplus
57}
58#endif
59
60
61#ifdef DEBUG
62
63#define AssertSetModule(m) (void)Dutil_SetAssertModule(m)
64#define AssertSetDisplayFunction(pfn) (void)Dutil_SetAssertDisplayFunction(pfn)
65#define Assert(f) ((f) ? (void)0 : (void)Dutil_Assert(__FILE__, __LINE__))
66#define AssertSz(f, sz) ((f) ? (void)0 : (void)Dutil_AssertSz(__FILE__, __LINE__, sz))
67
68#define TraceSetLevel(l, f) (void)Dutil_TraceSetLevel(l, f)
69#define TraceGetLevel() (REPORT_LEVEL)Dutil_TraceGetLevel()
70#define Trace(l, f, ...) (void)Dutil_Trace(__FILE__, __LINE__, l, f, __VA_ARGS__)
71#define TraceError(x, f, ...) (void)Dutil_TraceError(__FILE__, __LINE__, REPORT_ERROR, x, f, __VA_ARGS__)
72#define TraceErrorDebug(x, f, ...) (void)Dutil_TraceError(__FILE__, __LINE__, REPORT_DEBUG, x, f, __VA_ARGS__)
73
74#else // !DEBUG
75
76#define AssertSetModule(m)
77#define AssertSetDisplayFunction(pfn)
78#define Assert(f)
79#define AssertSz(f, sz)
80
81#define TraceSetLevel(l, f)
82#define Trace(l, f, ...)
83#define TraceError(x, f, ...)
84#define TraceErrorDebug(x, f, ...)
85
86#endif // DEBUG
87
88// DUTIL_SOURCE_DEFAULT can be overriden
89#ifndef DUTIL_SOURCE_DEFAULT
90#define DUTIL_SOURCE_DEFAULT DUTIL_SOURCE_UNKNOWN
91#endif
92
93// Exit macros
94#define ExitFunction() { goto LExit; }
95#define ExitFunction1(x) { x; goto LExit; }
96
97#define ExitFunctionWithLastError(x) { x = HRESULT_FROM_WIN32(::GetLastError()); goto LExit; }
98
99#define ExitTraceSource(d, x, s, ...) { TraceError(x, s, __VA_ARGS__); (void)Dutil_TraceErrorSource(__FILE__, __LINE__, REPORT_ERROR, d, x, s, __VA_ARGS__); }
100#define ExitTraceDebugSource(d, x, s, ...) { TraceErrorDebug(x, s, __VA_ARGS__); (void)Dutil_TraceErrorSource(__FILE__, __LINE__, REPORT_DEBUG, d, x, s, __VA_ARGS__); }
101
102#define ExitOnLastErrorSource(d, x, s, ...) { DWORD Dutil_er = ::GetLastError(); x = HRESULT_FROM_WIN32(Dutil_er); if (FAILED(x)) { Dutil_RootFailure(__FILE__, __LINE__, x); ExitTraceSource(d, x, s, __VA_ARGS__); goto LExit; } }
103#define ExitOnLastErrorDebugTraceSource(d, x, s, ...) { DWORD Dutil_er = ::GetLastError(); x = HRESULT_FROM_WIN32(Dutil_er); if (FAILED(x)) { Dutil_RootFailure(__FILE__, __LINE__, x); ExitTraceDebugSource(d, x, s, __VA_ARGS__); goto LExit; } }
104#define ExitWithLastErrorSource(d, x, s, ...) { DWORD Dutil_er = ::GetLastError(); x = HRESULT_FROM_WIN32(Dutil_er); if (!FAILED(x)) { x = E_FAIL; } Dutil_RootFailure(__FILE__, __LINE__, x); ExitTraceSource(d, x, s, __VA_ARGS__); goto LExit; }
105#define ExitOnFailureSource(d, x, s, ...) if (FAILED(x)) { ExitTraceSource(d, x, s, __VA_ARGS__); goto LExit; }
106#define ExitOnRootFailureSource(d, x, s, ...) if (FAILED(x)) { Dutil_RootFailure(__FILE__, __LINE__, x); ExitTraceSource(d, x, s, __VA_ARGS__); goto LExit; }
107#define ExitOnFailureDebugTraceSource(d, x, s, ...) if (FAILED(x)) { ExitTraceDebugSource(d, x, s, __VA_ARGS__); goto LExit; }
108#define ExitOnNullSource(d, p, x, e, s, ...) if (NULL == p) { x = e; Dutil_RootFailure(__FILE__, __LINE__, x); ExitTraceSource(d, x, s, __VA_ARGS__); goto LExit; }
109#define ExitOnNullWithLastErrorSource(d, p, x, s, ...) if (NULL == p) { DWORD Dutil_er = ::GetLastError(); x = HRESULT_FROM_WIN32(Dutil_er); if (!FAILED(x)) { x = E_FAIL; } Dutil_RootFailure(__FILE__, __LINE__, x); ExitTraceSource(d, x, s, __VA_ARGS__); goto LExit; }
110#define ExitOnNullDebugTraceSource(d, p, x, e, s, ...) if (NULL == p) { x = e; Dutil_RootFailure(__FILE__, __LINE__, x); ExitTraceDebugSource(d, x, s, __VA_ARGS__); goto LExit; }
111#define ExitOnInvalidHandleWithLastErrorSource(d, p, x, s, ...) if (INVALID_HANDLE_VALUE == p) { DWORD Dutil_er = ::GetLastError(); x = HRESULT_FROM_WIN32(Dutil_er); if (!FAILED(x)) { x = E_FAIL; } Dutil_RootFailure(__FILE__, __LINE__, x); ExitTraceSource(d, x, s, __VA_ARGS__); goto LExit; }
112#define ExitOnWin32ErrorSource(d, e, x, s, ...) if (ERROR_SUCCESS != e) { x = HRESULT_FROM_WIN32(e); if (!FAILED(x)) { x = E_FAIL; } Dutil_RootFailure(__FILE__, __LINE__, x); ExitTraceSource(d, x, s, __VA_ARGS__); goto LExit; }
113
114#define ExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_DEFAULT, x, s, __VA_ARGS__)
115#define ExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_DEFAULT, x, s, __VA_ARGS__)
116#define ExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_DEFAULT, x, s, __VA_ARGS__)
117#define ExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_DEFAULT, x, s, __VA_ARGS__)
118#define ExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_DEFAULT, x, s, __VA_ARGS__)
119#define ExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_DEFAULT, x, s, __VA_ARGS__)
120#define ExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_DEFAULT, p, x, e, s, __VA_ARGS__)
121#define ExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_DEFAULT, p, x, s, __VA_ARGS__)
122#define ExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_DEFAULT, p, x, e, s, __VA_ARGS__)
123#define ExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_DEFAULT, p, x, s, __VA_ARGS__)
124#define ExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_DEFAULT, e, x, s, __VA_ARGS__)
125
126// release macros
127#define ReleaseObject(x) if (x) { x->Release(); }
128#define ReleaseObjectArray(prg, cel) if (prg) { for (DWORD Dutil_ReleaseObjectArrayIndex = 0; Dutil_ReleaseObjectArrayIndex < cel; ++Dutil_ReleaseObjectArrayIndex) { ReleaseObject(prg[Dutil_ReleaseObjectArrayIndex]); } ReleaseMem(prg); }
129#define ReleaseVariant(x) { ::VariantClear(&x); }
130#define ReleaseNullObject(x) if (x) { (x)->Release(); x = NULL; }
131#define ReleaseCertificate(x) if (x) { ::CertFreeCertificateContext(x); x=NULL; }
132#define ReleaseHandle(x) if (x) { ::CloseHandle(x); x = NULL; }
133
134
135// useful defines and macros
136#define Unused(x) ((void)x)
137
138#ifndef countof
139#if 1
140#define countof(ary) (sizeof(ary) / sizeof(ary[0]))
141#else
142#ifndef __cplusplus
143#define countof(ary) (sizeof(ary) / sizeof(ary[0]))
144#else
145template<typename T> static char countofVerify(void const *, T) throw() { return 0; }
146template<typename T> static void countofVerify(T *const, T *const *) throw() {};
147#define countof(arr) (sizeof(countofVerify(arr,&(arr))) * sizeof(arr)/sizeof(*(arr)))
148#endif
149#endif
150#endif
151
152#define roundup(x, n) roundup_typed(x, n, DWORD)
153#define roundup_typed(x, n, t) (((t)(x) + ((t)(n) - 1)) & ~((t)(n) - 1))
154
155#define HRESULT_FROM_RPC(x) ((HRESULT) ((x) | FACILITY_RPC))
156
157#ifndef MAXSIZE_T
158#define MAXSIZE_T ((SIZE_T)~((SIZE_T)0))
159#endif
160
161typedef const BYTE* LPCBYTE;
162
163#define E_FILENOTFOUND HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)
164#define E_PATHNOTFOUND HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)
165#define E_INVALIDDATA HRESULT_FROM_WIN32(ERROR_INVALID_DATA)
166#define E_INVALIDSTATE HRESULT_FROM_WIN32(ERROR_INVALID_STATE)
167#define E_INSUFFICIENT_BUFFER HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)
168#define E_MOREDATA HRESULT_FROM_WIN32(ERROR_MORE_DATA)
169#define E_NOMOREITEMS HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS)
170#define E_NOTFOUND HRESULT_FROM_WIN32(ERROR_NOT_FOUND)
171#define E_MODNOTFOUND HRESULT_FROM_WIN32(ERROR_MOD_NOT_FOUND)
172#define E_BADCONFIGURATION HRESULT_FROM_WIN32(ERROR_BAD_CONFIGURATION)
173
174#define AddRefAndRelease(x) { x->AddRef(); x->Release(); }
175
176#define MAKEDWORD(lo, hi) ((DWORD)MAKELONG(lo, hi))
177#define MAKEQWORDVERSION(mj, mi, b, r) (((DWORD64)MAKELONG(r, b)) | (((DWORD64)MAKELONG(mi, mj)) << 32))
178
179
180#ifdef __cplusplus
181extern "C" {
182#endif
183
184// other functions
185HRESULT DAPI LoadSystemLibrary(__in_z LPCWSTR wzModuleName, __out HMODULE *phModule);
186HRESULT DAPI LoadSystemLibraryWithPath(__in_z LPCWSTR wzModuleName, __out HMODULE *phModule, __deref_out_z_opt LPWSTR* psczPath);
187
188#ifdef __cplusplus
189}
190#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/dutilsources.h b/src/libs/dutil/WixToolset.DUtil/inc/dutilsources.h
new file mode 100644
index 00000000..7d512cb3
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/dutilsources.h
@@ -0,0 +1,76 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4typedef enum DUTIL_SOURCE
5{
6 DUTIL_SOURCE_UNKNOWN,
7 DUTIL_SOURCE_ACLUTIL,
8 DUTIL_SOURCE_APPUTIL,
9 DUTIL_SOURCE_APUPUTIL,
10 DUTIL_SOURCE_ATOMUTIL,
11 DUTIL_SOURCE_BUFFUTIL,
12 DUTIL_SOURCE_BUTIL,
13 DUTIL_SOURCE_CABCUTIL,
14 DUTIL_SOURCE_CABUTIL,
15 DUTIL_SOURCE_CERTUTIL,
16 DUTIL_SOURCE_CONUTIL,
17 DUTIL_SOURCE_CRYPUTIL,
18 DUTIL_SOURCE_DEPUTIL,
19 DUTIL_SOURCE_DICTUTIL,
20 DUTIL_SOURCE_DIRUTIL,
21 DUTIL_SOURCE_DLUTIL,
22 DUTIL_SOURCE_DPIUTIL,
23 DUTIL_SOURCE_DUTIL,
24 DUTIL_SOURCE_ESEUTIL,
25 DUTIL_SOURCE_FILEUTIL,
26 DUTIL_SOURCE_GDIPUTIL,
27 DUTIL_SOURCE_GUIDUTIL,
28 DUTIL_SOURCE_IIS7UTIL,
29 DUTIL_SOURCE_INETUTIL,
30 DUTIL_SOURCE_INIUTIL,
31 DUTIL_SOURCE_JSONUTIL,
32 DUTIL_SOURCE_LOCUTIL,
33 DUTIL_SOURCE_LOGUTIL,
34 DUTIL_SOURCE_MEMUTIL,
35 DUTIL_SOURCE_METAUTIL,
36 DUTIL_SOURCE_MONUTIL,
37 DUTIL_SOURCE_OSUTIL,
38 DUTIL_SOURCE_PATHUTIL,
39 DUTIL_SOURCE_PERFUTIL,
40 DUTIL_SOURCE_POLCUTIL,
41 DUTIL_SOURCE_PROCUTIL,
42 DUTIL_SOURCE_REGUTIL,
43 DUTIL_SOURCE_RESRUTIL,
44 DUTIL_SOURCE_RESWUTIL,
45 DUTIL_SOURCE_REXUTIL,
46 DUTIL_SOURCE_RMUTIL,
47 DUTIL_SOURCE_RSSUTIL,
48 DUTIL_SOURCE_SCEUTIL,
49 DUTIL_SOURCE_SCZUTIL,
50 DUTIL_SOURCE_SHELUTIL,
51 DUTIL_SOURCE_SQLUTIL,
52 DUTIL_SOURCE_SRPUTIL,
53 DUTIL_SOURCE_STRUTIL,
54 DUTIL_SOURCE_SVCUTIL,
55 DUTIL_SOURCE_THMUTIL,
56 DUTIL_SOURCE_TIMEUTIL,
57 DUTIL_SOURCE_UNCUTIL,
58 DUTIL_SOURCE_URIUTIL,
59 DUTIL_SOURCE_USERUTIL,
60 DUTIL_SOURCE_WIUTIL,
61 DUTIL_SOURCE_WUAUTIL,
62 DUTIL_SOURCE_XMLUTIL,
63 DUTIL_SOURCE_VERUTIL,
64
65 DUTIL_SOURCE_EXTERNAL = 256,
66} DUTIL_SOURCE;
67
68typedef enum REPORT_LEVEL
69{
70 REPORT_NONE, // turns off report (only valid for XXXSetLevel())
71 REPORT_WARNING, // written if want only warnings or reporting is on in general
72 REPORT_STANDARD, // written if reporting is on
73 REPORT_VERBOSE, // written only if verbose reporting is on
74 REPORT_DEBUG, // reporting useful when debugging code
75 REPORT_ERROR, // always gets reported, but can never be specified
76} REPORT_LEVEL;
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/eseutil.h b/src/libs/dutil/WixToolset.DUtil/inc/eseutil.h
new file mode 100644
index 00000000..bea47b2b
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/eseutil.h
@@ -0,0 +1,223 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9#define ReleaseEseQuery(pqh) if (pqh) { EseFinishQuery(pqh); }
10#define ReleaseNullEseQuery(pqh) if (pqh) { EseFinishQuery(pqh); pqh = NULL; }
11
12struct ESE_COLUMN_SCHEMA
13{
14 JET_COLUMNID jcColumn;
15 LPCWSTR pszName;
16 JET_COLTYP jcColumnType;
17 BOOL fKey; // If this column is part of the key of the table
18 BOOL fFixed;
19 BOOL fNullable;
20 BOOL fAutoIncrement;
21};
22
23struct ESE_TABLE_SCHEMA
24{
25 JET_TABLEID jtTable;
26 LPCWSTR pszName;
27 DWORD dwColumns;
28 ESE_COLUMN_SCHEMA *pcsColumns;
29};
30
31struct ESE_DATABASE_SCHEMA
32{
33 DWORD dwTables;
34 ESE_TABLE_SCHEMA *ptsTables;
35};
36
37typedef enum ESE_QUERY_TYPE
38{
39 ESE_QUERY_EXACT,
40 ESE_QUERY_FROM_TOP,
41 ESE_QUERY_FROM_BOTTOM
42} ESE_QUERY_TYPE;
43
44typedef void* ESE_QUERY_HANDLE;
45
46HRESULT DAPI EseBeginSession(
47 __out JET_INSTANCE *pjiInstance,
48 __out JET_SESID *pjsSession,
49 __in_z LPCWSTR pszInstance,
50 __in_z LPCWSTR pszPath
51 );
52HRESULT DAPI EseEndSession(
53 __in JET_INSTANCE jiInstance,
54 __in JET_SESID jsSession
55 );
56HRESULT DAPI EseEnsureDatabase(
57 __in JET_SESID jsSession,
58 __in_z LPCWSTR pszFile,
59 __in ESE_DATABASE_SCHEMA *pdsSchema,
60 __out JET_DBID* pjdbDb,
61 __in BOOL fExclusive,
62 __in BOOL fReadonly
63 );
64HRESULT DAPI EseCloseDatabase(
65 __in JET_SESID jsSession,
66 __in JET_DBID jdbDb
67 );
68HRESULT DAPI EseCreateTable(
69 __in JET_SESID jsSession,
70 __in JET_DBID jdbDb,
71 __in_z LPCWSTR pszTable,
72 __out JET_TABLEID *pjtTable
73 );
74HRESULT DAPI EseOpenTable(
75 __in JET_SESID jsSession,
76 __in JET_DBID jdbDb,
77 __in_z LPCWSTR pszTable,
78 __out JET_TABLEID *pjtTable
79 );
80HRESULT DAPI EseCloseTable(
81 __in JET_SESID jsSession,
82 __in JET_TABLEID jtTable
83 );
84HRESULT DAPI EseEnsureColumn(
85 __in JET_SESID jsSession,
86 __in JET_TABLEID jtTable,
87 __in_z LPCWSTR pszColumnName,
88 __in JET_COLTYP jcColumnType,
89 __in ULONG ulColumnSize,
90 __in BOOL fFixed,
91 __in BOOL fNullable,
92 __out_opt JET_COLUMNID *pjcColumn
93 );
94HRESULT DAPI EseGetColumn(
95 __in JET_SESID jsSession,
96 __in JET_TABLEID jtTable,
97 __in_z LPCWSTR pszColumnName,
98 __out JET_COLUMNID *pjcColumn
99 );
100HRESULT DAPI EseMoveCursor(
101 __in JET_SESID jsSession,
102 __in JET_TABLEID jtTable,
103 __in LONG lRow
104 );
105HRESULT DAPI EseDeleteRow(
106 __in JET_SESID jsSession,
107 __in JET_TABLEID jtTable
108 );
109HRESULT DAPI EseBeginTransaction(
110 __in JET_SESID jsSession
111 );
112HRESULT DAPI EseRollbackTransaction(
113 __in JET_SESID jsSession,
114 __in BOOL fAll
115 );
116HRESULT DAPI EseCommitTransaction(
117 __in JET_SESID jsSession
118 );
119HRESULT DAPI EsePrepareUpdate(
120 __in JET_SESID jsSession,
121 __in JET_TABLEID jtTable,
122 __in ULONG ulPrep
123 );
124HRESULT DAPI EseFinishUpdate(
125 __in JET_SESID jsSession,
126 __in JET_TABLEID jtTable,
127 __in BOOL fSeekToInsertedRecord
128 );
129HRESULT DAPI EseSetColumnBinary(
130 __in JET_SESID jsSession,
131 __in ESE_TABLE_SCHEMA tsTable,
132 __in DWORD dwColumn,
133 __in_bcount(cbBuffer) const BYTE* pbBuffer,
134 __in SIZE_T cbBuffer
135 );
136HRESULT DAPI EseSetColumnDword(
137 __in JET_SESID jsSession,
138 __in ESE_TABLE_SCHEMA tsTable,
139 __in DWORD dwColumn,
140 __in DWORD dwValue
141 );
142HRESULT DAPI EseSetColumnBool(
143 __in JET_SESID jsSession,
144 __in ESE_TABLE_SCHEMA tsTable,
145 __in DWORD dwColumn,
146 __in BOOL fValue
147 );
148HRESULT DAPI EseSetColumnString(
149 __in JET_SESID jsSession,
150 __in ESE_TABLE_SCHEMA tsTable,
151 __in DWORD dwColumn,
152 __in_z LPCWSTR pszValue
153 );
154HRESULT DAPI EseSetColumnEmpty(
155 __in JET_SESID jsSession,
156 __in ESE_TABLE_SCHEMA tsTable,
157 __in DWORD dwColumn
158 );
159HRESULT DAPI EseGetColumnBinary(
160 __in JET_SESID jsSession,
161 __in ESE_TABLE_SCHEMA tsTable,
162 __in DWORD dwColumn,
163 __deref_inout_bcount(*piBuffer) BYTE** ppbBuffer,
164 __inout SIZE_T* piBuffer
165 );
166HRESULT DAPI EseGetColumnDword(
167 __in JET_SESID jsSession,
168 __in ESE_TABLE_SCHEMA tsTable,
169 __in DWORD dwColumn,
170 __out DWORD *pdwValue
171 );
172HRESULT DAPI EseGetColumnBool(
173 __in JET_SESID jsSession,
174 __in ESE_TABLE_SCHEMA tsTable,
175 __in DWORD dwColumn,
176 __out BOOL *pfValue
177 );
178HRESULT DAPI EseGetColumnString(
179 __in JET_SESID jsSession,
180 __in ESE_TABLE_SCHEMA tsTable,
181 __in DWORD dwColumn,
182 __out LPWSTR *ppszValue
183 );
184
185// Call this once for each key column in the table
186HRESULT DAPI EseBeginQuery(
187 __in JET_SESID jsSession,
188 __in JET_TABLEID jtTable,
189 __in ESE_QUERY_TYPE qtQueryType,
190 __out ESE_QUERY_HANDLE *peqhHandle
191 );
192HRESULT DAPI EseSetQueryColumnBinary(
193 __in ESE_QUERY_HANDLE eqhHandle,
194 __in_bcount(cbBuffer) const BYTE* pbBuffer,
195 __in SIZE_T cbBuffer,
196 __in BOOL fFinal // If this is true, all other key columns in the query will be set to "*"
197 );
198HRESULT DAPI EseSetQueryColumnDword(
199 __in ESE_QUERY_HANDLE eqhHandle,
200 __in DWORD dwData,
201 __in BOOL fFinal // If this is true, all other key columns in the query will be set to "*"
202 );
203HRESULT DAPI EseSetQueryColumnBool(
204 __in ESE_QUERY_HANDLE eqhHandle,
205 __in BOOL fValue,
206 __in BOOL fFinal // If this is true, all other key columns in the query will be set to "*"
207 );
208HRESULT DAPI EseSetQueryColumnString(
209 __in ESE_QUERY_HANDLE eqhHandle,
210 __in_z LPCWSTR pszString,
211 __in BOOL fFinal // If this is true, all other key columns in the query will be set to "*"
212 );
213HRESULT DAPI EseFinishQuery(
214 __in ESE_QUERY_HANDLE eqhHandle
215 );
216// Once all columns have been set up, call this and read the result
217HRESULT DAPI EseRunQuery(
218 __in ESE_QUERY_HANDLE eqhHandle
219 );
220
221#ifdef __cplusplus
222}
223#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/fileutil.h b/src/libs/dutil/WixToolset.DUtil/inc/fileutil.h
new file mode 100644
index 00000000..d3e326f7
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/fileutil.h
@@ -0,0 +1,247 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9#define ReleaseFile(h) if (INVALID_HANDLE_VALUE != h) { ::CloseHandle(h); h = INVALID_HANDLE_VALUE; }
10#define ReleaseFileHandle(h) if (INVALID_HANDLE_VALUE != h) { ::CloseHandle(h); h = INVALID_HANDLE_VALUE; }
11#define ReleaseFileFindHandle(h) if (INVALID_HANDLE_VALUE != h) { ::FindClose(h); h = INVALID_HANDLE_VALUE; }
12
13#define FILEMAKEVERSION(major, minor, build, revision) static_cast<DWORD64>((static_cast<DWORD64>(major & 0xFFFF) << 48) \
14 | (static_cast<DWORD64>(minor & 0xFFFF) << 32) \
15 | (static_cast<DWORD64>(build & 0xFFFF) << 16) \
16 | (static_cast<DWORD64>(revision & 0xFFFF)))
17
18typedef enum FILE_ARCHITECTURE
19{
20 FILE_ARCHITECTURE_UNKNOWN,
21 FILE_ARCHITECTURE_X86,
22 FILE_ARCHITECTURE_X64,
23 FILE_ARCHITECTURE_IA64,
24} FILE_ARCHITECTURE;
25
26typedef enum FILE_ENCODING
27{
28 FILE_ENCODING_UNSPECIFIED = 0,
29 // TODO: distinguish between non-BOM utf-8 and ANSI in the future?
30 FILE_ENCODING_UTF8,
31 FILE_ENCODING_UTF8_WITH_BOM,
32 FILE_ENCODING_UTF16,
33 FILE_ENCODING_UTF16_WITH_BOM,
34} FILE_ENCODING;
35
36
37LPWSTR DAPI FileFromPath(
38 __in_z LPCWSTR wzPath
39 );
40HRESULT DAPI FileResolvePath(
41 __in_z LPCWSTR wzRelativePath,
42 __out LPWSTR *ppwzFullPath
43 );
44HRESULT DAPI FileStripExtension(
45 __in_z LPCWSTR wzFileName,
46 __out LPWSTR *ppwzFileNameNoExtension
47 );
48HRESULT DAPI FileChangeExtension(
49 __in_z LPCWSTR wzFileName,
50 __in_z LPCWSTR wzNewExtension,
51 __out LPWSTR *ppwzFileNameNewExtension
52 );
53HRESULT DAPI FileAddSuffixToBaseName(
54 __in_z LPCWSTR wzFileName,
55 __in_z LPCWSTR wzSuffix,
56 __out_z LPWSTR* psczNewFileName
57 );
58HRESULT DAPI FileVersionFromString(
59 __in_z LPCWSTR wzVersion,
60 __out DWORD *pdwVerMajor,
61 __out DWORD* pdwVerMinor
62 );
63HRESULT DAPI FileVersionFromStringEx(
64 __in_z LPCWSTR wzVersion,
65 __in SIZE_T cchVersion,
66 __out DWORD64* pqwVersion
67 );
68HRESULT DAPI FileVersionToStringEx(
69 __in DWORD64 qwVersion,
70 __out LPWSTR* psczVersion
71 );
72HRESULT DAPI FileSetPointer(
73 __in HANDLE hFile,
74 __in DWORD64 dw64Move,
75 __out_opt DWORD64* pdw64NewPosition,
76 __in DWORD dwMoveMethod
77 );
78HRESULT DAPI FileSize(
79 __in_z LPCWSTR pwzFileName,
80 __out LONGLONG* pllSize
81 );
82HRESULT DAPI FileSizeByHandle(
83 __in HANDLE hFile,
84 __out LONGLONG* pllSize
85 );
86BOOL DAPI FileExistsEx(
87 __in_z LPCWSTR wzPath,
88 __out_opt DWORD *pdwAttributes
89 );
90BOOL DAPI FileExistsAfterRestart(
91 __in_z LPCWSTR wzPath,
92 __out_opt DWORD *pdwAttributes
93 );
94HRESULT DAPI FileRemoveFromPendingRename(
95 __in_z LPCWSTR wzPath
96 );
97HRESULT DAPI FileRead(
98 __deref_out_bcount_full(*pcbDest) LPBYTE* ppbDest,
99 __out SIZE_T* pcbDest,
100 __in_z LPCWSTR wzSrcPath
101 );
102HRESULT DAPI FileReadEx(
103 __deref_out_bcount_full(*pcbDest) LPBYTE* ppbDest,
104 __out SIZE_T* pcbDest,
105 __in_z LPCWSTR wzSrcPath,
106 __in DWORD dwShareMode
107 );
108HRESULT DAPI FileReadUntil(
109 __deref_out_bcount_full(*pcbDest) LPBYTE* ppbDest,
110 __out_range(<=, cbMaxRead) SIZE_T* pcbDest,
111 __in_z LPCWSTR wzSrcPath,
112 __in DWORD cbMaxRead
113 );
114HRESULT DAPI FileReadPartial(
115 __deref_out_bcount_full(*pcbDest) LPBYTE* ppbDest,
116 __out_range(<=, cbMaxRead) SIZE_T* pcbDest,
117 __in_z LPCWSTR wzSrcPath,
118 __in BOOL fSeek,
119 __in DWORD cbStartPosition,
120 __in DWORD cbMaxRead,
121 __in BOOL fPartialOK
122 );
123HRESULT DAPI FileReadPartialEx(
124 __deref_inout_bcount_full(*pcbDest) LPBYTE* ppbDest,
125 __out_range(<=, cbMaxRead) SIZE_T* pcbDest,
126 __in_z LPCWSTR wzSrcPath,
127 __in BOOL fSeek,
128 __in DWORD cbStartPosition,
129 __in DWORD cbMaxRead,
130 __in BOOL fPartialOK,
131 __in DWORD dwShareMode
132 );
133HRESULT DAPI FileReadHandle(
134 __in HANDLE hFile,
135 __in_bcount(cbDest) LPBYTE pbDest,
136 __in SIZE_T cbDest
137 );
138HRESULT DAPI FileWrite(
139 __in_z LPCWSTR pwzFileName,
140 __in DWORD dwFlagsAndAttributes,
141 __in_bcount_opt(cbData) LPCBYTE pbData,
142 __in SIZE_T cbData,
143 __out_opt HANDLE* pHandle
144 );
145HRESULT DAPI FileWriteHandle(
146 __in HANDLE hFile,
147 __in_bcount_opt(cbData) LPCBYTE pbData,
148 __in SIZE_T cbData
149 );
150HRESULT DAPI FileCopyUsingHandles(
151 __in HANDLE hSource,
152 __in HANDLE hTarget,
153 __in DWORD64 cbCopy,
154 __out_opt DWORD64* pcbCopied
155 );
156HRESULT DAPI FileCopyUsingHandlesWithProgress(
157 __in HANDLE hSource,
158 __in HANDLE hTarget,
159 __in DWORD64 cbCopy,
160 __in_opt LPPROGRESS_ROUTINE lpProgressRoutine,
161 __in_opt LPVOID lpData
162 );
163HRESULT DAPI FileEnsureCopy(
164 __in_z LPCWSTR wzSource,
165 __in_z LPCWSTR wzTarget,
166 __in BOOL fOverwrite
167 );
168HRESULT DAPI FileEnsureCopyWithRetry(
169 __in LPCWSTR wzSource,
170 __in LPCWSTR wzTarget,
171 __in BOOL fOverwrite,
172 __in DWORD cRetry,
173 __in DWORD dwWaitMilliseconds
174 );
175HRESULT DAPI FileEnsureMove(
176 __in_z LPCWSTR wzSource,
177 __in_z LPCWSTR wzTarget,
178 __in BOOL fOverwrite,
179 __in BOOL fAllowCopy
180 );
181HRESULT DAPI FileEnsureMoveWithRetry(
182 __in LPCWSTR wzSource,
183 __in LPCWSTR wzTarget,
184 __in BOOL fOverwrite,
185 __in BOOL fAllowCopy,
186 __in DWORD cRetry,
187 __in DWORD dwWaitMilliseconds
188 );
189HRESULT DAPI FileCreateTemp(
190 __in_z LPCWSTR wzPrefix,
191 __in_z LPCWSTR wzExtension,
192 __deref_opt_out_z LPWSTR* ppwzTempFile,
193 __out_opt HANDLE* phTempFile
194 );
195HRESULT DAPI FileCreateTempW(
196 __in_z LPCWSTR wzPrefix,
197 __in_z LPCWSTR wzExtension,
198 __deref_opt_out_z LPWSTR* ppwzTempFile,
199 __out_opt HANDLE* phTempFile
200 );
201HRESULT DAPI FileVersion(
202 __in_z LPCWSTR wzFilename,
203 __out DWORD *pdwVerMajor,
204 __out DWORD* pdwVerMinor
205 );
206HRESULT DAPI FileIsSame(
207 __in_z LPCWSTR wzFile1,
208 __in_z LPCWSTR wzFile2,
209 __out LPBOOL lpfSameFile
210 );
211HRESULT DAPI FileEnsureDelete(
212 __in_z LPCWSTR wzFile
213 );
214HRESULT DAPI FileGetTime(
215 __in_z LPCWSTR wzFile,
216 __out_opt LPFILETIME lpCreationTime,
217 __out_opt LPFILETIME lpLastAccessTime,
218 __out_opt LPFILETIME lpLastWriteTime
219 );
220HRESULT DAPI FileSetTime(
221 __in_z LPCWSTR wzFile,
222 __in_opt const FILETIME *lpCreationTime,
223 __in_opt const FILETIME *lpLastAccessTime,
224 __in_opt const FILETIME *lpLastWriteTime
225 );
226HRESULT DAPI FileResetTime(
227 __in_z LPCWSTR wzFile
228 );
229HRESULT DAPI FileExecutableArchitecture(
230 __in_z LPCWSTR wzFile,
231 __out FILE_ARCHITECTURE *pArchitecture
232 );
233HRESULT DAPI FileToString(
234 __in_z LPCWSTR wzFile,
235 __out LPWSTR *psczString,
236 __out_opt FILE_ENCODING *pfeEncoding
237 );
238HRESULT DAPI FileFromString(
239 __in_z LPCWSTR wzFile,
240 __in DWORD dwFlagsAndAttributes,
241 __in_z LPCWSTR sczString,
242 __in FILE_ENCODING feEncoding
243 );
244
245#ifdef __cplusplus
246}
247#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/gdiputil.h b/src/libs/dutil/WixToolset.DUtil/inc/gdiputil.h
new file mode 100644
index 00000000..f2145828
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/gdiputil.h
@@ -0,0 +1,39 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#define ExitOnGdipFailureSource(d, g, x, s, ...) { x = GdipHresultFromStatus(g); if (FAILED(x)) { Dutil_RootFailure(__FILE__, __LINE__, x); ExitTraceSource(d, x, s, __VA_ARGS__); goto LExit; } }
6#define ExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_DEFAULT, g, x, s, __VA_ARGS__)
7
8#ifdef __cplusplus
9extern "C" {
10#endif
11
12HRESULT DAPI GdipInitialize(
13 __in const Gdiplus::GdiplusStartupInput* pInput,
14 __out ULONG_PTR* pToken,
15 __out_opt Gdiplus::GdiplusStartupOutput *pOutput
16 );
17
18void DAPI GdipUninitialize(
19 __in ULONG_PTR token
20 );
21
22HRESULT DAPI GdipBitmapFromResource(
23 __in_opt HINSTANCE hinst,
24 __in_z LPCSTR szId,
25 __out Gdiplus::Bitmap **ppBitmap
26 );
27
28HRESULT DAPI GdipBitmapFromFile(
29 __in_z LPCWSTR wzFileName,
30 __out Gdiplus::Bitmap **ppBitmap
31 );
32
33HRESULT DAPI GdipHresultFromStatus(
34 __in Gdiplus::Status gs
35 );
36
37#ifdef __cplusplus
38}
39#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/guidutil.h b/src/libs/dutil/WixToolset.DUtil/inc/guidutil.h
new file mode 100644
index 00000000..478a464f
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/guidutil.h
@@ -0,0 +1,21 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9#define GUID_STRING_LENGTH 39
10
11HRESULT DAPI GuidFixedCreate(
12 _Out_z_cap_c_(GUID_STRING_LENGTH) WCHAR* wzGuid
13 );
14
15HRESULT DAPI GuidCreate(
16 __deref_out_z LPWSTR* psczGuid
17 );
18
19#ifdef __cplusplus
20}
21#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/iis7util.h b/src/libs/dutil/WixToolset.DUtil/inc/iis7util.h
new file mode 100644
index 00000000..3572b4e9
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/iis7util.h
@@ -0,0 +1,222 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9// IIS Config schema names
10#define IIS_CONFIG_ADD L"add"
11#define IIS_CONFIG_ALLOWED L"allowed"
12#define IIS_CONFIG_APPHOST_ROOT L"MACHINE/WEBROOT/APPHOST"
13#define IIS_CONFIG_APPLICATION L"application"
14#define IIS_CONFIG_APPPOOL L"applicationPool"
15#define IIS_CONFIG_APPPOOL_AUTO L"autoStart"
16#define IIS_CONFIG_APPPOOL_SECTION L"system.applicationHost/applicationPools"
17#define IIS_CONFIG_AUTOSTART L"serverAutoStart"
18#define IIS_CONFIG_BINDING L"binding"
19#define IIS_CONFIG_BINDINGINFO L"bindingInformation"
20#define IIS_CONFIG_BINDINGS L"bindings"
21#define IIS_CONFIG_DESC L"description"
22#define IIS_CONFIG_EXECUTABLE L"scriptProcessor"
23#define IIS_CONFIG_ENABLED L"enabled"
24#define IIS_CONFIG_ENABLE32 L"enable32BitAppOnWin64"
25#define IIS_CONFIG_FILEEXT L"fileExtension"
26#define IIS_CONFIG_FILTER L"filter"
27#define IIS_CONFIG_GROUPID L"groupId"
28#define IIS_CONFIG_HEADERS L"customHeaders"
29#define IIS_CONFIG_HTTPERRORS_SECTION L"system.webServer/httpErrors"
30#define IIS_CONFIG_ID L"id"
31#define IIS_CONFIG_ISAPI_SECTION L"system.webServer/isapiFilters"
32#define IIS_CONFIG_HTTPPROTO_SECTION L"system.webServer/httpProtocol"
33#define IIS_CONFIG_LOG_SECTION L"system.applicationHost/log"
34#define IIS_CONFIG_LOG_UTF8 L"logInUTF8"
35#define IIS_CONFIG_LIMITS L"limits"
36#define IIS_CONFIG_PIPELINEMODE L"managedPipelineMode"
37#define IIS_CONFIG_MANAGEDRUNTIMEVERSION L"managedRuntimeVersion"
38#define IIS_CONFIG_WEBLOG L"logFile"
39#define IIS_CONFIG_LOGFORMAT L"logFormat"
40#define IIS_CONFIG_MIMEMAP L"mimeMap"
41#define IIS_CONFIG_MIMETYPE L"mimeType"
42#define IIS_CONFIG_MODULES L"modules"
43#define IIS_CONFIG_NAME L"name"
44#define IIS_CONFIG_PATH L"path"
45#define IIS_CONFIG_PHYSPATH L"physicalPath"
46#define IIS_CONFIG_PROTOCOL L"protocol"
47#define IIS_CONFIG_RESTRICTION_SECTION L"system.webServer/security/isapiCgiRestriction"
48#define IIS_CONFIG_SITE L"site"
49#define IIS_CONFIG_SITE_ID L"id"
50#define IIS_CONFIG_SITES_SECTION L"system.applicationHost/sites"
51#define IIS_CONFIG_CONNECTTIMEOUT L"connectionTimeout"
52#define IIS_CONFIG_VDIR L"virtualDirectory"
53#define IIS_CONFIG_VALUE L"value"
54#define IIS_CONFIG_VERBS L"verb"
55#define IIS_CONFIG_WEBLIMITS_SECTION L"system.applicationHost/webLimits"
56#define IIS_CONFIG_WEBLIMITS_MAXBAND L"maxGlobalBandwidth"
57#define IIS_CONFIG_TRUE L"true"
58#define IIS_CONFIG_FALSE L"false"
59#define IIS_CONFIG_ERROR L"error"
60#define IIS_CONFIG_STATUSCODE L"statusCode"
61#define IIS_CONFIG_SUBSTATUS L"subStatusCode"
62#define IIS_CONFIG_LANGPATH L"prefixLanguageFilePath"
63#define IIS_CONFIG_RESPMODE L"responseMode"
64#define IIS_CONFIG_CLEAR L"clear"
65#define IIS_CONFIG_RECYCLING L"recycling"
66#define IIS_CONFIG_PEROIDRESTART L"periodicRestart"
67#define IIS_CONFIG_TIME L"time"
68#define IIS_CONFIG_REQUESTS L"requests"
69#define IIS_CONFIG_SCHEDULE L"schedule"
70#define IIS_CONFIG_MEMORY L"memory"
71#define IIS_CONFIG_PRIVMEMORY L"privateMemory"
72#define IIS_CONFIG_PROCESSMODEL L"processModel"
73#define IIS_CONFIG_IDLETIMEOUT L"idleTimeout"
74#define IIS_CONFIG_QUEUELENGTH L"queueLength"
75#define IIS_CONFIG_IDENITITYTYPE L"identityType"
76#define IIS_CONFIG_LOCALSYSTEM L"LocalSystem"
77#define IIS_CONFIG_LOCALSERVICE L"LocalService"
78#define IIS_CONFIG_NETWORKSERVICE L"NetworkService"
79#define IIS_CONFIG_SPECIFICUSER L"SpecificUser"
80#define IIS_CONFIG_APPLICATIONPOOLIDENTITY L"ApplicationPoolIdentity"
81#define IIS_CONFIG_USERNAME L"userName"
82#define IIS_CONFIG_PASSWORD L"password"
83#define IIS_CONFIG_CPU L"cpu"
84#define IIS_CONFIG_LIMIT L"limit"
85#define IIS_CONFIG_CPU_ACTION L"action"
86#define IIS_CONFIG_KILLW3WP L"KillW3wp"
87#define IIS_CONFIG_NOACTION L"NoAction"
88#define IIS_CONFIG_RESETINTERVAL L"resetInterval"
89#define IIS_CONFIG_MAXWRKPROCESSES L"maxProcesses"
90#define IIS_CONFIG_HANDLERS_SECTION L"system.webServer/handlers"
91#define IIS_CONFIG_DEFAULTDOC_SECTION L"system.webServer/defaultDocument"
92#define IIS_CONFIG_ASP_SECTION L"system.webServer/asp"
93#define IIS_CONFIG_SCRIPTERROR L"scriptErrorSentToBrowser"
94#define IIS_CONFIG_STATICCONTENT_SECTION L"system.webServer/staticContent"
95#define IIS_CONFIG_HTTPEXPIRES L"httpExpires"
96#define IIS_CONFIG_MAXAGE L"cacheControlMaxAge"
97#define IIS_CONFIG_CLIENTCACHE L"clientCache"
98#define IIS_CONFIG_CACHECONTROLMODE L"cacheControlMode"
99#define IIS_CONFIG_USEMAXAGE L"UseMaxAge"
100#define IIS_CONFIG_USEEXPIRES L"UseExpires"
101#define IIS_CONFIG_CACHECUST L"cacheControlCustom"
102#define IIS_CONFIG_ASP_SECTION L"system.webServer/asp"
103#define IIS_CONFIG_SESSION L"session"
104#define IIS_CONFIG_ALLOWSTATE L"allowSessionState"
105#define IIS_CONFIG_TIMEOUT L"timeout"
106#define IIS_CONFIG_BUFFERING L"bufferingOn"
107#define IIS_CONFIG_PARENTPATHS L"enableParentPaths"
108#define IIS_CONFIG_SCRIPTLANG L"scriptLanguage"
109#define IIS_CONFIG_SCRIPTTIMEOUT L"scriptTimeout"
110#define IIS_CONFIG_LIMITS L"limits"
111#define IIS_CONFIG_ALLOWDEBUG L"appAllowDebugging"
112#define IIS_CONFIG_ALLOWCLIENTDEBUG L"appAllowClientDebug"
113#define IIS_CONFIG_CERTIFICATEHASH L"certificateHash"
114#define IIS_CONFIG_CERTIFICATESTORENAME L"certificateStoreName"
115#define IIS_CONFIG_HTTPLOGGING_SECTION L"system.webServer/httpLogging"
116#define IIS_CONFIG_DONTLOG L"dontLog"
117
118typedef BOOL (CALLBACK* ENUMAPHOSTELEMENTPROC)(IAppHostElement*, LPVOID);
119typedef BOOL (CALLBACK* VARIANTCOMPARATORPROC)(VARIANT*, VARIANT*);
120
121HRESULT DAPI Iis7PutPropertyVariant(
122 __in IAppHostElement *pElement,
123 __in LPCWSTR wzPropName,
124 __in VARIANT vtPut
125 );
126
127HRESULT DAPI Iis7PutPropertyInteger(
128 __in IAppHostElement *pElement,
129 __in LPCWSTR wzPropName,
130 __in DWORD dValue
131 );
132
133HRESULT DAPI Iis7PutPropertyString(
134 __in IAppHostElement *pElement,
135 __in LPCWSTR wzPropName,
136 __in LPCWSTR wzString
137 );
138
139HRESULT DAPI Iis7PutPropertyBool(
140 __in IAppHostElement *pElement,
141 __in LPCWSTR wzPropName,
142 __in BOOL fValue);
143
144HRESULT DAPI Iis7GetPropertyVariant(
145 __in IAppHostElement *pElement,
146 __in LPCWSTR wzPropName,
147 __in VARIANT* vtGet
148 );
149
150HRESULT DAPI Iis7GetPropertyString(
151 __in IAppHostElement *pElement,
152 __in LPCWSTR wzPropName,
153 __in LPWSTR* psczGet
154 );
155
156struct IIS7_APPHOSTELEMENTCOMPARISON
157{
158 LPCWSTR sczElementName;
159 LPCWSTR sczAttributeName;
160 VARIANT* pvAttributeValue;
161 VARIANTCOMPARATORPROC pComparator;
162};
163
164BOOL DAPI Iis7IsMatchingAppHostElement(
165 __in IAppHostElement *pElement,
166 __in IIS7_APPHOSTELEMENTCOMPARISON* pComparison
167 );
168
169HRESULT DAPI Iis7FindAppHostElementString(
170 __in IAppHostElementCollection *pCollection,
171 __in LPCWSTR wzElementName,
172 __in LPCWSTR wzAttributeName,
173 __in LPCWSTR wzAttributeValue,
174 __out IAppHostElement** ppElement,
175 __out DWORD* pdwIndex
176 );
177
178HRESULT DAPI Iis7FindAppHostElementPath(
179 __in IAppHostElementCollection *pCollection,
180 __in LPCWSTR wzElementName,
181 __in LPCWSTR wzAttributeName,
182 __in LPCWSTR wzAttributeValue,
183 __out IAppHostElement** ppElement,
184 __out DWORD* pdwIndex
185 );
186
187HRESULT DAPI Iis7FindAppHostElementInteger(
188 __in IAppHostElementCollection *pCollection,
189 __in LPCWSTR wzElementName,
190 __in LPCWSTR wzAttributeName,
191 __in DWORD dwAttributeValue,
192 __out IAppHostElement** ppElement,
193 __out DWORD* pdwIndex
194 );
195
196HRESULT DAPI Iis7FindAppHostElementVariant(
197 __in IAppHostElementCollection *pCollection,
198 __in LPCWSTR wzElementName,
199 __in LPCWSTR wzAttributeName,
200 __in VARIANT* pvAttributeValue,
201 __out IAppHostElement** ppElement,
202 __out DWORD* pdwIndex
203 );
204
205HRESULT DAPI Iis7EnumAppHostElements(
206 __in IAppHostElementCollection *pCollection,
207 __in ENUMAPHOSTELEMENTPROC pCallback,
208 __in LPVOID pContext,
209 __out IAppHostElement** ppElement,
210 __out DWORD* pdwIndex
211 );
212
213HRESULT DAPI Iis7FindAppHostMethod(
214 __in IAppHostMethodCollection *pCollection,
215 __in LPCWSTR wzMethodName,
216 __out IAppHostMethod** ppMethod,
217 __out DWORD* pdwIndex
218 );
219
220#ifdef __cplusplus
221}
222#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/inetutil.h b/src/libs/dutil/WixToolset.DUtil/inc/inetutil.h
new file mode 100644
index 00000000..19ace88b
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/inetutil.h
@@ -0,0 +1,39 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9#define ReleaseInternet(h) if (h) { ::InternetCloseHandle(h); h = NULL; }
10#define ReleaseNullInternet(h) if (h) { ::InternetCloseHandle(h); h = NULL; }
11
12
13// functions
14HRESULT DAPI InternetGetSizeByHandle(
15 __in HINTERNET hiFile,
16 __out LONGLONG* pllSize
17 );
18
19HRESULT DAPI InternetGetCreateTimeByHandle(
20 __in HINTERNET hiFile,
21 __out LPFILETIME pft
22 );
23
24HRESULT DAPI InternetQueryInfoString(
25 __in HINTERNET h,
26 __in DWORD dwInfo,
27 __deref_out_z LPWSTR* psczValue
28 );
29
30HRESULT DAPI InternetQueryInfoNumber(
31 __in HINTERNET h,
32 __in DWORD dwInfo,
33 __inout LONG* plInfo
34 );
35
36#ifdef __cplusplus
37}
38#endif
39
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/iniutil.h b/src/libs/dutil/WixToolset.DUtil/inc/iniutil.h
new file mode 100644
index 00000000..c8503155
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/iniutil.h
@@ -0,0 +1,79 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9#define ReleaseIni(ih) if (ih) { IniUninitialize(ih); }
10#define ReleaseNullIni(ih) if (ih) { IniUninitialize(ih); ih = NULL; }
11
12typedef void* INI_HANDLE;
13typedef const void* C_INI_HANDLE;
14
15extern const int INI_HANDLE_BYTES;
16
17struct INI_VALUE
18{
19 LPCWSTR wzName;
20 LPCWSTR wzValue;
21
22 DWORD dwLineNumber;
23};
24
25HRESULT DAPI IniInitialize(
26 __out_bcount(INI_HANDLE_BYTES) INI_HANDLE* piHandle
27 );
28void DAPI IniUninitialize(
29 __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle
30 );
31HRESULT DAPI IniSetOpenTag(
32 __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
33 __in_z_opt LPCWSTR wzOpenTagPrefix,
34 __in_z_opt LPCWSTR wzOpenTagPostfix
35 );
36HRESULT DAPI IniSetValueStyle(
37 __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
38 __in_z_opt LPCWSTR wzValuePrefix,
39 __in_z_opt LPCWSTR wzValueSeparator
40 );
41HRESULT DAPI IniSetValueSeparatorException(
42 __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
43 __in_z LPCWSTR wzValueNamePrefix
44 );
45HRESULT DAPI IniSetCommentStyle(
46 __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
47 __in_z_opt LPCWSTR wzLinePrefix
48 );
49HRESULT DAPI IniParse(
50 __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
51 __in LPCWSTR wzPath,
52 __out_opt FILE_ENCODING *pfeEncodingFound
53 );
54// Gets the full value array, this includes values that may have been deleted
55// (their value will be NULL)
56HRESULT DAPI IniGetValueList(
57 __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
58 __deref_out_ecount_opt(*pcValues) INI_VALUE** prgivValues,
59 __out DWORD *pcValues
60 );
61HRESULT DAPI IniGetValue(
62 __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
63 __in LPCWSTR wzValueName,
64 __deref_out_z LPWSTR* psczValue
65 );
66HRESULT DAPI IniSetValue(
67 __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
68 __in LPCWSTR wzValueName,
69 __in_z_opt LPCWSTR wzValue
70 );
71HRESULT DAPI IniWriteFile(
72 __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
73 __in_z_opt LPCWSTR wzPath,
74 __in FILE_ENCODING feOverrideEncoding
75 );
76
77#ifdef __cplusplus
78}
79#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/jsonutil.h b/src/libs/dutil/WixToolset.DUtil/inc/jsonutil.h
new file mode 100644
index 00000000..b05e9970
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/jsonutil.h
@@ -0,0 +1,112 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9typedef enum JSON_TOKEN
10{
11 JSON_TOKEN_NONE,
12 JSON_TOKEN_ARRAY_START,
13 JSON_TOKEN_ARRAY_VALUE,
14 JSON_TOKEN_ARRAY_END,
15 JSON_TOKEN_OBJECT_START,
16 JSON_TOKEN_OBJECT_KEY,
17 JSON_TOKEN_OBJECT_VALUE,
18 JSON_TOKEN_OBJECT_END,
19 JSON_TOKEN_VALUE,
20} JSON_TOKEN;
21
22typedef struct _JSON_VALUE
23{
24} JSON_VALUE;
25
26typedef struct _JSON_READER
27{
28 CRITICAL_SECTION cs;
29 LPWSTR sczJson;
30
31 LPWSTR pwz;
32 JSON_TOKEN token;
33} JSON_READER;
34
35typedef struct _JSON_WRITER
36{
37 CRITICAL_SECTION cs;
38 LPWSTR sczJson;
39
40 JSON_TOKEN* rgTokenStack;
41 DWORD cTokens;
42 DWORD cMaxTokens;
43} JSON_WRITER;
44
45
46DAPI_(HRESULT) JsonInitializeReader(
47 __in_z LPCWSTR wzJson,
48 __in JSON_READER* pReader
49 );
50
51DAPI_(void) JsonUninitializeReader(
52 __in JSON_READER* pReader
53 );
54
55DAPI_(HRESULT) JsonReadNext(
56 __in JSON_READER* pReader,
57 __out JSON_TOKEN* pToken,
58 __out JSON_VALUE* pValue
59 );
60
61DAPI_(HRESULT) JsonReadValue(
62 __in JSON_READER* pReader,
63 __in JSON_VALUE* pValue
64 );
65
66DAPI_(HRESULT) JsonInitializeWriter(
67 __in JSON_WRITER* pWriter
68 );
69
70DAPI_(void) JsonUninitializeWriter(
71 __in JSON_WRITER* pWriter
72 );
73
74DAPI_(HRESULT) JsonWriteBool(
75 __in JSON_WRITER* pWriter,
76 __in BOOL fValue
77 );
78
79DAPI_(HRESULT) JsonWriteNumber(
80 __in JSON_WRITER* pWriter,
81 __in DWORD dwValue
82 );
83
84DAPI_(HRESULT) JsonWriteString(
85 __in JSON_WRITER* pWriter,
86 __in_z LPCWSTR wzValue
87 );
88
89DAPI_(HRESULT) JsonWriteArrayStart(
90 __in JSON_WRITER* pWriter
91 );
92
93DAPI_(HRESULT) JsonWriteArrayEnd(
94 __in JSON_WRITER* pWriter
95 );
96
97DAPI_(HRESULT) JsonWriteObjectStart(
98 __in JSON_WRITER* pWriter
99 );
100
101DAPI_(HRESULT) JsonWriteObjectKey(
102 __in JSON_WRITER* pWriter,
103 __in_z LPCWSTR wzKey
104 );
105
106DAPI_(HRESULT) JsonWriteObjectEnd(
107 __in JSON_WRITER* pWriter
108 );
109
110#ifdef __cplusplus
111}
112#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/locutil.h b/src/libs/dutil/WixToolset.DUtil/inc/locutil.h
new file mode 100644
index 00000000..38ddda20
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/locutil.h
@@ -0,0 +1,120 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9struct LOC_STRING
10{
11 LPWSTR wzId;
12 LPWSTR wzText;
13 BOOL bOverridable;
14};
15
16const int LOC_CONTROL_NOT_SET = INT_MAX;
17
18struct LOC_CONTROL
19{
20 LPWSTR wzControl;
21 int nX;
22 int nY;
23 int nWidth;
24 int nHeight;
25 LPWSTR wzText;
26};
27
28const int WIX_LOCALIZATION_LANGUAGE_NOT_SET = INT_MAX;
29
30struct WIX_LOCALIZATION
31{
32 DWORD dwLangId;
33
34 DWORD cLocStrings;
35 LOC_STRING* rgLocStrings;
36
37 DWORD cLocControls;
38 LOC_CONTROL* rgLocControls;
39};
40
41/********************************************************************
42 LocProbeForFile - Searches for a localization file on disk.
43
44*******************************************************************/
45HRESULT DAPI LocProbeForFile(
46 __in_z LPCWSTR wzBasePath,
47 __in_z LPCWSTR wzLocFileName,
48 __in_z_opt LPCWSTR wzLanguage,
49 __inout LPWSTR* psczPath
50 );
51
52/********************************************************************
53 LocLoadFromFile - Loads a localization file
54
55*******************************************************************/
56HRESULT DAPI LocLoadFromFile(
57 __in_z LPCWSTR wzWxlFile,
58 __out WIX_LOCALIZATION** ppWixLoc
59 );
60
61/********************************************************************
62 LocLoadFromResource - loads a localization file from a module's data
63 resource.
64
65 NOTE: The resource data must be UTF-8 encoded.
66*******************************************************************/
67HRESULT DAPI LocLoadFromResource(
68 __in HMODULE hModule,
69 __in_z LPCSTR szResource,
70 __out WIX_LOCALIZATION** ppWixLoc
71 );
72
73/********************************************************************
74 LocFree - free memory allocated when loading a localization file
75
76*******************************************************************/
77void DAPI LocFree(
78 __in_opt WIX_LOCALIZATION* pWixLoc
79 );
80
81/********************************************************************
82 LocLocalizeString - replace any #(loc.id) in a string with the
83 correct sub string
84*******************************************************************/
85HRESULT DAPI LocLocalizeString(
86 __in const WIX_LOCALIZATION* pWixLoc,
87 __inout LPWSTR* psczInput
88 );
89
90/********************************************************************
91 LocGetControl - returns a control's localization information
92*******************************************************************/
93HRESULT DAPI LocGetControl(
94 __in const WIX_LOCALIZATION* pWixLoc,
95 __in_z LPCWSTR wzId,
96 __out LOC_CONTROL** ppLocControl
97 );
98
99/********************************************************************
100LocGetString - returns a string's localization information
101*******************************************************************/
102extern "C" HRESULT DAPI LocGetString(
103 __in const WIX_LOCALIZATION* pWixLoc,
104 __in_z LPCWSTR wzId,
105 __out LOC_STRING** ppLocString
106 );
107
108/********************************************************************
109LocAddString - adds a localization string
110*******************************************************************/
111extern "C" HRESULT DAPI LocAddString(
112 __in WIX_LOCALIZATION* pWixLoc,
113 __in_z LPCWSTR wzId,
114 __in_z LPCWSTR wzLocString,
115 __in BOOL bOverridable
116 );
117
118#ifdef __cplusplus
119}
120#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/logutil.h b/src/libs/dutil/WixToolset.DUtil/inc/logutil.h
new file mode 100644
index 00000000..426506ee
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/logutil.h
@@ -0,0 +1,192 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9#define LogExitOnFailureSource(d, x, i, f, ...) if (FAILED(x)) { LogErrorId(x, i, __VA_ARGS__); ExitTraceSource(d, x, f, __VA_ARGS__); goto LExit; }
10#define LogExitOnRootFailureSource(d, x, i, f, ...) if (FAILED(x)) { LogErrorId(x, i, __VA_ARGS__); Dutil_RootFailure(__FILE__, __LINE__, x); ExitTraceSource(d, x, f, __VA_ARGS__); goto LExit; }
11
12#define LogExitOnFailure(x, i, f, ...) LogExitOnFailureSource(DUTIL_SOURCE_DEFAULT, x, i, f, __VA_ARGS__)
13#define LogExitOnRootFailure(x, i, f, ...) LogExitOnRootFailureSource(DUTIL_SOURCE_DEFAULT, x, i, f, __VA_ARGS__)
14
15typedef HRESULT (DAPI *PFN_LOGSTRINGWORKRAW)(
16 __in_z LPCSTR szString,
17 __in_opt LPVOID pvContext
18 );
19
20// enums
21
22// structs
23
24// functions
25BOOL DAPI IsLogInitialized();
26
27BOOL DAPI IsLogOpen();
28
29void DAPI LogInitialize(
30 __in HMODULE hModule
31 );
32
33HRESULT DAPI LogOpen(
34 __in_z_opt LPCWSTR wzDirectory,
35 __in_z LPCWSTR wzLog,
36 __in_z_opt LPCWSTR wzPostfix,
37 __in_z_opt LPCWSTR wzExt,
38 __in BOOL fAppend,
39 __in BOOL fHeader,
40 __out_z_opt LPWSTR* psczLogPath
41 );
42
43void DAPI LogDisable();
44
45void DAPI LogRedirect(
46 __in_opt PFN_LOGSTRINGWORKRAW vpfLogStringWorkRaw,
47 __in_opt LPVOID pvContext
48 );
49
50HRESULT DAPI LogRename(
51 __in_z LPCWSTR wzNewPath
52 );
53
54void DAPI LogClose(
55 __in BOOL fFooter
56 );
57
58void DAPI LogUninitialize(
59 __in BOOL fFooter
60 );
61
62BOOL DAPI LogIsOpen();
63
64HRESULT DAPI LogSetSpecialParams(
65 __in_z_opt LPCWSTR wzSpecialBeginLine,
66 __in_z_opt LPCWSTR wzSpecialAfterTimeStamp,
67 __in_z_opt LPCWSTR wzSpecialEndLine
68 );
69
70REPORT_LEVEL DAPI LogSetLevel(
71 __in REPORT_LEVEL rl,
72 __in BOOL fLogChange
73 );
74
75REPORT_LEVEL DAPI LogGetLevel();
76
77HRESULT DAPI LogGetPath(
78 __out_ecount_z(cchLogPath) LPWSTR pwzLogPath,
79 __in DWORD cchLogPath
80 );
81
82HANDLE DAPI LogGetHandle();
83
84HRESULT DAPIV LogString(
85 __in REPORT_LEVEL rl,
86 __in_z __format_string LPCSTR szFormat,
87 ...
88 );
89
90HRESULT DAPI LogStringArgs(
91 __in REPORT_LEVEL rl,
92 __in_z __format_string LPCSTR szFormat,
93 __in va_list args
94 );
95
96HRESULT DAPIV LogStringLine(
97 __in REPORT_LEVEL rl,
98 __in_z __format_string LPCSTR szFormat,
99 ...
100 );
101
102HRESULT DAPI LogStringLineArgs(
103 __in REPORT_LEVEL rl,
104 __in_z __format_string LPCSTR szFormat,
105 __in va_list args
106 );
107
108HRESULT DAPI LogIdModuleArgs(
109 __in REPORT_LEVEL rl,
110 __in DWORD dwLogId,
111 __in_opt HMODULE hModule,
112 __in va_list args
113 );
114
115/*
116 * Wraps LogIdModuleArgs, so inline to save the function call
117 */
118
119inline HRESULT LogId(
120 __in REPORT_LEVEL rl,
121 __in DWORD dwLogId,
122 ...
123 )
124{
125 HRESULT hr = S_OK;
126 va_list args;
127
128 va_start(args, dwLogId);
129 hr = LogIdModuleArgs(rl, dwLogId, NULL, args);
130 va_end(args);
131
132 return hr;
133}
134
135
136/*
137 * Wraps LogIdModuleArgs, so inline to save the function call
138 */
139
140inline HRESULT LogIdArgs(
141 __in REPORT_LEVEL rl,
142 __in DWORD dwLogId,
143 __in va_list args
144 )
145{
146 return LogIdModuleArgs(rl, dwLogId, NULL, args);
147}
148
149HRESULT DAPIV LogErrorString(
150 __in HRESULT hrError,
151 __in_z __format_string LPCSTR szFormat,
152 ...
153 );
154
155HRESULT DAPI LogErrorStringArgs(
156 __in HRESULT hrError,
157 __in_z __format_string LPCSTR szFormat,
158 __in va_list args
159 );
160
161HRESULT DAPI LogErrorIdModule(
162 __in HRESULT hrError,
163 __in DWORD dwLogId,
164 __in_opt HMODULE hModule,
165 __in_z_opt LPCWSTR wzString1,
166 __in_z_opt LPCWSTR wzString2,
167 __in_z_opt LPCWSTR wzString3
168 );
169
170inline HRESULT LogErrorId(
171 __in HRESULT hrError,
172 __in DWORD dwLogId,
173 __in_z_opt LPCWSTR wzString1 = NULL,
174 __in_z_opt LPCWSTR wzString2 = NULL,
175 __in_z_opt LPCWSTR wzString3 = NULL
176 )
177{
178 return LogErrorIdModule(hrError, dwLogId, NULL, wzString1, wzString2, wzString3);
179}
180
181HRESULT DAPI LogHeader();
182
183HRESULT DAPI LogFooter();
184
185HRESULT LogStringWorkRaw(
186 __in_z LPCSTR szLogData
187 );
188
189#ifdef __cplusplus
190}
191#endif
192
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/memutil.h b/src/libs/dutil/WixToolset.DUtil/inc/memutil.h
new file mode 100644
index 00000000..49f86e0a
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/memutil.h
@@ -0,0 +1,80 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9#define ReleaseMem(p) if (p) { MemFree(p); }
10#define ReleaseNullMem(p) if (p) { MemFree(p); p = NULL; }
11
12HRESULT DAPI MemInitialize();
13void DAPI MemUninitialize();
14
15LPVOID DAPI MemAlloc(
16 __in SIZE_T cbSize,
17 __in BOOL fZero
18 );
19LPVOID DAPI MemReAlloc(
20 __in LPVOID pv,
21 __in SIZE_T cbSize,
22 __in BOOL fZero
23 );
24HRESULT DAPI MemReAllocSecure(
25 __in LPVOID pv,
26 __in SIZE_T cbSize,
27 __in BOOL fZero,
28 __deref_out LPVOID* ppvNew
29 );
30HRESULT DAPI MemAllocArray(
31 __inout LPVOID* ppvArray,
32 __in SIZE_T cbArrayType,
33 __in DWORD dwItemCount
34 );
35HRESULT DAPI MemReAllocArray(
36 __inout LPVOID* ppvArray,
37 __in DWORD cArray,
38 __in SIZE_T cbArrayType,
39 __in DWORD dwNewItemCount
40 );
41HRESULT DAPI MemEnsureArraySize(
42 __deref_inout_bcount(cArray * cbArrayType) LPVOID* ppvArray,
43 __in DWORD cArray,
44 __in SIZE_T cbArrayType,
45 __in DWORD dwGrowthCount
46 );
47HRESULT DAPI MemInsertIntoArray(
48 __deref_inout_bcount((cExistingArray + cInsertItems) * cbArrayType) LPVOID* ppvArray,
49 __in DWORD dwInsertIndex,
50 __in DWORD cInsertItems,
51 __in DWORD cExistingArray,
52 __in SIZE_T cbArrayType,
53 __in DWORD dwGrowthCount
54 );
55void DAPI MemRemoveFromArray(
56 __inout_bcount((cExistingArray) * cbArrayType) LPVOID pvArray,
57 __in DWORD dwRemoveIndex,
58 __in DWORD cRemoveItems,
59 __in DWORD cExistingArray,
60 __in SIZE_T cbArrayType,
61 __in BOOL fPreserveOrder
62 );
63void DAPI MemArraySwapItems(
64 __inout_bcount(cbArrayType) LPVOID pvArray,
65 __in DWORD dwIndex1,
66 __in DWORD dwIndex2,
67 __in SIZE_T cbArrayType
68 );
69
70HRESULT DAPI MemFree(
71 __in LPVOID pv
72 );
73SIZE_T DAPI MemSize(
74 __in LPCVOID pv
75 );
76
77#ifdef __cplusplus
78}
79#endif
80
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/metautil.h b/src/libs/dutil/WixToolset.DUtil/inc/metautil.h
new file mode 100644
index 00000000..31f9ff5c
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/metautil.h
@@ -0,0 +1,52 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#include <iadmw.h>
6#include <iiscnfg.h>
7#include <iwamreg.h>
8#include <mddefw.h>
9
10#ifdef __cplusplus
11extern "C" {
12#endif
13
14// structs
15
16// prototypes
17HRESULT DAPI MetaFindWebBase(
18 __in IMSAdminBaseW* piMetabase,
19 __in_z LPCWSTR wzIP,
20 __in int iPort,
21 __in_z LPCWSTR wzHeader,
22 __in BOOL fSecure,
23 __out_ecount(cchWebBase) LPWSTR wzWebBase,
24 __in DWORD cchWebBase
25 );
26HRESULT DAPI MetaFindFreeWebBase(
27 __in IMSAdminBaseW* piMetabase,
28 __out_ecount(cchWebBase) LPWSTR wzWebBase,
29 __in DWORD cchWebBase
30 );
31
32HRESULT DAPI MetaOpenKey(
33 __in IMSAdminBaseW* piMetabase,
34 __in METADATA_HANDLE mhKey,
35 __in_z LPCWSTR wzKey,
36 __in DWORD dwAccess,
37 __in DWORD cRetries,
38 __out METADATA_HANDLE* pmh
39 );
40HRESULT DAPI MetaGetValue(
41 __in IMSAdminBaseW* piMetabase,
42 __in METADATA_HANDLE mhKey,
43 __in_z LPCWSTR wzKey,
44 __inout METADATA_RECORD* pmr
45 );
46void DAPI MetaFreeValue(
47 __in METADATA_RECORD* pmr
48 );
49
50#ifdef __cplusplus
51}
52#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/monutil.h b/src/libs/dutil/WixToolset.DUtil/inc/monutil.h
new file mode 100644
index 00000000..f644e205
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/monutil.h
@@ -0,0 +1,108 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9#define ReleaseMon(mh) if (mh) { MonDestroy(mh); }
10#define ReleaseNullMon(mh) if (mh) { MonDestroy(mh); mh = NULL; }
11
12typedef void* MON_HANDLE;
13typedef const void* C_MON_HANDLE;
14
15// Defined in regutil.h
16enum REG_KEY_BITNESS;
17
18extern const int MON_HANDLE_BYTES;
19
20// Note: callbacks must be implemented in a thread-safe manner. They will be called asynchronously by a MonUtil-spawned thread.
21// They must also be written to return as soon as possible - they are called from the waiter thread
22typedef void (*PFN_MONGENERAL)(
23 __in HRESULT hr,
24 __in_opt LPVOID pvContext
25 );
26// This callback is not specific to any wait - it will notify client of any drive status changes, such as removable drive insertion / removal
27typedef void (*PFN_MONDRIVESTATUS)(
28 __in WCHAR chDrive,
29 __in BOOL fArriving,
30 __in_opt LPVOID pvContext
31 );
32// Note if these fire with a failed result it means an error has occurred with the wait, and so the wait will stay in the list and be retried. When waits start succeeding again,
33// MonUtil will notify of changes, because it may have not noticed changes during the interval for which the wait had failed. This behavior can result in false positive notifications,
34// so all consumers of MonUtil should be designed with this in mind.
35typedef void (*PFN_MONDIRECTORY)(
36 __in HRESULT hr,
37 __in_z LPCWSTR wzPath,
38 __in BOOL fRecursive,
39 __in_opt LPVOID pvContext,
40 __in_opt LPVOID pvDirectoryContext
41 );
42typedef void (*PFN_MONREGKEY)(
43 __in HRESULT hr,
44 __in HKEY hkRoot,
45 __in_z LPCWSTR wzSubKey,
46 __in REG_KEY_BITNESS kbKeyBitness,
47 __in BOOL fRecursive,
48 __in_opt LPVOID pvContext,
49 __in_opt LPVOID pvRegKeyContext
50 );
51
52// Silence period allows you to avoid lots of notifications when a lot of writes are going on in a directory
53// MonUtil will wait until the directory has been "silent" for at least dwSilencePeriodInMs milliseconds
54// The drawback to setting this to a value higher than zero is that even single write notifications
55// are delayed by this amount
56HRESULT DAPI MonCreate(
57 __out_bcount(MON_HANDLE_BYTES) MON_HANDLE *pHandle,
58 __in PFN_MONGENERAL vpfMonGeneral,
59 __in_opt PFN_MONDRIVESTATUS vpfMonDriveStatus,
60 __in_opt PFN_MONDIRECTORY vpfMonDirectory,
61 __in_opt PFN_MONREGKEY vpfMonRegKey,
62 __in_opt LPVOID pvContext
63 );
64// Don't add multiple identical waits! Not only is it wasteful and will cause multiple fires for the exact same change, it will also
65// result in slightly odd behavior when you remove a duplicated wait (removing a wait may or may not remove multiple waits)
66// This is due to the way coordinator thread and waiter threads handle removing, and while it is possible to solve, doing so would complicate the code.
67// So instead, de-dupe your wait requests before sending them to MonUtil.
68// Special notes for network waits: MonUtil can send false positive notifications (i.e. notifications when nothing had changed) if connection
69// to the share is lost and reconnected, because MonUtil can't know for sure whether changes occurred while the connection was lost.
70// Also, MonUtil will very every 20 minutes retry even successful network waits, because the underlying Win32 API cannot notify us if a remote server
71// had its network cable unplugged or similar sudden failure. When we retry the successful network waits, we will also send a false positive notification,
72// because it's impossible for MonUtil to detect if we're reconnecting to a server that had died and come back to life, or if we're reconnecting to a server that had
73// been up all along. For both of the above reasons, clients of MonUtil must be written to do very, very little work in the case of false positive network waits.
74HRESULT DAPI MonAddDirectory(
75 __in_bcount(MON_HANDLE_BYTES) MON_HANDLE handle,
76 __in_z LPCWSTR wzPath,
77 __in BOOL fRecursive,
78 __in DWORD dwSilencePeriodInMs,
79 __in_opt LPVOID pvDirectoryContext
80 );
81HRESULT DAPI MonAddRegKey(
82 __in_bcount(MON_HANDLE_BYTES) MON_HANDLE handle,
83 __in HKEY hkRoot,
84 __in_z LPCWSTR wzSubKey,
85 __in REG_KEY_BITNESS kbKeyBitness,
86 __in BOOL fRecursive,
87 __in DWORD dwSilencePeriodInMs,
88 __in_opt LPVOID pvRegKeyContext
89 );
90HRESULT DAPI MonRemoveDirectory(
91 __in_bcount(MON_HANDLE_BYTES) MON_HANDLE handle,
92 __in_z LPCWSTR wzPath,
93 __in BOOL fRecursive
94 );
95HRESULT DAPI MonRemoveRegKey(
96 __in_bcount(MON_HANDLE_BYTES) MON_HANDLE handle,
97 __in HKEY hkRoot,
98 __in_z LPCWSTR wzSubKey,
99 __in REG_KEY_BITNESS kbKeyBitness,
100 __in BOOL fRecursive
101 );
102void DAPI MonDestroy(
103 __in_bcount(MON_HANDLE_BYTES) MON_HANDLE handle
104 );
105
106#ifdef __cplusplus
107}
108#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/osutil.h b/src/libs/dutil/WixToolset.DUtil/inc/osutil.h
new file mode 100644
index 00000000..2cce6f63
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/osutil.h
@@ -0,0 +1,42 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9typedef enum OS_VERSION
10{
11 OS_VERSION_UNKNOWN,
12 OS_VERSION_WINNT,
13 OS_VERSION_WIN2000,
14 OS_VERSION_WINXP,
15 OS_VERSION_WIN2003,
16 OS_VERSION_VISTA,
17 OS_VERSION_WIN2008,
18 OS_VERSION_WIN7,
19 OS_VERSION_WIN2008_R2,
20 OS_VERSION_FUTURE
21} OS_VERSION;
22
23void DAPI OsGetVersion(
24 __out OS_VERSION* pVersion,
25 __out DWORD* pdwServicePack
26 );
27HRESULT DAPI OsCouldRunPrivileged(
28 __out BOOL* pfPrivileged
29 );
30HRESULT DAPI OsIsRunningPrivileged(
31 __out BOOL* pfPrivileged
32 );
33HRESULT DAPI OsIsUacEnabled(
34 __out BOOL* pfUacEnabled
35 );
36HRESULT DAPI OsRtlGetVersion(
37 __inout RTL_OSVERSIONINFOEXW* pOvix
38 );
39
40#ifdef __cplusplus
41}
42#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h b/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h
new file mode 100644
index 00000000..579b8454
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h
@@ -0,0 +1,252 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9typedef enum PATH_EXPAND
10{
11 PATH_EXPAND_ENVIRONMENT = 0x0001,
12 PATH_EXPAND_FULLPATH = 0x0002,
13} PATH_EXPAND;
14
15
16/*******************************************************************
17 PathCommandLineAppend - appends a command line argument on to a
18 string such that ::CommandLineToArgv() will shred them correctly
19 (i.e. quote arguments with spaces in them).
20********************************************************************/
21DAPI_(HRESULT) PathCommandLineAppend(
22 __deref_inout_z LPWSTR* psczCommandLine,
23 __in_z LPCWSTR wzArgument
24 );
25
26/*******************************************************************
27 PathFile - returns a pointer to the file part of the path.
28********************************************************************/
29DAPI_(LPWSTR) PathFile(
30 __in_z LPCWSTR wzPath
31 );
32
33/*******************************************************************
34 PathExtension - returns a pointer to the extension part of the path
35 (including the dot).
36********************************************************************/
37DAPI_(LPCWSTR) PathExtension(
38 __in_z LPCWSTR wzPath
39 );
40
41/*******************************************************************
42 PathGetDirectory - extracts the directory from a path.
43********************************************************************/
44DAPI_(HRESULT) PathGetDirectory(
45 __in_z LPCWSTR wzPath,
46 __out_z LPWSTR *psczDirectory
47 );
48
49/*******************************************************************
50PathGetParentPath - extracts the parent directory from a full path.
51********************************************************************/
52DAPI_(HRESULT) PathGetParentPath(
53 __in_z LPCWSTR wzPath,
54 __out_z LPWSTR *psczDirectory
55 );
56
57/*******************************************************************
58 PathExpand - gets the full path to a file resolving environment
59 variables along the way.
60********************************************************************/
61DAPI_(HRESULT) PathExpand(
62 __out LPWSTR *psczFullPath,
63 __in_z LPCWSTR wzRelativePath,
64 __in DWORD dwResolveFlags
65 );
66
67/*******************************************************************
68 PathPrefix - prefixes a full path with \\?\ or \\?\UNC as
69 appropriate.
70********************************************************************/
71DAPI_(HRESULT) PathPrefix(
72 __inout LPWSTR *psczFullPath
73 );
74
75/*******************************************************************
76 PathFixedBackslashTerminate - appends a \ if path does not have it
77 already, but fails if the buffer is
78 insufficient.
79********************************************************************/
80DAPI_(HRESULT) PathFixedBackslashTerminate(
81 __inout_ecount_z(cchPath) LPWSTR wzPath,
82 __in SIZE_T cchPath
83 );
84
85/*******************************************************************
86 PathBackslashTerminate - appends a \ if path does not have it
87 already.
88********************************************************************/
89DAPI_(HRESULT) PathBackslashTerminate(
90 __inout LPWSTR* psczPath
91 );
92
93/*******************************************************************
94 PathForCurrentProcess - gets the full path to the currently executing
95 process or (optionally) a module inside the process.
96********************************************************************/
97DAPI_(HRESULT) PathForCurrentProcess(
98 __inout LPWSTR *psczFullPath,
99 __in_opt HMODULE hModule
100 );
101
102/*******************************************************************
103 PathRelativeToModule - gets the name of a file in the same
104 directory as the current process or (optionally) a module inside
105 the process
106********************************************************************/
107DAPI_(HRESULT) PathRelativeToModule(
108 __inout LPWSTR *psczFullPath,
109 __in_opt LPCWSTR wzFileName,
110 __in_opt HMODULE hModule
111 );
112
113/*******************************************************************
114 PathCreateTempFile
115
116 Note: if wzDirectory is null, ::GetTempPath() will be used instead.
117 if wzFileNameTemplate is null, GetTempFileName() will be used instead.
118*******************************************************************/
119DAPI_(HRESULT) PathCreateTempFile(
120 __in_opt LPCWSTR wzDirectory,
121 __in_opt __format_string LPCWSTR wzFileNameTemplate,
122 __in DWORD dwUniqueCount,
123 __in DWORD dwFileAttributes,
124 __out_opt LPWSTR* psczTempFile,
125 __out_opt HANDLE* phTempFile
126 );
127
128/*******************************************************************
129 PathCreateTimeBasedTempFile - creates an empty temp file based on current
130 system time
131********************************************************************/
132DAPI_(HRESULT) PathCreateTimeBasedTempFile(
133 __in_z_opt LPCWSTR wzDirectory,
134 __in_z LPCWSTR wzPrefix,
135 __in_z_opt LPCWSTR wzPostfix,
136 __in_z LPCWSTR wzExtension,
137 __deref_opt_out_z LPWSTR* psczTempFile,
138 __out_opt HANDLE* phTempFile
139 );
140
141/*******************************************************************
142 PathCreateTempDirectory
143
144 Note: if wzDirectory is null, ::GetTempPath() will be used instead.
145*******************************************************************/
146DAPI_(HRESULT) PathCreateTempDirectory(
147 __in_opt LPCWSTR wzDirectory,
148 __in __format_string LPCWSTR wzDirectoryNameTemplate,
149 __in DWORD dwUniqueCount,
150 __out LPWSTR* psczTempDirectory
151 );
152
153/*******************************************************************
154 PathGetKnownFolder - returns the path to a well-known shell folder
155
156*******************************************************************/
157DAPI_(HRESULT) PathGetKnownFolder(
158 __in int csidl,
159 __out LPWSTR* psczKnownFolder
160 );
161
162/*******************************************************************
163 PathIsAbsolute - returns true if the path is absolute; false
164 otherwise.
165*******************************************************************/
166DAPI_(BOOL) PathIsAbsolute(
167 __in_z LPCWSTR wzPath
168 );
169
170/*******************************************************************
171 PathConcat - like .NET's Path.Combine, lets you build up a path
172 one piece -- file or directory -- at a time.
173*******************************************************************/
174DAPI_(HRESULT) PathConcat(
175 __in_opt LPCWSTR wzPath1,
176 __in_opt LPCWSTR wzPath2,
177 __deref_out_z LPWSTR* psczCombined
178 );
179
180/*******************************************************************
181 PathConcatCch - like .NET's Path.Combine, lets you build up a path
182 one piece -- file or directory -- at a time.
183*******************************************************************/
184DAPI_(HRESULT) PathConcatCch(
185 __in_opt LPCWSTR wzPath1,
186 __in SIZE_T cchPath1,
187 __in_opt LPCWSTR wzPath2,
188 __in SIZE_T cchPath2,
189 __deref_out_z LPWSTR* psczCombined
190 );
191
192/*******************************************************************
193 PathEnsureQuoted - ensures that a path is quoted; optionally,
194 this function also terminates a directory with a backslash
195 if it is not already.
196*******************************************************************/
197DAPI_(HRESULT) PathEnsureQuoted(
198 __inout LPWSTR* ppszPath,
199 __in BOOL fDirectory
200 );
201
202/*******************************************************************
203 PathCompare - compares the fully expanded path of the two paths using
204 ::CompareStringW().
205*******************************************************************/
206DAPI_(HRESULT) PathCompare(
207 __in_z LPCWSTR wzPath1,
208 __in_z LPCWSTR wzPath2,
209 __out int* pnResult
210 );
211
212/*******************************************************************
213 PathCompress - sets the compression state on an existing file or
214 directory. A no-op on file systems that don't
215 support compression.
216*******************************************************************/
217DAPI_(HRESULT) PathCompress(
218 __in_z LPCWSTR wzPath
219 );
220
221/*******************************************************************
222 PathGetHierarchyArray - allocates an array containing,
223 in order, every parent directory of the specified path,
224 ending with the actual input path
225 This function also works with registry subkeys
226*******************************************************************/
227DAPI_(HRESULT) PathGetHierarchyArray(
228 __in_z LPCWSTR wzPath,
229 __deref_inout_ecount_opt(*pcPathArray) LPWSTR **prgsczPathArray,
230 __inout LPUINT pcPathArray
231 );
232
233/*******************************************************************
234 PathCanonicalizePath - wrapper around PathCanonicalizeW.
235*******************************************************************/
236DAPI_(HRESULT) PathCanonicalizePath(
237 __in_z LPCWSTR wzPath,
238 __deref_out_z LPWSTR* psczCanonicalized
239 );
240
241/*******************************************************************
242PathDirectoryContainsPath - checks if wzPath is located inside
243 wzDirectory.
244*******************************************************************/
245DAPI_(HRESULT) PathDirectoryContainsPath(
246 __in_z LPCWSTR wzDirectory,
247 __in_z LPCWSTR wzPath
248 );
249
250#ifdef __cplusplus
251}
252#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/perfutil.h b/src/libs/dutil/WixToolset.DUtil/inc/perfutil.h
new file mode 100644
index 00000000..7557511d
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/perfutil.h
@@ -0,0 +1,24 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9// structs
10
11
12// functions
13void DAPI PerfInitialize(
14 );
15void DAPI PerfClickTime(
16 __out_opt LARGE_INTEGER* pliElapsed
17 );
18double DAPI PerfConvertToSeconds(
19 __in const LARGE_INTEGER* pli
20 );
21
22#ifdef __cplusplus
23}
24#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/polcutil.h b/src/libs/dutil/WixToolset.DUtil/inc/polcutil.h
new file mode 100644
index 00000000..13618043
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/polcutil.h
@@ -0,0 +1,39 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9const LPCWSTR POLICY_BURN_REGISTRY_PATH = L"WiX\\Burn";
10
11/********************************************************************
12PolcReadNumber - reads a number from policy.
13
14NOTE: S_FALSE returned if policy not set.
15NOTE: out is set to default on S_FALSE or any error.
16********************************************************************/
17HRESULT DAPI PolcReadNumber(
18 __in_z LPCWSTR wzPolicyPath,
19 __in_z LPCWSTR wzPolicyName,
20 __in DWORD dwDefault,
21 __out DWORD* pdw
22 );
23
24/********************************************************************
25PolcReadString - reads a string from policy.
26
27NOTE: S_FALSE returned if policy not set.
28NOTE: out is set to default on S_FALSE or any error.
29********************************************************************/
30HRESULT DAPI PolcReadString(
31 __in_z LPCWSTR wzPolicyPath,
32 __in_z LPCWSTR wzPolicyName,
33 __in_z_opt LPCWSTR wzDefault,
34 __deref_out_z LPWSTR* pscz
35 );
36
37#ifdef __cplusplus
38}
39#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/procutil.h b/src/libs/dutil/WixToolset.DUtil/inc/procutil.h
new file mode 100644
index 00000000..00f3f358
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/procutil.h
@@ -0,0 +1,75 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9// structs
10typedef struct _PROC_FILESYSTEMREDIRECTION
11{
12 BOOL fDisabled;
13 LPVOID pvRevertState;
14} PROC_FILESYSTEMREDIRECTION;
15
16HRESULT DAPI ProcElevated(
17 __in HANDLE hProcess,
18 __out BOOL* pfElevated
19 );
20
21HRESULT DAPI ProcWow64(
22 __in HANDLE hProcess,
23 __out BOOL* pfWow64
24 );
25HRESULT DAPI ProcDisableWowFileSystemRedirection(
26 __in PROC_FILESYSTEMREDIRECTION* pfsr
27 );
28HRESULT DAPI ProcRevertWowFileSystemRedirection(
29 __in PROC_FILESYSTEMREDIRECTION* pfsr
30 );
31
32HRESULT DAPI ProcExec(
33 __in_z LPCWSTR wzExecutablePath,
34 __in_z_opt LPCWSTR wzCommandLine,
35 __in int nCmdShow,
36 __out HANDLE *phProcess
37 );
38HRESULT DAPI ProcExecute(
39 __in_z LPWSTR wzCommand,
40 __out HANDLE *phProcess,
41 __out_opt HANDLE *phChildStdIn,
42 __out_opt HANDLE *phChildStdOutErr
43 );
44HRESULT DAPI ProcWaitForCompletion(
45 __in HANDLE hProcess,
46 __in DWORD dwTimeout,
47 __out DWORD *pReturnCode
48 );
49HRESULT DAPI ProcWaitForIds(
50 __in_ecount(cProcessIds) const DWORD* pdwProcessIds,
51 __in DWORD cProcessIds,
52 __in DWORD dwMilliseconds
53 );
54HRESULT DAPI ProcCloseIds(
55 __in_ecount(cProcessIds) const DWORD* pdwProcessIds,
56 __in DWORD cProcessIds
57 );
58
59// following code in proc2utl.cpp due to dependency on PSAPI.DLL.
60HRESULT DAPI ProcFindAllIdsFromExeName(
61 __in_z LPCWSTR wzExeName,
62 __out DWORD** ppdwProcessIds,
63 __out DWORD* pcProcessIds
64 );
65
66// following code in proc3utl.cpp due to dependency on Wtsapi32.DLL.
67HRESULT DAPI ProcExecuteAsInteractiveUser(
68 __in_z LPCWSTR wzExecutablePath,
69 __in_z LPCWSTR wzCommand,
70 __out HANDLE *phProcess
71 );
72
73#ifdef __cplusplus
74}
75#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/regutil.h b/src/libs/dutil/WixToolset.DUtil/inc/regutil.h
new file mode 100644
index 00000000..75284940
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/regutil.h
@@ -0,0 +1,246 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9
10#define ReleaseRegKey(h) if (h) { ::RegCloseKey(h); h = NULL; }
11
12typedef enum REG_KEY_BITNESS
13{
14 REG_KEY_DEFAULT = 0,
15 REG_KEY_32BIT = 1,
16 REG_KEY_64BIT = 2
17} REG_KEY_BITNESS;
18
19typedef LSTATUS (APIENTRY *PFN_REGCREATEKEYEXW)(
20 __in HKEY hKey,
21 __in LPCWSTR lpSubKey,
22 __reserved DWORD Reserved,
23 __in_opt LPWSTR lpClass,
24 __in DWORD dwOptions,
25 __in REGSAM samDesired,
26 __in_opt CONST LPSECURITY_ATTRIBUTES lpSecurityAttributes,
27 __out PHKEY phkResult,
28 __out_opt LPDWORD lpdwDisposition
29 );
30typedef LSTATUS (APIENTRY *PFN_REGOPENKEYEXW)(
31 __in HKEY hKey,
32 __in_opt LPCWSTR lpSubKey,
33 __reserved DWORD ulOptions,
34 __in REGSAM samDesired,
35 __out PHKEY phkResult
36 );
37typedef LSTATUS (APIENTRY *PFN_REGDELETEKEYEXW)(
38 __in HKEY hKey,
39 __in LPCWSTR lpSubKey,
40 __in REGSAM samDesired,
41 __reserved DWORD Reserved
42 );
43typedef LSTATUS (APIENTRY *PFN_REGDELETEKEYW)(
44 __in HKEY hKey,
45 __in LPCWSTR lpSubKey
46 );
47typedef LSTATUS (APIENTRY *PFN_REGENUMKEYEXW)(
48 __in HKEY hKey,
49 __in DWORD dwIndex,
50 __out LPWSTR lpName,
51 __inout LPDWORD lpcName,
52 __reserved LPDWORD lpReserved,
53 __inout_opt LPWSTR lpClass,
54 __inout_opt LPDWORD lpcClass,
55 __out_opt PFILETIME lpftLastWriteTime
56 );
57typedef LSTATUS (APIENTRY *PFN_REGENUMVALUEW)(
58 __in HKEY hKey,
59 __in DWORD dwIndex,
60 __out LPWSTR lpValueName,
61 __inout LPDWORD lpcchValueName,
62 __reserved LPDWORD lpReserved,
63 __out_opt LPDWORD lpType,
64 __out_opt LPBYTE lpData,
65 __out_opt LPDWORD lpcbData
66 );
67typedef LSTATUS (APIENTRY *PFN_REGQUERYINFOKEYW)(
68 __in HKEY hKey,
69 __out_opt LPWSTR lpClass,
70 __inout_opt LPDWORD lpcClass,
71 __reserved LPDWORD lpReserved,
72 __out_opt LPDWORD lpcSubKeys,
73 __out_opt LPDWORD lpcMaxSubKeyLen,
74 __out_opt LPDWORD lpcMaxClassLen,
75 __out_opt LPDWORD lpcValues,
76 __out_opt LPDWORD lpcMaxValueNameLen,
77 __out_opt LPDWORD lpcMaxValueLen,
78 __out_opt LPDWORD lpcbSecurityDescriptor,
79 __out_opt PFILETIME lpftLastWriteTime
80 );
81typedef LSTATUS (APIENTRY *PFN_REGQUERYVALUEEXW)(
82 __in HKEY hKey,
83 __in_opt LPCWSTR lpValueName,
84 __reserved LPDWORD lpReserved,
85 __out_opt LPDWORD lpType,
86 __out_bcount_part_opt(*lpcbData, *lpcbData) __out_data_source(REGISTRY) LPBYTE lpData,
87 __inout_opt LPDWORD lpcbData
88 );
89typedef LSTATUS (APIENTRY *PFN_REGSETVALUEEXW)(
90 __in HKEY hKey,
91 __in_opt LPCWSTR lpValueName,
92 __reserved DWORD Reserved,
93 __in DWORD dwType,
94 __in_bcount_opt(cbData) CONST BYTE* lpData,
95 __in DWORD cbData
96 );
97typedef LSTATUS (APIENTRY *PFN_REGDELETEVALUEW)(
98 __in HKEY hKey,
99 __in_opt LPCWSTR lpValueName
100 );
101
102HRESULT DAPI RegInitialize();
103void DAPI RegUninitialize();
104
105void DAPI RegFunctionOverride(
106 __in_opt PFN_REGCREATEKEYEXW pfnRegCreateKeyExW,
107 __in_opt PFN_REGOPENKEYEXW pfnRegOpenKeyExW,
108 __in_opt PFN_REGDELETEKEYEXW pfnRegDeleteKeyExW,
109 __in_opt PFN_REGENUMKEYEXW pfnRegEnumKeyExW,
110 __in_opt PFN_REGENUMVALUEW pfnRegEnumValueW,
111 __in_opt PFN_REGQUERYINFOKEYW pfnRegQueryInfoKeyW,
112 __in_opt PFN_REGQUERYVALUEEXW pfnRegQueryValueExW,
113 __in_opt PFN_REGSETVALUEEXW pfnRegSetValueExW,
114 __in_opt PFN_REGDELETEVALUEW pfnRegDeleteValueW
115 );
116HRESULT DAPI RegCreate(
117 __in HKEY hkRoot,
118 __in_z LPCWSTR wzSubKey,
119 __in DWORD dwAccess,
120 __out HKEY* phk
121 );
122HRESULT DAPI RegCreateEx(
123 __in HKEY hkRoot,
124 __in_z LPCWSTR wzSubKey,
125 __in DWORD dwAccess,
126 __in BOOL fVolatile,
127 __in_opt SECURITY_ATTRIBUTES* pSecurityAttributes,
128 __out HKEY* phk,
129 __out_opt BOOL* pfCreated
130 );
131HRESULT DAPI RegOpen(
132 __in HKEY hkRoot,
133 __in_z LPCWSTR wzSubKey,
134 __in DWORD dwAccess,
135 __out HKEY* phk
136 );
137HRESULT DAPI RegDelete(
138 __in HKEY hkRoot,
139 __in_z LPCWSTR wzSubKey,
140 __in REG_KEY_BITNESS kbKeyBitness,
141 __in BOOL fDeleteTree
142 );
143HRESULT DAPI RegKeyEnum(
144 __in HKEY hk,
145 __in DWORD dwIndex,
146 __deref_out_z LPWSTR* psczKey
147 );
148HRESULT DAPI RegValueEnum(
149 __in HKEY hk,
150 __in DWORD dwIndex,
151 __deref_out_z LPWSTR* psczName,
152 __out_opt DWORD *pdwType
153 );
154HRESULT DAPI RegGetType(
155 __in HKEY hk,
156 __in_z_opt LPCWSTR wzName,
157 __out DWORD *pdwType
158 );
159HRESULT DAPI RegReadBinary(
160 __in HKEY hk,
161 __in_z_opt LPCWSTR wzName,
162 __deref_out_bcount_opt(*pcbBuffer) BYTE** ppbBuffer,
163 __out SIZE_T *pcbBuffer
164 );
165HRESULT DAPI RegReadString(
166 __in HKEY hk,
167 __in_z_opt LPCWSTR wzName,
168 __deref_out_z LPWSTR* psczValue
169 );
170HRESULT DAPI RegReadStringArray(
171 __in HKEY hk,
172 __in_z_opt LPCWSTR wzName,
173 __deref_out_ecount_opt(*pcStrings) LPWSTR** prgsczStrings,
174 __out DWORD *pcStrings
175 );
176HRESULT DAPI RegReadVersion(
177 __in HKEY hk,
178 __in_z_opt LPCWSTR wzName,
179 __out DWORD64* pdw64Version
180 );
181HRESULT DAPI RegReadNumber(
182 __in HKEY hk,
183 __in_z_opt LPCWSTR wzName,
184 __out DWORD* pdwValue
185 );
186HRESULT DAPI RegReadQword(
187 __in HKEY hk,
188 __in_z_opt LPCWSTR wzName,
189 __out DWORD64* pqwValue
190 );
191HRESULT DAPI RegWriteBinary(
192 __in HKEY hk,
193 __in_z_opt LPCWSTR wzName,
194 __in_bcount(cbBuffer) const BYTE *pbBuffer,
195 __in DWORD cbBuffer
196 );
197HRESULT DAPI RegWriteString(
198 __in HKEY hk,
199 __in_z_opt LPCWSTR wzName,
200 __in_z_opt LPCWSTR wzValue
201 );
202HRESULT DAPI RegWriteStringArray(
203 __in HKEY hk,
204 __in_z_opt LPCWSTR wzName,
205 __in_ecount(cStrings) LPWSTR *rgwzStrings,
206 __in DWORD cStrings
207 );
208HRESULT DAPI RegWriteStringFormatted(
209 __in HKEY hk,
210 __in_z_opt LPCWSTR wzName,
211 __in __format_string LPCWSTR szFormat,
212 ...
213 );
214HRESULT DAPI RegWriteNumber(
215 __in HKEY hk,
216 __in_z_opt LPCWSTR wzName,
217 __in DWORD dwValue
218 );
219HRESULT DAPI RegWriteQword(
220 __in HKEY hk,
221 __in_z_opt LPCWSTR wzName,
222 __in DWORD64 qwValue
223 );
224HRESULT DAPI RegQueryKey(
225 __in HKEY hk,
226 __out_opt DWORD* pcSubKeys,
227 __out_opt DWORD* pcValues
228 );
229HRESULT DAPI RegKeyReadNumber(
230 __in HKEY hk,
231 __in_z LPCWSTR wzSubKey,
232 __in_z_opt LPCWSTR wzName,
233 __in BOOL f64Bit,
234 __out DWORD* pdwValue
235 );
236BOOL DAPI RegValueExists(
237 __in HKEY hk,
238 __in_z LPCWSTR wzSubKey,
239 __in_z_opt LPCWSTR wzName,
240 __in BOOL f64Bit
241 );
242
243#ifdef __cplusplus
244}
245#endif
246
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/resrutil.h b/src/libs/dutil/WixToolset.DUtil/inc/resrutil.h
new file mode 100644
index 00000000..1f4d8e17
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/resrutil.h
@@ -0,0 +1,43 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9HRESULT DAPI ResGetStringLangId(
10 __in_opt LPCWSTR wzPath,
11 __in UINT uID,
12 __out WORD *pwLangId
13 );
14
15HRESULT DAPI ResReadString(
16 __in HINSTANCE hinst,
17 __in UINT uID,
18 __deref_out_z LPWSTR* ppwzString
19 );
20
21HRESULT DAPI ResReadStringAnsi(
22 __in HINSTANCE hinst,
23 __in UINT uID,
24 __deref_out_z LPSTR* ppszString
25 );
26
27HRESULT DAPI ResReadData(
28 __in_opt HINSTANCE hinst,
29 __in_z LPCSTR szDataName,
30 __deref_out_bcount(*pcb) PVOID *ppv,
31 __out DWORD *pcb
32 );
33
34HRESULT DAPI ResExportDataToFile(
35 __in_z LPCSTR szDataName,
36 __in_z LPCWSTR wzTargetFile,
37 __in DWORD dwCreationDisposition
38 );
39
40#ifdef __cplusplus
41}
42#endif
43
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/reswutil.h b/src/libs/dutil/WixToolset.DUtil/inc/reswutil.h
new file mode 100644
index 00000000..31435ae2
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/reswutil.h
@@ -0,0 +1,31 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9HRESULT DAPI ResWriteString(
10 __in_z LPCWSTR wzResourceFile,
11 __in DWORD dwDataId,
12 __in_z LPCWSTR wzData,
13 __in WORD wLangId
14 );
15
16HRESULT DAPI ResWriteData(
17 __in_z LPCWSTR wzResourceFile,
18 __in_z LPCSTR szDataName,
19 __in PVOID pData,
20 __in DWORD cbData
21 );
22
23HRESULT DAPI ResImportDataFromFile(
24 __in_z LPCWSTR wzTargetFile,
25 __in_z LPCWSTR wzSourceFile,
26 __in_z LPCSTR szDataName
27 );
28
29#ifdef __cplusplus
30}
31#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/rexutil.h b/src/libs/dutil/WixToolset.DUtil/inc/rexutil.h
new file mode 100644
index 00000000..77f5604a
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/rexutil.h
@@ -0,0 +1,54 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#include <sys\stat.h>
6#include <fdi.h>
7
8#ifdef __cplusplus
9extern "C" {
10#endif
11
12// defines
13#define FILETABLESIZE 40
14
15// structs
16struct MEM_FILE
17{
18 LPCBYTE vpStart;
19 UINT uiCurrent;
20 UINT uiLength;
21};
22
23typedef enum FAKE_FILE_TYPE { NORMAL_FILE, MEMORY_FILE } FAKE_FILE_TYPE;
24
25typedef HRESULT (*REX_CALLBACK_PROGRESS)(BOOL fBeginFile, LPCWSTR wzFileId, LPVOID pvContext);
26typedef VOID (*REX_CALLBACK_WRITE)(UINT cb);
27
28
29struct FAKE_FILE // used __in internal file table
30{
31 BOOL fUsed;
32 FAKE_FILE_TYPE fftType;
33 MEM_FILE mfFile; // State for memory file
34 HANDLE hFile; // Handle for disk file
35};
36
37// functions
38HRESULT RexInitialize();
39void RexUninitialize();
40
41HRESULT RexExtract(
42 __in_z LPCSTR szResource,
43 __in_z LPCWSTR wzExtractId,
44 __in_z LPCWSTR wzExtractDir,
45 __in_z LPCWSTR wzExtractName,
46 __in REX_CALLBACK_PROGRESS pfnProgress,
47 __in REX_CALLBACK_WRITE pfnWrite,
48 __in LPVOID pvContext
49 );
50
51#ifdef __cplusplus
52}
53#endif
54
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/rmutil.h b/src/libs/dutil/WixToolset.DUtil/inc/rmutil.h
new file mode 100644
index 00000000..ce7bf254
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/rmutil.h
@@ -0,0 +1,46 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9typedef struct _RMU_SESSION *PRMU_SESSION;
10
11HRESULT DAPI RmuJoinSession(
12 __out PRMU_SESSION *ppSession,
13 __in_z LPCWSTR wzSessionKey
14 );
15
16HRESULT DAPI RmuAddFile(
17 __in PRMU_SESSION pSession,
18 __in_z LPCWSTR wzPath
19 );
20
21HRESULT DAPI RmuAddProcessById(
22 __in PRMU_SESSION pSession,
23 __in DWORD dwProcessId
24 );
25
26HRESULT DAPI RmuAddProcessesByName(
27 __in PRMU_SESSION pSession,
28 __in_z LPCWSTR wzProcessName
29 );
30
31HRESULT DAPI RmuAddService(
32 __in PRMU_SESSION pSession,
33 __in_z LPCWSTR wzServiceName
34 );
35
36HRESULT DAPI RmuRegisterResources(
37 __in PRMU_SESSION pSession
38 );
39
40HRESULT DAPI RmuEndSession(
41 __in PRMU_SESSION pSession
42 );
43
44#ifdef __cplusplus
45}
46#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/rssutil.h b/src/libs/dutil/WixToolset.DUtil/inc/rssutil.h
new file mode 100644
index 00000000..064ab147
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/rssutil.h
@@ -0,0 +1,89 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9#define ReleaseRssChannel(p) if (p) { RssFreeChannel(p); }
10#define ReleaseNullRssChannel(p) if (p) { RssFreeChannel(p); p = NULL; }
11
12
13struct RSS_UNKNOWN_ATTRIBUTE
14{
15 LPWSTR wzNamespace;
16 LPWSTR wzAttribute;
17 LPWSTR wzValue;
18
19 RSS_UNKNOWN_ATTRIBUTE* pNext;
20};
21
22struct RSS_UNKNOWN_ELEMENT
23{
24 LPWSTR wzNamespace;
25 LPWSTR wzElement;
26 LPWSTR wzValue;
27
28 RSS_UNKNOWN_ATTRIBUTE* pAttributes;
29 RSS_UNKNOWN_ELEMENT* pNext;
30};
31
32struct RSS_ITEM
33{
34 LPWSTR wzTitle;
35 LPWSTR wzLink;
36 LPWSTR wzDescription;
37
38 LPWSTR wzGuid;
39 FILETIME ftPublished;
40
41 LPWSTR wzEnclosureUrl;
42 DWORD dwEnclosureSize;
43 LPWSTR wzEnclosureType;
44
45 RSS_UNKNOWN_ELEMENT* pUnknownElements;
46};
47
48struct RSS_CHANNEL
49{
50 LPWSTR wzTitle;
51 LPWSTR wzLink;
52 LPWSTR wzDescription;
53 DWORD dwTimeToLive;
54
55 RSS_UNKNOWN_ELEMENT* pUnknownElements;
56
57 DWORD cItems;
58 RSS_ITEM rgItems[1];
59};
60
61HRESULT DAPI RssInitialize(
62 );
63
64void DAPI RssUninitialize(
65 );
66
67HRESULT DAPI RssParseFromString(
68 __in_z LPCWSTR wzRssString,
69 __out RSS_CHANNEL **ppChannel
70 );
71
72HRESULT DAPI RssParseFromFile(
73 __in_z LPCWSTR wzRssFile,
74 __out RSS_CHANNEL **ppChannel
75 );
76
77// Adding this until we have the updated specstrings.h
78#ifndef __in_xcount
79#define __in_xcount(size)
80#endif
81
82void DAPI RssFreeChannel(
83 __in_xcount(pChannel->cItems) RSS_CHANNEL *pChannel
84 );
85
86#ifdef __cplusplus
87}
88#endif
89
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/sceutil.h b/src/libs/dutil/WixToolset.DUtil/inc/sceutil.h
new file mode 100644
index 00000000..9d14eecf
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/sceutil.h
@@ -0,0 +1,273 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9#include <sqlce_oledb.h>
10#include <sqlce_sync.h>
11#include <sqlce_err.h>
12
13typedef void* SCE_DATABASE_HANDLE;
14typedef void* SCE_ROW_HANDLE;
15typedef void* SCE_QUERY_HANDLE;
16typedef void* SCE_QUERY_RESULTS_HANDLE;
17
18extern const int SCE_ROW_HANDLE_BYTES;
19extern const int SCE_QUERY_HANDLE_BYTES;
20extern const int SCE_QUERY_RESULTS_HANDLE_BYTES;
21
22#define ReleaseSceRow(prrh) if (prrh) { SceFreeRow(prrh); }
23#define ReleaseNullSceRow(prrh) if (prrh) { SceFreeRow(prrh); prrh = NULL; }
24#define ReleaseSceQuery(pqh) if (pqh) { SceFreeQuery(pqh); }
25#define ReleaseNullSceQuery(pqh) if (pqh) { SceFreeQuery(pqh); pqh = NULL; }
26#define ReleaseSceQueryResults(pqh) if (pqh) { SceFreeQueryResults(pqh); }
27#define ReleaseNullSceQueryResults(pqh) if (pqh) { SceFreeQueryResults(pqh); pqh = NULL; }
28
29struct SCE_COLUMN_SCHEMA
30{
31 LPCWSTR wzName;
32 DBTYPE dbtColumnType;
33 DWORD dwLength;
34 BOOL fPrimaryKey; // If this column is the primary key
35 BOOL fNullable;
36 BOOL fAutoIncrement;
37 BOOL fDescending; // If this column should be descending when used in an index (default is ascending)
38
39 LPWSTR wzRelationName;
40 DWORD dwForeignKeyTable;
41 DWORD dwForeignKeyColumn;
42};
43
44struct SCE_INDEX_SCHEMA
45{
46 LPWSTR wzName;
47
48 DWORD *rgColumns;
49 DWORD cColumns;
50};
51
52struct SCE_TABLE_SCHEMA
53{
54 LPCWSTR wzName;
55 DWORD cColumns;
56 SCE_COLUMN_SCHEMA *rgColumns;
57
58 DWORD cIndexes;
59 SCE_INDEX_SCHEMA *rgIndexes;
60
61 // Internal to SCEUtil - consumers shouldn't access or modify
62 // TODO: enforce / hide in a handle of some sort?
63 IRowset *pIRowset;
64 IRowsetChange *pIRowsetChange;
65};
66
67struct SCE_DATABASE_SCHEMA
68{
69 DWORD cTables;
70 SCE_TABLE_SCHEMA *rgTables;
71};
72
73struct SCE_DATABASE
74{
75 SCE_DATABASE_HANDLE sdbHandle;
76 SCE_DATABASE_SCHEMA *pdsSchema;
77};
78
79HRESULT DAPI SceCreateDatabase(
80 __in_z LPCWSTR sczFile,
81 __in_z_opt LPCWSTR wzSqlCeDllPath,
82 __deref_out SCE_DATABASE **ppDatabase
83 );
84HRESULT DAPI SceOpenDatabase(
85 __in_z LPCWSTR sczFile,
86 __in_z_opt LPCWSTR wzSqlCeDllPath,
87 __in LPCWSTR wzSchemaType,
88 __in DWORD dwExpectedVersion,
89 __deref_out SCE_DATABASE **ppDatabase,
90 __in BOOL fReadOnly
91 );
92HRESULT DAPI SceEnsureDatabase(
93 __in_z LPCWSTR sczFile,
94 __in_z_opt LPCWSTR wzSqlCeDllPath,
95 __in LPCWSTR wzSchemaType,
96 __in DWORD dwExpectedVersion,
97 __in SCE_DATABASE_SCHEMA *pdsSchema,
98 __deref_out SCE_DATABASE **ppDatabase
99 );
100HRESULT DAPI SceIsTableEmpty(
101 __in SCE_DATABASE *pDatabase,
102 __in DWORD dwTableIndex,
103 __out BOOL *pfEmpty
104 );
105HRESULT DAPI SceGetFirstRow(
106 __in SCE_DATABASE *pDatabase,
107 __in DWORD dwTableIndex,
108 __deref_out_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE *pRowHandle
109 );
110HRESULT DAPI SceGetNextRow(
111 __in SCE_DATABASE *pDatabase,
112 __in DWORD dwTableIndex,
113 __deref_out_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE *pRowHandle
114 );
115HRESULT DAPI SceBeginTransaction(
116 __in SCE_DATABASE *pDatabase
117 );
118HRESULT DAPI SceCommitTransaction(
119 __in SCE_DATABASE *pDatabase
120 );
121HRESULT DAPI SceRollbackTransaction(
122 __in SCE_DATABASE *pDatabase
123 );
124HRESULT DAPI SceDeleteRow(
125 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE *pRowHandle
126 );
127HRESULT DAPI ScePrepareInsert(
128 __in SCE_DATABASE *pDatabase,
129 __in DWORD dwTableIndex,
130 __deref_out_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE *pRowHandle
131 );
132HRESULT DAPI SceFinishUpdate(
133 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowHandle
134 );
135HRESULT DAPI SceSetColumnBinary(
136 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowHandle,
137 __in DWORD dwColumnIndex,
138 __in_bcount(cbBuffer) const BYTE* pbBuffer,
139 __in SIZE_T cbBuffer
140 );
141HRESULT DAPI SceSetColumnDword(
142 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowHandle,
143 __in DWORD dwColumnIndex,
144 __in const DWORD dwValue
145 );
146HRESULT DAPI SceSetColumnQword(
147 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowHandle,
148 __in DWORD dwColumnIndex,
149 __in const DWORD64 qwValue
150 );
151HRESULT DAPI SceSetColumnBool(
152 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowHandle,
153 __in DWORD dwColumnIndex,
154 __in const BOOL fValue
155 );
156HRESULT DAPI SceSetColumnString(
157 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowHandle,
158 __in DWORD dwColumnIndex,
159 __in_z_opt LPCWSTR wzValue
160 );
161HRESULT DAPI SceSetColumnSystemTime(
162 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowHandle,
163 __in DWORD dwColumnIndex,
164 __in const SYSTEMTIME *pst
165 );
166HRESULT DAPI SceSetColumnNull(
167 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowHandle,
168 __in DWORD dwColumnIndex
169 );
170HRESULT DAPI SceGetColumnBinary(
171 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowReadHandle,
172 __in DWORD dwColumnIndex,
173 __out_opt BYTE **ppbBuffer,
174 __inout SIZE_T *pcbBuffer
175 );
176HRESULT DAPI SceGetColumnDword(
177 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowReadHandle,
178 __in DWORD dwColumnIndex,
179 __out DWORD *pdwValue
180 );
181HRESULT DAPI SceGetColumnQword(
182 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowReadHandle,
183 __in DWORD dwColumnIndex,
184 __out DWORD64 *pqwValue
185 );
186HRESULT DAPI SceGetColumnBool(
187 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowReadHandle,
188 __in DWORD dwColumnIndex,
189 __out BOOL *pfValue
190 );
191HRESULT DAPI SceGetColumnString(
192 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowReadHandle,
193 __in DWORD dwColumnIndex,
194 __out_z LPWSTR *psczValue
195 );
196HRESULT DAPI SceGetColumnSystemTime(
197 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowReadHandle,
198 __in DWORD dwColumnIndex,
199 __out SYSTEMTIME *pst
200 );
201HRESULT DAPI SceBeginQuery(
202 __in SCE_DATABASE *pDatabase,
203 __in DWORD dwTableIndex,
204 __in DWORD dwIndex,
205 __deref_out_bcount(SCE_QUERY_HANDLE_BYTES) SCE_QUERY_HANDLE *psqhHandle
206 );
207HRESULT DAPI SceSetQueryColumnBinary(
208 __in_bcount(SCE_QUERY_BYTES) SCE_QUERY_HANDLE sqhHandle,
209 __in_bcount(cbBuffer) const BYTE* pbBuffer,
210 __in SIZE_T cbBuffer
211 );
212HRESULT DAPI SceSetQueryColumnDword(
213 __in_bcount(SCE_QUERY_BYTES) SCE_QUERY_HANDLE sqhHandle,
214 __in const DWORD dwValue
215 );
216HRESULT DAPI SceSetQueryColumnQword(
217 __in_bcount(SCE_QUERY_BYTES) SCE_QUERY_HANDLE sqhHandle,
218 __in const DWORD64 qwValue
219 );
220HRESULT DAPI SceSetQueryColumnBool(
221 __in_bcount(SCE_QUERY_BYTES) SCE_QUERY_HANDLE sqhHandle,
222 __in const BOOL fValue
223 );
224HRESULT DAPI SceSetQueryColumnString(
225 __in_bcount(SCE_QUERY_BYTES) SCE_QUERY_HANDLE sqhHandle,
226 __in_z_opt LPCWSTR wzString
227 );
228HRESULT DAPI SceSetQueryColumnSystemTime(
229 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowHandle,
230 __in const SYSTEMTIME *pst
231 );
232HRESULT DAPI SceSetQueryColumnEmpty(
233 __in_bcount(SCE_QUERY_BYTES) SCE_QUERY_HANDLE sqhHandle
234 );
235HRESULT DAPI SceRunQueryExact(
236 __in_bcount(SCE_QUERY_BYTES) SCE_QUERY_HANDLE *psqhHandle,
237 __deref_out_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE *pRowHandle
238 );
239HRESULT DAPI SceRunQueryRange(
240 __in_bcount(SCE_QUERY_BYTES) SCE_QUERY_HANDLE *psqhHandle,
241 __deref_out_bcount(SCE_QUERY_RESULTS_BYTES) SCE_QUERY_RESULTS_HANDLE *psqrhHandle
242 );
243HRESULT DAPI SceGetNextResultRow(
244 __in_bcount(SCE_QUERY_RESULTS_BYTES) SCE_QUERY_RESULTS_HANDLE sqrhHandle,
245 __deref_out_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE *pRowHandle
246 );
247void DAPI SceCloseTable(
248 __in SCE_TABLE_SCHEMA *pTable
249 );
250// Returns whether the data in the database changed. Ignores schema changes.
251BOOL DAPI SceDatabaseChanged(
252 __in SCE_DATABASE *pDatabase
253 );
254// Resets the database changed flag
255void DAPI SceResetDatabaseChanged(
256 __in SCE_DATABASE *pDatabase
257 );
258HRESULT DAPI SceCloseDatabase(
259 __in SCE_DATABASE *pDatabase
260 );
261void DAPI SceFreeRow(
262 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowReadHandle
263 );
264void DAPI SceFreeQuery(
265 __in_bcount(SCE_QUERY_BYTES) SCE_QUERY_HANDLE sqhHandle
266 );
267void DAPI SceFreeQueryResults(
268 __in_bcount(SCE_QUERY_RESULTS_BYTES) SCE_QUERY_RESULTS_HANDLE sqrhHandle
269 );
270
271#ifdef __cplusplus
272}
273#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/sczutil.h b/src/libs/dutil/WixToolset.DUtil/inc/sczutil.h
new file mode 100644
index 00000000..fcfbd13a
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/sczutil.h
@@ -0,0 +1,30 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6class PSCZ
7{
8public:
9 PSCZ() : m_scz(NULL) { }
10
11 ~PSCZ() { ReleaseNullStr(m_scz); }
12
13 operator LPWSTR() { return m_scz; }
14
15 operator LPCWSTR() { return m_scz; }
16
17 operator bool() { return NULL != m_scz; }
18
19 LPWSTR* operator &() { return &m_scz; }
20
21 bool operator !() { return !m_scz; }
22
23 WCHAR operator *() { return *m_scz; }
24
25 LPWSTR Detach() { LPWSTR scz = m_scz; m_scz = NULL; return scz; }
26
27private:
28 LPWSTR m_scz;
29};
30#endif //__cplusplus
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/shelutil.h b/src/libs/dutil/WixToolset.DUtil/inc/shelutil.h
new file mode 100644
index 00000000..0b9f539d
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/shelutil.h
@@ -0,0 +1,47 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifndef REFKNOWNFOLDERID
6#define REFKNOWNFOLDERID REFGUID
7#endif
8
9#ifdef __cplusplus
10extern "C" {
11#endif
12
13typedef BOOL (STDAPICALLTYPE *PFN_SHELLEXECUTEEXW)(
14 __inout LPSHELLEXECUTEINFOW lpExecInfo
15 );
16
17void DAPI ShelFunctionOverride(
18 __in_opt PFN_SHELLEXECUTEEXW pfnShellExecuteExW
19 );
20HRESULT DAPI ShelExec(
21 __in_z LPCWSTR wzTargetPath,
22 __in_z_opt LPCWSTR wzParameters,
23 __in_z_opt LPCWSTR wzVerb,
24 __in_z_opt LPCWSTR wzWorkingDirectory,
25 __in int nShowCmd,
26 __in_opt HWND hwndParent,
27 __out_opt HANDLE* phProcess
28 );
29HRESULT DAPI ShelExecUnelevated(
30 __in_z LPCWSTR wzTargetPath,
31 __in_z_opt LPCWSTR wzParameters,
32 __in_z_opt LPCWSTR wzVerb,
33 __in_z_opt LPCWSTR wzWorkingDirectory,
34 __in int nShowCmd
35 );
36HRESULT DAPI ShelGetFolder(
37 __out_z LPWSTR* psczFolderPath,
38 __in int csidlFolder
39 );
40HRESULT DAPI ShelGetKnownFolder(
41 __out_z LPWSTR* psczFolderPath,
42 __in REFKNOWNFOLDERID rfidFolder
43 );
44
45#ifdef __cplusplus
46}
47#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/sqlutil.h b/src/libs/dutil/WixToolset.DUtil/inc/sqlutil.h
new file mode 100644
index 00000000..ddf09323
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/sqlutil.h
@@ -0,0 +1,136 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#include <cguid.h>
6#include <oledberr.h>
7#include <sqloledb.h>
8
9
10#ifdef __cplusplus
11extern "C" {
12#endif
13
14// Adding this until the SQL annotations are published to specstrings.h
15#ifndef __sql_command
16#define __sql_command
17#endif
18
19// structs
20struct SQL_FILESPEC
21{
22 WCHAR wzName[MAX_PATH];
23 WCHAR wzFilename[MAX_PATH];
24 WCHAR wzSize[MAX_PATH];
25 WCHAR wzMaxSize[MAX_PATH];
26 WCHAR wzGrow[MAX_PATH];
27};
28
29
30// functions
31HRESULT DAPI SqlConnectDatabase(
32 __in_z LPCWSTR wzServer,
33 __in_z LPCWSTR wzInstance,
34 __in_z LPCWSTR wzDatabase,
35 __in BOOL fIntegratedAuth,
36 __in_z LPCWSTR wzUser,
37 __in_z LPCWSTR wzPassword,
38 __out IDBCreateSession** ppidbSession
39 );
40HRESULT DAPI SqlStartTransaction(
41 __in IDBCreateSession* pidbSession,
42 __out IDBCreateCommand** ppidbCommand,
43 __out ITransaction** ppit
44 );
45HRESULT DAPI SqlEndTransaction(
46 __in ITransaction* pit,
47 __in BOOL fCommit
48 );
49HRESULT DAPI SqlDatabaseExists(
50 __in_z LPCWSTR wzServer,
51 __in_z LPCWSTR wzInstance,
52 __in_z LPCWSTR wzDatabase,
53 __in BOOL fIntegratedAuth,
54 __in_z LPCWSTR wzUser,
55 __in_z LPCWSTR wzPassword,
56 __out_opt BSTR* pbstrErrorDescription
57 );
58HRESULT DAPI SqlSessionDatabaseExists(
59 __in IDBCreateSession* pidbSession,
60 __in_z LPCWSTR wzDatabase,
61 __out_opt BSTR* pbstrErrorDescription
62 );
63HRESULT DAPI SqlDatabaseEnsureExists(
64 __in_z LPCWSTR wzServer,
65 __in_z LPCWSTR wzInstance,
66 __in_z LPCWSTR wzDatabase,
67 __in BOOL fIntegratedAuth,
68 __in_z LPCWSTR wzUser,
69 __in_z LPCWSTR wzPassword,
70 __in_opt const SQL_FILESPEC* psfDatabase,
71 __in_opt const SQL_FILESPEC* psfLog,
72 __out_opt BSTR* pbstrErrorDescription
73 );
74HRESULT DAPI SqlSessionDatabaseEnsureExists(
75 __in IDBCreateSession* pidbSession,
76 __in_z LPCWSTR wzDatabase,
77 __in_opt const SQL_FILESPEC* psfDatabase,
78 __in_opt const SQL_FILESPEC* psfLog,
79 __out_opt BSTR* pbstrErrorDescription
80 );
81HRESULT DAPI SqlCreateDatabase(
82 __in_z LPCWSTR wzServer,
83 __in_z LPCWSTR wzInstance,
84 __in_z LPCWSTR wzDatabase,
85 __in BOOL fIntegratedAuth,
86 __in_z LPCWSTR wzUser,
87 __in_z LPCWSTR wzPassword,
88 __in_opt const SQL_FILESPEC* psfDatabase,
89 __in_opt const SQL_FILESPEC* psfLog,
90 __out_opt BSTR* pbstrErrorDescription
91 );
92HRESULT DAPI SqlSessionCreateDatabase(
93 __in IDBCreateSession* pidbSession,
94 __in_z LPCWSTR wzDatabase,
95 __in_opt const SQL_FILESPEC* psfDatabase,
96 __in_opt const SQL_FILESPEC* psfLog,
97 __out_opt BSTR* pbstrErrorDescription
98 );
99HRESULT DAPI SqlDropDatabase(
100 __in_z LPCWSTR wzServer,
101 __in_z LPCWSTR wzInstance,
102 __in_z LPCWSTR wzDatabase,
103 __in BOOL fIntegratedAuth,
104 __in_z LPCWSTR wzUser,
105 __in_z LPCWSTR wzPassword,
106 __out_opt BSTR* pbstrErrorDescription
107 );
108HRESULT DAPI SqlSessionDropDatabase(
109 __in IDBCreateSession* pidbSession,
110 __in_z LPCWSTR wzDatabase,
111 __out_opt BSTR* pbstrErrorDescription
112 );
113HRESULT DAPI SqlSessionExecuteQuery(
114 __in IDBCreateSession* pidbSession,
115 __in __sql_command LPCWSTR wzSql,
116 __out_opt IRowset** ppirs,
117 __out_opt DBROWCOUNT* pcRows,
118 __out_opt BSTR* pbstrErrorDescription
119 );
120HRESULT DAPI SqlCommandExecuteQuery(
121 __in IDBCreateCommand* pidbCommand,
122 __in __sql_command LPCWSTR wzSql,
123 __out IRowset** ppirs,
124 __out DBROWCOUNT* pcRows
125 );
126HRESULT DAPI SqlGetErrorInfo(
127 __in IUnknown* pObjectWithError,
128 __in REFIID IID_InterfaceWithError,
129 __in DWORD dwLocaleId,
130 __out_opt BSTR* pbstrErrorSource,
131 __out_opt BSTR* pbstrErrorDescription
132 );
133
134#ifdef __cplusplus
135}
136#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/srputil.h b/src/libs/dutil/WixToolset.DUtil/inc/srputil.h
new file mode 100644
index 00000000..95e96231
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/srputil.h
@@ -0,0 +1,45 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9
10typedef enum SRP_ACTION
11{
12 SRP_ACTION_UNKNOWN,
13 SRP_ACTION_UNINSTALL,
14 SRP_ACTION_INSTALL,
15 SRP_ACTION_MODIFY,
16} SRP_ACTION;
17
18
19/********************************************************************
20 SrpInitialize - initializes system restore point functionality.
21
22*******************************************************************/
23DAPI_(HRESULT) SrpInitialize(
24 __in BOOL fInitializeComSecurity
25 );
26
27/********************************************************************
28 SrpUninitialize - uninitializes system restore point functionality.
29
30*******************************************************************/
31DAPI_(void) SrpUninitialize();
32
33/********************************************************************
34 SrpCreateRestorePoint - creates a system restore point.
35
36*******************************************************************/
37DAPI_(HRESULT) SrpCreateRestorePoint(
38 __in_z LPCWSTR wzApplicationName,
39 __in SRP_ACTION action
40 );
41
42#ifdef __cplusplus
43}
44#endif
45
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/strutil.h b/src/libs/dutil/WixToolset.DUtil/inc/strutil.h
new file mode 100644
index 00000000..1cff9ab8
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/strutil.h
@@ -0,0 +1,316 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9#define ReleaseStr(pwz) if (pwz) { StrFree(pwz); }
10#define ReleaseNullStr(pwz) if (pwz) { StrFree(pwz); pwz = NULL; }
11#define ReleaseBSTR(bstr) if (bstr) { ::SysFreeString(bstr); }
12#define ReleaseNullBSTR(bstr) if (bstr) { ::SysFreeString(bstr); bstr = NULL; }
13#define ReleaseStrArray(rg, c) { if (rg) { StrArrayFree(rg, c); } }
14#define ReleaseNullStrArray(rg, c) { if (rg) { StrArrayFree(rg, c); c = 0; rg = NULL; } }
15#define ReleaseNullStrSecure(pwz) if (pwz) { StrSecureZeroFreeString(pwz); pwz = NULL; }
16
17#define DeclareConstBSTR(bstr_const, wz) const WCHAR bstr_const[] = { 0x00, 0x00, sizeof(wz)-sizeof(WCHAR), 0x00, wz }
18#define UseConstBSTR(bstr_const) const_cast<BSTR>(bstr_const + 4)
19
20HRESULT DAPI StrAlloc(
21 __deref_out_ecount_part(cch, 0) LPWSTR* ppwz,
22 __in SIZE_T cch
23 );
24HRESULT DAPI StrAllocSecure(
25 __deref_out_ecount_part(cch, 0) LPWSTR* ppwz,
26 __in SIZE_T cch
27 );
28HRESULT DAPI StrTrimCapacity(
29 __deref_out_z LPWSTR* ppwz
30 );
31HRESULT DAPI StrTrimWhitespace(
32 __deref_out_z LPWSTR* ppwz,
33 __in_z LPCWSTR wzSource
34 );
35HRESULT DAPI StrAnsiAlloc(
36 __deref_out_ecount_part(cch, 0) LPSTR* ppz,
37 __in SIZE_T cch
38 );
39HRESULT DAPI StrAnsiTrimCapacity(
40 __deref_out_z LPSTR* ppz
41 );
42HRESULT DAPI StrAnsiTrimWhitespace(
43 __deref_out_z LPSTR* ppz,
44 __in_z LPCSTR szSource
45 );
46HRESULT DAPI StrAllocString(
47 __deref_out_ecount_z(cchSource+1) LPWSTR* ppwz,
48 __in_z LPCWSTR wzSource,
49 __in SIZE_T cchSource
50 );
51HRESULT DAPI StrAllocStringSecure(
52 __deref_out_ecount_z(cchSource + 1) LPWSTR* ppwz,
53 __in_z LPCWSTR wzSource,
54 __in SIZE_T cchSource
55 );
56HRESULT DAPI StrAnsiAllocString(
57 __deref_out_ecount_z(cchSource+1) LPSTR* ppsz,
58 __in_z LPCWSTR wzSource,
59 __in SIZE_T cchSource,
60 __in UINT uiCodepage
61 );
62HRESULT DAPI StrAllocStringAnsi(
63 __deref_out_ecount_z(cchSource+1) LPWSTR* ppwz,
64 __in_z LPCSTR szSource,
65 __in SIZE_T cchSource,
66 __in UINT uiCodepage
67 );
68HRESULT DAPI StrAnsiAllocStringAnsi(
69 __deref_out_ecount_z(cchSource+1) LPSTR* ppsz,
70 __in_z LPCSTR szSource,
71 __in SIZE_T cchSource
72 );
73HRESULT DAPI StrAllocPrefix(
74 __deref_out_z LPWSTR* ppwz,
75 __in_z LPCWSTR wzPrefix,
76 __in SIZE_T cchPrefix
77 );
78HRESULT DAPI StrAllocConcat(
79 __deref_out_z LPWSTR* ppwz,
80 __in_z LPCWSTR wzSource,
81 __in SIZE_T cchSource
82 );
83HRESULT DAPI StrAllocConcatSecure(
84 __deref_out_z LPWSTR* ppwz,
85 __in_z LPCWSTR wzSource,
86 __in SIZE_T cchSource
87 );
88HRESULT DAPI StrAnsiAllocConcat(
89 __deref_out_z LPSTR* ppz,
90 __in_z LPCSTR pzSource,
91 __in SIZE_T cchSource
92 );
93HRESULT __cdecl StrAllocFormatted(
94 __deref_out_z LPWSTR* ppwz,
95 __in __format_string LPCWSTR wzFormat,
96 ...
97 );
98HRESULT __cdecl StrAllocConcatFormatted(
99 __deref_out_z LPWSTR* ppwz,
100 __in __format_string LPCWSTR wzFormat,
101 ...
102 );
103HRESULT __cdecl StrAllocConcatFormattedSecure(
104 __deref_out_z LPWSTR* ppwz,
105 __in __format_string LPCWSTR wzFormat,
106 ...
107 );
108HRESULT __cdecl StrAllocFormattedSecure(
109 __deref_out_z LPWSTR* ppwz,
110 __in __format_string LPCWSTR wzFormat,
111 ...
112 );
113HRESULT __cdecl StrAnsiAllocFormatted(
114 __deref_out_z LPSTR* ppsz,
115 __in __format_string LPCSTR szFormat,
116 ...
117 );
118HRESULT DAPI StrAllocFormattedArgs(
119 __deref_out_z LPWSTR* ppwz,
120 __in __format_string LPCWSTR wzFormat,
121 __in va_list args
122 );
123HRESULT DAPI StrAllocFormattedArgsSecure(
124 __deref_out_z LPWSTR* ppwz,
125 __in __format_string LPCWSTR wzFormat,
126 __in va_list args
127 );
128HRESULT DAPI StrAnsiAllocFormattedArgs(
129 __deref_out_z LPSTR* ppsz,
130 __in __format_string LPCSTR szFormat,
131 __in va_list args
132 );
133HRESULT DAPI StrAllocFromError(
134 __inout LPWSTR *ppwzMessage,
135 __in HRESULT hrError,
136 __in_opt HMODULE hModule,
137 ...
138 );
139
140HRESULT DAPI StrMaxLength(
141 __in LPCVOID p,
142 __out SIZE_T* pcbch
143 );
144HRESULT DAPI StrSize(
145 __in LPCVOID p,
146 __out SIZE_T* pcbb
147 );
148
149HRESULT DAPI StrFree(
150 __in LPVOID p
151 );
152
153
154HRESULT DAPI StrReplaceStringAll(
155 __inout LPWSTR* ppwzOriginal,
156 __in_z LPCWSTR wzOldSubString,
157 __in_z LPCWSTR wzNewSubString
158 );
159HRESULT DAPI StrReplaceString(
160 __inout LPWSTR* ppwzOriginal,
161 __inout DWORD* pdwStartIndex,
162 __in_z LPCWSTR wzOldSubString,
163 __in_z LPCWSTR wzNewSubString
164 );
165
166HRESULT DAPI StrHexEncode(
167 __in_ecount(cbSource) const BYTE* pbSource,
168 __in SIZE_T cbSource,
169 __out_ecount(cchDest) LPWSTR wzDest,
170 __in SIZE_T cchDest
171 );
172HRESULT DAPI StrAllocHexEncode(
173 __in_ecount(cbSource) const BYTE* pbSource,
174 __in SIZE_T cbSource,
175 __deref_out_ecount_z(2*(cbSource+1)) LPWSTR* ppwzDest
176 );
177HRESULT DAPI StrHexDecode(
178 __in_z LPCWSTR wzSource,
179 __out_bcount(cbDest) BYTE* pbDest,
180 __in SIZE_T cbDest
181 );
182HRESULT DAPI StrAllocHexDecode(
183 __in_z LPCWSTR wzSource,
184 __out_bcount(*pcbDest) BYTE** ppbDest,
185 __out_opt DWORD* pcbDest
186 );
187
188HRESULT DAPI StrAllocBase85Encode(
189 __in_bcount_opt(cbSource) const BYTE* pbSource,
190 __in SIZE_T cbSource,
191 __deref_out_z LPWSTR* pwzDest
192 );
193HRESULT DAPI StrAllocBase85Decode(
194 __in_z LPCWSTR wzSource,
195 __deref_out_bcount(*pcbDest) BYTE** ppbDest,
196 __out SIZE_T* pcbDest
197);
198
199HRESULT DAPI MultiSzLen(
200 __in_ecount(*pcch) __nullnullterminated LPCWSTR pwzMultiSz,
201 __out SIZE_T* pcch
202 );
203HRESULT DAPI MultiSzPrepend(
204 __deref_inout_ecount(*pcchMultiSz) __nullnullterminated LPWSTR* ppwzMultiSz,
205 __inout_opt SIZE_T* pcchMultiSz,
206 __in __nullnullterminated LPCWSTR pwzInsert
207 );
208HRESULT DAPI MultiSzFindSubstring(
209 __in __nullnullterminated LPCWSTR pwzMultiSz,
210 __in __nullnullterminated LPCWSTR pwzSubstring,
211 __out_opt DWORD_PTR* pdwIndex,
212 __deref_opt_out __nullnullterminated LPCWSTR* ppwzFoundIn
213 );
214HRESULT DAPI MultiSzFindString(
215 __in __nullnullterminated LPCWSTR pwzMultiSz,
216 __in __nullnullterminated LPCWSTR pwzString,
217 __out_opt DWORD_PTR* pdwIndex,
218 __deref_opt_out __nullnullterminated LPCWSTR* ppwzFound
219 );
220HRESULT DAPI MultiSzRemoveString(
221 __deref_inout __nullnullterminated LPWSTR* ppwzMultiSz,
222 __in DWORD_PTR dwIndex
223 );
224HRESULT DAPI MultiSzInsertString(
225 __deref_inout __nullnullterminated LPWSTR* ppwzMultiSz,
226 __inout_opt SIZE_T* pcchMultiSz,
227 __in DWORD_PTR dwIndex,
228 __in_z LPCWSTR pwzInsert
229 );
230HRESULT DAPI MultiSzReplaceString(
231 __deref_inout __nullnullterminated LPWSTR* ppwzMultiSz,
232 __in DWORD_PTR dwIndex,
233 __in_z LPCWSTR pwzString
234 );
235
236LPCWSTR DAPI wcsistr(
237 __in_z LPCWSTR wzString,
238 __in_z LPCWSTR wzCharSet
239 );
240
241HRESULT DAPI StrStringToInt16(
242 __in_z LPCWSTR wzIn,
243 __in DWORD cchIn,
244 __out SHORT* psOut
245 );
246HRESULT DAPI StrStringToUInt16(
247 __in_z LPCWSTR wzIn,
248 __in DWORD cchIn,
249 __out USHORT* pusOut
250 );
251HRESULT DAPI StrStringToInt32(
252 __in_z LPCWSTR wzIn,
253 __in DWORD cchIn,
254 __out INT* piOut
255 );
256HRESULT DAPI StrStringToUInt32(
257 __in_z LPCWSTR wzIn,
258 __in DWORD cchIn,
259 __out UINT* puiOut
260 );
261HRESULT DAPI StrStringToInt64(
262 __in_z LPCWSTR wzIn,
263 __in DWORD cchIn,
264 __out LONGLONG* pllOut
265 );
266HRESULT DAPI StrStringToUInt64(
267 __in_z LPCWSTR wzIn,
268 __in DWORD cchIn,
269 __out ULONGLONG* pullOut
270 );
271void DAPI StrStringToUpper(
272 __inout_z LPWSTR wzIn
273 );
274void DAPI StrStringToLower(
275 __inout_z LPWSTR wzIn
276 );
277HRESULT DAPI StrAllocStringToUpperInvariant(
278 __deref_out_z LPWSTR* pscz,
279 __in_z LPCWSTR wzSource,
280 __in SIZE_T cchSource
281 );
282HRESULT DAPI StrAllocStringToLowerInvariant(
283 __deref_out_z LPWSTR* pscz,
284 __in_z LPCWSTR wzSource,
285 __in SIZE_T cchSource
286 );
287
288HRESULT DAPI StrArrayAllocString(
289 __deref_inout_ecount_opt(*pcStrArray) LPWSTR **prgsczStrArray,
290 __inout LPUINT pcStrArray,
291 __in_z LPCWSTR wzSource,
292 __in SIZE_T cchSource
293 );
294
295HRESULT DAPI StrArrayFree(
296 __in_ecount(cStrArray) LPWSTR *rgsczStrArray,
297 __in UINT cStrArray
298 );
299
300HRESULT DAPI StrSplitAllocArray(
301 __deref_inout_ecount_opt(*pcStrArray) LPWSTR **prgsczStrArray,
302 __inout LPUINT pcStrArray,
303 __in_z LPCWSTR wzSource,
304 __in_z LPCWSTR wzDelim
305 );
306
307HRESULT DAPI StrSecureZeroString(
308 __in LPWSTR pwz
309 );
310HRESULT DAPI StrSecureZeroFreeString(
311 __in LPWSTR pwz
312 );
313
314#ifdef __cplusplus
315}
316#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/svcutil.h b/src/libs/dutil/WixToolset.DUtil/inc/svcutil.h
new file mode 100644
index 00000000..80d6326c
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/svcutil.h
@@ -0,0 +1,21 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9
10#define ReleaseServiceHandle(h) if (h) { ::CloseServiceHandle(h); h = NULL; }
11
12
13HRESULT DAPI SvcQueryConfig(
14 __in SC_HANDLE sch,
15 __out QUERY_SERVICE_CONFIGW** ppConfig
16 );
17
18
19#ifdef __cplusplus
20}
21#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/thmutil.h b/src/libs/dutil/WixToolset.DUtil/inc/thmutil.h
new file mode 100644
index 00000000..d3dd6d21
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/thmutil.h
@@ -0,0 +1,765 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9#define ReleaseTheme(p) if (p) { ThemeFree(p); p = NULL; }
10
11typedef HRESULT(CALLBACK *PFNTHM_EVALUATE_VARIABLE_CONDITION)(
12 __in_z LPCWSTR wzCondition,
13 __out BOOL* pf,
14 __in_opt LPVOID pvContext
15 );
16typedef HRESULT(CALLBACK *PFNTHM_FORMAT_VARIABLE_STRING)(
17 __in_z LPCWSTR wzFormat,
18 __inout LPWSTR* psczOut,
19 __in_opt LPVOID pvContext
20 );
21typedef HRESULT(CALLBACK *PFNTHM_GET_VARIABLE_NUMERIC)(
22 __in_z LPCWSTR wzVariable,
23 __out LONGLONG* pllValue,
24 __in_opt LPVOID pvContext
25 );
26typedef HRESULT(CALLBACK *PFNTHM_SET_VARIABLE_NUMERIC)(
27 __in_z LPCWSTR wzVariable,
28 __in LONGLONG llValue,
29 __in_opt LPVOID pvContext
30 );
31typedef HRESULT(CALLBACK *PFNTHM_GET_VARIABLE_STRING)(
32 __in_z LPCWSTR wzVariable,
33 __inout LPWSTR* psczValue,
34 __in_opt LPVOID pvContext
35 );
36typedef HRESULT(CALLBACK *PFNTHM_SET_VARIABLE_STRING)(
37 __in_z LPCWSTR wzVariable,
38 __in_z_opt LPCWSTR wzValue,
39 __in BOOL fFormatted,
40 __in_opt LPVOID pvContext
41 );
42
43typedef enum THEME_ACTION_TYPE
44{
45 THEME_ACTION_TYPE_BROWSE_DIRECTORY,
46 THEME_ACTION_TYPE_CHANGE_PAGE,
47 THEME_ACTION_TYPE_CLOSE_WINDOW,
48} THEME_ACTION_TYPE;
49
50typedef enum THEME_CONTROL_DATA
51{
52 THEME_CONTROL_DATA_HOVER = 1,
53} THEME_CONTROL_DATA;
54
55typedef enum THEME_CONTROL_TYPE
56{
57 THEME_CONTROL_TYPE_UNKNOWN,
58 THEME_CONTROL_TYPE_BILLBOARD,
59 THEME_CONTROL_TYPE_BUTTON,
60 THEME_CONTROL_TYPE_CHECKBOX,
61 THEME_CONTROL_TYPE_COMBOBOX,
62 THEME_CONTROL_TYPE_COMMANDLINK,
63 THEME_CONTROL_TYPE_EDITBOX,
64 THEME_CONTROL_TYPE_HYPERLINK,
65 THEME_CONTROL_TYPE_HYPERTEXT,
66 THEME_CONTROL_TYPE_IMAGE,
67 THEME_CONTROL_TYPE_LABEL,
68 THEME_CONTROL_TYPE_PANEL,
69 THEME_CONTROL_TYPE_PROGRESSBAR,
70 THEME_CONTROL_TYPE_RADIOBUTTON,
71 THEME_CONTROL_TYPE_RICHEDIT,
72 THEME_CONTROL_TYPE_STATIC,
73 THEME_CONTROL_TYPE_LISTVIEW,
74 THEME_CONTROL_TYPE_TREEVIEW,
75 THEME_CONTROL_TYPE_TAB,
76} THEME_CONTROL_TYPE;
77
78typedef enum THEME_SHOW_PAGE_REASON
79{
80 THEME_SHOW_PAGE_REASON_DEFAULT,
81 THEME_SHOW_PAGE_REASON_CANCEL,
82 THEME_SHOW_PAGE_REASON_REFRESH,
83} THEME_SHOW_PAGE_REASON;
84
85typedef enum THEME_WINDOW_INITIAL_POSITION
86{
87 THEME_WINDOW_INITIAL_POSITION_DEFAULT,
88 THEME_WINDOW_INITIAL_POSITION_CENTER_MONITOR_FROM_COORDINATES,
89} THEME_WINDOW_INITIAL_POSITION;
90
91
92struct THEME_COLUMN
93{
94 LPWSTR pszName;
95 UINT uStringId;
96 int nDefaultDpiBaseWidth;
97 int nBaseWidth;
98 int nWidth;
99 BOOL fExpands;
100};
101
102
103struct THEME_TAB
104{
105 LPWSTR pszName;
106 UINT uStringId;
107};
108
109struct THEME_ACTION
110{
111 LPWSTR sczCondition;
112 THEME_ACTION_TYPE type;
113 union
114 {
115 struct
116 {
117 LPWSTR sczVariableName;
118 } BrowseDirectory;
119 struct
120 {
121 LPWSTR sczPageName;
122 BOOL fCancel;
123 } ChangePage;
124 };
125};
126
127struct THEME_CONDITIONAL_TEXT
128{
129 LPWSTR sczCondition;
130 LPWSTR sczText;
131};
132
133// THEME_ASSIGN_CONTROL_ID - Used to apply a specific id to a named control (usually
134// to set the WM_COMMAND).
135struct THEME_ASSIGN_CONTROL_ID
136{
137 WORD wId; // id to apply to control
138 LPCWSTR wzName; // name of control to match
139};
140
141const DWORD THEME_FIRST_ASSIGN_CONTROL_ID = 1024; // Recommended first control id to be assigned.
142
143struct THEME_CONTROL
144{
145 THEME_CONTROL_TYPE type;
146
147 WORD wId;
148 WORD wPageId;
149
150 LPWSTR sczName; // optional name for control, used to apply control id and link the control to a variable.
151 LPWSTR sczText;
152 LPWSTR sczTooltip;
153 LPWSTR sczNote; // optional text for command link
154 int nDefaultDpiX;
155 int nDefaultDpiY;
156 int nDefaultDpiHeight;
157 int nDefaultDpiWidth;
158 int nX;
159 int nY;
160 int nHeight;
161 int nWidth;
162 int nSourceX;
163 int nSourceY;
164 UINT uStringId;
165
166 LPWSTR sczEnableCondition;
167 LPWSTR sczVisibleCondition;
168 BOOL fDisableVariableFunctionality;
169
170 HBITMAP hImage;
171 HICON hIcon;
172
173 // Don't free these; it's just a handle to the central image lists stored in THEME. The handle is freed once, there.
174 HIMAGELIST rghImageList[4];
175
176 DWORD dwStyle;
177 DWORD dwExtendedStyle;
178 DWORD dwInternalStyle;
179
180 DWORD dwFontId;
181
182 // child controls
183 DWORD cControls;
184 THEME_CONTROL* rgControls;
185
186 // Used by billboard controls
187 WORD wBillboardInterval;
188 BOOL fBillboardLoops;
189
190 // Used by button and command link controls
191 THEME_ACTION* rgActions;
192 DWORD cActions;
193 THEME_ACTION* pDefaultAction;
194
195 // Used by hyperlink and owner-drawn button controls
196 DWORD dwFontHoverId;
197 DWORD dwFontSelectedId;
198
199 // Used by listview controls
200 THEME_COLUMN *ptcColumns;
201 DWORD cColumns;
202
203 // Used by radio button controls
204 BOOL fLastRadioButton;
205 LPWSTR sczValue;
206 LPWSTR sczVariable;
207
208 // Used by tab controls
209 THEME_TAB *pttTabs;
210 DWORD cTabs;
211
212 // Used by controls that have text
213 DWORD cConditionalText;
214 THEME_CONDITIONAL_TEXT* rgConditionalText;
215
216 // Used by command link controls
217 DWORD cConditionalNotes;
218 THEME_CONDITIONAL_TEXT* rgConditionalNotes;
219
220 // state variables that should be ignored
221 HWND hWnd;
222 DWORD dwData; // type specific data
223};
224
225
226struct THEME_IMAGELIST
227{
228 LPWSTR sczName;
229
230 HIMAGELIST hImageList;
231};
232
233struct THEME_SAVEDVARIABLE
234{
235 LPWSTR wzName;
236 LPWSTR sczValue;
237};
238
239struct THEME_PAGE
240{
241 WORD wId;
242 LPWSTR sczName;
243
244 DWORD cControlIndices;
245
246 DWORD cSavedVariables;
247 THEME_SAVEDVARIABLE* rgSavedVariables;
248};
249
250struct THEME_FONT_INSTANCE
251{
252 UINT nDpi;
253 HFONT hFont;
254};
255
256struct THEME_FONT
257{
258 LONG lfHeight;
259 LONG lfWeight;
260 BYTE lfUnderline;
261 BYTE lfQuality;
262 LPWSTR sczFaceName;
263
264 COLORREF crForeground;
265 HBRUSH hForeground;
266 COLORREF crBackground;
267 HBRUSH hBackground;
268
269 DWORD cFontInstances;
270 THEME_FONT_INSTANCE* rgFontInstances;
271};
272
273
274struct THEME
275{
276 WORD wId;
277
278 BOOL fAutoResize;
279 BOOL fForceResize;
280
281 DWORD dwStyle;
282 DWORD dwFontId;
283 HANDLE hIcon;
284 LPWSTR sczCaption;
285 int nDefaultDpiHeight;
286 int nDefaultDpiMinimumHeight;
287 int nDefaultDpiWidth;
288 int nDefaultDpiMinimumWidth;
289 int nHeight;
290 int nMinimumHeight;
291 int nWidth;
292 int nMinimumWidth;
293 int nWindowHeight;
294 int nWindowWidth;
295 int nSourceX;
296 int nSourceY;
297 UINT uStringId;
298
299 HBITMAP hImage;
300
301 DWORD cFonts;
302 THEME_FONT* rgFonts;
303
304 DWORD cPages;
305 THEME_PAGE* rgPages;
306
307 DWORD cImageLists;
308 THEME_IMAGELIST* rgImageLists;
309
310 DWORD cControls;
311 THEME_CONTROL* rgControls;
312
313 // internal state variables -- do not use outside ThmUtil.cpp
314 HWND hwndParent; // parent for loaded controls
315 HWND hwndHover; // current hwnd hovered over
316 DWORD dwCurrentPageId;
317 HWND hwndTooltip;
318
319 UINT nDpi;
320
321 // callback functions
322 PFNTHM_EVALUATE_VARIABLE_CONDITION pfnEvaluateCondition;
323 PFNTHM_FORMAT_VARIABLE_STRING pfnFormatString;
324 PFNTHM_GET_VARIABLE_NUMERIC pfnGetNumericVariable;
325 PFNTHM_SET_VARIABLE_NUMERIC pfnSetNumericVariable;
326 PFNTHM_GET_VARIABLE_STRING pfnGetStringVariable;
327 PFNTHM_SET_VARIABLE_STRING pfnSetStringVariable;
328
329 LPVOID pvVariableContext;
330};
331
332
333/********************************************************************
334 ThemeInitialize - initialized theme management.
335
336*******************************************************************/
337HRESULT DAPI ThemeInitialize(
338 __in_opt HMODULE hModule
339 );
340
341/********************************************************************
342 ThemeUninitialize - uninitialize theme management.
343
344*******************************************************************/
345void DAPI ThemeUninitialize();
346
347/********************************************************************
348 ThemeLoadFromFile - loads a theme from a loose file.
349
350 *******************************************************************/
351HRESULT DAPI ThemeLoadFromFile(
352 __in_z LPCWSTR wzThemeFile,
353 __out THEME** ppTheme
354 );
355
356/********************************************************************
357 ThemeLoadFromResource - loads a theme from a module's data resource.
358
359 NOTE: The resource data must be UTF-8 encoded.
360*******************************************************************/
361HRESULT DAPI ThemeLoadFromResource(
362 __in_opt HMODULE hModule,
363 __in_z LPCSTR szResource,
364 __out THEME** ppTheme
365 );
366
367/********************************************************************
368 ThemeFree - frees any memory associated with a theme.
369
370*******************************************************************/
371void DAPI ThemeFree(
372 __in THEME* pTheme
373 );
374
375/********************************************************************
376ThemeRegisterVariableCallbacks - registers a context and callbacks
377 for working with variables.
378
379*******************************************************************/
380HRESULT DAPI ThemeRegisterVariableCallbacks(
381 __in THEME* pTheme,
382 __in_opt PFNTHM_EVALUATE_VARIABLE_CONDITION pfnEvaluateCondition,
383 __in_opt PFNTHM_FORMAT_VARIABLE_STRING pfnFormatString,
384 __in_opt PFNTHM_GET_VARIABLE_NUMERIC pfnGetNumericVariable,
385 __in_opt PFNTHM_SET_VARIABLE_NUMERIC pfnSetNumericVariable,
386 __in_opt PFNTHM_GET_VARIABLE_STRING pfnGetStringVariable,
387 __in_opt PFNTHM_SET_VARIABLE_STRING pfnSetStringVariable,
388 __in_opt LPVOID pvContext
389 );
390
391/********************************************************************
392 ThemeCreateParentWindow - creates a parent window for the theme.
393
394*******************************************************************/
395HRESULT DAPI ThemeCreateParentWindow(
396 __in THEME* pTheme,
397 __in DWORD dwExStyle,
398 __in LPCWSTR szClassName,
399 __in LPCWSTR szWindowName,
400 __in DWORD dwStyle,
401 __in int x,
402 __in int y,
403 __in_opt HWND hwndParent,
404 __in_opt HINSTANCE hInstance,
405 __in_opt LPVOID lpParam,
406 __in THEME_WINDOW_INITIAL_POSITION initialPosition,
407 __out_opt HWND* phWnd
408 );
409
410/********************************************************************
411 ThemeLoadControls - creates the windows for all the theme controls
412 using the window created in ThemeCreateParentWindow.
413
414*******************************************************************/
415HRESULT DAPI ThemeLoadControls(
416 __in THEME* pTheme,
417 __in_ecount_opt(cAssignControlIds) const THEME_ASSIGN_CONTROL_ID* rgAssignControlIds,
418 __in DWORD cAssignControlIds
419 );
420
421/********************************************************************
422 ThemeUnloadControls - resets all the theme control windows so the theme
423 controls can be reloaded.
424
425*******************************************************************/
426void DAPI ThemeUnloadControls(
427 __in THEME* pTheme
428 );
429
430/********************************************************************
431 ThemeLocalize - Localizes all of the strings in the theme.
432
433*******************************************************************/
434HRESULT DAPI ThemeLocalize(
435 __in THEME *pTheme,
436 __in const WIX_LOCALIZATION *pLocStringSet
437 );
438
439HRESULT DAPI ThemeLoadStrings(
440 __in THEME* pTheme,
441 __in HMODULE hResModule
442 );
443
444/********************************************************************
445 ThemeLoadRichEditFromFile - Attach a richedit control to a RTF file.
446
447 *******************************************************************/
448HRESULT DAPI ThemeLoadRichEditFromFile(
449 __in THEME* pTheme,
450 __in DWORD dwControl,
451 __in_z LPCWSTR wzFileName,
452 __in HMODULE hModule
453 );
454
455/********************************************************************
456 ThemeLoadRichEditFromResource - Attach a richedit control to resource data.
457
458 *******************************************************************/
459HRESULT DAPI ThemeLoadRichEditFromResource(
460 __in THEME* pTheme,
461 __in DWORD dwControl,
462 __in_z LPCSTR szResourceName,
463 __in HMODULE hModule
464 );
465
466/********************************************************************
467 ThemeLoadRichEditFromResourceToHWnd - Attach a richedit control (by
468 HWND) to resource data.
469
470 *******************************************************************/
471HRESULT DAPI ThemeLoadRichEditFromResourceToHWnd(
472 __in HWND hWnd,
473 __in_z LPCSTR szResourceName,
474 __in HMODULE hModule
475 );
476
477/********************************************************************
478 ThemeHandleKeyboardMessage - will translate the message using the active
479 accelerator table.
480
481*******************************************************************/
482BOOL DAPI ThemeHandleKeyboardMessage(
483 __in_opt THEME* pTheme,
484 __in HWND hWnd,
485 __in MSG* pMsg
486 );
487
488/********************************************************************
489 ThemeDefWindowProc - replacement for DefWindowProc() when using theme.
490
491*******************************************************************/
492LRESULT CALLBACK ThemeDefWindowProc(
493 __in_opt THEME* pTheme,
494 __in HWND hWnd,
495 __in UINT uMsg,
496 __in WPARAM wParam,
497 __in LPARAM lParam
498 );
499
500/********************************************************************
501 ThemeGetPageIds - gets the page ids for the theme via page names.
502
503*******************************************************************/
504void DAPI ThemeGetPageIds(
505 __in const THEME* pTheme,
506 __in_ecount(cGetPages) LPCWSTR* rgwzFindNames,
507 __inout_ecount(cGetPages) DWORD* rgdwPageIds,
508 __in DWORD cGetPages
509 );
510
511/********************************************************************
512 ThemeGetPage - gets a theme page by id.
513
514 *******************************************************************/
515THEME_PAGE* DAPI ThemeGetPage(
516 __in const THEME* pTheme,
517 __in DWORD dwPage
518 );
519
520/********************************************************************
521 ThemeShowPage - shows or hides all of the controls in the page at one time.
522
523 *******************************************************************/
524HRESULT DAPI ThemeShowPage(
525 __in THEME* pTheme,
526 __in DWORD dwPage,
527 __in int nCmdShow
528 );
529
530/********************************************************************
531ThemeShowPageEx - shows or hides all of the controls in the page at one time.
532 When using variables, TSPR_CANCEL reverts any changes made.
533 TSPR_REFRESH forces reevaluation of conditions.
534 It is expected that the current page is hidden before
535 showing a new page.
536
537*******************************************************************/
538HRESULT DAPI ThemeShowPageEx(
539 __in THEME* pTheme,
540 __in DWORD dwPage,
541 __in int nCmdShow,
542 __in THEME_SHOW_PAGE_REASON reason
543 );
544
545
546/********************************************************************
547ThemeShowChild - shows a control's specified child control, hiding the rest.
548
549*******************************************************************/
550void DAPI ThemeShowChild(
551 __in THEME* pTheme,
552 __in THEME_CONTROL* pParentControl,
553 __in DWORD dwIndex
554 );
555
556/********************************************************************
557 ThemeControlExists - check if a control with the specified id exists.
558
559 *******************************************************************/
560BOOL DAPI ThemeControlExists(
561 __in const THEME* pTheme,
562 __in DWORD dwControl
563 );
564
565/********************************************************************
566 ThemeControlEnable - enables/disables a control.
567
568 *******************************************************************/
569void DAPI ThemeControlEnable(
570 __in THEME* pTheme,
571 __in DWORD dwControl,
572 __in BOOL fEnable
573 );
574
575/********************************************************************
576 ThemeControlEnabled - returns whether a control is enabled/disabled.
577
578 *******************************************************************/
579BOOL DAPI ThemeControlEnabled(
580 __in THEME* pTheme,
581 __in DWORD dwControl
582 );
583
584/********************************************************************
585 ThemeControlElevates - sets/removes the shield icon on a control.
586
587 *******************************************************************/
588void DAPI ThemeControlElevates(
589 __in THEME* pTheme,
590 __in DWORD dwControl,
591 __in BOOL fElevates
592 );
593
594/********************************************************************
595 ThemeShowControl - shows/hides a control.
596
597 *******************************************************************/
598void DAPI ThemeShowControl(
599 __in THEME* pTheme,
600 __in DWORD dwControl,
601 __in int nCmdShow
602 );
603
604/********************************************************************
605ThemeShowControlEx - shows/hides a control with support for
606conditional text and notes.
607
608*******************************************************************/
609void DAPI ThemeShowControlEx(
610 __in THEME* pTheme,
611 __in DWORD dwControl,
612 __in int nCmdShow
613 );
614
615/********************************************************************
616 ThemeControlVisible - returns whether a control is visible.
617
618 *******************************************************************/
619BOOL DAPI ThemeControlVisible(
620 __in THEME* pTheme,
621 __in DWORD dwControl
622 );
623
624BOOL DAPI ThemePostControlMessage(
625 __in THEME* pTheme,
626 __in DWORD dwControl,
627 __in UINT Msg,
628 __in WPARAM wParam,
629 __in LPARAM lParam
630 );
631
632LRESULT DAPI ThemeSendControlMessage(
633 __in const THEME* pTheme,
634 __in DWORD dwControl,
635 __in UINT Msg,
636 __in WPARAM wParam,
637 __in LPARAM lParam
638 );
639
640/********************************************************************
641 ThemeDrawBackground - draws the theme background.
642
643*******************************************************************/
644HRESULT DAPI ThemeDrawBackground(
645 __in THEME* pTheme,
646 __in PAINTSTRUCT* pps
647 );
648
649/********************************************************************
650 ThemeDrawControl - draw an owner drawn control.
651
652*******************************************************************/
653HRESULT DAPI ThemeDrawControl(
654 __in THEME* pTheme,
655 __in DRAWITEMSTRUCT* pdis
656 );
657
658/********************************************************************
659 ThemeHoverControl - mark a control as hover.
660
661*******************************************************************/
662BOOL DAPI ThemeHoverControl(
663 __in THEME* pTheme,
664 __in HWND hwndParent,
665 __in HWND hwndControl
666 );
667
668/********************************************************************
669 ThemeIsControlChecked - gets whether a control is checked. Only
670 really useful for checkbox controls.
671
672*******************************************************************/
673BOOL DAPI ThemeIsControlChecked(
674 __in THEME* pTheme,
675 __in DWORD dwControl
676 );
677
678/********************************************************************
679 ThemeSetControlColor - sets the color of text for a control.
680
681*******************************************************************/
682BOOL DAPI ThemeSetControlColor(
683 __in THEME* pTheme,
684 __in HDC hdc,
685 __in HWND hWnd,
686 __out HBRUSH* phBackgroundBrush
687 );
688
689/********************************************************************
690 ThemeSetProgressControl - sets the current percentage complete in a
691 progress bar control.
692
693*******************************************************************/
694HRESULT DAPI ThemeSetProgressControl(
695 __in THEME* pTheme,
696 __in DWORD dwControl,
697 __in DWORD dwProgressPercentage
698 );
699
700/********************************************************************
701 ThemeSetProgressControlColor - sets the current color of a
702 progress bar control.
703
704*******************************************************************/
705HRESULT DAPI ThemeSetProgressControlColor(
706 __in THEME* pTheme,
707 __in DWORD dwControl,
708 __in DWORD dwColorIndex
709 );
710
711/********************************************************************
712 ThemeSetTextControl - sets the text of a control.
713
714*******************************************************************/
715HRESULT DAPI ThemeSetTextControl(
716 __in const THEME* pTheme,
717 __in DWORD dwControl,
718 __in_z_opt LPCWSTR wzText
719 );
720
721/********************************************************************
722ThemeSetTextControl - sets the text of a control and optionally
723 invalidates the control.
724
725*******************************************************************/
726HRESULT DAPI ThemeSetTextControlEx(
727 __in const THEME* pTheme,
728 __in DWORD dwControl,
729 __in BOOL fUpdate,
730 __in_z_opt LPCWSTR wzText
731 );
732
733/********************************************************************
734 ThemeGetTextControl - gets the text of a control.
735
736*******************************************************************/
737HRESULT DAPI ThemeGetTextControl(
738 __in const THEME* pTheme,
739 __in DWORD dwControl,
740 __inout_z LPWSTR* psczText
741 );
742
743/********************************************************************
744 ThemeUpdateCaption - updates the caption in the theme.
745
746*******************************************************************/
747HRESULT DAPI ThemeUpdateCaption(
748 __in THEME* pTheme,
749 __in_z LPCWSTR wzCaption
750 );
751
752/********************************************************************
753 ThemeSetFocus - set the focus to the control supplied or the next
754 enabled control if it is disabled.
755
756*******************************************************************/
757void DAPI ThemeSetFocus(
758 __in THEME* pTheme,
759 __in DWORD dwControl
760 );
761
762#ifdef __cplusplus
763}
764#endif
765
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/timeutil.h b/src/libs/dutil/WixToolset.DUtil/inc/timeutil.h
new file mode 100644
index 00000000..3655c00a
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/timeutil.h
@@ -0,0 +1,38 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9HRESULT DAPI TimeFromString(
10 __in_z LPCWSTR wzTime,
11 __out FILETIME* pFileTime
12 );
13HRESULT DAPI TimeFromString3339(
14 __in_z LPCWSTR wzTime,
15 __out FILETIME* pFileTime
16 );
17HRESULT DAPI TimeCurrentTime(
18 __deref_out_z LPWSTR* ppwz,
19 __in BOOL fGMT
20 );
21HRESULT DAPI TimeCurrentDateTime(
22 __deref_out_z LPWSTR* ppwz,
23 __in BOOL fGMT
24 );
25HRESULT DAPI TimeSystemDateTime(
26 __deref_out_z LPWSTR* ppwz,
27 __in const SYSTEMTIME *pst,
28 __in BOOL fGMT
29 );
30HRESULT DAPI TimeSystemToDateTimeString(
31 __deref_out_z LPWSTR* ppwz,
32 __in const SYSTEMTIME *pst,
33 __in LCID locale
34 );
35
36#ifdef __cplusplus
37}
38#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/uncutil.h b/src/libs/dutil/WixToolset.DUtil/inc/uncutil.h
new file mode 100644
index 00000000..6516a801
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/uncutil.h
@@ -0,0 +1,20 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9/*******************************************************************
10 UncConvertFromMountedDrive - Converts the string in-place from a
11 mounted drive path to a UNC path
12*******************************************************************/
13DAPI_(HRESULT) UncConvertFromMountedDrive(
14 __inout LPWSTR *psczUNCPath,
15 __in LPCWSTR sczMountedDrivePath
16 );
17
18#ifdef __cplusplus
19}
20#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/uriutil.h b/src/libs/dutil/WixToolset.DUtil/inc/uriutil.h
new file mode 100644
index 00000000..d6dfdd6b
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/uriutil.h
@@ -0,0 +1,100 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#include "wininet.h"
6
7
8#ifdef __cplusplus
9extern "C" {
10#endif
11
12typedef enum URI_PROTOCOL
13{
14 URI_PROTOCOL_UNKNOWN,
15 URI_PROTOCOL_FILE,
16 URI_PROTOCOL_FTP,
17 URI_PROTOCOL_HTTP,
18 URI_PROTOCOL_HTTPS,
19 URI_PROTOCOL_LOCAL,
20 URI_PROTOCOL_UNC
21} URI_PROTOCOL;
22
23typedef struct _URI_INFO
24{
25 INTERNET_SCHEME scheme;
26 LPWSTR sczHostName;
27 INTERNET_PORT port;
28 LPWSTR sczUser;
29 LPWSTR sczPassword;
30 LPWSTR sczPath;
31 LPWSTR sczQueryString;
32} URI_INFO;
33
34
35HRESULT DAPI UriCanonicalize(
36 __inout_z LPWSTR* psczUri
37 );
38
39HRESULT DAPI UriCrack(
40 __in_z LPCWSTR wzUri,
41 __out_opt INTERNET_SCHEME* pScheme,
42 __deref_opt_out_z LPWSTR* psczHostName,
43 __out_opt INTERNET_PORT* pPort,
44 __deref_opt_out_z LPWSTR* psczUser,
45 __deref_opt_out_z LPWSTR* psczPassword,
46 __deref_opt_out_z LPWSTR* psczPath,
47 __deref_opt_out_z LPWSTR* psczQueryString
48 );
49
50HRESULT DAPI UriCrackEx(
51 __in_z LPCWSTR wzUri,
52 __in URI_INFO* pUriInfo
53 );
54
55void DAPI UriInfoUninitialize(
56 __in URI_INFO* pUriInfo
57 );
58
59HRESULT DAPI UriCreate(
60 __inout_z LPWSTR* psczUri,
61 __in INTERNET_SCHEME scheme,
62 __in_z_opt LPWSTR wzHostName,
63 __in INTERNET_PORT port,
64 __in_z_opt LPWSTR wzUser,
65 __in_z_opt LPWSTR wzPassword,
66 __in_z_opt LPWSTR wzPath,
67 __in_z_opt LPWSTR wzQueryString
68 );
69
70HRESULT DAPI UriCanonicalize(
71 __inout_z LPWSTR* psczUri
72 );
73
74HRESULT DAPI UriFile(
75 __deref_out_z LPWSTR* psczFile,
76 __in_z LPCWSTR wzUri
77 );
78
79HRESULT DAPI UriProtocol(
80 __in_z LPCWSTR wzUri,
81 __out URI_PROTOCOL* pProtocol
82 );
83
84HRESULT DAPI UriRoot(
85 __in_z LPCWSTR wzUri,
86 __out LPWSTR* ppwzRoot,
87 __out_opt URI_PROTOCOL* pProtocol
88 );
89
90HRESULT DAPI UriResolve(
91 __in_z LPCWSTR wzUri,
92 __in_opt LPCWSTR wzBaseUri,
93 __out LPWSTR* ppwzResolvedUri,
94 __out_opt URI_PROTOCOL* pResolvedProtocol
95 );
96
97#ifdef __cplusplus
98}
99#endif
100
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/userutil.h b/src/libs/dutil/WixToolset.DUtil/inc/userutil.h
new file mode 100644
index 00000000..2c86d229
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/userutil.h
@@ -0,0 +1,32 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9HRESULT DAPI UserBuildDomainUserName(
10 __out_ecount_z(cchDest) LPWSTR wzDest,
11 __in int cchDest,
12 __in_z LPCWSTR pwzName,
13 __in_z LPCWSTR pwzDomain
14 );
15
16HRESULT DAPI UserCheckIsMember(
17 __in_z LPCWSTR pwzName,
18 __in_z LPCWSTR pwzDomain,
19 __in_z LPCWSTR pwzGroupName,
20 __in_z LPCWSTR pwzGroupDomain,
21 __out LPBOOL lpfMember
22 );
23
24HRESULT DAPI UserCreateADsPath(
25 __in_z LPCWSTR wzObjectDomain,
26 __in_z LPCWSTR wzObjectName,
27 __out BSTR *pbstrAdsPath
28 );
29
30#ifdef __cplusplus
31}
32#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/verutil.h b/src/libs/dutil/WixToolset.DUtil/inc/verutil.h
new file mode 100644
index 00000000..5247bb61
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/verutil.h
@@ -0,0 +1,93 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9#define ReleaseVerutilVersion(p) if (p) { VerFreeVersion(p); p = NULL; }
10
11typedef struct _VERUTIL_VERSION_RELEASE_LABEL
12{
13 BOOL fNumeric;
14 DWORD dwValue;
15 SIZE_T cchLabelOffset;
16 int cchLabel;
17} VERUTIL_VERSION_RELEASE_LABEL;
18
19typedef struct _VERUTIL_VERSION
20{
21 LPWSTR sczVersion;
22 DWORD dwMajor;
23 DWORD dwMinor;
24 DWORD dwPatch;
25 DWORD dwRevision;
26 DWORD cReleaseLabels;
27 VERUTIL_VERSION_RELEASE_LABEL* rgReleaseLabels;
28 SIZE_T cchMetadataOffset;
29 BOOL fInvalid;
30} VERUTIL_VERSION;
31
32/*******************************************************************
33 VerCompareParsedVersions - compares the Verutil versions.
34
35*******************************************************************/
36HRESULT DAPI VerCompareParsedVersions(
37 __in_opt VERUTIL_VERSION* pVersion1,
38 __in_opt VERUTIL_VERSION* pVersion2,
39 __out int* pnResult
40 );
41
42/*******************************************************************
43 VerCompareStringVersions - parses the strings with VerParseVersion and then
44 compares the Verutil versions with VerCompareParsedVersions.
45
46*******************************************************************/
47HRESULT DAPI VerCompareStringVersions(
48 __in_z LPCWSTR wzVersion1,
49 __in_z LPCWSTR wzVersion2,
50 __in BOOL fStrict,
51 __out int* pnResult
52 );
53
54/********************************************************************
55 VerCopyVersion - copies the given Verutil version.
56
57*******************************************************************/
58HRESULT DAPI VerCopyVersion(
59 __in VERUTIL_VERSION* pSource,
60 __out VERUTIL_VERSION** ppVersion
61 );
62
63/********************************************************************
64 VerFreeVersion - frees any memory associated with a Verutil version.
65
66*******************************************************************/
67void DAPI VerFreeVersion(
68 __in VERUTIL_VERSION* pVersion
69 );
70
71/*******************************************************************
72 VerParseVersion - parses the string into a Verutil version.
73
74*******************************************************************/
75HRESULT DAPI VerParseVersion(
76 __in_z LPCWSTR wzVersion,
77 __in SIZE_T cchVersion,
78 __in BOOL fStrict,
79 __out VERUTIL_VERSION** ppVersion
80 );
81
82/*******************************************************************
83 VerParseVersion - parses the QWORD into a Verutil version.
84
85*******************************************************************/
86HRESULT DAPI VerVersionFromQword(
87 __in DWORD64 qwVersion,
88 __out VERUTIL_VERSION** ppVersion
89 );
90
91#ifdef __cplusplus
92}
93#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/wiutil.h b/src/libs/dutil/WixToolset.DUtil/inc/wiutil.h
new file mode 100644
index 00000000..9c2de209
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/wiutil.h
@@ -0,0 +1,402 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9// constants
10
11#define IDNOACTION 0
12#define WIU_MB_OKIGNORECANCELRETRY 0xE
13
14#define MAX_DARWIN_KEY 73
15#define MAX_DARWIN_COLUMN 255
16
17#define WIU_LOG_DEFAULT INSTALLLOGMODE_FATALEXIT | INSTALLLOGMODE_ERROR | INSTALLLOGMODE_WARNING | \
18 INSTALLLOGMODE_USER | INSTALLLOGMODE_INFO | INSTALLLOGMODE_RESOLVESOURCE | \
19 INSTALLLOGMODE_OUTOFDISKSPACE | INSTALLLOGMODE_ACTIONSTART | \
20 INSTALLLOGMODE_ACTIONDATA | INSTALLLOGMODE_COMMONDATA | INSTALLLOGMODE_PROPERTYDUMP
21
22#define ReleaseMsi(h) if (h) { ::MsiCloseHandle(h); }
23#define ReleaseNullMsi(h) if (h) { ::MsiCloseHandle(h); h = NULL; }
24
25
26typedef enum WIU_RESTART
27{
28 WIU_RESTART_NONE,
29 WIU_RESTART_REQUIRED,
30 WIU_RESTART_INITIATED,
31} WIU_RESTART;
32
33typedef enum WIU_MSI_EXECUTE_MESSAGE_TYPE
34{
35 WIU_MSI_EXECUTE_MESSAGE_NONE,
36 WIU_MSI_EXECUTE_MESSAGE_PROGRESS,
37 WIU_MSI_EXECUTE_MESSAGE_ERROR,
38 WIU_MSI_EXECUTE_MESSAGE_MSI_MESSAGE,
39 WIU_MSI_EXECUTE_MESSAGE_MSI_FILES_IN_USE,
40} WIU_MSI_EXECUTE_MESSAGE_TYPE;
41
42
43// structures
44
45typedef struct _WIU_MSI_EXECUTE_MESSAGE
46{
47 WIU_MSI_EXECUTE_MESSAGE_TYPE type;
48 DWORD dwAllowedResults;
49
50 DWORD cData;
51 LPCWSTR* rgwzData;
52
53 INT nResultRecommendation; // recommended return result for this message based on analysis of real world installs.
54
55 union
56 {
57 struct
58 {
59 DWORD dwPercentage;
60 } progress;
61 struct
62 {
63 DWORD dwErrorCode;
64 LPCWSTR wzMessage;
65 } error;
66 struct
67 {
68 INSTALLMESSAGE mt;
69 LPCWSTR wzMessage;
70 } msiMessage;
71 struct
72 {
73 DWORD cFiles;
74 LPCWSTR* rgwzFiles;
75 } msiFilesInUse;
76 };
77} WIU_MSI_EXECUTE_MESSAGE;
78
79typedef struct _WIU_MSI_PROGRESS
80{
81 DWORD dwTotal;
82 DWORD dwCompleted;
83 DWORD dwStep;
84 BOOL fMoveForward;
85 BOOL fEnableActionData;
86 BOOL fScriptInProgress;
87} WIU_MSI_PROGRESS;
88
89
90typedef int (*PFN_MSIEXECUTEMESSAGEHANDLER)(
91 __in WIU_MSI_EXECUTE_MESSAGE* pMessage,
92 __in_opt LPVOID pvContext
93 );
94
95typedef struct _WIU_MSI_EXECUTE_CONTEXT
96{
97 BOOL fRollback;
98 PFN_MSIEXECUTEMESSAGEHANDLER pfnMessageHandler;
99 LPVOID pvContext;
100 WIU_MSI_PROGRESS rgMsiProgress[64];
101 DWORD dwCurrentProgressIndex;
102
103 INSTALLUILEVEL previousInstallUILevel;
104 HWND hwndPreviousParentWindow;
105 INSTALLUI_HANDLERW pfnPreviousExternalUI;
106 INSTALLUI_HANDLER_RECORD pfnPreviousExternalUIRecord;
107
108 BOOL fSetPreviousExternalUIRecord;
109 BOOL fSetPreviousExternalUI;
110} WIU_MSI_EXECUTE_CONTEXT;
111
112
113// typedefs
114typedef UINT (WINAPI *PFN_MSIENABLELOGW)(
115 __in DWORD dwLogMode,
116 __in_z LPCWSTR szLogFile,
117 __in DWORD dwLogAttributes
118 );
119typedef UINT (WINAPI *PFN_MSIGETPRODUCTINFOW)(
120 __in LPCWSTR szProductCode,
121 __in LPCWSTR szProperty,
122 __out_ecount_opt(*pcchValue) LPWSTR szValue,
123 __inout LPDWORD pcchValue
124 );
125typedef INSTALLSTATE (WINAPI *PFN_MSIGETCOMPONENTPATHW)(
126 __in LPCWSTR szProduct,
127 __in LPCWSTR szComponent,
128 __out_ecount_opt(*pcchBuf) LPWSTR lpPathBuf,
129 __inout_opt LPDWORD pcchBuf
130 );
131typedef INSTALLSTATE (WINAPI *PFN_MSILOCATECOMPONENTW)(
132 __in LPCWSTR szComponent,
133 __out_ecount_opt(*pcchBuf) LPWSTR lpPathBuf,
134 __inout_opt LPDWORD pcchBuf
135 );
136typedef UINT (WINAPI *PFN_MSIGETPRODUCTINFOEXW)(
137 __in LPCWSTR szProductCode,
138 __in_opt LPCWSTR szUserSid,
139 __in MSIINSTALLCONTEXT dwContext,
140 __in LPCWSTR szProperty,
141 __out_ecount_opt(*pcchValue) LPWSTR szValue,
142 __inout_opt LPDWORD pcchValue
143 );
144typedef INSTALLSTATE (WINAPI *PFN_MSIQUERYFEATURESTATEW)(
145 __in LPCWSTR szProduct,
146 __in LPCWSTR szFeature
147 );
148typedef UINT (WINAPI *PFN_MSIGETPATCHINFOEXW)(
149 __in_z LPCWSTR wzPatchCode,
150 __in_z LPCWSTR wzProductCode,
151 __in_z_opt LPCWSTR wzUserSid,
152 __in MSIINSTALLCONTEXT dwContext,
153 __in_z LPCWSTR wzProperty,
154 __out_opt LPWSTR wzValue,
155 __inout DWORD* pcchValue
156 );
157typedef UINT (WINAPI *PFN_MSIDETERMINEPATCHSEQUENCEW)(
158 __in_z LPCWSTR wzProductCode,
159 __in_z_opt LPCWSTR wzUserSid,
160 __in MSIINSTALLCONTEXT context,
161 __in DWORD cPatchInfo,
162 __in PMSIPATCHSEQUENCEINFOW pPatchInfo
163 );
164typedef UINT (WINAPI *PFN_MSIDETERMINEAPPLICABLEPATCHESW)(
165 __in_z LPCWSTR wzProductPackagePath,
166 __in DWORD cPatchInfo,
167 __in PMSIPATCHSEQUENCEINFOW pPatchInfo
168 );
169typedef UINT (WINAPI *PFN_MSIINSTALLPRODUCTW)(
170 __in LPCWSTR szPackagePath,
171 __in_opt LPCWSTR szCommandLine
172 );
173typedef UINT (WINAPI *PFN_MSICONFIGUREPRODUCTEXW)(
174 __in LPCWSTR szProduct,
175 __in int iInstallLevel,
176 __in INSTALLSTATE eInstallState,
177 __in_opt LPCWSTR szCommandLine
178 );
179typedef UINT (WINAPI *PFN_MSIREMOVEPATCHESW)(
180 __in_z LPCWSTR wzPatchList,
181 __in_z LPCWSTR wzProductCode,
182 __in INSTALLTYPE eUninstallType,
183 __in_z_opt LPCWSTR szPropertyList
184 );
185typedef INSTALLUILEVEL (WINAPI *PFN_MSISETINTERNALUI)(
186 __in INSTALLUILEVEL dwUILevel,
187 __inout_opt HWND *phWnd
188 );
189typedef UINT (WINAPI *PFN_MSISETEXTERNALUIRECORD)(
190 __in_opt INSTALLUI_HANDLER_RECORD puiHandler,
191 __in DWORD dwMessageFilter,
192 __in_opt LPVOID pvContext,
193 __out_opt PINSTALLUI_HANDLER_RECORD ppuiPrevHandler
194 );
195typedef INSTALLUI_HANDLERW (WINAPI *PFN_MSISETEXTERNALUIW)(
196 __in_opt INSTALLUI_HANDLERW puiHandler,
197 __in DWORD dwMessageFilter,
198 __in_opt LPVOID pvContext
199 );
200typedef UINT (WINAPI *PFN_MSIENUMPRODUCTSW)(
201 __in DWORD iProductIndex,
202 __out_ecount(MAX_GUID_CHARS + 1) LPWSTR lpProductBuf
203 );
204typedef UINT (WINAPI *PFN_MSIENUMPRODUCTSEXW)(
205 __in_z_opt LPCWSTR wzProductCode,
206 __in_z_opt LPCWSTR wzUserSid,
207 __in DWORD dwContext,
208 __in DWORD dwIndex,
209 __out_opt WCHAR wzInstalledProductCode[39],
210 __out_opt MSIINSTALLCONTEXT *pdwInstalledContext,
211 __out_opt LPWSTR wzSid,
212 __inout_opt LPDWORD pcchSid
213 );
214
215typedef UINT (WINAPI *PFN_MSIENUMRELATEDPRODUCTSW)(
216 __in LPCWSTR lpUpgradeCode,
217 __reserved DWORD dwReserved,
218 __in DWORD iProductIndex,
219 __out_ecount(MAX_GUID_CHARS + 1) LPWSTR lpProductBuf
220 );
221typedef UINT (WINAPI *PFN_MSISOURCELISTADDSOURCEEXW)(
222 __in LPCWSTR szProductCodeOrPatchCode,
223 __in_opt LPCWSTR szUserSid,
224 __in MSIINSTALLCONTEXT dwContext,
225 __in DWORD dwOptions,
226 __in LPCWSTR szSource,
227 __in_opt DWORD dwIndex
228 );
229typedef UINT(WINAPI* PFN_MSIBEGINTRANSACTIONW)(
230 __in LPCWSTR szName,
231 __in DWORD dwTransactionAttributes,
232 __out MSIHANDLE* phTransactionHandle,
233 __out HANDLE* phChangeOfOwnerEvent
234 );
235typedef UINT(WINAPI* PFN_MSIENDTRANSACTION)(
236 __in DWORD dwTransactionState
237 );
238
239
240HRESULT DAPI WiuInitialize(
241 );
242void DAPI WiuUninitialize(
243 );
244void DAPI WiuFunctionOverride(
245 __in_opt PFN_MSIENABLELOGW pfnMsiEnableLogW,
246 __in_opt PFN_MSIGETCOMPONENTPATHW pfnMsiGetComponentPathW,
247 __in_opt PFN_MSILOCATECOMPONENTW pfnMsiLocateComponentW,
248 __in_opt PFN_MSIQUERYFEATURESTATEW pfnMsiQueryFeatureStateW,
249 __in_opt PFN_MSIGETPRODUCTINFOW pfnMsiGetProductInfoW,
250 __in_opt PFN_MSIGETPRODUCTINFOEXW pfnMsiGetProductInfoExW,
251 __in_opt PFN_MSIINSTALLPRODUCTW pfnMsiInstallProductW,
252 __in_opt PFN_MSICONFIGUREPRODUCTEXW pfnMsiConfigureProductExW,
253 __in_opt PFN_MSISETINTERNALUI pfnMsiSetInternalUI,
254 __in_opt PFN_MSISETEXTERNALUIW pfnMsiSetExternalUIW,
255 __in_opt PFN_MSIENUMRELATEDPRODUCTSW pfnMsiEnumRelatedProductsW,
256 __in_opt PFN_MSISETEXTERNALUIRECORD pfnMsiSetExternalUIRecord,
257 __in_opt PFN_MSISOURCELISTADDSOURCEEXW pfnMsiSourceListAddSourceExW
258 );
259HRESULT DAPI WiuGetComponentPath(
260 __in_z LPCWSTR wzProductCode,
261 __in_z LPCWSTR wzComponentId,
262 __out INSTALLSTATE* pInstallState,
263 __out_z LPWSTR* psczValue
264 );
265HRESULT DAPI WiuLocateComponent(
266 __in_z LPCWSTR wzComponentId,
267 __out INSTALLSTATE* pInstallState,
268 __out_z LPWSTR* psczValue
269 );
270HRESULT DAPI WiuQueryFeatureState(
271 __in_z LPCWSTR wzProduct,
272 __in_z LPCWSTR wzFeature,
273 __out INSTALLSTATE* pInstallState
274 );
275HRESULT DAPI WiuGetProductInfo(
276 __in_z LPCWSTR wzProductCode,
277 __in_z LPCWSTR wzProperty,
278 __out LPWSTR* psczValue
279 );
280HRESULT DAPI WiuGetProductInfoEx(
281 __in_z LPCWSTR wzProductCode,
282 __in_z_opt LPCWSTR wzUserSid,
283 __in MSIINSTALLCONTEXT dwContext,
284 __in_z LPCWSTR wzProperty,
285 __out LPWSTR* psczValue
286 );
287HRESULT DAPI WiuGetProductProperty(
288 __in MSIHANDLE hProduct,
289 __in_z LPCWSTR wzProperty,
290 __out LPWSTR* psczValue
291 );
292HRESULT DAPI WiuGetPatchInfoEx(
293 __in_z LPCWSTR wzPatchCode,
294 __in_z LPCWSTR wzProductCode,
295 __in_z_opt LPCWSTR wzUserSid,
296 __in MSIINSTALLCONTEXT dwContext,
297 __in_z LPCWSTR wzProperty,
298 __out LPWSTR* psczValue
299 );
300HRESULT DAPI WiuDeterminePatchSequence(
301 __in_z LPCWSTR wzProductCode,
302 __in_z_opt LPCWSTR wzUserSid,
303 __in MSIINSTALLCONTEXT context,
304 __in PMSIPATCHSEQUENCEINFOW pPatchInfo,
305 __in DWORD cPatchInfo
306 );
307HRESULT DAPI WiuDetermineApplicablePatches(
308 __in_z LPCWSTR wzProductPackagePath,
309 __in PMSIPATCHSEQUENCEINFOW pPatchInfo,
310 __in DWORD cPatchInfo
311 );
312HRESULT DAPI WiuEnumProducts(
313 __in DWORD iProductIndex,
314 __out_ecount(MAX_GUID_CHARS + 1) LPWSTR wzProductCode
315 );
316HRESULT DAPI WiuEnumProductsEx(
317 __in_z_opt LPCWSTR wzProductCode,
318 __in_z_opt LPCWSTR wzUserSid,
319 __in DWORD dwContext,
320 __in DWORD dwIndex,
321 __out_opt WCHAR wzInstalledProductCode[39],
322 __out_opt MSIINSTALLCONTEXT *pdwInstalledContext,
323 __out_opt LPWSTR wzSid,
324 __inout_opt LPDWORD pcchSid
325 );
326HRESULT DAPI WiuEnumRelatedProducts(
327 __in_z LPCWSTR wzUpgradeCode,
328 __in DWORD iProductIndex,
329 __out_ecount(MAX_GUID_CHARS + 1) LPWSTR wzProductCode
330 );
331HRESULT DAPI WiuEnumRelatedProductCodes(
332 __in_z LPCWSTR wzUpgradeCode,
333 __deref_out_ecount_opt(*pcRelatedProducts) LPWSTR** prgsczProductCodes,
334 __out DWORD* pcRelatedProducts,
335 __in BOOL fReturnHighestVersionOnly
336 );
337HRESULT DAPI WiuEnableLog(
338 __in DWORD dwLogMode,
339 __in_z LPCWSTR wzLogFile,
340 __in DWORD dwLogAttributes
341 );
342HRESULT DAPI WiuInitializeInternalUI(
343 __in INSTALLUILEVEL internalUILevel,
344 __in_opt HWND hwndParent,
345 __in WIU_MSI_EXECUTE_CONTEXT* pExecuteContext
346 );
347HRESULT DAPI WiuInitializeExternalUI(
348 __in PFN_MSIEXECUTEMESSAGEHANDLER pfnMessageHandler,
349 __in INSTALLUILEVEL internalUILevel,
350 __in_opt HWND hwndParent,
351 __in LPVOID pvContext,
352 __in BOOL fRollback,
353 __in WIU_MSI_EXECUTE_CONTEXT* pExecuteContext
354 );
355void DAPI WiuUninitializeExternalUI(
356 __in WIU_MSI_EXECUTE_CONTEXT* pExecuteContext
357 );
358HRESULT DAPI WiuConfigureProductEx(
359 __in_z LPCWSTR wzProduct,
360 __in int iInstallLevel,
361 __in INSTALLSTATE eInstallState,
362 __in_z LPCWSTR wzCommandLine,
363 __out WIU_RESTART* pRestart
364 );
365HRESULT DAPI WiuInstallProduct(
366 __in_z LPCWSTR wzPackagPath,
367 __in_z LPCWSTR wzCommandLine,
368 __out WIU_RESTART* pRestart
369 );
370HRESULT DAPI WiuRemovePatches(
371 __in_z LPCWSTR wzPatchList,
372 __in_z LPCWSTR wzProductCode,
373 __in_z LPCWSTR wzPropertyList,
374 __out WIU_RESTART* pRestart
375 );
376HRESULT DAPI WiuSourceListAddSourceEx(
377 __in_z LPCWSTR wzProductCodeOrPatchCode,
378 __in_z_opt LPCWSTR wzUserSid,
379 __in MSIINSTALLCONTEXT dwContext,
380 __in DWORD dwCode,
381 __in_z LPCWSTR wzSource,
382 __in_opt DWORD dwIndex
383 );
384HRESULT DAPI WiuBeginTransaction(
385 __in_z LPCWSTR szName,
386 __in DWORD dwTransactionAttributes,
387 __out MSIHANDLE* phTransactionHandle,
388 __out HANDLE* phChangeOfOwnerEvent,
389 __in DWORD dwLogMode,
390 __in_z LPCWSTR szLogPath
391 );
392HRESULT DAPI WiuEndTransaction(
393 __in DWORD dwTransactionState,
394 __in DWORD dwLogMode,
395 __in_z LPCWSTR szLogPath
396 );
397BOOL DAPI WiuIsMsiTransactionSupported(
398 );
399
400#ifdef __cplusplus
401}
402#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/wuautil.h b/src/libs/dutil/WixToolset.DUtil/inc/wuautil.h
new file mode 100644
index 00000000..b239c4e6
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/wuautil.h
@@ -0,0 +1,19 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#if defined(__cplusplus)
6extern "C" {
7#endif
8
9HRESULT DAPI WuaPauseAutomaticUpdates();
10
11HRESULT DAPI WuaResumeAutomaticUpdates();
12
13HRESULT DAPI WuaRestartRequired(
14 __out BOOL* pfRestartRequired
15 );
16
17#if defined(__cplusplus)
18}
19#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inc/xmlutil.h b/src/libs/dutil/WixToolset.DUtil/inc/xmlutil.h
new file mode 100644
index 00000000..ba92ada9
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inc/xmlutil.h
@@ -0,0 +1,167 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5extern __declspec(selectany) const CLSID XmlUtil_CLSID_DOMDocument = {0x2933BF90, 0x7B36, 0x11d2, {0xB2, 0x0E, 0x00, 0xC0, 0x4F, 0x98, 0x3E, 0x60}};
6extern __declspec(selectany) const CLSID XmlUtil_CLSID_DOMDocument20 = {0xF6D90F11, 0x9C73, 0x11D3, {0xB3, 0x2E, 0x00, 0xC0, 0x4F, 0x99, 0x0B, 0xB4}};
7extern __declspec(selectany) const CLSID XmlUtil_CLSID_DOMDocument26 = {0xf5078f1b, 0xc551, 0x11d3, {0x89, 0xb9, 0x00, 0x00, 0xf8, 0x1f, 0xe2, 0x21}};
8extern __declspec(selectany) const CLSID XmlUtil_CLSID_DOMDocument30 = {0xf5078f32, 0xc551, 0x11d3, {0x89, 0xb9, 0x00, 0x00, 0xf8, 0x1f, 0xe2, 0x21}};
9extern __declspec(selectany) const CLSID XmlUtil_CLSID_DOMDocument40 = {0x88d969c0, 0xf192, 0x11d4, {0xa6, 0x5f, 0x00, 0x40, 0x96, 0x32, 0x51, 0xe5}};
10extern __declspec(selectany) const CLSID XmlUtil_CLSID_DOMDocument50 = {0x88d969e5, 0xf192, 0x11d4, {0xa6, 0x5f, 0x00, 0x40, 0x96, 0x32, 0x51, 0xe5}};
11extern __declspec(selectany) const CLSID XmlUtil_CLSID_DOMDocument60 = {0x88d96a05, 0xf192, 0x11d4, {0xa6, 0x5f, 0x00, 0x40, 0x96, 0x32, 0x51, 0xe5}};
12extern __declspec(selectany) const CLSID XmlUtil_CLSID_XMLSchemaCache = {0x88d969c2, 0xf192, 0x11d4, {0xa6, 0x5f, 0x00, 0x40, 0x96, 0x32, 0x51, 0xe5}};
13
14extern __declspec(selectany) const IID XmlUtil_IID_IXMLDOMDocument = {0x2933BF81, 0x7B36, 0x11D2, {0xB2, 0x0E, 0x00, 0xC0, 0x4F, 0x98, 0x3E, 0x60}};
15extern __declspec(selectany) const IID XmlUtil_IID_IXMLDOMDocument2 = {0x2933BF95, 0x7B36, 0x11D2, {0xB2, 0x0E, 0x00, 0xC0, 0x4F, 0x98, 0x3E, 0x60}};
16extern __declspec(selectany) const IID XmlUtil_IID_IXMLDOMSchemaCollection = {0x373984C8, 0xB845, 0x449B, {0x91, 0xE7, 0x45, 0xAC, 0x83, 0x03, 0x6A, 0xDE}};
17
18typedef enum XML_LOAD_ATTRIBUTE
19{
20 XML_LOAD_PRESERVE_WHITESPACE = 1,
21} XML_LOAD_ATTRIBUTE;
22
23
24#ifdef __cplusplus
25extern "C" {
26#endif
27
28HRESULT DAPI XmlInitialize();
29void DAPI XmlUninitialize();
30
31HRESULT DAPI XmlCreateElement(
32 __in IXMLDOMDocument *pixdDocument,
33 __in_z LPCWSTR wzElementName,
34 __out IXMLDOMElement **ppixnElement
35 );
36HRESULT DAPI XmlCreateDocument(
37 __in_opt LPCWSTR pwzElementName,
38 __out IXMLDOMDocument** ppixdDocument,
39 __out_opt IXMLDOMElement** ppixeRootElement = NULL
40 );
41HRESULT DAPI XmlLoadDocument(
42 __in_z LPCWSTR wzDocument,
43 __out IXMLDOMDocument** ppixdDocument
44 );
45HRESULT DAPI XmlLoadDocumentEx(
46 __in_z LPCWSTR wzDocument,
47 __in DWORD dwAttributes,
48 __out IXMLDOMDocument** ppixdDocument
49 );
50HRESULT DAPI XmlLoadDocumentFromFile(
51 __in_z LPCWSTR wzPath,
52 __out IXMLDOMDocument** ppixdDocument
53 );
54HRESULT DAPI XmlLoadDocumentFromBuffer(
55 __in_bcount(cbSource) const BYTE* pbSource,
56 __in SIZE_T cbSource,
57 __out IXMLDOMDocument** ppixdDocument
58 );
59HRESULT DAPI XmlLoadDocumentFromFileEx(
60 __in_z LPCWSTR wzPath,
61 __in DWORD dwAttributes,
62 __out IXMLDOMDocument** ppixdDocument
63 );
64HRESULT DAPI XmlSelectSingleNode(
65 __in IXMLDOMNode* pixnParent,
66 __in_z LPCWSTR wzXPath,
67 __out IXMLDOMNode **ppixnChild
68 );
69HRESULT DAPI XmlSetAttribute(
70 __in IXMLDOMNode* pixnNode,
71 __in_z LPCWSTR pwzAttribute,
72 __in_z LPCWSTR pwzAttributeValue
73 );
74HRESULT DAPI XmlCreateTextNode(
75 __in IXMLDOMDocument *pixdDocument,
76 __in_z LPCWSTR wzText,
77 __out IXMLDOMText **ppixnTextNode
78 );
79HRESULT DAPI XmlGetText(
80 __in IXMLDOMNode* pixnNode,
81 __deref_out_z BSTR* pbstrText
82 );
83HRESULT DAPI XmlGetAttribute(
84 __in IXMLDOMNode* pixnNode,
85 __in_z LPCWSTR pwzAttribute,
86 __deref_out_z BSTR* pbstrAttributeValue
87 );
88HRESULT DAPI XmlGetAttributeEx(
89 __in IXMLDOMNode* pixnNode,
90 __in_z LPCWSTR wzAttribute,
91 __deref_out_z LPWSTR* psczAttributeValue
92 );
93HRESULT DAPI XmlGetYesNoAttribute(
94 __in IXMLDOMNode* pixnNode,
95 __in_z LPCWSTR wzAttribute,
96 __out BOOL* pfYes
97 );
98HRESULT DAPI XmlGetAttributeNumber(
99 __in IXMLDOMNode* pixnNode,
100 __in_z LPCWSTR pwzAttribute,
101 __out DWORD* pdwValue
102 );
103HRESULT DAPI XmlGetAttributeNumberBase(
104 __in IXMLDOMNode* pixnNode,
105 __in_z LPCWSTR pwzAttribute,
106 __in int nBase,
107 __out DWORD* pdwValue
108 );
109HRESULT DAPI XmlGetAttributeLargeNumber(
110 __in IXMLDOMNode* pixnNode,
111 __in_z LPCWSTR pwzAttribute,
112 __out DWORD64* pdw64Value
113 );
114HRESULT DAPI XmlGetNamedItem(
115 __in IXMLDOMNamedNodeMap *pixnmAttributes,
116 __in_opt LPCWSTR wzName,
117 __out IXMLDOMNode **ppixnNamedItem
118 );
119HRESULT DAPI XmlSetText(
120 __in IXMLDOMNode* pixnNode,
121 __in_z LPCWSTR pwzText
122 );
123HRESULT DAPI XmlSetTextNumber(
124 __in IXMLDOMNode *pixnNode,
125 __in DWORD dwValue
126 );
127HRESULT DAPI XmlCreateChild(
128 __in IXMLDOMNode* pixnParent,
129 __in_z LPCWSTR pwzElementType,
130 __out IXMLDOMNode** ppixnChild
131 );
132HRESULT DAPI XmlRemoveAttribute(
133 __in IXMLDOMNode* pixnNode,
134 __in_z LPCWSTR pwzAttribute
135 );
136HRESULT DAPI XmlSelectNodes(
137 __in IXMLDOMNode* pixnParent,
138 __in_z LPCWSTR wzXPath,
139 __out IXMLDOMNodeList **ppixnChild
140 );
141HRESULT DAPI XmlNextAttribute(
142 __in IXMLDOMNamedNodeMap* pixnnm,
143 __out IXMLDOMNode** pixnAttribute,
144 __deref_opt_out_z_opt BSTR* pbstrAttribute
145 );
146HRESULT DAPI XmlNextElement(
147 __in IXMLDOMNodeList* pixnl,
148 __out IXMLDOMNode** pixnElement,
149 __deref_opt_out_z_opt BSTR* pbstrElement
150 );
151HRESULT DAPI XmlRemoveChildren(
152 __in IXMLDOMNode* pixnSource,
153 __in_z LPCWSTR pwzXPath
154 );
155HRESULT DAPI XmlSaveDocument(
156 __in IXMLDOMDocument* pixdDocument,
157 __inout LPCWSTR wzPath
158 );
159HRESULT DAPI XmlSaveDocumentToBuffer(
160 __in IXMLDOMDocument* pixdDocument,
161 __deref_out_bcount(*pcbDest) BYTE** ppbDest,
162 __out DWORD* pcbDest
163 );
164
165#ifdef __cplusplus
166}
167#endif
diff --git a/src/libs/dutil/WixToolset.DUtil/inetutil.cpp b/src/libs/dutil/WixToolset.DUtil/inetutil.cpp
new file mode 100644
index 00000000..8dace55f
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/inetutil.cpp
@@ -0,0 +1,155 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define InetExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_INETUTIL, x, s, __VA_ARGS__)
8#define InetExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_INETUTIL, x, s, __VA_ARGS__)
9#define InetExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_INETUTIL, x, s, __VA_ARGS__)
10#define InetExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_INETUTIL, x, s, __VA_ARGS__)
11#define InetExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_INETUTIL, x, s, __VA_ARGS__)
12#define InetExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_INETUTIL, x, s, __VA_ARGS__)
13#define InetExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_INETUTIL, p, x, e, s, __VA_ARGS__)
14#define InetExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_INETUTIL, p, x, s, __VA_ARGS__)
15#define InetExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_INETUTIL, p, x, e, s, __VA_ARGS__)
16#define InetExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_INETUTIL, p, x, s, __VA_ARGS__)
17#define InetExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_INETUTIL, e, x, s, __VA_ARGS__)
18#define InetExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_INETUTIL, g, x, s, __VA_ARGS__)
19
20
21/*******************************************************************
22 InternetGetSizeByHandle - returns size of file by url handle
23
24*******************************************************************/
25extern "C" HRESULT DAPI InternetGetSizeByHandle(
26 __in HINTERNET hiFile,
27 __out LONGLONG* pllSize
28 )
29{
30 Assert(pllSize);
31
32 HRESULT hr = S_OK;
33 DWORD dwSize = 0;
34 DWORD cb = 0;
35
36 cb = sizeof(dwSize);
37 if (!::HttpQueryInfoW(hiFile, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, reinterpret_cast<LPVOID>(&dwSize), &cb, NULL))
38 {
39 InetExitOnLastError(hr, "Failed to get size for internet file handle");
40 }
41
42 *pllSize = dwSize;
43LExit:
44 return hr;
45}
46
47
48/*******************************************************************
49 InetGetCreateTimeByHandle - returns url creation time
50
51******************************************************************/
52extern "C" HRESULT DAPI InternetGetCreateTimeByHandle(
53 __in HINTERNET hiFile,
54 __out LPFILETIME pft
55 )
56{
57 Assert(pft);
58
59 HRESULT hr = S_OK;
60 SYSTEMTIME st = {0 };
61 DWORD cb = sizeof(SYSTEMTIME);
62
63 if (!::HttpQueryInfoW(hiFile, HTTP_QUERY_LAST_MODIFIED | HTTP_QUERY_FLAG_SYSTEMTIME, reinterpret_cast<LPVOID>(&st), &cb, NULL))
64 {
65 InetExitWithLastError(hr, "failed to get create time for internet file handle");
66 }
67
68 if (!::SystemTimeToFileTime(&st, pft))
69 {
70 InetExitWithLastError(hr, "failed to convert system time to file time");
71 }
72
73LExit:
74 return hr;
75}
76
77
78/*******************************************************************
79 InternetQueryInfoString - query info string
80
81*******************************************************************/
82extern "C" HRESULT DAPI InternetQueryInfoString(
83 __in HINTERNET hRequest,
84 __in DWORD dwInfo,
85 __deref_out_z LPWSTR* psczValue
86 )
87{
88 HRESULT hr = S_OK;
89 SIZE_T cbOriginal = 0;
90 DWORD cbValue = 0;
91 DWORD dwIndex = 0;
92
93 // If nothing was provided start off with some arbitrary size.
94 if (!*psczValue)
95 {
96 hr = StrAlloc(psczValue, 64);
97 InetExitOnFailure(hr, "Failed to allocate memory for value.");
98 }
99
100 hr = StrSize(*psczValue, &cbOriginal);
101 InetExitOnFailure(hr, "Failed to get size of value.");
102
103 cbValue = (DWORD)min(DWORD_MAX, cbOriginal);
104
105 if (!::HttpQueryInfoW(hRequest, dwInfo, static_cast<void*>(*psczValue), &cbValue, &dwIndex))
106 {
107 DWORD er = ::GetLastError();
108 if (ERROR_INSUFFICIENT_BUFFER == er)
109 {
110 cbValue += sizeof(WCHAR); // add one character for the null terminator.
111
112 hr = StrAlloc(psczValue, cbValue / sizeof(WCHAR));
113 InetExitOnFailure(hr, "Failed to allocate value.");
114
115 if (!::HttpQueryInfoW(hRequest, dwInfo, static_cast<void*>(*psczValue), &cbValue, &dwIndex))
116 {
117 er = ::GetLastError();
118 }
119 else
120 {
121 er = ERROR_SUCCESS;
122 }
123 }
124
125 hr = HRESULT_FROM_WIN32(er);
126 InetExitOnRootFailure(hr, "Failed to get query information.");
127 }
128
129LExit:
130 return hr;
131}
132
133
134/*******************************************************************
135 InternetQueryInfoNumber - query info number
136
137*******************************************************************/
138extern "C" HRESULT DAPI InternetQueryInfoNumber(
139 __in HINTERNET hRequest,
140 __in DWORD dwInfo,
141 __inout LONG* plInfo
142 )
143{
144 HRESULT hr = S_OK;
145 DWORD cbCode = sizeof(LONG);
146 DWORD dwIndex = 0;
147
148 if (!::HttpQueryInfoW(hRequest, dwInfo | HTTP_QUERY_FLAG_NUMBER, static_cast<void*>(plInfo), &cbCode, &dwIndex))
149 {
150 InetExitWithLastError(hr, "Failed to get query information.");
151 }
152
153LExit:
154 return hr;
155}
diff --git a/src/libs/dutil/WixToolset.DUtil/iniutil.cpp b/src/libs/dutil/WixToolset.DUtil/iniutil.cpp
new file mode 100644
index 00000000..70b62995
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/iniutil.cpp
@@ -0,0 +1,768 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define IniExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_INIUTIL, x, s, __VA_ARGS__)
8#define IniExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_INIUTIL, x, s, __VA_ARGS__)
9#define IniExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_INIUTIL, x, s, __VA_ARGS__)
10#define IniExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_INIUTIL, x, s, __VA_ARGS__)
11#define IniExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_INIUTIL, x, s, __VA_ARGS__)
12#define IniExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_INIUTIL, x, s, __VA_ARGS__)
13#define IniExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_INIUTIL, p, x, e, s, __VA_ARGS__)
14#define IniExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_INIUTIL, p, x, s, __VA_ARGS__)
15#define IniExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_INIUTIL, p, x, e, s, __VA_ARGS__)
16#define IniExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_INIUTIL, p, x, s, __VA_ARGS__)
17#define IniExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_INIUTIL, e, x, s, __VA_ARGS__)
18#define IniExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_INIUTIL, g, x, s, __VA_ARGS__)
19
20const LPCWSTR wzSectionSeparator = L"\\";
21
22struct INI_STRUCT
23{
24 LPWSTR sczPath; // the path to the INI file to be parsed
25
26 LPWSTR sczOpenTagPrefix; // For regular ini, this would be '['
27 LPWSTR sczOpenTagPostfix; // For regular ini, this would be ']'
28
29 LPWSTR sczValuePrefix; // for regular ini, this would be NULL
30 LPWSTR sczValueSeparator; // for regular ini, this would be '='
31
32 LPWSTR *rgsczValueSeparatorExceptions;
33 DWORD cValueSeparatorExceptions;
34
35 LPWSTR sczCommentLinePrefix; // for regular ini, this would be ';'
36
37 INI_VALUE *rgivValues;
38 DWORD cValues;
39
40 LPWSTR *rgsczLines;
41 DWORD cLines;
42
43 FILE_ENCODING feEncoding;
44 BOOL fModified;
45};
46
47const int INI_HANDLE_BYTES = sizeof(INI_STRUCT);
48
49static HRESULT GetSectionPrefixFromName(
50 __in_z LPCWSTR wzName,
51 __deref_inout_z LPWSTR* psczOutput
52 );
53static void UninitializeIniValue(
54 INI_VALUE *pivValue
55 );
56
57extern "C" HRESULT DAPI IniInitialize(
58 __out_bcount(INI_HANDLE_BYTES) INI_HANDLE* piHandle
59 )
60{
61 HRESULT hr = S_OK;
62
63 // Allocate the handle
64 *piHandle = static_cast<INI_HANDLE>(MemAlloc(sizeof(INI_STRUCT), TRUE));
65 IniExitOnNull(*piHandle, hr, E_OUTOFMEMORY, "Failed to allocate ini object");
66
67LExit:
68 return hr;
69}
70
71extern "C" void DAPI IniUninitialize(
72 __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle
73 )
74{
75 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
76
77 ReleaseStr(pi->sczPath);
78 ReleaseStr(pi->sczOpenTagPrefix);
79 ReleaseStr(pi->sczOpenTagPostfix);
80 ReleaseStr(pi->sczValuePrefix);
81 ReleaseStr(pi->sczValueSeparator);
82
83 for (DWORD i = 0; i < pi->cValueSeparatorExceptions; ++i)
84 {
85 ReleaseStr(pi->rgsczValueSeparatorExceptions + i);
86 }
87
88 ReleaseStr(pi->sczCommentLinePrefix);
89
90 for (DWORD i = 0; i < pi->cValues; ++i)
91 {
92 UninitializeIniValue(pi->rgivValues + i);
93 }
94 ReleaseMem(pi->rgivValues);
95
96 ReleaseStrArray(pi->rgsczLines, pi->cLines);
97
98 ReleaseMem(pi);
99}
100
101extern "C" HRESULT DAPI IniSetOpenTag(
102 __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
103 __in_z_opt LPCWSTR wzOpenTagPrefix,
104 __in_z_opt LPCWSTR wzOpenTagPostfix
105 )
106{
107 HRESULT hr = S_OK;
108
109 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
110
111 if (wzOpenTagPrefix)
112 {
113 hr = StrAllocString(&pi->sczOpenTagPrefix, wzOpenTagPrefix, 0);
114 IniExitOnFailure(hr, "Failed to copy open tag prefix to ini struct: %ls", wzOpenTagPrefix);
115 }
116 else
117 {
118 ReleaseNullStr(pi->sczOpenTagPrefix);
119 }
120
121 if (wzOpenTagPostfix)
122 {
123 hr = StrAllocString(&pi->sczOpenTagPostfix, wzOpenTagPostfix, 0);
124 IniExitOnFailure(hr, "Failed to copy open tag postfix to ini struct: %ls", wzOpenTagPostfix);
125 }
126 else
127 {
128 ReleaseNullStr(pi->sczOpenTagPrefix);
129 }
130
131LExit:
132 return hr;
133}
134
135extern "C" HRESULT DAPI IniSetValueStyle(
136 __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
137 __in_z_opt LPCWSTR wzValuePrefix,
138 __in_z_opt LPCWSTR wzValueSeparator
139 )
140{
141 HRESULT hr = S_OK;
142
143 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
144
145 if (wzValuePrefix)
146 {
147 hr = StrAllocString(&pi->sczValuePrefix, wzValuePrefix, 0);
148 IniExitOnFailure(hr, "Failed to copy value prefix to ini struct: %ls", wzValuePrefix);
149 }
150 else
151 {
152 ReleaseNullStr(pi->sczValuePrefix);
153 }
154
155 if (wzValueSeparator)
156 {
157 hr = StrAllocString(&pi->sczValueSeparator, wzValueSeparator, 0);
158 IniExitOnFailure(hr, "Failed to copy value separator to ini struct: %ls", wzValueSeparator);
159 }
160 else
161 {
162 ReleaseNullStr(pi->sczValueSeparator);
163 }
164
165LExit:
166 return hr;
167}
168
169extern "C" HRESULT DAPI IniSetValueSeparatorException(
170 __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
171 __in_z LPCWSTR wzValueNamePrefix
172 )
173{
174 HRESULT hr = S_OK;
175 DWORD dwInsertedIndex = 0;
176
177 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
178
179 hr = MemEnsureArraySize(reinterpret_cast<void **>(&pi->rgsczValueSeparatorExceptions), pi->cValueSeparatorExceptions + 1, sizeof(LPWSTR), 5);
180 IniExitOnFailure(hr, "Failed to increase array size for value separator exceptions");
181 dwInsertedIndex = pi->cValueSeparatorExceptions;
182 ++pi->cValueSeparatorExceptions;
183
184 hr = StrAllocString(&pi->rgsczValueSeparatorExceptions[dwInsertedIndex], wzValueNamePrefix, 0);
185 IniExitOnFailure(hr, "Failed to copy value separator exception");
186
187LExit:
188 return hr;
189}
190
191extern "C" HRESULT DAPI IniSetCommentStyle(
192 __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
193 __in_z_opt LPCWSTR wzLinePrefix
194 )
195{
196 HRESULT hr = S_OK;
197
198 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
199
200 if (wzLinePrefix)
201 {
202 hr = StrAllocString(&pi->sczCommentLinePrefix, wzLinePrefix, 0);
203 IniExitOnFailure(hr, "Failed to copy comment line prefix to ini struct: %ls", wzLinePrefix);
204 }
205 else
206 {
207 ReleaseNullStr(pi->sczCommentLinePrefix);
208 }
209
210LExit:
211 return hr;
212}
213
214extern "C" HRESULT DAPI IniParse(
215 __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
216 __in LPCWSTR wzPath,
217 __out_opt FILE_ENCODING *pfeEncodingFound
218 )
219{
220 HRESULT hr = S_OK;
221 DWORD dwValuePrefixLength = 0;
222 DWORD dwValueSeparatorExceptionLength = 0;
223 LPWSTR sczContents = NULL;
224 LPWSTR sczCurrentSection = NULL;
225 LPWSTR sczName = NULL;
226 LPWSTR sczNameTrimmed = NULL;
227 LPWSTR sczValue = NULL;
228 LPWSTR sczValueTrimmed = NULL;
229 LPWSTR wzOpenTagPrefix = NULL;
230 LPWSTR wzOpenTagPostfix = NULL;
231 LPWSTR wzValuePrefix = NULL;
232 LPWSTR wzValueNameStart = NULL;
233 LPWSTR wzValueSeparator = NULL;
234 LPWSTR wzCommentLinePrefix = NULL;
235 LPWSTR wzValueBegin = NULL;
236 LPCWSTR wzTemp = NULL;
237
238 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
239
240 BOOL fSections = (NULL != pi->sczOpenTagPrefix) && (NULL != pi->sczOpenTagPostfix);
241 BOOL fValuePrefix = (NULL != pi->sczValuePrefix);
242
243 hr = StrAllocString(&pi->sczPath, wzPath, 0);
244 IniExitOnFailure(hr, "Failed to copy path to ini struct: %ls", wzPath);
245
246 hr = FileToString(pi->sczPath, &sczContents, &pi->feEncoding);
247 IniExitOnFailure(hr, "Failed to convert file to string: %ls", pi->sczPath);
248
249 if (pfeEncodingFound)
250 {
251 *pfeEncodingFound = pi->feEncoding;
252 }
253
254 if (!sczContents || !*sczContents)
255 {
256 // Empty string, nothing to parse
257 ExitFunction1(hr = S_OK);
258 }
259
260 dwValuePrefixLength = lstrlenW(pi->sczValuePrefix);
261 hr = StrSplitAllocArray(&pi->rgsczLines, reinterpret_cast<UINT *>(&pi->cLines), sczContents, L"\n");
262 IniExitOnFailure(hr, "Failed to split INI file into lines");
263
264 for (DWORD i = 0; i < pi->cLines; ++i)
265 {
266 if (!*pi->rgsczLines[i] || '\r' == *pi->rgsczLines[i])
267 {
268 continue;
269 }
270
271 if (pi->sczCommentLinePrefix)
272 {
273 wzCommentLinePrefix = wcsstr(pi->rgsczLines[i], pi->sczCommentLinePrefix);
274
275 if (wzCommentLinePrefix && wzCommentLinePrefix <= pi->rgsczLines[i] + 1)
276 {
277 continue;
278 }
279 }
280
281 if (pi->sczOpenTagPrefix)
282 {
283 wzOpenTagPrefix = wcsstr(pi->rgsczLines[i], pi->sczOpenTagPrefix);
284 if (wzOpenTagPrefix)
285 {
286 // If there is an open tag prefix but there is anything but whitespace before it, then it's NOT an open tag prefix
287 // This is important, for example, to support values with names like "Array[0]=blah" in INI format
288 for (wzTemp = pi->rgsczLines[i]; wzTemp < wzOpenTagPrefix; ++wzTemp)
289 {
290 if (*wzTemp != L' ' && *wzTemp != L'\t')
291 {
292 wzOpenTagPrefix = NULL;
293 break;
294 }
295 }
296 }
297 }
298
299 if (pi->sczOpenTagPostfix)
300 {
301 wzOpenTagPostfix = wcsstr(pi->rgsczLines[i], pi->sczOpenTagPostfix);
302 }
303
304 if (pi->sczValuePrefix)
305 {
306 wzValuePrefix = wcsstr(pi->rgsczLines[i], pi->sczValuePrefix);
307 if (wzValuePrefix != NULL)
308 {
309 wzValueNameStart = wzValuePrefix + dwValuePrefixLength;
310 }
311 }
312 else
313 {
314 wzValueNameStart = pi->rgsczLines[i];
315 }
316
317 if (pi->sczValueSeparator && NULL != wzValueNameStart && *wzValueNameStart != L'\0')
318 {
319 dwValueSeparatorExceptionLength = 0;
320 for (DWORD j = 0; j < pi->cValueSeparatorExceptions; ++j)
321 {
322 if (pi->rgsczLines[i] == wcsstr(pi->rgsczLines[i], pi->rgsczValueSeparatorExceptions[j]))
323 {
324 dwValueSeparatorExceptionLength = lstrlenW(pi->rgsczValueSeparatorExceptions[j]);
325 break;
326 }
327 }
328
329 wzValueSeparator = wcsstr(wzValueNameStart + dwValueSeparatorExceptionLength, pi->sczValueSeparator);
330 }
331
332 // Don't keep the endline
333 if (pi->rgsczLines[i][lstrlenW(pi->rgsczLines[i])-1] == L'\r')
334 {
335 pi->rgsczLines[i][lstrlenW(pi->rgsczLines[i])-1] = L'\0';
336 }
337
338 if (fSections && wzOpenTagPrefix && wzOpenTagPostfix && wzOpenTagPrefix < wzOpenTagPostfix && (NULL == wzCommentLinePrefix || wzOpenTagPrefix < wzCommentLinePrefix))
339 {
340 // There is an section starting here, let's keep track of it and move on
341 hr = StrAllocString(&sczCurrentSection, wzOpenTagPrefix + lstrlenW(pi->sczOpenTagPrefix), wzOpenTagPostfix - (wzOpenTagPrefix + lstrlenW(pi->sczOpenTagPrefix)));
342 IniExitOnFailure(hr, "Failed to record section name for line: %ls of INI file: %ls", pi->rgsczLines[i], pi->sczPath);
343
344 // Sections will be calculated dynamically after any set operations, so don't include this in the list of lines to remember for output
345 ReleaseNullStr(pi->rgsczLines[i]);
346 }
347 else if (wzValueSeparator && (NULL == wzCommentLinePrefix || wzValueSeparator < wzCommentLinePrefix)
348 && (!fValuePrefix || wzValuePrefix))
349 {
350 if (fValuePrefix)
351 {
352 wzValueBegin = wzValuePrefix + lstrlenW(pi->sczValuePrefix);
353 }
354 else
355 {
356 wzValueBegin = pi->rgsczLines[i];
357 }
358
359 hr = MemEnsureArraySize(reinterpret_cast<void **>(&pi->rgivValues), pi->cValues + 1, sizeof(INI_VALUE), 100);
360 IniExitOnFailure(hr, "Failed to increase array size for value array");
361
362 if (sczCurrentSection)
363 {
364 hr = StrAllocString(&sczName, sczCurrentSection, 0);
365 IniExitOnFailure(hr, "Failed to copy current section name");
366
367 hr = StrAllocConcat(&sczName, wzSectionSeparator, 0);
368 IniExitOnFailure(hr, "Failed to copy current section name");
369 }
370
371 hr = StrAllocConcat(&sczName, wzValueBegin, wzValueSeparator - wzValueBegin);
372 IniExitOnFailure(hr, "Failed to copy name");
373
374 hr = StrAllocString(&sczValue, wzValueSeparator + lstrlenW(pi->sczValueSeparator), 0);
375 IniExitOnFailure(hr, "Failed to copy value");
376
377 hr = StrTrimWhitespace(&sczNameTrimmed, sczName);
378 IniExitOnFailure(hr, "Failed to trim whitespace from name");
379
380 hr = StrTrimWhitespace(&sczValueTrimmed, sczValue);
381 IniExitOnFailure(hr, "Failed to trim whitespace from value");
382
383 pi->rgivValues[pi->cValues].wzName = const_cast<LPCWSTR>(sczNameTrimmed);
384 sczNameTrimmed = NULL;
385 pi->rgivValues[pi->cValues].wzValue = const_cast<LPCWSTR>(sczValueTrimmed);
386 sczValueTrimmed = NULL;
387 pi->rgivValues[pi->cValues].dwLineNumber = i + 1;
388
389 ++pi->cValues;
390
391 // Values will be calculated dynamically after any set operations, so don't include this in the list of lines to remember for output
392 ReleaseNullStr(pi->rgsczLines[i]);
393 }
394 else
395 {
396 // Must be a comment, so ignore it and keep it in the list to output
397 }
398
399 ReleaseNullStr(sczName);
400 }
401
402LExit:
403 ReleaseStr(sczCurrentSection);
404 ReleaseStr(sczContents);
405 ReleaseStr(sczName);
406 ReleaseStr(sczNameTrimmed);
407 ReleaseStr(sczValue);
408 ReleaseStr(sczValueTrimmed);
409
410 return hr;
411}
412
413extern "C" HRESULT DAPI IniGetValueList(
414 __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
415 __deref_out_ecount_opt(*pcValues) INI_VALUE** prgivValues,
416 __out DWORD *pcValues
417 )
418{
419 HRESULT hr = S_OK;
420
421 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
422
423 *prgivValues = pi->rgivValues;
424 *pcValues = pi->cValues;
425
426 return hr;
427}
428
429extern "C" HRESULT DAPI IniGetValue(
430 __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
431 __in LPCWSTR wzValueName,
432 __deref_out_z LPWSTR* psczValue
433 )
434{
435 HRESULT hr = S_OK;
436
437 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
438 INI_VALUE *pValue = NULL;
439
440 for (DWORD i = 0; i < pi->cValues; ++i)
441 {
442 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pi->rgivValues[i].wzName, -1, wzValueName, -1))
443 {
444 pValue = pi->rgivValues + i;
445 break;
446 }
447 }
448
449 if (NULL == pValue)
450 {
451 hr = E_NOTFOUND;
452 IniExitOnFailure(hr, "Failed to check for INI value: %ls", wzValueName);
453 }
454
455 if (NULL == pValue->wzValue)
456 {
457 ExitFunction1(hr = E_NOTFOUND);
458 }
459
460 hr = StrAllocString(psczValue, pValue->wzValue, 0);
461 IniExitOnFailure(hr, "Failed to make copy of value while looking up INI value named: %ls", wzValueName);
462
463LExit:
464 return hr;
465}
466
467extern "C" HRESULT DAPI IniSetValue(
468 __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
469 __in LPCWSTR wzValueName,
470 __in_z_opt LPCWSTR wzValue
471 )
472{
473 HRESULT hr = S_OK;
474 LPWSTR sczSectionPrefix = NULL; // includes section name and backslash
475 LPWSTR sczName = NULL;
476 LPWSTR sczValue = NULL;
477 DWORD dwInsertIndex = DWORD_MAX;
478
479 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
480 INI_VALUE *pValue = NULL;
481
482 for (DWORD i = 0; i < pi->cValues; ++i)
483 {
484 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pi->rgivValues[i].wzName, -1, wzValueName, -1))
485 {
486 pValue = pi->rgivValues + i;
487 break;
488 }
489 }
490
491 // We're killing the value
492 if (NULL == wzValue)
493 {
494 if (pValue && pValue->wzValue)
495 {
496 pi->fModified = TRUE;
497 sczValue = const_cast<LPWSTR>(pValue->wzValue);
498 pValue->wzValue = NULL;
499 ReleaseNullStr(sczValue);
500 }
501
502 ExitFunction();
503 }
504 else
505 {
506 if (pValue)
507 {
508 if (CSTR_EQUAL != ::CompareStringW(LOCALE_INVARIANT, 0, pValue->wzValue, -1, wzValue, -1))
509 {
510 pi->fModified = TRUE;
511 hr = StrAllocString(const_cast<LPWSTR *>(&pValue->wzValue), wzValue, 0);
512 IniExitOnFailure(hr, "Failed to update value INI value named: %ls", wzValueName);
513 }
514
515 ExitFunction1(hr = S_OK);
516 }
517 else
518 {
519 if (wzValueName)
520 {
521 hr = GetSectionPrefixFromName(wzValueName, &sczSectionPrefix);
522 IniExitOnFailure(hr, "Failed to get section prefix from value name: %ls", wzValueName);
523 }
524
525 // If we have a section prefix, figure out the index to insert it (at the end of the section it belongs in)
526 if (sczSectionPrefix)
527 {
528 for (DWORD i = 0; i < pi->cValues; ++i)
529 {
530 if (0 == wcsncmp(pi->rgivValues[i].wzName, sczSectionPrefix, lstrlenW(sczSectionPrefix)))
531 {
532 dwInsertIndex = i;
533 }
534 else if (DWORD_MAX != dwInsertIndex)
535 {
536 break;
537 }
538 }
539 }
540 else
541 {
542 for (DWORD i = 0; i < pi->cValues; ++i)
543 {
544 if (NULL == wcsstr(pi->rgivValues[i].wzName, wzSectionSeparator))
545 {
546 dwInsertIndex = i;
547 }
548 else if (DWORD_MAX != dwInsertIndex)
549 {
550 break;
551 }
552 }
553 }
554
555 // Otherwise, just add it to the end
556 if (DWORD_MAX == dwInsertIndex)
557 {
558 dwInsertIndex = pi->cValues;
559 }
560
561 pi->fModified = TRUE;
562 hr = MemInsertIntoArray(reinterpret_cast<void **>(&pi->rgivValues), dwInsertIndex, 1, pi->cValues + 1, sizeof(INI_VALUE), 100);
563 IniExitOnFailure(hr, "Failed to insert value into array");
564
565 hr = StrAllocString(&sczName, wzValueName, 0);
566 IniExitOnFailure(hr, "Failed to copy name");
567
568 hr = StrAllocString(&sczValue, wzValue, 0);
569 IniExitOnFailure(hr, "Failed to copy value");
570
571 pi->rgivValues[dwInsertIndex].wzName = const_cast<LPCWSTR>(sczName);
572 sczName = NULL;
573 pi->rgivValues[dwInsertIndex].wzValue = const_cast<LPCWSTR>(sczValue);
574 sczValue = NULL;
575
576 ++pi->cValues;
577 }
578 }
579
580LExit:
581 ReleaseStr(sczName);
582 ReleaseStr(sczValue);
583
584 return hr;
585}
586
587extern "C" HRESULT DAPI IniWriteFile(
588 __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
589 __in_z_opt LPCWSTR wzPath,
590 __in FILE_ENCODING feOverrideEncoding
591 )
592{
593 HRESULT hr = S_OK;
594 LPWSTR sczCurrentSectionPrefix = NULL;
595 LPWSTR sczNewSectionPrefix = NULL;
596 LPWSTR sczContents = NULL;
597 LPCWSTR wzName = NULL;
598 DWORD dwLineArrayIndex = 1;
599 FILE_ENCODING feEncoding;
600
601 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
602
603 if (FILE_ENCODING_UNSPECIFIED == feOverrideEncoding)
604 {
605 feEncoding = pi->feEncoding;
606 }
607 else
608 {
609 feEncoding = feOverrideEncoding;
610 }
611
612 if (FILE_ENCODING_UNSPECIFIED == feEncoding)
613 {
614 feEncoding = FILE_ENCODING_UTF16_WITH_BOM;
615 }
616
617 if (!pi->fModified)
618 {
619 ExitFunction1(hr = S_OK);
620 }
621 if (NULL == wzPath && NULL == pi->sczPath)
622 {
623 ExitFunction1(hr = E_NOTFOUND);
624 }
625
626 BOOL fSections = (pi->sczOpenTagPrefix) && (pi->sczOpenTagPostfix);
627
628 hr = StrAllocString(&sczContents, L"", 0);
629 IniExitOnFailure(hr, "Failed to begin contents string as empty string");
630
631 // Insert any beginning lines we didn't understand like comments
632 if (0 < pi->cLines)
633 {
634 while (pi->rgsczLines[dwLineArrayIndex])
635 {
636 hr = StrAllocConcat(&sczContents, pi->rgsczLines[dwLineArrayIndex], 0);
637 IniExitOnFailure(hr, "Failed to add previous line to ini output buffer in-memory");
638
639 hr = StrAllocConcat(&sczContents, L"\r\n", 2);
640 IniExitOnFailure(hr, "Failed to add endline to ini output buffer in-memory");
641
642 ++dwLineArrayIndex;
643 }
644 }
645
646 for (DWORD i = 0; i < pi->cValues; ++i)
647 {
648 // Skip if this value was killed off
649 if (NULL == pi->rgivValues[i].wzValue)
650 {
651 continue;
652 }
653
654 // Now generate any lines for the current value like value line and maybe also a new section line before it
655
656 // First see if we need to write a section line
657 hr = GetSectionPrefixFromName(pi->rgivValues[i].wzName, &sczNewSectionPrefix);
658 IniExitOnFailure(hr, "Failed to get section prefix from name: %ls", pi->rgivValues[i].wzName);
659
660 // If the new section prefix is different, write a section out for it
661 if (fSections && sczNewSectionPrefix && (NULL == sczCurrentSectionPrefix || CSTR_EQUAL != ::CompareStringW(LOCALE_INVARIANT, 0, sczNewSectionPrefix, -1, sczCurrentSectionPrefix, -1)))
662 {
663 hr = StrAllocConcat(&sczContents, pi->sczOpenTagPrefix, 0);
664 IniExitOnFailure(hr, "Failed to concat open tag prefix to string");
665
666 // Exclude section separator (i.e. backslash) from new section prefix
667 hr = StrAllocConcat(&sczContents, sczNewSectionPrefix, lstrlenW(sczNewSectionPrefix)-lstrlenW(wzSectionSeparator));
668 IniExitOnFailure(hr, "Failed to concat section name to string");
669
670 hr = StrAllocConcat(&sczContents, pi->sczOpenTagPostfix, 0);
671 IniExitOnFailure(hr, "Failed to concat open tag postfix to string");
672
673 hr = StrAllocConcat(&sczContents, L"\r\n", 2);
674 IniExitOnFailure(hr, "Failed to add endline to ini output buffer in-memory");
675
676 ReleaseNullStr(sczCurrentSectionPrefix);
677 sczCurrentSectionPrefix = sczNewSectionPrefix;
678 sczNewSectionPrefix = NULL;
679 }
680
681 // Inserting lines we read before the current value if appropriate
682 while (pi->rgivValues[i].dwLineNumber > dwLineArrayIndex)
683 {
684 // Skip any lines were purposely forgot
685 if (NULL == pi->rgsczLines[dwLineArrayIndex])
686 {
687 ++dwLineArrayIndex;
688 continue;
689 }
690
691 hr = StrAllocConcat(&sczContents, pi->rgsczLines[dwLineArrayIndex++], 0);
692 IniExitOnFailure(hr, "Failed to add previous line to ini output buffer in-memory");
693
694 hr = StrAllocConcat(&sczContents, L"\r\n", 2);
695 IniExitOnFailure(hr, "Failed to add endline to ini output buffer in-memory");
696 }
697
698 wzName = pi->rgivValues[i].wzName;
699 if (fSections)
700 {
701 wzName += lstrlenW(sczCurrentSectionPrefix);
702 }
703
704 // OK, now just write the name/value pair, if it isn't deleted
705 if (pi->sczValuePrefix)
706 {
707 hr = StrAllocConcat(&sczContents, pi->sczValuePrefix, 0);
708 IniExitOnFailure(hr, "Failed to concat value prefix to ini output buffer");
709 }
710
711 hr = StrAllocConcat(&sczContents, wzName, 0);
712 IniExitOnFailure(hr, "Failed to concat value name to ini output buffer");
713
714 hr = StrAllocConcat(&sczContents, pi->sczValueSeparator, 0);
715 IniExitOnFailure(hr, "Failed to concat value separator to ini output buffer");
716
717 hr = StrAllocConcat(&sczContents, pi->rgivValues[i].wzValue, 0);
718 IniExitOnFailure(hr, "Failed to concat value to ini output buffer");
719
720 hr = StrAllocConcat(&sczContents, L"\r\n", 2);
721 IniExitOnFailure(hr, "Failed to add endline to ini output buffer in-memory");
722 }
723
724 // If no path was specified, use the path to the file we parsed
725 if (NULL == wzPath)
726 {
727 wzPath = pi->sczPath;
728 }
729
730 hr = FileFromString(wzPath, 0, sczContents, feEncoding);
731 IniExitOnFailure(hr, "Failed to write INI contents out to file: %ls", wzPath);
732
733LExit:
734 ReleaseStr(sczContents);
735 ReleaseStr(sczCurrentSectionPrefix);
736 ReleaseStr(sczNewSectionPrefix);
737
738 return hr;
739}
740
741static void UninitializeIniValue(
742 INI_VALUE *pivValue
743 )
744{
745 ReleaseStr(const_cast<LPWSTR>(pivValue->wzName));
746 ReleaseStr(const_cast<LPWSTR>(pivValue->wzValue));
747}
748
749static HRESULT GetSectionPrefixFromName(
750 __in_z LPCWSTR wzName,
751 __deref_inout_z LPWSTR* psczOutput
752 )
753{
754 HRESULT hr = S_OK;
755 LPCWSTR wzSectionDelimiter = NULL;
756
757 ReleaseNullStr(*psczOutput);
758
759 wzSectionDelimiter = wcsstr(wzName, wzSectionSeparator);
760 if (wzSectionDelimiter && wzSectionDelimiter != wzName)
761 {
762 hr = StrAllocString(psczOutput, wzName, wzSectionDelimiter - wzName + 1);
763 IniExitOnFailure(hr, "Failed to copy section prefix");
764 }
765
766LExit:
767 return hr;
768}
diff --git a/src/libs/dutil/WixToolset.DUtil/jsonutil.cpp b/src/libs/dutil/WixToolset.DUtil/jsonutil.cpp
new file mode 100644
index 00000000..3450ba59
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/jsonutil.cpp
@@ -0,0 +1,687 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define JsonExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_JSONUTIL, x, s, __VA_ARGS__)
8#define JsonExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_JSONUTIL, x, s, __VA_ARGS__)
9#define JsonExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_JSONUTIL, x, s, __VA_ARGS__)
10#define JsonExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_JSONUTIL, x, s, __VA_ARGS__)
11#define JsonExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_JSONUTIL, x, s, __VA_ARGS__)
12#define JsonExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_JSONUTIL, x, s, __VA_ARGS__)
13#define JsonExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_JSONUTIL, p, x, e, s, __VA_ARGS__)
14#define JsonExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_JSONUTIL, p, x, s, __VA_ARGS__)
15#define JsonExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_JSONUTIL, p, x, e, s, __VA_ARGS__)
16#define JsonExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_JSONUTIL, p, x, s, __VA_ARGS__)
17#define JsonExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_JSONUTIL, e, x, s, __VA_ARGS__)
18#define JsonExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_JSONUTIL, g, x, s, __VA_ARGS__)
19
20const DWORD JSON_STACK_INCREMENT = 5;
21
22// Prototypes
23static HRESULT DoStart(
24 __in JSON_WRITER* pWriter,
25 __in JSON_TOKEN tokenStart,
26 __in_z LPCWSTR wzStartString
27 );
28static HRESULT DoEnd(
29 __in JSON_WRITER* pWriter,
30 __in JSON_TOKEN tokenEnd,
31 __in_z LPCWSTR wzEndString
32 );
33static HRESULT DoKey(
34 __in JSON_WRITER* pWriter,
35 __in_z LPCWSTR wzKey
36 );
37static HRESULT DoValue(
38 __in JSON_WRITER* pWriter,
39 __in_z_opt LPCWSTR wzValue
40 );
41static HRESULT EnsureTokenStack(
42 __in JSON_WRITER* pWriter
43 );
44static HRESULT SerializeJsonString(
45 __out_z LPWSTR* psczJsonString,
46 __in_z LPCWSTR wzString
47 );
48
49
50
51DAPI_(HRESULT) JsonInitializeReader(
52 __in_z LPCWSTR wzJson,
53 __in JSON_READER* pReader
54 )
55{
56 HRESULT hr = S_OK;
57
58 memset(pReader, 0, sizeof(JSON_READER));
59 ::InitializeCriticalSection(&pReader->cs);
60
61 hr = StrAllocString(&pReader->sczJson, wzJson, 0);
62 JsonExitOnFailure(hr, "Failed to allocate json string.");
63
64 pReader->pwz = pReader->sczJson;
65
66LExit:
67 return hr;
68}
69
70
71DAPI_(void) JsonUninitializeReader(
72 __in JSON_READER* pReader
73 )
74{
75 ReleaseStr(pReader->sczJson);
76
77 ::DeleteCriticalSection(&pReader->cs);
78 memset(pReader, 0, sizeof(JSON_READER));
79}
80
81
82static HRESULT NextToken(
83 __in JSON_READER* pReader,
84 __out JSON_TOKEN* pToken
85 )
86{
87 HRESULT hr = S_OK;
88
89 // Skip whitespace.
90 while (L' ' == *pReader->pwz ||
91 L'\t' == *pReader->pwz ||
92 L'\r' == *pReader->pwz ||
93 L'\n' == *pReader->pwz ||
94 L',' == *pReader->pwz)
95 {
96 ++pReader->pwz;
97 }
98
99 switch (*pReader->pwz)
100 {
101 case L'{':
102 *pToken = JSON_TOKEN_OBJECT_START;
103 break;
104
105 case L':': // begin object value
106 *pToken = JSON_TOKEN_OBJECT_VALUE;
107 break;
108
109 case L'}': // end object
110 *pToken = JSON_TOKEN_OBJECT_END;
111 break;
112
113 case L'[': // begin array
114 *pToken = JSON_TOKEN_ARRAY_START;
115 break;
116
117 case L']': // end array
118 *pToken = JSON_TOKEN_ARRAY_END;
119 break;
120
121 case L'"': // string
122 *pToken = JSON_TOKEN_OBJECT_START == *pToken ? JSON_TOKEN_VALUE : JSON_TOKEN_OBJECT_KEY;
123 break;
124
125 case L't': // true
126 case L'f': // false
127 case L'n': // null
128 case L'-': // number
129 case L'0':
130 case L'1':
131 case L'2':
132 case L'3':
133 case L'4':
134 case L'5':
135 case L'6':
136 case L'7':
137 case L'8':
138 case L'9':
139 *pToken = JSON_TOKEN_VALUE;
140 break;
141
142 case L'\0':
143 hr = E_NOMOREITEMS;
144 break;
145
146 default:
147 hr = E_INVALIDSTATE;
148 }
149
150 return hr;
151}
152
153
154DAPI_(HRESULT) JsonReadNext(
155 __in JSON_READER* pReader,
156 __out JSON_TOKEN* pToken,
157 __out JSON_VALUE* pValue
158 )
159{
160 HRESULT hr = S_OK;
161 //WCHAR wz;
162 //JSON_TOKEN token = JSON_TOKEN_NONE;
163
164 ::EnterCriticalSection(&pReader->cs);
165
166 hr = NextToken(pReader, pToken);
167 if (E_NOMOREITEMS == hr)
168 {
169 ExitFunction();
170 }
171 JsonExitOnFailure(hr, "Failed to get next token.");
172
173 if (JSON_TOKEN_VALUE == *pToken)
174 {
175 hr = JsonReadValue(pReader, pValue);
176 }
177 else
178 {
179 ++pReader->pwz;
180 }
181
182LExit:
183 ::LeaveCriticalSection(&pReader->cs);
184 return hr;
185}
186
187
188DAPI_(HRESULT) JsonReadValue(
189 __in JSON_READER* /*pReader*/,
190 __in JSON_VALUE* /*pValue*/
191 )
192{
193 HRESULT hr = S_OK;
194
195//LExit:
196 return hr;
197}
198
199
200DAPI_(HRESULT) JsonInitializeWriter(
201 __in JSON_WRITER* pWriter
202 )
203{
204 memset(pWriter, 0, sizeof(JSON_WRITER));
205 ::InitializeCriticalSection(&pWriter->cs);
206
207 return S_OK;
208}
209
210
211DAPI_(void) JsonUninitializeWriter(
212 __in JSON_WRITER* pWriter
213 )
214{
215 ReleaseMem(pWriter->rgTokenStack);
216 ReleaseStr(pWriter->sczJson);
217
218 ::DeleteCriticalSection(&pWriter->cs);
219 memset(pWriter, 0, sizeof(JSON_WRITER));
220}
221
222
223DAPI_(HRESULT) JsonWriteBool(
224 __in JSON_WRITER* pWriter,
225 __in BOOL fValue
226 )
227{
228 HRESULT hr = S_OK;
229 LPWSTR sczValue = NULL;
230
231 hr = StrAllocString(&sczValue, fValue ? L"true" : L"false", 0);
232 JsonExitOnFailure(hr, "Failed to convert boolean to string.");
233
234 hr = DoValue(pWriter, sczValue);
235 JsonExitOnFailure(hr, "Failed to add boolean to JSON.");
236
237LExit:
238 ReleaseStr(sczValue);
239 return hr;
240}
241
242
243DAPI_(HRESULT) JsonWriteNumber(
244 __in JSON_WRITER* pWriter,
245 __in DWORD dwValue
246 )
247{
248 HRESULT hr = S_OK;
249 LPWSTR sczValue = NULL;
250
251 hr = StrAllocFormatted(&sczValue, L"%u", dwValue);
252 JsonExitOnFailure(hr, "Failed to convert number to string.");
253
254 hr = DoValue(pWriter, sczValue);
255 JsonExitOnFailure(hr, "Failed to add number to JSON.");
256
257LExit:
258 ReleaseStr(sczValue);
259 return hr;
260}
261
262
263DAPI_(HRESULT) JsonWriteString(
264 __in JSON_WRITER* pWriter,
265 __in_z LPCWSTR wzValue
266 )
267{
268 HRESULT hr = S_OK;
269 LPWSTR sczJsonString = NULL;
270
271 hr = SerializeJsonString(&sczJsonString, wzValue);
272 JsonExitOnFailure(hr, "Failed to allocate string JSON.");
273
274 hr = DoValue(pWriter, sczJsonString);
275 JsonExitOnFailure(hr, "Failed to add string to JSON.");
276
277LExit:
278 ReleaseStr(sczJsonString);
279 return hr;
280}
281
282
283DAPI_(HRESULT) JsonWriteArrayStart(
284 __in JSON_WRITER* pWriter
285 )
286{
287 HRESULT hr = S_OK;
288
289 hr = DoStart(pWriter, JSON_TOKEN_ARRAY_START, L"[");
290 JsonExitOnFailure(hr, "Failed to start JSON array.");
291
292LExit:
293 return hr;
294}
295
296
297DAPI_(HRESULT) JsonWriteArrayEnd(
298 __in JSON_WRITER* pWriter
299 )
300{
301 HRESULT hr = S_OK;
302
303 hr = DoEnd(pWriter, JSON_TOKEN_ARRAY_END, L"]");
304 JsonExitOnFailure(hr, "Failed to end JSON array.");
305
306LExit:
307 return hr;
308}
309
310
311DAPI_(HRESULT) JsonWriteObjectStart(
312 __in JSON_WRITER* pWriter
313 )
314{
315 HRESULT hr = S_OK;
316
317 hr = DoStart(pWriter, JSON_TOKEN_OBJECT_START, L"{");
318 JsonExitOnFailure(hr, "Failed to start JSON object.");
319
320LExit:
321 return hr;
322}
323
324
325DAPI_(HRESULT) JsonWriteObjectKey(
326 __in JSON_WRITER* pWriter,
327 __in_z LPCWSTR wzKey
328 )
329{
330 HRESULT hr = S_OK;
331 LPWSTR sczObjectKey = NULL;
332
333 hr = StrAllocFormatted(&sczObjectKey, L"\"%ls\":", wzKey);
334 JsonExitOnFailure(hr, "Failed to allocate JSON object key.");
335
336 hr = DoKey(pWriter, sczObjectKey);
337 JsonExitOnFailure(hr, "Failed to add object key to JSON.");
338
339LExit:
340 ReleaseStr(sczObjectKey);
341 return hr;
342}
343
344
345DAPI_(HRESULT) JsonWriteObjectEnd(
346 __in JSON_WRITER* pWriter
347 )
348{
349 HRESULT hr = S_OK;
350
351 hr = DoEnd(pWriter, JSON_TOKEN_OBJECT_END, L"}");
352 JsonExitOnFailure(hr, "Failed to end JSON object.");
353
354LExit:
355 return hr;
356}
357
358
359static HRESULT DoStart(
360 __in JSON_WRITER* pWriter,
361 __in JSON_TOKEN tokenStart,
362 __in_z LPCWSTR wzStartString
363 )
364{
365 Assert(JSON_TOKEN_ARRAY_START == tokenStart || JSON_TOKEN_OBJECT_START == tokenStart);
366
367 HRESULT hr = S_OK;
368 JSON_TOKEN token = JSON_TOKEN_NONE;
369 BOOL fNeedComma = FALSE;
370 BOOL fPushToken = TRUE;
371
372 ::EnterCriticalSection(&pWriter->cs);
373
374 hr = EnsureTokenStack(pWriter);
375 JsonExitOnFailure(hr, "Failed to ensure token stack for start.");
376
377 token = pWriter->rgTokenStack[pWriter->cTokens - 1];
378 switch (token)
379 {
380 case JSON_TOKEN_NONE:
381 token = tokenStart;
382 fPushToken = FALSE;
383 break;
384
385 case JSON_TOKEN_ARRAY_START: // array start changes to array value.
386 token = JSON_TOKEN_ARRAY_VALUE;
387 break;
388
389 case JSON_TOKEN_ARRAY_VALUE:
390 case JSON_TOKEN_ARRAY_END:
391 case JSON_TOKEN_OBJECT_END:
392 fNeedComma = TRUE;
393 break;
394
395 default: // everything else is not allowed.
396 hr = E_UNEXPECTED;
397 break;
398 }
399 JsonExitOnRootFailure(hr, "Cannot start array or object to JSON serializer now.");
400
401 if (fNeedComma)
402 {
403 hr = StrAllocConcat(&pWriter->sczJson, L",", 0);
404 JsonExitOnFailure(hr, "Failed to add comma for start array or object to JSON.");
405 }
406
407 hr = StrAllocConcat(&pWriter->sczJson, wzStartString, 0);
408 JsonExitOnFailure(hr, "Failed to start JSON array or object.");
409
410 pWriter->rgTokenStack[pWriter->cTokens - 1] = token;
411 if (fPushToken)
412 {
413 pWriter->rgTokenStack[pWriter->cTokens] = tokenStart;
414 ++pWriter->cTokens;
415 }
416
417LExit:
418 ::LeaveCriticalSection(&pWriter->cs);
419 return hr;
420}
421
422
423static HRESULT DoEnd(
424 __in JSON_WRITER* pWriter,
425 __in JSON_TOKEN tokenEnd,
426 __in_z LPCWSTR wzEndString
427 )
428{
429 HRESULT hr = S_OK;
430
431 ::EnterCriticalSection(&pWriter->cs);
432
433 if (!pWriter->rgTokenStack || 0 == pWriter->cTokens)
434 {
435 hr = E_UNEXPECTED;
436 JsonExitOnRootFailure(hr, "Failure to pop token because the stack is empty.");
437 }
438 else
439 {
440 JSON_TOKEN token = pWriter->rgTokenStack[pWriter->cTokens - 1];
441 if ((JSON_TOKEN_ARRAY_END == tokenEnd && JSON_TOKEN_ARRAY_START != token && JSON_TOKEN_ARRAY_VALUE != token) ||
442 (JSON_TOKEN_OBJECT_END == tokenEnd && JSON_TOKEN_OBJECT_START != token && JSON_TOKEN_OBJECT_VALUE != token))
443 {
444 hr = E_UNEXPECTED;
445 JsonExitOnRootFailure(hr, "Failure to pop token because the stack did not match the expected token: %d", tokenEnd);
446 }
447 }
448
449 hr = StrAllocConcat(&pWriter->sczJson, wzEndString, 0);
450 JsonExitOnFailure(hr, "Failed to end JSON array or object.");
451
452 --pWriter->cTokens;
453
454LExit:
455 ::LeaveCriticalSection(&pWriter->cs);
456 return hr;
457}
458
459
460static HRESULT DoKey(
461 __in JSON_WRITER* pWriter,
462 __in_z LPCWSTR wzKey
463 )
464{
465 HRESULT hr = S_OK;
466 JSON_TOKEN token = JSON_TOKEN_NONE;
467 BOOL fNeedComma = FALSE;
468
469 ::EnterCriticalSection(&pWriter->cs);
470
471 hr = EnsureTokenStack(pWriter);
472 JsonExitOnFailure(hr, "Failed to ensure token stack for key.");
473
474 token = pWriter->rgTokenStack[pWriter->cTokens - 1];
475 switch (token)
476 {
477 case JSON_TOKEN_OBJECT_START:
478 token = JSON_TOKEN_OBJECT_KEY;
479 break;
480
481 case JSON_TOKEN_OBJECT_VALUE:
482 token = JSON_TOKEN_OBJECT_KEY;
483 fNeedComma = TRUE;
484 break;
485
486 default: // everything else is not allowed.
487 hr = E_UNEXPECTED;
488 break;
489 }
490 JsonExitOnRootFailure(hr, "Cannot add key to JSON serializer now.");
491
492 if (fNeedComma)
493 {
494 hr = StrAllocConcat(&pWriter->sczJson, L",", 0);
495 JsonExitOnFailure(hr, "Failed to add comma for key to JSON.");
496 }
497
498 hr = StrAllocConcat(&pWriter->sczJson, wzKey, 0);
499 JsonExitOnFailure(hr, "Failed to add key to JSON.");
500
501 pWriter->rgTokenStack[pWriter->cTokens - 1] = token;
502
503LExit:
504 ::LeaveCriticalSection(&pWriter->cs);
505 return hr;
506}
507
508
509static HRESULT DoValue(
510 __in JSON_WRITER* pWriter,
511 __in_z_opt LPCWSTR wzValue
512 )
513{
514 HRESULT hr = S_OK;
515 JSON_TOKEN token = JSON_TOKEN_NONE;
516 BOOL fNeedComma = FALSE;
517
518 ::EnterCriticalSection(&pWriter->cs);
519
520 hr = EnsureTokenStack(pWriter);
521 JsonExitOnFailure(hr, "Failed to ensure token stack for value.");
522
523 token = pWriter->rgTokenStack[pWriter->cTokens - 1];
524 switch (token)
525 {
526 case JSON_TOKEN_ARRAY_START:
527 token = JSON_TOKEN_ARRAY_VALUE;
528 break;
529
530 case JSON_TOKEN_ARRAY_VALUE:
531 fNeedComma = TRUE;
532 break;
533
534 case JSON_TOKEN_OBJECT_KEY:
535 token = JSON_TOKEN_OBJECT_VALUE;
536 break;
537
538 case JSON_TOKEN_NONE:
539 token = JSON_TOKEN_VALUE;
540 break;
541
542 default: // everything else is not allowed.
543 hr = E_UNEXPECTED;
544 break;
545 }
546 JsonExitOnRootFailure(hr, "Cannot add value to JSON serializer now.");
547
548 if (fNeedComma)
549 {
550 hr = StrAllocConcat(&pWriter->sczJson, L",", 0);
551 JsonExitOnFailure(hr, "Failed to add comma for value to JSON.");
552 }
553
554 if (wzValue)
555 {
556 hr = StrAllocConcat(&pWriter->sczJson, wzValue, 0);
557 JsonExitOnFailure(hr, "Failed to add value to JSON.");
558 }
559 else
560 {
561 hr = StrAllocConcat(&pWriter->sczJson, L"null", 0);
562 JsonExitOnFailure(hr, "Failed to add null value to JSON.");
563 }
564
565 pWriter->rgTokenStack[pWriter->cTokens - 1] = token;
566
567LExit:
568 ::LeaveCriticalSection(&pWriter->cs);
569 return hr;
570}
571
572
573static HRESULT EnsureTokenStack(
574 __in JSON_WRITER* pWriter
575 )
576{
577 HRESULT hr = S_OK;
578 DWORD cNumAlloc = pWriter->cTokens != 0 ? pWriter->cTokens : 0;
579
580 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&pWriter->rgTokenStack), cNumAlloc, sizeof(JSON_TOKEN), JSON_STACK_INCREMENT);
581 JsonExitOnFailure(hr, "Failed to allocate JSON token stack.");
582
583 if (0 == pWriter->cTokens)
584 {
585 pWriter->rgTokenStack[0] = JSON_TOKEN_NONE;
586 ++pWriter->cTokens;
587 }
588
589LExit:
590 return hr;
591}
592
593
594static HRESULT SerializeJsonString(
595 __out_z LPWSTR* psczJsonString,
596 __in_z LPCWSTR wzString
597 )
598{
599 HRESULT hr = S_OK;
600 DWORD cchRequired = 3; // start with enough space for null terminated empty quoted string (aka: ""\0)
601
602 for (LPCWSTR pch = wzString; *pch; ++pch)
603 {
604 // If it is a special JSON character, add space for the escape backslash.
605 if (L'"' == *pch || L'\\' == *pch || L'/' == *pch || L'\b' == *pch || L'\f' == *pch || L'\n' == *pch || L'\r' == *pch || L'\t' == *pch)
606 {
607 ++cchRequired;
608 }
609
610 ++cchRequired;
611 }
612
613 hr = StrAlloc(psczJsonString, cchRequired);
614 JsonExitOnFailure(hr, "Failed to allocate space for JSON string.");
615
616 LPWSTR pchTarget = *psczJsonString;
617
618 *pchTarget = L'\"';
619 ++pchTarget;
620
621 for (LPCWSTR pch = wzString; *pch; ++pch, ++pchTarget)
622 {
623 // If it is a special JSON character, handle it or just add the character as is.
624 switch (*pch)
625 {
626 case L'"':
627 *pchTarget = L'\\';
628 ++pchTarget;
629 *pchTarget = L'"';
630 break;
631
632 case L'\\':
633 *pchTarget = L'\\';
634 ++pchTarget;
635 *pchTarget = L'\\';
636 break;
637
638 case L'/':
639 *pchTarget = L'\\';
640 ++pchTarget;
641 *pchTarget = L'/';
642 break;
643
644 case L'\b':
645 *pchTarget = L'\\';
646 ++pchTarget;
647 *pchTarget = L'b';
648 break;
649
650 case L'\f':
651 *pchTarget = L'\\';
652 ++pchTarget;
653 *pchTarget = L'f';
654 break;
655
656 case L'\n':
657 *pchTarget = L'\\';
658 ++pchTarget;
659 *pchTarget = L'n';
660 break;
661
662 case L'\r':
663 *pchTarget = L'\\';
664 ++pchTarget;
665 *pchTarget = L'r';
666 break;
667
668 case L'\t':
669 *pchTarget = L'\\';
670 ++pchTarget;
671 *pchTarget = L't';
672 break;
673
674 default:
675 *pchTarget = *pch;
676 break;
677 }
678
679 }
680
681 *pchTarget = L'\"';
682 ++pchTarget;
683 *pchTarget = L'\0';
684
685LExit:
686 return hr;
687}
diff --git a/src/libs/dutil/WixToolset.DUtil/locutil.cpp b/src/libs/dutil/WixToolset.DUtil/locutil.cpp
new file mode 100644
index 00000000..c4567c03
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/locutil.cpp
@@ -0,0 +1,628 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define LocExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_LOCUTIL, x, s, __VA_ARGS__)
8#define LocExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_LOCUTIL, x, s, __VA_ARGS__)
9#define LocExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_LOCUTIL, x, s, __VA_ARGS__)
10#define LocExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_LOCUTIL, x, s, __VA_ARGS__)
11#define LocExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_LOCUTIL, x, s, __VA_ARGS__)
12#define LocExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_LOCUTIL, x, s, __VA_ARGS__)
13#define LocExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_LOCUTIL, p, x, e, s, __VA_ARGS__)
14#define LocExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_LOCUTIL, p, x, s, __VA_ARGS__)
15#define LocExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_LOCUTIL, p, x, e, s, __VA_ARGS__)
16#define LocExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_LOCUTIL, p, x, s, __VA_ARGS__)
17#define LocExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_LOCUTIL, e, x, s, __VA_ARGS__)
18#define LocExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_LOCUTIL, g, x, s, __VA_ARGS__)
19
20// prototypes
21static HRESULT ParseWxl(
22 __in IXMLDOMDocument* pixd,
23 __out WIX_LOCALIZATION** ppWixLoc
24 );
25static HRESULT ParseWxlStrings(
26 __in IXMLDOMElement* pElement,
27 __in WIX_LOCALIZATION* pWixLoc
28 );
29static HRESULT ParseWxlControls(
30 __in IXMLDOMElement* pElement,
31 __in WIX_LOCALIZATION* pWixLoc
32 );
33static HRESULT ParseWxlString(
34 __in IXMLDOMNode* pixn,
35 __in DWORD dwIdx,
36 __in WIX_LOCALIZATION* pWixLoc
37 );
38static HRESULT ParseWxlControl(
39 __in IXMLDOMNode* pixn,
40 __in DWORD dwIdx,
41 __in WIX_LOCALIZATION* pWixLoc
42 );
43
44// from Winnls.h
45#ifndef MUI_LANGUAGE_ID
46#define MUI_LANGUAGE_ID 0x4 // Use traditional language ID convention
47#endif
48#ifndef MUI_MERGE_USER_FALLBACK
49#define MUI_MERGE_USER_FALLBACK 0x20 // GetThreadPreferredUILanguages merges in user preferred languages
50#endif
51#ifndef MUI_MERGE_SYSTEM_FALLBACK
52#define MUI_MERGE_SYSTEM_FALLBACK 0x10 // GetThreadPreferredUILanguages merges in parent and base languages
53#endif
54typedef WINBASEAPI BOOL (WINAPI *GET_THREAD_PREFERRED_UI_LANGUAGES) (
55 __in DWORD dwFlags,
56 __out PULONG pulNumLanguages,
57 __out_ecount_opt(*pcchLanguagesBuffer) PZZWSTR pwszLanguagesBuffer,
58 __inout PULONG pcchLanguagesBuffer
59);
60
61extern "C" HRESULT DAPI LocProbeForFile(
62 __in_z LPCWSTR wzBasePath,
63 __in_z LPCWSTR wzLocFileName,
64 __in_z_opt LPCWSTR wzLanguage,
65 __inout LPWSTR* psczPath
66 )
67{
68 HRESULT hr = S_OK;
69 LPWSTR sczProbePath = NULL;
70 LANGID langid = 0;
71 LPWSTR sczLangIdFile = NULL;
72 LPWSTR sczLangsBuff = NULL;
73 GET_THREAD_PREFERRED_UI_LANGUAGES pvfnGetThreadPreferredUILanguages =
74 reinterpret_cast<GET_THREAD_PREFERRED_UI_LANGUAGES>(
75 GetProcAddress(GetModuleHandle("Kernel32.dll"), "GetThreadPreferredUILanguages"));
76
77 // If a language was specified, look for a loc file in that as a directory.
78 if (wzLanguage && *wzLanguage)
79 {
80 hr = PathConcat(wzBasePath, wzLanguage, &sczProbePath);
81 LocExitOnFailure(hr, "Failed to concat base path to language.");
82
83 hr = PathConcat(sczProbePath, wzLocFileName, &sczProbePath);
84 LocExitOnFailure(hr, "Failed to concat loc file name to probe path.");
85
86 if (FileExistsEx(sczProbePath, NULL))
87 {
88 ExitFunction();
89 }
90 }
91
92 if (pvfnGetThreadPreferredUILanguages)
93 {
94 ULONG nLangs;
95 ULONG cchLangs = 0;
96 DWORD dwFlags = MUI_LANGUAGE_ID | MUI_MERGE_USER_FALLBACK | MUI_MERGE_SYSTEM_FALLBACK;
97 if (!(*pvfnGetThreadPreferredUILanguages)(dwFlags, &nLangs, NULL, &cchLangs))
98 {
99 LocExitWithLastError(hr, "GetThreadPreferredUILanguages failed to return buffer size.");
100 }
101
102 hr = StrAlloc(&sczLangsBuff, cchLangs);
103 LocExitOnFailure(hr, "Failed to allocate buffer for languages");
104
105 nLangs = 0;
106 if (!(*pvfnGetThreadPreferredUILanguages)(dwFlags, &nLangs, sczLangsBuff, &cchLangs))
107 {
108 LocExitWithLastError(hr, "GetThreadPreferredUILanguages failed to return language list.");
109 }
110
111 LPWSTR szLangs = sczLangsBuff;
112 for (ULONG i = 0; i < nLangs; ++i, szLangs += 5)
113 {
114 // StrHexDecode assumes low byte is first. We'll need to swap the bytes once we parse out the value.
115 hr = StrHexDecode(szLangs, reinterpret_cast<BYTE*>(&langid), sizeof(langid));
116 LocExitOnFailure(hr, "Failed to parse langId.");
117
118 langid = MAKEWORD(HIBYTE(langid), LOBYTE(langid));
119 hr = StrAllocFormatted(&sczLangIdFile, L"%u\\%ls", langid, wzLocFileName);
120 LocExitOnFailure(hr, "Failed to format user preferred langid.");
121
122 hr = PathConcat(wzBasePath, sczLangIdFile, &sczProbePath);
123 LocExitOnFailure(hr, "Failed to concat user preferred langid file name to base path.");
124
125 if (FileExistsEx(sczProbePath, NULL))
126 {
127 ExitFunction();
128 }
129 }
130 }
131
132 langid = ::GetUserDefaultUILanguage();
133
134 hr = StrAllocFormatted(&sczLangIdFile, L"%u\\%ls", langid, wzLocFileName);
135 LocExitOnFailure(hr, "Failed to format user langid.");
136
137 hr = PathConcat(wzBasePath, sczLangIdFile, &sczProbePath);
138 LocExitOnFailure(hr, "Failed to concat user langid file name to base path.");
139
140 if (FileExistsEx(sczProbePath, NULL))
141 {
142 ExitFunction();
143 }
144
145 if (MAKELANGID(langid & 0x3FF, SUBLANG_DEFAULT) != langid)
146 {
147 langid = MAKELANGID(langid & 0x3FF, SUBLANG_DEFAULT);
148
149 hr = StrAllocFormatted(&sczLangIdFile, L"%u\\%ls", langid, wzLocFileName);
150 LocExitOnFailure(hr, "Failed to format user langid (default sublang).");
151
152 hr = PathConcat(wzBasePath, sczLangIdFile, &sczProbePath);
153 LocExitOnFailure(hr, "Failed to concat user langid file name to base path (default sublang).");
154
155 if (FileExistsEx(sczProbePath, NULL))
156 {
157 ExitFunction();
158 }
159 }
160
161 langid = ::GetSystemDefaultUILanguage();
162
163 hr = StrAllocFormatted(&sczLangIdFile, L"%u\\%ls", langid, wzLocFileName);
164 LocExitOnFailure(hr, "Failed to format system langid.");
165
166 hr = PathConcat(wzBasePath, sczLangIdFile, &sczProbePath);
167 LocExitOnFailure(hr, "Failed to concat system langid file name to base path.");
168
169 if (FileExistsEx(sczProbePath, NULL))
170 {
171 ExitFunction();
172 }
173
174 if (MAKELANGID(langid & 0x3FF, SUBLANG_DEFAULT) != langid)
175 {
176 langid = MAKELANGID(langid & 0x3FF, SUBLANG_DEFAULT);
177
178 hr = StrAllocFormatted(&sczLangIdFile, L"%u\\%ls", langid, wzLocFileName);
179 LocExitOnFailure(hr, "Failed to format user langid (default sublang).");
180
181 hr = PathConcat(wzBasePath, sczLangIdFile, &sczProbePath);
182 LocExitOnFailure(hr, "Failed to concat user langid file name to base path (default sublang).");
183
184 if (FileExistsEx(sczProbePath, NULL))
185 {
186 ExitFunction();
187 }
188 }
189
190 // Finally, look for the loc file in the base path.
191 hr = PathConcat(wzBasePath, wzLocFileName, &sczProbePath);
192 LocExitOnFailure(hr, "Failed to concat loc file name to base path.");
193
194 if (!FileExistsEx(sczProbePath, NULL))
195 {
196 hr = E_FILENOTFOUND;
197 }
198
199LExit:
200 if (SUCCEEDED(hr))
201 {
202 hr = StrAllocString(psczPath, sczProbePath, 0);
203 }
204
205 ReleaseStr(sczLangIdFile);
206 ReleaseStr(sczProbePath);
207 ReleaseStr(sczLangsBuff);
208
209 return hr;
210}
211
212extern "C" HRESULT DAPI LocLoadFromFile(
213 __in_z LPCWSTR wzWxlFile,
214 __out WIX_LOCALIZATION** ppWixLoc
215 )
216{
217 HRESULT hr = S_OK;
218 IXMLDOMDocument* pixd = NULL;
219
220 hr = XmlLoadDocumentFromFile(wzWxlFile, &pixd);
221 LocExitOnFailure(hr, "Failed to load WXL file as XML document.");
222
223 hr = ParseWxl(pixd, ppWixLoc);
224 LocExitOnFailure(hr, "Failed to parse WXL.");
225
226LExit:
227 ReleaseObject(pixd);
228
229 return hr;
230}
231
232extern "C" HRESULT DAPI LocLoadFromResource(
233 __in HMODULE hModule,
234 __in_z LPCSTR szResource,
235 __out WIX_LOCALIZATION** ppWixLoc
236 )
237{
238 HRESULT hr = S_OK;
239 LPVOID pvResource = NULL;
240 DWORD cbResource = 0;
241 LPWSTR sczXml = NULL;
242 IXMLDOMDocument* pixd = NULL;
243
244 hr = ResReadData(hModule, szResource, &pvResource, &cbResource);
245 LocExitOnFailure(hr, "Failed to read theme from resource.");
246
247 hr = StrAllocStringAnsi(&sczXml, reinterpret_cast<LPCSTR>(pvResource), cbResource, CP_UTF8);
248 LocExitOnFailure(hr, "Failed to convert XML document data from UTF-8 to unicode string.");
249
250 hr = XmlLoadDocument(sczXml, &pixd);
251 LocExitOnFailure(hr, "Failed to load theme resource as XML document.");
252
253 hr = ParseWxl(pixd, ppWixLoc);
254 LocExitOnFailure(hr, "Failed to parse WXL.");
255
256LExit:
257 ReleaseObject(pixd);
258 ReleaseStr(sczXml);
259
260 return hr;
261}
262
263extern "C" void DAPI LocFree(
264 __in_opt WIX_LOCALIZATION* pWixLoc
265 )
266{
267 if (pWixLoc)
268 {
269 for (DWORD idx = 0; idx < pWixLoc->cLocStrings; ++idx)
270 {
271 ReleaseStr(pWixLoc->rgLocStrings[idx].wzId);
272 ReleaseStr(pWixLoc->rgLocStrings[idx].wzText);
273 }
274
275 for (DWORD idx = 0; idx < pWixLoc->cLocControls; ++idx)
276 {
277 ReleaseStr(pWixLoc->rgLocControls[idx].wzControl);
278 ReleaseStr(pWixLoc->rgLocControls[idx].wzText);
279 }
280
281 ReleaseMem(pWixLoc->rgLocStrings);
282 ReleaseMem(pWixLoc->rgLocControls);
283 ReleaseMem(pWixLoc);
284 }
285}
286
287extern "C" HRESULT DAPI LocLocalizeString(
288 __in const WIX_LOCALIZATION* pWixLoc,
289 __inout LPWSTR* ppsczInput
290 )
291{
292 Assert(ppsczInput && pWixLoc);
293 HRESULT hr = S_OK;
294
295 for (DWORD i = 0; i < pWixLoc->cLocStrings; ++i)
296 {
297 hr = StrReplaceStringAll(ppsczInput, pWixLoc->rgLocStrings[i].wzId, pWixLoc->rgLocStrings[i].wzText);
298 LocExitOnFailure(hr, "Localizing string failed.");
299 }
300
301LExit:
302 return hr;
303}
304
305extern "C" HRESULT DAPI LocGetControl(
306 __in const WIX_LOCALIZATION* pWixLoc,
307 __in_z LPCWSTR wzId,
308 __out LOC_CONTROL** ppLocControl
309 )
310{
311 HRESULT hr = S_OK;
312 LOC_CONTROL* pLocControl = NULL;
313
314 for (DWORD i = 0; i < pWixLoc->cLocControls; ++i)
315 {
316 pLocControl = &pWixLoc->rgLocControls[i];
317
318 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pLocControl->wzControl, -1, wzId, -1))
319 {
320 *ppLocControl = pLocControl;
321 ExitFunction1(hr = S_OK);
322 }
323 }
324
325 hr = E_NOTFOUND;
326
327LExit:
328 return hr;
329}
330
331extern "C" HRESULT DAPI LocGetString(
332 __in const WIX_LOCALIZATION* pWixLoc,
333 __in_z LPCWSTR wzId,
334 __out LOC_STRING** ppLocString
335 )
336{
337 HRESULT hr = E_NOTFOUND;
338 LOC_STRING* pLocString = NULL;
339
340 for (DWORD i = 0; i < pWixLoc->cLocStrings; ++i)
341 {
342 pLocString = pWixLoc->rgLocStrings + i;
343
344 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pLocString->wzId, -1, wzId, -1))
345 {
346 *ppLocString = pLocString;
347 hr = S_OK;
348 break;
349 }
350 }
351
352 return hr;
353}
354
355extern "C" HRESULT DAPI LocAddString(
356 __in WIX_LOCALIZATION* pWixLoc,
357 __in_z LPCWSTR wzId,
358 __in_z LPCWSTR wzLocString,
359 __in BOOL bOverridable
360 )
361{
362 HRESULT hr = S_OK;
363
364 ++pWixLoc->cLocStrings;
365 pWixLoc->rgLocStrings = static_cast<LOC_STRING*>(MemReAlloc(pWixLoc->rgLocStrings, sizeof(LOC_STRING) * pWixLoc->cLocStrings, TRUE));
366 LocExitOnNull(pWixLoc->rgLocStrings, hr, E_OUTOFMEMORY, "Failed to reallocate memory for localization strings.");
367
368 LOC_STRING* pLocString = pWixLoc->rgLocStrings + (pWixLoc->cLocStrings - 1);
369
370 hr = StrAllocFormatted(&pLocString->wzId, L"#(loc.%s)", wzId);
371 LocExitOnFailure(hr, "Failed to set localization string Id.");
372
373 hr = StrAllocString(&pLocString->wzText, wzLocString, 0);
374 LocExitOnFailure(hr, "Failed to set localization string Text.");
375
376 pLocString->bOverridable = bOverridable;
377
378LExit:
379 return hr;
380}
381
382// helper functions
383
384static HRESULT ParseWxl(
385 __in IXMLDOMDocument* pixd,
386 __out WIX_LOCALIZATION** ppWixLoc
387 )
388{
389 HRESULT hr = S_OK;
390 IXMLDOMElement *pWxlElement = NULL;
391 WIX_LOCALIZATION* pWixLoc = NULL;
392
393 pWixLoc = static_cast<WIX_LOCALIZATION*>(MemAlloc(sizeof(WIX_LOCALIZATION), TRUE));
394 LocExitOnNull(pWixLoc, hr, E_OUTOFMEMORY, "Failed to allocate memory for Wxl file.");
395
396 // read the WixLocalization tag
397 hr = pixd->get_documentElement(&pWxlElement);
398 LocExitOnFailure(hr, "Failed to get localization element.");
399
400 // get the Language attribute if present
401 pWixLoc->dwLangId = WIX_LOCALIZATION_LANGUAGE_NOT_SET;
402 hr = XmlGetAttributeNumber(pWxlElement, L"Language", &pWixLoc->dwLangId);
403 if (S_FALSE == hr)
404 {
405 hr = S_OK;
406 }
407 LocExitOnFailure(hr, "Failed to get Language value.");
408
409 // store the strings and controls in a node list
410 hr = ParseWxlStrings(pWxlElement, pWixLoc);
411 LocExitOnFailure(hr, "Parsing localization strings failed.");
412
413 hr = ParseWxlControls(pWxlElement, pWixLoc);
414 LocExitOnFailure(hr, "Parsing localization controls failed.");
415
416 *ppWixLoc = pWixLoc;
417 pWixLoc = NULL;
418
419LExit:
420 ReleaseObject(pWxlElement);
421 ReleaseMem(pWixLoc);
422
423 return hr;
424}
425
426
427static HRESULT ParseWxlStrings(
428 __in IXMLDOMElement* pElement,
429 __in WIX_LOCALIZATION* pWixLoc
430 )
431{
432 HRESULT hr = S_OK;
433 IXMLDOMNode* pixn = NULL;
434 IXMLDOMNodeList* pixnl = NULL;
435 DWORD dwIdx = 0;
436
437 hr = XmlSelectNodes(pElement, L"String", &pixnl);
438 LocExitOnLastError(hr, "Failed to get String child nodes of Wxl File.");
439
440 hr = pixnl->get_length(reinterpret_cast<long*>(&pWixLoc->cLocStrings));
441 LocExitOnLastError(hr, "Failed to get number of String child nodes in Wxl File.");
442
443 if (0 < pWixLoc->cLocStrings)
444 {
445 pWixLoc->rgLocStrings = static_cast<LOC_STRING*>(MemAlloc(sizeof(LOC_STRING) * pWixLoc->cLocStrings, TRUE));
446 LocExitOnNull(pWixLoc->rgLocStrings, hr, E_OUTOFMEMORY, "Failed to allocate memory for localization strings.");
447
448 while (S_OK == (hr = XmlNextElement(pixnl, &pixn, NULL)))
449 {
450 hr = ParseWxlString(pixn, dwIdx, pWixLoc);
451 LocExitOnFailure(hr, "Failed to parse localization string.");
452
453 ++dwIdx;
454 ReleaseNullObject(pixn);
455 }
456
457 hr = S_OK;
458 LocExitOnFailure(hr, "Failed to enumerate all localization strings.");
459 }
460
461LExit:
462 if (FAILED(hr) && pWixLoc->rgLocStrings)
463 {
464 for (DWORD idx = 0; idx < pWixLoc->cLocStrings; ++idx)
465 {
466 ReleaseStr(pWixLoc->rgLocStrings[idx].wzId);
467 ReleaseStr(pWixLoc->rgLocStrings[idx].wzText);
468 }
469
470 ReleaseMem(pWixLoc->rgLocStrings);
471 }
472
473 ReleaseObject(pixn);
474 ReleaseObject(pixnl);
475
476 return hr;
477}
478
479static HRESULT ParseWxlControls(
480 __in IXMLDOMElement* pElement,
481 __in WIX_LOCALIZATION* pWixLoc
482 )
483{
484 HRESULT hr = S_OK;
485 IXMLDOMNode* pixn = NULL;
486 IXMLDOMNodeList* pixnl = NULL;
487 DWORD dwIdx = 0;
488
489 hr = XmlSelectNodes(pElement, L"UI|Control", &pixnl);
490 LocExitOnLastError(hr, "Failed to get UI child nodes of Wxl File.");
491
492 hr = pixnl->get_length(reinterpret_cast<long*>(&pWixLoc->cLocControls));
493 LocExitOnLastError(hr, "Failed to get number of UI child nodes in Wxl File.");
494
495 if (0 < pWixLoc->cLocControls)
496 {
497 pWixLoc->rgLocControls = static_cast<LOC_CONTROL*>(MemAlloc(sizeof(LOC_CONTROL) * pWixLoc->cLocControls, TRUE));
498 LocExitOnNull(pWixLoc->rgLocControls, hr, E_OUTOFMEMORY, "Failed to allocate memory for localized controls.");
499
500 while (S_OK == (hr = XmlNextElement(pixnl, &pixn, NULL)))
501 {
502 hr = ParseWxlControl(pixn, dwIdx, pWixLoc);
503 LocExitOnFailure(hr, "Failed to parse localized control.");
504
505 ++dwIdx;
506 ReleaseNullObject(pixn);
507 }
508
509 hr = S_OK;
510 LocExitOnFailure(hr, "Failed to enumerate all localized controls.");
511 }
512
513LExit:
514 if (FAILED(hr) && pWixLoc->rgLocControls)
515 {
516 for (DWORD idx = 0; idx < pWixLoc->cLocControls; ++idx)
517 {
518 ReleaseStr(pWixLoc->rgLocControls[idx].wzControl);
519 ReleaseStr(pWixLoc->rgLocControls[idx].wzText);
520 }
521
522 ReleaseMem(pWixLoc->rgLocControls);
523 }
524
525 ReleaseObject(pixn);
526 ReleaseObject(pixnl);
527
528 return hr;
529}
530
531static HRESULT ParseWxlString(
532 __in IXMLDOMNode* pixn,
533 __in DWORD dwIdx,
534 __in WIX_LOCALIZATION* pWixLoc
535 )
536{
537 HRESULT hr = S_OK;
538 LOC_STRING* pLocString = NULL;
539 BSTR bstrText = NULL;
540
541 pLocString = pWixLoc->rgLocStrings + dwIdx;
542
543 // Id
544 hr = XmlGetAttribute(pixn, L"Id", &bstrText);
545 LocExitOnFailure(hr, "Failed to get Xml attribute Id in Wxl file.");
546
547 hr = StrAllocFormatted(&pLocString->wzId, L"#(loc.%s)", bstrText);
548 LocExitOnFailure(hr, "Failed to duplicate Xml attribute Id in Wxl file.");
549
550 ReleaseNullBSTR(bstrText);
551
552 // Overrideable
553 hr = XmlGetAttribute(pixn, L"Overridable", &bstrText);
554 LocExitOnFailure(hr, "Failed to get Xml attribute Overridable.");
555
556 if (S_OK == hr)
557 {
558 pLocString->bOverridable = CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrText, -1, L"yes", -1);
559 }
560
561 ReleaseNullBSTR(bstrText);
562
563 // Text
564 hr = XmlGetText(pixn, &bstrText);
565 LocExitOnFailure(hr, "Failed to get Xml text in Wxl file.");
566
567 hr = StrAllocString(&pLocString->wzText, bstrText, 0);
568 LocExitOnFailure(hr, "Failed to duplicate Xml text in Wxl file.");
569
570LExit:
571 ReleaseBSTR(bstrText);
572
573 return hr;
574}
575
576static HRESULT ParseWxlControl(
577 __in IXMLDOMNode* pixn,
578 __in DWORD dwIdx,
579 __in WIX_LOCALIZATION* pWixLoc
580 )
581{
582 HRESULT hr = S_OK;
583 LOC_CONTROL* pLocControl = NULL;
584 BSTR bstrText = NULL;
585
586 pLocControl = pWixLoc->rgLocControls + dwIdx;
587
588 // Id
589 hr = XmlGetAttribute(pixn, L"Control", &bstrText);
590 LocExitOnFailure(hr, "Failed to get Xml attribute Control in Wxl file.");
591
592 hr = StrAllocString(&pLocControl->wzControl, bstrText, 0);
593 LocExitOnFailure(hr, "Failed to duplicate Xml attribute Control in Wxl file.");
594
595 ReleaseNullBSTR(bstrText);
596
597 // X
598 pLocControl->nX = LOC_CONTROL_NOT_SET;
599 hr = XmlGetAttributeNumber(pixn, L"X", reinterpret_cast<DWORD*>(&pLocControl->nX));
600 LocExitOnFailure(hr, "Failed to get control X attribute.");
601
602 // Y
603 pLocControl->nY = LOC_CONTROL_NOT_SET;
604 hr = XmlGetAttributeNumber(pixn, L"Y", reinterpret_cast<DWORD*>(&pLocControl->nY));
605 LocExitOnFailure(hr, "Failed to get control Y attribute.");
606
607 // Width
608 pLocControl->nWidth = LOC_CONTROL_NOT_SET;
609 hr = XmlGetAttributeNumber(pixn, L"Width", reinterpret_cast<DWORD*>(&pLocControl->nWidth));
610 LocExitOnFailure(hr, "Failed to get control width attribute.");
611
612 // Height
613 pLocControl->nHeight = LOC_CONTROL_NOT_SET;
614 hr = XmlGetAttributeNumber(pixn, L"Height", reinterpret_cast<DWORD*>(&pLocControl->nHeight));
615 LocExitOnFailure(hr, "Failed to get control height attribute.");
616
617 // Text
618 hr = XmlGetText(pixn, &bstrText);
619 LocExitOnFailure(hr, "Failed to get control text in Wxl file.");
620
621 hr = StrAllocString(&pLocControl->wzText, bstrText, 0);
622 LocExitOnFailure(hr, "Failed to duplicate control text in Wxl file.");
623
624LExit:
625 ReleaseBSTR(bstrText);
626
627 return hr;
628}
diff --git a/src/libs/dutil/WixToolset.DUtil/logutil.cpp b/src/libs/dutil/WixToolset.DUtil/logutil.cpp
new file mode 100644
index 00000000..ac68036a
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/logutil.cpp
@@ -0,0 +1,961 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define LoguExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_LOGUTIL, x, s, __VA_ARGS__)
8#define LoguExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_LOGUTIL, x, s, __VA_ARGS__)
9#define LoguExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_LOGUTIL, x, s, __VA_ARGS__)
10#define LoguExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_LOGUTIL, x, s, __VA_ARGS__)
11#define LoguExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_LOGUTIL, x, s, __VA_ARGS__)
12#define LoguExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_LOGUTIL, x, s, __VA_ARGS__)
13#define LoguExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_LOGUTIL, p, x, e, s, __VA_ARGS__)
14#define LoguExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_LOGUTIL, p, x, s, __VA_ARGS__)
15#define LoguExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_LOGUTIL, p, x, e, s, __VA_ARGS__)
16#define LoguExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_LOGUTIL, p, x, s, __VA_ARGS__)
17#define LoguExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_LOGUTIL, e, x, s, __VA_ARGS__)
18#define LoguExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_LOGUTIL, g, x, s, __VA_ARGS__)
19
20// globals
21static HMODULE LogUtil_hModule = NULL;
22static BOOL LogUtil_fDisabled = FALSE;
23static HANDLE LogUtil_hLog = INVALID_HANDLE_VALUE;
24static LPWSTR LogUtil_sczLogPath = NULL;
25static LPSTR LogUtil_sczPreInitBuffer = NULL;
26static REPORT_LEVEL LogUtil_rlCurrent = REPORT_STANDARD;
27static CRITICAL_SECTION LogUtil_csLog = { };
28static BOOL LogUtil_fInitializedCriticalSection = FALSE;
29
30// Customization of certain parts of the string, within a line
31static LPWSTR LogUtil_sczSpecialBeginLine = NULL;
32static LPWSTR LogUtil_sczSpecialEndLine = NULL;
33static LPWSTR LogUtil_sczSpecialAfterTimeStamp = NULL;
34
35static LPCSTR LOGUTIL_UNKNOWN = "unknown";
36static LPCSTR LOGUTIL_WARNING = "warning";
37static LPCSTR LOGUTIL_STANDARD = "standard";
38static LPCSTR LOGUTIL_VERBOSE = "verbose";
39static LPCSTR LOGUTIL_DEBUG = "debug";
40static LPCSTR LOGUTIL_NONE = "none";
41
42// prototypes
43static HRESULT LogIdWork(
44 __in REPORT_LEVEL rl,
45 __in_opt HMODULE hModule,
46 __in DWORD dwLogId,
47 __in va_list args,
48 __in BOOL fLOGUTIL_NEWLINE
49 );
50static HRESULT LogStringWorkArgs(
51 __in REPORT_LEVEL rl,
52 __in_z __format_string LPCSTR szFormat,
53 __in va_list args,
54 __in BOOL fLOGUTIL_NEWLINE
55 );
56static HRESULT LogStringWork(
57 __in REPORT_LEVEL rl,
58 __in DWORD dwLogId,
59 __in_z LPCWSTR sczString,
60 __in BOOL fLOGUTIL_NEWLINE
61 );
62
63// Hook to allow redirecting LogStringWorkRaw function calls
64static PFN_LOGSTRINGWORKRAW s_vpfLogStringWorkRaw = NULL;
65static LPVOID s_vpvLogStringWorkRawContext = NULL;
66
67
68/********************************************************************
69 IsLogInitialized - Checks if log is currently initialized.
70********************************************************************/
71extern "C" BOOL DAPI IsLogInitialized()
72{
73 return LogUtil_fInitializedCriticalSection;
74}
75
76/********************************************************************
77 IsLogOpen - Checks if log is currently initialized and open.
78********************************************************************/
79extern "C" BOOL DAPI IsLogOpen()
80{
81 return (INVALID_HANDLE_VALUE != LogUtil_hLog && NULL != LogUtil_sczLogPath);
82}
83
84
85/********************************************************************
86 LogInitialize - initializes the logutil API
87
88********************************************************************/
89extern "C" void DAPI LogInitialize(
90 __in HMODULE hModule
91 )
92{
93 AssertSz(INVALID_HANDLE_VALUE == LogUtil_hLog && !LogUtil_sczLogPath, "LogInitialize() or LogOpen() - already called.");
94
95 LogUtil_hModule = hModule;
96 LogUtil_fDisabled = FALSE;
97
98 ::InitializeCriticalSection(&LogUtil_csLog);
99 LogUtil_fInitializedCriticalSection = TRUE;
100}
101
102
103/********************************************************************
104 LogOpen - creates an application log file
105
106 NOTE: if wzExt is null then wzLog is path to desired log else wzLog and wzExt are used to generate log name
107********************************************************************/
108extern "C" HRESULT DAPI LogOpen(
109 __in_z_opt LPCWSTR wzDirectory,
110 __in_z LPCWSTR wzLog,
111 __in_z_opt LPCWSTR wzPostfix,
112 __in_z_opt LPCWSTR wzExt,
113 __in BOOL fAppend,
114 __in BOOL fHeader,
115 __out_z_opt LPWSTR* psczLogPath
116 )
117{
118 HRESULT hr = S_OK;
119 BOOL fEnteredCriticalSection = FALSE;
120 LPWSTR sczLogDirectory = NULL;
121
122 ::EnterCriticalSection(&LogUtil_csLog);
123 fEnteredCriticalSection = TRUE;
124
125 if (wzExt && *wzExt)
126 {
127 hr = PathCreateTimeBasedTempFile(wzDirectory, wzLog, wzPostfix, wzExt, &LogUtil_sczLogPath, &LogUtil_hLog);
128 LoguExitOnFailure(hr, "Failed to create log based on current system time.");
129 }
130 else
131 {
132 hr = PathConcat(wzDirectory, wzLog, &LogUtil_sczLogPath);
133 LoguExitOnFailure(hr, "Failed to combine the log path.");
134
135 hr = PathGetDirectory(LogUtil_sczLogPath, &sczLogDirectory);
136 LoguExitOnFailure(hr, "Failed to get log directory.");
137
138 hr = DirEnsureExists(sczLogDirectory, NULL);
139 LoguExitOnFailure(hr, "Failed to ensure log file directory exists: %ls", sczLogDirectory);
140
141 LogUtil_hLog = ::CreateFileW(LogUtil_sczLogPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, (fAppend) ? OPEN_ALWAYS : CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
142 if (INVALID_HANDLE_VALUE == LogUtil_hLog)
143 {
144 LoguExitOnLastError(hr, "failed to create log file: %ls", LogUtil_sczLogPath);
145 }
146
147 if (fAppend)
148 {
149 ::SetFilePointer(LogUtil_hLog, 0, 0, FILE_END);
150 }
151 }
152
153 LogUtil_fDisabled = FALSE;
154
155 if (fHeader)
156 {
157 LogHeader();
158 }
159
160 if (NULL != LogUtil_sczPreInitBuffer)
161 {
162 // Log anything that was logged before LogOpen() was called.
163 LogStringWorkRaw(LogUtil_sczPreInitBuffer);
164 ReleaseNullStr(LogUtil_sczPreInitBuffer);
165 }
166
167 if (psczLogPath)
168 {
169 hr = StrAllocString(psczLogPath, LogUtil_sczLogPath, 0);
170 LoguExitOnFailure(hr, "Failed to copy log path.");
171 }
172
173LExit:
174 if (fEnteredCriticalSection)
175 {
176 ::LeaveCriticalSection(&LogUtil_csLog);
177 }
178
179 ReleaseStr(sczLogDirectory);
180
181 return hr;
182}
183
184
185/********************************************************************
186 LogDisable - closes any open files and disables in memory logging.
187
188********************************************************************/
189void DAPI LogDisable()
190{
191 ::EnterCriticalSection(&LogUtil_csLog);
192
193 LogUtil_fDisabled = TRUE;
194
195 ReleaseFileHandle(LogUtil_hLog);
196 ReleaseNullStr(LogUtil_sczLogPath);
197 ReleaseNullStr(LogUtil_sczPreInitBuffer);
198
199 ::LeaveCriticalSection(&LogUtil_csLog);
200}
201
202
203/********************************************************************
204 LogRedirect - Redirects all logging strings to the specified
205 function - or set NULL to disable the hook
206********************************************************************/
207void DAPI LogRedirect(
208 __in_opt PFN_LOGSTRINGWORKRAW vpfLogStringWorkRaw,
209 __in_opt LPVOID pvContext
210 )
211{
212 s_vpfLogStringWorkRaw = vpfLogStringWorkRaw;
213 s_vpvLogStringWorkRawContext = pvContext;
214}
215
216
217/********************************************************************
218 LogRename - Renames a logfile, moving its contents to a new path,
219 and re-opening the file for appending at the new
220 location
221********************************************************************/
222HRESULT DAPI LogRename(
223 __in_z LPCWSTR wzNewPath
224 )
225{
226 HRESULT hr = S_OK;
227 BOOL fEnteredCriticalSection = FALSE;
228
229 ::EnterCriticalSection(&LogUtil_csLog);
230 fEnteredCriticalSection = TRUE;
231
232 ReleaseFileHandle(LogUtil_hLog);
233
234 hr = FileEnsureMove(LogUtil_sczLogPath, wzNewPath, TRUE, TRUE);
235 LoguExitOnFailure(hr, "Failed to move logfile to new location: %ls", wzNewPath);
236
237 hr = StrAllocString(&LogUtil_sczLogPath, wzNewPath, 0);
238 LoguExitOnFailure(hr, "Failed to store new logfile path: %ls", wzNewPath);
239
240 LogUtil_hLog = ::CreateFileW(LogUtil_sczLogPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
241 if (INVALID_HANDLE_VALUE == LogUtil_hLog)
242 {
243 LoguExitOnLastError(hr, "failed to create log file: %ls", LogUtil_sczLogPath);
244 }
245
246 // Enable "append" mode by moving file pointer to the end
247 ::SetFilePointer(LogUtil_hLog, 0, 0, FILE_END);
248
249LExit:
250 if (fEnteredCriticalSection)
251 {
252 ::LeaveCriticalSection(&LogUtil_csLog);
253 }
254
255 return hr;
256}
257
258
259extern "C" void DAPI LogClose(
260 __in BOOL fFooter
261 )
262{
263 if (INVALID_HANDLE_VALUE != LogUtil_hLog && fFooter)
264 {
265 LogFooter();
266 }
267
268 ReleaseFileHandle(LogUtil_hLog);
269 ReleaseNullStr(LogUtil_sczLogPath);
270 ReleaseNullStr(LogUtil_sczPreInitBuffer);
271}
272
273
274extern "C" void DAPI LogUninitialize(
275 __in BOOL fFooter
276 )
277{
278 LogClose(fFooter);
279
280 if (LogUtil_fInitializedCriticalSection)
281 {
282 ::DeleteCriticalSection(&LogUtil_csLog);
283 LogUtil_fInitializedCriticalSection = FALSE;
284 }
285
286 LogUtil_hModule = NULL;
287 LogUtil_fDisabled = FALSE;
288
289 ReleaseNullStr(LogUtil_sczSpecialBeginLine);
290 ReleaseNullStr(LogUtil_sczSpecialAfterTimeStamp);
291 ReleaseNullStr(LogUtil_sczSpecialEndLine);
292}
293
294
295/********************************************************************
296 LogIsOpen - returns whether log file is open or note
297
298********************************************************************/
299extern "C" BOOL DAPI LogIsOpen()
300{
301 return INVALID_HANDLE_VALUE != LogUtil_hLog;
302}
303
304
305/********************************************************************
306 LogSetSpecialParams - sets a special beginline string, endline
307 string, post-timestamp string, etc.
308********************************************************************/
309HRESULT DAPI LogSetSpecialParams(
310 __in_z_opt LPCWSTR wzSpecialBeginLine,
311 __in_z_opt LPCWSTR wzSpecialAfterTimeStamp,
312 __in_z_opt LPCWSTR wzSpecialEndLine
313 )
314{
315 HRESULT hr = S_OK;
316
317 // Handle special string to be prepended before every full line
318 if (NULL == wzSpecialBeginLine)
319 {
320 ReleaseNullStr(LogUtil_sczSpecialBeginLine);
321 }
322 else
323 {
324 hr = StrAllocConcat(&LogUtil_sczSpecialBeginLine, wzSpecialBeginLine, 0);
325 LoguExitOnFailure(hr, "Failed to allocate copy of special beginline string");
326 }
327
328 // Handle special string to be appended to every time stamp
329 if (NULL == wzSpecialAfterTimeStamp)
330 {
331 ReleaseNullStr(LogUtil_sczSpecialAfterTimeStamp);
332 }
333 else
334 {
335 hr = StrAllocConcat(&LogUtil_sczSpecialAfterTimeStamp, wzSpecialAfterTimeStamp, 0);
336 LoguExitOnFailure(hr, "Failed to allocate copy of special post-timestamp string");
337 }
338
339 // Handle special string to be appended before every full line
340 if (NULL == wzSpecialEndLine)
341 {
342 ReleaseNullStr(LogUtil_sczSpecialEndLine);
343 }
344 else
345 {
346 hr = StrAllocConcat(&LogUtil_sczSpecialEndLine, wzSpecialEndLine, 0);
347 LoguExitOnFailure(hr, "Failed to allocate copy of special endline string");
348 }
349
350LExit:
351 return hr;
352}
353
354/********************************************************************
355 LogSetLevel - sets the logging level
356
357 NOTE: returns previous logging level
358********************************************************************/
359extern "C" REPORT_LEVEL DAPI LogSetLevel(
360 __in REPORT_LEVEL rl,
361 __in BOOL fLogChange
362 )
363{
364 AssertSz(REPORT_ERROR != rl, "REPORT_ERROR is not a valid logging level to set");
365
366 REPORT_LEVEL rlPrev = LogUtil_rlCurrent;
367
368 if (LogUtil_rlCurrent != rl)
369 {
370 LogUtil_rlCurrent = rl;
371
372 if (fLogChange)
373 {
374 LPCSTR szLevel = LOGUTIL_UNKNOWN;
375 switch (LogUtil_rlCurrent)
376 {
377 case REPORT_WARNING:
378 szLevel = LOGUTIL_WARNING;
379 break;
380 case REPORT_STANDARD:
381 szLevel = LOGUTIL_STANDARD;
382 break;
383 case REPORT_VERBOSE:
384 szLevel = LOGUTIL_VERBOSE;
385 break;
386 case REPORT_DEBUG:
387 szLevel = LOGUTIL_DEBUG;
388 break;
389 case REPORT_NONE:
390 szLevel = LOGUTIL_NONE;
391 break;
392 }
393
394 LogStringLine(REPORT_STANDARD, "--- logging level: %hs ---", szLevel);
395 }
396 }
397
398 return rlPrev;
399}
400
401
402/********************************************************************
403 LogGetLevel - gets the current logging level
404
405********************************************************************/
406extern "C" REPORT_LEVEL DAPI LogGetLevel()
407{
408 return LogUtil_rlCurrent;
409}
410
411
412/********************************************************************
413 LogGetPath - gets the current log path
414
415********************************************************************/
416extern "C" HRESULT DAPI LogGetPath(
417 __out_ecount_z(cchLogPath) LPWSTR pwzLogPath,
418 __in DWORD cchLogPath
419 )
420{
421 Assert(pwzLogPath);
422
423 HRESULT hr = S_OK;
424
425 if (NULL == LogUtil_sczLogPath) // they can't have a path if there isn't one!
426 {
427 ExitFunction1(hr = E_UNEXPECTED);
428 }
429
430 hr = ::StringCchCopyW(pwzLogPath, cchLogPath, LogUtil_sczLogPath);
431
432LExit:
433 return hr;
434}
435
436
437/********************************************************************
438 LogGetHandle - gets the current log file handle
439
440********************************************************************/
441extern "C" HANDLE DAPI LogGetHandle()
442{
443 return LogUtil_hLog;
444}
445
446
447/********************************************************************
448 LogString - write a string to the log
449
450 NOTE: use printf formatting ("%ls", "%d", etc.)
451********************************************************************/
452extern "C" HRESULT DAPIV LogString(
453 __in REPORT_LEVEL rl,
454 __in_z __format_string LPCSTR szFormat,
455 ...
456 )
457{
458 HRESULT hr = S_OK;
459 va_list args;
460
461 va_start(args, szFormat);
462 hr = LogStringArgs(rl, szFormat, args);
463 va_end(args);
464
465 return hr;
466}
467
468extern "C" HRESULT DAPI LogStringArgs(
469 __in REPORT_LEVEL rl,
470 __in_z __format_string LPCSTR szFormat,
471 __in va_list args
472 )
473{
474 AssertSz(REPORT_NONE != rl, "REPORT_NONE is not a valid logging level");
475 HRESULT hr = S_OK;
476
477 if (REPORT_ERROR != rl && LogUtil_rlCurrent < rl)
478 {
479 ExitFunction1(hr = S_FALSE);
480 }
481
482 hr = LogStringWorkArgs(rl, szFormat, args, FALSE);
483
484LExit:
485 return hr;
486}
487
488/********************************************************************
489 LogStringLine - write a string plus LOGUTIL_NEWLINE to the log
490
491 NOTE: use printf formatting ("%ls", "%d", etc.)
492********************************************************************/
493extern "C" HRESULT DAPIV LogStringLine(
494 __in REPORT_LEVEL rl,
495 __in_z __format_string LPCSTR szFormat,
496 ...
497 )
498{
499 HRESULT hr = S_OK;
500 va_list args;
501
502 va_start(args, szFormat);
503 hr = LogStringLineArgs(rl, szFormat, args);
504 va_end(args);
505
506 return hr;
507}
508
509extern "C" HRESULT DAPI LogStringLineArgs(
510 __in REPORT_LEVEL rl,
511 __in_z __format_string LPCSTR szFormat,
512 __in va_list args
513 )
514{
515 AssertSz(REPORT_NONE != rl, "REPORT_NONE is not a valid logging level");
516 HRESULT hr = S_OK;
517
518 if (REPORT_ERROR != rl && LogUtil_rlCurrent < rl)
519 {
520 ExitFunction1(hr = S_FALSE);
521 }
522
523 hr = LogStringWorkArgs(rl, szFormat, args, TRUE);
524
525LExit:
526 return hr;
527}
528
529/********************************************************************
530 LogIdModuleArgs - write a string embedded in a MESSAGETABLE to the log
531
532 NOTE: uses format string from MESSAGETABLE resource
533********************************************************************/
534
535extern "C" HRESULT DAPI LogIdModuleArgs(
536 __in REPORT_LEVEL rl,
537 __in DWORD dwLogId,
538 __in_opt HMODULE hModule,
539 __in va_list args
540 )
541{
542 AssertSz(REPORT_NONE != rl, "REPORT_NONE is not a valid logging level");
543 HRESULT hr = S_OK;
544
545 if (REPORT_ERROR != rl && LogUtil_rlCurrent < rl)
546 {
547 ExitFunction1(hr = S_FALSE);
548 }
549
550 hr = LogIdWork(rl, (hModule) ? hModule : LogUtil_hModule, dwLogId, args, TRUE);
551
552LExit:
553 return hr;
554}
555
556extern "C" HRESULT DAPI LogIdModule(
557 __in REPORT_LEVEL rl,
558 __in DWORD dwLogId,
559 __in_opt HMODULE hModule,
560 ...
561 )
562{
563 AssertSz(REPORT_NONE != rl, "REPORT_NONE is not a valid logging level");
564 HRESULT hr = S_OK;
565 va_list args;
566
567 if (REPORT_ERROR != rl && LogUtil_rlCurrent < rl)
568 {
569 ExitFunction1(hr = S_FALSE);
570 }
571
572 va_start(args, hModule);
573 hr = LogIdWork(rl, (hModule) ? hModule : LogUtil_hModule, dwLogId, args, TRUE);
574 va_end(args);
575
576LExit:
577 return hr;
578}
579
580
581
582
583/********************************************************************
584 LogError - write an error to the log
585
586 NOTE: use printf formatting ("%ls", "%d", etc.)
587********************************************************************/
588extern "C" HRESULT DAPIV LogErrorString(
589 __in HRESULT hrError,
590 __in_z __format_string LPCSTR szFormat,
591 ...
592 )
593{
594 HRESULT hr = S_OK;
595
596 va_list args;
597 va_start(args, szFormat);
598 hr = LogErrorStringArgs(hrError, szFormat, args);
599 va_end(args);
600
601 return hr;
602}
603
604extern "C" HRESULT DAPI LogErrorStringArgs(
605 __in HRESULT hrError,
606 __in_z __format_string LPCSTR szFormat,
607 __in va_list args
608 )
609{
610 HRESULT hr = S_OK;
611 LPWSTR sczFormat = NULL;
612 LPWSTR sczMessage = NULL;
613
614 hr = StrAllocStringAnsi(&sczFormat, szFormat, 0, CP_ACP);
615 LoguExitOnFailure(hr, "Failed to convert format string to wide character string");
616
617 // format the string as a unicode string - this is necessary to be able to include
618 // international characters in our output string. This does have the counterintuitive effect
619 // that the caller's "%s" is interpreted differently
620 // (so callers should use %hs for LPSTR and %ls for LPWSTR)
621 hr = StrAllocFormattedArgs(&sczMessage, sczFormat, args);
622 LoguExitOnFailure(hr, "Failed to format error message: \"%ls\"", sczFormat);
623
624 hr = LogStringLine(REPORT_ERROR, "Error 0x%x: %ls", hrError, sczMessage);
625
626LExit:
627 ReleaseStr(sczFormat);
628 ReleaseStr(sczMessage);
629
630 return hr;
631}
632
633
634/********************************************************************
635 LogErrorIdModule - write an error string embedded in a MESSAGETABLE to the log
636
637 NOTE: uses format string from MESSAGETABLE resource
638 can log no more than three strings in the error message
639********************************************************************/
640extern "C" HRESULT DAPI LogErrorIdModule(
641 __in HRESULT hrError,
642 __in DWORD dwLogId,
643 __in_opt HMODULE hModule,
644 __in_z_opt LPCWSTR wzString1 = NULL,
645 __in_z_opt LPCWSTR wzString2 = NULL,
646 __in_z_opt LPCWSTR wzString3 = NULL
647 )
648{
649 HRESULT hr = S_OK;
650 WCHAR wzError[11];
651 WORD cStrings = 1; // guaranteed wzError is in the list
652
653 hr = ::StringCchPrintfW(wzError, countof(wzError), L"0x%08x", hrError);
654 LoguExitOnFailure(hr, "failed to format error code: \"0%08x\"", hrError);
655
656 cStrings += wzString1 ? 1 : 0;
657 cStrings += wzString2 ? 1 : 0;
658 cStrings += wzString3 ? 1 : 0;
659
660 hr = LogIdModule(REPORT_ERROR, dwLogId, hModule, wzError, wzString1, wzString2, wzString3);
661 LoguExitOnFailure(hr, "Failed to log id module.");
662
663LExit:
664 return hr;
665}
666
667/********************************************************************
668 LogHeader - write a standard header to the log
669
670********************************************************************/
671extern "C" HRESULT DAPI LogHeader()
672{
673 HRESULT hr = S_OK;
674 WCHAR wzComputerName[MAX_PATH];
675 DWORD cchComputerName = countof(wzComputerName);
676 WCHAR wzPath[MAX_PATH];
677 DWORD dwMajorVersion = 0;
678 DWORD dwMinorVersion = 0;
679 LPCSTR szLevel = LOGUTIL_UNKNOWN;
680 LPWSTR sczCurrentDateTime = NULL;
681
682 //
683 // get the interesting data
684 //
685 if (!::GetModuleFileNameW(NULL, wzPath, countof(wzPath)))
686 {
687 memset(wzPath, 0, sizeof(wzPath));
688 }
689
690 hr = FileVersion(wzPath, &dwMajorVersion, &dwMinorVersion);
691 if (FAILED(hr))
692 {
693 dwMajorVersion = 0;
694 dwMinorVersion = 0;
695 }
696
697 if (!::GetComputerNameW(wzComputerName, &cchComputerName))
698 {
699 ::SecureZeroMemory(wzComputerName, sizeof(wzComputerName));
700 }
701
702 TimeCurrentDateTime(&sczCurrentDateTime, FALSE);
703
704 //
705 // write data to the log
706 //
707 LogStringLine(REPORT_STANDARD, "=== Logging started: %ls ===", sczCurrentDateTime);
708 LogStringLine(REPORT_STANDARD, "Executable: %ls v%d.%d.%d.%d", wzPath, dwMajorVersion >> 16, dwMajorVersion & 0xFFFF, dwMinorVersion >> 16, dwMinorVersion & 0xFFFF);
709 LogStringLine(REPORT_STANDARD, "Computer : %ls", wzComputerName);
710 switch (LogUtil_rlCurrent)
711 {
712 case REPORT_WARNING:
713 szLevel = LOGUTIL_WARNING;
714 break;
715 case REPORT_STANDARD:
716 szLevel = LOGUTIL_STANDARD;
717 break;
718 case REPORT_VERBOSE:
719 szLevel = LOGUTIL_VERBOSE;
720 break;
721 case REPORT_DEBUG:
722 szLevel = LOGUTIL_DEBUG;
723 break;
724 case REPORT_NONE:
725 szLevel = LOGUTIL_NONE;
726 break;
727 }
728 LogStringLine(REPORT_STANDARD, "--- logging level: %hs ---", szLevel);
729
730 hr = S_OK;
731
732 ReleaseStr(sczCurrentDateTime);
733
734 return hr;
735}
736
737
738/********************************************************************
739 LogFooterWork - write a standard footer to the log
740
741********************************************************************/
742
743static HRESULT LogFooterWork(
744 __in_z __format_string LPCSTR szFormat,
745 ...
746 )
747{
748 HRESULT hr = S_OK;
749
750 va_list args;
751 va_start(args, szFormat);
752 hr = LogStringWorkArgs(REPORT_STANDARD, szFormat, args, TRUE);
753 va_end(args);
754
755 return hr;
756}
757
758extern "C" HRESULT DAPI LogFooter()
759{
760 HRESULT hr = S_OK;
761 LPWSTR sczCurrentDateTime = NULL;
762 TimeCurrentDateTime(&sczCurrentDateTime, FALSE);
763 hr = LogFooterWork("=== Logging stopped: %ls ===", sczCurrentDateTime);
764 ReleaseStr(sczCurrentDateTime);
765 return hr;
766}
767
768/********************************************************************
769 LogStringWorkRaw - Write a raw, unformatted string to the log
770
771********************************************************************/
772extern "C" HRESULT LogStringWorkRaw(
773 __in_z LPCSTR szLogData
774 )
775{
776 Assert(szLogData && *szLogData);
777
778 HRESULT hr = S_OK;
779 size_t cchLogData = 0;
780 DWORD cbLogData = 0;
781 DWORD cbTotal = 0;
782 DWORD cbWrote = 0;
783
784 hr = ::StringCchLengthA(szLogData, STRSAFE_MAX_CCH, &cchLogData);
785 LoguExitOnRootFailure(hr, "Failed to get length of raw string");
786
787 cbLogData = (DWORD)cchLogData;
788
789 // If the log hasn't been initialized yet, store it in a buffer
790 if (INVALID_HANDLE_VALUE == LogUtil_hLog)
791 {
792 hr = StrAnsiAllocConcat(&LogUtil_sczPreInitBuffer, szLogData, 0);
793 LoguExitOnFailure(hr, "Failed to concatenate string to pre-init buffer");
794
795 ExitFunction1(hr = S_OK);
796 }
797
798 // write the string
799 while (cbTotal < cbLogData)
800 {
801 if (!::WriteFile(LogUtil_hLog, reinterpret_cast<const BYTE*>(szLogData) + cbTotal, cbLogData - cbTotal, &cbWrote, NULL))
802 {
803 LoguExitOnLastError(hr, "Failed to write output to log: %ls - %hs", LogUtil_sczLogPath, szLogData);
804 }
805
806 cbTotal += cbWrote;
807 }
808
809LExit:
810 return hr;
811}
812
813//
814// private worker functions
815//
816static HRESULT LogIdWork(
817 __in REPORT_LEVEL rl,
818 __in_opt HMODULE hModule,
819 __in DWORD dwLogId,
820 __in va_list args,
821 __in BOOL fLOGUTIL_NEWLINE
822 )
823{
824 HRESULT hr = S_OK;
825 LPWSTR pwz = NULL;
826 DWORD cch = 0;
827
828 // get the string for the id
829#pragma prefast(push)
830#pragma prefast(disable:25028)
831#pragma prefast(disable:25068)
832 cch = ::FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE,
833 static_cast<LPCVOID>(hModule), dwLogId, 0, reinterpret_cast<LPWSTR>(&pwz), 0, &args);
834#pragma prefast(pop)
835
836 if (0 == cch)
837 {
838 LoguExitOnLastError(hr, "failed to log id: %d", dwLogId);
839 }
840
841 if (2 <= cch && L'\r' == pwz[cch-2] && L'\n' == pwz[cch-1])
842 {
843 pwz[cch-2] = L'\0'; // remove newline from message table
844 }
845
846 LogStringWork(rl, dwLogId, pwz, fLOGUTIL_NEWLINE);
847
848LExit:
849 if (pwz)
850 {
851 ::LocalFree(pwz);
852 }
853
854 return hr;
855}
856
857
858static HRESULT LogStringWorkArgs(
859 __in REPORT_LEVEL rl,
860 __in_z __format_string LPCSTR szFormat,
861 __in va_list args,
862 __in BOOL fLOGUTIL_NEWLINE
863 )
864{
865 Assert(szFormat && *szFormat);
866
867 HRESULT hr = S_OK;
868 LPWSTR sczFormat = NULL;
869 LPWSTR sczMessage = NULL;
870
871 hr = StrAllocStringAnsi(&sczFormat, szFormat, 0, CP_ACP);
872 LoguExitOnFailure(hr, "Failed to convert format string to wide character string");
873
874 // format the string as a unicode string
875 hr = StrAllocFormattedArgs(&sczMessage, sczFormat, args);
876 LoguExitOnFailure(hr, "Failed to format message: \"%ls\"", sczFormat);
877
878 hr = LogStringWork(rl, 0, sczMessage, fLOGUTIL_NEWLINE);
879 LoguExitOnFailure(hr, "Failed to write formatted string to log:%ls", sczMessage);
880
881LExit:
882 ReleaseStr(sczFormat);
883 ReleaseStr(sczMessage);
884
885 return hr;
886}
887
888
889static HRESULT LogStringWork(
890 __in REPORT_LEVEL rl,
891 __in DWORD dwLogId,
892 __in_z LPCWSTR sczString,
893 __in BOOL fLOGUTIL_NEWLINE
894 )
895{
896 Assert(sczString && *sczString);
897
898 HRESULT hr = S_OK;
899 BOOL fEnteredCriticalSection = FALSE;
900 LPWSTR scz = NULL;
901 LPCWSTR wzLogData = NULL;
902 LPSTR sczMultiByte = NULL;
903
904 // If logging is disabled, just bail.
905 if (LogUtil_fDisabled)
906 {
907 ExitFunction();
908 }
909
910 ::EnterCriticalSection(&LogUtil_csLog);
911 fEnteredCriticalSection = TRUE;
912
913 if (fLOGUTIL_NEWLINE)
914 {
915 // get the process and thread id.
916 DWORD dwProcessId = ::GetCurrentProcessId();
917 DWORD dwThreadId = ::GetCurrentThreadId();
918
919 // get the time relative to GMT.
920 SYSTEMTIME st = { };
921 ::GetLocalTime(&st);
922
923 DWORD dwId = dwLogId & 0xFFFFFFF;
924 DWORD dwType = dwLogId & 0xF0000000;
925 LPSTR szType = (0xE0000000 == dwType || REPORT_ERROR == rl) ? "e" : (0xA0000000 == dwType || REPORT_WARNING == rl) ? "w" : "i";
926
927 // add line prefix and trailing newline
928 hr = StrAllocFormatted(&scz, L"%ls[%04X:%04X][%04hu-%02hu-%02huT%02hu:%02hu:%02hu]%hs%03d:%ls %ls%ls", LogUtil_sczSpecialBeginLine ? LogUtil_sczSpecialBeginLine : L"",
929 dwProcessId, dwThreadId, st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, szType, dwId,
930 LogUtil_sczSpecialAfterTimeStamp ? LogUtil_sczSpecialAfterTimeStamp : L"", sczString, LogUtil_sczSpecialEndLine ? LogUtil_sczSpecialEndLine : L"\r\n");
931 LoguExitOnFailure(hr, "Failed to format line prefix.");
932 }
933
934 wzLogData = scz ? scz : sczString;
935
936 // Convert to UTF-8 before writing out to the log file
937 hr = StrAnsiAllocString(&sczMultiByte, wzLogData, 0, CP_UTF8);
938 LoguExitOnFailure(hr, "Failed to convert log string to UTF-8");
939
940 if (s_vpfLogStringWorkRaw)
941 {
942 hr = s_vpfLogStringWorkRaw(sczMultiByte, s_vpvLogStringWorkRawContext);
943 LoguExitOnFailure(hr, "Failed to write string to log using redirected function: %ls", sczString);
944 }
945 else
946 {
947 hr = LogStringWorkRaw(sczMultiByte);
948 LoguExitOnFailure(hr, "Failed to write string to log using default function: %ls", sczString);
949 }
950
951LExit:
952 if (fEnteredCriticalSection)
953 {
954 ::LeaveCriticalSection(&LogUtil_csLog);
955 }
956
957 ReleaseStr(scz);
958 ReleaseStr(sczMultiByte);
959
960 return hr;
961}
diff --git a/src/libs/dutil/WixToolset.DUtil/memutil.cpp b/src/libs/dutil/WixToolset.DUtil/memutil.cpp
new file mode 100644
index 00000000..c805a9c0
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/memutil.cpp
@@ -0,0 +1,336 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define MemExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_MEMUTIL, x, s, __VA_ARGS__)
8#define MemExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_MEMUTIL, x, s, __VA_ARGS__)
9#define MemExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_MEMUTIL, x, s, __VA_ARGS__)
10#define MemExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_MEMUTIL, x, s, __VA_ARGS__)
11#define MemExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_MEMUTIL, x, s, __VA_ARGS__)
12#define MemExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_MEMUTIL, x, s, __VA_ARGS__)
13#define MemExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_MEMUTIL, p, x, e, s, __VA_ARGS__)
14#define MemExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_MEMUTIL, p, x, s, __VA_ARGS__)
15#define MemExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_MEMUTIL, p, x, e, s, __VA_ARGS__)
16#define MemExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_MEMUTIL, p, x, s, __VA_ARGS__)
17#define MemExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_MEMUTIL, e, x, s, __VA_ARGS__)
18#define MemExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_MEMUTIL, g, x, s, __VA_ARGS__)
19
20
21#if DEBUG
22static BOOL vfMemInitialized = FALSE;
23#endif
24
25extern "C" HRESULT DAPI MemInitialize()
26{
27#if DEBUG
28 vfMemInitialized = TRUE;
29#endif
30 return S_OK;
31}
32
33extern "C" void DAPI MemUninitialize()
34{
35#if DEBUG
36 vfMemInitialized = FALSE;
37#endif
38}
39
40extern "C" LPVOID DAPI MemAlloc(
41 __in SIZE_T cbSize,
42 __in BOOL fZero
43 )
44{
45// AssertSz(vfMemInitialized, "MemInitialize() not called, this would normally crash");
46 AssertSz(0 < cbSize, "MemAlloc() called with invalid size");
47 return ::HeapAlloc(::GetProcessHeap(), fZero ? HEAP_ZERO_MEMORY : 0, cbSize);
48}
49
50
51extern "C" LPVOID DAPI MemReAlloc(
52 __in LPVOID pv,
53 __in SIZE_T cbSize,
54 __in BOOL fZero
55 )
56{
57// AssertSz(vfMemInitialized, "MemInitialize() not called, this would normally crash");
58 AssertSz(0 < cbSize, "MemReAlloc() called with invalid size");
59 return ::HeapReAlloc(::GetProcessHeap(), fZero ? HEAP_ZERO_MEMORY : 0, pv, cbSize);
60}
61
62
63extern "C" HRESULT DAPI MemReAllocSecure(
64 __in LPVOID pv,
65 __in SIZE_T cbSize,
66 __in BOOL fZero,
67 __deref_out LPVOID* ppvNew
68 )
69{
70// AssertSz(vfMemInitialized, "MemInitialize() not called, this would normally crash");
71 AssertSz(ppvNew, "MemReAllocSecure() called with uninitialized pointer");
72 AssertSz(0 < cbSize, "MemReAllocSecure() called with invalid size");
73
74 HRESULT hr = S_OK;
75 DWORD dwFlags = HEAP_REALLOC_IN_PLACE_ONLY;
76 LPVOID pvNew = NULL;
77
78 dwFlags |= fZero ? HEAP_ZERO_MEMORY : 0;
79 pvNew = ::HeapReAlloc(::GetProcessHeap(), dwFlags, pv, cbSize);
80 if (!pvNew)
81 {
82 pvNew = MemAlloc(cbSize, fZero);
83 if (pvNew)
84 {
85 const SIZE_T cbCurrent = MemSize(pv);
86 if (-1 == cbCurrent)
87 {
88 MemExitOnRootFailure(hr = E_INVALIDARG, "Failed to get memory size");
89 }
90
91 // HeapReAlloc may allocate more memory than requested.
92 const SIZE_T cbNew = MemSize(pvNew);
93 if (-1 == cbNew)
94 {
95 MemExitOnRootFailure(hr = E_INVALIDARG, "Failed to get memory size");
96 }
97
98 cbSize = cbNew;
99 if (cbSize > cbCurrent)
100 {
101 cbSize = cbCurrent;
102 }
103
104 memcpy_s(pvNew, cbNew, pv, cbSize);
105
106 SecureZeroMemory(pv, cbCurrent);
107 MemFree(pv);
108 }
109 }
110 MemExitOnNull(pvNew, hr, E_OUTOFMEMORY, "Failed to reallocate memory");
111
112 *ppvNew = pvNew;
113 pvNew = NULL;
114
115LExit:
116 ReleaseMem(pvNew);
117
118 return hr;
119}
120
121
122extern "C" HRESULT DAPI MemAllocArray(
123 __inout LPVOID* ppvArray,
124 __in SIZE_T cbArrayType,
125 __in DWORD dwItemCount
126 )
127{
128 return MemReAllocArray(ppvArray, 0, cbArrayType, dwItemCount);
129}
130
131
132extern "C" HRESULT DAPI MemReAllocArray(
133 __inout LPVOID* ppvArray,
134 __in DWORD cArray,
135 __in SIZE_T cbArrayType,
136 __in DWORD dwNewItemCount
137 )
138{
139 HRESULT hr = S_OK;
140 DWORD cNew = 0;
141 LPVOID pvNew = NULL;
142 SIZE_T cbNew = 0;
143
144 hr = ::DWordAdd(cArray, dwNewItemCount, &cNew);
145 MemExitOnFailure(hr, "Integer overflow when calculating new element count.");
146
147 hr = ::SIZETMult(cNew, cbArrayType, &cbNew);
148 MemExitOnFailure(hr, "Integer overflow when calculating new block size.");
149
150 if (*ppvArray)
151 {
152 SIZE_T cbCurrent = MemSize(*ppvArray);
153 if (cbCurrent < cbNew)
154 {
155 pvNew = MemReAlloc(*ppvArray, cbNew, TRUE);
156 MemExitOnNull(pvNew, hr, E_OUTOFMEMORY, "Failed to allocate larger array.");
157
158 *ppvArray = pvNew;
159 }
160 }
161 else
162 {
163 pvNew = MemAlloc(cbNew, TRUE);
164 MemExitOnNull(pvNew, hr, E_OUTOFMEMORY, "Failed to allocate new array.");
165
166 *ppvArray = pvNew;
167 }
168
169LExit:
170 return hr;
171}
172
173
174extern "C" HRESULT DAPI MemEnsureArraySize(
175 __deref_inout_bcount(cArray * cbArrayType) LPVOID* ppvArray,
176 __in DWORD cArray,
177 __in SIZE_T cbArrayType,
178 __in DWORD dwGrowthCount
179 )
180{
181 HRESULT hr = S_OK;
182 DWORD cNew = 0;
183 LPVOID pvNew = NULL;
184 SIZE_T cbNew = 0;
185
186 hr = ::DWordAdd(cArray, dwGrowthCount, &cNew);
187 MemExitOnFailure(hr, "Integer overflow when calculating new element count.");
188
189 hr = ::SIZETMult(cNew, cbArrayType, &cbNew);
190 MemExitOnFailure(hr, "Integer overflow when calculating new block size.");
191
192 if (*ppvArray)
193 {
194 SIZE_T cbUsed = cArray * cbArrayType;
195 SIZE_T cbCurrent = MemSize(*ppvArray);
196 if (cbCurrent < cbUsed)
197 {
198 pvNew = MemReAlloc(*ppvArray, cbNew, TRUE);
199 MemExitOnNull(pvNew, hr, E_OUTOFMEMORY, "Failed to allocate array larger.");
200
201 *ppvArray = pvNew;
202 }
203 }
204 else
205 {
206 pvNew = MemAlloc(cbNew, TRUE);
207 MemExitOnNull(pvNew, hr, E_OUTOFMEMORY, "Failed to allocate new array.");
208
209 *ppvArray = pvNew;
210 }
211
212LExit:
213 return hr;
214}
215
216
217extern "C" HRESULT DAPI MemInsertIntoArray(
218 __deref_inout_bcount((cExistingArray + cInsertItems) * cbArrayType) LPVOID* ppvArray,
219 __in DWORD dwInsertIndex,
220 __in DWORD cInsertItems,
221 __in DWORD cExistingArray,
222 __in SIZE_T cbArrayType,
223 __in DWORD dwGrowthCount
224 )
225{
226 HRESULT hr = S_OK;
227 DWORD i;
228 BYTE *pbArray = NULL;
229
230 if (0 == cInsertItems)
231 {
232 ExitFunction1(hr = S_OK);
233 }
234
235 hr = MemEnsureArraySize(ppvArray, cExistingArray + cInsertItems, cbArrayType, dwGrowthCount);
236 MemExitOnFailure(hr, "Failed to resize array while inserting items");
237
238 pbArray = reinterpret_cast<BYTE *>(*ppvArray);
239 for (i = cExistingArray + cInsertItems - 1; i > dwInsertIndex; --i)
240 {
241 memcpy_s(pbArray + i * cbArrayType, cbArrayType, pbArray + (i - 1) * cbArrayType, cbArrayType);
242 }
243
244 // Zero out the newly-inserted items
245 memset(pbArray + dwInsertIndex * cbArrayType, 0, cInsertItems * cbArrayType);
246
247LExit:
248 return hr;
249}
250
251extern "C" void DAPI MemRemoveFromArray(
252 __inout_bcount((cExistingArray) * cbArrayType) LPVOID pvArray,
253 __in DWORD dwRemoveIndex,
254 __in DWORD cRemoveItems,
255 __in DWORD cExistingArray,
256 __in SIZE_T cbArrayType,
257 __in BOOL fPreserveOrder
258 )
259{
260 BYTE *pbArray = static_cast<BYTE *>(pvArray);
261 DWORD cItemsLeftAfterRemoveIndex = (cExistingArray - cRemoveItems - dwRemoveIndex);
262
263 if (fPreserveOrder)
264 {
265 memmove(pbArray + dwRemoveIndex * cbArrayType, pbArray + (dwRemoveIndex + cRemoveItems) * cbArrayType, cItemsLeftAfterRemoveIndex * cbArrayType);
266 }
267 else
268 {
269 DWORD cItemsToMove = (cRemoveItems > cItemsLeftAfterRemoveIndex ? cItemsLeftAfterRemoveIndex : cRemoveItems);
270 memmove(pbArray + dwRemoveIndex * cbArrayType, pbArray + (cExistingArray - cItemsToMove) * cbArrayType, cItemsToMove * cbArrayType);
271 }
272
273 ZeroMemory(pbArray + (cExistingArray - cRemoveItems) * cbArrayType, cRemoveItems * cbArrayType);
274}
275
276extern "C" void DAPI MemArraySwapItems(
277 __inout_bcount(cbArrayType) LPVOID pvArray,
278 __in DWORD dwIndex1,
279 __in DWORD dwIndex2,
280 __in SIZE_T cbArrayType
281 )
282{
283 BYTE *pbArrayItem1 = static_cast<BYTE *>(pvArray) + dwIndex1 * cbArrayType;
284 BYTE *pbArrayItem2 = static_cast<BYTE *>(pvArray) + dwIndex2 * cbArrayType;
285 DWORD dwByteIndex = 0;
286
287 if (dwIndex1 == dwIndex2)
288 {
289 return;
290 }
291
292 // Use XOR swapping to avoid the need for a temporary item
293 while (dwByteIndex < cbArrayType)
294 {
295 // Try to do many bytes at a time in most cases
296 if (cbArrayType - dwByteIndex > sizeof(DWORD64))
297 {
298 // x: X xor Y
299 *(reinterpret_cast<DWORD64 *>(pbArrayItem1 + dwByteIndex)) ^= *(reinterpret_cast<DWORD64 *>(pbArrayItem2 + dwByteIndex));
300 // y: X xor Y
301 *(reinterpret_cast<DWORD64 *>(pbArrayItem2 + dwByteIndex)) = *(reinterpret_cast<DWORD64 *>(pbArrayItem1 + dwByteIndex)) ^ *(reinterpret_cast<DWORD64 *>(pbArrayItem2 + dwByteIndex));
302 // x: X xor Y
303 *(reinterpret_cast<DWORD64 *>(pbArrayItem1 + dwByteIndex)) ^= *(reinterpret_cast<DWORD64 *>(pbArrayItem2 + dwByteIndex));
304
305 dwByteIndex += sizeof(DWORD64);
306 }
307 else
308 {
309 // x: X xor Y
310 *(reinterpret_cast<unsigned char *>(pbArrayItem1 + dwByteIndex)) ^= *(reinterpret_cast<unsigned char *>(pbArrayItem2 + dwByteIndex));
311 // y: X xor Y
312 *(reinterpret_cast<unsigned char *>(pbArrayItem2 + dwByteIndex)) = *(reinterpret_cast<unsigned char *>(pbArrayItem1 + dwByteIndex)) ^ *(reinterpret_cast<unsigned char *>(pbArrayItem2 + dwByteIndex));
313 // x: X xor Y
314 *(reinterpret_cast<unsigned char *>(pbArrayItem1 + dwByteIndex)) ^= *(reinterpret_cast<unsigned char *>(pbArrayItem2 + dwByteIndex));
315
316 dwByteIndex += sizeof(unsigned char);
317 }
318 }
319}
320
321extern "C" HRESULT DAPI MemFree(
322 __in LPVOID pv
323 )
324{
325// AssertSz(vfMemInitialized, "MemInitialize() not called, this would normally crash");
326 return ::HeapFree(::GetProcessHeap(), 0, pv) ? S_OK : HRESULT_FROM_WIN32(::GetLastError());
327}
328
329
330extern "C" SIZE_T DAPI MemSize(
331 __in LPCVOID pv
332 )
333{
334// AssertSz(vfMemInitialized, "MemInitialize() not called, this would normally crash");
335 return ::HeapSize(::GetProcessHeap(), 0, pv);
336}
diff --git a/src/libs/dutil/WixToolset.DUtil/metautil.cpp b/src/libs/dutil/WixToolset.DUtil/metautil.cpp
new file mode 100644
index 00000000..f313fc1c
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/metautil.cpp
@@ -0,0 +1,378 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5// okay, this may look a little weird, but metautil.h cannot be in the
6// pre-compiled header because we need to #define these things so the
7// correct GUID's get pulled into this object file
8#include <initguid.h>
9#include "metautil.h"
10
11
12// Exit macros
13#define MetaExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_METAUTIL, x, s, __VA_ARGS__)
14#define MetaExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_METAUTIL, x, s, __VA_ARGS__)
15#define MetaExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_METAUTIL, x, s, __VA_ARGS__)
16#define MetaExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_METAUTIL, x, s, __VA_ARGS__)
17#define MetaExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_METAUTIL, x, s, __VA_ARGS__)
18#define MetaExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_METAUTIL, x, s, __VA_ARGS__)
19#define MetaExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_METAUTIL, p, x, e, s, __VA_ARGS__)
20#define MetaExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_METAUTIL, p, x, s, __VA_ARGS__)
21#define MetaExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_METAUTIL, p, x, e, s, __VA_ARGS__)
22#define MetaExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_METAUTIL, p, x, s, __VA_ARGS__)
23#define MetaExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_METAUTIL, e, x, s, __VA_ARGS__)
24#define MetaExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_METAUTIL, g, x, s, __VA_ARGS__)
25
26
27// prototypes
28static void Sort(
29 __in_ecount(cArray) DWORD dwArray[],
30 __in int cArray
31 );
32
33
34/********************************************************************
35 MetaFindWebBase - finds a metabase base string that matches IP, Port and Header
36
37********************************************************************/
38extern "C" HRESULT DAPI MetaFindWebBase(
39 __in IMSAdminBaseW* piMetabase,
40 __in_z LPCWSTR wzIP,
41 __in int iPort,
42 __in_z LPCWSTR wzHeader,
43 __in BOOL fSecure,
44 __out_ecount(cchWebBase) LPWSTR wzWebBase,
45 __in DWORD cchWebBase
46 )
47{
48 Assert(piMetabase && cchWebBase);
49
50 HRESULT hr = S_OK;
51
52 BOOL fFound = FALSE;
53
54 WCHAR wzKey[METADATA_MAX_NAME_LEN];
55 WCHAR wzSubkey[METADATA_MAX_NAME_LEN];
56 DWORD dwIndex = 0;
57
58 METADATA_RECORD mr;
59 METADATA_RECORD mrAddress;
60
61 LPWSTR pwzExists = NULL;
62 LPWSTR pwzIPExists = NULL;
63 LPWSTR pwzPortExists = NULL;
64 int iPortExists = 0;
65 LPCWSTR pwzHeaderExists = NULL;
66
67 memset(&mr, 0, sizeof(mr));
68 mr.dwMDIdentifier = MD_KEY_TYPE;
69 mr.dwMDAttributes = METADATA_INHERIT;
70 mr.dwMDUserType = IIS_MD_UT_SERVER;
71 mr.dwMDDataType = ALL_METADATA;
72
73 memset(&mrAddress, 0, sizeof(mrAddress));
74 mrAddress.dwMDIdentifier = (fSecure) ? MD_SECURE_BINDINGS : MD_SERVER_BINDINGS;
75 mrAddress.dwMDAttributes = METADATA_INHERIT;
76 mrAddress.dwMDUserType = IIS_MD_UT_SERVER;
77 mrAddress.dwMDDataType = ALL_METADATA;
78
79 // loop through the "web keys" looking for the "IIsWebServer" key that matches wzWeb
80 for (dwIndex = 0; SUCCEEDED(hr); ++dwIndex)
81 {
82 hr = piMetabase->EnumKeys(METADATA_MASTER_ROOT_HANDLE, L"/LM/W3SVC", wzSubkey, dwIndex);
83 if (FAILED(hr))
84 break;
85
86 ::StringCchPrintfW(wzKey, countof(wzKey), L"/LM/W3SVC/%s", wzSubkey);
87 hr = MetaGetValue(piMetabase, METADATA_MASTER_ROOT_HANDLE, wzKey, &mr);
88 if (MD_ERROR_DATA_NOT_FOUND == hr || HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr)
89 {
90 hr = S_FALSE; // didn't find anything, try next one
91 continue;
92 }
93 MetaExitOnFailure(hr, "failed to get key from metabase while searching for web servers");
94
95 // if we have an IIsWebServer store the key
96 if (0 == lstrcmpW(L"IIsWebServer", (LPCWSTR)mr.pbMDData))
97 {
98 hr = MetaGetValue(piMetabase, METADATA_MASTER_ROOT_HANDLE, wzKey, &mrAddress);
99 if (MD_ERROR_DATA_NOT_FOUND == hr)
100 hr = S_FALSE;
101 MetaExitOnFailure(hr, "failed to get address from metabase while searching for web servers");
102
103 // break down the first address into parts
104 pwzIPExists = reinterpret_cast<LPWSTR>(mrAddress.pbMDData);
105 pwzExists = wcsstr(pwzIPExists, L":");
106 if (NULL == pwzExists)
107 continue;
108
109 *pwzExists = L'\0';
110
111 pwzPortExists = pwzExists + 1;
112 pwzExists = wcsstr(pwzPortExists, L":");
113 if (NULL == pwzExists)
114 continue;
115
116 *pwzExists = L'\0';
117 iPortExists = wcstol(pwzPortExists, NULL, 10);
118
119 pwzHeaderExists = pwzExists + 1;
120
121 // compare the passed in address with the address listed for this web
122 if (S_OK == hr &&
123 (0 == lstrcmpW(wzIP, pwzIPExists) || 0 == lstrcmpW(wzIP, L"*")) &&
124 iPort == iPortExists &&
125 0 == lstrcmpW(wzHeader, pwzHeaderExists))
126 {
127 // if the passed in buffer wasn't big enough
128 hr = ::StringCchCopyW(wzWebBase, cchWebBase, wzKey);
129 MetaExitOnFailure(hr, "failed to copy in web base: %ls", wzKey);
130
131 fFound = TRUE;
132 break;
133 }
134 }
135 }
136
137 if (E_NOMOREITEMS == hr)
138 {
139 Assert(!fFound);
140 hr = S_FALSE;
141 }
142
143LExit:
144 MetaFreeValue(&mrAddress);
145 MetaFreeValue(&mr);
146
147 if (!fFound && SUCCEEDED(hr))
148 hr = S_FALSE;
149
150 return hr;
151}
152
153
154/********************************************************************
155 MetaFindFreeWebBase - finds the next metabase base string
156
157********************************************************************/
158extern "C" HRESULT DAPI MetaFindFreeWebBase(
159 __in IMSAdminBaseW* piMetabase,
160 __out_ecount(cchWebBase) LPWSTR wzWebBase,
161 __in DWORD cchWebBase
162 )
163{
164 Assert(piMetabase);
165
166 HRESULT hr = S_OK;
167
168 WCHAR wzKey[METADATA_MAX_NAME_LEN];
169 WCHAR wzSubkey[METADATA_MAX_NAME_LEN];
170 DWORD dwSubKeys[100];
171 int cSubKeys = 0;
172 DWORD dwIndex = 0;
173
174 int i;
175 DWORD dwKey;
176
177 METADATA_RECORD mr;
178
179 memset(&mr, 0, sizeof(mr));
180 mr.dwMDIdentifier = MD_KEY_TYPE;
181 mr.dwMDAttributes = 0;
182 mr.dwMDUserType = IIS_MD_UT_SERVER;
183 mr.dwMDDataType = STRING_METADATA;
184
185 // loop through the "web keys" looking for the "IIsWebServer" key that matches wzWeb
186 for (dwIndex = 0; SUCCEEDED(hr); ++dwIndex)
187 {
188 hr = piMetabase->EnumKeys(METADATA_MASTER_ROOT_HANDLE, L"/LM/W3SVC", wzSubkey, dwIndex);
189 if (FAILED(hr))
190 break;
191
192 ::StringCchPrintfW(wzKey, countof(wzKey), L"/LM/W3SVC/%s", wzSubkey);
193
194 hr = MetaGetValue(piMetabase, METADATA_MASTER_ROOT_HANDLE, wzKey, &mr);
195 if (MD_ERROR_DATA_NOT_FOUND == hr || HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr)
196 {
197 hr = S_FALSE; // didn't find anything, try next one
198 continue;
199 }
200 MetaExitOnFailure(hr, "failed to get key from metabase while searching for free web root");
201
202 // if we have a IIsWebServer get the address information
203 if (0 == lstrcmpW(L"IIsWebServer", reinterpret_cast<LPCWSTR>(mr.pbMDData)))
204 {
205 if (cSubKeys >= countof(dwSubKeys))
206 {
207 hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
208 MetaExitOnFailure(hr, "Insufficient buffer to track all sub-WebSites");
209 }
210
211 dwSubKeys[cSubKeys] = wcstol(wzSubkey, NULL, 10);
212 ++cSubKeys;
213 Sort(dwSubKeys, cSubKeys);
214 }
215 }
216
217 if (E_NOMOREITEMS == hr)
218 hr = S_OK;
219 MetaExitOnFailure(hr, "failed to find free web root");
220
221 // find the lowest free web root
222 dwKey = 1;
223 for (i = 0; i < cSubKeys; ++i)
224 {
225 if (dwKey < dwSubKeys[i])
226 break;
227
228 dwKey = dwSubKeys[i] + 1;
229 }
230
231 hr = ::StringCchPrintfW(wzWebBase, cchWebBase, L"/LM/W3SVC/%u", dwKey);
232LExit:
233 MetaFreeValue(&mr);
234 return hr;
235}
236
237
238/********************************************************************
239 MetaOpenKey - open key
240
241********************************************************************/
242extern "C" HRESULT DAPI MetaOpenKey(
243 __in IMSAdminBaseW* piMetabase,
244 __in METADATA_HANDLE mhKey,
245 __in_z LPCWSTR wzKey,
246 __in DWORD dwAccess,
247 __in DWORD cRetries,
248 __out METADATA_HANDLE* pmh
249 )
250{
251 Assert(piMetabase && pmh);
252
253 HRESULT hr = S_OK;
254
255 // loop while the key is busy
256 do
257 {
258 hr = piMetabase->OpenKey(mhKey, wzKey, dwAccess, 10, pmh);
259 if (HRESULT_FROM_WIN32(ERROR_PATH_BUSY) == hr)
260 ::SleepEx(1000, TRUE);
261 } while (HRESULT_FROM_WIN32(ERROR_PATH_BUSY) == hr && 0 < cRetries--);
262
263 return hr;
264}
265
266
267/********************************************************************
268 MetaGetValue - finds the next metabase base string
269
270 NOTE: piMetabase is optional
271********************************************************************/
272extern "C" HRESULT DAPI MetaGetValue(
273 __in IMSAdminBaseW* piMetabase,
274 __in METADATA_HANDLE mhKey,
275 __in_z LPCWSTR wzKey,
276 __inout METADATA_RECORD* pmr
277 )
278{
279 Assert(pmr);
280
281 HRESULT hr = S_OK;
282 BOOL fInitialized = FALSE;
283 DWORD cbRequired = 0;
284
285 if (!piMetabase)
286 {
287 hr = ::CoInitialize(NULL);
288 MetaExitOnFailure(hr, "failed to initialize COM");
289 fInitialized = TRUE;
290
291 hr = ::CoCreateInstance(CLSID_MSAdminBase, NULL, CLSCTX_ALL, IID_IMSAdminBase, reinterpret_cast<LPVOID*>(&piMetabase));
292 MetaExitOnFailure(hr, "failed to get IID_IMSAdminBaseW object");
293 }
294
295 if (!pmr->pbMDData)
296 {
297 pmr->dwMDDataLen = 256;
298 pmr->pbMDData = static_cast<BYTE*>(MemAlloc(pmr->dwMDDataLen, TRUE));
299 MetaExitOnNull(pmr->pbMDData, hr, E_OUTOFMEMORY, "failed to allocate memory for metabase value");
300 }
301 else // set the size of the data to the actual size of the memory
302 {
303 SIZE_T cb = MemSize(pmr->pbMDData);
304 if (cb > DWORD_MAX)
305 {
306 MetaExitOnRootFailure(hr = E_INVALIDSTATE, "metabase data is too large: %Iu", cb);
307 }
308 pmr->dwMDDataLen = (DWORD)cb;
309 }
310
311 hr = piMetabase->GetData(mhKey, wzKey, pmr, &cbRequired);
312 if (HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) == hr)
313 {
314 pmr->dwMDDataLen = cbRequired;
315 BYTE* pb = static_cast<BYTE*>(MemReAlloc(pmr->pbMDData, pmr->dwMDDataLen, TRUE));
316 MetaExitOnNull(pb, hr, E_OUTOFMEMORY, "failed to reallocate memory for metabase value");
317
318 pmr->pbMDData = pb;
319 hr = piMetabase->GetData(mhKey, wzKey, pmr, &cbRequired);
320 }
321 MetaExitOnFailure(hr, "failed to get metabase data");
322
323LExit:
324 if (fInitialized)
325 {
326 ReleaseObject(piMetabase);
327 ::CoUninitialize();
328 }
329
330 return hr;
331}
332
333
334/********************************************************************
335 MetaFreeValue - frees data in METADATA_RECORD remove MetaGetValue()
336
337 NOTE: METADATA_RECORD must have been returned from MetaGetValue() above
338********************************************************************/
339extern "C" void DAPI MetaFreeValue(
340 __in METADATA_RECORD* pmr
341 )
342{
343 Assert(pmr);
344
345 ReleaseNullMem(pmr->pbMDData);
346}
347
348
349//
350// private
351//
352
353/********************************************************************
354 Sort - quick and dirty insertion sort
355
356********************************************************************/
357static void Sort(
358 __in_ecount(cArray) DWORD dwArray[],
359 __in int cArray
360 )
361{
362 int i, j;
363 DWORD dwData;
364
365 for (i = 1; i < cArray; ++i)
366 {
367 dwData = dwArray[i];
368
369 j = i - 1;
370 while (0 <= j && dwArray[j] > dwData)
371 {
372 dwArray[j + 1] = dwArray[j];
373 j--;
374 }
375
376 dwArray[j + 1] = dwData;
377 }
378}
diff --git a/src/libs/dutil/WixToolset.DUtil/monutil.cpp b/src/libs/dutil/WixToolset.DUtil/monutil.cpp
new file mode 100644
index 00000000..6a7f0596
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/monutil.cpp
@@ -0,0 +1,2019 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define MonExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_MONUTIL, x, s, __VA_ARGS__)
8#define MonExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_MONUTIL, x, s, __VA_ARGS__)
9#define MonExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_MONUTIL, x, s, __VA_ARGS__)
10#define MonExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_MONUTIL, x, s, __VA_ARGS__)
11#define MonExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_MONUTIL, x, s, __VA_ARGS__)
12#define MonExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_MONUTIL, x, s, __VA_ARGS__)
13#define MonExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_MONUTIL, p, x, e, s, __VA_ARGS__)
14#define MonExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_MONUTIL, p, x, s, __VA_ARGS__)
15#define MonExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_MONUTIL, p, x, e, s, __VA_ARGS__)
16#define MonExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_MONUTIL, p, x, s, __VA_ARGS__)
17#define MonExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_MONUTIL, e, x, s, __VA_ARGS__)
18#define MonExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_MONUTIL, g, x, s, __VA_ARGS__)
19
20const int MON_THREAD_GROWTH = 5;
21const int MON_ARRAY_GROWTH = 40;
22const int MON_MAX_MONITORS_PER_THREAD = 63;
23const int MON_THREAD_INIT_RETRIES = 1000;
24const int MON_THREAD_INIT_RETRY_PERIOD_IN_MS = 10;
25const int MON_THREAD_NETWORK_FAIL_RETRY_IN_MS = 1000*60; // if we know we failed to connect, retry every minute
26const int MON_THREAD_NETWORK_SUCCESSFUL_RETRY_IN_MS = 1000*60*20; // if we're just checking for remote servers dieing, check much less frequently
27const int MON_THREAD_WAIT_REMOVE_DEVICE = 5000;
28const LPCWSTR MONUTIL_WINDOW_CLASS = L"MonUtilClass";
29
30enum MON_MESSAGE
31{
32 MON_MESSAGE_ADD = WM_APP + 1,
33 MON_MESSAGE_REMOVE,
34 MON_MESSAGE_REMOVED, // Sent by waiter thread back to coordinator thread to indicate a remove occurred
35 MON_MESSAGE_NETWORK_WAIT_FAILED, // Sent by waiter thread back to coordinator thread to indicate a network wait failed. Coordinator thread will periodically trigger retries (via MON_MESSAGE_NETWORK_STATUS_UPDATE messages).
36 MON_MESSAGE_NETWORK_WAIT_SUCCEEDED, // Sent by waiter thread back to coordinator thread to indicate a previously failing network wait is now succeeding. Coordinator thread will stop triggering retries if no other failing waits exist.
37 MON_MESSAGE_NETWORK_STATUS_UPDATE, // Some change to network connectivity occurred (a network connection was connected or disconnected for example)
38 MON_MESSAGE_NETWORK_RETRY_SUCCESSFUL_NETWORK_WAITS, // Coordinator thread is telling waiters to retry any successful network waits.
39 // Annoyingly, this is necessary to catch the rare case that the remote server goes offline unexpectedly, such as by
40 // network cable unplugged or power loss - in this case there is no local network status change, and the wait will just never fire.
41 // So we very occasionally retry all successful network waits. When this occurs, we notify for changes, even though there may not have been any.
42 // This is because we have no way to detect if the old wait had failed (and changes were lost) due to the remote server going offline during that time or not.
43 // If we do this often, it can cause a lot of wasted work (which could be expensive for battery life), so the default is to do it very rarely (every 20 minutes).
44 MON_MESSAGE_NETWORK_RETRY_FAILED_NETWORK_WAITS, // Coordinator thread is telling waiters to retry any failed network waits
45 MON_MESSAGE_DRIVE_STATUS_UPDATE, // Some change to local drive has occurred (new drive created or plugged in, or removed)
46 MON_MESSAGE_DRIVE_QUERY_REMOVE, // User wants to unplug a drive, which MonUtil will always allow
47 MON_MESSAGE_STOP
48};
49
50enum MON_TYPE
51{
52 MON_NONE = 0,
53 MON_DIRECTORY = 1,
54 MON_REGKEY = 2
55};
56
57struct MON_REQUEST
58{
59 MON_TYPE type;
60 DWORD dwMaxSilencePeriodInMs;
61
62 // Handle to the main window for RegisterDeviceNotification() (same handle as owned by coordinator thread)
63 HWND hwnd;
64 // and handle to the notification (specific to this request)
65 HDEVNOTIFY hNotify;
66
67 BOOL fRecursive;
68 void *pvContext;
69
70 HRESULT hrStatus;
71
72 LPWSTR sczOriginalPathRequest;
73 BOOL fNetwork; // This reflects either a UNC or mounted drive original request
74 DWORD dwPathHierarchyIndex;
75 LPWSTR *rgsczPathHierarchy;
76 DWORD cPathHierarchy;
77
78 // If the notify fires, fPendingFire gets set to TRUE, and we wait to see if other writes are occurring, and only after the configured silence period do we notify of changes
79 // after notification, we set fPendingFire back to FALSE
80 BOOL fPendingFire;
81 BOOL fSkipDeltaAdd;
82 DWORD dwSilencePeriodInMs;
83
84 union
85 {
86 struct
87 {
88 } directory;
89 struct
90 {
91 HKEY hkRoot;
92 HKEY hkSubKey;
93 REG_KEY_BITNESS kbKeyBitness; // Only used to pass on 32-bit, 64-bit, or default parameter
94 } regkey;
95 };
96};
97
98struct MON_ADD_MESSAGE
99{
100 MON_REQUEST request;
101 HANDLE handle;
102};
103
104struct MON_REMOVE_MESSAGE
105{
106 MON_TYPE type;
107 BOOL fRecursive;
108
109 union
110 {
111 struct
112 {
113 LPWSTR sczDirectory;
114 } directory;
115 struct
116 {
117 HKEY hkRoot;
118 LPWSTR sczSubKey;
119 REG_KEY_BITNESS kbKeyBitness;
120 } regkey;
121 };
122};
123
124struct MON_WAITER_CONTEXT
125{
126 DWORD dwCoordinatorThreadId;
127
128 HANDLE hWaiterThread;
129 DWORD dwWaiterThreadId;
130 BOOL fWaiterThreadMessageQueueInitialized;
131
132 // Callbacks
133 PFN_MONGENERAL vpfMonGeneral;
134 PFN_MONDIRECTORY vpfMonDirectory;
135 PFN_MONREGKEY vpfMonRegKey;
136
137 // Context for callbacks
138 LPVOID pvContext;
139
140 // HANDLEs are in their own array for easy use with WaitForMultipleObjects()
141 // After initialization, the very first handle is just to wake the listener thread to have it re-wait on a new list
142 // Because this array is read by both coordinator thread and waiter thread, to avoid locking between both threads, it must start at the maximum size
143 HANDLE *rgHandles;
144 DWORD cHandles;
145
146 // Requested things to monitor
147 MON_REQUEST *rgRequests;
148 DWORD cRequests;
149
150 // Number of pending notifications
151 DWORD cRequestsPending;
152
153 // Number of requests in a failed state (couldn't initiate wait)
154 DWORD cRequestsFailing;
155};
156
157// Info stored about each waiter by the coordinator
158struct MON_WAITER_INFO
159{
160 DWORD cMonitorCount;
161
162 MON_WAITER_CONTEXT *pWaiterContext;
163};
164
165// This struct is used when Thread A wants to send a task to another thread B (and get notified when the task finishes)
166// You typically declare this struct in a manner that a pointer to it is valid as long as a thread that could respond is still running
167// (even long after sender is no longer waiting, in case thread has huge message queue)
168// and you must send 2 parameters in the message:
169// 1) a pointer to this struct (which is always valid)
170// 2) the original value of dwIteration
171// The receiver of the message can compare the current value of dwSendIteration in the struct with what was sent in the message
172// If values are different, we're too late and thread A is no longer waiting on this response
173// otherwise, set dwResponseIteration to the same value, and call ::SetEvent() on hWait
174// Thread A will then wakeup, and must verify that dwResponseIteration == dwSendIteration to ensure it isn't an earlier out-of-date reply
175// replying to a newer wait
176// pvContext is used to send a misc parameter related to processing data
177struct MON_INTERNAL_TEMPORARY_WAIT
178{
179 // Should be incremented each time sender sends a pointer to this struct, so each request has a different iteration
180 DWORD dwSendIteration;
181 DWORD dwReceiveIteration;
182 HANDLE hWait;
183 void *pvContext;
184};
185
186struct MON_STRUCT
187{
188 HANDLE hCoordinatorThread;
189 DWORD dwCoordinatorThreadId;
190 BOOL fCoordinatorThreadMessageQueueInitialized;
191
192 // Invisible window for receiving network status & drive added/removal messages
193 HWND hwnd;
194 // Used by window procedure for sending request and waiting for response from waiter threads
195 // such as in event of a request to remove a device
196 MON_INTERNAL_TEMPORARY_WAIT internalWait;
197
198 // Callbacks
199 PFN_MONGENERAL vpfMonGeneral;
200 PFN_MONDRIVESTATUS vpfMonDriveStatus;
201 PFN_MONDIRECTORY vpfMonDirectory;
202 PFN_MONREGKEY vpfMonRegKey;
203
204 // Context for callbacks
205 LPVOID pvContext;
206
207 // Waiter thread array
208 MON_WAITER_INFO *rgWaiterThreads;
209 DWORD cWaiterThreads;
210};
211
212const int MON_HANDLE_BYTES = sizeof(MON_STRUCT);
213
214static DWORD WINAPI CoordinatorThread(
215 __in_bcount(sizeof(MON_STRUCT)) LPVOID pvContext
216 );
217// Initiates (or if *pHandle is non-null, continues) wait on the directory or subkey
218// if the directory or subkey doesn't exist, instead calls it on the first existing parent directory or subkey
219// writes to pRequest->dwPathHierarchyIndex with the array index that was waited on
220static HRESULT InitiateWait(
221 __inout MON_REQUEST *pRequest,
222 __inout HANDLE *pHandle
223 );
224static DWORD WINAPI WaiterThread(
225 __in_bcount(sizeof(MON_WAITER_CONTEXT)) LPVOID pvContext
226 );
227static void Notify(
228 __in HRESULT hr,
229 __in MON_WAITER_CONTEXT *pWaiterContext,
230 __in MON_REQUEST *pRequest
231 );
232static void MonRequestDestroy(
233 __in MON_REQUEST *pRequest
234 );
235static void MonAddMessageDestroy(
236 __in_opt MON_ADD_MESSAGE *pMessage
237 );
238static void MonRemoveMessageDestroy(
239 __in_opt MON_REMOVE_MESSAGE *pMessage
240 );
241static BOOL GetRecursiveFlag(
242 __in MON_REQUEST *pRequest,
243 __in DWORD dwIndex
244 );
245static HRESULT FindRequestIndex(
246 __in MON_WAITER_CONTEXT *pWaiterContext,
247 __in MON_REMOVE_MESSAGE *pMessage,
248 __out DWORD *pdwIndex
249 );
250static HRESULT RemoveRequest(
251 __inout MON_WAITER_CONTEXT *pWaiterContext,
252 __in DWORD dwRequestIndex
253 );
254static REGSAM GetRegKeyBitness(
255 __in MON_REQUEST *pRequest
256 );
257static HRESULT DuplicateRemoveMessage(
258 __in MON_REMOVE_MESSAGE *pMessage,
259 __out MON_REMOVE_MESSAGE **ppMessage
260 );
261static LRESULT CALLBACK MonWndProc(
262 __in HWND hWnd,
263 __in UINT uMsg,
264 __in WPARAM wParam,
265 __in LPARAM lParam
266 );
267static HRESULT CreateMonWindow(
268 __in MON_STRUCT *pm,
269 __out HWND *pHwnd
270 );
271// if *phMonitor is non-NULL, closes the old wait before re-starting the new wait
272static HRESULT WaitForNetworkChanges(
273 __inout HANDLE *phMonitor,
274 __in MON_STRUCT *pm
275 );
276static HRESULT UpdateWaitStatus(
277 __in HRESULT hrNewStatus,
278 __inout MON_WAITER_CONTEXT *pWaiterContext,
279 __in DWORD dwRequestIndex,
280 __out_opt DWORD *pdwNewRequestIndex
281 );
282
283extern "C" HRESULT DAPI MonCreate(
284 __out_bcount(MON_HANDLE_BYTES) MON_HANDLE *pHandle,
285 __in PFN_MONGENERAL vpfMonGeneral,
286 __in_opt PFN_MONDRIVESTATUS vpfMonDriveStatus,
287 __in_opt PFN_MONDIRECTORY vpfMonDirectory,
288 __in_opt PFN_MONREGKEY vpfMonRegKey,
289 __in_opt LPVOID pvContext
290 )
291{
292 HRESULT hr = S_OK;
293 DWORD dwRetries = MON_THREAD_INIT_RETRIES;
294
295 MonExitOnNull(pHandle, hr, E_INVALIDARG, "Pointer to handle not specified while creating monitor");
296
297 // Allocate the struct
298 *pHandle = static_cast<MON_HANDLE>(MemAlloc(sizeof(MON_STRUCT), TRUE));
299 MonExitOnNull(*pHandle, hr, E_OUTOFMEMORY, "Failed to allocate monitor object");
300
301 MON_STRUCT *pm = static_cast<MON_STRUCT *>(*pHandle);
302
303 pm->vpfMonGeneral = vpfMonGeneral;
304 pm->vpfMonDriveStatus = vpfMonDriveStatus;
305 pm->vpfMonDirectory = vpfMonDirectory;
306 pm->vpfMonRegKey = vpfMonRegKey;
307 pm->pvContext = pvContext;
308
309 pm->hCoordinatorThread = ::CreateThread(NULL, 0, CoordinatorThread, pm, 0, &pm->dwCoordinatorThreadId);
310 if (!pm->hCoordinatorThread)
311 {
312 MonExitWithLastError(hr, "Failed to create waiter thread.");
313 }
314
315 // Ensure the created thread initializes its message queue. It does this first thing, so if it doesn't within 10 seconds, there must be a huge problem.
316 while (!pm->fCoordinatorThreadMessageQueueInitialized && 0 < dwRetries)
317 {
318 ::Sleep(MON_THREAD_INIT_RETRY_PERIOD_IN_MS);
319 --dwRetries;
320 }
321
322 if (0 == dwRetries)
323 {
324 hr = E_UNEXPECTED;
325 MonExitOnFailure(hr, "Waiter thread apparently never initialized its message queue.");
326 }
327
328LExit:
329 return hr;
330}
331
332extern "C" HRESULT DAPI MonAddDirectory(
333 __in_bcount(MON_HANDLE_BYTES) MON_HANDLE handle,
334 __in_z LPCWSTR wzDirectory,
335 __in BOOL fRecursive,
336 __in DWORD dwSilencePeriodInMs,
337 __in_opt LPVOID pvDirectoryContext
338 )
339{
340 HRESULT hr = S_OK;
341 MON_STRUCT *pm = static_cast<MON_STRUCT *>(handle);
342 LPWSTR sczDirectory = NULL;
343 LPWSTR sczOriginalPathRequest = NULL;
344 MON_ADD_MESSAGE *pMessage = NULL;
345
346 hr = StrAllocString(&sczOriginalPathRequest, wzDirectory, 0);
347 MonExitOnFailure(hr, "Failed to convert directory string to UNC path");
348
349 hr = PathBackslashTerminate(&sczOriginalPathRequest);
350 MonExitOnFailure(hr, "Failed to ensure directory ends in backslash");
351
352 pMessage = reinterpret_cast<MON_ADD_MESSAGE *>(MemAlloc(sizeof(MON_ADD_MESSAGE), TRUE));
353 MonExitOnNull(pMessage, hr, E_OUTOFMEMORY, "Failed to allocate memory for message");
354
355 if (sczOriginalPathRequest[0] == L'\\' && sczOriginalPathRequest[1] == L'\\')
356 {
357 pMessage->request.fNetwork = TRUE;
358 }
359 else
360 {
361 hr = UncConvertFromMountedDrive(&sczDirectory, sczOriginalPathRequest);
362 if (SUCCEEDED(hr))
363 {
364 pMessage->request.fNetwork = TRUE;
365 }
366 }
367
368 if (NULL == sczDirectory)
369 {
370 // Likely not a mounted drive - just copy the request then
371 hr = S_OK;
372
373 hr = StrAllocString(&sczDirectory, sczOriginalPathRequest, 0);
374 MonExitOnFailure(hr, "Failed to copy original path request: %ls", sczOriginalPathRequest);
375 }
376
377 pMessage->handle = INVALID_HANDLE_VALUE;
378 pMessage->request.type = MON_DIRECTORY;
379 pMessage->request.fRecursive = fRecursive;
380 pMessage->request.dwMaxSilencePeriodInMs = dwSilencePeriodInMs;
381 pMessage->request.hwnd = pm->hwnd;
382 pMessage->request.pvContext = pvDirectoryContext;
383 pMessage->request.sczOriginalPathRequest = sczOriginalPathRequest;
384 sczOriginalPathRequest = NULL;
385
386 hr = PathGetHierarchyArray(sczDirectory, &pMessage->request.rgsczPathHierarchy, reinterpret_cast<LPUINT>(&pMessage->request.cPathHierarchy));
387 MonExitOnFailure(hr, "Failed to get hierarchy array for path %ls", sczDirectory);
388
389 if (0 < pMessage->request.cPathHierarchy)
390 {
391 pMessage->request.hrStatus = InitiateWait(&pMessage->request, &pMessage->handle);
392 if (!::PostThreadMessageW(pm->dwCoordinatorThreadId, MON_MESSAGE_ADD, reinterpret_cast<WPARAM>(pMessage), 0))
393 {
394 MonExitWithLastError(hr, "Failed to send message to worker thread to add directory wait for path %ls", sczDirectory);
395 }
396 pMessage = NULL;
397 }
398
399LExit:
400 ReleaseStr(sczDirectory);
401 ReleaseStr(sczOriginalPathRequest);
402 MonAddMessageDestroy(pMessage);
403
404 return hr;
405}
406
407extern "C" HRESULT DAPI MonAddRegKey(
408 __in_bcount(MON_HANDLE_BYTES) MON_HANDLE handle,
409 __in HKEY hkRoot,
410 __in_z LPCWSTR wzSubKey,
411 __in REG_KEY_BITNESS kbKeyBitness,
412 __in BOOL fRecursive,
413 __in DWORD dwSilencePeriodInMs,
414 __in_opt LPVOID pvRegKeyContext
415 )
416{
417 HRESULT hr = S_OK;
418 MON_STRUCT *pm = static_cast<MON_STRUCT *>(handle);
419 LPWSTR sczSubKey = NULL;
420 MON_ADD_MESSAGE *pMessage = NULL;
421
422 hr = StrAllocString(&sczSubKey, wzSubKey, 0);
423 MonExitOnFailure(hr, "Failed to copy subkey string");
424
425 hr = PathBackslashTerminate(&sczSubKey);
426 MonExitOnFailure(hr, "Failed to ensure subkey path ends in backslash");
427
428 pMessage = reinterpret_cast<MON_ADD_MESSAGE *>(MemAlloc(sizeof(MON_ADD_MESSAGE), TRUE));
429 MonExitOnNull(pMessage, hr, E_OUTOFMEMORY, "Failed to allocate memory for message");
430
431 pMessage->handle = ::CreateEventW(NULL, TRUE, FALSE, NULL);
432 MonExitOnNullWithLastError(pMessage->handle, hr, "Failed to create anonymous event for regkey monitor");
433
434 pMessage->request.type = MON_REGKEY;
435 pMessage->request.regkey.hkRoot = hkRoot;
436 pMessage->request.regkey.kbKeyBitness = kbKeyBitness;
437 pMessage->request.fRecursive = fRecursive;
438 pMessage->request.dwMaxSilencePeriodInMs = dwSilencePeriodInMs,
439 pMessage->request.hwnd = pm->hwnd;
440 pMessage->request.pvContext = pvRegKeyContext;
441
442 hr = PathGetHierarchyArray(sczSubKey, &pMessage->request.rgsczPathHierarchy, reinterpret_cast<LPUINT>(&pMessage->request.cPathHierarchy));
443 MonExitOnFailure(hr, "Failed to get hierarchy array for subkey %ls", sczSubKey);
444
445 if (0 < pMessage->request.cPathHierarchy)
446 {
447 pMessage->request.hrStatus = InitiateWait(&pMessage->request, &pMessage->handle);
448 MonExitOnFailure(hr, "Failed to initiate wait");
449
450 if (!::PostThreadMessageW(pm->dwCoordinatorThreadId, MON_MESSAGE_ADD, reinterpret_cast<WPARAM>(pMessage), 0))
451 {
452 MonExitWithLastError(hr, "Failed to send message to worker thread to add directory wait for regkey %ls", sczSubKey);
453 }
454 pMessage = NULL;
455 }
456
457LExit:
458 ReleaseStr(sczSubKey);
459 MonAddMessageDestroy(pMessage);
460
461 return hr;
462}
463
464extern "C" HRESULT DAPI MonRemoveDirectory(
465 __in_bcount(MON_HANDLE_BYTES) MON_HANDLE handle,
466 __in_z LPCWSTR wzDirectory,
467 __in BOOL fRecursive
468 )
469{
470 HRESULT hr = S_OK;
471 MON_STRUCT *pm = static_cast<MON_STRUCT *>(handle);
472 LPWSTR sczDirectory = NULL;
473 MON_REMOVE_MESSAGE *pMessage = NULL;
474
475 hr = StrAllocString(&sczDirectory, wzDirectory, 0);
476 MonExitOnFailure(hr, "Failed to copy directory string");
477
478 hr = PathBackslashTerminate(&sczDirectory);
479 MonExitOnFailure(hr, "Failed to ensure directory ends in backslash");
480
481 pMessage = reinterpret_cast<MON_REMOVE_MESSAGE *>(MemAlloc(sizeof(MON_REMOVE_MESSAGE), TRUE));
482 MonExitOnNull(pMessage, hr, E_OUTOFMEMORY, "Failed to allocate memory for message");
483
484 pMessage->type = MON_DIRECTORY;
485 pMessage->fRecursive = fRecursive;
486
487 hr = StrAllocString(&pMessage->directory.sczDirectory, sczDirectory, 0);
488 MonExitOnFailure(hr, "Failed to allocate copy of directory string");
489
490 if (!::PostThreadMessageW(pm->dwCoordinatorThreadId, MON_MESSAGE_REMOVE, reinterpret_cast<WPARAM>(pMessage), 0))
491 {
492 MonExitWithLastError(hr, "Failed to send message to worker thread to add directory wait for path %ls", sczDirectory);
493 }
494 pMessage = NULL;
495
496LExit:
497 MonRemoveMessageDestroy(pMessage);
498
499 return hr;
500}
501
502extern "C" HRESULT DAPI MonRemoveRegKey(
503 __in_bcount(MON_HANDLE_BYTES) MON_HANDLE handle,
504 __in HKEY hkRoot,
505 __in_z LPCWSTR wzSubKey,
506 __in REG_KEY_BITNESS kbKeyBitness,
507 __in BOOL fRecursive
508 )
509{
510 HRESULT hr = S_OK;
511 MON_STRUCT *pm = static_cast<MON_STRUCT *>(handle);
512 LPWSTR sczSubKey = NULL;
513 MON_REMOVE_MESSAGE *pMessage = NULL;
514
515 hr = StrAllocString(&sczSubKey, wzSubKey, 0);
516 MonExitOnFailure(hr, "Failed to copy subkey string");
517
518 hr = PathBackslashTerminate(&sczSubKey);
519 MonExitOnFailure(hr, "Failed to ensure subkey path ends in backslash");
520
521 pMessage = reinterpret_cast<MON_REMOVE_MESSAGE *>(MemAlloc(sizeof(MON_REMOVE_MESSAGE), TRUE));
522 MonExitOnNull(pMessage, hr, E_OUTOFMEMORY, "Failed to allocate memory for message");
523
524 pMessage->type = MON_REGKEY;
525 pMessage->regkey.hkRoot = hkRoot;
526 pMessage->regkey.kbKeyBitness = kbKeyBitness;
527 pMessage->fRecursive = fRecursive;
528
529 hr = StrAllocString(&pMessage->regkey.sczSubKey, sczSubKey, 0);
530 MonExitOnFailure(hr, "Failed to allocate copy of directory string");
531
532 if (!::PostThreadMessageW(pm->dwCoordinatorThreadId, MON_MESSAGE_REMOVE, reinterpret_cast<WPARAM>(pMessage), 0))
533 {
534 MonExitWithLastError(hr, "Failed to send message to worker thread to add directory wait for path %ls", sczSubKey);
535 }
536 pMessage = NULL;
537
538LExit:
539 ReleaseStr(sczSubKey);
540 MonRemoveMessageDestroy(pMessage);
541
542 return hr;
543}
544
545extern "C" void DAPI MonDestroy(
546 __in_bcount(MON_HANDLE_BYTES) MON_HANDLE handle
547 )
548{
549 HRESULT hr = S_OK;
550 DWORD er = ERROR_SUCCESS;
551 MON_STRUCT *pm = static_cast<MON_STRUCT *>(handle);
552
553 if (!::PostThreadMessageW(pm->dwCoordinatorThreadId, MON_MESSAGE_STOP, 0, 0))
554 {
555 er = ::GetLastError();
556 if (ERROR_INVALID_THREAD_ID == er)
557 {
558 // It already halted, or doesn't exist for some other reason, so let's just ignore it and clean up
559 er = ERROR_SUCCESS;
560 }
561 MonExitOnWin32Error(er, hr, "Failed to send message to background thread to halt");
562 }
563
564 if (pm->hCoordinatorThread)
565 {
566 ::WaitForSingleObject(pm->hCoordinatorThread, INFINITE);
567 ::CloseHandle(pm->hCoordinatorThread);
568 }
569
570LExit:
571 return;
572}
573
574static void MonRequestDestroy(
575 __in MON_REQUEST *pRequest
576 )
577{
578 if (NULL != pRequest)
579 {
580 if (MON_REGKEY == pRequest->type)
581 {
582 ReleaseRegKey(pRequest->regkey.hkSubKey);
583 }
584 else if (MON_DIRECTORY == pRequest->type && pRequest->hNotify)
585 {
586 UnregisterDeviceNotification(pRequest->hNotify);
587 pRequest->hNotify = NULL;
588 }
589 ReleaseStr(pRequest->sczOriginalPathRequest);
590 ReleaseStrArray(pRequest->rgsczPathHierarchy, pRequest->cPathHierarchy);
591 }
592}
593
594static void MonAddMessageDestroy(
595 __in_opt MON_ADD_MESSAGE *pMessage
596 )
597{
598 if (pMessage)
599 {
600 MonRequestDestroy(&pMessage->request);
601 if (MON_DIRECTORY == pMessage->request.type && INVALID_HANDLE_VALUE != pMessage->handle)
602 {
603 ::FindCloseChangeNotification(pMessage->handle);
604 }
605 else if (MON_REGKEY == pMessage->request.type)
606 {
607 ReleaseHandle(pMessage->handle);
608 }
609
610 ReleaseMem(pMessage);
611 }
612}
613
614static void MonRemoveMessageDestroy(
615 __in_opt MON_REMOVE_MESSAGE *pMessage
616 )
617{
618 if (pMessage)
619 {
620 switch (pMessage->type)
621 {
622 case MON_DIRECTORY:
623 ReleaseStr(pMessage->directory.sczDirectory);
624 break;
625 case MON_REGKEY:
626 ReleaseStr(pMessage->regkey.sczSubKey);
627 break;
628 default:
629 Assert(false);
630 }
631
632 ReleaseMem(pMessage);
633 }
634}
635
636static DWORD WINAPI CoordinatorThread(
637 __in_bcount(sizeof(MON_STRUCT)) LPVOID pvContext
638 )
639{
640 HRESULT hr = S_OK;
641 MSG msg = { };
642 DWORD dwThreadIndex = DWORD_MAX;
643 DWORD dwRetries;
644 DWORD dwFailingNetworkWaits = 0;
645 MON_WAITER_CONTEXT *pWaiterContext = NULL;
646 MON_REMOVE_MESSAGE *pRemoveMessage = NULL;
647 MON_REMOVE_MESSAGE *pTempRemoveMessage = NULL;
648 MON_STRUCT *pm = reinterpret_cast<MON_STRUCT*>(pvContext);
649 WSADATA wsaData = { };
650 HANDLE hMonitor = NULL;
651 BOOL fRet = FALSE;
652 UINT_PTR uTimerSuccessfulNetworkRetry = 0;
653 UINT_PTR uTimerFailedNetworkRetry = 0;
654
655 // Ensure the thread has a message queue
656 ::PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
657 pm->fCoordinatorThreadMessageQueueInitialized = TRUE;
658
659 hr = CreateMonWindow(pm, &pm->hwnd);
660 MonExitOnFailure(hr, "Failed to create window for status update thread");
661
662 ::WSAStartup(MAKEWORD(2, 2), &wsaData);
663
664 hr = WaitForNetworkChanges(&hMonitor, pm);
665 MonExitOnFailure(hr, "Failed to wait for network changes");
666
667 uTimerSuccessfulNetworkRetry = ::SetTimer(NULL, 1, MON_THREAD_NETWORK_SUCCESSFUL_RETRY_IN_MS, NULL);
668 if (0 == uTimerSuccessfulNetworkRetry)
669 {
670 MonExitWithLastError(hr, "Failed to set timer for network successful retry");
671 }
672
673 while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0)))
674 {
675 if (-1 == fRet)
676 {
677 hr = E_UNEXPECTED;
678 MonExitOnRootFailure(hr, "Unexpected return value from message pump.");
679 }
680 else
681 {
682 switch (msg.message)
683 {
684 case MON_MESSAGE_ADD:
685 dwThreadIndex = DWORD_MAX;
686 for (DWORD i = 0; i < pm->cWaiterThreads; ++i)
687 {
688 if (pm->rgWaiterThreads[i].cMonitorCount < MON_MAX_MONITORS_PER_THREAD)
689 {
690 dwThreadIndex = i;
691 break;
692 }
693 }
694
695 if (dwThreadIndex < pm->cWaiterThreads)
696 {
697 pWaiterContext = pm->rgWaiterThreads[dwThreadIndex].pWaiterContext;
698 }
699 else
700 {
701 hr = MemEnsureArraySize(reinterpret_cast<void **>(&pm->rgWaiterThreads), pm->cWaiterThreads + 1, sizeof(MON_WAITER_INFO), MON_THREAD_GROWTH);
702 MonExitOnFailure(hr, "Failed to grow waiter thread array size");
703 ++pm->cWaiterThreads;
704
705 dwThreadIndex = pm->cWaiterThreads - 1;
706 pm->rgWaiterThreads[dwThreadIndex].pWaiterContext = reinterpret_cast<MON_WAITER_CONTEXT*>(MemAlloc(sizeof(MON_WAITER_CONTEXT), TRUE));
707 MonExitOnNull(pm->rgWaiterThreads[dwThreadIndex].pWaiterContext, hr, E_OUTOFMEMORY, "Failed to allocate waiter context struct");
708 pWaiterContext = pm->rgWaiterThreads[dwThreadIndex].pWaiterContext;
709 pWaiterContext->dwCoordinatorThreadId = ::GetCurrentThreadId();
710 pWaiterContext->vpfMonGeneral = pm->vpfMonGeneral;
711 pWaiterContext->vpfMonDirectory = pm->vpfMonDirectory;
712 pWaiterContext->vpfMonRegKey = pm->vpfMonRegKey;
713 pWaiterContext->pvContext = pm->pvContext;
714
715 hr = MemEnsureArraySize(reinterpret_cast<void **>(&pWaiterContext->rgHandles), MON_MAX_MONITORS_PER_THREAD + 1, sizeof(HANDLE), 0);
716 MonExitOnFailure(hr, "Failed to allocate first handle");
717 pWaiterContext->cHandles = 1;
718
719 pWaiterContext->rgHandles[0] = ::CreateEventW(NULL, FALSE, FALSE, NULL);
720 MonExitOnNullWithLastError(pWaiterContext->rgHandles[0], hr, "Failed to create general event");
721
722 pWaiterContext->hWaiterThread = ::CreateThread(NULL, 0, WaiterThread, pWaiterContext, 0, &pWaiterContext->dwWaiterThreadId);
723 if (!pWaiterContext->hWaiterThread)
724 {
725 MonExitWithLastError(hr, "Failed to create waiter thread.");
726 }
727
728 dwRetries = MON_THREAD_INIT_RETRIES;
729 while (!pWaiterContext->fWaiterThreadMessageQueueInitialized && 0 < dwRetries)
730 {
731 ::Sleep(MON_THREAD_INIT_RETRY_PERIOD_IN_MS);
732 --dwRetries;
733 }
734
735 if (0 == dwRetries)
736 {
737 hr = E_UNEXPECTED;
738 MonExitOnFailure(hr, "Waiter thread apparently never initialized its message queue.");
739 }
740 }
741
742 ++pm->rgWaiterThreads[dwThreadIndex].cMonitorCount;
743 if (!::PostThreadMessageW(pWaiterContext->dwWaiterThreadId, MON_MESSAGE_ADD, msg.wParam, 0))
744 {
745 MonExitWithLastError(hr, "Failed to send message to waiter thread to add monitor");
746 }
747
748 if (!::SetEvent(pWaiterContext->rgHandles[0]))
749 {
750 MonExitWithLastError(hr, "Failed to set event to notify waiter thread of incoming message");
751 }
752 break;
753
754 case MON_MESSAGE_REMOVE:
755 // Send remove to all waiter threads. They'll ignore it if they don't have that monitor.
756 // If they do have that monitor, they'll remove it from their list, and tell coordinator they have another
757 // empty slot via MON_MESSAGE_REMOVED message
758 for (DWORD i = 0; i < pm->cWaiterThreads; ++i)
759 {
760 pWaiterContext = pm->rgWaiterThreads[i].pWaiterContext;
761 pRemoveMessage = reinterpret_cast<MON_REMOVE_MESSAGE *>(msg.wParam);
762
763 hr = DuplicateRemoveMessage(pRemoveMessage, &pTempRemoveMessage);
764 MonExitOnFailure(hr, "Failed to duplicate remove message");
765
766 if (!::PostThreadMessageW(pWaiterContext->dwWaiterThreadId, MON_MESSAGE_REMOVE, reinterpret_cast<WPARAM>(pTempRemoveMessage), msg.lParam))
767 {
768 MonExitWithLastError(hr, "Failed to send message to waiter thread to add monitor");
769 }
770 pTempRemoveMessage = NULL;
771
772 if (!::SetEvent(pWaiterContext->rgHandles[0]))
773 {
774 MonExitWithLastError(hr, "Failed to set event to notify waiter thread of incoming remove message");
775 }
776 }
777 MonRemoveMessageDestroy(pRemoveMessage);
778 pRemoveMessage = NULL;
779 break;
780
781 case MON_MESSAGE_REMOVED:
782 for (DWORD i = 0; i < pm->cWaiterThreads; ++i)
783 {
784 if (pm->rgWaiterThreads[i].pWaiterContext->dwWaiterThreadId == static_cast<DWORD>(msg.wParam))
785 {
786 Assert(pm->rgWaiterThreads[i].cMonitorCount > 0);
787 --pm->rgWaiterThreads[i].cMonitorCount;
788 if (0 == pm->rgWaiterThreads[i].cMonitorCount)
789 {
790 if (!::PostThreadMessageW(pm->rgWaiterThreads[i].pWaiterContext->dwWaiterThreadId, MON_MESSAGE_STOP, msg.wParam, msg.lParam))
791 {
792 MonExitWithLastError(hr, "Failed to send message to waiter thread to stop");
793 }
794 MemRemoveFromArray(reinterpret_cast<LPVOID>(pm->rgWaiterThreads), i, 1, pm->cWaiterThreads, sizeof(MON_WAITER_INFO), TRUE);
795 --pm->cWaiterThreads;
796 --i; // reprocess this index in the for loop, which will now contain the item after the one we removed
797 }
798 }
799 }
800 break;
801
802 case MON_MESSAGE_NETWORK_WAIT_FAILED:
803 if (0 == dwFailingNetworkWaits)
804 {
805 uTimerFailedNetworkRetry = ::SetTimer(NULL, uTimerSuccessfulNetworkRetry + 1, MON_THREAD_NETWORK_FAIL_RETRY_IN_MS, NULL);
806 if (0 == uTimerFailedNetworkRetry)
807 {
808 MonExitWithLastError(hr, "Failed to set timer for network fail retry");
809 }
810 }
811 ++dwFailingNetworkWaits;
812 break;
813
814 case MON_MESSAGE_NETWORK_WAIT_SUCCEEDED:
815 --dwFailingNetworkWaits;
816 if (0 == dwFailingNetworkWaits)
817 {
818 if (!::KillTimer(NULL, uTimerFailedNetworkRetry))
819 {
820 MonExitWithLastError(hr, "Failed to kill timer for network fail retry");
821 }
822 uTimerFailedNetworkRetry = 0;
823 }
824 break;
825
826 case MON_MESSAGE_NETWORK_STATUS_UPDATE:
827 hr = WaitForNetworkChanges(&hMonitor, pm);
828 MonExitOnFailure(hr, "Failed to re-wait for network changes");
829
830 // Propagate any network status update messages to all waiter threads
831 for (DWORD i = 0; i < pm->cWaiterThreads; ++i)
832 {
833 pWaiterContext = pm->rgWaiterThreads[i].pWaiterContext;
834
835 if (!::PostThreadMessageW(pWaiterContext->dwWaiterThreadId, MON_MESSAGE_NETWORK_STATUS_UPDATE, 0, 0))
836 {
837 MonExitWithLastError(hr, "Failed to send message to waiter thread to notify of network status update");
838 }
839
840 if (!::SetEvent(pWaiterContext->rgHandles[0]))
841 {
842 MonExitWithLastError(hr, "Failed to set event to notify waiter thread of incoming network status update message");
843 }
844 }
845 break;
846
847 case WM_TIMER:
848 // Timer means some network wait is failing, and we need to retry every so often in case a remote server goes back up
849 for (DWORD i = 0; i < pm->cWaiterThreads; ++i)
850 {
851 pWaiterContext = pm->rgWaiterThreads[i].pWaiterContext;
852
853 if (!::PostThreadMessageW(pWaiterContext->dwWaiterThreadId, msg.wParam == uTimerFailedNetworkRetry ? MON_MESSAGE_NETWORK_RETRY_FAILED_NETWORK_WAITS : MON_MESSAGE_NETWORK_RETRY_SUCCESSFUL_NETWORK_WAITS, 0, 0))
854 {
855 MonExitWithLastError(hr, "Failed to send message to waiter thread to notify of network status update");
856 }
857
858 if (!::SetEvent(pWaiterContext->rgHandles[0]))
859 {
860 MonExitWithLastError(hr, "Failed to set event to notify waiter thread of incoming network status update message");
861 }
862 }
863 break;
864
865 case MON_MESSAGE_DRIVE_STATUS_UPDATE:
866 // If user requested to be notified of drive status updates, notify!
867 if (pm->vpfMonDriveStatus)
868 {
869 pm->vpfMonDriveStatus(static_cast<WCHAR>(msg.wParam), static_cast<BOOL>(msg.lParam), pm->pvContext);
870 }
871
872 // Propagate any drive status update messages to all waiter threads
873 for (DWORD i = 0; i < pm->cWaiterThreads; ++i)
874 {
875 pWaiterContext = pm->rgWaiterThreads[i].pWaiterContext;
876
877 if (!::PostThreadMessageW(pWaiterContext->dwWaiterThreadId, MON_MESSAGE_DRIVE_STATUS_UPDATE, msg.wParam, msg.lParam))
878 {
879 MonExitWithLastError(hr, "Failed to send message to waiter thread to notify of drive status update");
880 }
881
882 if (!::SetEvent(pWaiterContext->rgHandles[0]))
883 {
884 MonExitWithLastError(hr, "Failed to set event to notify waiter thread of incoming drive status update message");
885 }
886 }
887 break;
888
889 case MON_MESSAGE_STOP:
890 ExitFunction1(hr = static_cast<HRESULT>(msg.wParam));
891
892 default:
893 // This thread owns a window, so this handles all the other random messages we get
894 ::TranslateMessage(&msg);
895 ::DispatchMessageW(&msg);
896 break;
897 }
898 }
899 }
900
901LExit:
902 if (uTimerFailedNetworkRetry)
903 {
904 fRet = ::KillTimer(NULL, uTimerFailedNetworkRetry);
905 }
906 if (uTimerSuccessfulNetworkRetry)
907 {
908 fRet = ::KillTimer(NULL, uTimerSuccessfulNetworkRetry);
909 }
910
911 if (pm->hwnd)
912 {
913 ::CloseWindow(pm->hwnd);
914 }
915
916 // Tell all waiter threads to shutdown
917 for (DWORD i = 0; i < pm->cWaiterThreads; ++i)
918 {
919 pWaiterContext = pm->rgWaiterThreads[i].pWaiterContext;
920 if (NULL != pWaiterContext->rgHandles[0])
921 {
922 if (!::PostThreadMessageW(pWaiterContext->dwWaiterThreadId, MON_MESSAGE_STOP, msg.wParam, msg.lParam))
923 {
924 TraceError(HRESULT_FROM_WIN32(::GetLastError()), "Failed to send message to waiter thread to stop");
925 }
926
927 if (!::SetEvent(pWaiterContext->rgHandles[0]))
928 {
929 TraceError(HRESULT_FROM_WIN32(::GetLastError()), "Failed to set event to notify waiter thread of incoming message");
930 }
931 }
932 }
933
934 if (hMonitor != NULL)
935 {
936 ::WSALookupServiceEnd(hMonitor);
937 }
938
939 // Now confirm they're actually shut down before returning
940 for (DWORD i = 0; i < pm->cWaiterThreads; ++i)
941 {
942 pWaiterContext = pm->rgWaiterThreads[i].pWaiterContext;
943 if (NULL != pWaiterContext->hWaiterThread)
944 {
945 ::WaitForSingleObject(pWaiterContext->hWaiterThread, INFINITE);
946 ::CloseHandle(pWaiterContext->hWaiterThread);
947 }
948
949 // Waiter thread can't release these, because coordinator thread uses it to try communicating with waiter thread
950 ReleaseHandle(pWaiterContext->rgHandles[0]);
951 ReleaseMem(pWaiterContext->rgHandles);
952
953 ReleaseMem(pWaiterContext);
954 }
955
956 if (FAILED(hr))
957 {
958 // If coordinator thread fails, notify general callback of an error
959 Assert(pm->vpfMonGeneral);
960 pm->vpfMonGeneral(hr, pm->pvContext);
961 }
962 MonRemoveMessageDestroy(pRemoveMessage);
963 MonRemoveMessageDestroy(pTempRemoveMessage);
964
965 ::WSACleanup();
966
967 return hr;
968}
969
970static HRESULT InitiateWait(
971 __inout MON_REQUEST *pRequest,
972 __inout HANDLE *pHandle
973 )
974{
975 HRESULT hr = S_OK;
976 HRESULT hrTemp = S_OK;
977 DEV_BROADCAST_HANDLE dev = { };
978 BOOL fRedo = FALSE;
979 BOOL fHandleFound;
980 DWORD er = ERROR_SUCCESS;
981 DWORD dwIndex = 0;
982 HKEY hk = NULL;
983 HANDLE hTemp = INVALID_HANDLE_VALUE;
984
985 if (pRequest->hNotify)
986 {
987 UnregisterDeviceNotification(pRequest->hNotify);
988 pRequest->hNotify = NULL;
989 }
990
991 do
992 {
993 fRedo = FALSE;
994 fHandleFound = FALSE;
995
996 for (DWORD i = 0; i < pRequest->cPathHierarchy && !fHandleFound; ++i)
997 {
998 dwIndex = pRequest->cPathHierarchy - i - 1;
999 switch (pRequest->type)
1000 {
1001 case MON_DIRECTORY:
1002 if (INVALID_HANDLE_VALUE != *pHandle)
1003 {
1004 ::FindCloseChangeNotification(*pHandle);
1005 *pHandle = INVALID_HANDLE_VALUE;
1006 }
1007
1008 *pHandle = ::FindFirstChangeNotificationW(pRequest->rgsczPathHierarchy[dwIndex], GetRecursiveFlag(pRequest, dwIndex), FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SECURITY);
1009 if (INVALID_HANDLE_VALUE == *pHandle)
1010 {
1011 hr = HRESULT_FROM_WIN32(::GetLastError());
1012 if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr || E_ACCESSDENIED == hr)
1013 {
1014 continue;
1015 }
1016 MonExitOnWin32Error(er, hr, "Failed to wait on path %ls", pRequest->rgsczPathHierarchy[dwIndex]);
1017 }
1018 else
1019 {
1020 fHandleFound = TRUE;
1021 hr = S_OK;
1022 }
1023 break;
1024 case MON_REGKEY:
1025 ReleaseRegKey(pRequest->regkey.hkSubKey);
1026 hr = RegOpen(pRequest->regkey.hkRoot, pRequest->rgsczPathHierarchy[dwIndex], KEY_NOTIFY | GetRegKeyBitness(pRequest), &pRequest->regkey.hkSubKey);
1027 if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr)
1028 {
1029 continue;
1030 }
1031 MonExitOnFailure(hr, "Failed to open regkey %ls", pRequest->rgsczPathHierarchy[dwIndex]);
1032
1033 er = ::RegNotifyChangeKeyValue(pRequest->regkey.hkSubKey, GetRecursiveFlag(pRequest, dwIndex), REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_SECURITY, *pHandle, TRUE);
1034 ReleaseRegKey(hk);
1035 hr = HRESULT_FROM_WIN32(er);
1036 if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr || HRESULT_FROM_WIN32(ERROR_KEY_DELETED) == hr)
1037 {
1038 continue;
1039 }
1040 else
1041 {
1042 MonExitOnWin32Error(er, hr, "Failed to wait on subkey %ls", pRequest->rgsczPathHierarchy[dwIndex]);
1043
1044 fHandleFound = TRUE;
1045 }
1046
1047 break;
1048 default:
1049 return E_INVALIDARG;
1050 }
1051 }
1052
1053 pRequest->dwPathHierarchyIndex = dwIndex;
1054
1055 // If we're monitoring a parent instead of the real path because the real path didn't exist, double-check the child hasn't been created since.
1056 // If it has, restart the whole loop
1057 if (dwIndex < pRequest->cPathHierarchy - 1)
1058 {
1059 switch (pRequest->type)
1060 {
1061 case MON_DIRECTORY:
1062 hTemp = ::FindFirstChangeNotificationW(pRequest->rgsczPathHierarchy[dwIndex + 1], GetRecursiveFlag(pRequest, dwIndex + 1), FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SECURITY);
1063 if (INVALID_HANDLE_VALUE != hTemp)
1064 {
1065 ::FindCloseChangeNotification(hTemp);
1066 fRedo = TRUE;
1067 }
1068 break;
1069 case MON_REGKEY:
1070 hrTemp = RegOpen(pRequest->regkey.hkRoot, pRequest->rgsczPathHierarchy[dwIndex + 1], KEY_NOTIFY | GetRegKeyBitness(pRequest), &hk);
1071 ReleaseRegKey(hk);
1072 fRedo = SUCCEEDED(hrTemp);
1073 break;
1074 default:
1075 Assert(false);
1076 }
1077 }
1078 } while (fRedo);
1079
1080 MonExitOnFailure(hr, "Didn't get a successful wait after looping through all available options %ls", pRequest->rgsczPathHierarchy[pRequest->cPathHierarchy - 1]);
1081
1082 if (MON_DIRECTORY == pRequest->type)
1083 {
1084 dev.dbch_size = sizeof(dev);
1085 dev.dbch_devicetype = DBT_DEVTYP_HANDLE;
1086 dev.dbch_handle = *pHandle;
1087 // Ignore failure on this - some drives by design don't support it (like network paths), and the worst that can happen is a
1088 // removable device will be left in use so user cannot gracefully remove
1089 pRequest->hNotify = RegisterDeviceNotification(pRequest->hwnd, &dev, DEVICE_NOTIFY_WINDOW_HANDLE);
1090 }
1091
1092LExit:
1093 ReleaseRegKey(hk);
1094
1095 return hr;
1096}
1097
1098static DWORD WINAPI WaiterThread(
1099 __in_bcount(sizeof(MON_WAITER_CONTEXT)) LPVOID pvContext
1100 )
1101{
1102 HRESULT hr = S_OK;
1103 HRESULT hrTemp = S_OK;
1104 DWORD dwRet = 0;
1105 BOOL fAgain = FALSE;
1106 BOOL fContinue = TRUE;
1107 BOOL fNotify = FALSE;
1108 BOOL fRet = FALSE;
1109 MSG msg = { };
1110 MON_ADD_MESSAGE *pAddMessage = NULL;
1111 MON_REMOVE_MESSAGE *pRemoveMessage = NULL;
1112 MON_WAITER_CONTEXT *pWaiterContext = reinterpret_cast<MON_WAITER_CONTEXT *>(pvContext);
1113 DWORD dwRequestIndex;
1114 DWORD dwNewRequestIndex;
1115 // If we have one or more requests pending notification, this is the period we intend to wait for multiple objects (shortest amount of time to next potential notify)
1116 DWORD dwWait = 0;
1117 DWORD uCurrentTime = 0;
1118 DWORD uLastTimeInMs = ::GetTickCount();
1119 DWORD uDeltaInMs = 0;
1120 DWORD cRequestsPendingBeforeLoop = 0;
1121 LPWSTR sczDirectory = NULL;
1122 bool rgfProcessedIndex[MON_MAX_MONITORS_PER_THREAD + 1] = { };
1123 MON_INTERNAL_TEMPORARY_WAIT * pInternalWait = NULL;
1124
1125 // Ensure the thread has a message queue
1126 ::PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
1127 pWaiterContext->fWaiterThreadMessageQueueInitialized = TRUE;
1128
1129 do
1130 {
1131 dwRet = ::WaitForMultipleObjects(pWaiterContext->cHandles - pWaiterContext->cRequestsFailing, pWaiterContext->rgHandles, FALSE, pWaiterContext->cRequestsPending > 0 ? dwWait : INFINITE);
1132
1133 uCurrentTime = ::GetTickCount();
1134 uDeltaInMs = uCurrentTime - uLastTimeInMs;
1135 uLastTimeInMs = uCurrentTime;
1136
1137 if (WAIT_OBJECT_0 == dwRet)
1138 {
1139 do
1140 {
1141 fRet = ::PeekMessage(&msg, reinterpret_cast<HWND>(-1), 0, 0, PM_REMOVE);
1142 fAgain = fRet;
1143 if (fRet)
1144 {
1145 switch (msg.message)
1146 {
1147 case MON_MESSAGE_ADD:
1148 pAddMessage = reinterpret_cast<MON_ADD_MESSAGE *>(msg.wParam);
1149
1150 // Don't just blindly put it at the end of the array - it must be before any failing requests
1151 // for WaitForMultipleObjects() to succeed
1152 dwNewRequestIndex = pWaiterContext->cRequests - pWaiterContext->cRequestsFailing;
1153 if (FAILED(pAddMessage->request.hrStatus))
1154 {
1155 ++pWaiterContext->cRequestsFailing;
1156 }
1157
1158 hr = MemInsertIntoArray(reinterpret_cast<void **>(&pWaiterContext->rgHandles), dwNewRequestIndex + 1, 1, pWaiterContext->cHandles, sizeof(HANDLE), MON_ARRAY_GROWTH);
1159 MonExitOnFailure(hr, "Failed to insert additional handle");
1160 ++pWaiterContext->cHandles;
1161
1162 // Ugh - directory types start with INVALID_HANDLE_VALUE instead of NULL
1163 if (MON_DIRECTORY == pAddMessage->request.type)
1164 {
1165 pWaiterContext->rgHandles[dwNewRequestIndex + 1] = INVALID_HANDLE_VALUE;
1166 }
1167
1168 hr = MemInsertIntoArray(reinterpret_cast<void **>(&pWaiterContext->rgRequests), dwNewRequestIndex, 1, pWaiterContext->cRequests, sizeof(MON_REQUEST), MON_ARRAY_GROWTH);
1169 MonExitOnFailure(hr, "Failed to insert additional request struct");
1170 ++pWaiterContext->cRequests;
1171
1172 pWaiterContext->rgRequests[dwNewRequestIndex] = pAddMessage->request;
1173 pWaiterContext->rgHandles[dwNewRequestIndex + 1] = pAddMessage->handle;
1174
1175 ReleaseNullMem(pAddMessage);
1176 break;
1177
1178 case MON_MESSAGE_REMOVE:
1179 pRemoveMessage = reinterpret_cast<MON_REMOVE_MESSAGE *>(msg.wParam);
1180
1181 // Find the request to remove
1182 hr = FindRequestIndex(pWaiterContext, pRemoveMessage, &dwRequestIndex);
1183 if (E_NOTFOUND == hr)
1184 {
1185 // Coordinator sends removes blindly to all waiter threads, so maybe this one wasn't intended for us
1186 hr = S_OK;
1187 }
1188 else
1189 {
1190 MonExitOnFailure(hr, "Failed to find request index for remove message");
1191
1192 hr = RemoveRequest(pWaiterContext, dwRequestIndex);
1193 MonExitOnFailure(hr, "Failed to remove request after request from coordinator thread.");
1194 }
1195
1196 MonRemoveMessageDestroy(pRemoveMessage);
1197 pRemoveMessage = NULL;
1198 break;
1199
1200 case MON_MESSAGE_NETWORK_RETRY_FAILED_NETWORK_WAITS:
1201 if (::PeekMessage(&msg, NULL, MON_MESSAGE_NETWORK_RETRY_FAILED_NETWORK_WAITS, MON_MESSAGE_NETWORK_RETRY_FAILED_NETWORK_WAITS, PM_NOREMOVE))
1202 {
1203 // If there is another a pending retry failed wait message, skip this one
1204 continue;
1205 }
1206
1207 ZeroMemory(rgfProcessedIndex, sizeof(rgfProcessedIndex));
1208 for (DWORD i = 0; i < pWaiterContext->cRequests; ++i)
1209 {
1210 if (rgfProcessedIndex[i])
1211 {
1212 // if we already processed this item due to UpdateWaitStatus swapping array indices, then skip it
1213 continue;
1214 }
1215
1216 if (MON_DIRECTORY == pWaiterContext->rgRequests[i].type && pWaiterContext->rgRequests[i].fNetwork && FAILED(pWaiterContext->rgRequests[i].hrStatus))
1217 {
1218 // This is not a failure, just record this in the request's status
1219 hrTemp = InitiateWait(pWaiterContext->rgRequests + i, pWaiterContext->rgHandles + i + 1);
1220
1221 hr = UpdateWaitStatus(hrTemp, pWaiterContext, i, &dwNewRequestIndex);
1222 MonExitOnFailure(hr, "Failed to update wait status");
1223 hrTemp = S_OK;
1224
1225 if (dwNewRequestIndex != i)
1226 {
1227 // If this request was moved to the end of the list, reprocess this index and mark the new index for skipping
1228 rgfProcessedIndex[dwNewRequestIndex] = true;
1229 --i;
1230 }
1231 }
1232 }
1233 break;
1234
1235 case MON_MESSAGE_NETWORK_RETRY_SUCCESSFUL_NETWORK_WAITS:
1236 if (::PeekMessage(&msg, NULL, MON_MESSAGE_NETWORK_RETRY_SUCCESSFUL_NETWORK_WAITS, MON_MESSAGE_NETWORK_RETRY_SUCCESSFUL_NETWORK_WAITS, PM_NOREMOVE))
1237 {
1238 // If there is another a pending retry successful wait message, skip this one
1239 continue;
1240 }
1241
1242 ZeroMemory(rgfProcessedIndex, sizeof(rgfProcessedIndex));
1243 for (DWORD i = 0; i < pWaiterContext->cRequests; ++i)
1244 {
1245 if (rgfProcessedIndex[i])
1246 {
1247 // if we already processed this item due to UpdateWaitStatus swapping array indices, then skip it
1248 continue;
1249 }
1250
1251 if (MON_DIRECTORY == pWaiterContext->rgRequests[i].type && pWaiterContext->rgRequests[i].fNetwork && SUCCEEDED(pWaiterContext->rgRequests[i].hrStatus))
1252 {
1253 // This is not a failure, just record this in the request's status
1254 hrTemp = InitiateWait(pWaiterContext->rgRequests + i, pWaiterContext->rgHandles + i + 1);
1255
1256 hr = UpdateWaitStatus(hrTemp, pWaiterContext, i, &dwNewRequestIndex);
1257 MonExitOnFailure(hr, "Failed to update wait status");
1258 hrTemp = S_OK;
1259
1260 if (dwNewRequestIndex != i)
1261 {
1262 // If this request was moved to the end of the list, reprocess this index and mark the new index for skipping
1263 rgfProcessedIndex[dwNewRequestIndex] = true;
1264 --i;
1265 }
1266 }
1267 }
1268 break;
1269
1270 case MON_MESSAGE_NETWORK_STATUS_UPDATE:
1271 if (::PeekMessage(&msg, NULL, MON_MESSAGE_NETWORK_STATUS_UPDATE, MON_MESSAGE_NETWORK_STATUS_UPDATE, PM_NOREMOVE))
1272 {
1273 // If there is another a pending network status update message, skip this one
1274 continue;
1275 }
1276
1277 ZeroMemory(rgfProcessedIndex, sizeof(rgfProcessedIndex));
1278 for (DWORD i = 0; i < pWaiterContext->cRequests; ++i)
1279 {
1280 if (rgfProcessedIndex[i])
1281 {
1282 // if we already processed this item due to UpdateWaitStatus swapping array indices, then skip it
1283 continue;
1284 }
1285
1286 if (MON_DIRECTORY == pWaiterContext->rgRequests[i].type && pWaiterContext->rgRequests[i].fNetwork)
1287 {
1288 // Failures here get recorded in the request's status
1289 hrTemp = InitiateWait(pWaiterContext->rgRequests + i, pWaiterContext->rgHandles + i + 1);
1290
1291 hr = UpdateWaitStatus(hrTemp, pWaiterContext, i, &dwNewRequestIndex);
1292 MonExitOnFailure(hr, "Failed to update wait status");
1293 hrTemp = S_OK;
1294
1295 if (dwNewRequestIndex != i)
1296 {
1297 // If this request was moved to the end of the list, reprocess this index and mark the new index for skipping
1298 rgfProcessedIndex[dwNewRequestIndex] = true;
1299 --i;
1300 }
1301 }
1302 }
1303 break;
1304
1305 case MON_MESSAGE_DRIVE_STATUS_UPDATE:
1306 ZeroMemory(rgfProcessedIndex, sizeof(rgfProcessedIndex));
1307 for (DWORD i = 0; i < pWaiterContext->cRequests; ++i)
1308 {
1309 if (rgfProcessedIndex[i])
1310 {
1311 // if we already processed this item due to UpdateWaitStatus swapping array indices, then skip it
1312 continue;
1313 }
1314
1315 if (MON_DIRECTORY == pWaiterContext->rgRequests[i].type && pWaiterContext->rgRequests[i].sczOriginalPathRequest[0] == static_cast<WCHAR>(msg.wParam))
1316 {
1317 // Failures here get recorded in the request's status
1318 if (static_cast<BOOL>(msg.lParam))
1319 {
1320 hrTemp = InitiateWait(pWaiterContext->rgRequests + i, pWaiterContext->rgHandles + i + 1);
1321 }
1322 else
1323 {
1324 // If the message says the drive is disconnected, don't even try to wait, just mark it as gone
1325 hrTemp = E_PATHNOTFOUND;
1326 }
1327
1328 hr = UpdateWaitStatus(hrTemp, pWaiterContext, i, &dwNewRequestIndex);
1329 MonExitOnFailure(hr, "Failed to update wait status");
1330 hrTemp = S_OK;
1331
1332 if (dwNewRequestIndex != i)
1333 {
1334 // If this request was moved to the end of the list, reprocess this index and mark the new index for skipping
1335 rgfProcessedIndex[dwNewRequestIndex] = true;
1336 --i;
1337 }
1338 }
1339 }
1340 break;
1341
1342 case MON_MESSAGE_DRIVE_QUERY_REMOVE:
1343 pInternalWait = reinterpret_cast<MON_INTERNAL_TEMPORARY_WAIT *>(msg.wParam);
1344 // Only do any work if message is not yet out of date
1345 // While it could become out of date while doing this processing, sending thread will check response to guard against this
1346 if (pInternalWait->dwSendIteration == static_cast<DWORD>(msg.lParam))
1347 {
1348 for (DWORD i = 0; i < pWaiterContext->cRequests; ++i)
1349 {
1350 if (MON_DIRECTORY == pWaiterContext->rgRequests[i].type && pWaiterContext->rgHandles[i + 1] == reinterpret_cast<HANDLE>(pInternalWait->pvContext))
1351 {
1352 // Release handles ASAP so the remove request will succeed
1353 if (pWaiterContext->rgRequests[i].hNotify)
1354 {
1355 UnregisterDeviceNotification(pWaiterContext->rgRequests[i].hNotify);
1356 pWaiterContext->rgRequests[i].hNotify = NULL;
1357 }
1358 ::FindCloseChangeNotification(pWaiterContext->rgHandles[i + 1]);
1359 pWaiterContext->rgHandles[i + 1] = INVALID_HANDLE_VALUE;
1360
1361 // Reply to unblock our reply to the remove request
1362 pInternalWait->dwReceiveIteration = static_cast<DWORD>(msg.lParam);
1363 if (!::SetEvent(pInternalWait->hWait))
1364 {
1365 TraceError(HRESULT_FROM_WIN32(::GetLastError()), "Failed to set event to notify coordinator thread that removable device handle was released, this could be due to wndproc no longer waiting for waiter thread's response");
1366 }
1367
1368 // Drive is disconnecting, don't even try to wait, just mark it as gone
1369 hrTemp = E_PATHNOTFOUND;
1370
1371 hr = UpdateWaitStatus(hrTemp, pWaiterContext, i, &dwNewRequestIndex);
1372 MonExitOnFailure(hr, "Failed to update wait status");
1373 hrTemp = S_OK;
1374 break;
1375 }
1376 }
1377 }
1378 break;
1379
1380 case MON_MESSAGE_STOP:
1381 // Stop requested, so abort the whole thread
1382 Trace(REPORT_DEBUG, "Waiter thread was told to stop");
1383 fAgain = FALSE;
1384 fContinue = FALSE;
1385 ExitFunction1(hr = static_cast<HRESULT>(msg.wParam));
1386
1387 default:
1388 Assert(false);
1389 break;
1390 }
1391 }
1392 } while (fAgain);
1393 }
1394 else if (dwRet > WAIT_OBJECT_0 && dwRet - WAIT_OBJECT_0 < pWaiterContext->cHandles)
1395 {
1396 // OK a handle fired - only notify if it's the actual target, and not just some parent waiting for the target child to exist
1397 dwRequestIndex = dwRet - WAIT_OBJECT_0 - 1;
1398 fNotify = (pWaiterContext->rgRequests[dwRequestIndex].dwPathHierarchyIndex == pWaiterContext->rgRequests[dwRequestIndex].cPathHierarchy - 1);
1399
1400 // Initiate re-waits before we notify callback, to ensure we don't miss a single update
1401 hrTemp = InitiateWait(pWaiterContext->rgRequests + dwRequestIndex, pWaiterContext->rgHandles + dwRequestIndex + 1);
1402 hr = UpdateWaitStatus(hrTemp, pWaiterContext, dwRequestIndex, &dwRequestIndex);
1403 MonExitOnFailure(hr, "Failed to update wait status");
1404 hrTemp = S_OK;
1405
1406 // If there were no errors and we were already waiting on the right target, or if we weren't yet but are able to now, it's a successful notify
1407 if (SUCCEEDED(pWaiterContext->rgRequests[dwRequestIndex].hrStatus) && (fNotify || (pWaiterContext->rgRequests[dwRequestIndex].dwPathHierarchyIndex == pWaiterContext->rgRequests[dwRequestIndex].cPathHierarchy - 1)))
1408 {
1409 Trace(REPORT_DEBUG, "Changes detected, waiting for silence period index %u", dwRequestIndex);
1410
1411 if (0 < pWaiterContext->rgRequests[dwRequestIndex].dwMaxSilencePeriodInMs)
1412 {
1413 pWaiterContext->rgRequests[dwRequestIndex].dwSilencePeriodInMs = 0;
1414 pWaiterContext->rgRequests[dwRequestIndex].fSkipDeltaAdd = TRUE;
1415
1416 if (!pWaiterContext->rgRequests[dwRequestIndex].fPendingFire)
1417 {
1418 pWaiterContext->rgRequests[dwRequestIndex].fPendingFire = TRUE;
1419 ++pWaiterContext->cRequestsPending;
1420 }
1421 }
1422 else
1423 {
1424 // If no silence period, notify immediately
1425 Notify(S_OK, pWaiterContext, pWaiterContext->rgRequests + dwRequestIndex);
1426 }
1427 }
1428 }
1429 else if (WAIT_TIMEOUT != dwRet)
1430 {
1431 MonExitWithLastError(hr, "Failed to wait for multiple objects with return code %u", dwRet);
1432 }
1433
1434 // OK, now that we've checked all triggered handles (resetting silence period timers appropriately), check for any pending notifications that we can finally fire
1435 // And set dwWait appropriately so we awaken at the right time to fire the next pending notification (in case no further writes occur during that time)
1436 if (0 < pWaiterContext->cRequestsPending)
1437 {
1438 // Start at max value and find the lowest wait we can below that
1439 dwWait = DWORD_MAX;
1440 cRequestsPendingBeforeLoop = pWaiterContext->cRequestsPending;
1441
1442 for (DWORD i = 0; i < pWaiterContext->cRequests; ++i)
1443 {
1444 if (pWaiterContext->rgRequests[i].fPendingFire)
1445 {
1446 if (0 == cRequestsPendingBeforeLoop)
1447 {
1448 Assert(FALSE);
1449 hr = HRESULT_FROM_WIN32(ERROR_EA_LIST_INCONSISTENT);
1450 MonExitOnFailure(hr, "Phantom pending fires were found!");
1451 }
1452 --cRequestsPendingBeforeLoop;
1453
1454 dwRequestIndex = i;
1455
1456 if (pWaiterContext->rgRequests[dwRequestIndex].fSkipDeltaAdd)
1457 {
1458 pWaiterContext->rgRequests[dwRequestIndex].fSkipDeltaAdd = FALSE;
1459 }
1460 else
1461 {
1462 pWaiterContext->rgRequests[dwRequestIndex].dwSilencePeriodInMs += uDeltaInMs;
1463 }
1464
1465 // silence period has elapsed without further notifications, so reset pending-related variables, and finally fire a notify!
1466 if (pWaiterContext->rgRequests[dwRequestIndex].dwSilencePeriodInMs >= pWaiterContext->rgRequests[dwRequestIndex].dwMaxSilencePeriodInMs)
1467 {
1468 Trace(REPORT_DEBUG, "Silence period surpassed, notifying %u ms late", pWaiterContext->rgRequests[dwRequestIndex].dwSilencePeriodInMs - pWaiterContext->rgRequests[dwRequestIndex].dwMaxSilencePeriodInMs);
1469 Notify(S_OK, pWaiterContext, pWaiterContext->rgRequests + dwRequestIndex);
1470 }
1471 else
1472 {
1473 // set dwWait to the shortest interval period so that if no changes occur, WaitForMultipleObjects
1474 // wakes the thread back up when it's time to fire the next pending notification
1475 if (dwWait > pWaiterContext->rgRequests[dwRequestIndex].dwMaxSilencePeriodInMs - pWaiterContext->rgRequests[dwRequestIndex].dwSilencePeriodInMs)
1476 {
1477 dwWait = pWaiterContext->rgRequests[dwRequestIndex].dwMaxSilencePeriodInMs - pWaiterContext->rgRequests[dwRequestIndex].dwSilencePeriodInMs;
1478 }
1479 }
1480 }
1481 }
1482
1483 // Some post-loop list validation for sanity checking
1484 if (0 < cRequestsPendingBeforeLoop)
1485 {
1486 Assert(FALSE);
1487 hr = HRESULT_FROM_WIN32(PEERDIST_ERROR_MISSING_DATA);
1488 MonExitOnFailure(hr, "Missing %u pending fires! Total pending fires: %u, wait: %u", cRequestsPendingBeforeLoop, pWaiterContext->cRequestsPending, dwWait);
1489 }
1490 if (0 < pWaiterContext->cRequestsPending && DWORD_MAX == dwWait)
1491 {
1492 Assert(FALSE);
1493 hr = HRESULT_FROM_WIN32(ERROR_CANT_WAIT);
1494 MonExitOnFailure(hr, "Pending fires exist (%u), but wait was infinite", cRequestsPendingBeforeLoop);
1495 }
1496 }
1497 } while (fContinue);
1498
1499 // Don't bother firing pending notifications. We were told to stop monitoring, so client doesn't care.
1500
1501LExit:
1502 ReleaseStr(sczDirectory);
1503 MonAddMessageDestroy(pAddMessage);
1504 MonRemoveMessageDestroy(pRemoveMessage);
1505
1506 for (DWORD i = 0; i < pWaiterContext->cRequests; ++i)
1507 {
1508 MonRequestDestroy(pWaiterContext->rgRequests + i);
1509
1510 switch (pWaiterContext->rgRequests[i].type)
1511 {
1512 case MON_DIRECTORY:
1513 if (INVALID_HANDLE_VALUE != pWaiterContext->rgHandles[i + 1])
1514 {
1515 ::FindCloseChangeNotification(pWaiterContext->rgHandles[i + 1]);
1516 }
1517 break;
1518 case MON_REGKEY:
1519 ReleaseHandle(pWaiterContext->rgHandles[i + 1]);
1520 break;
1521 default:
1522 Assert(false);
1523 }
1524 }
1525
1526 if (FAILED(hr))
1527 {
1528 // If waiter thread fails, notify general callback of an error
1529 Assert(pWaiterContext->vpfMonGeneral);
1530 pWaiterContext->vpfMonGeneral(hr, pWaiterContext->pvContext);
1531
1532 // And tell coordinator to shut all other waiters down
1533 if (!::PostThreadMessageW(pWaiterContext->dwCoordinatorThreadId, MON_MESSAGE_STOP, 0, 0))
1534 {
1535 TraceError(HRESULT_FROM_WIN32(::GetLastError()), "Failed to send message to coordinator thread to stop (due to general failure).");
1536 }
1537 }
1538
1539 return hr;
1540}
1541
1542static void Notify(
1543 __in HRESULT hr,
1544 __in MON_WAITER_CONTEXT *pWaiterContext,
1545 __in MON_REQUEST *pRequest
1546 )
1547{
1548 if (pRequest->fPendingFire)
1549 {
1550 --pWaiterContext->cRequestsPending;
1551 }
1552
1553 pRequest->fPendingFire = FALSE;
1554 pRequest->fSkipDeltaAdd = FALSE;
1555 pRequest->dwSilencePeriodInMs = 0;
1556
1557 switch (pRequest->type)
1558 {
1559 case MON_DIRECTORY:
1560 Assert(pWaiterContext->vpfMonDirectory);
1561 pWaiterContext->vpfMonDirectory(hr, pRequest->sczOriginalPathRequest, pRequest->fRecursive, pWaiterContext->pvContext, pRequest->pvContext);
1562 break;
1563 case MON_REGKEY:
1564 Assert(pWaiterContext->vpfMonRegKey);
1565 pWaiterContext->vpfMonRegKey(hr, pRequest->regkey.hkRoot, pRequest->rgsczPathHierarchy[pRequest->cPathHierarchy - 1], pRequest->regkey.kbKeyBitness, pRequest->fRecursive, pWaiterContext->pvContext, pRequest->pvContext);
1566 break;
1567 default:
1568 Assert(false);
1569 }
1570}
1571
1572static BOOL GetRecursiveFlag(
1573 __in MON_REQUEST *pRequest,
1574 __in DWORD dwIndex
1575 )
1576{
1577 if (pRequest->cPathHierarchy - 1 == dwIndex)
1578 {
1579 return pRequest->fRecursive;
1580 }
1581 else
1582 {
1583 return FALSE;
1584 }
1585}
1586
1587static HRESULT FindRequestIndex(
1588 __in MON_WAITER_CONTEXT *pWaiterContext,
1589 __in MON_REMOVE_MESSAGE *pMessage,
1590 __out DWORD *pdwIndex
1591 )
1592{
1593 HRESULT hr = S_OK;
1594
1595 for (DWORD i = 0; i < pWaiterContext->cRequests; ++i)
1596 {
1597 if (pWaiterContext->rgRequests[i].type == pMessage->type)
1598 {
1599 switch (pWaiterContext->rgRequests[i].type)
1600 {
1601 case MON_DIRECTORY:
1602 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pWaiterContext->rgRequests[i].rgsczPathHierarchy[pWaiterContext->rgRequests[i].cPathHierarchy - 1], -1, pMessage->directory.sczDirectory, -1) && pWaiterContext->rgRequests[i].fRecursive == pMessage->fRecursive)
1603 {
1604 *pdwIndex = i;
1605 ExitFunction1(hr = S_OK);
1606 }
1607 break;
1608 case MON_REGKEY:
1609 if (reinterpret_cast<DWORD_PTR>(pMessage->regkey.hkRoot) == reinterpret_cast<DWORD_PTR>(pWaiterContext->rgRequests[i].regkey.hkRoot) && CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pWaiterContext->rgRequests[i].rgsczPathHierarchy[pWaiterContext->rgRequests[i].cPathHierarchy - 1], -1, pMessage->regkey.sczSubKey, -1) && pWaiterContext->rgRequests[i].fRecursive == pMessage->fRecursive && pWaiterContext->rgRequests[i].regkey.kbKeyBitness == pMessage->regkey.kbKeyBitness)
1610 {
1611 *pdwIndex = i;
1612 ExitFunction1(hr = S_OK);
1613 }
1614 break;
1615 default:
1616 Assert(false);
1617 }
1618 }
1619 }
1620
1621 hr = E_NOTFOUND;
1622
1623LExit:
1624 return hr;
1625}
1626
1627static HRESULT RemoveRequest(
1628 __inout MON_WAITER_CONTEXT *pWaiterContext,
1629 __in DWORD dwRequestIndex
1630 )
1631{
1632 HRESULT hr = S_OK;
1633
1634 MonRequestDestroy(pWaiterContext->rgRequests + dwRequestIndex);
1635
1636 switch (pWaiterContext->rgRequests[dwRequestIndex].type)
1637 {
1638 case MON_DIRECTORY:
1639 if (pWaiterContext->rgHandles[dwRequestIndex + 1] != INVALID_HANDLE_VALUE)
1640 {
1641 ::FindCloseChangeNotification(pWaiterContext->rgHandles[dwRequestIndex + 1]);
1642 }
1643 break;
1644 case MON_REGKEY:
1645 ReleaseHandle(pWaiterContext->rgHandles[dwRequestIndex + 1]);
1646 break;
1647 default:
1648 Assert(false);
1649 }
1650
1651 if (pWaiterContext->rgRequests[dwRequestIndex].fPendingFire)
1652 {
1653 --pWaiterContext->cRequestsPending;
1654 }
1655
1656 if (FAILED(pWaiterContext->rgRequests[dwRequestIndex].hrStatus))
1657 {
1658 --pWaiterContext->cRequestsFailing;
1659 }
1660
1661 MemRemoveFromArray(reinterpret_cast<void *>(pWaiterContext->rgHandles), dwRequestIndex + 1, 1, pWaiterContext->cHandles, sizeof(HANDLE), TRUE);
1662 --pWaiterContext->cHandles;
1663 MemRemoveFromArray(reinterpret_cast<void *>(pWaiterContext->rgRequests), dwRequestIndex, 1, pWaiterContext->cRequests, sizeof(MON_REQUEST), TRUE);
1664 --pWaiterContext->cRequests;
1665
1666 // Notify coordinator thread that a wait was removed
1667 if (!::PostThreadMessageW(pWaiterContext->dwCoordinatorThreadId, MON_MESSAGE_REMOVED, static_cast<WPARAM>(::GetCurrentThreadId()), 0))
1668 {
1669 MonExitWithLastError(hr, "Failed to send message to coordinator thread to confirm directory was removed.");
1670 }
1671
1672LExit:
1673 return hr;
1674}
1675
1676static REGSAM GetRegKeyBitness(
1677 __in MON_REQUEST *pRequest
1678 )
1679{
1680 if (REG_KEY_32BIT == pRequest->regkey.kbKeyBitness)
1681 {
1682 return KEY_WOW64_32KEY;
1683 }
1684 else if (REG_KEY_64BIT == pRequest->regkey.kbKeyBitness)
1685 {
1686 return KEY_WOW64_64KEY;
1687 }
1688 else
1689 {
1690 return 0;
1691 }
1692}
1693
1694static HRESULT DuplicateRemoveMessage(
1695 __in MON_REMOVE_MESSAGE *pMessage,
1696 __out MON_REMOVE_MESSAGE **ppMessage
1697 )
1698{
1699 HRESULT hr = S_OK;
1700
1701 *ppMessage = reinterpret_cast<MON_REMOVE_MESSAGE *>(MemAlloc(sizeof(MON_REMOVE_MESSAGE), TRUE));
1702 MonExitOnNull(*ppMessage, hr, E_OUTOFMEMORY, "Failed to allocate copy of remove message");
1703
1704 (*ppMessage)->type = pMessage->type;
1705 (*ppMessage)->fRecursive = pMessage->fRecursive;
1706
1707 switch (pMessage->type)
1708 {
1709 case MON_DIRECTORY:
1710 hr = StrAllocString(&(*ppMessage)->directory.sczDirectory, pMessage->directory.sczDirectory, 0);
1711 MonExitOnFailure(hr, "Failed to copy directory");
1712 break;
1713 case MON_REGKEY:
1714 (*ppMessage)->regkey.hkRoot = pMessage->regkey.hkRoot;
1715 (*ppMessage)->regkey.kbKeyBitness = pMessage->regkey.kbKeyBitness;
1716 hr = StrAllocString(&(*ppMessage)->regkey.sczSubKey, pMessage->regkey.sczSubKey, 0);
1717 MonExitOnFailure(hr, "Failed to copy subkey");
1718 break;
1719 default:
1720 Assert(false);
1721 break;
1722 }
1723
1724LExit:
1725 return hr;
1726}
1727
1728static LRESULT CALLBACK MonWndProc(
1729 __in HWND hWnd,
1730 __in UINT uMsg,
1731 __in WPARAM wParam,
1732 __in LPARAM lParam
1733 )
1734{
1735 HRESULT hr = S_OK;
1736 DEV_BROADCAST_HDR *pHdr = NULL;
1737 DEV_BROADCAST_HANDLE *pHandle = NULL;
1738 DEV_BROADCAST_VOLUME *pVolume = NULL;
1739 DWORD dwUnitMask = 0;
1740 DWORD er = ERROR_SUCCESS;
1741 WCHAR chDrive = L'\0';
1742 BOOL fArrival = FALSE;
1743 BOOL fReturnTrue = FALSE;
1744 CREATESTRUCT *pCreateStruct = NULL;
1745 MON_WAITER_CONTEXT *pWaiterContext = NULL;
1746 MON_STRUCT *pm = NULL;
1747
1748 // keep track of the MON_STRUCT pointer that was passed in on init, associate it with the window
1749 if (WM_CREATE == uMsg)
1750 {
1751 pCreateStruct = reinterpret_cast<CREATESTRUCT *>(lParam);
1752 if (pCreateStruct)
1753 {
1754 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pCreateStruct->lpCreateParams));
1755 }
1756 }
1757 else if (WM_NCDESTROY == uMsg)
1758 {
1759 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0);
1760 }
1761
1762 // Note this message ONLY comes in through WndProc, it isn't visible from the GetMessage loop.
1763 else if (WM_DEVICECHANGE == uMsg)
1764 {
1765 if (DBT_DEVICEARRIVAL == wParam || DBT_DEVICEREMOVECOMPLETE == wParam)
1766 {
1767 fArrival = DBT_DEVICEARRIVAL == wParam;
1768
1769 pHdr = reinterpret_cast<DEV_BROADCAST_HDR*>(lParam);
1770 if (DBT_DEVTYP_VOLUME == pHdr->dbch_devicetype)
1771 {
1772 pVolume = reinterpret_cast<DEV_BROADCAST_VOLUME*>(lParam);
1773 dwUnitMask = pVolume->dbcv_unitmask;
1774 chDrive = L'a';
1775 while (0 < dwUnitMask)
1776 {
1777 if (dwUnitMask & 0x1)
1778 {
1779 // This drive had a status update, so send it out to all threads
1780 if (!::PostThreadMessageW(::GetCurrentThreadId(), MON_MESSAGE_DRIVE_STATUS_UPDATE, static_cast<WPARAM>(chDrive), static_cast<LPARAM>(fArrival)))
1781 {
1782 MonExitWithLastError(hr, "Failed to send drive status update with drive %wc and arrival %ls", chDrive, fArrival ? L"TRUE" : L"FALSE");
1783 }
1784 }
1785 dwUnitMask >>= 1;
1786 ++chDrive;
1787
1788 if (chDrive == 'z')
1789 {
1790 hr = E_UNEXPECTED;
1791 MonExitOnFailure(hr, "UnitMask showed drives beyond z:. Remaining UnitMask at this point: %u", dwUnitMask);
1792 }
1793 }
1794 }
1795 }
1796 // We can only process device query remove messages if we have a MON_STRUCT pointer
1797 else if (DBT_DEVICEQUERYREMOVE == wParam)
1798 {
1799 pm = reinterpret_cast<MON_STRUCT*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
1800 if (!pm)
1801 {
1802 hr = E_POINTER;
1803 MonExitOnFailure(hr, "DBT_DEVICEQUERYREMOVE message received with no MON_STRUCT pointer, so message was ignored");
1804 }
1805
1806 fReturnTrue = TRUE;
1807
1808 pHdr = reinterpret_cast<DEV_BROADCAST_HDR*>(lParam);
1809 if (DBT_DEVTYP_HANDLE == pHdr->dbch_devicetype)
1810 {
1811 // We must wait for the actual wait handle to be released by waiter thread before telling windows to proceed with device removal, otherwise it could fail
1812 // due to handles still being open, so use a MON_INTERNAL_TEMPORARY_WAIT struct to send and receive a reply from a waiter thread
1813 pm->internalWait.hWait = ::CreateEventW(NULL, TRUE, FALSE, NULL);
1814 MonExitOnNullWithLastError(pm->internalWait.hWait, hr, "Failed to create anonymous event for waiter to notify wndproc device can be removed");
1815
1816 pHandle = reinterpret_cast<DEV_BROADCAST_HANDLE*>(lParam);
1817 pm->internalWait.pvContext = pHandle->dbch_handle;
1818 pm->internalWait.dwReceiveIteration = pm->internalWait.dwSendIteration - 1;
1819 // This drive had a status update, so send it out to all threads
1820 for (DWORD i = 0; i < pm->cWaiterThreads; ++i)
1821 {
1822 pWaiterContext = pm->rgWaiterThreads[i].pWaiterContext;
1823
1824 if (!::PostThreadMessageW(pWaiterContext->dwWaiterThreadId, MON_MESSAGE_DRIVE_QUERY_REMOVE, reinterpret_cast<WPARAM>(&pm->internalWait), static_cast<LPARAM>(pm->internalWait.dwSendIteration)))
1825 {
1826 MonExitWithLastError(hr, "Failed to send message to waiter thread to notify of drive query remove");
1827 }
1828
1829 if (!::SetEvent(pWaiterContext->rgHandles[0]))
1830 {
1831 MonExitWithLastError(hr, "Failed to set event to notify waiter thread of incoming drive query remove message");
1832 }
1833 }
1834
1835 er = ::WaitForSingleObject(pm->internalWait.hWait, MON_THREAD_WAIT_REMOVE_DEVICE);
1836 // Make sure any waiter thread processing really old messages can immediately know that we're no longer waiting for a response
1837 if (WAIT_OBJECT_0 == er)
1838 {
1839 // If the response ID matches what we sent, we actually got a valid reply!
1840 if (pm->internalWait.dwReceiveIteration != pm->internalWait.dwSendIteration)
1841 {
1842 TraceError(HRESULT_FROM_WIN32(er), "Waiter thread received wrong ID reply");
1843 }
1844 }
1845 else if (WAIT_TIMEOUT == er)
1846 {
1847 TraceError(HRESULT_FROM_WIN32(er), "No response from any waiter thread for query remove message");
1848 }
1849 else
1850 {
1851 MonExitWithLastError(hr, "WaitForSingleObject failed with non-timeout reason while waiting for response from waiter thread");
1852 }
1853 ++pm->internalWait.dwSendIteration;
1854 }
1855 }
1856 }
1857
1858LExit:
1859 if (pm)
1860 {
1861 ReleaseHandle(pm->internalWait.hWait);
1862 }
1863
1864 if (fReturnTrue)
1865 {
1866 return TRUE;
1867 }
1868 else
1869 {
1870 return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
1871 }
1872}
1873
1874static HRESULT CreateMonWindow(
1875 __in MON_STRUCT *pm,
1876 __out HWND *pHwnd
1877 )
1878{
1879 HRESULT hr = S_OK;
1880 WNDCLASSW wc = { };
1881
1882 wc.lpfnWndProc = MonWndProc;
1883 wc.hInstance = ::GetModuleHandleW(NULL);
1884 wc.lpszClassName = MONUTIL_WINDOW_CLASS;
1885 if (!::RegisterClassW(&wc))
1886 {
1887 if (ERROR_CLASS_ALREADY_EXISTS != ::GetLastError())
1888 {
1889 MonExitWithLastError(hr, "Failed to register MonUtil window class.");
1890 }
1891 }
1892
1893 *pHwnd = ::CreateWindowExW(0, wc.lpszClassName, L"", 0, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, HWND_DESKTOP, NULL, wc.hInstance, pm);
1894 MonExitOnNullWithLastError(*pHwnd, hr, "Failed to create window.");
1895
1896 // Rumor has it that drive arrival / removal events can be lost in the rare event that some other application higher up in z-order is hanging if we don't make our window topmost
1897 // SWP_NOACTIVATE is important so the currently active window doesn't lose focus
1898 SetWindowPos(*pHwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_DEFERERASE | SWP_NOACTIVATE);
1899
1900LExit:
1901 return hr;
1902}
1903
1904static HRESULT WaitForNetworkChanges(
1905 __inout HANDLE *phMonitor,
1906 __in MON_STRUCT *pm
1907 )
1908{
1909 HRESULT hr = S_OK;
1910 int nResult = 0;
1911 DWORD dwBytesReturned = 0;
1912 WSACOMPLETION wsaCompletion = { };
1913 WSAQUERYSET qsRestrictions = { };
1914
1915 qsRestrictions.dwSize = sizeof(WSAQUERYSET);
1916 qsRestrictions.dwNameSpace = NS_NLA;
1917
1918 if (NULL != *phMonitor)
1919 {
1920 ::WSALookupServiceEnd(*phMonitor);
1921 *phMonitor = NULL;
1922 }
1923
1924 if (::WSALookupServiceBegin(&qsRestrictions, LUP_RETURN_ALL, phMonitor))
1925 {
1926 hr = HRESULT_FROM_WIN32(::WSAGetLastError());
1927 MonExitOnFailure(hr, "WSALookupServiceBegin() failed");
1928 }
1929
1930 wsaCompletion.Type = NSP_NOTIFY_HWND;
1931 wsaCompletion.Parameters.WindowMessage.hWnd = pm->hwnd;
1932 wsaCompletion.Parameters.WindowMessage.uMsg = MON_MESSAGE_NETWORK_STATUS_UPDATE;
1933 nResult = ::WSANSPIoctl(*phMonitor, SIO_NSP_NOTIFY_CHANGE, NULL, 0, NULL, 0, &dwBytesReturned, &wsaCompletion);
1934 if (SOCKET_ERROR != nResult || WSA_IO_PENDING != ::WSAGetLastError())
1935 {
1936 hr = HRESULT_FROM_WIN32(::WSAGetLastError());
1937 if (SUCCEEDED(hr))
1938 {
1939 hr = E_FAIL;
1940 }
1941 MonExitOnFailure(hr, "WSANSPIoctl() failed with return code %i, wsa last error %u", nResult, ::WSAGetLastError());
1942 }
1943
1944LExit:
1945 return hr;
1946}
1947
1948static HRESULT UpdateWaitStatus(
1949 __in HRESULT hrNewStatus,
1950 __inout MON_WAITER_CONTEXT *pWaiterContext,
1951 __in DWORD dwRequestIndex,
1952 __out_opt DWORD *pdwNewRequestIndex
1953 )
1954{
1955 HRESULT hr = S_OK;
1956 DWORD dwNewRequestIndex;
1957 MON_REQUEST *pRequest = pWaiterContext->rgRequests + dwRequestIndex;
1958
1959 if (NULL != pdwNewRequestIndex)
1960 {
1961 *pdwNewRequestIndex = dwRequestIndex;
1962 }
1963
1964 if (SUCCEEDED(pRequest->hrStatus) || SUCCEEDED(hrNewStatus))
1965 {
1966 // If it's a network wait, notify as long as it's new status is successful because we *may* have lost some changes
1967 // before the wait was re-initiated. Otherwise, only notify if there was an interesting status change
1968 if (SUCCEEDED(pRequest->hrStatus) != SUCCEEDED(hrNewStatus) || (pRequest->fNetwork && SUCCEEDED(hrNewStatus)))
1969 {
1970 Notify(hrNewStatus, pWaiterContext, pRequest);
1971 }
1972
1973 if (SUCCEEDED(pRequest->hrStatus) && FAILED(hrNewStatus))
1974 {
1975 // If it's a network wait, notify coordinator thread that a network wait is failing
1976 if (pRequest->fNetwork && !::PostThreadMessageW(pWaiterContext->dwCoordinatorThreadId, MON_MESSAGE_NETWORK_WAIT_FAILED, 0, 0))
1977 {
1978 MonExitWithLastError(hr, "Failed to send message to coordinator thread to notify a network wait started to fail");
1979 }
1980
1981 // Move the failing wait to the end of the list of waits and increment cRequestsFailing so WaitForMultipleObjects isn't passed an invalid handle
1982 ++pWaiterContext->cRequestsFailing;
1983 dwNewRequestIndex = pWaiterContext->cRequests - 1;
1984 MemArraySwapItems(reinterpret_cast<void *>(pWaiterContext->rgHandles), dwRequestIndex + 1, dwNewRequestIndex + 1, sizeof(*pWaiterContext->rgHandles));
1985 MemArraySwapItems(reinterpret_cast<void *>(pWaiterContext->rgRequests), dwRequestIndex, dwNewRequestIndex, sizeof(*pWaiterContext->rgRequests));
1986 // Reset pRequest to the newly swapped item
1987 pRequest = pWaiterContext->rgRequests + dwNewRequestIndex;
1988 if (NULL != pdwNewRequestIndex)
1989 {
1990 *pdwNewRequestIndex = dwNewRequestIndex;
1991 }
1992 }
1993 else if (FAILED(pRequest->hrStatus) && SUCCEEDED(hrNewStatus))
1994 {
1995 Assert(pWaiterContext->cRequestsFailing > 0);
1996 // If it's a network wait, notify coordinator thread that a network wait is succeeding again
1997 if (pRequest->fNetwork && !::PostThreadMessageW(pWaiterContext->dwCoordinatorThreadId, MON_MESSAGE_NETWORK_WAIT_SUCCEEDED, 0, 0))
1998 {
1999 MonExitWithLastError(hr, "Failed to send message to coordinator thread to notify a network wait is succeeding again");
2000 }
2001
2002 --pWaiterContext->cRequestsFailing;
2003 dwNewRequestIndex = 0;
2004 MemArraySwapItems(reinterpret_cast<void *>(pWaiterContext->rgHandles), dwRequestIndex + 1, dwNewRequestIndex + 1, sizeof(*pWaiterContext->rgHandles));
2005 MemArraySwapItems(reinterpret_cast<void *>(pWaiterContext->rgRequests), dwRequestIndex, dwNewRequestIndex, sizeof(*pWaiterContext->rgRequests));
2006 // Reset pRequest to the newly swapped item
2007 pRequest = pWaiterContext->rgRequests + dwNewRequestIndex;
2008 if (NULL != pdwNewRequestIndex)
2009 {
2010 *pdwNewRequestIndex = dwNewRequestIndex;
2011 }
2012 }
2013 }
2014
2015 pRequest->hrStatus = hrNewStatus;
2016
2017LExit:
2018 return hr;
2019}
diff --git a/src/libs/dutil/WixToolset.DUtil/osutil.cpp b/src/libs/dutil/WixToolset.DUtil/osutil.cpp
new file mode 100644
index 00000000..880ec3ea
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/osutil.cpp
@@ -0,0 +1,251 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define OsExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_OSUTIL, x, s, __VA_ARGS__)
8#define OsExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_OSUTIL, x, s, __VA_ARGS__)
9#define OsExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_OSUTIL, x, s, __VA_ARGS__)
10#define OsExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_OSUTIL, x, s, __VA_ARGS__)
11#define OsExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_OSUTIL, x, s, __VA_ARGS__)
12#define OsExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_OSUTIL, x, s, __VA_ARGS__)
13#define OsExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_OSUTIL, p, x, e, s, __VA_ARGS__)
14#define OsExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_OSUTIL, p, x, s, __VA_ARGS__)
15#define OsExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_OSUTIL, p, x, e, s, __VA_ARGS__)
16#define OsExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_OSUTIL, p, x, s, __VA_ARGS__)
17#define OsExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_OSUTIL, e, x, s, __VA_ARGS__)
18#define OsExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_OSUTIL, g, x, s, __VA_ARGS__)
19
20typedef NTSTATUS(NTAPI* PFN_RTL_GET_VERSION)(_Out_ PRTL_OSVERSIONINFOEXW lpVersionInformation);
21
22OS_VERSION vOsVersion = OS_VERSION_UNKNOWN;
23DWORD vdwOsServicePack = 0;
24RTL_OSVERSIONINFOEXW vovix = { };
25
26/********************************************************************
27 OsGetVersion
28
29********************************************************************/
30extern "C" void DAPI OsGetVersion(
31 __out OS_VERSION* pVersion,
32 __out DWORD* pdwServicePack
33 )
34{
35 OSVERSIONINFOEXW ovi = { };
36
37 if (OS_VERSION_UNKNOWN == vOsVersion)
38 {
39 ovi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW);
40
41#pragma warning (push)
42#pragma warning(suppress: 4996) // deprecated
43#pragma warning (push)
44#pragma warning(suppress: 28159)// deprecated, use other function instead
45 ::GetVersionExW(reinterpret_cast<OSVERSIONINFOW*>(&ovi)); // only fails if version info size is set incorrectly.
46#pragma warning (pop)
47#pragma warning (pop)
48
49 vdwOsServicePack = static_cast<DWORD>(ovi.wServicePackMajor) << 16 | ovi.wServicePackMinor;
50 if (4 == ovi.dwMajorVersion)
51 {
52 vOsVersion = OS_VERSION_WINNT;
53 }
54 else if (5 == ovi.dwMajorVersion)
55 {
56 if (0 == ovi.dwMinorVersion)
57 {
58 vOsVersion = OS_VERSION_WIN2000;
59 }
60 else if (1 == ovi.dwMinorVersion)
61 {
62 vOsVersion = OS_VERSION_WINXP;
63 }
64 else if (2 == ovi.dwMinorVersion)
65 {
66 vOsVersion = OS_VERSION_WIN2003;
67 }
68 else
69 {
70 vOsVersion = OS_VERSION_FUTURE;
71 }
72 }
73 else if (6 == ovi.dwMajorVersion)
74 {
75 if (0 == ovi.dwMinorVersion)
76 {
77 vOsVersion = (VER_NT_WORKSTATION == ovi.wProductType) ? OS_VERSION_VISTA : OS_VERSION_WIN2008;
78 }
79 else if (1 == ovi.dwMinorVersion)
80 {
81 vOsVersion = (VER_NT_WORKSTATION == ovi.wProductType) ? OS_VERSION_WIN7 : OS_VERSION_WIN2008_R2;
82 }
83 else
84 {
85 vOsVersion = OS_VERSION_FUTURE;
86 }
87 }
88 else
89 {
90 vOsVersion = OS_VERSION_FUTURE;
91 }
92 }
93
94 *pVersion = vOsVersion;
95 *pdwServicePack = vdwOsServicePack;
96}
97
98extern "C" HRESULT DAPI OsCouldRunPrivileged(
99 __out BOOL* pfPrivileged
100 )
101{
102 HRESULT hr = S_OK;
103 BOOL fUacEnabled = FALSE;
104 SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
105 PSID AdministratorsGroup = NULL;
106
107 // Do a best effort check to see if UAC is enabled on this machine.
108 OsIsUacEnabled(&fUacEnabled);
109
110 // If UAC is enabled then the process could run privileged by asking to elevate.
111 if (fUacEnabled)
112 {
113 *pfPrivileged = TRUE;
114 }
115 else // no UAC so only privilged if user is in administrators group.
116 {
117 *pfPrivileged = ::AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &AdministratorsGroup);
118 if (*pfPrivileged)
119 {
120 if (!::CheckTokenMembership(NULL, AdministratorsGroup, pfPrivileged))
121 {
122 *pfPrivileged = FALSE;
123 }
124 }
125 }
126
127 ReleaseSid(AdministratorsGroup);
128 return hr;
129}
130
131extern "C" HRESULT DAPI OsIsRunningPrivileged(
132 __out BOOL* pfPrivileged
133 )
134{
135 HRESULT hr = S_OK;
136 UINT er = ERROR_SUCCESS;
137 HANDLE hToken = NULL;
138 TOKEN_ELEVATION_TYPE elevationType = TokenElevationTypeDefault;
139 DWORD dwSize = 0;
140 SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
141 PSID AdministratorsGroup = NULL;
142
143 if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &hToken))
144 {
145 OsExitOnLastError(hr, "Failed to open process token.");
146 }
147
148 if (::GetTokenInformation(hToken, TokenElevationType, &elevationType, sizeof(TOKEN_ELEVATION_TYPE), &dwSize))
149 {
150 *pfPrivileged = (TokenElevationTypeFull == elevationType);
151 ExitFunction1(hr = S_OK);
152 }
153
154 // If it's invalid argument, this means they don't support TokenElevationType, and we should fallback to another check
155 er = ::GetLastError();
156 if (ERROR_INVALID_FUNCTION == er)
157 {
158 er = ERROR_SUCCESS;
159 }
160 OsExitOnWin32Error(er, hr, "Failed to get process token information.");
161
162 // Fallback to this check for some OS's (like XP)
163 *pfPrivileged = ::AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &AdministratorsGroup);
164 if (*pfPrivileged)
165 {
166 if (!::CheckTokenMembership(NULL, AdministratorsGroup, pfPrivileged))
167 {
168 *pfPrivileged = FALSE;
169 }
170 }
171
172LExit:
173 ReleaseSid(AdministratorsGroup);
174
175 if (hToken)
176 {
177 ::CloseHandle(hToken);
178 }
179
180 return hr;
181}
182
183extern "C" HRESULT DAPI OsIsUacEnabled(
184 __out BOOL* pfUacEnabled
185 )
186{
187 HRESULT hr = S_OK;
188 HKEY hk = NULL;
189 DWORD dwUacEnabled = 0;
190
191 *pfUacEnabled = FALSE; // assume UAC not enabled.
192
193 hr = RegOpen(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", KEY_READ, &hk);
194 if (E_FILENOTFOUND == hr)
195 {
196 ExitFunction1(hr = S_OK);
197 }
198 OsExitOnFailure(hr, "Failed to open system policy key to detect UAC.");
199
200 hr = RegReadNumber(hk, L"EnableLUA", &dwUacEnabled);
201 if (E_FILENOTFOUND == hr)
202 {
203 ExitFunction1(hr = S_OK);
204 }
205 OsExitOnFailure(hr, "Failed to read registry value to detect UAC.");
206
207 *pfUacEnabled = (0 != dwUacEnabled);
208
209LExit:
210 ReleaseRegKey(hk);
211
212 return hr;
213}
214
215HRESULT DAPI OsRtlGetVersion(
216 __inout RTL_OSVERSIONINFOEXW* pOvix
217 )
218{
219 HRESULT hr = S_OK;
220 HMODULE hNtdll = NULL;
221 PFN_RTL_GET_VERSION pfnRtlGetVersion = NULL;
222
223 if (vovix.dwOSVersionInfoSize)
224 {
225 ExitFunction();
226 }
227
228 vovix.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW);
229
230 hr = LoadSystemLibrary(L"ntdll.dll", &hNtdll);
231 if (E_MODNOTFOUND == hr)
232 {
233 OsExitOnRootFailure(hr = E_NOTIMPL, "Failed to load ntdll.dll");
234 }
235 OsExitOnFailure(hr, "Failed to load ntdll.dll.");
236
237 pfnRtlGetVersion = reinterpret_cast<PFN_RTL_GET_VERSION>(::GetProcAddress(hNtdll, "RtlGetVersion"));
238 OsExitOnNullWithLastError(pfnRtlGetVersion, hr, "Failed to locate RtlGetVersion.");
239
240 hr = static_cast<HRESULT>(pfnRtlGetVersion(&vovix));
241
242LExit:
243 memcpy(pOvix, &vovix, sizeof(RTL_OSVERSIONINFOEXW));
244
245 if (hNtdll)
246 {
247 ::FreeLibrary(hNtdll);
248 }
249
250 return hr;
251}
diff --git a/src/libs/dutil/WixToolset.DUtil/packages.config b/src/libs/dutil/WixToolset.DUtil/packages.config
new file mode 100644
index 00000000..5bbcd994
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/packages.config
@@ -0,0 +1,7 @@
1<?xml version="1.0" encoding="utf-8"?>
2<packages>
3 <package id="Microsoft.Build.Tasks.Git" version="1.0.0" targetFramework="native" developmentDependency="true" />
4 <package id="Microsoft.SourceLink.Common" version="1.0.0" targetFramework="native" developmentDependency="true" />
5 <package id="Microsoft.SourceLink.GitHub" version="1.0.0" targetFramework="native" developmentDependency="true" />
6 <package id="Nerdbank.GitVersioning" version="3.3.37" targetFramework="native" developmentDependency="true" />
7</packages> \ No newline at end of file
diff --git a/src/libs/dutil/WixToolset.DUtil/path2utl.cpp b/src/libs/dutil/WixToolset.DUtil/path2utl.cpp
new file mode 100644
index 00000000..ff3a946d
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/path2utl.cpp
@@ -0,0 +1,104 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define PathExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
8#define PathExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
9#define PathExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
10#define PathExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
11#define PathExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
12#define PathExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
13#define PathExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_PATHUTIL, p, x, e, s, __VA_ARGS__)
14#define PathExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_PATHUTIL, p, x, s, __VA_ARGS__)
15#define PathExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_PATHUTIL, p, x, e, s, __VA_ARGS__)
16#define PathExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_PATHUTIL, p, x, s, __VA_ARGS__)
17#define PathExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_PATHUTIL, e, x, s, __VA_ARGS__)
18#define PathExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_PATHUTIL, g, x, s, __VA_ARGS__)
19
20
21DAPI_(HRESULT) PathCanonicalizePath(
22 __in_z LPCWSTR wzPath,
23 __deref_out_z LPWSTR* psczCanonicalized
24 )
25{
26 HRESULT hr = S_OK;
27 int cch = MAX_PATH + 1;
28
29 hr = StrAlloc(psczCanonicalized, cch);
30 PathExitOnFailure(hr, "Failed to allocate string for the canonicalized path.");
31
32 if (::PathCanonicalizeW(*psczCanonicalized, wzPath))
33 {
34 hr = S_OK;
35 }
36 else
37 {
38 ExitFunctionWithLastError(hr);
39 }
40
41LExit:
42 return hr;
43}
44
45DAPI_(HRESULT) PathDirectoryContainsPath(
46 __in_z LPCWSTR wzDirectory,
47 __in_z LPCWSTR wzPath
48 )
49{
50 HRESULT hr = S_OK;
51 LPWSTR sczPath = NULL;
52 LPWSTR sczDirectory = NULL;
53 LPWSTR sczOriginalPath = NULL;
54 LPWSTR sczOriginalDirectory = NULL;
55
56 hr = PathCanonicalizePath(wzPath, &sczOriginalPath);
57 PathExitOnFailure(hr, "Failed to canonicalize the path.");
58
59 hr = PathCanonicalizePath(wzDirectory, &sczOriginalDirectory);
60 PathExitOnFailure(hr, "Failed to canonicalize the directory.");
61
62 if (!sczOriginalPath || !*sczOriginalPath)
63 {
64 ExitFunction1(hr = S_FALSE);
65 }
66 if (!sczOriginalDirectory || !*sczOriginalDirectory)
67 {
68 ExitFunction1(hr = S_FALSE);
69 }
70
71 sczPath = sczOriginalPath;
72 sczDirectory = sczOriginalDirectory;
73
74 for (; *sczDirectory;)
75 {
76 if (!*sczPath)
77 {
78 ExitFunction1(hr = S_FALSE);
79 }
80
81 if (CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, sczDirectory, 1, sczPath, 1))
82 {
83 ExitFunction1(hr = S_FALSE);
84 }
85
86 ++sczDirectory;
87 ++sczPath;
88 }
89
90 --sczDirectory;
91 if (('\\' == *sczDirectory && *sczPath) || '\\' == *sczPath)
92 {
93 hr = S_OK;
94 }
95 else
96 {
97 hr = S_FALSE;
98 }
99
100LExit:
101 ReleaseStr(sczOriginalPath);
102 ReleaseStr(sczOriginalDirectory);
103 return hr;
104}
diff --git a/src/libs/dutil/WixToolset.DUtil/pathutil.cpp b/src/libs/dutil/WixToolset.DUtil/pathutil.cpp
new file mode 100644
index 00000000..7c3cfe06
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/pathutil.cpp
@@ -0,0 +1,1083 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define PathExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
8#define PathExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
9#define PathExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
10#define PathExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
11#define PathExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
12#define PathExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
13#define PathExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_PATHUTIL, p, x, e, s, __VA_ARGS__)
14#define PathExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_PATHUTIL, p, x, s, __VA_ARGS__)
15#define PathExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_PATHUTIL, p, x, e, s, __VA_ARGS__)
16#define PathExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_PATHUTIL, p, x, s, __VA_ARGS__)
17#define PathExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_PATHUTIL, e, x, s, __VA_ARGS__)
18#define PathExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_PATHUTIL, g, x, s, __VA_ARGS__)
19
20#define PATH_GOOD_ENOUGH 64
21
22
23DAPI_(HRESULT) PathCommandLineAppend(
24 __deref_inout_z LPWSTR* psczCommandLine,
25 __in_z LPCWSTR wzArgument
26 )
27{
28 HRESULT hr = S_OK;
29 LPWSTR sczQuotedArg = NULL;
30 BOOL fRequiresQuoting = FALSE;
31 DWORD dwMaxEscapedSize = 0;
32
33 // Loop through the argument determining if it needs to be quoted and what the maximum
34 // size would be if there are escape characters required.
35 for (LPCWSTR pwz = wzArgument; *pwz; ++pwz)
36 {
37 // Arguments with whitespace need quoting.
38 if (L' ' == *pwz || L'\t' == *pwz || L'\n' == *pwz || L'\v' == *pwz)
39 {
40 fRequiresQuoting = TRUE;
41 }
42 else if (L'"' == *pwz) // quotes need quoting and sometimes escaping.
43 {
44 fRequiresQuoting = TRUE;
45 ++dwMaxEscapedSize;
46 }
47 else if (L'\\' == *pwz) // some backslashes need escaping, so we'll count them all to make sure there is room.
48 {
49 ++dwMaxEscapedSize;
50 }
51
52 ++dwMaxEscapedSize;
53 }
54
55 // If we found anything in the argument that requires our argument to be quoted
56 if (fRequiresQuoting)
57 {
58 hr = StrAlloc(&sczQuotedArg, dwMaxEscapedSize + 3); // plus three for the start and end quote plus null terminator.
59 PathExitOnFailure(hr, "Failed to allocate argument to be quoted.");
60
61 LPCWSTR pwz = wzArgument;
62 LPWSTR pwzQuoted = sczQuotedArg;
63
64 *pwzQuoted = L'"';
65 ++pwzQuoted;
66 while (*pwz)
67 {
68 DWORD dwBackslashes = 0;
69 while (L'\\' == *pwz)
70 {
71 ++dwBackslashes;
72 ++pwz;
73 }
74
75 // Escape all backslashes at the end of the string.
76 if (!*pwz)
77 {
78 dwBackslashes *= 2;
79 }
80 else if (L'"' == *pwz) // escape all backslashes before the quote and escape the quote itself.
81 {
82 dwBackslashes = dwBackslashes * 2 + 1;
83 }
84 // the backslashes don't have to be escaped.
85
86 // Add the appropriate number of backslashes
87 for (DWORD i = 0; i < dwBackslashes; ++i)
88 {
89 *pwzQuoted = L'\\';
90 ++pwzQuoted;
91 }
92
93 // If there is a character, add it after all the escaped backslashes
94 if (*pwz)
95 {
96 *pwzQuoted = *pwz;
97 ++pwz;
98 ++pwzQuoted;
99 }
100 }
101
102 *pwzQuoted = L'"';
103 ++pwzQuoted;
104 *pwzQuoted = L'\0'; // ensure the arg is null terminated.
105 }
106
107 // If there is already data in the command line, append a space before appending the
108 // argument.
109 if (*psczCommandLine && **psczCommandLine)
110 {
111 hr = StrAllocConcat(psczCommandLine, L" ", 0);
112 PathExitOnFailure(hr, "Failed to append space to command line with existing data.");
113 }
114
115 hr = StrAllocConcat(psczCommandLine, sczQuotedArg ? sczQuotedArg : wzArgument, 0);
116 PathExitOnFailure(hr, "Failed to copy command line argument.");
117
118LExit:
119 ReleaseStr(sczQuotedArg);
120
121 return hr;
122}
123
124
125DAPI_(LPWSTR) PathFile(
126 __in_z LPCWSTR wzPath
127 )
128{
129 if (!wzPath)
130 {
131 return NULL;
132 }
133
134 LPWSTR wzFile = const_cast<LPWSTR>(wzPath);
135 for (LPWSTR wz = wzFile; *wz; ++wz)
136 {
137 // valid delineators
138 // \ => Windows path
139 // / => unix and URL path
140 // : => relative path from mapped root
141 if (L'\\' == *wz || L'/' == *wz || (L':' == *wz && wz == wzPath + 1))
142 {
143 wzFile = wz + 1;
144 }
145 }
146
147 return wzFile;
148}
149
150
151DAPI_(LPCWSTR) PathExtension(
152 __in_z LPCWSTR wzPath
153 )
154{
155 if (!wzPath)
156 {
157 return NULL;
158 }
159
160 // Find the last dot in the last thing that could be a file.
161 LPCWSTR wzExtension = NULL;
162 for (LPCWSTR wz = wzPath; *wz; ++wz)
163 {
164 if (L'\\' == *wz || L'/' == *wz || L':' == *wz)
165 {
166 wzExtension = NULL;
167 }
168 else if (L'.' == *wz)
169 {
170 wzExtension = wz;
171 }
172 }
173
174 return wzExtension;
175}
176
177
178DAPI_(HRESULT) PathGetDirectory(
179 __in_z LPCWSTR wzPath,
180 __out_z LPWSTR *psczDirectory
181 )
182{
183 HRESULT hr = S_OK;
184 size_t cchDirectory = SIZE_T_MAX;
185
186 for (LPCWSTR wz = wzPath; *wz; ++wz)
187 {
188 // valid delineators:
189 // \ => Windows path
190 // / => unix and URL path
191 // : => relative path from mapped root
192 if (L'\\' == *wz || L'/' == *wz || (L':' == *wz && wz == wzPath + 1))
193 {
194 cchDirectory = static_cast<size_t>(wz - wzPath) + 1;
195 }
196 }
197
198 if (SIZE_T_MAX == cchDirectory)
199 {
200 // we were given just a file name, so there's no directory available
201 return S_FALSE;
202 }
203
204 if (wzPath[0] == L'\"')
205 {
206 ++wzPath;
207 --cchDirectory;
208 }
209
210 hr = StrAllocString(psczDirectory, wzPath, cchDirectory);
211 PathExitOnFailure(hr, "Failed to copy directory.");
212
213LExit:
214 return hr;
215}
216
217
218DAPI_(HRESULT) PathGetParentPath(
219 __in_z LPCWSTR wzPath,
220 __out_z LPWSTR *psczParent
221 )
222{
223 HRESULT hr = S_OK;
224 LPCWSTR wzParent = NULL;
225
226 for (LPCWSTR wz = wzPath; *wz; ++wz)
227 {
228 if (wz[1] && (L'\\' == *wz || L'/' == *wz))
229 {
230 wzParent = wz;
231 }
232 }
233
234 if (wzParent)
235 {
236 size_t cchPath = static_cast<size_t>(wzParent - wzPath) + 1;
237
238 hr = StrAllocString(psczParent, wzPath, cchPath);
239 PathExitOnFailure(hr, "Failed to copy directory.");
240 }
241 else
242 {
243 ReleaseNullStr(psczParent);
244 }
245
246LExit:
247 return hr;
248}
249
250
251DAPI_(HRESULT) PathExpand(
252 __out LPWSTR *psczFullPath,
253 __in_z LPCWSTR wzRelativePath,
254 __in DWORD dwResolveFlags
255 )
256{
257 Assert(wzRelativePath && *wzRelativePath);
258
259 HRESULT hr = S_OK;
260 DWORD cch = 0;
261 LPWSTR sczExpandedPath = NULL;
262 DWORD cchExpandedPath = 0;
263 SIZE_T cbSize = 0;
264
265 LPWSTR sczFullPath = NULL;
266
267 //
268 // First, expand any environment variables.
269 //
270 if (dwResolveFlags & PATH_EXPAND_ENVIRONMENT)
271 {
272 cchExpandedPath = PATH_GOOD_ENOUGH;
273
274 hr = StrAlloc(&sczExpandedPath, cchExpandedPath);
275 PathExitOnFailure(hr, "Failed to allocate space for expanded path.");
276
277 cch = ::ExpandEnvironmentStringsW(wzRelativePath, sczExpandedPath, cchExpandedPath);
278 if (0 == cch)
279 {
280 PathExitWithLastError(hr, "Failed to expand environment variables in string: %ls", wzRelativePath);
281 }
282 else if (cchExpandedPath < cch)
283 {
284 cchExpandedPath = cch;
285 hr = StrAlloc(&sczExpandedPath, cchExpandedPath);
286 PathExitOnFailure(hr, "Failed to re-allocate more space for expanded path.");
287
288 cch = ::ExpandEnvironmentStringsW(wzRelativePath, sczExpandedPath, cchExpandedPath);
289 if (0 == cch)
290 {
291 PathExitWithLastError(hr, "Failed to expand environment variables in string: %ls", wzRelativePath);
292 }
293 else if (cchExpandedPath < cch)
294 {
295 hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
296 PathExitOnRootFailure(hr, "Failed to allocate buffer for expanded path.");
297 }
298 }
299
300 if (MAX_PATH < cch)
301 {
302 hr = PathPrefix(&sczExpandedPath); // ignore invald arg from path prefix because this may not be a complete path yet
303 if (E_INVALIDARG == hr)
304 {
305 hr = S_OK;
306 }
307 PathExitOnFailure(hr, "Failed to prefix long path after expanding environment variables.");
308
309 hr = StrMaxLength(sczExpandedPath, &cbSize);
310 PathExitOnFailure(hr, "Failed to get max length of expanded path.");
311
312 cchExpandedPath = (DWORD)min(DWORD_MAX, cbSize);
313 }
314 }
315
316 //
317 // Second, get the full path.
318 //
319 if (dwResolveFlags & PATH_EXPAND_FULLPATH)
320 {
321 LPWSTR wzFileName = NULL;
322 LPCWSTR wzPath = sczExpandedPath ? sczExpandedPath : wzRelativePath;
323 DWORD cchFullPath = max(PATH_GOOD_ENOUGH, cchExpandedPath);
324
325 hr = StrAlloc(&sczFullPath, cchFullPath);
326 PathExitOnFailure(hr, "Failed to allocate space for full path.");
327
328 cch = ::GetFullPathNameW(wzPath, cchFullPath, sczFullPath, &wzFileName);
329 if (0 == cch)
330 {
331 PathExitWithLastError(hr, "Failed to get full path for string: %ls", wzPath);
332 }
333 else if (cchFullPath < cch)
334 {
335 cchFullPath = cch < MAX_PATH ? cch : cch + 7; // ensure space for "\\?\UNC" prefix if needed
336 hr = StrAlloc(&sczFullPath, cchFullPath);
337 PathExitOnFailure(hr, "Failed to re-allocate more space for full path.");
338
339 cch = ::GetFullPathNameW(wzPath, cchFullPath, sczFullPath, &wzFileName);
340 if (0 == cch)
341 {
342 PathExitWithLastError(hr, "Failed to get full path for string: %ls", wzPath);
343 }
344 else if (cchFullPath < cch)
345 {
346 hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
347 PathExitOnRootFailure(hr, "Failed to allocate buffer for full path.");
348 }
349 }
350
351 if (MAX_PATH < cch)
352 {
353 hr = PathPrefix(&sczFullPath);
354 PathExitOnFailure(hr, "Failed to prefix long path after expanding.");
355 }
356 }
357 else
358 {
359 sczFullPath = sczExpandedPath;
360 sczExpandedPath = NULL;
361 }
362
363 hr = StrAllocString(psczFullPath, sczFullPath? sczFullPath : wzRelativePath, 0);
364 PathExitOnFailure(hr, "Failed to copy relative path into full path.");
365
366LExit:
367 ReleaseStr(sczFullPath);
368 ReleaseStr(sczExpandedPath);
369
370 return hr;
371}
372
373
374DAPI_(HRESULT) PathPrefix(
375 __inout LPWSTR *psczFullPath
376 )
377{
378 Assert(psczFullPath && *psczFullPath);
379
380 HRESULT hr = S_OK;
381 LPWSTR wzFullPath = *psczFullPath;
382 SIZE_T cbFullPath = 0;
383
384 if (((L'a' <= wzFullPath[0] && L'z' >= wzFullPath[0]) ||
385 (L'A' <= wzFullPath[0] && L'Z' >= wzFullPath[0])) &&
386 L':' == wzFullPath[1] &&
387 L'\\' == wzFullPath[2]) // normal path
388 {
389 hr = StrAllocPrefix(psczFullPath, L"\\\\?\\", 4);
390 PathExitOnFailure(hr, "Failed to add prefix to file path.");
391 }
392 else if (L'\\' == wzFullPath[0] && L'\\' == wzFullPath[1]) // UNC
393 {
394 // ensure that we're not already prefixed
395 if (!(L'?' == wzFullPath[2] && L'\\' == wzFullPath[3]))
396 {
397 hr = StrSize(*psczFullPath, &cbFullPath);
398 PathExitOnFailure(hr, "Failed to get size of full path.");
399
400 memmove_s(wzFullPath, cbFullPath, wzFullPath + 1, cbFullPath - sizeof(WCHAR));
401
402 hr = StrAllocPrefix(psczFullPath, L"\\\\?\\UNC", 7);
403 PathExitOnFailure(hr, "Failed to add prefix to UNC path.");
404 }
405 }
406 else
407 {
408 hr = E_INVALIDARG;
409 PathExitOnFailure(hr, "Invalid path provided to prefix: %ls.", wzFullPath);
410 }
411
412LExit:
413 return hr;
414}
415
416
417DAPI_(HRESULT) PathFixedBackslashTerminate(
418 __inout_ecount_z(cchPath) LPWSTR wzPath,
419 __in SIZE_T cchPath
420 )
421{
422 HRESULT hr = S_OK;
423 size_t cchLength = 0;
424
425 hr = ::StringCchLengthW(wzPath, cchPath, &cchLength);
426 PathExitOnFailure(hr, "Failed to get length of path.");
427
428 if (cchLength >= cchPath)
429 {
430 hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
431 }
432 else if (L'\\' != wzPath[cchLength - 1])
433 {
434 wzPath[cchLength] = L'\\';
435 wzPath[cchLength + 1] = L'\0';
436 }
437
438LExit:
439 return hr;
440}
441
442
443DAPI_(HRESULT) PathBackslashTerminate(
444 __inout LPWSTR* psczPath
445 )
446{
447 Assert(psczPath && *psczPath);
448
449 HRESULT hr = S_OK;
450 SIZE_T cchPath = 0;
451 size_t cchLength = 0;
452
453 hr = StrMaxLength(*psczPath, &cchPath);
454 PathExitOnFailure(hr, "Failed to get size of path string.");
455
456 hr = ::StringCchLengthW(*psczPath, cchPath, &cchLength);
457 PathExitOnFailure(hr, "Failed to get length of path.");
458
459 if (L'\\' != (*psczPath)[cchLength - 1])
460 {
461 hr = StrAllocConcat(psczPath, L"\\", 1);
462 PathExitOnFailure(hr, "Failed to concat backslash onto string.");
463 }
464
465LExit:
466 return hr;
467}
468
469
470DAPI_(HRESULT) PathForCurrentProcess(
471 __inout LPWSTR *psczFullPath,
472 __in_opt HMODULE hModule
473 )
474{
475 HRESULT hr = S_OK;
476 DWORD cch = MAX_PATH;
477
478 do
479 {
480 hr = StrAlloc(psczFullPath, cch);
481 PathExitOnFailure(hr, "Failed to allocate string for module path.");
482
483 DWORD cchRequired = ::GetModuleFileNameW(hModule, *psczFullPath, cch);
484 if (0 == cchRequired)
485 {
486 PathExitWithLastError(hr, "Failed to get path for executing process.");
487 }
488 else if (cchRequired == cch)
489 {
490 cch = cchRequired + 1;
491 hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
492 }
493 else
494 {
495 hr = S_OK;
496 }
497 } while (HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) == hr);
498
499LExit:
500 return hr;
501}
502
503
504DAPI_(HRESULT) PathRelativeToModule(
505 __inout LPWSTR *psczFullPath,
506 __in_opt LPCWSTR wzFileName,
507 __in_opt HMODULE hModule
508 )
509{
510 HRESULT hr = PathForCurrentProcess(psczFullPath, hModule);
511 PathExitOnFailure(hr, "Failed to get current module path.");
512
513 hr = PathGetDirectory(*psczFullPath, psczFullPath);
514 PathExitOnFailure(hr, "Failed to get current module directory.");
515
516 if (wzFileName)
517 {
518 hr = PathConcat(*psczFullPath, wzFileName, psczFullPath);
519 PathExitOnFailure(hr, "Failed to append filename.");
520 }
521
522LExit:
523 return hr;
524}
525
526
527DAPI_(HRESULT) PathCreateTempFile(
528 __in_opt LPCWSTR wzDirectory,
529 __in_opt __format_string LPCWSTR wzFileNameTemplate,
530 __in DWORD dwUniqueCount,
531 __in DWORD dwFileAttributes,
532 __out_opt LPWSTR* psczTempFile,
533 __out_opt HANDLE* phTempFile
534 )
535{
536 AssertSz(0 < dwUniqueCount, "Must specify a non-zero unique count.");
537
538 HRESULT hr = S_OK;
539
540 LPWSTR sczTempPath = NULL;
541 DWORD cchTempPath = MAX_PATH;
542
543 HANDLE hTempFile = INVALID_HANDLE_VALUE;
544 LPWSTR scz = NULL;
545 LPWSTR sczTempFile = NULL;
546
547 if (wzDirectory && *wzDirectory)
548 {
549 hr = StrAllocString(&sczTempPath, wzDirectory, 0);
550 PathExitOnFailure(hr, "Failed to copy temp path.");
551 }
552 else
553 {
554 hr = StrAlloc(&sczTempPath, cchTempPath);
555 PathExitOnFailure(hr, "Failed to allocate memory for the temp path.");
556
557 if (!::GetTempPathW(cchTempPath, sczTempPath))
558 {
559 PathExitWithLastError(hr, "Failed to get temp path.");
560 }
561 }
562
563 if (wzFileNameTemplate && *wzFileNameTemplate)
564 {
565 for (DWORD i = 1; i <= dwUniqueCount && INVALID_HANDLE_VALUE == hTempFile; ++i)
566 {
567 hr = StrAllocFormatted(&scz, wzFileNameTemplate, i);
568 PathExitOnFailure(hr, "Failed to allocate memory for file template.");
569
570 hr = StrAllocFormatted(&sczTempFile, L"%s%s", sczTempPath, scz);
571 PathExitOnFailure(hr, "Failed to allocate temp file name.");
572
573 hTempFile = ::CreateFileW(sczTempFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_NEW, dwFileAttributes, NULL);
574 if (INVALID_HANDLE_VALUE == hTempFile)
575 {
576 // if the file already exists, just try again
577 hr = HRESULT_FROM_WIN32(::GetLastError());
578 if (HRESULT_FROM_WIN32(ERROR_FILE_EXISTS) == hr)
579 {
580 hr = S_OK;
581 }
582 PathExitOnFailure(hr, "Failed to create file: %ls", sczTempFile);
583 }
584 }
585 }
586
587 // If we were not able to or we did not try to create a temp file, ask
588 // the system to provide us a temp file using its built-in mechanism.
589 if (INVALID_HANDLE_VALUE == hTempFile)
590 {
591 hr = StrAlloc(&sczTempFile, MAX_PATH);
592 PathExitOnFailure(hr, "Failed to allocate memory for the temp path");
593
594 if (!::GetTempFileNameW(sczTempPath, L"TMP", 0, sczTempFile))
595 {
596 PathExitWithLastError(hr, "Failed to create new temp file name.");
597 }
598
599 hTempFile = ::CreateFileW(sczTempFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, dwFileAttributes, NULL);
600 if (INVALID_HANDLE_VALUE == hTempFile)
601 {
602 PathExitWithLastError(hr, "Failed to open new temp file: %ls", sczTempFile);
603 }
604 }
605
606 // If the caller wanted the temp file name or handle, return them here.
607 if (psczTempFile)
608 {
609 hr = StrAllocString(psczTempFile, sczTempFile, 0);
610 PathExitOnFailure(hr, "Failed to copy temp file string.");
611 }
612
613 if (phTempFile)
614 {
615 *phTempFile = hTempFile;
616 hTempFile = INVALID_HANDLE_VALUE;
617 }
618
619LExit:
620 if (INVALID_HANDLE_VALUE != hTempFile)
621 {
622 ::CloseHandle(hTempFile);
623 }
624
625 ReleaseStr(scz);
626 ReleaseStr(sczTempFile);
627 ReleaseStr(sczTempPath);
628
629 return hr;
630}
631
632
633DAPI_(HRESULT) PathCreateTimeBasedTempFile(
634 __in_z_opt LPCWSTR wzDirectory,
635 __in_z LPCWSTR wzPrefix,
636 __in_z_opt LPCWSTR wzPostfix,
637 __in_z LPCWSTR wzExtension,
638 __deref_opt_out_z LPWSTR* psczTempFile,
639 __out_opt HANDLE* phTempFile
640 )
641{
642 HRESULT hr = S_OK;
643 BOOL fRetry = FALSE;
644 WCHAR wzTempPath[MAX_PATH] = { };
645 LPWSTR sczPrefix = NULL;
646 LPWSTR sczPrefixFolder = NULL;
647 SYSTEMTIME time = { };
648
649 LPWSTR sczTempPath = NULL;
650 HANDLE hTempFile = INVALID_HANDLE_VALUE;
651 DWORD dwAttempts = 0;
652
653 if (wzDirectory && *wzDirectory)
654 {
655 hr = PathConcat(wzDirectory, wzPrefix, &sczPrefix);
656 PathExitOnFailure(hr, "Failed to combine directory and log prefix.");
657 }
658 else
659 {
660 if (!::GetTempPathW(countof(wzTempPath), wzTempPath))
661 {
662 PathExitWithLastError(hr, "Failed to get temp folder.");
663 }
664
665 hr = PathConcat(wzTempPath, wzPrefix, &sczPrefix);
666 PathExitOnFailure(hr, "Failed to concatenate the temp folder and log prefix.");
667 }
668
669 hr = PathGetDirectory(sczPrefix, &sczPrefixFolder);
670 if (S_OK == hr)
671 {
672 hr = DirEnsureExists(sczPrefixFolder, NULL);
673 PathExitOnFailure(hr, "Failed to ensure temp file path exists: %ls", sczPrefixFolder);
674 }
675
676 if (!wzPostfix)
677 {
678 wzPostfix = L"";
679 }
680
681 do
682 {
683 fRetry = FALSE;
684 ++dwAttempts;
685
686 ::GetLocalTime(&time);
687
688 // Log format: pre YYYY MM dd hh mm ss post ext
689 hr = StrAllocFormatted(&sczTempPath, L"%ls_%04u%02u%02u%02u%02u%02u%ls%ls%ls", sczPrefix, time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond, wzPostfix, L'.' == *wzExtension ? L"" : L".", wzExtension);
690 PathExitOnFailure(hr, "failed to allocate memory for the temp path");
691
692 hTempFile = ::CreateFileW(sczTempPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
693 if (INVALID_HANDLE_VALUE == hTempFile)
694 {
695 // If the file already exists, just try again.
696 DWORD er = ::GetLastError();
697 if (ERROR_FILE_EXISTS == er || ERROR_ACCESS_DENIED == er)
698 {
699 ::Sleep(100);
700
701 if (10 > dwAttempts)
702 {
703 er = ERROR_SUCCESS;
704 fRetry = TRUE;
705 }
706 }
707
708 hr = HRESULT_FROM_WIN32(er);
709 PathExitOnFailureDebugTrace(hr, "Failed to create temp file: %ls", sczTempPath);
710 }
711 } while (fRetry);
712
713 if (psczTempFile)
714 {
715 hr = StrAllocString(psczTempFile, sczTempPath, 0);
716 PathExitOnFailure(hr, "Failed to copy temp path to return.");
717 }
718
719 if (phTempFile)
720 {
721 *phTempFile = hTempFile;
722 hTempFile = INVALID_HANDLE_VALUE;
723 }
724
725LExit:
726 ReleaseFile(hTempFile);
727 ReleaseStr(sczTempPath);
728 ReleaseStr(sczPrefixFolder);
729 ReleaseStr(sczPrefix);
730
731 return hr;
732}
733
734
735DAPI_(HRESULT) PathCreateTempDirectory(
736 __in_opt LPCWSTR wzDirectory,
737 __in __format_string LPCWSTR wzDirectoryNameTemplate,
738 __in DWORD dwUniqueCount,
739 __out LPWSTR* psczTempDirectory
740 )
741{
742 AssertSz(wzDirectoryNameTemplate && *wzDirectoryNameTemplate, "DirectoryNameTemplate must be specified.");
743 AssertSz(0 < dwUniqueCount, "Must specify a non-zero unique count.");
744
745 HRESULT hr = S_OK;
746
747 LPWSTR sczTempPath = NULL;
748 DWORD cchTempPath = MAX_PATH;
749
750 LPWSTR scz = NULL;
751
752 if (wzDirectory && *wzDirectory)
753 {
754 hr = StrAllocString(&sczTempPath, wzDirectory, 0);
755 PathExitOnFailure(hr, "Failed to copy temp path.");
756
757 hr = PathBackslashTerminate(&sczTempPath);
758 PathExitOnFailure(hr, "Failed to ensure path ends in backslash: %ls", wzDirectory);
759 }
760 else
761 {
762 hr = StrAlloc(&sczTempPath, cchTempPath);
763 PathExitOnFailure(hr, "Failed to allocate memory for the temp path.");
764
765 if (!::GetTempPathW(cchTempPath, sczTempPath))
766 {
767 PathExitWithLastError(hr, "Failed to get temp path.");
768 }
769 }
770
771 for (DWORD i = 1; i <= dwUniqueCount; ++i)
772 {
773 hr = StrAllocFormatted(&scz, wzDirectoryNameTemplate, i);
774 PathExitOnFailure(hr, "Failed to allocate memory for directory name template.");
775
776 hr = StrAllocFormatted(psczTempDirectory, L"%s%s", sczTempPath, scz);
777 PathExitOnFailure(hr, "Failed to allocate temp directory name.");
778
779 if (!::CreateDirectoryW(*psczTempDirectory, NULL))
780 {
781 DWORD er = ::GetLastError();
782 if (ERROR_ALREADY_EXISTS == er)
783 {
784 hr = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS);
785 continue;
786 }
787 else if (ERROR_PATH_NOT_FOUND == er)
788 {
789 hr = DirEnsureExists(*psczTempDirectory, NULL);
790 break;
791 }
792 else
793 {
794 hr = HRESULT_FROM_WIN32(er);
795 break;
796 }
797 }
798 else
799 {
800 hr = S_OK;
801 break;
802 }
803 }
804 PathExitOnFailure(hr, "Failed to create temp directory.");
805
806 hr = PathBackslashTerminate(psczTempDirectory);
807 PathExitOnFailure(hr, "Failed to ensure temp directory is backslash terminated.");
808
809LExit:
810 ReleaseStr(scz);
811 ReleaseStr(sczTempPath);
812
813 return hr;
814}
815
816
817DAPI_(HRESULT) PathGetKnownFolder(
818 __in int csidl,
819 __out LPWSTR* psczKnownFolder
820 )
821{
822 HRESULT hr = S_OK;
823
824 hr = StrAlloc(psczKnownFolder, MAX_PATH);
825 PathExitOnFailure(hr, "Failed to allocate memory for known folder.");
826
827 hr = ::SHGetFolderPathW(NULL, csidl, NULL, SHGFP_TYPE_CURRENT, *psczKnownFolder);
828 PathExitOnFailure(hr, "Failed to get known folder path.");
829
830 hr = PathBackslashTerminate(psczKnownFolder);
831 PathExitOnFailure(hr, "Failed to ensure known folder path is backslash terminated.");
832
833LExit:
834 return hr;
835}
836
837
838DAPI_(BOOL) PathIsAbsolute(
839 __in_z LPCWSTR wzPath
840 )
841{
842 return wzPath && wzPath[0] && wzPath[1] && (wzPath[0] == L'\\') || (wzPath[1] == L':');
843}
844
845
846DAPI_(HRESULT) PathConcat(
847 __in_opt LPCWSTR wzPath1,
848 __in_opt LPCWSTR wzPath2,
849 __deref_out_z LPWSTR* psczCombined
850 )
851{
852 return PathConcatCch(wzPath1, 0, wzPath2, 0, psczCombined);
853}
854
855
856DAPI_(HRESULT) PathConcatCch(
857 __in_opt LPCWSTR wzPath1,
858 __in SIZE_T cchPath1,
859 __in_opt LPCWSTR wzPath2,
860 __in SIZE_T cchPath2,
861 __deref_out_z LPWSTR* psczCombined
862 )
863{
864 HRESULT hr = S_OK;
865
866 if (!wzPath2 || !*wzPath2)
867 {
868 hr = StrAllocString(psczCombined, wzPath1, cchPath1);
869 PathExitOnFailure(hr, "Failed to copy just path1 to output.");
870 }
871 else if (!wzPath1 || !*wzPath1 || PathIsAbsolute(wzPath2))
872 {
873 hr = StrAllocString(psczCombined, wzPath2, cchPath2);
874 PathExitOnFailure(hr, "Failed to copy just path2 to output.");
875 }
876 else
877 {
878 hr = StrAllocString(psczCombined, wzPath1, cchPath1);
879 PathExitOnFailure(hr, "Failed to copy path1 to output.");
880
881 hr = PathBackslashTerminate(psczCombined);
882 PathExitOnFailure(hr, "Failed to backslashify.");
883
884 hr = StrAllocConcat(psczCombined, wzPath2, cchPath2);
885 PathExitOnFailure(hr, "Failed to append path2 to output.");
886 }
887
888LExit:
889 return hr;
890}
891
892
893DAPI_(HRESULT) PathEnsureQuoted(
894 __inout LPWSTR* ppszPath,
895 __in BOOL fDirectory
896 )
897{
898 Assert(ppszPath && *ppszPath);
899
900 HRESULT hr = S_OK;
901 size_t cchPath = 0;
902
903 hr = ::StringCchLengthW(*ppszPath, STRSAFE_MAX_CCH, &cchPath);
904 PathExitOnFailure(hr, "Failed to get the length of the path.");
905
906 // Handle simple special cases.
907 if (0 == cchPath || (1 == cchPath && L'"' == (*ppszPath)[0]))
908 {
909 hr = StrAllocString(ppszPath, L"\"\"", 2);
910 PathExitOnFailure(hr, "Failed to allocate a quoted empty string.");
911
912 ExitFunction();
913 }
914
915 if (L'"' != (*ppszPath)[0])
916 {
917 hr = StrAllocPrefix(ppszPath, L"\"", 1);
918 PathExitOnFailure(hr, "Failed to allocate an opening quote.");
919
920 // Add a char for the opening quote.
921 ++cchPath;
922 }
923
924 if (L'"' != (*ppszPath)[cchPath - 1])
925 {
926 hr = StrAllocConcat(ppszPath, L"\"", 1);
927 PathExitOnFailure(hr, "Failed to allocate a closing quote.");
928
929 // Add a char for the closing quote.
930 ++cchPath;
931 }
932
933 if (fDirectory)
934 {
935 if (L'\\' != (*ppszPath)[cchPath - 2])
936 {
937 // Change the last char to a backslash and re-append the closing quote.
938 (*ppszPath)[cchPath - 1] = L'\\';
939
940 hr = StrAllocConcat(ppszPath, L"\"", 1);
941 PathExitOnFailure(hr, "Failed to allocate another closing quote after the backslash.");
942 }
943 }
944
945LExit:
946
947 return hr;
948}
949
950
951DAPI_(HRESULT) PathCompare(
952 __in_z LPCWSTR wzPath1,
953 __in_z LPCWSTR wzPath2,
954 __out int* pnResult
955 )
956{
957 HRESULT hr = S_OK;
958 LPWSTR sczPath1 = NULL;
959 LPWSTR sczPath2 = NULL;
960
961 hr = PathExpand(&sczPath1, wzPath1, PATH_EXPAND_ENVIRONMENT | PATH_EXPAND_FULLPATH);
962 PathExitOnFailure(hr, "Failed to expand path1.");
963
964 hr = PathExpand(&sczPath2, wzPath2, PATH_EXPAND_ENVIRONMENT | PATH_EXPAND_FULLPATH);
965 PathExitOnFailure(hr, "Failed to expand path2.");
966
967 *pnResult = ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, sczPath1, -1, sczPath2, -1);
968
969LExit:
970 ReleaseStr(sczPath2);
971 ReleaseStr(sczPath1);
972
973 return hr;
974}
975
976
977DAPI_(HRESULT) PathCompress(
978 __in_z LPCWSTR wzPath
979 )
980{
981 HRESULT hr = S_OK;
982 HANDLE hPath = INVALID_HANDLE_VALUE;
983
984 hPath = ::CreateFileW(wzPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
985 if (INVALID_HANDLE_VALUE == hPath)
986 {
987 PathExitWithLastError(hr, "Failed to open path %ls for compression.", wzPath);
988 }
989
990 DWORD dwBytesReturned = 0;
991 USHORT usCompressionFormat = COMPRESSION_FORMAT_DEFAULT;
992 if (0 == ::DeviceIoControl(hPath, FSCTL_SET_COMPRESSION, &usCompressionFormat, sizeof(usCompressionFormat), NULL, 0, &dwBytesReturned, NULL))
993 {
994 // ignore compression attempts on file systems that don't support it
995 DWORD er = ::GetLastError();
996 if (ERROR_INVALID_FUNCTION != er)
997 {
998 PathExitOnWin32Error(er, hr, "Failed to set compression state for path %ls.", wzPath);
999 }
1000 }
1001
1002LExit:
1003 ReleaseFile(hPath);
1004
1005 return hr;
1006}
1007
1008DAPI_(HRESULT) PathGetHierarchyArray(
1009 __in_z LPCWSTR wzPath,
1010 __deref_inout_ecount_opt(*pcPathArray) LPWSTR **prgsczPathArray,
1011 __inout LPUINT pcPathArray
1012 )
1013{
1014 HRESULT hr = S_OK;
1015 LPWSTR sczPathCopy = NULL;
1016 LPWSTR sczNewPathCopy = NULL;
1017 DWORD cArraySpacesNeeded = 0;
1018 size_t cchPath = 0;
1019
1020 hr = ::StringCchLengthW(wzPath, STRSAFE_MAX_LENGTH, &cchPath);
1021 PathExitOnRootFailure(hr, "Failed to get string length of path: %ls", wzPath);
1022
1023 if (!cchPath)
1024 {
1025 ExitFunction1(hr = E_INVALIDARG);
1026 }
1027
1028 for (size_t i = 0; i < cchPath; ++i)
1029 {
1030 if (wzPath[i] == L'\\')
1031 {
1032 ++cArraySpacesNeeded;
1033 }
1034 }
1035
1036 if (wzPath[cchPath - 1] != L'\\')
1037 {
1038 ++cArraySpacesNeeded;
1039 }
1040
1041 // If it's a UNC path, cut off the first three paths, 2 because it starts with a double backslash, and another because the first ("\\servername\") isn't a path.
1042 if (wzPath[0] == L'\\' && wzPath[1] == L'\\')
1043 {
1044 cArraySpacesNeeded -= 3;
1045 }
1046
1047 Assert(cArraySpacesNeeded >= 1);
1048
1049 hr = MemEnsureArraySize(reinterpret_cast<void **>(prgsczPathArray), cArraySpacesNeeded, sizeof(LPWSTR), 0);
1050 PathExitOnFailure(hr, "Failed to allocate array of size %u for parent directories", cArraySpacesNeeded);
1051 *pcPathArray = cArraySpacesNeeded;
1052
1053 hr = StrAllocString(&sczPathCopy, wzPath, 0);
1054 PathExitOnFailure(hr, "Failed to allocate copy of original path");
1055
1056 for (DWORD i = 0; i < cArraySpacesNeeded; ++i)
1057 {
1058 hr = StrAllocString((*prgsczPathArray) + cArraySpacesNeeded - 1 - i, sczPathCopy, 0);
1059 PathExitOnFailure(hr, "Failed to copy path");
1060
1061 DWORD cchPathCopy = lstrlenW(sczPathCopy);
1062
1063 // If it ends in a backslash, it's a directory path, so cut off everything the last backslash before we get the directory portion of the path
1064 if (wzPath[cchPathCopy - 1] == L'\\')
1065 {
1066 sczPathCopy[cchPathCopy - 1] = L'\0';
1067 }
1068
1069 hr = PathGetDirectory(sczPathCopy, &sczNewPathCopy);
1070 PathExitOnFailure(hr, "Failed to get directory portion of path");
1071
1072 ReleaseStr(sczPathCopy);
1073 sczPathCopy = sczNewPathCopy;
1074 sczNewPathCopy = NULL;
1075 }
1076
1077 hr = S_OK;
1078
1079LExit:
1080 ReleaseStr(sczPathCopy);
1081
1082 return hr;
1083}
diff --git a/src/libs/dutil/WixToolset.DUtil/perfutil.cpp b/src/libs/dutil/WixToolset.DUtil/perfutil.cpp
new file mode 100644
index 00000000..bc138d34
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/perfutil.cpp
@@ -0,0 +1,82 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define PerfExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_PERFUTIL, x, s, __VA_ARGS__)
8#define PerfExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_PERFUTIL, x, s, __VA_ARGS__)
9#define PerfExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_PERFUTIL, x, s, __VA_ARGS__)
10#define PerfExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_PERFUTIL, x, s, __VA_ARGS__)
11#define PerfExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_PERFUTIL, x, s, __VA_ARGS__)
12#define PerfExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_PERFUTIL, x, s, __VA_ARGS__)
13#define PerfExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_PERFUTIL, p, x, e, s, __VA_ARGS__)
14#define PerfExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_PERFUTIL, p, x, s, __VA_ARGS__)
15#define PerfExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_PERFUTIL, p, x, e, s, __VA_ARGS__)
16#define PerfExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_PERFUTIL, p, x, s, __VA_ARGS__)
17#define PerfExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_PERFUTIL, e, x, s, __VA_ARGS__)
18#define PerfExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_PERFUTIL, g, x, s, __VA_ARGS__)
19
20static BOOL vfHighPerformanceCounter = TRUE; // assume the system has a high performance counter
21static double vdFrequency = 1;
22
23
24/********************************************************************
25 PerfInitialize - initializes internal static variables
26
27********************************************************************/
28extern "C" void DAPI PerfInitialize(
29 )
30{
31 LARGE_INTEGER liFrequency = { };
32
33 //
34 // check for high perf counter
35 //
36 if (!::QueryPerformanceFrequency(&liFrequency))
37 {
38 vfHighPerformanceCounter = FALSE;
39 vdFrequency = 1000; // ticks are measured in milliseconds
40 }
41 else
42 vdFrequency = static_cast<double>(liFrequency.QuadPart);
43}
44
45
46/********************************************************************
47 PerfClickTime - resets the clicker, or returns elapsed time since last call
48
49 NOTE: if pliElapsed is NULL, resets the elapsed time
50 if pliElapsed is not NULL, returns perf number since last call to PerfClickTime()
51********************************************************************/
52extern "C" void DAPI PerfClickTime(
53 __out_opt LARGE_INTEGER* pliElapsed
54 )
55{
56 static LARGE_INTEGER liStart = { };
57 LARGE_INTEGER* pli = pliElapsed;
58
59 if (!pli) // if elapsed time time was not requested, reset the start time
60 pli = &liStart;
61
62 if (vfHighPerformanceCounter)
63 ::QueryPerformanceCounter(pli);
64 else
65 pli->QuadPart = ::GetTickCount();
66
67 if (pliElapsed)
68 pliElapsed->QuadPart -= liStart.QuadPart;
69}
70
71
72/********************************************************************
73 PerfConvertToSeconds - converts perf number to seconds
74
75********************************************************************/
76extern "C" double DAPI PerfConvertToSeconds(
77 __in const LARGE_INTEGER* pli
78 )
79{
80 Assert(0 < vdFrequency);
81 return pli->QuadPart / vdFrequency;
82}
diff --git a/src/libs/dutil/WixToolset.DUtil/polcutil.cpp b/src/libs/dutil/WixToolset.DUtil/polcutil.cpp
new file mode 100644
index 00000000..1fdfa18c
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/polcutil.cpp
@@ -0,0 +1,126 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define PolcExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_POLCUTIL, x, s, __VA_ARGS__)
8#define PolcExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_POLCUTIL, x, s, __VA_ARGS__)
9#define PolcExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_POLCUTIL, x, s, __VA_ARGS__)
10#define PolcExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_POLCUTIL, x, s, __VA_ARGS__)
11#define PolcExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_POLCUTIL, x, s, __VA_ARGS__)
12#define PolcExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_POLCUTIL, x, s, __VA_ARGS__)
13#define PolcExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_POLCUTIL, p, x, e, s, __VA_ARGS__)
14#define PolcExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_POLCUTIL, p, x, s, __VA_ARGS__)
15#define PolcExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_POLCUTIL, p, x, e, s, __VA_ARGS__)
16#define PolcExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_POLCUTIL, p, x, s, __VA_ARGS__)
17#define PolcExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_POLCUTIL, e, x, s, __VA_ARGS__)
18#define PolcExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_POLCUTIL, g, x, s, __VA_ARGS__)
19
20const LPCWSTR REGISTRY_POLICIES_KEY = L"SOFTWARE\\Policies\\";
21
22static HRESULT OpenPolicyKey(
23 __in_z LPCWSTR wzPolicyPath,
24 __out HKEY* phk
25 );
26
27
28extern "C" HRESULT DAPI PolcReadNumber(
29 __in_z LPCWSTR wzPolicyPath,
30 __in_z LPCWSTR wzPolicyName,
31 __in DWORD dwDefault,
32 __out DWORD* pdw
33 )
34{
35 HRESULT hr = S_OK;
36 HKEY hk = NULL;
37
38 hr = OpenPolicyKey(wzPolicyPath, &hk);
39 if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr)
40 {
41 ExitFunction1(hr = S_FALSE);
42 }
43 PolcExitOnFailure(hr, "Failed to open policy key: %ls", wzPolicyPath);
44
45 hr = RegReadNumber(hk, wzPolicyName, pdw);
46 if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr)
47 {
48 ExitFunction1(hr = S_FALSE);
49 }
50 PolcExitOnFailure(hr, "Failed to open policy key: %ls, name: %ls", wzPolicyPath, wzPolicyName);
51
52LExit:
53 ReleaseRegKey(hk);
54
55 if (S_FALSE == hr || FAILED(hr))
56 {
57 *pdw = dwDefault;
58 }
59
60 return hr;
61}
62
63extern "C" HRESULT DAPI PolcReadString(
64 __in_z LPCWSTR wzPolicyPath,
65 __in_z LPCWSTR wzPolicyName,
66 __in_z_opt LPCWSTR wzDefault,
67 __deref_out_z LPWSTR* pscz
68 )
69{
70 HRESULT hr = S_OK;
71 HKEY hk = NULL;
72
73 hr = OpenPolicyKey(wzPolicyPath, &hk);
74 if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr)
75 {
76 ExitFunction1(hr = S_FALSE);
77 }
78 PolcExitOnFailure(hr, "Failed to open policy key: %ls", wzPolicyPath);
79
80 hr = RegReadString(hk, wzPolicyName, pscz);
81 if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr)
82 {
83 ExitFunction1(hr = S_FALSE);
84 }
85 PolcExitOnFailure(hr, "Failed to open policy key: %ls, name: %ls", wzPolicyPath, wzPolicyName);
86
87LExit:
88 ReleaseRegKey(hk);
89
90 if (S_FALSE == hr || FAILED(hr))
91 {
92 if (NULL == wzDefault)
93 {
94 ReleaseNullStr(*pscz);
95 }
96 else
97 {
98 hr = StrAllocString(pscz, wzDefault, 0);
99 }
100 }
101
102 return hr;
103}
104
105
106// internal functions
107
108static HRESULT OpenPolicyKey(
109 __in_z LPCWSTR wzPolicyPath,
110 __out HKEY* phk
111 )
112{
113 HRESULT hr = S_OK;
114 LPWSTR sczPath = NULL;
115
116 hr = PathConcat(REGISTRY_POLICIES_KEY, wzPolicyPath, &sczPath);
117 PolcExitOnFailure(hr, "Failed to combine logging path with root path.");
118
119 hr = RegOpen(HKEY_LOCAL_MACHINE, sczPath, KEY_READ, phk);
120 PolcExitOnFailure(hr, "Failed to open policy registry key.");
121
122LExit:
123 ReleaseStr(sczPath);
124
125 return hr;
126}
diff --git a/src/libs/dutil/WixToolset.DUtil/precomp.h b/src/libs/dutil/WixToolset.DUtil/precomp.h
new file mode 100644
index 00000000..f8f3b944
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/precomp.h
@@ -0,0 +1,98 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#ifndef _WIN32_WINNT
6#define _WIN32_WINNT 0x0500
7#endif
8
9#ifndef _WIN32_MSI
10#define _WIN32_MSI 200
11#endif
12
13#define JET_VERSION 0x0501
14
15#include <WinSock2.h>
16#include <windows.h>
17#include <windowsx.h>
18#include <intsafe.h>
19#include <strsafe.h>
20#include <wininet.h>
21#include <msi.h>
22#include <msiquery.h>
23#include <psapi.h>
24#include <shlobj.h>
25#include <shlwapi.h>
26#include <gdiplus.h>
27#include <Tlhelp32.h>
28#include <lm.h>
29#include <Iads.h>
30#include <activeds.h>
31#include <richedit.h>
32#include <stddef.h>
33#include <esent.h>
34#include <ahadmin.h>
35#include <SRRestorePtAPI.h>
36#include <userenv.h>
37#include <WinIoCtl.h>
38#include <wtsapi32.h>
39#include <wuapi.h>
40#include <commctrl.h>
41#include <dbt.h>
42#include <ShellScalingApi.h>
43
44#include "dutilsources.h"
45#include "dutil.h"
46#include "verutil.h"
47#include "aclutil.h"
48#include "atomutil.h"
49#include "buffutil.h"
50#include "butil.h"
51#include "cabcutil.h"
52#include "cabutil.h"
53#include "conutil.h"
54#include "cryputil.h"
55#include "eseutil.h"
56#include "dirutil.h"
57#include "dlutil.h"
58#include "dpiutil.h"
59#include "fileutil.h"
60#include "guidutil.h"
61#include "gdiputil.h"
62#include "dictutil.h"
63#include "deputil.h" // NOTE: This must come after dictutil.h since it uses it.
64#include "inetutil.h"
65#include "iniutil.h"
66#include "jsonutil.h"
67#include "locutil.h"
68#include "logutil.h"
69#include "memutil.h" // NOTE: almost everying is inlined so there is a small .cpp file
70//#include "metautil.h" - see metautil.cpp why this *must* be commented out
71#include "monutil.h"
72#include "osutil.h"
73#include "pathutil.h"
74#include "perfutil.h"
75#include "polcutil.h"
76#include "procutil.h"
77#include "regutil.h"
78#include "resrutil.h"
79#include "reswutil.h"
80#include "rmutil.h"
81#include "rssutil.h"
82#include "apuputil.h" // NOTE: this must come after atomutil.h and rssutil.h since it uses them.
83#include "shelutil.h"
84//#include "sqlutil.h" - see sqlutil.cpp why this *must* be commented out
85#include "srputil.h"
86#include "strutil.h"
87#include "timeutil.h"
88#include "timeutil.h"
89#include "thmutil.h"
90#include "uncutil.h"
91#include "uriutil.h"
92#include "userutil.h"
93#include "wiutil.h"
94#include "wuautil.h"
95#include <comutil.h> // This header is needed for msxml2.h to compile correctly
96#include <msxml2.h> // This file is needed to include xmlutil.h
97#include "xmlutil.h"
98
diff --git a/src/libs/dutil/WixToolset.DUtil/proc2utl.cpp b/src/libs/dutil/WixToolset.DUtil/proc2utl.cpp
new file mode 100644
index 00000000..a59d2ffc
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/proc2utl.cpp
@@ -0,0 +1,83 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define ProcExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_PROCUTIL, x, s, __VA_ARGS__)
8#define ProcExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_PROCUTIL, x, s, __VA_ARGS__)
9#define ProcExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_PROCUTIL, x, s, __VA_ARGS__)
10#define ProcExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_PROCUTIL, x, s, __VA_ARGS__)
11#define ProcExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_PROCUTIL, x, s, __VA_ARGS__)
12#define ProcExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_PROCUTIL, x, s, __VA_ARGS__)
13#define ProcExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_PROCUTIL, p, x, e, s, __VA_ARGS__)
14#define ProcExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_PROCUTIL, p, x, s, __VA_ARGS__)
15#define ProcExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_PROCUTIL, p, x, e, s, __VA_ARGS__)
16#define ProcExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_PROCUTIL, p, x, s, __VA_ARGS__)
17#define ProcExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_PROCUTIL, e, x, s, __VA_ARGS__)
18#define ProcExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_PROCUTIL, g, x, s, __VA_ARGS__)
19
20/********************************************************************
21 ProcFindAllIdsFromExeName() - returns an array of process ids that are running specified executable.
22
23*******************************************************************/
24extern "C" HRESULT DAPI ProcFindAllIdsFromExeName(
25 __in_z LPCWSTR wzExeName,
26 __out DWORD** ppdwProcessIds,
27 __out DWORD* pcProcessIds
28 )
29{
30 HRESULT hr = S_OK;
31 DWORD er = ERROR_SUCCESS;
32 HANDLE hSnap = INVALID_HANDLE_VALUE;
33 BOOL fContinue = FALSE;
34 PROCESSENTRY32W peData = { sizeof(peData) };
35
36 hSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
37 if (INVALID_HANDLE_VALUE == hSnap)
38 {
39 ProcExitWithLastError(hr, "Failed to create snapshot of processes on system");
40 }
41
42 fContinue = ::Process32FirstW(hSnap, &peData);
43
44 while (fContinue)
45 {
46 if (0 == lstrcmpiW((LPCWSTR)&(peData.szExeFile), wzExeName))
47 {
48 if (!*ppdwProcessIds)
49 {
50 *ppdwProcessIds = static_cast<DWORD*>(MemAlloc(sizeof(DWORD), TRUE));
51 ProcExitOnNull(ppdwProcessIds, hr, E_OUTOFMEMORY, "Failed to allocate array for returned process IDs.");
52 }
53 else
54 {
55 DWORD* pdwReAllocReturnedPids = NULL;
56 pdwReAllocReturnedPids = static_cast<DWORD*>(MemReAlloc(*ppdwProcessIds, sizeof(DWORD) * ((*pcProcessIds) + 1), TRUE));
57 ProcExitOnNull(pdwReAllocReturnedPids, hr, E_OUTOFMEMORY, "Failed to re-allocate array for returned process IDs.");
58
59 *ppdwProcessIds = pdwReAllocReturnedPids;
60 }
61
62 (*ppdwProcessIds)[*pcProcessIds] = peData.th32ProcessID;
63 ++(*pcProcessIds);
64 }
65
66 fContinue = ::Process32NextW(hSnap, &peData);
67 }
68
69 er = ::GetLastError();
70 if (ERROR_NO_MORE_FILES == er)
71 {
72 hr = S_OK;
73 }
74 else
75 {
76 hr = HRESULT_FROM_WIN32(er);
77 }
78
79LExit:
80 ReleaseFile(hSnap);
81
82 return hr;
83}
diff --git a/src/libs/dutil/WixToolset.DUtil/proc3utl.cpp b/src/libs/dutil/WixToolset.DUtil/proc3utl.cpp
new file mode 100644
index 00000000..6d3cbc67
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/proc3utl.cpp
@@ -0,0 +1,129 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define ProcExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_PROCUTIL, x, s, __VA_ARGS__)
8#define ProcExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_PROCUTIL, x, s, __VA_ARGS__)
9#define ProcExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_PROCUTIL, x, s, __VA_ARGS__)
10#define ProcExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_PROCUTIL, x, s, __VA_ARGS__)
11#define ProcExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_PROCUTIL, x, s, __VA_ARGS__)
12#define ProcExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_PROCUTIL, x, s, __VA_ARGS__)
13#define ProcExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_PROCUTIL, p, x, e, s, __VA_ARGS__)
14#define ProcExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_PROCUTIL, p, x, s, __VA_ARGS__)
15#define ProcExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_PROCUTIL, p, x, e, s, __VA_ARGS__)
16#define ProcExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_PROCUTIL, p, x, s, __VA_ARGS__)
17#define ProcExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_PROCUTIL, e, x, s, __VA_ARGS__)
18#define ProcExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_PROCUTIL, g, x, s, __VA_ARGS__)
19
20static HRESULT GetActiveSessionUserToken(
21 __out HANDLE *phToken
22 );
23
24
25/********************************************************************
26 ProcExecuteAsInteractiveUser() - runs process as currently logged in
27 user.
28*******************************************************************/
29extern "C" HRESULT DAPI ProcExecuteAsInteractiveUser(
30 __in_z LPCWSTR wzExecutablePath,
31 __in_z LPCWSTR wzCommandLine,
32 __out HANDLE *phProcess
33 )
34{
35 HRESULT hr = S_OK;
36 HANDLE hToken = NULL;
37 LPVOID pEnvironment = NULL;
38 LPWSTR sczFullCommandLine = NULL;
39 STARTUPINFOW si = { };
40 PROCESS_INFORMATION pi = { };
41
42 hr = GetActiveSessionUserToken(&hToken);
43 ProcExitOnFailure(hr, "Failed to get active session user token.");
44
45 if (!::CreateEnvironmentBlock(&pEnvironment, hToken, FALSE))
46 {
47 ProcExitWithLastError(hr, "Failed to create environment block for UI process.");
48 }
49
50 hr = StrAllocFormatted(&sczFullCommandLine, L"\"%ls\" %ls", wzExecutablePath, wzCommandLine);
51 ProcExitOnFailure(hr, "Failed to allocate full command-line.");
52
53 si.cb = sizeof(si);
54 if (!::CreateProcessAsUserW(hToken, wzExecutablePath, sczFullCommandLine, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, pEnvironment, NULL, &si, &pi))
55 {
56 ProcExitWithLastError(hr, "Failed to create UI process: %ls", sczFullCommandLine);
57 }
58
59 *phProcess = pi.hProcess;
60 pi.hProcess = NULL;
61
62LExit:
63 ReleaseHandle(pi.hThread);
64 ReleaseHandle(pi.hProcess);
65 ReleaseStr(sczFullCommandLine);
66
67 if (pEnvironment)
68 {
69 ::DestroyEnvironmentBlock(pEnvironment);
70 }
71
72 ReleaseHandle(hToken);
73
74 return hr;
75}
76
77
78static HRESULT GetActiveSessionUserToken(
79 __out HANDLE *phToken
80 )
81{
82 HRESULT hr = S_OK;
83 PWTS_SESSION_INFO pSessionInfo = NULL;
84 DWORD cSessions = 0;
85 DWORD dwSessionId = 0;
86 BOOL fSessionFound = FALSE;
87 HANDLE hToken = NULL;
88
89 // Loop through the sessions looking for the active one.
90 if (!::WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSessionInfo, &cSessions))
91 {
92 ProcExitWithLastError(hr, "Failed to enumerate sessions.");
93 }
94
95 for (DWORD i = 0; i < cSessions; ++i)
96 {
97 if (WTSActive == pSessionInfo[i].State)
98 {
99 dwSessionId = pSessionInfo[i].SessionId;
100 fSessionFound = TRUE;
101
102 break;
103 }
104 }
105
106 if (!fSessionFound)
107 {
108 ExitFunction1(hr = E_NOTFOUND);
109 }
110
111 // Get the user token from the active session.
112 if (!::WTSQueryUserToken(dwSessionId, &hToken))
113 {
114 ProcExitWithLastError(hr, "Failed to get active session user token.");
115 }
116
117 *phToken = hToken;
118 hToken = NULL;
119
120LExit:
121 ReleaseHandle(hToken);
122
123 if (pSessionInfo)
124 {
125 ::WTSFreeMemory(pSessionInfo);
126 }
127
128 return hr;
129}
diff --git a/src/libs/dutil/WixToolset.DUtil/procutil.cpp b/src/libs/dutil/WixToolset.DUtil/procutil.cpp
new file mode 100644
index 00000000..6bfe5017
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/procutil.cpp
@@ -0,0 +1,522 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define ProcExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_PROCUTIL, x, s, __VA_ARGS__)
8#define ProcExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_PROCUTIL, x, s, __VA_ARGS__)
9#define ProcExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_PROCUTIL, x, s, __VA_ARGS__)
10#define ProcExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_PROCUTIL, x, s, __VA_ARGS__)
11#define ProcExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_PROCUTIL, x, s, __VA_ARGS__)
12#define ProcExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_PROCUTIL, x, s, __VA_ARGS__)
13#define ProcExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_PROCUTIL, p, x, e, s, __VA_ARGS__)
14#define ProcExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_PROCUTIL, p, x, s, __VA_ARGS__)
15#define ProcExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_PROCUTIL, p, x, e, s, __VA_ARGS__)
16#define ProcExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_PROCUTIL, p, x, s, __VA_ARGS__)
17#define ProcExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_PROCUTIL, e, x, s, __VA_ARGS__)
18#define ProcExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_PROCUTIL, g, x, s, __VA_ARGS__)
19
20
21// private functions
22static HRESULT CreatePipes(
23 __out HANDLE *phOutRead,
24 __out HANDLE *phOutWrite,
25 __out HANDLE *phErrWrite,
26 __out HANDLE *phInRead,
27 __out HANDLE *phInWrite
28 );
29
30static BOOL CALLBACK CloseWindowEnumCallback(
31 __in HWND hWnd,
32 __in LPARAM lParam
33 );
34
35
36extern "C" HRESULT DAPI ProcElevated(
37 __in HANDLE hProcess,
38 __out BOOL* pfElevated
39 )
40{
41 HRESULT hr = S_OK;
42 HANDLE hToken = NULL;
43 TOKEN_ELEVATION tokenElevated = { };
44 DWORD cbToken = 0;
45
46 if (!::OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
47 {
48 ProcExitWithLastError(hr, "Failed to open process token.");
49 }
50
51 if (::GetTokenInformation(hToken, TokenElevation, &tokenElevated, sizeof(TOKEN_ELEVATION), &cbToken))
52 {
53 *pfElevated = (0 != tokenElevated.TokenIsElevated);
54 }
55 else
56 {
57 DWORD er = ::GetLastError();
58 hr = HRESULT_FROM_WIN32(er);
59
60 // If it's invalid argument, this means the OS doesn't support TokenElevation, so we're not elevated.
61 if (E_INVALIDARG == hr)
62 {
63 *pfElevated = FALSE;
64 hr = S_OK;
65 }
66 else
67 {
68 ProcExitOnRootFailure(hr, "Failed to get elevation token from process.");
69 }
70 }
71
72LExit:
73 ReleaseHandle(hToken);
74
75 return hr;
76}
77
78extern "C" HRESULT DAPI ProcWow64(
79 __in HANDLE hProcess,
80 __out BOOL* pfWow64
81 )
82{
83 HRESULT hr = S_OK;
84 BOOL fIsWow64 = FALSE;
85
86 typedef BOOL(WINAPI* LPFN_ISWOW64PROCESS2)(HANDLE, USHORT *, USHORT *);
87 LPFN_ISWOW64PROCESS2 pfnIsWow64Process2 = (LPFN_ISWOW64PROCESS2)::GetProcAddress(::GetModuleHandleW(L"kernel32"), "IsWow64Process2");
88
89 if (pfnIsWow64Process2)
90 {
91 USHORT pProcessMachine = IMAGE_FILE_MACHINE_UNKNOWN;
92 if (!pfnIsWow64Process2(hProcess, &pProcessMachine, nullptr))
93 {
94 ProcExitWithLastError(hr, "Failed to check WOW64 process - IsWow64Process2.");
95 }
96
97 if (pProcessMachine != IMAGE_FILE_MACHINE_UNKNOWN)
98 {
99 fIsWow64 = TRUE;
100 }
101 }
102 else
103 {
104 typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS)(HANDLE, PBOOL);
105 LPFN_ISWOW64PROCESS pfnIsWow64Process = (LPFN_ISWOW64PROCESS)::GetProcAddress(::GetModuleHandleW(L"kernel32"), "IsWow64Process");
106
107 if (pfnIsWow64Process)
108 {
109 if (!pfnIsWow64Process(hProcess, &fIsWow64))
110 {
111 ProcExitWithLastError(hr, "Failed to check WOW64 process - IsWow64Process.");
112 }
113 }
114 }
115
116 *pfWow64 = fIsWow64;
117
118LExit:
119 return hr;
120}
121
122extern "C" HRESULT DAPI ProcDisableWowFileSystemRedirection(
123 __in PROC_FILESYSTEMREDIRECTION* pfsr
124 )
125{
126 AssertSz(!pfsr->fDisabled, "File system redirection was already disabled.");
127 HRESULT hr = S_OK;
128
129 typedef BOOL (WINAPI *LPFN_Wow64DisableWow64FsRedirection)(PVOID *);
130 LPFN_Wow64DisableWow64FsRedirection pfnWow64DisableWow64FsRedirection = (LPFN_Wow64DisableWow64FsRedirection)::GetProcAddress(::GetModuleHandleW(L"kernel32"), "Wow64DisableWow64FsRedirection");
131
132 if (!pfnWow64DisableWow64FsRedirection)
133 {
134 ExitFunction1(hr = E_NOTIMPL);
135 }
136
137 if (!pfnWow64DisableWow64FsRedirection(&pfsr->pvRevertState))
138 {
139 ProcExitWithLastError(hr, "Failed to disable file system redirection.");
140 }
141
142 pfsr->fDisabled = TRUE;
143
144LExit:
145 return hr;
146}
147
148extern "C" HRESULT DAPI ProcRevertWowFileSystemRedirection(
149 __in PROC_FILESYSTEMREDIRECTION* pfsr
150 )
151{
152 HRESULT hr = S_OK;
153
154 if (pfsr->fDisabled)
155 {
156 typedef BOOL (WINAPI *LPFN_Wow64RevertWow64FsRedirection)(PVOID);
157 LPFN_Wow64RevertWow64FsRedirection pfnWow64RevertWow64FsRedirection = (LPFN_Wow64RevertWow64FsRedirection)::GetProcAddress(::GetModuleHandleW(L"kernel32"), "Wow64RevertWow64FsRedirection");
158
159 if (!pfnWow64RevertWow64FsRedirection(pfsr->pvRevertState))
160 {
161 ProcExitWithLastError(hr, "Failed to revert file system redirection.");
162 }
163
164 pfsr->fDisabled = FALSE;
165 pfsr->pvRevertState = NULL;
166 }
167
168LExit:
169 return hr;
170}
171
172
173extern "C" HRESULT DAPI ProcExec(
174 __in_z LPCWSTR wzExecutablePath,
175 __in_z_opt LPCWSTR wzCommandLine,
176 __in int nCmdShow,
177 __out HANDLE *phProcess
178 )
179{
180 HRESULT hr = S_OK;
181 LPWSTR sczFullCommandLine = NULL;
182 STARTUPINFOW si = { };
183 PROCESS_INFORMATION pi = { };
184
185 hr = StrAllocFormatted(&sczFullCommandLine, L"\"%ls\" %ls", wzExecutablePath, wzCommandLine ? wzCommandLine : L"");
186 ProcExitOnFailure(hr, "Failed to allocate full command-line.");
187
188 si.cb = sizeof(si);
189 si.wShowWindow = static_cast<WORD>(nCmdShow);
190 if (!::CreateProcessW(wzExecutablePath, sczFullCommandLine, NULL, NULL, FALSE, 0, 0, NULL, &si, &pi))
191 {
192 ProcExitWithLastError(hr, "Failed to create process: %ls", sczFullCommandLine);
193 }
194
195 *phProcess = pi.hProcess;
196 pi.hProcess = NULL;
197
198LExit:
199 ReleaseHandle(pi.hThread);
200 ReleaseHandle(pi.hProcess);
201 ReleaseStr(sczFullCommandLine);
202
203 return hr;
204}
205
206
207/********************************************************************
208 ProcExecute() - executes a command-line.
209
210*******************************************************************/
211extern "C" HRESULT DAPI ProcExecute(
212 __in_z LPWSTR wzCommand,
213 __out HANDLE *phProcess,
214 __out_opt HANDLE *phChildStdIn,
215 __out_opt HANDLE *phChildStdOutErr
216 )
217{
218 HRESULT hr = S_OK;
219
220 PROCESS_INFORMATION pi = { };
221 STARTUPINFOW si = { };
222
223 HANDLE hOutRead = INVALID_HANDLE_VALUE;
224 HANDLE hOutWrite = INVALID_HANDLE_VALUE;
225 HANDLE hErrWrite = INVALID_HANDLE_VALUE;
226 HANDLE hInRead = INVALID_HANDLE_VALUE;
227 HANDLE hInWrite = INVALID_HANDLE_VALUE;
228
229 // Create redirect pipes.
230 hr = CreatePipes(&hOutRead, &hOutWrite, &hErrWrite, &hInRead, &hInWrite);
231 ProcExitOnFailure(hr, "failed to create output pipes");
232
233 // Set up startup structure.
234 si.cb = sizeof(STARTUPINFOW);
235 si.dwFlags = STARTF_USESTDHANDLES;
236 si.hStdInput = hInRead;
237 si.hStdOutput = hOutWrite;
238 si.hStdError = hErrWrite;
239
240#pragma prefast(push)
241#pragma prefast(disable:25028)
242 if (::CreateProcessW(NULL,
243 wzCommand, // command line
244 NULL, // security info
245 NULL, // thread info
246 TRUE, // inherit handles
247 ::GetPriorityClass(::GetCurrentProcess()) | CREATE_NO_WINDOW, // creation flags
248 NULL, // environment
249 NULL, // cur dir
250 &si,
251 &pi))
252#pragma prefast(pop)
253 {
254 // Close child process output/input handles so child doesn't hang
255 // while waiting for input from parent process.
256 ::CloseHandle(hOutWrite);
257 hOutWrite = INVALID_HANDLE_VALUE;
258
259 ::CloseHandle(hErrWrite);
260 hErrWrite = INVALID_HANDLE_VALUE;
261
262 ::CloseHandle(hInRead);
263 hInRead = INVALID_HANDLE_VALUE;
264 }
265 else
266 {
267 ProcExitWithLastError(hr, "Process failed to execute.");
268 }
269
270 *phProcess = pi.hProcess;
271 pi.hProcess = 0;
272
273 if (phChildStdIn)
274 {
275 *phChildStdIn = hInWrite;
276 hInWrite = INVALID_HANDLE_VALUE;
277 }
278
279 if (phChildStdOutErr)
280 {
281 *phChildStdOutErr = hOutRead;
282 hOutRead = INVALID_HANDLE_VALUE;
283 }
284
285LExit:
286 if (pi.hThread)
287 {
288 ::CloseHandle(pi.hThread);
289 }
290
291 if (pi.hProcess)
292 {
293 ::CloseHandle(pi.hProcess);
294 }
295
296 ReleaseFileHandle(hOutRead);
297 ReleaseFileHandle(hOutWrite);
298 ReleaseFileHandle(hErrWrite);
299 ReleaseFileHandle(hInRead);
300 ReleaseFileHandle(hInWrite);
301
302 return hr;
303}
304
305
306/********************************************************************
307 ProcWaitForCompletion() - waits for process to complete and gets return code.
308
309*******************************************************************/
310extern "C" HRESULT DAPI ProcWaitForCompletion(
311 __in HANDLE hProcess,
312 __in DWORD dwTimeout,
313 __out DWORD *pReturnCode
314 )
315{
316 HRESULT hr = S_OK;
317 DWORD er = ERROR_SUCCESS;
318
319 // Wait for everything to finish
320 er = ::WaitForSingleObject(hProcess, dwTimeout);
321 if (WAIT_FAILED == er)
322 {
323 ProcExitWithLastError(hr, "Failed to wait for process to complete.");
324 }
325 else if (WAIT_TIMEOUT == er)
326 {
327 ExitFunction1(hr = HRESULT_FROM_WIN32(er));
328 }
329
330 if (!::GetExitCodeProcess(hProcess, &er))
331 {
332 ProcExitWithLastError(hr, "Failed to get process return code.");
333 }
334
335 *pReturnCode = er;
336
337LExit:
338 return hr;
339}
340
341/********************************************************************
342 ProcWaitForIds() - waits for multiple processes to complete.
343
344*******************************************************************/
345extern "C" HRESULT DAPI ProcWaitForIds(
346 __in_ecount(cProcessIds) const DWORD rgdwProcessIds[],
347 __in DWORD cProcessIds,
348 __in DWORD dwMilliseconds
349 )
350{
351 HRESULT hr = S_OK;
352 DWORD er = ERROR_SUCCESS;
353 HANDLE hProcess = NULL;
354 HANDLE * rghProcesses = NULL;
355 DWORD cProcesses = 0;
356
357 rghProcesses = static_cast<HANDLE*>(MemAlloc(sizeof(DWORD) * cProcessIds, TRUE));
358 ProcExitOnNull(rgdwProcessIds, hr, E_OUTOFMEMORY, "Failed to allocate array for process ID Handles.");
359
360 for (DWORD i = 0; i < cProcessIds; ++i)
361 {
362 hProcess = ::OpenProcess(SYNCHRONIZE, FALSE, rgdwProcessIds[i]);
363 if (hProcess != NULL)
364 {
365 rghProcesses[cProcesses++] = hProcess;
366 }
367 }
368
369 er = ::WaitForMultipleObjects(cProcesses, rghProcesses, TRUE, dwMilliseconds);
370 if (WAIT_FAILED == er)
371 {
372 ProcExitWithLastError(hr, "Failed to wait for process to complete.");
373 }
374 else if (WAIT_TIMEOUT == er)
375 {
376 ProcExitOnWin32Error(er, hr, "Timed out while waiting for process to complete.");
377 }
378
379LExit:
380 if (rghProcesses)
381 {
382 for (DWORD i = 0; i < cProcesses; ++i)
383 {
384 if (NULL != rghProcesses[i])
385 {
386 ::CloseHandle(rghProcesses[i]);
387 }
388 }
389
390 MemFree(rghProcesses);
391 }
392
393 return hr;
394}
395
396/********************************************************************
397 ProcCloseIds() - sends WM_CLOSE messages to all process ids.
398
399*******************************************************************/
400extern "C" HRESULT DAPI ProcCloseIds(
401 __in_ecount(cProcessIds) const DWORD* pdwProcessIds,
402 __in DWORD cProcessIds
403 )
404{
405 HRESULT hr = S_OK;
406
407 for (DWORD i = 0; i < cProcessIds; ++i)
408 {
409 if (!::EnumWindows(&CloseWindowEnumCallback, pdwProcessIds[i]))
410 {
411 ProcExitWithLastError(hr, "Failed to enumerate windows.");
412 }
413 }
414
415LExit:
416 return hr;
417}
418
419
420static HRESULT CreatePipes(
421 __out HANDLE *phOutRead,
422 __out HANDLE *phOutWrite,
423 __out HANDLE *phErrWrite,
424 __out HANDLE *phInRead,
425 __out HANDLE *phInWrite
426 )
427{
428 HRESULT hr = S_OK;
429 SECURITY_ATTRIBUTES sa;
430 HANDLE hOutTemp = INVALID_HANDLE_VALUE;
431 HANDLE hInTemp = INVALID_HANDLE_VALUE;
432
433 HANDLE hOutRead = INVALID_HANDLE_VALUE;
434 HANDLE hOutWrite = INVALID_HANDLE_VALUE;
435 HANDLE hErrWrite = INVALID_HANDLE_VALUE;
436 HANDLE hInRead = INVALID_HANDLE_VALUE;
437 HANDLE hInWrite = INVALID_HANDLE_VALUE;
438
439 // Fill out security structure so we can inherit handles
440 ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
441 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
442 sa.bInheritHandle = TRUE;
443 sa.lpSecurityDescriptor = NULL;
444
445 // Create pipes
446 if (!::CreatePipe(&hOutTemp, &hOutWrite, &sa, 0))
447 {
448 ProcExitWithLastError(hr, "failed to create output pipe");
449 }
450
451 if (!::CreatePipe(&hInRead, &hInTemp, &sa, 0))
452 {
453 ProcExitWithLastError(hr, "failed to create input pipe");
454 }
455
456 // Duplicate output pipe so standard error and standard output write to the same pipe.
457 if (!::DuplicateHandle(::GetCurrentProcess(), hOutWrite, ::GetCurrentProcess(), &hErrWrite, 0, TRUE, DUPLICATE_SAME_ACCESS))
458 {
459 ProcExitWithLastError(hr, "failed to duplicate write handle");
460 }
461
462 // We need to create new "output read" and "input write" handles that are non inheritable. Otherwise CreateProcess will creates handles in
463 // the child process that can't be closed.
464 if (!::DuplicateHandle(::GetCurrentProcess(), hOutTemp, ::GetCurrentProcess(), &hOutRead, 0, FALSE, DUPLICATE_SAME_ACCESS))
465 {
466 ProcExitWithLastError(hr, "failed to duplicate output pipe");
467 }
468
469 if (!::DuplicateHandle(::GetCurrentProcess(), hInTemp, ::GetCurrentProcess(), &hInWrite, 0, FALSE, DUPLICATE_SAME_ACCESS))
470 {
471 ProcExitWithLastError(hr, "failed to duplicate input pipe");
472 }
473
474 // now that everything has succeeded, assign to the outputs
475 *phOutRead = hOutRead;
476 hOutRead = INVALID_HANDLE_VALUE;
477
478 *phOutWrite = hOutWrite;
479 hOutWrite = INVALID_HANDLE_VALUE;
480
481 *phErrWrite = hErrWrite;
482 hErrWrite = INVALID_HANDLE_VALUE;
483
484 *phInRead = hInRead;
485 hInRead = INVALID_HANDLE_VALUE;
486
487 *phInWrite = hInWrite;
488 hInWrite = INVALID_HANDLE_VALUE;
489
490LExit:
491 ReleaseFileHandle(hOutRead);
492 ReleaseFileHandle(hOutWrite);
493 ReleaseFileHandle(hErrWrite);
494 ReleaseFileHandle(hInRead);
495 ReleaseFileHandle(hInWrite);
496 ReleaseFileHandle(hOutTemp);
497 ReleaseFileHandle(hInTemp);
498
499 return hr;
500}
501
502
503/********************************************************************
504 CloseWindowEnumCallback() - outputs trace and log info
505
506*******************************************************************/
507static BOOL CALLBACK CloseWindowEnumCallback(
508 __in HWND hWnd,
509 __in LPARAM lParam
510 )
511{
512 DWORD dwPid = static_cast<DWORD>(lParam);
513 DWORD dwProcessId = 0;
514
515 ::GetWindowThreadProcessId(hWnd, &dwProcessId);
516 if (dwPid == dwProcessId)
517 {
518 ::SendMessageW(hWnd, WM_CLOSE, 0, 0);
519 }
520
521 return TRUE;
522}
diff --git a/src/libs/dutil/WixToolset.DUtil/regutil.cpp b/src/libs/dutil/WixToolset.DUtil/regutil.cpp
new file mode 100644
index 00000000..cb617932
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/regutil.cpp
@@ -0,0 +1,1035 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define RegExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_REGUTIL, x, s, __VA_ARGS__)
8#define RegExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_REGUTIL, x, s, __VA_ARGS__)
9#define RegExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_REGUTIL, x, s, __VA_ARGS__)
10#define RegExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_REGUTIL, x, s, __VA_ARGS__)
11#define RegExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_REGUTIL, x, s, __VA_ARGS__)
12#define RegExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_REGUTIL, x, s, __VA_ARGS__)
13#define RegExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_REGUTIL, p, x, e, s, __VA_ARGS__)
14#define RegExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_REGUTIL, p, x, s, __VA_ARGS__)
15#define RegExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_REGUTIL, p, x, e, s, __VA_ARGS__)
16#define RegExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_REGUTIL, p, x, s, __VA_ARGS__)
17#define RegExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_REGUTIL, e, x, s, __VA_ARGS__)
18#define RegExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_REGUTIL, g, x, s, __VA_ARGS__)
19
20static PFN_REGCREATEKEYEXW vpfnRegCreateKeyExW = ::RegCreateKeyExW;
21static PFN_REGOPENKEYEXW vpfnRegOpenKeyExW = ::RegOpenKeyExW;
22static PFN_REGDELETEKEYEXW vpfnRegDeleteKeyExW = NULL;
23static PFN_REGDELETEKEYEXW vpfnRegDeleteKeyExWFromLibrary = NULL;
24static PFN_REGDELETEKEYW vpfnRegDeleteKeyW = ::RegDeleteKeyW;
25static PFN_REGENUMKEYEXW vpfnRegEnumKeyExW = ::RegEnumKeyExW;
26static PFN_REGENUMVALUEW vpfnRegEnumValueW = ::RegEnumValueW;
27static PFN_REGQUERYINFOKEYW vpfnRegQueryInfoKeyW = ::RegQueryInfoKeyW;
28static PFN_REGQUERYVALUEEXW vpfnRegQueryValueExW = ::RegQueryValueExW;
29static PFN_REGSETVALUEEXW vpfnRegSetValueExW = ::RegSetValueExW;
30static PFN_REGDELETEVALUEW vpfnRegDeleteValueW = ::RegDeleteValueW;
31
32static HMODULE vhAdvApi32Dll = NULL;
33static BOOL vfRegInitialized = FALSE;
34
35static HRESULT WriteStringToRegistry(
36 __in HKEY hk,
37 __in_z_opt LPCWSTR wzName,
38 __in_z_opt LPCWSTR wzValue,
39 __in DWORD dwType
40);
41
42/********************************************************************
43 RegInitialize - initializes regutil
44
45*********************************************************************/
46extern "C" HRESULT DAPI RegInitialize(
47 )
48{
49 HRESULT hr = S_OK;
50
51 hr = LoadSystemLibrary(L"AdvApi32.dll", &vhAdvApi32Dll);
52 RegExitOnFailure(hr, "Failed to load AdvApi32.dll");
53
54 // ignore failures - if this doesn't exist, we'll fall back to RegDeleteKeyW
55 vpfnRegDeleteKeyExWFromLibrary = reinterpret_cast<PFN_REGDELETEKEYEXW>(::GetProcAddress(vhAdvApi32Dll, "RegDeleteKeyExW"));
56
57 if (NULL == vpfnRegDeleteKeyExW)
58 {
59 vpfnRegDeleteKeyExW = vpfnRegDeleteKeyExWFromLibrary;
60 }
61
62 vfRegInitialized = TRUE;
63
64LExit:
65 return hr;
66}
67
68
69/********************************************************************
70 RegUninitialize - uninitializes regutil
71
72*********************************************************************/
73extern "C" void DAPI RegUninitialize(
74 )
75{
76 if (vhAdvApi32Dll)
77 {
78 ::FreeLibrary(vhAdvApi32Dll);
79 vhAdvApi32Dll = NULL;
80 vpfnRegDeleteKeyExWFromLibrary = NULL;
81 vpfnRegDeleteKeyExW = NULL;
82 }
83
84 vfRegInitialized = FALSE;
85}
86
87
88/********************************************************************
89 RegFunctionOverride - overrides the registry functions. Typically used
90 for unit testing.
91
92*********************************************************************/
93extern "C" void DAPI RegFunctionOverride(
94 __in_opt PFN_REGCREATEKEYEXW pfnRegCreateKeyExW,
95 __in_opt PFN_REGOPENKEYEXW pfnRegOpenKeyExW,
96 __in_opt PFN_REGDELETEKEYEXW pfnRegDeleteKeyExW,
97 __in_opt PFN_REGENUMKEYEXW pfnRegEnumKeyExW,
98 __in_opt PFN_REGENUMVALUEW pfnRegEnumValueW,
99 __in_opt PFN_REGQUERYINFOKEYW pfnRegQueryInfoKeyW,
100 __in_opt PFN_REGQUERYVALUEEXW pfnRegQueryValueExW,
101 __in_opt PFN_REGSETVALUEEXW pfnRegSetValueExW,
102 __in_opt PFN_REGDELETEVALUEW pfnRegDeleteValueW
103 )
104{
105 vpfnRegCreateKeyExW = pfnRegCreateKeyExW ? pfnRegCreateKeyExW : ::RegCreateKeyExW;
106 vpfnRegOpenKeyExW = pfnRegOpenKeyExW ? pfnRegOpenKeyExW : ::RegOpenKeyExW;
107 vpfnRegDeleteKeyExW = pfnRegDeleteKeyExW ? pfnRegDeleteKeyExW : vpfnRegDeleteKeyExWFromLibrary;
108 vpfnRegEnumKeyExW = pfnRegEnumKeyExW ? pfnRegEnumKeyExW : ::RegEnumKeyExW;
109 vpfnRegEnumValueW = pfnRegEnumValueW ? pfnRegEnumValueW : ::RegEnumValueW;
110 vpfnRegQueryInfoKeyW = pfnRegQueryInfoKeyW ? pfnRegQueryInfoKeyW : ::RegQueryInfoKeyW;
111 vpfnRegQueryValueExW = pfnRegQueryValueExW ? pfnRegQueryValueExW : ::RegQueryValueExW;
112 vpfnRegSetValueExW = pfnRegSetValueExW ? pfnRegSetValueExW : ::RegSetValueExW;
113 vpfnRegDeleteValueW = pfnRegDeleteValueW ? pfnRegDeleteValueW : ::RegDeleteValueW;
114}
115
116
117/********************************************************************
118 RegCreate - creates a registry key.
119
120*********************************************************************/
121extern "C" HRESULT DAPI RegCreate(
122 __in HKEY hkRoot,
123 __in_z LPCWSTR wzSubKey,
124 __in DWORD dwAccess,
125 __out HKEY* phk
126 )
127{
128 HRESULT hr = S_OK;
129 DWORD er = ERROR_SUCCESS;
130
131 er = vpfnRegCreateKeyExW(hkRoot, wzSubKey, 0, NULL, REG_OPTION_NON_VOLATILE, dwAccess, NULL, phk, NULL);
132 RegExitOnWin32Error(er, hr, "Failed to create registry key.");
133
134LExit:
135 return hr;
136}
137
138
139/********************************************************************
140 RegCreate - creates a registry key with extra options.
141
142*********************************************************************/
143HRESULT DAPI RegCreateEx(
144 __in HKEY hkRoot,
145 __in_z LPCWSTR wzSubKey,
146 __in DWORD dwAccess,
147 __in BOOL fVolatile,
148 __in_opt SECURITY_ATTRIBUTES* pSecurityAttributes,
149 __out HKEY* phk,
150 __out_opt BOOL* pfCreated
151 )
152{
153 HRESULT hr = S_OK;
154 DWORD er = ERROR_SUCCESS;
155 DWORD dwDisposition;
156
157 er = vpfnRegCreateKeyExW(hkRoot, wzSubKey, 0, NULL, fVolatile ? REG_OPTION_VOLATILE : REG_OPTION_NON_VOLATILE, dwAccess, pSecurityAttributes, phk, &dwDisposition);
158 RegExitOnWin32Error(er, hr, "Failed to create registry key.");
159
160 if (pfCreated)
161 {
162 *pfCreated = (REG_CREATED_NEW_KEY == dwDisposition);
163 }
164
165LExit:
166 return hr;
167}
168
169
170/********************************************************************
171 RegOpen - opens a registry key.
172
173*********************************************************************/
174extern "C" HRESULT DAPI RegOpen(
175 __in HKEY hkRoot,
176 __in_z LPCWSTR wzSubKey,
177 __in DWORD dwAccess,
178 __out HKEY* phk
179 )
180{
181 HRESULT hr = S_OK;
182 DWORD er = ERROR_SUCCESS;
183
184 er = vpfnRegOpenKeyExW(hkRoot, wzSubKey, 0, dwAccess, phk);
185 if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er))
186 {
187 ExitFunction1(hr = E_FILENOTFOUND);
188 }
189 RegExitOnWin32Error(er, hr, "Failed to open registry key.");
190
191LExit:
192 return hr;
193}
194
195
196/********************************************************************
197 RegDelete - deletes a registry key (and optionally it's whole tree).
198
199*********************************************************************/
200extern "C" HRESULT DAPI RegDelete(
201 __in HKEY hkRoot,
202 __in_z LPCWSTR wzSubKey,
203 __in REG_KEY_BITNESS kbKeyBitness,
204 __in BOOL fDeleteTree
205 )
206{
207 HRESULT hr = S_OK;
208 DWORD er = ERROR_SUCCESS;
209 LPWSTR pszEnumeratedSubKey = NULL;
210 LPWSTR pszRecursiveSubKey = NULL;
211 HKEY hkKey = NULL;
212 REGSAM samDesired = 0;
213
214 if (!vfRegInitialized && REG_KEY_DEFAULT != kbKeyBitness)
215 {
216 hr = E_INVALIDARG;
217 RegExitOnFailure(hr, "RegInitialize must be called first in order to RegDelete() a key with non-default bit attributes!");
218 }
219
220 switch (kbKeyBitness)
221 {
222 case REG_KEY_32BIT:
223 samDesired = KEY_WOW64_32KEY;
224 break;
225 case REG_KEY_64BIT:
226 samDesired = KEY_WOW64_64KEY;
227 break;
228 case REG_KEY_DEFAULT:
229 // Nothing to do
230 break;
231 }
232
233 if (fDeleteTree)
234 {
235 hr = RegOpen(hkRoot, wzSubKey, KEY_READ | samDesired, &hkKey);
236 if (E_FILENOTFOUND == hr)
237 {
238 ExitFunction1(hr = S_OK);
239 }
240 RegExitOnFailure(hr, "Failed to open this key for enumerating subkeys: %ls", wzSubKey);
241
242 // Yes, keep enumerating the 0th item, because we're deleting it every time
243 while (E_NOMOREITEMS != (hr = RegKeyEnum(hkKey, 0, &pszEnumeratedSubKey)))
244 {
245 RegExitOnFailure(hr, "Failed to enumerate key 0");
246
247 hr = PathConcat(wzSubKey, pszEnumeratedSubKey, &pszRecursiveSubKey);
248 RegExitOnFailure(hr, "Failed to concatenate paths while recursively deleting subkeys. Path1: %ls, Path2: %ls", wzSubKey, pszEnumeratedSubKey);
249
250 hr = RegDelete(hkRoot, pszRecursiveSubKey, kbKeyBitness, fDeleteTree);
251 RegExitOnFailure(hr, "Failed to recursively delete subkey: %ls", pszRecursiveSubKey);
252 }
253
254 hr = S_OK;
255 }
256
257 if (NULL != vpfnRegDeleteKeyExW)
258 {
259 er = vpfnRegDeleteKeyExW(hkRoot, wzSubKey, samDesired, 0);
260 if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er))
261 {
262 ExitFunction1(hr = E_FILENOTFOUND);
263 }
264 RegExitOnWin32Error(er, hr, "Failed to delete registry key (ex).");
265 }
266 else
267 {
268 er = vpfnRegDeleteKeyW(hkRoot, wzSubKey);
269 if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er))
270 {
271 ExitFunction1(hr = E_FILENOTFOUND);
272 }
273 RegExitOnWin32Error(er, hr, "Failed to delete registry key.");
274 }
275
276LExit:
277 ReleaseRegKey(hkKey);
278 ReleaseStr(pszEnumeratedSubKey);
279 ReleaseStr(pszRecursiveSubKey);
280
281 return hr;
282}
283
284
285/********************************************************************
286 RegKeyEnum - enumerates child registry keys.
287
288*********************************************************************/
289extern "C" HRESULT DAPI RegKeyEnum(
290 __in HKEY hk,
291 __in DWORD dwIndex,
292 __deref_out_z LPWSTR* psczKey
293 )
294{
295 HRESULT hr = S_OK;
296 DWORD er = ERROR_SUCCESS;
297 SIZE_T cb = 0;
298 DWORD cch = 0;
299
300 if (psczKey && *psczKey)
301 {
302 hr = StrMaxLength(*psczKey, &cb);
303 RegExitOnFailure(hr, "Failed to determine length of string.");
304
305 cch = (DWORD)min(DWORD_MAX, cb);
306 }
307
308 if (2 > cch)
309 {
310 cch = 2;
311
312 hr = StrAlloc(psczKey, cch);
313 RegExitOnFailure(hr, "Failed to allocate string to minimum size.");
314 }
315
316 er = vpfnRegEnumKeyExW(hk, dwIndex, *psczKey, &cch, NULL, NULL, NULL, NULL);
317 if (ERROR_MORE_DATA == er)
318 {
319 er = vpfnRegQueryInfoKeyW(hk, NULL, NULL, NULL, NULL, &cch, NULL, NULL, NULL, NULL, NULL, NULL);
320 RegExitOnWin32Error(er, hr, "Failed to get max size of subkey name under registry key.");
321
322 ++cch; // add one because RegQueryInfoKeyW() returns the length of the subkeys without the null terminator.
323 hr = StrAlloc(psczKey, cch);
324 RegExitOnFailure(hr, "Failed to allocate string bigger for enum registry key.");
325
326 er = vpfnRegEnumKeyExW(hk, dwIndex, *psczKey, &cch, NULL, NULL, NULL, NULL);
327 }
328 else if (ERROR_NO_MORE_ITEMS == er)
329 {
330 ExitFunction1(hr = E_NOMOREITEMS);
331 }
332 RegExitOnWin32Error(er, hr, "Failed to enum registry key.");
333
334 // Always ensure the registry key name is null terminated.
335#pragma prefast(push)
336#pragma prefast(disable:26018)
337 (*psczKey)[cch] = L'\0'; // note that cch will always be one less than the size of the buffer because that's how RegEnumKeyExW() works.
338#pragma prefast(pop)
339
340LExit:
341 return hr;
342}
343
344
345/********************************************************************
346 RegValueEnum - enumerates registry values.
347
348*********************************************************************/
349HRESULT DAPI RegValueEnum(
350 __in HKEY hk,
351 __in DWORD dwIndex,
352 __deref_out_z LPWSTR* psczName,
353 __out_opt DWORD *pdwType
354 )
355{
356 HRESULT hr = S_OK;
357 DWORD er = ERROR_SUCCESS;
358 DWORD cbValueName = 0;
359
360 er = vpfnRegQueryInfoKeyW(hk, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &cbValueName, NULL, NULL, NULL);
361 RegExitOnWin32Error(er, hr, "Failed to get max size of value name under registry key.");
362
363 // Add one for null terminator
364 ++cbValueName;
365
366 hr = StrAlloc(psczName, cbValueName);
367 RegExitOnFailure(hr, "Failed to allocate array for registry value name");
368
369 er = vpfnRegEnumValueW(hk, dwIndex, *psczName, &cbValueName, NULL, pdwType, NULL, NULL);
370 if (ERROR_NO_MORE_ITEMS == er)
371 {
372 ExitFunction1(hr = E_NOMOREITEMS);
373 }
374 RegExitOnWin32Error(er, hr, "Failed to enumerate registry value");
375
376LExit:
377 return hr;
378}
379
380/********************************************************************
381 RegGetType - reads a registry key value type.
382 *********************************************************************/
383HRESULT DAPI RegGetType(
384 __in HKEY hk,
385 __in_z_opt LPCWSTR wzName,
386 __out DWORD *pdwType
387 )
388{
389 HRESULT hr = S_OK;
390 DWORD er = ERROR_SUCCESS;
391
392 er = vpfnRegQueryValueExW(hk, wzName, NULL, pdwType, NULL, NULL);
393 if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er))
394 {
395 ExitFunction1(hr = E_FILENOTFOUND);
396 }
397 RegExitOnWin32Error(er, hr, "Failed to read registry value.");
398LExit:
399
400 return hr;
401}
402
403/********************************************************************
404 RegReadBinary - reads a registry key binary value.
405 NOTE: caller is responsible for freeing *ppbBuffer
406*********************************************************************/
407HRESULT DAPI RegReadBinary(
408 __in HKEY hk,
409 __in_z_opt LPCWSTR wzName,
410 __deref_out_bcount_opt(*pcbBuffer) BYTE** ppbBuffer,
411 __out SIZE_T *pcbBuffer
412 )
413{
414 HRESULT hr = S_OK;
415 LPBYTE pbBuffer = NULL;
416 DWORD er = ERROR_SUCCESS;
417 DWORD cb = 0;
418 DWORD dwType = 0;
419
420 er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, NULL, &cb);
421 RegExitOnWin32Error(er, hr, "Failed to get size of registry value.");
422
423 // Zero-length binary values can exist
424 if (0 < cb)
425 {
426 pbBuffer = static_cast<LPBYTE>(MemAlloc(cb, FALSE));
427 RegExitOnNull(pbBuffer, hr, E_OUTOFMEMORY, "Failed to allocate buffer for binary registry value.");
428
429 er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, pbBuffer, &cb);
430 if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er))
431 {
432 ExitFunction1(hr = E_FILENOTFOUND);
433 }
434 RegExitOnWin32Error(er, hr, "Failed to read registry value.");
435 }
436
437 if (REG_BINARY == dwType)
438 {
439 *ppbBuffer = pbBuffer;
440 pbBuffer = NULL;
441 *pcbBuffer = cb;
442 }
443 else
444 {
445 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
446 RegExitOnRootFailure(hr, "Error reading binary registry value due to unexpected data type: %u", dwType);
447 }
448
449LExit:
450 ReleaseMem(pbBuffer);
451
452 return hr;
453}
454
455
456/********************************************************************
457 RegReadString - reads a registry key value as a string.
458
459*********************************************************************/
460extern "C" HRESULT DAPI RegReadString(
461 __in HKEY hk,
462 __in_z_opt LPCWSTR wzName,
463 __deref_out_z LPWSTR* psczValue
464 )
465{
466 HRESULT hr = S_OK;
467 DWORD er = ERROR_SUCCESS;
468 SIZE_T cbValue = 0;
469 DWORD cch = 0;
470 DWORD cb = 0;
471 DWORD dwType = 0;
472 LPWSTR sczExpand = NULL;
473
474 if (psczValue && *psczValue)
475 {
476 hr = StrMaxLength(*psczValue, &cbValue);
477 RegExitOnFailure(hr, "Failed to determine length of string.");
478
479 cch = (DWORD)min(DWORD_MAX, cbValue);
480 }
481
482 if (2 > cch)
483 {
484 cch = 2;
485
486 hr = StrAlloc(psczValue, cch);
487 RegExitOnFailure(hr, "Failed to allocate string to minimum size.");
488 }
489
490 cb = sizeof(WCHAR) * (cch - 1); // subtract one to ensure there will be a space at the end of the string for the null terminator.
491 er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, reinterpret_cast<LPBYTE>(*psczValue), &cb);
492 if (ERROR_MORE_DATA == er)
493 {
494 cch = cb / sizeof(WCHAR) + 1; // add one to ensure there will be space at the end for the null terminator
495 hr = StrAlloc(psczValue, cch);
496 RegExitOnFailure(hr, "Failed to allocate string bigger for registry value.");
497
498 er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, reinterpret_cast<LPBYTE>(*psczValue), &cb);
499 }
500 if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er))
501 {
502 ExitFunction1(hr = E_FILENOTFOUND);
503 }
504 RegExitOnWin32Error(er, hr, "Failed to read registry key.");
505
506 if (REG_SZ == dwType || REG_EXPAND_SZ == dwType)
507 {
508 // Always ensure the registry value is null terminated.
509 (*psczValue)[cch - 1] = L'\0';
510
511 if (REG_EXPAND_SZ == dwType)
512 {
513 hr = StrAllocString(&sczExpand, *psczValue, 0);
514 RegExitOnFailure(hr, "Failed to copy registry value to expand.");
515
516 hr = PathExpand(psczValue, sczExpand, PATH_EXPAND_ENVIRONMENT);
517 RegExitOnFailure(hr, "Failed to expand registry value: %ls", *psczValue);
518 }
519 }
520 else
521 {
522 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
523 RegExitOnRootFailure(hr, "Error reading string registry value due to unexpected data type: %u", dwType);
524 }
525
526LExit:
527 ReleaseStr(sczExpand);
528
529 return hr;
530}
531
532
533/********************************************************************
534 RegReadStringArray - reads a registry key value REG_MULTI_SZ value as a string array.
535
536*********************************************************************/
537HRESULT DAPI RegReadStringArray(
538 __in HKEY hk,
539 __in_z_opt LPCWSTR wzName,
540 __deref_out_ecount_opt(*pcStrings) LPWSTR** prgsczStrings,
541 __out DWORD *pcStrings
542 )
543{
544 HRESULT hr = S_OK;
545 DWORD er = ERROR_SUCCESS;
546 DWORD dwNullCharacters = 0;
547 DWORD dwType = 0;
548 DWORD cb = 0;
549 DWORD cch = 0;
550 LPCWSTR wzSource = NULL;
551 LPWSTR sczValue = NULL;
552
553 er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, reinterpret_cast<LPBYTE>(sczValue), &cb);
554 if (0 < cb)
555 {
556 cch = cb / sizeof(WCHAR);
557 hr = StrAlloc(&sczValue, cch);
558 RegExitOnFailure(hr, "Failed to allocate string for registry value.");
559
560 er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, reinterpret_cast<LPBYTE>(sczValue), &cb);
561 }
562 if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er))
563 {
564 ExitFunction1(hr = E_FILENOTFOUND);
565 }
566 RegExitOnWin32Error(er, hr, "Failed to read registry key.");
567
568 if (cb / sizeof(WCHAR) != cch)
569 {
570 hr = E_UNEXPECTED;
571 RegExitOnFailure(hr, "The size of registry value %ls unexpected changed between 2 reads", wzName);
572 }
573
574 if (REG_MULTI_SZ != dwType)
575 {
576 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
577 RegExitOnRootFailure(hr, "Tried to read string array, but registry value %ls is of an incorrect type", wzName);
578 }
579
580 // Value exists, but is empty, so no strings to return.
581 if (2 > cch)
582 {
583 *prgsczStrings = NULL;
584 *pcStrings = 0;
585 ExitFunction1(hr = S_OK);
586 }
587
588 // The docs specifically say if the value was written without double-null-termination, it'll get read back without it too.
589 if (L'\0' != sczValue[cch-1] || L'\0' != sczValue[cch-2])
590 {
591 hr = E_INVALIDARG;
592 RegExitOnFailure(hr, "Tried to read string array, but registry value %ls is invalid (isn't double-null-terminated)", wzName);
593 }
594
595 cch = cb / sizeof(WCHAR);
596 for (DWORD i = 0; i < cch; ++i)
597 {
598 if (L'\0' == sczValue[i])
599 {
600 ++dwNullCharacters;
601 }
602 }
603
604 // There's one string for every null character encountered (except the extra 1 at the end of the string)
605 *pcStrings = dwNullCharacters - 1;
606 hr = MemEnsureArraySize(reinterpret_cast<LPVOID *>(prgsczStrings), *pcStrings, sizeof(LPWSTR), 0);
607 RegExitOnFailure(hr, "Failed to resize array while reading REG_MULTI_SZ value");
608
609#pragma prefast(push)
610#pragma prefast(disable:26010)
611 wzSource = sczValue;
612 for (DWORD i = 0; i < *pcStrings; ++i)
613 {
614 hr = StrAllocString(&(*prgsczStrings)[i], wzSource, 0);
615 RegExitOnFailure(hr, "Failed to allocate copy of string");
616
617 // Skip past this string
618 wzSource += lstrlenW(wzSource) + 1;
619 }
620#pragma prefast(pop)
621
622LExit:
623 ReleaseStr(sczValue);
624
625 return hr;
626}
627
628
629/********************************************************************
630 RegReadVersion - reads a registry key value as a version.
631
632*********************************************************************/
633extern "C" HRESULT DAPI RegReadVersion(
634 __in HKEY hk,
635 __in_z_opt LPCWSTR wzName,
636 __out DWORD64* pdw64Version
637 )
638{
639 HRESULT hr = S_OK;
640 DWORD er = ERROR_SUCCESS;
641 DWORD dwType = 0;
642 DWORD cb = 0;
643 LPWSTR sczVersion = NULL;
644
645 cb = sizeof(DWORD64);
646 er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, reinterpret_cast<LPBYTE>(*pdw64Version), &cb);
647 if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er))
648 {
649 ExitFunction1(hr = E_FILENOTFOUND);
650 }
651 if (REG_SZ == dwType || REG_EXPAND_SZ == dwType)
652 {
653 hr = RegReadString(hk, wzName, &sczVersion);
654 RegExitOnFailure(hr, "Failed to read registry version as string.");
655
656 hr = FileVersionFromStringEx(sczVersion, 0, pdw64Version);
657 RegExitOnFailure(hr, "Failed to convert registry string to version.");
658 }
659 else if (REG_QWORD == dwType)
660 {
661 RegExitOnWin32Error(er, hr, "Failed to read registry key.");
662 }
663 else // unexpected data type
664 {
665 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
666 RegExitOnRootFailure(hr, "Error reading version registry value due to unexpected data type: %u", dwType);
667 }
668
669LExit:
670 ReleaseStr(sczVersion);
671
672 return hr;
673}
674
675
676/********************************************************************
677 RegReadNumber - reads a DWORD registry key value as a number.
678
679*********************************************************************/
680extern "C" HRESULT DAPI RegReadNumber(
681 __in HKEY hk,
682 __in_z_opt LPCWSTR wzName,
683 __out DWORD* pdwValue
684 )
685{
686 HRESULT hr = S_OK;
687 DWORD er = ERROR_SUCCESS;
688 DWORD dwType = 0;
689 DWORD cb = sizeof(DWORD);
690
691 er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, reinterpret_cast<LPBYTE>(pdwValue), &cb);
692 if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er))
693 {
694 ExitFunction1(hr = E_FILENOTFOUND);
695 }
696 RegExitOnWin32Error(er, hr, "Failed to query registry key value.");
697
698 if (REG_DWORD != dwType)
699 {
700 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
701 RegExitOnRootFailure(hr, "Error reading version registry value due to unexpected data type: %u", dwType);
702 }
703
704LExit:
705 return hr;
706}
707
708
709/********************************************************************
710 RegReadQword - reads a QWORD registry key value as a number.
711
712*********************************************************************/
713extern "C" HRESULT DAPI RegReadQword(
714 __in HKEY hk,
715 __in_z_opt LPCWSTR wzName,
716 __out DWORD64* pqwValue
717 )
718{
719 HRESULT hr = S_OK;
720 DWORD er = ERROR_SUCCESS;
721 DWORD dwType = 0;
722 DWORD cb = sizeof(DWORD64);
723
724 er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, reinterpret_cast<LPBYTE>(pqwValue), &cb);
725 if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er))
726 {
727 ExitFunction1(hr = E_FILENOTFOUND);
728 }
729 RegExitOnWin32Error(er, hr, "Failed to query registry key value.");
730
731 if (REG_QWORD != dwType)
732 {
733 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
734 RegExitOnRootFailure(hr, "Error reading version registry value due to unexpected data type: %u", dwType);
735 }
736
737LExit:
738 return hr;
739}
740
741
742/********************************************************************
743 RegWriteBinary - writes a registry key value as a binary.
744
745*********************************************************************/
746HRESULT DAPI RegWriteBinary(
747 __in HKEY hk,
748 __in_z_opt LPCWSTR wzName,
749 __in_bcount(cbBuffer) const BYTE *pbBuffer,
750 __in DWORD cbBuffer
751 )
752{
753 HRESULT hr = S_OK;
754 DWORD er = ERROR_SUCCESS;
755
756 er = vpfnRegSetValueExW(hk, wzName, 0, REG_BINARY, pbBuffer, cbBuffer);
757 RegExitOnWin32Error(er, hr, "Failed to write binary registry value with name: %ls", wzName);
758
759LExit:
760 return hr;
761}
762
763
764/********************************************************************
765RegWriteExpandString - writes a registry key value as an expand string.
766
767Note: if wzValue is NULL the value will be removed.
768*********************************************************************/
769extern "C" HRESULT DAPI RegWriteExpandString(
770 __in HKEY hk,
771 __in_z_opt LPCWSTR wzName,
772 __in_z_opt LPCWSTR wzValue
773)
774{
775 return WriteStringToRegistry(hk, wzName, wzValue, REG_EXPAND_SZ);
776}
777
778
779/********************************************************************
780 RegWriteString - writes a registry key value as a string.
781
782 Note: if wzValue is NULL the value will be removed.
783*********************************************************************/
784extern "C" HRESULT DAPI RegWriteString(
785 __in HKEY hk,
786 __in_z_opt LPCWSTR wzName,
787 __in_z_opt LPCWSTR wzValue
788 )
789{
790 return WriteStringToRegistry(hk, wzName, wzValue, REG_SZ);
791}
792
793
794/********************************************************************
795 RegWriteStringFormatted - writes a registry key value as a formatted string.
796
797*********************************************************************/
798extern "C" HRESULT DAPI RegWriteStringFormatted(
799 __in HKEY hk,
800 __in_z_opt LPCWSTR wzName,
801 __in __format_string LPCWSTR szFormat,
802 ...
803 )
804{
805 HRESULT hr = S_OK;
806 LPWSTR sczValue = NULL;
807 va_list args;
808
809 va_start(args, szFormat);
810 hr = StrAllocFormattedArgs(&sczValue, szFormat, args);
811 va_end(args);
812 RegExitOnFailure(hr, "Failed to allocate %ls value.", wzName);
813
814 hr = WriteStringToRegistry(hk, wzName, sczValue, REG_SZ);
815
816LExit:
817 ReleaseStr(sczValue);
818
819 return hr;
820}
821
822
823/********************************************************************
824 RegWriteStringArray - writes an array of strings as a REG_MULTI_SZ value
825
826*********************************************************************/
827HRESULT DAPI RegWriteStringArray(
828 __in HKEY hk,
829 __in_z_opt LPCWSTR wzName,
830 __in_ecount(cValues) LPWSTR *rgwzValues,
831 __in DWORD cValues
832 )
833{
834 HRESULT hr = S_OK;
835 DWORD er = ERROR_SUCCESS;
836 LPWSTR wzCopyDestination = NULL;
837 LPCWSTR wzWriteValue = NULL;
838 LPWSTR sczWriteValue = NULL;
839 DWORD dwTotalStringSize = 0;
840 DWORD cbTotalStringSize = 0;
841 DWORD dwTemp = 0;
842
843 if (0 == cValues)
844 {
845 wzWriteValue = L"\0";
846 }
847 else
848 {
849 // Add space for the null terminator
850 dwTotalStringSize = 1;
851
852 for (DWORD i = 0; i < cValues; ++i)
853 {
854 dwTemp = dwTotalStringSize;
855 hr = ::DWordAdd(dwTemp, 1 + lstrlenW(rgwzValues[i]), &dwTotalStringSize);
856 RegExitOnFailure(hr, "DWORD Overflow while adding length of string to write REG_MULTI_SZ");
857 }
858
859 hr = StrAlloc(&sczWriteValue, dwTotalStringSize);
860 RegExitOnFailure(hr, "Failed to allocate space for string while writing REG_MULTI_SZ");
861
862 wzCopyDestination = sczWriteValue;
863 dwTemp = dwTotalStringSize;
864 for (DWORD i = 0; i < cValues; ++i)
865 {
866 hr = ::StringCchCopyW(wzCopyDestination, dwTotalStringSize, rgwzValues[i]);
867 RegExitOnFailure(hr, "failed to copy string: %ls", rgwzValues[i]);
868
869 dwTemp -= lstrlenW(rgwzValues[i]) + 1;
870 wzCopyDestination += lstrlenW(rgwzValues[i]) + 1;
871 }
872
873 wzWriteValue = sczWriteValue;
874 }
875
876 hr = ::DWordMult(dwTotalStringSize, sizeof(WCHAR), &cbTotalStringSize);
877 RegExitOnFailure(hr, "Failed to get total string size in bytes");
878
879 er = vpfnRegSetValueExW(hk, wzName, 0, REG_MULTI_SZ, reinterpret_cast<const BYTE *>(wzWriteValue), cbTotalStringSize);
880 RegExitOnWin32Error(er, hr, "Failed to set registry value to array of strings (first string of which is): %ls", wzWriteValue);
881
882LExit:
883 ReleaseStr(sczWriteValue);
884
885 return hr;
886}
887
888/********************************************************************
889 RegWriteNumber - writes a registry key value as a number.
890
891*********************************************************************/
892extern "C" HRESULT DAPI RegWriteNumber(
893 __in HKEY hk,
894 __in_z_opt LPCWSTR wzName,
895 __in DWORD dwValue
896 )
897{
898 HRESULT hr = S_OK;
899 DWORD er = ERROR_SUCCESS;
900
901 er = vpfnRegSetValueExW(hk, wzName, 0, REG_DWORD, reinterpret_cast<const BYTE *>(&dwValue), sizeof(dwValue));
902 RegExitOnWin32Error(er, hr, "Failed to set %ls value.", wzName);
903
904LExit:
905 return hr;
906}
907
908/********************************************************************
909 RegWriteQword - writes a registry key value as a Qword.
910
911*********************************************************************/
912extern "C" HRESULT DAPI RegWriteQword(
913 __in HKEY hk,
914 __in_z_opt LPCWSTR wzName,
915 __in DWORD64 qwValue
916 )
917{
918 HRESULT hr = S_OK;
919 DWORD er = ERROR_SUCCESS;
920
921 er = vpfnRegSetValueExW(hk, wzName, 0, REG_QWORD, reinterpret_cast<const BYTE *>(&qwValue), sizeof(qwValue));
922 RegExitOnWin32Error(er, hr, "Failed to set %ls value.", wzName);
923
924LExit:
925 return hr;
926}
927
928/********************************************************************
929 RegQueryKey - queries the key for the number of subkeys and values.
930
931*********************************************************************/
932extern "C" HRESULT DAPI RegQueryKey(
933 __in HKEY hk,
934 __out_opt DWORD* pcSubKeys,
935 __out_opt DWORD* pcValues
936 )
937{
938 HRESULT hr = S_OK;
939 DWORD er = ERROR_SUCCESS;
940
941 er = vpfnRegQueryInfoKeyW(hk, NULL, NULL, NULL, pcSubKeys, NULL, NULL, pcValues, NULL, NULL, NULL, NULL);
942 RegExitOnWin32Error(er, hr, "Failed to get the number of subkeys and values under registry key.");
943
944LExit:
945 return hr;
946}
947
948/********************************************************************
949RegKeyReadNumber - reads a DWORD registry key value as a number from
950a specified subkey.
951
952*********************************************************************/
953extern "C" HRESULT DAPI RegKeyReadNumber(
954 __in HKEY hk,
955 __in_z LPCWSTR wzSubKey,
956 __in_z_opt LPCWSTR wzName,
957 __in BOOL f64Bit,
958 __out DWORD* pdwValue
959 )
960{
961 HRESULT hr = S_OK;
962 HKEY hkKey = NULL;
963
964 hr = RegOpen(hk, wzSubKey, KEY_READ | f64Bit ? KEY_WOW64_64KEY : 0, &hkKey);
965 RegExitOnFailure(hr, "Failed to open key: %ls", wzSubKey);
966
967 hr = RegReadNumber(hkKey, wzName, pdwValue);
968 RegExitOnFailure(hr, "Failed to read value: %ls/@%ls", wzSubKey, wzName);
969
970LExit:
971 ReleaseRegKey(hkKey);
972
973 return hr;
974}
975
976/********************************************************************
977RegValueExists - determines whether a named value exists in a
978specified subkey.
979
980*********************************************************************/
981extern "C" BOOL DAPI RegValueExists(
982 __in HKEY hk,
983 __in_z LPCWSTR wzSubKey,
984 __in_z_opt LPCWSTR wzName,
985 __in BOOL f64Bit
986 )
987{
988 HRESULT hr = S_OK;
989 HKEY hkKey = NULL;
990 DWORD dwType = 0;
991
992 hr = RegOpen(hk, wzSubKey, KEY_READ | f64Bit ? KEY_WOW64_64KEY : 0, &hkKey);
993 RegExitOnFailure(hr, "Failed to open key: %ls", wzSubKey);
994
995 hr = RegGetType(hkKey, wzName, &dwType);
996 RegExitOnFailure(hr, "Failed to read value type: %ls/@%ls", wzSubKey, wzName);
997
998LExit:
999 ReleaseRegKey(hkKey);
1000
1001 return SUCCEEDED(hr);
1002}
1003
1004static HRESULT WriteStringToRegistry(
1005 __in HKEY hk,
1006 __in_z_opt LPCWSTR wzName,
1007 __in_z_opt LPCWSTR wzValue,
1008 __in DWORD dwType
1009 )
1010{
1011 HRESULT hr = S_OK;
1012 DWORD er = ERROR_SUCCESS;
1013 size_t cbValue = 0;
1014
1015 if (wzValue)
1016 {
1017 hr = ::StringCbLengthW(wzValue, STRSAFE_MAX_CCH * sizeof(TCHAR), &cbValue);
1018 RegExitOnFailure(hr, "Failed to determine length of registry value: %ls", wzName);
1019
1020 er = vpfnRegSetValueExW(hk, wzName, 0, dwType, reinterpret_cast<const BYTE *>(wzValue), static_cast<DWORD>(cbValue));
1021 RegExitOnWin32Error(er, hr, "Failed to set registry value: %ls", wzName);
1022 }
1023 else
1024 {
1025 er = vpfnRegDeleteValueW(hk, wzName);
1026 if (ERROR_FILE_NOT_FOUND == er || ERROR_PATH_NOT_FOUND == er)
1027 {
1028 er = ERROR_SUCCESS;
1029 }
1030 RegExitOnWin32Error(er, hr, "Failed to delete registry value: %ls", wzName);
1031 }
1032
1033LExit:
1034 return hr;
1035}
diff --git a/src/libs/dutil/WixToolset.DUtil/resrutil.cpp b/src/libs/dutil/WixToolset.DUtil/resrutil.cpp
new file mode 100644
index 00000000..a6a7ee23
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/resrutil.cpp
@@ -0,0 +1,266 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define ResrExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_RESRUTIL, x, s, __VA_ARGS__)
8#define ResrExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_RESRUTIL, x, s, __VA_ARGS__)
9#define ResrExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_RESRUTIL, x, s, __VA_ARGS__)
10#define ResrExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_RESRUTIL, x, s, __VA_ARGS__)
11#define ResrExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_RESRUTIL, x, s, __VA_ARGS__)
12#define ResrExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_RESRUTIL, x, s, __VA_ARGS__)
13#define ResrExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_RESRUTIL, p, x, e, s, __VA_ARGS__)
14#define ResrExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_RESRUTIL, p, x, s, __VA_ARGS__)
15#define ResrExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_RESRUTIL, p, x, e, s, __VA_ARGS__)
16#define ResrExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_RESRUTIL, p, x, s, __VA_ARGS__)
17#define ResrExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_RESRUTIL, e, x, s, __VA_ARGS__)
18#define ResrExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_RESRUTIL, g, x, s, __VA_ARGS__)
19
20#define RES_STRINGS_PER_BLOCK 16
21
22
23BOOL CALLBACK EnumLangIdProc(
24 __in_opt HMODULE hModule,
25 __in_z LPCSTR lpType,
26 __in_z LPCSTR lpName,
27 __in WORD wLanguage,
28 __in LONG_PTR lParam
29 );
30
31/********************************************************************
32ResGetStringLangId - get the language id for a string in the string table.
33
34********************************************************************/
35extern "C" HRESULT DAPI ResGetStringLangId(
36 __in_opt LPCWSTR wzPath,
37 __in UINT uID,
38 __out WORD *pwLangId
39 )
40{
41 Assert(pwLangId);
42
43 HRESULT hr = S_OK;
44 HINSTANCE hModule = NULL;
45 DWORD dwBlockId = (uID / RES_STRINGS_PER_BLOCK) + 1;
46 WORD wFoundLangId = 0;
47
48 if (wzPath && *wzPath)
49 {
50 hModule = LoadLibraryExW(wzPath, NULL, DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE);
51 ResrExitOnNullWithLastError(hModule, hr, "Failed to open resource file: %ls", wzPath);
52 }
53
54#pragma prefast(push)
55#pragma prefast(disable:25068)
56 if (!::EnumResourceLanguagesA(hModule, RT_STRING, MAKEINTRESOURCE(dwBlockId), static_cast<ENUMRESLANGPROC>(EnumLangIdProc), reinterpret_cast<LONG_PTR>(&wFoundLangId)))
57#pragma prefast(pop)
58 {
59 ResrExitWithLastError(hr, "Failed to find string language identifier.");
60 }
61
62 *pwLangId = wFoundLangId;
63
64LExit:
65 if (hModule)
66 {
67 ::FreeLibrary(hModule);
68 }
69
70 return hr;
71}
72
73
74/********************************************************************
75ResReadString
76
77NOTE: ppwzString should be freed with StrFree()
78********************************************************************/
79extern "C" HRESULT DAPI ResReadString(
80 __in HINSTANCE hinst,
81 __in UINT uID,
82 __deref_out_z LPWSTR* ppwzString
83 )
84{
85 Assert(hinst && ppwzString);
86
87 HRESULT hr = S_OK;
88 DWORD cch = 64; // first guess
89 DWORD cchReturned = 0;
90
91 do
92 {
93 hr = StrAlloc(ppwzString, cch);
94 ResrExitOnFailureDebugTrace(hr, "Failed to allocate string for resource id: %d", uID);
95
96 cchReturned = ::LoadStringW(hinst, uID, *ppwzString, cch);
97 if (0 == cchReturned)
98 {
99 ResrExitWithLastError(hr, "Failed to load string resource id: %d", uID);
100 }
101
102 // if the returned string count is one character too small, it's likely we have
103 // more data to read
104 if (cchReturned + 1 == cch)
105 {
106 cch *= 2;
107 hr = S_FALSE;
108 }
109 } while (S_FALSE == hr);
110 ResrExitOnFailure(hr, "Failed to load string resource id: %d", uID);
111
112LExit:
113 return hr;
114}
115
116
117/********************************************************************
118 ResReadStringAnsi
119
120 NOTE: ppszString should be freed with StrFree()
121********************************************************************/
122extern "C" HRESULT DAPI ResReadStringAnsi(
123 __in HINSTANCE hinst,
124 __in UINT uID,
125 __deref_out_z LPSTR* ppszString
126 )
127{
128 Assert(hinst && ppszString);
129
130 HRESULT hr = S_OK;
131 DWORD cch = 64; // first guess
132 DWORD cchReturned = 0;
133
134 do
135 {
136 hr = StrAnsiAlloc(ppszString, cch);
137 ResrExitOnFailureDebugTrace(hr, "Failed to allocate string for resource id: %d", uID);
138
139#pragma prefast(push)
140#pragma prefast(disable:25068)
141 cchReturned = ::LoadStringA(hinst, uID, *ppszString, cch);
142#pragma prefast(pop)
143 if (0 == cchReturned)
144 {
145 ResrExitWithLastError(hr, "Failed to load string resource id: %d", uID);
146 }
147
148 // if the returned string count is one character too small, it's likely we have
149 // more data to read
150 if (cchReturned + 1 == cch)
151 {
152 cch *= 2;
153 hr = S_FALSE;
154 }
155 } while (S_FALSE == hr);
156 ResrExitOnFailure(hr, "failed to load string resource id: %d", uID);
157
158LExit:
159 return hr;
160}
161
162
163/********************************************************************
164ResReadData - returns a pointer to the specified resource data
165
166NOTE: there is no "free" function for this call
167********************************************************************/
168extern "C" HRESULT DAPI ResReadData(
169 __in_opt HINSTANCE hinst,
170 __in_z LPCSTR szDataName,
171 __deref_out_bcount(*pcb) PVOID *ppv,
172 __out DWORD *pcb
173 )
174{
175 Assert(szDataName);
176 Assert(ppv);
177
178 HRESULT hr = S_OK;
179 HRSRC hRsrc = NULL;
180 HGLOBAL hData = NULL;
181 DWORD cbData = 0;
182
183#pragma prefast(push)
184#pragma prefast(disable:25068)
185 hRsrc = ::FindResourceExA(hinst, RT_RCDATA, szDataName, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL));
186#pragma prefast(pop)
187 ResrExitOnNullWithLastError(hRsrc, hr, "Failed to find resource.");
188
189 hData = ::LoadResource(hinst, hRsrc);
190 ResrExitOnNullWithLastError(hData, hr, "Failed to load resource.");
191
192 cbData = ::SizeofResource(hinst, hRsrc);
193 if (!cbData)
194 {
195 ResrExitWithLastError(hr, "Failed to get size of resource.");
196 }
197
198 *ppv = ::LockResource(hData);
199 ResrExitOnNullWithLastError(*ppv, hr, "Failed to lock data resource.");
200 *pcb = cbData;
201
202LExit:
203 return hr;
204}
205
206
207/********************************************************************
208ResExportDataToFile - extracts the resource data to the specified target file
209
210********************************************************************/
211extern "C" HRESULT DAPI ResExportDataToFile(
212 __in_z LPCSTR szDataName,
213 __in_z LPCWSTR wzTargetFile,
214 __in DWORD dwCreationDisposition
215 )
216{
217 HRESULT hr = S_OK;
218 PVOID pData = NULL;
219 DWORD cbData = 0;
220 DWORD cbWritten = 0;
221 HANDLE hFile = INVALID_HANDLE_VALUE;
222 BOOL bCreatedFile = FALSE;
223
224 hr = ResReadData(NULL, szDataName, &pData, &cbData);
225 ResrExitOnFailure(hr, "Failed to GetData from %s.", szDataName);
226
227 hFile = ::CreateFileW(wzTargetFile, GENERIC_WRITE, 0, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
228 if (INVALID_HANDLE_VALUE == hFile)
229 {
230 ResrExitWithLastError(hr, "Failed to CreateFileW for %ls.", wzTargetFile);
231 }
232 bCreatedFile = TRUE;
233
234 if (!::WriteFile(hFile, pData, cbData, &cbWritten, NULL))
235 {
236 ResrExitWithLastError(hr, "Failed to ::WriteFile for %ls.", wzTargetFile);
237 }
238
239LExit:
240 ReleaseFile(hFile);
241
242 if (FAILED(hr))
243 {
244 if (bCreatedFile)
245 {
246 ::DeleteFileW(wzTargetFile);
247 }
248 }
249
250 return hr;
251}
252
253
254BOOL CALLBACK EnumLangIdProc(
255 __in_opt HMODULE /* hModule */,
256 __in_z LPCSTR /* lpType */,
257 __in_z LPCSTR /* lpName */,
258 __in WORD wLanguage,
259 __in LONG_PTR lParam
260 )
261{
262 WORD *pwLangId = reinterpret_cast<WORD*>(lParam);
263
264 *pwLangId = wLanguage;
265 return TRUE;
266}
diff --git a/src/libs/dutil/WixToolset.DUtil/reswutil.cpp b/src/libs/dutil/WixToolset.DUtil/reswutil.cpp
new file mode 100644
index 00000000..e78de84a
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/reswutil.cpp
@@ -0,0 +1,386 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define ReswExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_RESWUTIL, x, s, __VA_ARGS__)
8#define ReswExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_RESWUTIL, x, s, __VA_ARGS__)
9#define ReswExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_RESWUTIL, x, s, __VA_ARGS__)
10#define ReswExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_RESWUTIL, x, s, __VA_ARGS__)
11#define ReswExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_RESWUTIL, x, s, __VA_ARGS__)
12#define ReswExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_RESWUTIL, x, s, __VA_ARGS__)
13#define ReswExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_RESWUTIL, p, x, e, s, __VA_ARGS__)
14#define ReswExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_RESWUTIL, p, x, s, __VA_ARGS__)
15#define ReswExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_RESWUTIL, p, x, e, s, __VA_ARGS__)
16#define ReswExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_RESWUTIL, p, x, s, __VA_ARGS__)
17#define ReswExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_RESWUTIL, e, x, s, __VA_ARGS__)
18#define ReswExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_RESWUTIL, g, x, s, __VA_ARGS__)
19
20#define RES_STRINGS_PER_BLOCK 16
21
22// Internal data structure format for a string block in a resource table.
23// Note: Strings are always stored as UNICODED.
24typedef struct _RES_STRING_BLOCK
25{
26 DWORD dwBlockId;
27 WORD wLangId;
28 LPWSTR rgwz[RES_STRINGS_PER_BLOCK];
29} RES_STRING_BLOCK;
30
31
32// private functions
33static HRESULT StringBlockInitialize(
34 __in_opt HINSTANCE hModule,
35 __in DWORD dwBlockId,
36 __in WORD wLangId,
37 __in RES_STRING_BLOCK* pStrBlock
38 );
39static void StringBlockUnitialize(
40 __in RES_STRING_BLOCK* pStrBlock
41 );
42static HRESULT StringBlockChangeString(
43 __in RES_STRING_BLOCK* pStrBlock,
44 __in DWORD dwStringId,
45 __in_z LPCWSTR szData
46 );
47static HRESULT StringBlockConvertToResourceData(
48 __in const RES_STRING_BLOCK* pStrBlock,
49 __deref_out_bcount(*pcbData) LPVOID* ppvData,
50 __out DWORD* pcbData
51 );
52static HRESULT StringBlockConvertFromResourceData(
53 __in RES_STRING_BLOCK* pStrBlock,
54 __in_bcount(cbData) LPCVOID pvData,
55 __in SIZE_T cbData
56 );
57
58
59/********************************************************************
60ResWriteString - sets the string into to the specified file's resource name
61
62********************************************************************/
63extern "C" HRESULT DAPI ResWriteString(
64 __in_z LPCWSTR wzResourceFile,
65 __in DWORD dwDataId,
66 __in_z LPCWSTR wzData,
67 __in WORD wLangId
68 )
69{
70 Assert(wzResourceFile);
71 Assert(wzData);
72
73 HRESULT hr = S_OK;
74 HINSTANCE hModule = NULL;
75 HANDLE hUpdate = NULL;
76 RES_STRING_BLOCK StrBlock = { };
77 LPVOID pvData = NULL;
78 DWORD cbData = 0;
79
80 DWORD dwBlockId = (dwDataId / RES_STRINGS_PER_BLOCK) + 1;
81 DWORD dwStringId = (dwDataId % RES_STRINGS_PER_BLOCK);
82
83 hModule = LoadLibraryExW(wzResourceFile, NULL, DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE);
84 ReswExitOnNullWithLastError(hModule, hr, "Failed to load library: %ls", wzResourceFile);
85
86 hr = StringBlockInitialize(hModule, dwBlockId, wLangId, &StrBlock);
87 ReswExitOnFailure(hr, "Failed to get string block to update.");
88
89 hr = StringBlockChangeString(&StrBlock, dwStringId, wzData);
90 ReswExitOnFailure(hr, "Failed to update string block string.");
91
92 hr = StringBlockConvertToResourceData(&StrBlock, &pvData, &cbData);
93 ReswExitOnFailure(hr, "Failed to convert string block to resource data.");
94
95 ::FreeLibrary(hModule);
96 hModule = NULL;
97
98 hUpdate = ::BeginUpdateResourceW(wzResourceFile, FALSE);
99 ReswExitOnNullWithLastError(hUpdate, hr, "Failed to ::BeginUpdateResourcesW.");
100
101 if (!::UpdateResourceA(hUpdate, RT_STRING, MAKEINTRESOURCE(dwBlockId), wLangId, pvData, cbData))
102 {
103 ReswExitWithLastError(hr, "Failed to ::UpdateResourceA.");
104 }
105
106 if (!::EndUpdateResource(hUpdate, FALSE))
107 {
108 ReswExitWithLastError(hr, "Failed to ::EndUpdateResourceW.");
109 }
110
111 hUpdate = NULL;
112
113LExit:
114 ReleaseMem(pvData);
115
116 StringBlockUnitialize(&StrBlock);
117
118 if (hUpdate)
119 {
120 ::EndUpdateResource(hUpdate, TRUE);
121 }
122
123 if (hModule)
124 {
125 ::FreeLibrary(hModule);
126 }
127
128 return hr;
129}
130
131
132/********************************************************************
133ResWriteData - sets the data into to the specified file's resource name
134
135********************************************************************/
136extern "C" HRESULT DAPI ResWriteData(
137 __in_z LPCWSTR wzResourceFile,
138 __in_z LPCSTR szDataName,
139 __in PVOID pData,
140 __in DWORD cbData
141 )
142{
143 Assert(wzResourceFile);
144 Assert(szDataName);
145 Assert(pData);
146 Assert(cbData);
147
148 HRESULT hr = S_OK;
149 HANDLE hUpdate = NULL;
150
151 hUpdate = ::BeginUpdateResourceW(wzResourceFile, FALSE);
152 ReswExitOnNullWithLastError(hUpdate, hr, "Failed to ::BeginUpdateResourcesW.");
153
154 if (!::UpdateResourceA(hUpdate, RT_RCDATA, szDataName, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), pData, cbData))
155 {
156 ReswExitWithLastError(hr, "Failed to ::UpdateResourceA.");
157 }
158
159 if (!::EndUpdateResource(hUpdate, FALSE))
160 {
161 ReswExitWithLastError(hr, "Failed to ::EndUpdateResourceW.");
162 }
163
164 hUpdate = NULL;
165
166LExit:
167 if (hUpdate)
168 {
169 ::EndUpdateResource(hUpdate, TRUE);
170 }
171
172 return hr;
173}
174
175
176/********************************************************************
177ResImportDataFromFile - reads a file and sets the data into to the specified file's resource name
178
179********************************************************************/
180extern "C" HRESULT DAPI ResImportDataFromFile(
181 __in_z LPCWSTR wzTargetFile,
182 __in_z LPCWSTR wzSourceFile,
183 __in_z LPCSTR szDataName
184 )
185{
186 HRESULT hr = S_OK;
187 HANDLE hFile = INVALID_HANDLE_VALUE;
188 DWORD cbFile = 0;
189 HANDLE hMap = NULL;
190 PVOID pv = NULL;
191
192 hFile = ::CreateFileW(wzSourceFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
193 if (INVALID_HANDLE_VALUE == hFile)
194 {
195 ReswExitWithLastError(hr, "Failed to CreateFileW for %ls.", wzSourceFile);
196 }
197
198 cbFile = ::GetFileSize(hFile, NULL);
199 if (!cbFile)
200 {
201 ReswExitWithLastError(hr, "Failed to GetFileSize for %ls.", wzSourceFile);
202 }
203
204 hMap = ::CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
205 ReswExitOnNullWithLastError(hMap, hr, "Failed to CreateFileMapping for %ls.", wzSourceFile);
206
207 pv = ::MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, cbFile);
208 ReswExitOnNullWithLastError(pv, hr, "Failed to MapViewOfFile for %ls.", wzSourceFile);
209
210 hr = ResWriteData(wzTargetFile, szDataName, pv, cbFile);
211 ReswExitOnFailure(hr, "Failed to ResSetData %s on file %ls.", szDataName, wzTargetFile);
212
213LExit:
214 if (pv)
215 {
216 ::UnmapViewOfFile(pv);
217 }
218
219 if (hMap)
220 {
221 ::CloseHandle(hMap);
222 }
223
224 ReleaseFile(hFile);
225
226 return hr;
227}
228
229
230static HRESULT StringBlockInitialize(
231 __in_opt HINSTANCE hModule,
232 __in DWORD dwBlockId,
233 __in WORD wLangId,
234 __in RES_STRING_BLOCK* pStrBlock
235 )
236{
237 HRESULT hr = S_OK;
238 HRSRC hRsrc = NULL;
239 HGLOBAL hData = NULL;
240 LPCVOID pvData = NULL; // does not need to be freed
241 DWORD cbData = 0;
242
243 hRsrc = ::FindResourceExA(hModule, RT_STRING, MAKEINTRESOURCE(dwBlockId), wLangId);
244 ReswExitOnNullWithLastError(hRsrc, hr, "Failed to ::FindResourceExW.");
245
246 hData = ::LoadResource(hModule, hRsrc);
247 ReswExitOnNullWithLastError(hData, hr, "Failed to ::LoadResource.");
248
249 cbData = ::SizeofResource(hModule, hRsrc);
250 if (!cbData)
251 {
252 ReswExitWithLastError(hr, "Failed to ::SizeofResource.");
253 }
254
255 pvData = ::LockResource(hData);
256 ReswExitOnNullWithLastError(pvData, hr, "Failed to lock data resource.");
257
258 pStrBlock->dwBlockId = dwBlockId;
259 pStrBlock->wLangId = wLangId;
260
261 hr = StringBlockConvertFromResourceData(pStrBlock, pvData, cbData);
262 ReswExitOnFailure(hr, "Failed to convert string block from resource data.");
263
264LExit:
265 return hr;
266}
267
268
269static void StringBlockUnitialize(
270 __in RES_STRING_BLOCK* pStrBlock
271 )
272{
273 if (pStrBlock)
274 {
275 for (DWORD i = 0; i < RES_STRINGS_PER_BLOCK; ++i)
276 {
277 ReleaseNullMem(pStrBlock->rgwz[i]);
278 }
279 }
280}
281
282
283static HRESULT StringBlockChangeString(
284 __in RES_STRING_BLOCK* pStrBlock,
285 __in DWORD dwStringId,
286 __in_z LPCWSTR szData
287 )
288{
289 HRESULT hr = S_OK;
290 LPWSTR pwzData = NULL;
291 size_t cchData = 0;
292
293 hr = ::StringCchLengthW(szData, STRSAFE_MAX_LENGTH, &cchData);
294 ReswExitOnRootFailure(hr, "Failed to get block string length.");
295
296 pwzData = static_cast<LPWSTR>(MemAlloc((cchData + 1) * sizeof(WCHAR), TRUE));
297 ReswExitOnNull(pwzData, hr, E_OUTOFMEMORY, "Failed to allocate new block string.");
298
299 hr = ::StringCchCopyW(pwzData, cchData + 1, szData);
300 ReswExitOnRootFailure(hr, "Failed to copy new block string.");
301
302 ReleaseNullMem(pStrBlock->rgwz[dwStringId]);
303
304 pStrBlock->rgwz[dwStringId] = pwzData;
305 pwzData = NULL;
306
307LExit:
308 ReleaseMem(pwzData);
309
310 return hr;
311}
312
313
314static HRESULT StringBlockConvertToResourceData(
315 __in const RES_STRING_BLOCK* pStrBlock,
316 __deref_out_bcount(*pcbData) LPVOID* ppvData,
317 __out DWORD* pcbData
318 )
319{
320 HRESULT hr = S_OK;
321 DWORD cbData = 0;
322 LPVOID pvData = NULL;
323 WCHAR* pwz = NULL;
324
325 for (DWORD i = 0; i < RES_STRINGS_PER_BLOCK; ++i)
326 {
327 cbData += (lstrlenW(pStrBlock->rgwz[i]) + 1);
328 }
329 cbData *= sizeof(WCHAR);
330
331 pvData = MemAlloc(cbData, TRUE);
332 ReswExitOnNull(pvData, hr, E_OUTOFMEMORY, "Failed to allocate buffer to convert string block.");
333
334 pwz = static_cast<LPWSTR>(pvData);
335 for (DWORD i = 0; i < RES_STRINGS_PER_BLOCK; ++i)
336 {
337 DWORD cch = lstrlenW(pStrBlock->rgwz[i]);
338
339 *pwz = static_cast<WCHAR>(cch);
340 ++pwz;
341
342 for (DWORD j = 0; j < cch; ++j)
343 {
344 *pwz = pStrBlock->rgwz[i][j];
345 ++pwz;
346 }
347 }
348
349 *pcbData = cbData;
350 *ppvData = pvData;
351 pvData = NULL;
352
353LExit:
354 ReleaseMem(pvData);
355
356 return hr;
357}
358
359
360static HRESULT StringBlockConvertFromResourceData(
361 __in RES_STRING_BLOCK* pStrBlock,
362 __in_bcount(cbData) LPCVOID pvData,
363 __in SIZE_T cbData
364 )
365{
366 UNREFERENCED_PARAMETER(cbData);
367 HRESULT hr = S_OK;
368 LPCWSTR pwzParse = static_cast<LPCWSTR>(pvData);
369
370 for (DWORD i = 0; i < RES_STRINGS_PER_BLOCK; ++i)
371 {
372 DWORD cchParse = static_cast<DWORD>(*pwzParse);
373 ++pwzParse;
374
375 pStrBlock->rgwz[i] = static_cast<LPWSTR>(MemAlloc((cchParse + 1) * sizeof(WCHAR), TRUE));
376 ReswExitOnNull(pStrBlock->rgwz[i], hr, E_OUTOFMEMORY, "Failed to populate pStrBlock.");
377
378 hr = ::StringCchCopyNExW(pStrBlock->rgwz[i], cchParse + 1, pwzParse, cchParse, NULL, NULL, STRSAFE_FILL_BEHIND_NULL);
379 ReswExitOnFailure(hr, "Failed to copy parsed resource data into string block.");
380
381 pwzParse += cchParse;
382 }
383
384LExit:
385 return hr;
386}
diff --git a/src/libs/dutil/WixToolset.DUtil/rexutil.cpp b/src/libs/dutil/WixToolset.DUtil/rexutil.cpp
new file mode 100644
index 00000000..155ca714
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/rexutil.cpp
@@ -0,0 +1,601 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4#include "rexutil.h"
5
6
7// Exit macros
8#define RexExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_REXUTIL, x, s, __VA_ARGS__)
9#define RexExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_REXUTIL, x, s, __VA_ARGS__)
10#define RexExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_REXUTIL, x, s, __VA_ARGS__)
11#define RexExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_REXUTIL, x, s, __VA_ARGS__)
12#define RexExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_REXUTIL, x, s, __VA_ARGS__)
13#define RexExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_REXUTIL, x, s, __VA_ARGS__)
14#define RexExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_REXUTIL, p, x, e, s, __VA_ARGS__)
15#define RexExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_REXUTIL, p, x, s, __VA_ARGS__)
16#define RexExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_REXUTIL, p, x, e, s, __VA_ARGS__)
17#define RexExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_REXUTIL, p, x, s, __VA_ARGS__)
18#define RexExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_REXUTIL, e, x, s, __VA_ARGS__)
19#define RexExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_REXUTIL, g, x, s, __VA_ARGS__)
20
21//
22// static globals
23//
24static HMODULE vhCabinetDll = NULL;
25static HFDI vhfdi = NULL;
26static ERF verf;
27
28static FAKE_FILE vrgffFileTable[FILETABLESIZE];
29static DWORD vcbRes;
30static LPCBYTE vpbRes;
31static CHAR vszResource[MAX_PATH];
32static REX_CALLBACK_WRITE vpfnWrite = NULL;
33
34static HRESULT vhrLastError = S_OK;
35
36//
37// structs
38//
39struct REX_CALLBACK_STRUCT
40{
41 BOOL fStopExtracting; // flag set when no more files are needed
42 LPCWSTR pwzExtract; // file to extract ("*" means extract all)
43 LPCWSTR pwzExtractDir; // directory to extract files to
44 LPCWSTR pwzExtractName; // name of file (pwzExtract can't be "*")
45
46 // possible user data
47 REX_CALLBACK_PROGRESS pfnProgress;
48 LPVOID pvContext;
49};
50
51//
52// prototypes
53//
54static __callback LPVOID DIAMONDAPI RexAlloc(DWORD dwSize);
55static __callback void DIAMONDAPI RexFree(LPVOID pvData);
56static __callback INT_PTR FAR DIAMONDAPI RexOpen(__in_z char FAR *pszFile, int oflag, int pmode);
57static __callback UINT FAR DIAMONDAPI RexRead(INT_PTR hf, __out_bcount(cb) void FAR *pv, UINT cb);
58static __callback UINT FAR DIAMONDAPI RexWrite(INT_PTR hf, __in_bcount(cb) void FAR *pv, UINT cb);
59static __callback int FAR DIAMONDAPI RexClose(INT_PTR hf);
60static __callback long FAR DIAMONDAPI RexSeek(INT_PTR hf, long dist, int seektype);
61static __callback INT_PTR DIAMONDAPI RexCallback(FDINOTIFICATIONTYPE iNotification, FDINOTIFICATION *pFDINotify);
62
63
64/********************************************************************
65 RexInitialize - initializes internal static variables
66
67*******************************************************************/
68extern "C" HRESULT RexInitialize()
69{
70 Assert(!vhfdi);
71
72 HRESULT hr = S_OK;
73
74 vhfdi = ::FDICreate(RexAlloc, RexFree, RexOpen, RexRead, RexWrite, RexClose, RexSeek, cpuUNKNOWN, &verf);
75 if (!vhfdi)
76 {
77 hr = E_FAIL;
78 RexExitOnFailure(hr, "failed to initialize cabinet.dll"); // TODO: put verf info in trace message here
79 }
80
81 ::ZeroMemory(vrgffFileTable, sizeof(vrgffFileTable));
82
83LExit:
84 if (FAILED(hr))
85 {
86 ::FDIDestroy(vhfdi);
87 vhfdi = NULL;
88 }
89
90 return hr;
91}
92
93
94/********************************************************************
95 RexUninitialize - initializes internal static variables
96
97*******************************************************************/
98extern "C" void RexUninitialize()
99{
100 if (vhfdi)
101 {
102 ::FDIDestroy(vhfdi);
103 vhfdi = NULL;
104 }
105}
106
107
108/********************************************************************
109 RexExtract - extracts one or all files from a resource cabinet
110
111 NOTE: wzExtractId can be a single file id or "*" to extract all files
112 wzExttractDir must be normalized (end in a "\")
113 wzExtractName is ignored if wzExtractId is "*"
114*******************************************************************/
115extern "C" HRESULT RexExtract(
116 __in_z LPCSTR szResource,
117 __in_z LPCWSTR wzExtractId,
118 __in_z LPCWSTR wzExtractDir,
119 __in_z LPCWSTR wzExtractName,
120 __in REX_CALLBACK_PROGRESS pfnProgress,
121 __in REX_CALLBACK_WRITE pfnWrite,
122 __in LPVOID pvContext
123 )
124{
125 Assert(vhfdi);
126 HRESULT hr = S_OK;
127 BOOL fResult;
128
129 HRSRC hResInfo = NULL;
130 HANDLE hRes = NULL;
131
132 REX_CALLBACK_STRUCT rcs;
133
134 // remember the write callback
135 vpfnWrite = pfnWrite;
136
137 //
138 // load the cabinet resource
139 //
140 hResInfo = ::FindResourceExA(NULL, RT_RCDATA, szResource, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL));
141 RexExitOnNullWithLastError(hResInfo, hr, "Failed to find resource.");
142 //hResInfo = ::FindResourceW(NULL, wzResource, /*RT_RCDATA*/MAKEINTRESOURCEW(10));
143 //ExitOnNullWithLastError(hResInfo, hr, "failed to load resource info");
144
145 hRes = ::LoadResource(NULL, hResInfo);
146 RexExitOnNullWithLastError(hRes, hr, "failed to load resource");
147
148 vcbRes = ::SizeofResource(NULL, hResInfo);
149 vpbRes = (const BYTE*)::LockResource(hRes);
150
151 // TODO: Call FDIIsCabinet to confirm resource is a cabinet before trying to extract from it
152
153 //
154 // convert the resource name to multi-byte
155 //
156 //if (!::WideCharToMultiByte(CP_ACP, 0, wzResource, -1, vszResource, countof(vszResource), NULL, NULL))
157 //{
158 // RexExitOnLastError(hr, "failed to convert cabinet resource name to ASCII: %ls", wzResource);
159 //}
160
161 hr = ::StringCchCopyA(vszResource, countof(vszResource), szResource);
162 RexExitOnFailure(hr, "Failed to copy resource name to global.");
163
164 //
165 // iterate through files in cabinet extracting them to the callback function
166 //
167 rcs.fStopExtracting = FALSE;
168 rcs.pwzExtract = wzExtractId;
169 rcs.pwzExtractDir = wzExtractDir;
170 rcs.pwzExtractName = wzExtractName;
171 rcs.pfnProgress = pfnProgress;
172 rcs.pvContext = pvContext;
173
174 fResult = ::FDICopy(vhfdi, vszResource, "", 0, RexCallback, NULL, static_cast<void*>(&rcs));
175 if (!fResult && !rcs.fStopExtracting) // if something went wrong and it wasn't us just stopping the extraction, then return a failure
176 {
177 hr = vhrLastError; // TODO: put verf info in trace message here
178 }
179
180LExit:
181 return hr;
182}
183
184
185/****************************************************************************
186 default extract routines
187
188****************************************************************************/
189static __callback LPVOID DIAMONDAPI RexAlloc(DWORD dwSize)
190{
191 return MemAlloc(dwSize, FALSE);
192}
193
194
195static __callback void DIAMONDAPI RexFree(LPVOID pvData)
196{
197 MemFree(pvData);
198}
199
200
201static __callback INT_PTR FAR DIAMONDAPI RexOpen(__in_z char FAR *pszFile, int oflag, int pmode)
202{
203 HRESULT hr = S_OK;
204 HANDLE hFile = INVALID_HANDLE_VALUE;
205 int i = 0;
206
207 // if FDI asks for some unusual mode (__in low memory situation it could ask for a scratch file) fail
208 if ((oflag != (/*_O_BINARY*/ 0x8000 | /*_O_RDONLY*/ 0x0000)) || (pmode != (_S_IREAD | _S_IWRITE)))
209 {
210 hr = E_OUTOFMEMORY;
211 RexExitOnFailure(hr, "FDI asked for to create a scratch file, which is unusual");
212 }
213
214 // find an empty spot in the fake file table
215 for (i = 0; i < FILETABLESIZE; ++i)
216 {
217 if (!vrgffFileTable[i].fUsed)
218 {
219 break;
220 }
221 }
222
223 // we should never run out of space in the fake file table
224 if (FILETABLESIZE <= i)
225 {
226 hr = E_OUTOFMEMORY;
227 RexExitOnFailure(hr, "File table exceeded");
228 }
229
230 if (0 == lstrcmpA(vszResource, pszFile))
231 {
232 vrgffFileTable[i].fUsed = TRUE;
233 vrgffFileTable[i].fftType = MEMORY_FILE;
234 vrgffFileTable[i].mfFile.vpStart = static_cast<LPCBYTE>(vpbRes);
235 vrgffFileTable[i].mfFile.uiCurrent = 0;
236 vrgffFileTable[i].mfFile.uiLength = vcbRes;
237 }
238 else // it's a real file
239 {
240 hFile = ::CreateFileA(pszFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
241 if (INVALID_HANDLE_VALUE == hFile)
242 {
243 RexExitWithLastError(hr, "failed to open file: %s", pszFile);
244 }
245
246 vrgffFileTable[i].fUsed = TRUE;
247 vrgffFileTable[i].fftType = NORMAL_FILE;
248 vrgffFileTable[i].hFile = hFile;
249 }
250
251LExit:
252 if (FAILED(hr))
253 {
254 vhrLastError = hr;
255 }
256
257 return FAILED(hr) ? -1 : i;
258}
259
260
261static __callback UINT FAR DIAMONDAPI RexRead(INT_PTR hf, __out_bcount(cb) void FAR *pv, UINT cb)
262{
263 Assert(vrgffFileTable[hf].fUsed);
264
265 HRESULT hr = S_OK;
266 DWORD cbRead = 0;
267 DWORD cbAvailable = 0;
268
269 if (MEMORY_FILE == vrgffFileTable[hf].fftType)
270 {
271 // ensure that we don't read past the length of the resource
272 cbAvailable = vrgffFileTable[hf].mfFile.uiLength - vrgffFileTable[hf].mfFile.uiCurrent;
273 cbRead = cb < cbAvailable? cb : cbAvailable;
274
275 memcpy(pv, static_cast<const void *>(vrgffFileTable[hf].mfFile.vpStart + vrgffFileTable[hf].mfFile.uiCurrent), cbRead);
276
277 vrgffFileTable[hf].mfFile.uiCurrent += cbRead;
278 }
279 else // NORMAL_FILE
280 {
281 Assert(vrgffFileTable[hf].hFile && vrgffFileTable[hf].hFile != INVALID_HANDLE_VALUE);
282
283 if (!::ReadFile(vrgffFileTable[hf].hFile, pv, cb, &cbRead, NULL))
284 {
285 RexExitWithLastError(hr, "failed to read during cabinet extraction");
286 }
287 }
288
289LExit:
290 if (FAILED(hr))
291 {
292 vhrLastError = hr;
293 }
294
295 return FAILED(hr) ? -1 : cbRead;
296}
297
298
299static __callback UINT FAR DIAMONDAPI RexWrite(INT_PTR hf, __in_bcount(cb) void FAR *pv, UINT cb)
300{
301 Assert(vrgffFileTable[hf].fUsed);
302 Assert(vrgffFileTable[hf].fftType == NORMAL_FILE); // we should never be writing to a memory file
303
304 HRESULT hr = S_OK;
305 DWORD cbWrite = 0;
306
307 Assert(vrgffFileTable[hf].hFile && vrgffFileTable[hf].hFile != INVALID_HANDLE_VALUE);
308 if (!::WriteFile(reinterpret_cast<HANDLE>(vrgffFileTable[hf].hFile), pv, cb, &cbWrite, NULL))
309 {
310 RexExitWithLastError(hr, "failed to write during cabinet extraction");
311 }
312
313 // call the writer callback if defined
314 if (vpfnWrite)
315 {
316 vpfnWrite(cb);
317 }
318
319LExit:
320 if (FAILED(hr))
321 {
322 vhrLastError = hr;
323 }
324
325 return FAILED(hr) ? -1 : cbWrite;
326}
327
328
329static __callback long FAR DIAMONDAPI RexSeek(INT_PTR hf, long dist, int seektype)
330{
331 Assert(vrgffFileTable[hf].fUsed);
332
333 HRESULT hr = S_OK;
334 DWORD dwMoveMethod;
335 LONG lMove = 0;
336
337 switch (seektype)
338 {
339 case 0: // SEEK_SET
340 dwMoveMethod = FILE_BEGIN;
341 break;
342 case 1: /// SEEK_CUR
343 dwMoveMethod = FILE_CURRENT;
344 break;
345 case 2: // SEEK_END
346 dwMoveMethod = FILE_END;
347 break;
348 default :
349 dwMoveMethod = 0;
350 hr = E_UNEXPECTED;
351 RexExitOnFailure(hr, "unexpected seektype in FDISeek(): %d", seektype);
352 }
353
354 if (MEMORY_FILE == vrgffFileTable[hf].fftType)
355 {
356 if (FILE_BEGIN == dwMoveMethod)
357 {
358 vrgffFileTable[hf].mfFile.uiCurrent = dist;
359 }
360 else if (FILE_CURRENT == dwMoveMethod)
361 {
362 vrgffFileTable[hf].mfFile.uiCurrent += dist;
363 }
364 else // FILE_END
365 {
366 vrgffFileTable[hf].mfFile.uiCurrent = vrgffFileTable[hf].mfFile.uiLength + dist;
367 }
368
369 lMove = vrgffFileTable[hf].mfFile.uiCurrent;
370 }
371 else // NORMAL_FILE
372 {
373 Assert(vrgffFileTable[hf].hFile && vrgffFileTable[hf].hFile != INVALID_HANDLE_VALUE);
374
375 // SetFilePointer returns -1 if it fails (this will cause FDI to quit with an FDIERROR_USER_ABORT error.
376 // (Unless this happens while working on a cabinet, in which case FDI returns FDIERROR_CORRUPT_CABINET)
377 lMove = ::SetFilePointer(vrgffFileTable[hf].hFile, dist, NULL, dwMoveMethod);
378 if (0xFFFFFFFF == lMove)
379 {
380 RexExitWithLastError(hr, "failed to move file pointer %d bytes", dist);
381 }
382 }
383
384LExit:
385 if (FAILED(hr))
386 {
387 vhrLastError = hr;
388 }
389
390 return FAILED(hr) ? -1 : lMove;
391}
392
393
394__callback int FAR DIAMONDAPI RexClose(INT_PTR hf)
395{
396 Assert(vrgffFileTable[hf].fUsed);
397
398 HRESULT hr = S_OK;
399
400 if (MEMORY_FILE == vrgffFileTable[hf].fftType)
401 {
402 vrgffFileTable[hf].mfFile.vpStart = NULL;
403 vrgffFileTable[hf].mfFile.uiCurrent = 0;
404 vrgffFileTable[hf].mfFile.uiLength = 0;
405 }
406 else
407 {
408 Assert(vrgffFileTable[hf].hFile && vrgffFileTable[hf].hFile != INVALID_HANDLE_VALUE);
409
410 if (!::CloseHandle(vrgffFileTable[hf].hFile))
411 {
412 RexExitWithLastError(hr, "failed to close file during cabinet extraction");
413 }
414
415 vrgffFileTable[hf].hFile = INVALID_HANDLE_VALUE;
416 }
417
418 vrgffFileTable[hf].fUsed = FALSE;
419
420LExit:
421 if (FAILED(hr))
422 {
423 vhrLastError = hr;
424 }
425
426 return FAILED(hr) ? -1 : 0;
427}
428
429
430static __callback INT_PTR DIAMONDAPI RexCallback(FDINOTIFICATIONTYPE iNotification, FDINOTIFICATION *pFDINotify)
431{
432 Assert(pFDINotify->pv);
433
434 HRESULT hr = S_OK;
435 int ipResult = 0; // result to return on success
436 HANDLE hFile = INVALID_HANDLE_VALUE;
437
438 REX_CALLBACK_STRUCT* prcs = static_cast<REX_CALLBACK_STRUCT*>(pFDINotify->pv);
439 LPCSTR sz;
440 WCHAR wz[MAX_PATH];
441 FILETIME ft;
442 int i = 0;
443
444 switch (iNotification)
445 {
446 case fdintCOPY_FILE: // beGIN extracting a resource from cabinet
447 Assert(pFDINotify->psz1);
448
449 if (prcs->fStopExtracting)
450 {
451 ExitFunction1(hr = S_FALSE); // no more extracting
452 }
453
454 // convert params to useful variables
455 sz = static_cast<LPCSTR>(pFDINotify->psz1);
456 if (!::MultiByteToWideChar(CP_ACP, 0, sz, -1, wz, countof(wz)))
457 {
458 RexExitWithLastError(hr, "failed to convert cabinet file id to unicode: %s", sz);
459 }
460
461 if (prcs->pfnProgress)
462 {
463 hr = prcs->pfnProgress(TRUE, wz, prcs->pvContext);
464 if (S_OK != hr)
465 {
466 ExitFunction();
467 }
468 }
469
470 if (L'*' == *prcs->pwzExtract || 0 == lstrcmpW(prcs->pwzExtract, wz))
471 {
472 // get the created date for the resource in the cabinet
473 if (!::DosDateTimeToFileTime(pFDINotify->date, pFDINotify->time, &ft))
474 {
475 RexExitWithLastError(hr, "failed to get time for resource: %ls", wz);
476 }
477
478 WCHAR wzPath[MAX_PATH];
479
480 hr = ::StringCchCopyW(wzPath, countof(wzPath), prcs->pwzExtractDir);
481 RexExitOnFailure(hr, "failed to copy extract directory: %ls for file: %ls", prcs->pwzExtractDir, wz);
482
483 if (L'*' == *prcs->pwzExtract)
484 {
485 hr = ::StringCchCatW(wzPath, countof(wzPath), wz);
486 RexExitOnFailure(hr, "failed to concat onto path: %ls file: %ls", wzPath, wz);
487 }
488 else
489 {
490 Assert(*prcs->pwzExtractName);
491
492 hr = ::StringCchCatW(wzPath, countof(wzPath), prcs->pwzExtractName);
493 RexExitOnFailure(hr, "failed to concat onto path: %ls file: %ls", wzPath, prcs->pwzExtractName);
494 }
495
496 // Quickly chop off the file name part of the path to ensure the path exists
497 // then put the file name back on the path (by putting the first character
498 // back over the null terminator).
499 LPWSTR wzFile = PathFile(wzPath);
500 WCHAR wzFileFirstChar = *wzFile;
501 *wzFile = L'\0';
502
503 hr = DirEnsureExists(wzPath, NULL);
504 RexExitOnFailure(hr, "failed to ensure directory: %ls", wzPath);
505
506 hr = S_OK;
507
508 *wzFile = wzFileFirstChar;
509
510 // find an empty spot in the fake file table
511 for (i = 0; i < FILETABLESIZE; ++i)
512 {
513 if (!vrgffFileTable[i].fUsed)
514 {
515 break;
516 }
517 }
518
519 // we should never run out of space in the fake file table
520 if (FILETABLESIZE <= i)
521 {
522 hr = E_OUTOFMEMORY;
523 RexExitOnFailure(hr, "File table exceeded");
524 }
525
526 // open the file
527 hFile = ::CreateFileW(wzPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
528 if (INVALID_HANDLE_VALUE == hFile)
529 {
530 RexExitWithLastError(hr, "failed to open file: %ls", wzPath);
531 }
532
533 vrgffFileTable[i].fUsed = TRUE;
534 vrgffFileTable[i].fftType = NORMAL_FILE;
535 vrgffFileTable[i].hFile = hFile;
536
537 ipResult = i;
538
539 ::SetFileTime(vrgffFileTable[i].hFile, &ft, &ft, &ft); // try to set the file time (who cares if it fails)
540
541 if (::SetFilePointer(vrgffFileTable[i].hFile, pFDINotify->cb, NULL, FILE_BEGIN)) // try to set the end of the file (don't worry if this fails)
542 {
543 if (::SetEndOfFile(vrgffFileTable[i].hFile))
544 {
545 ::SetFilePointer(vrgffFileTable[i].hFile, 0, NULL, FILE_BEGIN); // reset the file pointer
546 }
547 }
548 }
549 else // resource wasn't requested, skip it
550 {
551 hr = S_OK;
552 ipResult = 0;
553 }
554
555 break;
556 case fdintCLOSE_FILE_INFO: // resource extraction complete
557 Assert(pFDINotify->hf && pFDINotify->psz1);
558
559 // convert params to useful variables
560 sz = static_cast<LPCSTR>(pFDINotify->psz1);
561 if (!::MultiByteToWideChar(CP_ACP, 0, sz, -1, wz, countof(wz)))
562 {
563 RexExitWithLastError(hr, "failed to convert cabinet file id to unicode: %s", sz);
564 }
565
566 RexClose(pFDINotify->hf);
567
568 if (prcs->pfnProgress)
569 {
570 hr = prcs->pfnProgress(FALSE, wz, prcs->pvContext);
571 }
572
573 if (S_OK == hr && L'*' == *prcs->pwzExtract) // if everything is okay and we're extracting all files, keep going
574 {
575 ipResult = TRUE;
576 }
577 else // something went wrong or we only needed to extract one file
578 {
579 hr = S_OK;
580 ipResult = FALSE;
581 prcs->fStopExtracting = TRUE;
582 }
583
584 break;
585 case fdintPARTIAL_FILE: __fallthrough; // no action needed for these messages, fall through
586 case fdintNEXT_CABINET: __fallthrough;
587 case fdintENUMERATE: __fallthrough;
588 case fdintCABINET_INFO:
589 break;
590 default:
591 AssertSz(FALSE, "RexCallback() - unknown FDI notification command");
592 };
593
594LExit:
595 if (FAILED(hr))
596 {
597 vhrLastError = hr;
598 }
599
600 return (S_OK == hr) ? ipResult : -1;
601}
diff --git a/src/libs/dutil/WixToolset.DUtil/rmutil.cpp b/src/libs/dutil/WixToolset.DUtil/rmutil.cpp
new file mode 100644
index 00000000..95c8c8a4
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/rmutil.cpp
@@ -0,0 +1,488 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4#include <restartmanager.h>
5
6
7// Exit macros
8#define RmExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_RMUTIL, x, s, __VA_ARGS__)
9#define RmExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_RMUTIL, x, s, __VA_ARGS__)
10#define RmExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_RMUTIL, x, s, __VA_ARGS__)
11#define RmExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_RMUTIL, x, s, __VA_ARGS__)
12#define RmExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_RMUTIL, x, s, __VA_ARGS__)
13#define RmExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_RMUTIL, x, s, __VA_ARGS__)
14#define RmExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_RMUTIL, p, x, e, s, __VA_ARGS__)
15#define RmExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_RMUTIL, p, x, s, __VA_ARGS__)
16#define RmExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_RMUTIL, p, x, e, s, __VA_ARGS__)
17#define RmExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_RMUTIL, p, x, s, __VA_ARGS__)
18#define RmExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_RMUTIL, e, x, s, __VA_ARGS__)
19#define RmExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_RMUTIL, g, x, s, __VA_ARGS__)
20
21#define ARRAY_GROWTH_SIZE 5
22
23typedef DWORD (WINAPI *PFNRMJOINSESSION)(
24 __out DWORD *pSessionHandle,
25 __in_z const WCHAR strSessionKey[]
26 );
27
28typedef DWORD (WINAPI *PFNRMENDSESSION)(
29 __in DWORD dwSessionHandle
30 );
31
32typedef DWORD (WINAPI *PFNRMREGISTERRESOURCES)(
33 __in DWORD dwSessionHandle,
34 __in UINT nFiles,
35 __in_z_opt LPWSTR *rgsFilenames,
36 __in UINT nApplications,
37 __in_opt RM_UNIQUE_PROCESS *rgApplications,
38 __in UINT nServices,
39 __in_z_opt LPWSTR *rgsServiceNames
40 );
41
42typedef struct _RMU_SESSION
43{
44 CRITICAL_SECTION cs;
45 DWORD dwSessionHandle;
46 BOOL fStartedSessionHandle;
47 BOOL fInitialized;
48
49 UINT cFilenames;
50 LPWSTR *rgsczFilenames;
51
52 UINT cApplications;
53 RM_UNIQUE_PROCESS *rgApplications;
54
55 UINT cServiceNames;
56 LPWSTR *rgsczServiceNames;
57
58} RMU_SESSION;
59
60static volatile LONG vcRmuInitialized = 0;
61static HMODULE vhModule = NULL;
62static PFNRMJOINSESSION vpfnRmJoinSession = NULL;
63static PFNRMENDSESSION vpfnRmEndSession = NULL;
64static PFNRMREGISTERRESOURCES vpfnRmRegisterResources = NULL;
65
66static HRESULT RmuInitialize();
67static void RmuUninitialize();
68
69static HRESULT RmuApplicationArrayAlloc(
70 __deref_inout_ecount(*pcApplications) RM_UNIQUE_PROCESS **prgApplications,
71 __inout LPUINT pcApplications,
72 __in DWORD dwProcessId,
73 __in FILETIME ProcessStartTime
74 );
75
76static HRESULT RmuApplicationArrayFree(
77 __in RM_UNIQUE_PROCESS *rgApplications
78 );
79
80#define ReleaseNullApplicationArray(rg, c) { if (rg) { RmuApplicationArrayFree(rg); c = 0; rg = NULL; } }
81
82/********************************************************************
83RmuJoinSession - Joins an existing Restart Manager session.
84
85********************************************************************/
86extern "C" HRESULT DAPI RmuJoinSession(
87 __out PRMU_SESSION *ppSession,
88 __in_z LPCWSTR wzSessionKey
89 )
90{
91 HRESULT hr = S_OK;
92 DWORD er = ERROR_SUCCESS;
93 PRMU_SESSION pSession = NULL;
94
95 *ppSession = NULL;
96
97 pSession = static_cast<PRMU_SESSION>(MemAlloc(sizeof(RMU_SESSION), TRUE));
98 RmExitOnNull(pSession, hr, E_OUTOFMEMORY, "Failed to allocate the RMU_SESSION structure.");
99
100 hr = RmuInitialize();
101 RmExitOnFailure(hr, "Failed to initialize Restart Manager.");
102
103 er = vpfnRmJoinSession(&pSession->dwSessionHandle, wzSessionKey);
104 RmExitOnWin32Error(er, hr, "Failed to join Restart Manager session %ls.", wzSessionKey);
105
106 ::InitializeCriticalSection(&pSession->cs);
107 pSession->fInitialized = TRUE;
108
109 *ppSession = pSession;
110
111LExit:
112 if (FAILED(hr))
113 {
114 ReleaseNullMem(pSession);
115 }
116
117 return hr;
118}
119
120/********************************************************************
121RmuAddFile - Adds the file path to the Restart Manager session.
122
123You should call this multiple times as necessary before calling
124RmuRegisterResources.
125
126********************************************************************/
127extern "C" HRESULT DAPI RmuAddFile(
128 __in PRMU_SESSION pSession,
129 __in_z LPCWSTR wzPath
130 )
131{
132 HRESULT hr = S_OK;
133
134 ::EnterCriticalSection(&pSession->cs);
135
136 // Create or grow the jagged array.
137 hr = StrArrayAllocString(&pSession->rgsczFilenames, &pSession->cFilenames, wzPath, 0);
138 RmExitOnFailure(hr, "Failed to add the filename to the array.");
139
140LExit:
141 ::LeaveCriticalSection(&pSession->cs);
142 return hr;
143}
144
145/********************************************************************
146RmuAddProcessById - Adds the process ID to the Restart Manager sesion.
147
148You should call this multiple times as necessary before calling
149RmuRegisterResources.
150
151********************************************************************/
152extern "C" HRESULT DAPI RmuAddProcessById(
153 __in PRMU_SESSION pSession,
154 __in DWORD dwProcessId
155 )
156{
157 HRESULT hr = S_OK;
158 HANDLE hProcess = NULL;
159 FILETIME CreationTime = {};
160 FILETIME ExitTime = {};
161 FILETIME KernelTime = {};
162 FILETIME UserTime = {};
163 BOOL fLocked = FALSE;
164
165 HANDLE hToken = NULL;
166 TOKEN_PRIVILEGES priv = { 0 };
167 TOKEN_PRIVILEGES* pPrevPriv = NULL;
168 DWORD cbPrevPriv = 0;
169 DWORD er = ERROR_SUCCESS;
170 BOOL fAdjustedPrivileges = FALSE;
171 BOOL fElevated = FALSE;
172 ProcElevated(::GetCurrentProcess(), &fElevated);
173
174 // Must be elevated to adjust process privileges
175 if (fElevated) {
176 // Adding SeDebugPrivilege in the event that the process targeted by ::OpenProcess() is in a another user context.
177 if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken))
178 {
179 RmExitWithLastError(hr, "Failed to get process token.");
180 }
181
182 priv.PrivilegeCount = 1;
183 priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
184 if (!::LookupPrivilegeValueW(NULL, L"SeDebugPrivilege", &priv.Privileges[0].Luid))
185 {
186 RmExitWithLastError(hr, "Failed to get debug privilege LUID.");
187 }
188
189 cbPrevPriv = sizeof(TOKEN_PRIVILEGES);
190 pPrevPriv = static_cast<TOKEN_PRIVILEGES*>(MemAlloc(cbPrevPriv, TRUE));
191 RmExitOnNull(pPrevPriv, hr, E_OUTOFMEMORY, "Failed to allocate memory for empty previous privileges.");
192
193 if (!::AdjustTokenPrivileges(hToken, FALSE, &priv, cbPrevPriv, pPrevPriv, &cbPrevPriv))
194 {
195 LPVOID pv = MemReAlloc(pPrevPriv, cbPrevPriv, TRUE);
196 RmExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to allocate memory for previous privileges.");
197 pPrevPriv = static_cast<TOKEN_PRIVILEGES*>(pv);
198
199 if (!::AdjustTokenPrivileges(hToken, FALSE, &priv, cbPrevPriv, pPrevPriv, &cbPrevPriv))
200 {
201 RmExitWithLastError(hr, "Failed to get debug privilege LUID.");
202 }
203 }
204
205 fAdjustedPrivileges = TRUE;
206 }
207
208 hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwProcessId);
209 if (hProcess)
210 {
211 if (!::GetProcessTimes(hProcess, &CreationTime, &ExitTime, &KernelTime, &UserTime))
212 {
213 RmExitWithLastError(hr, "Failed to get the process times for process ID %d.", dwProcessId);
214 }
215
216 ::EnterCriticalSection(&pSession->cs);
217 fLocked = TRUE;
218 hr = RmuApplicationArrayAlloc(&pSession->rgApplications, &pSession->cApplications, dwProcessId, CreationTime);
219 RmExitOnFailure(hr, "Failed to add the application to the array.");
220 }
221 else
222 {
223 er = ::GetLastError();
224 if (ERROR_ACCESS_DENIED == er)
225 {
226 // OpenProcess will fail when not elevated and the target process is in another user context. Let the caller log and continue.
227 hr = E_NOTFOUND;
228 }
229 else
230 {
231 RmExitOnWin32Error(er, hr, "Failed to open the process ID %d.", dwProcessId);
232 }
233 }
234
235LExit:
236 if (hProcess)
237 {
238 ::CloseHandle(hProcess);
239 }
240
241 if (fAdjustedPrivileges)
242 {
243 ::AdjustTokenPrivileges(hToken, FALSE, pPrevPriv, 0, NULL, NULL);
244 }
245
246 ReleaseMem(pPrevPriv);
247 ReleaseHandle(hToken);
248
249 if (fLocked)
250 {
251 ::LeaveCriticalSection(&pSession->cs);
252 }
253
254 return hr;
255}
256
257/********************************************************************
258RmuAddProcessesByName - Adds all processes by the given process name
259 to the Restart Manager Session.
260
261You should call this multiple times as necessary before calling
262RmuRegisterResources.
263
264********************************************************************/
265extern "C" HRESULT DAPI RmuAddProcessesByName(
266 __in PRMU_SESSION pSession,
267 __in_z LPCWSTR wzProcessName
268 )
269{
270 HRESULT hr = S_OK;
271 DWORD *pdwProcessIds = NULL;
272 DWORD cProcessIds = 0;
273 BOOL fNotFound = FALSE;
274
275 hr = ProcFindAllIdsFromExeName(wzProcessName, &pdwProcessIds, &cProcessIds);
276 RmExitOnFailure(hr, "Failed to enumerate all the processes by name %ls.", wzProcessName);
277
278 for (DWORD i = 0; i < cProcessIds; ++i)
279 {
280 hr = RmuAddProcessById(pSession, pdwProcessIds[i]);
281 if (E_NOTFOUND == hr)
282 {
283 // RmuAddProcessById returns E_NOTFOUND when this setup is not elevated and OpenProcess returned access denied (target process running under another user account).
284 fNotFound = TRUE;
285 }
286 else
287 {
288 RmExitOnFailure(hr, "Failed to add process %ls (%d) to the Restart Manager session.", wzProcessName, pdwProcessIds[i]);
289 }
290 }
291
292 // If one or more calls to RmuAddProcessById returned E_NOTFOUND, then return E_NOTFOUND even if other calls succeeded, so that caller can log the issue.
293 if (fNotFound)
294 {
295 hr = E_NOTFOUND;
296 }
297
298LExit:
299 ReleaseMem(pdwProcessIds);
300
301 return hr;
302}
303
304/********************************************************************
305RmuAddService - Adds the service name to the Restart Manager session.
306
307You should call this multiple times as necessary before calling
308RmuRegisterResources.
309
310********************************************************************/
311extern "C" HRESULT DAPI RmuAddService(
312 __in PRMU_SESSION pSession,
313 __in_z LPCWSTR wzServiceName
314 )
315{
316 HRESULT hr = S_OK;
317
318 ::EnterCriticalSection(&pSession->cs);
319
320 hr = StrArrayAllocString(&pSession->rgsczServiceNames, &pSession->cServiceNames, wzServiceName, 0);
321 RmExitOnFailure(hr, "Failed to add the service name to the array.");
322
323LExit:
324 ::LeaveCriticalSection(&pSession->cs);
325 return hr;
326}
327
328/********************************************************************
329RmuRegisterResources - Registers resources for the Restart Manager.
330
331This should be called rarely because it is expensive to run. Call
332functions like RmuAddFile for multiple resources then commit them
333as a batch of updates to RmuRegisterResources.
334
335Duplicate resources appear to be handled by Restart Manager.
336Only one WM_QUERYENDSESSION is being sent for each top-level window.
337
338********************************************************************/
339extern "C" HRESULT DAPI RmuRegisterResources(
340 __in PRMU_SESSION pSession
341 )
342{
343 HRESULT hr = S_OK;
344 DWORD er = ERROR_SUCCESS;
345
346 AssertSz(vcRmuInitialized, "Restart Manager was not properly initialized.");
347
348 ::EnterCriticalSection(&pSession->cs);
349
350 er = vpfnRmRegisterResources(
351 pSession->dwSessionHandle,
352 pSession->cFilenames,
353 pSession->rgsczFilenames,
354 pSession->cApplications,
355 pSession->rgApplications,
356 pSession->cServiceNames,
357 pSession->rgsczServiceNames
358 );
359 RmExitOnWin32Error(er, hr, "Failed to register the resources with the Restart Manager session.");
360
361 // Empty the arrays if registered in case additional resources are added later.
362 ReleaseNullStrArray(pSession->rgsczFilenames, pSession->cFilenames);
363 ReleaseNullApplicationArray(pSession->rgApplications, pSession->cApplications);
364 ReleaseNullStrArray(pSession->rgsczServiceNames, pSession->cServiceNames);
365
366LExit:
367 ::LeaveCriticalSection(&pSession->cs);
368 return hr;
369}
370
371/********************************************************************
372RmuEndSession - Ends the session.
373
374If the session was joined by RmuJoinSession, any remaining resources
375are registered before the session is ended (left).
376
377********************************************************************/
378extern "C" HRESULT DAPI RmuEndSession(
379 __in PRMU_SESSION pSession
380 )
381{
382 HRESULT hr = S_OK;
383 DWORD er = ERROR_SUCCESS;
384
385 AssertSz(vcRmuInitialized, "Restart Manager was not properly initialized.");
386
387 // Make sure all resources are registered if we joined the session.
388 if (!pSession->fStartedSessionHandle)
389 {
390 hr = RmuRegisterResources(pSession);
391 RmExitOnFailure(hr, "Failed to register remaining resources.");
392 }
393
394 er = vpfnRmEndSession(pSession->dwSessionHandle);
395 RmExitOnWin32Error(er, hr, "Failed to end the Restart Manager session.");
396
397LExit:
398 if (pSession->fInitialized)
399 {
400 ::DeleteCriticalSection(&pSession->cs);
401 }
402
403 ReleaseNullStrArray(pSession->rgsczFilenames, pSession->cFilenames);
404 ReleaseNullApplicationArray(pSession->rgApplications, pSession->cApplications);
405 ReleaseNullStrArray(pSession->rgsczServiceNames, pSession->cServiceNames);
406 ReleaseNullMem(pSession);
407
408 RmuUninitialize();
409
410 return hr;
411}
412
413static HRESULT RmuInitialize()
414{
415 HRESULT hr = S_OK;
416 HMODULE hModule = NULL;
417
418 LONG iRef = ::InterlockedIncrement(&vcRmuInitialized);
419 if (1 == iRef && !vhModule)
420 {
421 hr = LoadSystemLibrary(L"rstrtmgr.dll", &hModule);
422 RmExitOnFailure(hr, "Failed to load the rstrtmgr.dll module.");
423
424 vpfnRmJoinSession = reinterpret_cast<PFNRMJOINSESSION>(::GetProcAddress(hModule, "RmJoinSession"));
425 RmExitOnNullWithLastError(vpfnRmJoinSession, hr, "Failed to get the RmJoinSession procedure from rstrtmgr.dll.");
426
427 vpfnRmRegisterResources = reinterpret_cast<PFNRMREGISTERRESOURCES>(::GetProcAddress(hModule, "RmRegisterResources"));
428 RmExitOnNullWithLastError(vpfnRmRegisterResources, hr, "Failed to get the RmRegisterResources procedure from rstrtmgr.dll.");
429
430 vpfnRmEndSession = reinterpret_cast<PFNRMENDSESSION>(::GetProcAddress(hModule, "RmEndSession"));
431 RmExitOnNullWithLastError(vpfnRmEndSession, hr, "Failed to get the RmEndSession procedure from rstrtmgr.dll.");
432
433 vhModule = hModule;
434 }
435
436LExit:
437 return hr;
438}
439
440static void RmuUninitialize()
441{
442 LONG iRef = ::InterlockedDecrement(&vcRmuInitialized);
443 if (0 == iRef && vhModule)
444 {
445 vpfnRmJoinSession = NULL;
446 vpfnRmEndSession = NULL;
447 vpfnRmRegisterResources = NULL;
448
449 ::FreeLibrary(vhModule);
450 vhModule = NULL;
451 }
452}
453
454static HRESULT RmuApplicationArrayAlloc(
455 __deref_inout_ecount(*pcApplications) RM_UNIQUE_PROCESS **prgApplications,
456 __inout LPUINT pcApplications,
457 __in DWORD dwProcessId,
458 __in FILETIME ProcessStartTime
459 )
460{
461 HRESULT hr = S_OK;
462 RM_UNIQUE_PROCESS *pApplication = NULL;
463
464 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(prgApplications), *pcApplications + 1, sizeof(RM_UNIQUE_PROCESS), ARRAY_GROWTH_SIZE);
465 RmExitOnFailure(hr, "Failed to allocate memory for the application array.");
466
467 pApplication = static_cast<RM_UNIQUE_PROCESS*>(&(*prgApplications)[*pcApplications]);
468 pApplication->dwProcessId = dwProcessId;
469 pApplication->ProcessStartTime = ProcessStartTime;
470
471 ++(*pcApplications);
472
473LExit:
474 return hr;
475}
476
477static HRESULT RmuApplicationArrayFree(
478 __in RM_UNIQUE_PROCESS *rgApplications
479 )
480{
481 HRESULT hr = S_OK;
482
483 hr = MemFree(rgApplications);
484 RmExitOnFailure(hr, "Failed to free memory for the application array.");
485
486LExit:
487 return hr;
488}
diff --git a/src/libs/dutil/WixToolset.DUtil/rssutil.cpp b/src/libs/dutil/WixToolset.DUtil/rssutil.cpp
new file mode 100644
index 00000000..8f994dfc
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/rssutil.cpp
@@ -0,0 +1,647 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define RssExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_RSSUTIL, x, s, __VA_ARGS__)
8#define RssExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_RSSUTIL, x, s, __VA_ARGS__)
9#define RssExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_RSSUTIL, x, s, __VA_ARGS__)
10#define RssExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_RSSUTIL, x, s, __VA_ARGS__)
11#define RssExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_RSSUTIL, x, s, __VA_ARGS__)
12#define RssExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_RSSUTIL, x, s, __VA_ARGS__)
13#define RssExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_RSSUTIL, p, x, e, s, __VA_ARGS__)
14#define RssExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_RSSUTIL, p, x, s, __VA_ARGS__)
15#define RssExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_RSSUTIL, p, x, e, s, __VA_ARGS__)
16#define RssExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_RSSUTIL, p, x, s, __VA_ARGS__)
17#define RssExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_RSSUTIL, e, x, s, __VA_ARGS__)
18#define RssExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_RSSUTIL, g, x, s, __VA_ARGS__)
19
20static HRESULT ParseRssDocument(
21 __in IXMLDOMDocument *pixd,
22 __out RSS_CHANNEL **ppChannel
23 );
24static HRESULT ParseRssChannel(
25 __in IXMLDOMNode *pixnChannel,
26 __out RSS_CHANNEL **ppChannel
27 );
28static HRESULT ParseRssItem(
29 __in IXMLDOMNode *pixnItem,
30 __in DWORD cItem,
31 __in_xcount(pChannel->cItems) RSS_CHANNEL *pChannel
32 );
33static HRESULT ParseRssUnknownElement(
34 __in IXMLDOMNode *pNode,
35 __inout RSS_UNKNOWN_ELEMENT** ppUnknownElement
36 );
37static HRESULT ParseRssUnknownAttribute(
38 __in IXMLDOMNode *pNode,
39 __inout RSS_UNKNOWN_ATTRIBUTE** ppUnknownAttribute
40 );
41static void FreeRssUnknownElementList(
42 __in_opt RSS_UNKNOWN_ELEMENT* pUnknownElement
43 );
44static void FreeRssUnknownAttributeList(
45 __in_opt RSS_UNKNOWN_ATTRIBUTE* pUnknownAttribute
46 );
47
48
49/********************************************************************
50 RssInitialize - Initialize RSS utilities.
51
52*********************************************************************/
53extern "C" HRESULT DAPI RssInitialize()
54{
55 return XmlInitialize();
56}
57
58
59/********************************************************************
60 RssUninitialize - Uninitialize RSS utilities.
61
62*********************************************************************/
63extern "C" void DAPI RssUninitialize()
64{
65 XmlUninitialize();
66}
67
68
69/********************************************************************
70 RssParseFromString - parses out an RSS channel from a string.
71
72*********************************************************************/
73extern "C" HRESULT DAPI RssParseFromString(
74 __in_z LPCWSTR wzRssString,
75 __out RSS_CHANNEL **ppChannel
76 )
77{
78 Assert(wzRssString);
79 Assert(ppChannel);
80
81 HRESULT hr = S_OK;
82 RSS_CHANNEL *pNewChannel = NULL;
83 IXMLDOMDocument *pixdRss = NULL;
84
85 hr = XmlLoadDocument(wzRssString, &pixdRss);
86 RssExitOnFailure(hr, "Failed to load RSS string as XML document.");
87
88 hr = ParseRssDocument(pixdRss, &pNewChannel);
89 RssExitOnFailure(hr, "Failed to parse RSS document.");
90
91 *ppChannel = pNewChannel;
92 pNewChannel = NULL;
93
94LExit:
95 ReleaseObject(pixdRss);
96
97 ReleaseRssChannel(pNewChannel);
98
99 return hr;
100}
101
102
103/********************************************************************
104 RssParseFromFile - parses out an RSS channel from a file path.
105
106*********************************************************************/
107extern "C" HRESULT DAPI RssParseFromFile(
108 __in_z LPCWSTR wzRssFile,
109 __out RSS_CHANNEL **ppChannel
110 )
111{
112 Assert(wzRssFile);
113 Assert(ppChannel);
114
115 HRESULT hr = S_OK;
116 RSS_CHANNEL *pNewChannel = NULL;
117 IXMLDOMDocument *pixdRss = NULL;
118
119 hr = XmlLoadDocumentFromFile(wzRssFile, &pixdRss);
120 RssExitOnFailure(hr, "Failed to load RSS string as XML document.");
121
122 hr = ParseRssDocument(pixdRss, &pNewChannel);
123 RssExitOnFailure(hr, "Failed to parse RSS document.");
124
125 *ppChannel = pNewChannel;
126 pNewChannel = NULL;
127
128LExit:
129 ReleaseObject(pixdRss);
130
131 ReleaseRssChannel(pNewChannel);
132
133 return hr;
134}
135
136
137/********************************************************************
138 RssFreeChannel - parses out an RSS channel from a string.
139
140*********************************************************************/
141extern "C" void DAPI RssFreeChannel(
142 __in_xcount(pChannel->cItems) RSS_CHANNEL *pChannel
143 )
144{
145 if (pChannel)
146 {
147 for (DWORD i = 0; i < pChannel->cItems; ++i)
148 {
149 ReleaseStr(pChannel->rgItems[i].wzTitle);
150 ReleaseStr(pChannel->rgItems[i].wzLink);
151 ReleaseStr(pChannel->rgItems[i].wzDescription);
152 ReleaseStr(pChannel->rgItems[i].wzGuid);
153 ReleaseStr(pChannel->rgItems[i].wzEnclosureUrl);
154 ReleaseStr(pChannel->rgItems[i].wzEnclosureType);
155
156 FreeRssUnknownElementList(pChannel->rgItems[i].pUnknownElements);
157 }
158
159 ReleaseStr(pChannel->wzTitle);
160 ReleaseStr(pChannel->wzLink);
161 ReleaseStr(pChannel->wzDescription);
162 FreeRssUnknownElementList(pChannel->pUnknownElements);
163
164 MemFree(pChannel);
165 }
166}
167
168
169/********************************************************************
170 ParseRssDocument - parses out an RSS channel from a loaded XML DOM document.
171
172*********************************************************************/
173static HRESULT ParseRssDocument(
174 __in IXMLDOMDocument *pixd,
175 __out RSS_CHANNEL **ppChannel
176 )
177{
178 Assert(pixd);
179 Assert(ppChannel);
180
181 HRESULT hr = S_OK;
182 IXMLDOMElement *pRssElement = NULL;
183 IXMLDOMNodeList *pChannelNodes = NULL;
184 IXMLDOMNode *pNode = NULL;
185 BSTR bstrNodeName = NULL;
186
187 RSS_CHANNEL *pNewChannel = NULL;
188
189 //
190 // Get the document element and start processing channels.
191 //
192 hr = pixd ->get_documentElement(&pRssElement);
193 RssExitOnFailure(hr, "failed get_documentElement in ParseRssDocument");
194
195 hr = pRssElement->get_childNodes(&pChannelNodes);
196 RssExitOnFailure(hr, "Failed to get child nodes of Rss Document element.");
197
198 while (S_OK == (hr = XmlNextElement(pChannelNodes, &pNode, &bstrNodeName)))
199 {
200 if (0 == lstrcmpW(bstrNodeName, L"channel"))
201 {
202 hr = ParseRssChannel(pNode, &pNewChannel);
203 RssExitOnFailure(hr, "Failed to parse RSS channel.");
204 }
205 else if (0 == lstrcmpW(bstrNodeName, L"link"))
206 {
207 }
208
209 ReleaseNullBSTR(bstrNodeName);
210 ReleaseNullObject(pNode);
211 }
212
213 if (S_FALSE == hr)
214 {
215 hr = S_OK;
216 }
217
218 *ppChannel = pNewChannel;
219 pNewChannel = NULL;
220
221LExit:
222 ReleaseBSTR(bstrNodeName);
223 ReleaseObject(pNode);
224 ReleaseObject(pChannelNodes);
225 ReleaseObject(pRssElement);
226
227 ReleaseRssChannel(pNewChannel);
228
229 return hr;
230}
231
232
233/********************************************************************
234 ParseRssChannel - parses out an RSS channel from a loaded XML DOM element.
235
236*********************************************************************/
237static HRESULT ParseRssChannel(
238 __in IXMLDOMNode *pixnChannel,
239 __out RSS_CHANNEL **ppChannel
240 )
241{
242 Assert(pixnChannel);
243 Assert(ppChannel);
244
245 HRESULT hr = S_OK;
246 IXMLDOMNodeList *pNodeList = NULL;
247
248 RSS_CHANNEL *pNewChannel = NULL;
249 long cItems = 0;
250
251 IXMLDOMNode *pNode = NULL;
252 BSTR bstrNodeName = NULL;
253 BSTR bstrNodeValue = NULL;
254
255 //
256 // First, calculate how many RSS items we're going to have and allocate
257 // the RSS_CHANNEL structure
258 //
259 hr = XmlSelectNodes(pixnChannel, L"item", &pNodeList);
260 RssExitOnFailure(hr, "Failed to select all RSS items in an RSS channel.");
261
262 hr = pNodeList->get_length(&cItems);
263 RssExitOnFailure(hr, "Failed to count the number of RSS items in RSS channel.");
264
265 pNewChannel = static_cast<RSS_CHANNEL*>(MemAlloc(sizeof(RSS_CHANNEL) + sizeof(RSS_ITEM) * cItems, TRUE));
266 RssExitOnNull(pNewChannel, hr, E_OUTOFMEMORY, "Failed to allocate RSS channel structure.");
267
268 pNewChannel->cItems = cItems;
269
270 //
271 // Process the elements under a channel now.
272 //
273 hr = pixnChannel->get_childNodes(&pNodeList);
274 RssExitOnFailure(hr, "Failed to get child nodes of RSS channel element.");
275
276 cItems = 0; // reset the counter and use this to walk through the channel items
277 while (S_OK == (hr = XmlNextElement(pNodeList, &pNode, &bstrNodeName)))
278 {
279 if (0 == lstrcmpW(bstrNodeName, L"title"))
280 {
281 hr = XmlGetText(pNode, &bstrNodeValue);
282 RssExitOnFailure(hr, "Failed to get RSS channel title.");
283
284 hr = StrAllocString(&pNewChannel->wzTitle, bstrNodeValue, 0);
285 RssExitOnFailure(hr, "Failed to allocate RSS channel title.");
286 }
287 else if (0 == lstrcmpW(bstrNodeName, L"link"))
288 {
289 hr = XmlGetText(pNode, &bstrNodeValue);
290 RssExitOnFailure(hr, "Failed to get RSS channel link.");
291
292 hr = StrAllocString(&pNewChannel->wzLink, bstrNodeValue, 0);
293 RssExitOnFailure(hr, "Failed to allocate RSS channel link.");
294 }
295 else if (0 == lstrcmpW(bstrNodeName, L"description"))
296 {
297 hr = XmlGetText(pNode, &bstrNodeValue);
298 RssExitOnFailure(hr, "Failed to get RSS channel description.");
299
300 hr = StrAllocString(&pNewChannel->wzDescription, bstrNodeValue, 0);
301 RssExitOnFailure(hr, "Failed to allocate RSS channel description.");
302 }
303 else if (0 == lstrcmpW(bstrNodeName, L"ttl"))
304 {
305 hr = XmlGetText(pNode, &bstrNodeValue);
306 RssExitOnFailure(hr, "Failed to get RSS channel description.");
307
308 pNewChannel->dwTimeToLive = (DWORD)wcstoul(bstrNodeValue, NULL, 10);
309 }
310 else if (0 == lstrcmpW(bstrNodeName, L"item"))
311 {
312 hr = ParseRssItem(pNode, cItems, pNewChannel);
313 RssExitOnFailure(hr, "Failed to parse RSS item.");
314
315 ++cItems;
316 }
317 else
318 {
319 hr = ParseRssUnknownElement(pNode, &pNewChannel->pUnknownElements);
320 RssExitOnFailure(hr, "Failed to parse unknown RSS channel element: %ls", bstrNodeName);
321 }
322
323 ReleaseNullBSTR(bstrNodeValue);
324 ReleaseNullBSTR(bstrNodeName);
325 ReleaseNullObject(pNode);
326 }
327
328 *ppChannel = pNewChannel;
329 pNewChannel = NULL;
330
331LExit:
332 ReleaseBSTR(bstrNodeName);
333 ReleaseObject(pNode);
334 ReleaseObject(pNodeList);
335
336 ReleaseRssChannel(pNewChannel);
337
338 return hr;
339}
340
341
342/********************************************************************
343 ParseRssItem - parses out an RSS item from a loaded XML DOM node.
344
345*********************************************************************/
346static HRESULT ParseRssItem(
347 __in IXMLDOMNode *pixnItem,
348 __in DWORD cItem,
349 __in_xcount(pChannel->cItems) RSS_CHANNEL *pChannel
350 )
351{
352 HRESULT hr = S_OK;
353
354 RSS_ITEM *pItem = NULL;
355 IXMLDOMNodeList *pNodeList = NULL;
356
357 IXMLDOMNode *pNode = NULL;
358 BSTR bstrNodeName = NULL;
359 BSTR bstrNodeValue = NULL;
360
361 //
362 // First make sure we're dealing with a valid item.
363 //
364 if (pChannel->cItems <= cItem)
365 {
366 hr = E_UNEXPECTED;
367 RssExitOnFailure(hr, "Unexpected number of items parsed.");
368 }
369
370 pItem = pChannel->rgItems + cItem;
371
372 //
373 // Process the elements under an item now.
374 //
375 hr = pixnItem->get_childNodes(&pNodeList);
376 RssExitOnFailure(hr, "Failed to get child nodes of RSS item element.");
377 while (S_OK == (hr = XmlNextElement(pNodeList, &pNode, &bstrNodeName)))
378 {
379 if (0 == lstrcmpW(bstrNodeName, L"title"))
380 {
381 hr = XmlGetText(pNode, &bstrNodeValue);
382 RssExitOnFailure(hr, "Failed to get RSS channel title.");
383
384 hr = StrAllocString(&pItem->wzTitle, bstrNodeValue, 0);
385 RssExitOnFailure(hr, "Failed to allocate RSS item title.");
386 }
387 else if (0 == lstrcmpW(bstrNodeName, L"link"))
388 {
389 hr = XmlGetText(pNode, &bstrNodeValue);
390 RssExitOnFailure(hr, "Failed to get RSS channel link.");
391
392 hr = StrAllocString(&pItem->wzLink, bstrNodeValue, 0);
393 RssExitOnFailure(hr, "Failed to allocate RSS item link.");
394 }
395 else if (0 == lstrcmpW(bstrNodeName, L"description"))
396 {
397 hr = XmlGetText(pNode, &bstrNodeValue);
398 RssExitOnFailure(hr, "Failed to get RSS item description.");
399
400 hr = StrAllocString(&pItem->wzDescription, bstrNodeValue, 0);
401 RssExitOnFailure(hr, "Failed to allocate RSS item description.");
402 }
403 else if (0 == lstrcmpW(bstrNodeName, L"guid"))
404 {
405 hr = XmlGetText(pNode, &bstrNodeValue);
406 RssExitOnFailure(hr, "Failed to get RSS item guid.");
407
408 hr = StrAllocString(&pItem->wzGuid, bstrNodeValue, 0);
409 RssExitOnFailure(hr, "Failed to allocate RSS item guid.");
410 }
411 else if (0 == lstrcmpW(bstrNodeName, L"pubDate"))
412 {
413 hr = XmlGetText(pNode, &bstrNodeValue);
414 RssExitOnFailure(hr, "Failed to get RSS item guid.");
415
416 hr = TimeFromString(bstrNodeValue, &pItem->ftPublished);
417 RssExitOnFailure(hr, "Failed to convert RSS item time.");
418 }
419 else if (0 == lstrcmpW(bstrNodeName, L"enclosure"))
420 {
421 hr = XmlGetAttribute(pNode, L"url", &bstrNodeValue);
422 RssExitOnFailure(hr, "Failed to get RSS item enclosure url.");
423
424 hr = StrAllocString(&pItem->wzEnclosureUrl, bstrNodeValue, 0);
425 RssExitOnFailure(hr, "Failed to allocate RSS item enclosure url.");
426 ReleaseNullBSTR(bstrNodeValue);
427
428 hr = XmlGetAttributeNumber(pNode, L"length", &pItem->dwEnclosureSize);
429 RssExitOnFailure(hr, "Failed to get RSS item enclosure length.");
430
431 hr = XmlGetAttribute(pNode, L"type", &bstrNodeValue);
432 RssExitOnFailure(hr, "Failed to get RSS item enclosure type.");
433
434 hr = StrAllocString(&pItem->wzEnclosureType, bstrNodeValue, 0);
435 RssExitOnFailure(hr, "Failed to allocate RSS item enclosure type.");
436 }
437 else
438 {
439 hr = ParseRssUnknownElement(pNode, &pItem->pUnknownElements);
440 RssExitOnFailure(hr, "Failed to parse unknown RSS item element: %ls", bstrNodeName);
441 }
442
443 ReleaseNullBSTR(bstrNodeValue);
444 ReleaseNullBSTR(bstrNodeName);
445 ReleaseNullObject(pNode);
446 }
447
448LExit:
449 ReleaseBSTR(bstrNodeValue);
450 ReleaseBSTR(bstrNodeName);
451 ReleaseObject(pNode);
452 ReleaseObject(pNodeList);
453
454 return hr;
455}
456
457
458/********************************************************************
459 ParseRssUnknownElement - parses out an unknown item from the RSS feed from a loaded XML DOM node.
460
461*********************************************************************/
462static HRESULT ParseRssUnknownElement(
463 __in IXMLDOMNode *pNode,
464 __inout RSS_UNKNOWN_ELEMENT** ppUnknownElement
465 )
466{
467 Assert(ppUnknownElement);
468
469 HRESULT hr = S_OK;
470 BSTR bstrNodeNamespace = NULL;
471 BSTR bstrNodeName = NULL;
472 BSTR bstrNodeValue = NULL;
473 IXMLDOMNamedNodeMap* pixnnmAttributes = NULL;
474 IXMLDOMNode* pixnAttribute = NULL;
475 RSS_UNKNOWN_ELEMENT* pNewUnknownElement;
476
477 pNewUnknownElement = static_cast<RSS_UNKNOWN_ELEMENT*>(MemAlloc(sizeof(RSS_UNKNOWN_ELEMENT), TRUE));
478 RssExitOnNull(pNewUnknownElement, hr, E_OUTOFMEMORY, "Failed to allocate unknown element.");
479
480 hr = pNode->get_namespaceURI(&bstrNodeNamespace);
481 if (S_OK == hr)
482 {
483 hr = StrAllocString(&pNewUnknownElement->wzNamespace, bstrNodeNamespace, 0);
484 RssExitOnFailure(hr, "Failed to allocate RSS unknown element namespace.");
485 }
486 else if (S_FALSE == hr)
487 {
488 hr = S_OK;
489 }
490 RssExitOnFailure(hr, "Failed to get unknown element namespace.");
491
492 hr = pNode->get_baseName(&bstrNodeName);
493 RssExitOnFailure(hr, "Failed to get unknown element name.");
494
495 hr = StrAllocString(&pNewUnknownElement->wzElement, bstrNodeName, 0);
496 RssExitOnFailure(hr, "Failed to allocate RSS unknown element name.");
497
498 hr = XmlGetText(pNode, &bstrNodeValue);
499 RssExitOnFailure(hr, "Failed to get unknown element value.");
500
501 hr = StrAllocString(&pNewUnknownElement->wzValue, bstrNodeValue, 0);
502 RssExitOnFailure(hr, "Failed to allocate RSS unknown element value.");
503
504 hr = pNode->get_attributes(&pixnnmAttributes);
505 RssExitOnFailure(hr, "Failed get attributes on RSS unknown element.");
506
507 while (S_OK == (hr = pixnnmAttributes->nextNode(&pixnAttribute)))
508 {
509 hr = ParseRssUnknownAttribute(pixnAttribute, &pNewUnknownElement->pAttributes);
510 RssExitOnFailure(hr, "Failed to parse attribute on RSS unknown element.");
511
512 ReleaseNullObject(pixnAttribute);
513 }
514
515 if (S_FALSE == hr)
516 {
517 hr = S_OK;
518 }
519 RssExitOnFailure(hr, "Failed to enumerate all attributes on RSS unknown element.");
520
521 RSS_UNKNOWN_ELEMENT** ppTail = ppUnknownElement;
522 while (*ppTail)
523 {
524 ppTail = &(*ppTail)->pNext;
525 }
526
527 *ppTail = pNewUnknownElement;
528 pNewUnknownElement = NULL;
529
530LExit:
531 FreeRssUnknownElementList(pNewUnknownElement);
532
533 ReleaseBSTR(bstrNodeNamespace);
534 ReleaseBSTR(bstrNodeName);
535 ReleaseBSTR(bstrNodeValue);
536 ReleaseObject(pixnnmAttributes);
537 ReleaseObject(pixnAttribute);
538
539 return hr;
540}
541
542
543/********************************************************************
544 ParseRssUnknownAttribute - parses out attribute from an unknown element
545
546*********************************************************************/
547static HRESULT ParseRssUnknownAttribute(
548 __in IXMLDOMNode *pNode,
549 __inout RSS_UNKNOWN_ATTRIBUTE** ppUnknownAttribute
550 )
551{
552 Assert(ppUnknownAttribute);
553
554 HRESULT hr = S_OK;
555 BSTR bstrNodeNamespace = NULL;
556 BSTR bstrNodeName = NULL;
557 BSTR bstrNodeValue = NULL;
558 RSS_UNKNOWN_ATTRIBUTE* pNewUnknownAttribute;
559
560 pNewUnknownAttribute = static_cast<RSS_UNKNOWN_ATTRIBUTE*>(MemAlloc(sizeof(RSS_UNKNOWN_ATTRIBUTE), TRUE));
561 RssExitOnNull(pNewUnknownAttribute, hr, E_OUTOFMEMORY, "Failed to allocate unknown attribute.");
562
563 hr = pNode->get_namespaceURI(&bstrNodeNamespace);
564 if (S_OK == hr)
565 {
566 hr = StrAllocString(&pNewUnknownAttribute->wzNamespace, bstrNodeNamespace, 0);
567 RssExitOnFailure(hr, "Failed to allocate RSS unknown attribute namespace.");
568 }
569 else if (S_FALSE == hr)
570 {
571 hr = S_OK;
572 }
573 RssExitOnFailure(hr, "Failed to get unknown attribute namespace.");
574
575 hr = pNode->get_baseName(&bstrNodeName);
576 RssExitOnFailure(hr, "Failed to get unknown attribute name.");
577
578 hr = StrAllocString(&pNewUnknownAttribute->wzAttribute, bstrNodeName, 0);
579 RssExitOnFailure(hr, "Failed to allocate RSS unknown attribute name.");
580
581 hr = XmlGetText(pNode, &bstrNodeValue);
582 RssExitOnFailure(hr, "Failed to get unknown attribute value.");
583
584 hr = StrAllocString(&pNewUnknownAttribute->wzValue, bstrNodeValue, 0);
585 RssExitOnFailure(hr, "Failed to allocate RSS unknown attribute value.");
586
587 RSS_UNKNOWN_ATTRIBUTE** ppTail = ppUnknownAttribute;
588 while (*ppTail)
589 {
590 ppTail = &(*ppTail)->pNext;
591 }
592
593 *ppTail = pNewUnknownAttribute;
594 pNewUnknownAttribute = NULL;
595
596LExit:
597 FreeRssUnknownAttributeList(pNewUnknownAttribute);
598
599 ReleaseBSTR(bstrNodeNamespace);
600 ReleaseBSTR(bstrNodeName);
601 ReleaseBSTR(bstrNodeValue);
602
603 return hr;
604}
605
606
607/********************************************************************
608 FreeRssUnknownElement - releases all of the memory used by a list of unknown elements
609
610*********************************************************************/
611static void FreeRssUnknownElementList(
612 __in_opt RSS_UNKNOWN_ELEMENT* pUnknownElement
613 )
614{
615 while (pUnknownElement)
616 {
617 RSS_UNKNOWN_ELEMENT* pFree = pUnknownElement;
618 pUnknownElement = pUnknownElement->pNext;
619
620 FreeRssUnknownAttributeList(pFree->pAttributes);
621 ReleaseStr(pFree->wzNamespace);
622 ReleaseStr(pFree->wzElement);
623 ReleaseStr(pFree->wzValue);
624 MemFree(pFree);
625 }
626}
627
628
629/********************************************************************
630 FreeRssUnknownAttribute - releases all of the memory used by a list of unknown attributes
631
632*********************************************************************/
633static void FreeRssUnknownAttributeList(
634 __in_opt RSS_UNKNOWN_ATTRIBUTE* pUnknownAttribute
635 )
636{
637 while (pUnknownAttribute)
638 {
639 RSS_UNKNOWN_ATTRIBUTE* pFree = pUnknownAttribute;
640 pUnknownAttribute = pUnknownAttribute->pNext;
641
642 ReleaseStr(pFree->wzNamespace);
643 ReleaseStr(pFree->wzAttribute);
644 ReleaseStr(pFree->wzValue);
645 MemFree(pFree);
646 }
647}
diff --git a/src/libs/dutil/WixToolset.DUtil/sceutil.cpp b/src/libs/dutil/WixToolset.DUtil/sceutil.cpp
new file mode 100644
index 00000000..cdb1623b
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/sceutil.cpp
@@ -0,0 +1,2489 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5#include "sceutil.h"
6
7// Limit is documented as 4 GB, but for some reason the API's don't let us specify anything above 4091 MB.
8#define MAX_SQLCE_DATABASE_SIZE 4091
9
10// In case of some older versions of sqlce_oledb.h don't have these definitions, define some types.
11#ifndef DBTYPEFOR_DBLENGTH
12#ifdef _WIN64
13#define SKIP_SCE_COMPILE
14#else
15#define SCE_32BIT_ONLY
16typedef DWORD DBLENGTH;
17typedef LONG DBROWOFFSET;
18typedef LONG DBROWCOUNT;
19typedef DWORD DBCOUNTITEM;
20typedef DWORD DBORDINAL;
21typedef LONG DB_LORDINAL;
22typedef DWORD DBBKMARK;
23typedef DWORD DBBYTEOFFSET;
24typedef DWORD DBREFCOUNT;
25typedef DWORD DB_UPARAMS;
26typedef LONG DB_LPARAMS;
27typedef DWORD DBHASHVALUE;
28typedef DWORD DB_DWRESERVE;
29typedef LONG DB_LRESERVE;
30typedef DWORD DB_URESERVE;
31#endif
32
33#endif
34
35#ifndef SKIP_SCE_COMPILE // If the sce headers don't support 64-bit, don't build for 64-bit
36
37// structs
38struct SCE_DATABASE_INTERNAL
39{
40 // In case we call DllGetClassObject on a specific file
41 HMODULE hSqlCeDll;
42
43 volatile LONG dwTransactionRefcount;
44 IDBInitialize *pIDBInitialize;
45 IDBCreateSession *pIDBCreateSession;
46 ITransactionLocal *pITransactionLocal;
47 IDBProperties *pIDBProperties;
48 IOpenRowset *pIOpenRowset;
49 ISessionProperties *pISessionProperties;
50
51 BOOL fChanges; // This database has changed
52 BOOL fPendingChanges; // Some changes are pending, upon transaction commit
53 BOOL fRollbackTransaction; // If this flag is true, the current transaction was requested to be rolled back
54 BOOL fTransactionBadState; // If this flag is true, we were unable to get out of a transaction, so starting a new transaction should fail
55
56 // If the database was opened as read-only, we copied it here - so delete it on close
57 LPWSTR sczTempDbFile;
58};
59
60struct SCE_ROW
61{
62 SCE_DATABASE_INTERNAL *pDatabaseInternal;
63
64 SCE_TABLE_SCHEMA *pTableSchema;
65 IRowset *pIRowset;
66 HROW hRow;
67 BOOL fInserting;
68
69 DWORD dwBindingIndex;
70 DBBINDING *rgBinding;
71 SIZE_T cbOffset;
72 BYTE *pbData;
73};
74
75struct SCE_QUERY
76{
77 SCE_TABLE_SCHEMA *pTableSchema;
78 SCE_INDEX_SCHEMA *pIndexSchema;
79 SCE_DATABASE_INTERNAL *pDatabaseInternal;
80
81 // Accessor build-up members
82 DWORD dwBindingIndex;
83 DBBINDING *rgBinding;
84 SIZE_T cbOffset;
85 BYTE *pbData;
86};
87
88struct SCE_QUERY_RESULTS
89{
90 SCE_DATABASE_INTERNAL *pDatabaseInternal;
91 IRowset *pIRowset;
92 SCE_TABLE_SCHEMA *pTableSchema;
93};
94
95extern const int SCE_ROW_HANDLE_BYTES = sizeof(SCE_ROW);
96extern const int SCE_QUERY_HANDLE_BYTES = sizeof(SCE_QUERY);
97extern const int SCE_QUERY_RESULTS_HANDLE_BYTES = sizeof(SCE_QUERY_RESULTS);
98
99// The following is the internal Sce-maintained table to tell the identifier and version of the schema
100const SCE_COLUMN_SCHEMA SCE_INTERNAL_VERSION_TABLE_VERSION_COLUMN_SCHEMA[] =
101{
102 {
103 L"AppIdentifier",
104 DBTYPE_WSTR,
105 0,
106 FALSE,
107 TRUE,
108 FALSE,
109 NULL,
110 0,
111 0
112 },
113 {
114 L"Version",
115 DBTYPE_I4,
116 0,
117 FALSE,
118 FALSE,
119 FALSE,
120 NULL,
121 0,
122 0
123 }
124};
125
126const SCE_TABLE_SCHEMA SCE_INTERNAL_VERSION_TABLE_SCHEMA[] =
127{
128 L"SceSchemaTablev1",
129 _countof(SCE_INTERNAL_VERSION_TABLE_VERSION_COLUMN_SCHEMA),
130 (SCE_COLUMN_SCHEMA *)SCE_INTERNAL_VERSION_TABLE_VERSION_COLUMN_SCHEMA,
131 0,
132 NULL,
133 NULL,
134 NULL
135};
136
137// internal function declarations
138
139// Creates an instance of SQL Server CE object, returning IDBInitialize object
140// If a file path is provided in wzSqlCeDllPath parameter, it calls DllGetClassObject
141// on that file specifically. Otherwise it calls CoCreateInstance
142static HRESULT CreateSqlCe(
143 __in_z_opt LPCWSTR wzSqlCeDllPath,
144 __out IDBInitialize **ppIDBInitialize,
145 __out_opt HMODULE *phSqlCeDll
146 );
147static HRESULT RunQuery(
148 __in BOOL fRange,
149 __in_bcount(SCE_QUERY_BYTES) SCE_QUERY_HANDLE psqhHandle,
150 __out SCE_QUERY_RESULTS **ppsqrhHandle
151 );
152static HRESULT FillOutColumnDescFromSchema(
153 __in const SCE_COLUMN_SCHEMA *pSchema,
154 __out DBCOLUMNDESC pColumnDesc
155 );
156static HRESULT EnsureSchema(
157 __in SCE_DATABASE *pDatabase,
158 __in SCE_DATABASE_SCHEMA *pDatabaseSchema
159 );
160static HRESULT OpenSchema(
161 __in SCE_DATABASE *pDatabase,
162 __in SCE_DATABASE_SCHEMA *pdsSchema
163 );
164static HRESULT SetColumnValue(
165 __in const SCE_TABLE_SCHEMA *pTableSchema,
166 __in DWORD dwColumnIndex,
167 __in_bcount_opt(cbSize) const BYTE *pbData,
168 __in SIZE_T cbSize,
169 __inout DBBINDING *pBinding,
170 __inout SIZE_T *pcbOffset,
171 __inout BYTE **ppbBuffer
172 );
173static HRESULT GetColumnValue(
174 __in SCE_ROW *pRow,
175 __in DWORD dwColumnIndex,
176 __out_opt BYTE **ppbData,
177 __out SIZE_T *pcbSize
178 );
179static HRESULT GetColumnValueFixed(
180 __in SCE_ROW *pRow,
181 __in DWORD dwColumnIndex,
182 __in DWORD cbSize,
183 __out BYTE *pbData
184 );
185static HRESULT EnsureLocalColumnConstraints(
186 __in ITableDefinition *pTableDefinition,
187 __in DBID *pTableID,
188 __in SCE_TABLE_SCHEMA *pTableSchema
189 );
190static HRESULT EnsureForeignColumnConstraints(
191 __in ITableDefinition *pTableDefinition,
192 __in DBID *pTableID,
193 __in SCE_TABLE_SCHEMA *pTableSchema,
194 __in SCE_DATABASE_SCHEMA *pDatabaseSchema
195 );
196static HRESULT SetSessionProperties(
197 __in ISessionProperties *pISessionProperties
198 );
199static HRESULT GetDatabaseSchemaInfo(
200 __in SCE_DATABASE *pDatabase,
201 __out LPWSTR *psczSchemaType,
202 __out DWORD *pdwVersion
203 );
204static HRESULT SetDatabaseSchemaInfo(
205 __in SCE_DATABASE *pDatabase,
206 __in LPCWSTR wzSchemaType,
207 __in DWORD dwVersion
208 );
209static void ReleaseDatabase(
210 SCE_DATABASE *pDatabase
211 );
212static void ReleaseDatabaseInternal(
213 SCE_DATABASE_INTERNAL *pDatabaseInternal
214 );
215
216// function definitions
217extern "C" HRESULT DAPI SceCreateDatabase(
218 __in_z LPCWSTR sczFile,
219 __in_z_opt LPCWSTR wzSqlCeDllPath,
220 __out SCE_DATABASE **ppDatabase
221 )
222{
223 HRESULT hr = S_OK;
224 LPWSTR sczDirectory = NULL;
225 SCE_DATABASE *pNewSceDatabase = NULL;
226 SCE_DATABASE_INTERNAL *pNewSceDatabaseInternal = NULL;
227 IDBDataSourceAdmin *pIDBDataSourceAdmin = NULL;
228 DBPROPSET rgdbpDataSourcePropSet[2] = { };
229 DBPROP rgdbpDataSourceProp[2] = { };
230 DBPROP rgdbpDataSourceSsceProp[1] = { };
231
232 pNewSceDatabase = reinterpret_cast<SCE_DATABASE *>(MemAlloc(sizeof(SCE_DATABASE), TRUE));
233 ExitOnNull(pNewSceDatabase, hr, E_OUTOFMEMORY, "Failed to allocate SCE_DATABASE struct");
234
235 pNewSceDatabaseInternal = reinterpret_cast<SCE_DATABASE_INTERNAL *>(MemAlloc(sizeof(SCE_DATABASE_INTERNAL), TRUE));
236 ExitOnNull(pNewSceDatabaseInternal, hr, E_OUTOFMEMORY, "Failed to allocate SCE_DATABASE_INTERNAL struct");
237
238 pNewSceDatabase->sdbHandle = reinterpret_cast<void *>(pNewSceDatabaseInternal);
239
240 hr = CreateSqlCe(wzSqlCeDllPath, &pNewSceDatabaseInternal->pIDBInitialize, &pNewSceDatabaseInternal->hSqlCeDll);
241 ExitOnFailure(hr, "Failed to get IDBInitialize interface");
242
243 hr = pNewSceDatabaseInternal->pIDBInitialize->QueryInterface(IID_IDBDataSourceAdmin, reinterpret_cast<void **>(&pIDBDataSourceAdmin));
244 ExitOnFailure(hr, "Failed to get IDBDataSourceAdmin interface");
245
246 hr = PathGetDirectory(sczFile, &sczDirectory);
247 ExitOnFailure(hr, "Failed to get directory portion of path: %ls", sczFile);
248
249 hr = DirEnsureExists(sczDirectory, NULL);
250 ExitOnFailure(hr, "Failed to ensure directory exists: %ls", sczDirectory);
251
252 rgdbpDataSourceProp[0].dwPropertyID = DBPROP_INIT_DATASOURCE;
253 rgdbpDataSourceProp[0].dwOptions = DBPROPOPTIONS_REQUIRED;
254 rgdbpDataSourceProp[0].vValue.vt = VT_BSTR;
255 rgdbpDataSourceProp[0].vValue.bstrVal = ::SysAllocString(sczFile);
256
257 rgdbpDataSourceProp[1].dwPropertyID = DBPROP_INIT_MODE;
258 rgdbpDataSourceProp[1].dwOptions = DBPROPOPTIONS_REQUIRED;
259 rgdbpDataSourceProp[1].vValue.vt = VT_I4;
260 rgdbpDataSourceProp[1].vValue.lVal = DB_MODE_SHARE_DENY_NONE;
261
262 // SQL CE doesn't seem to allow us to specify DBPROP_INIT_PROMPT if we include any properties from DBPROPSET_SSCE_DBINIT
263 rgdbpDataSourcePropSet[0].guidPropertySet = DBPROPSET_DBINIT;
264 rgdbpDataSourcePropSet[0].rgProperties = rgdbpDataSourceProp;
265 rgdbpDataSourcePropSet[0].cProperties = _countof(rgdbpDataSourceProp);
266
267 rgdbpDataSourceSsceProp[0].dwPropertyID = DBPROP_SSCE_MAX_DATABASE_SIZE;
268 rgdbpDataSourceSsceProp[0].dwOptions = DBPROPOPTIONS_REQUIRED;
269 rgdbpDataSourceSsceProp[0].vValue.vt = VT_I4;
270 rgdbpDataSourceSsceProp[0].vValue.intVal = MAX_SQLCE_DATABASE_SIZE;
271
272 rgdbpDataSourcePropSet[1].guidPropertySet = DBPROPSET_SSCE_DBINIT;
273 rgdbpDataSourcePropSet[1].rgProperties = rgdbpDataSourceSsceProp;
274 rgdbpDataSourcePropSet[1].cProperties = _countof(rgdbpDataSourceSsceProp);
275
276 hr = pIDBDataSourceAdmin->CreateDataSource(_countof(rgdbpDataSourcePropSet), rgdbpDataSourcePropSet, NULL, IID_IUnknown, NULL);
277 ExitOnFailure(hr, "Failed to create data source");
278
279 hr = pNewSceDatabaseInternal->pIDBInitialize->QueryInterface(IID_IDBProperties, reinterpret_cast<void **>(&pNewSceDatabaseInternal->pIDBProperties));
280 ExitOnFailure(hr, "Failed to get IDBProperties interface");
281
282 hr = pNewSceDatabaseInternal->pIDBInitialize->QueryInterface(IID_IDBCreateSession, reinterpret_cast<void **>(&pNewSceDatabaseInternal->pIDBCreateSession));
283 ExitOnFailure(hr, "Failed to get IDBCreateSession interface");
284
285 hr = pNewSceDatabaseInternal->pIDBCreateSession->CreateSession(NULL, IID_ISessionProperties, reinterpret_cast<IUnknown **>(&pNewSceDatabaseInternal->pISessionProperties));
286 ExitOnFailure(hr, "Failed to get ISessionProperties interface");
287
288 hr = SetSessionProperties(pNewSceDatabaseInternal->pISessionProperties);
289 ExitOnFailure(hr, "Failed to set session properties");
290
291 hr = pNewSceDatabaseInternal->pISessionProperties->QueryInterface(IID_IOpenRowset, reinterpret_cast<void **>(&pNewSceDatabaseInternal->pIOpenRowset));
292 ExitOnFailure(hr, "Failed to get IOpenRowset interface");
293
294 hr = pNewSceDatabaseInternal->pISessionProperties->QueryInterface(IID_ITransactionLocal, reinterpret_cast<void **>(&pNewSceDatabaseInternal->pITransactionLocal));
295 ExitOnFailure(hr, "Failed to get ITransactionLocal interface");
296
297 *ppDatabase = pNewSceDatabase;
298 pNewSceDatabase = NULL;
299
300LExit:
301 ReleaseStr(sczDirectory);
302 ReleaseObject(pIDBDataSourceAdmin);
303 ReleaseDatabase(pNewSceDatabase);
304 ReleaseBSTR(rgdbpDataSourceProp[0].vValue.bstrVal);
305
306 return hr;
307}
308
309extern "C" HRESULT DAPI SceOpenDatabase(
310 __in_z LPCWSTR sczFile,
311 __in_z_opt LPCWSTR wzSqlCeDllPath,
312 __in LPCWSTR wzExpectedSchemaType,
313 __in DWORD dwExpectedVersion,
314 __out SCE_DATABASE **ppDatabase,
315 __in BOOL fReadOnly
316 )
317{
318 HRESULT hr = S_OK;
319 DWORD dwVersionFound = 0;
320 WCHAR wzTempDbFile[MAX_PATH];
321 LPCWSTR wzPathToOpen = NULL;
322 LPWSTR sczSchemaType = NULL;
323 SCE_DATABASE *pNewSceDatabase = NULL;
324 SCE_DATABASE_INTERNAL *pNewSceDatabaseInternal = NULL;
325 DBPROPSET rgdbpDataSourcePropSet[2] = { };
326 DBPROP rgdbpDataSourceProp[2] = { };
327 DBPROP rgdbpDataSourceSsceProp[1] = { };
328
329 pNewSceDatabase = reinterpret_cast<SCE_DATABASE *>(MemAlloc(sizeof(SCE_DATABASE), TRUE));
330 ExitOnNull(pNewSceDatabase, hr, E_OUTOFMEMORY, "Failed to allocate SCE_DATABASE struct");
331
332 pNewSceDatabaseInternal = reinterpret_cast<SCE_DATABASE_INTERNAL *>(MemAlloc(sizeof(SCE_DATABASE_INTERNAL), TRUE));
333 ExitOnNull(pNewSceDatabaseInternal, hr, E_OUTOFMEMORY, "Failed to allocate SCE_DATABASE_INTERNAL struct");
334
335 pNewSceDatabase->sdbHandle = reinterpret_cast<void *>(pNewSceDatabaseInternal);
336
337 hr = CreateSqlCe(wzSqlCeDllPath, &pNewSceDatabaseInternal->pIDBInitialize, &pNewSceDatabaseInternal->hSqlCeDll);
338 ExitOnFailure(hr, "Failed to get IDBInitialize interface");
339
340 hr = pNewSceDatabaseInternal->pIDBInitialize->QueryInterface(IID_IDBProperties, reinterpret_cast<void **>(&pNewSceDatabaseInternal->pIDBProperties));
341 ExitOnFailure(hr, "Failed to get IDBProperties interface");
342
343 // TODO: had trouble getting SQL CE to read a file read-only, so we're copying it to a temp path for now.
344 if (fReadOnly)
345 {
346 hr = DirCreateTempPath(PathFile(sczFile), (LPWSTR)wzTempDbFile, _countof(wzTempDbFile));
347 ExitOnFailure(hr, "Failed to get temp path");
348
349 hr = FileEnsureCopy(sczFile, (LPCWSTR)wzTempDbFile, TRUE);
350 ExitOnFailure(hr, "Failed to copy file to temp path");
351
352 hr = StrAllocString(&pNewSceDatabaseInternal->sczTempDbFile, (LPCWSTR)wzTempDbFile, 0);
353 ExitOnFailure(hr, "Failed to copy temp db file path");
354
355 wzPathToOpen = (LPCWSTR)wzTempDbFile;
356 }
357 else
358 {
359 wzPathToOpen = sczFile;
360 }
361
362 rgdbpDataSourceProp[0].dwPropertyID = DBPROP_INIT_DATASOURCE;
363 rgdbpDataSourceProp[0].dwOptions = DBPROPOPTIONS_REQUIRED;
364 rgdbpDataSourceProp[0].vValue.vt = VT_BSTR;
365 rgdbpDataSourceProp[0].vValue.bstrVal = ::SysAllocString(wzPathToOpen);
366
367 rgdbpDataSourceProp[1].dwPropertyID = DBPROP_INIT_MODE;
368 rgdbpDataSourceProp[1].dwOptions = DBPROPOPTIONS_REQUIRED;
369 rgdbpDataSourceProp[1].vValue.vt = VT_I4;
370 rgdbpDataSourceProp[1].vValue.lVal = DB_MODE_SHARE_DENY_NONE;
371
372 // SQL CE doesn't seem to allow us to specify DBPROP_INIT_PROMPT if we include any properties from DBPROPSET_SSCE_DBINIT
373 rgdbpDataSourcePropSet[0].guidPropertySet = DBPROPSET_DBINIT;
374 rgdbpDataSourcePropSet[0].rgProperties = rgdbpDataSourceProp;
375 rgdbpDataSourcePropSet[0].cProperties = _countof(rgdbpDataSourceProp);
376
377 rgdbpDataSourceSsceProp[0].dwPropertyID = DBPROP_SSCE_MAX_DATABASE_SIZE;
378 rgdbpDataSourceSsceProp[0].dwOptions = DBPROPOPTIONS_REQUIRED;
379 rgdbpDataSourceSsceProp[0].vValue.vt = VT_I4;
380 rgdbpDataSourceSsceProp[0].vValue.lVal = MAX_SQLCE_DATABASE_SIZE;
381
382 rgdbpDataSourcePropSet[1].guidPropertySet = DBPROPSET_SSCE_DBINIT;
383 rgdbpDataSourcePropSet[1].rgProperties = rgdbpDataSourceSsceProp;
384 rgdbpDataSourcePropSet[1].cProperties = _countof(rgdbpDataSourceSsceProp);
385
386 hr = pNewSceDatabaseInternal->pIDBProperties->SetProperties(_countof(rgdbpDataSourcePropSet), rgdbpDataSourcePropSet);
387 ExitOnFailure(hr, "Failed to set initial properties to open database");
388
389 hr = pNewSceDatabaseInternal->pIDBInitialize->Initialize();
390 ExitOnFailure(hr, "Failed to open database: %ls", sczFile);
391
392 hr = pNewSceDatabaseInternal->pIDBInitialize->QueryInterface(IID_IDBCreateSession, reinterpret_cast<void **>(&pNewSceDatabaseInternal->pIDBCreateSession));
393 ExitOnFailure(hr, "Failed to get IDBCreateSession interface");
394
395 hr = pNewSceDatabaseInternal->pIDBCreateSession->CreateSession(NULL, IID_ISessionProperties, reinterpret_cast<IUnknown **>(&pNewSceDatabaseInternal->pISessionProperties));
396 ExitOnFailure(hr, "Failed to get ISessionProperties interface");
397
398 hr = SetSessionProperties(pNewSceDatabaseInternal->pISessionProperties);
399 ExitOnFailure(hr, "Failed to set session properties");
400
401 hr = pNewSceDatabaseInternal->pISessionProperties->QueryInterface(IID_IOpenRowset, reinterpret_cast<void **>(&pNewSceDatabaseInternal->pIOpenRowset));
402 ExitOnFailure(hr, "Failed to get IOpenRowset interface");
403
404 hr = pNewSceDatabaseInternal->pISessionProperties->QueryInterface(IID_ITransactionLocal, reinterpret_cast<void **>(&pNewSceDatabaseInternal->pITransactionLocal));
405 ExitOnFailure(hr, "Failed to get ITransactionLocal interface");
406
407 hr = GetDatabaseSchemaInfo(pNewSceDatabase, &sczSchemaType, &dwVersionFound);
408 ExitOnFailure(hr, "Failed to find schema version of database");
409
410 if (CSTR_EQUAL != ::CompareStringW(LOCALE_INVARIANT, 0, sczSchemaType, -1, wzExpectedSchemaType, -1))
411 {
412 hr = HRESULT_FROM_WIN32(ERROR_BAD_FILE_TYPE);
413 ExitOnRootFailure(hr, "Tried to open wrong database type - expected type %ls, found type %ls", wzExpectedSchemaType, sczSchemaType);
414 }
415 else if (dwVersionFound != dwExpectedVersion)
416 {
417 hr = HRESULT_FROM_WIN32(ERROR_PRODUCT_VERSION);
418 ExitOnRootFailure(hr, "Tried to open wrong database schema version - expected version %u, found version %u", dwExpectedVersion, dwVersionFound);
419 }
420
421 *ppDatabase = pNewSceDatabase;
422 pNewSceDatabase = NULL;
423
424LExit:
425 ReleaseBSTR(rgdbpDataSourceProp[0].vValue.bstrVal);
426 ReleaseStr(sczSchemaType);
427 ReleaseDatabase(pNewSceDatabase);
428
429 return hr;
430}
431
432extern "C" HRESULT DAPI SceEnsureDatabase(
433 __in_z LPCWSTR sczFile,
434 __in_z_opt LPCWSTR wzSqlCeDllPath,
435 __in LPCWSTR wzSchemaType,
436 __in DWORD dwExpectedVersion,
437 __in SCE_DATABASE_SCHEMA *pdsSchema,
438 __out SCE_DATABASE **ppDatabase
439 )
440{
441 HRESULT hr = S_OK;
442 SCE_DATABASE *pDatabase = NULL;
443
444 if (FileExistsEx(sczFile, NULL))
445 {
446 hr = SceOpenDatabase(sczFile, wzSqlCeDllPath, wzSchemaType, dwExpectedVersion, &pDatabase, FALSE);
447 ExitOnFailure(hr, "Failed to open database while ensuring database exists: %ls", sczFile);
448 }
449 else
450 {
451 hr = SceCreateDatabase(sczFile, wzSqlCeDllPath, &pDatabase);
452 ExitOnFailure(hr, "Failed to create database while ensuring database exists: %ls", sczFile);
453
454 hr = SetDatabaseSchemaInfo(pDatabase, wzSchemaType, dwExpectedVersion);
455 ExitOnFailure(hr, "Failed to set schema version of database");
456 }
457
458 hr = EnsureSchema(pDatabase, pdsSchema);
459 ExitOnFailure(hr, "Failed to ensure schema is correct in database: %ls", sczFile);
460
461 // Keep a pointer to the schema in the SCE_DATABASE object for future reference
462 pDatabase->pdsSchema = pdsSchema;
463
464 *ppDatabase = pDatabase;
465 pDatabase = NULL;
466
467LExit:
468 ReleaseDatabase(pDatabase);
469
470 return hr;
471}
472
473extern "C" HRESULT DAPI SceIsTableEmpty(
474 __in SCE_DATABASE *pDatabase,
475 __in DWORD dwTableIndex,
476 __out BOOL *pfEmpty
477 )
478{
479 HRESULT hr = S_OK;
480 SCE_ROW_HANDLE row = NULL;
481
482 hr = SceGetFirstRow(pDatabase, dwTableIndex, &row);
483 if (E_NOTFOUND == hr)
484 {
485 *pfEmpty = TRUE;
486 ExitFunction1(hr = S_OK);
487 }
488 ExitOnFailure(hr, "Failed to get first row while checking if table is empty");
489
490 *pfEmpty = FALSE;
491
492LExit:
493 ReleaseSceRow(row);
494
495 return hr;
496}
497
498extern "C" HRESULT DAPI SceGetFirstRow(
499 __in SCE_DATABASE *pDatabase,
500 __in DWORD dwTableIndex,
501 __out_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE *pRowHandle
502 )
503{
504 HRESULT hr = S_OK;
505 DBCOUNTITEM cRowsObtained = 0;
506 HROW hRow = DB_NULL_HROW;
507 HROW *phRow = &hRow;
508 SCE_ROW *pRow = NULL;
509 SCE_TABLE_SCHEMA *pTable = &(pDatabase->pdsSchema->rgTables[dwTableIndex]);
510
511 hr = pTable->pIRowset->RestartPosition(DB_NULL_HCHAPTER);
512 ExitOnFailure(hr, "Failed to reset IRowset position to beginning");
513
514 hr = pTable->pIRowset->GetNextRows(DB_NULL_HCHAPTER, 0, 1, &cRowsObtained, &phRow);
515 if (DB_S_ENDOFROWSET == hr)
516 {
517 ExitFunction1(hr = E_NOTFOUND);
518 }
519 ExitOnFailure(hr, "Failed to get next first row");
520
521 pRow = reinterpret_cast<SCE_ROW *>(MemAlloc(sizeof(SCE_ROW), TRUE));
522 ExitOnNull(pRow, hr, E_OUTOFMEMORY, "Failed to allocate SCE_ROW struct");
523
524 pRow->pDatabaseInternal = reinterpret_cast<SCE_DATABASE_INTERNAL *>(pDatabase->sdbHandle);
525 pRow->hRow = hRow;
526 pRow->pTableSchema = pTable;
527 pRow->pIRowset = pTable->pIRowset;
528 pRow->pIRowset->AddRef();
529
530 *pRowHandle = reinterpret_cast<SCE_ROW_HANDLE>(pRow);
531
532LExit:
533 return hr;
534}
535
536HRESULT DAPI SceGetNextRow(
537 __in SCE_DATABASE *pDatabase,
538 __in DWORD dwTableIndex,
539 __out_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE *pRowHandle
540 )
541{
542 HRESULT hr = S_OK;
543 DBCOUNTITEM cRowsObtained = 0;
544 HROW hRow = DB_NULL_HROW;
545 HROW *phRow = &hRow;
546 SCE_ROW *pRow = NULL;
547 SCE_TABLE_SCHEMA *pTable = &(pDatabase->pdsSchema->rgTables[dwTableIndex]);
548
549 hr = pTable->pIRowset->GetNextRows(DB_NULL_HCHAPTER, 0, 1, &cRowsObtained, &phRow);
550 if (DB_S_ENDOFROWSET == hr)
551 {
552 ExitFunction1(hr = E_NOTFOUND);
553 }
554 ExitOnFailure(hr, "Failed to get next first row");
555
556 pRow = reinterpret_cast<SCE_ROW *>(MemAlloc(sizeof(SCE_ROW), TRUE));
557 ExitOnNull(pRow, hr, E_OUTOFMEMORY, "Failed to allocate SCE_ROW struct");
558
559 pRow->pDatabaseInternal = reinterpret_cast<SCE_DATABASE_INTERNAL *>(pDatabase->sdbHandle);
560 pRow->hRow = hRow;
561 pRow->pTableSchema = pTable;
562 pRow->pIRowset = pTable->pIRowset;
563 pRow->pIRowset->AddRef();
564
565 *pRowHandle = reinterpret_cast<SCE_ROW_HANDLE>(pRow);
566
567LExit:
568 return hr;
569}
570
571extern "C" HRESULT DAPI SceBeginTransaction(
572 __in SCE_DATABASE *pDatabase
573 )
574{
575 HRESULT hr = S_OK;
576 SCE_DATABASE_INTERNAL *pDatabaseInternal = reinterpret_cast<SCE_DATABASE_INTERNAL *>(pDatabase->sdbHandle);
577
578 if (pDatabaseInternal->fTransactionBadState)
579 {
580 // We're in a hosed transaction state - we can't start a new one
581 ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_RECOVERY_FAILURE));
582 }
583
584 ::InterlockedIncrement(&pDatabaseInternal->dwTransactionRefcount);
585
586 if (1 == pDatabaseInternal->dwTransactionRefcount)
587 {
588 hr = pDatabaseInternal->pITransactionLocal->StartTransaction(ISOLATIONLEVEL_SERIALIZABLE, 0, NULL, NULL);
589 ExitOnFailure(hr, "Failed to start transaction");
590 }
591
592LExit:
593 if (FAILED(hr))
594 {
595 ::InterlockedDecrement(&pDatabaseInternal->dwTransactionRefcount);
596 }
597
598 return hr;
599}
600
601extern "C" HRESULT DAPI SceCommitTransaction(
602 __in SCE_DATABASE *pDatabase
603 )
604{
605 HRESULT hr = S_OK;
606 SCE_DATABASE_INTERNAL *pDatabaseInternal = reinterpret_cast<SCE_DATABASE_INTERNAL *>(pDatabase->sdbHandle);
607 Assert(0 < pDatabaseInternal->dwTransactionRefcount);
608
609 ::InterlockedDecrement(&pDatabaseInternal->dwTransactionRefcount);
610
611 if (0 == pDatabaseInternal->dwTransactionRefcount)
612 {
613 if (pDatabaseInternal->fRollbackTransaction)
614 {
615 hr = pDatabaseInternal->pITransactionLocal->Abort(NULL, FALSE, FALSE);
616 ExitOnFailure(hr, "Failed to abort transaction");
617 }
618 else
619 {
620 hr = pDatabaseInternal->pITransactionLocal->Commit(FALSE, XACTTC_SYNC, 0);
621 ExitOnFailure(hr, "Failed to commit transaction");
622 }
623
624 if (pDatabaseInternal->fPendingChanges)
625 {
626 pDatabaseInternal->fPendingChanges = FALSE;
627 pDatabaseInternal->fChanges = TRUE;
628 }
629
630 pDatabaseInternal->fRollbackTransaction = FALSE;
631 }
632
633LExit:
634 // If we tried to commit and failed, the caller should subsequently call rollback
635 if (FAILED(hr))
636 {
637 ::InterlockedIncrement(&pDatabaseInternal->dwTransactionRefcount);
638 }
639
640 return hr;
641}
642
643extern "C" HRESULT DAPI SceRollbackTransaction(
644 __in SCE_DATABASE *pDatabase
645 )
646{
647 HRESULT hr = S_OK;
648 SCE_DATABASE_INTERNAL *pDatabaseInternal = reinterpret_cast<SCE_DATABASE_INTERNAL *>(pDatabase->sdbHandle);
649 Assert(0 < pDatabaseInternal->dwTransactionRefcount);
650
651 ::InterlockedDecrement(&pDatabaseInternal->dwTransactionRefcount);
652
653 if (0 == pDatabaseInternal->dwTransactionRefcount)
654 {
655 hr = pDatabaseInternal->pITransactionLocal->Abort(NULL, FALSE, FALSE);
656 ExitOnFailure(hr, "Failed to abort transaction");
657 pDatabaseInternal->fPendingChanges = FALSE;
658
659 pDatabaseInternal->fRollbackTransaction = FALSE;
660 }
661 else
662 {
663 pDatabaseInternal->fRollbackTransaction = TRUE;
664 }
665
666LExit:
667 // We're just in a bad state now. Don't increment the transaction refcount (what is the user going to do - call us again?)
668 // but mark the database as bad so the user gets an error if they try to start a new transaction.
669 if (FAILED(hr))
670 {
671 TraceError(hr, "Failed to rollback transaction");
672 pDatabaseInternal->fTransactionBadState = TRUE;
673 }
674
675 return hr;
676}
677
678extern "C" HRESULT DAPI SceDeleteRow(
679 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE *pRowHandle
680 )
681{
682 HRESULT hr = S_OK;
683 SCE_ROW *pRow = reinterpret_cast<SCE_ROW *>(*pRowHandle);
684 IRowsetChange *pIRowsetChange = NULL;
685 DBROWSTATUS rowStatus = DBROWSTATUS_S_OK;
686
687 hr = pRow->pIRowset->QueryInterface(IID_IRowsetChange, reinterpret_cast<void **>(&pIRowsetChange));
688 ExitOnFailure(hr, "Failed to get IRowsetChange interface");
689
690 hr = pIRowsetChange->DeleteRows(DB_NULL_HCHAPTER, 1, &pRow->hRow, &rowStatus);
691 ExitOnFailure(hr, "Failed to delete row with status: %u", rowStatus);
692
693 ReleaseNullSceRow(*pRowHandle);
694
695LExit:
696 ReleaseObject(pIRowsetChange);
697
698 return hr;
699}
700
701extern "C" HRESULT DAPI ScePrepareInsert(
702 __in SCE_DATABASE *pDatabase,
703 __in DWORD dwTableIndex,
704 __out_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE *pRowHandle
705 )
706{
707 HRESULT hr = S_OK;
708 SCE_ROW *pRow = NULL;
709
710 pRow = reinterpret_cast<SCE_ROW *>(MemAlloc(sizeof(SCE_ROW), TRUE));
711 ExitOnNull(pRow, hr, E_OUTOFMEMORY, "Failed to allocate SCE_ROW struct");
712
713 pRow->pDatabaseInternal = reinterpret_cast<SCE_DATABASE_INTERNAL *>(pDatabase->sdbHandle);
714 pRow->hRow = DB_NULL_HROW;
715 pRow->pTableSchema = &(pDatabase->pdsSchema->rgTables[dwTableIndex]);
716 pRow->pIRowset = pRow->pTableSchema->pIRowset;
717 pRow->pIRowset->AddRef();
718 pRow->fInserting = TRUE;
719
720 *pRowHandle = reinterpret_cast<SCE_ROW_HANDLE>(pRow);
721 pRow = NULL;
722
723LExit:
724 ReleaseSceRow(pRow);
725
726 return hr;
727}
728
729extern "C" HRESULT DAPI SceFinishUpdate(
730 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowHandle
731 )
732{
733 HRESULT hr = S_OK;
734 SCE_ROW *pRow = reinterpret_cast<SCE_ROW *>(rowHandle);
735 IAccessor *pIAccessor = NULL;
736 IRowsetChange *pIRowsetChange = NULL;
737 DBBINDSTATUS *rgBindStatus = NULL;
738 HACCESSOR hAccessor = DB_NULL_HACCESSOR;
739 HROW hRow = DB_NULL_HROW;
740
741 hr = pRow->pIRowset->QueryInterface(IID_IAccessor, reinterpret_cast<void **>(&pIAccessor));
742 ExitOnFailure(hr, "Failed to get IAccessor interface");
743
744// This can be used when stepping through the debugger to see bind failures
745#ifdef DEBUG
746 if (0 < pRow->dwBindingIndex)
747 {
748 hr = MemEnsureArraySize(reinterpret_cast<void **>(&rgBindStatus), pRow->dwBindingIndex, sizeof(DBBINDSTATUS), 0);
749 ExitOnFailure(hr, "Failed to ensure binding status array size");
750 }
751#endif
752
753 hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, pRow->dwBindingIndex, pRow->rgBinding, 0, &hAccessor, rgBindStatus);
754 ExitOnFailure(hr, "Failed to create accessor");
755
756 hr = pRow->pIRowset->QueryInterface(IID_IRowsetChange, reinterpret_cast<void **>(&pIRowsetChange));
757 ExitOnFailure(hr, "Failed to get IRowsetChange interface");
758
759 if (pRow->fInserting)
760 {
761 hr = pIRowsetChange->InsertRow(DB_NULL_HCHAPTER, hAccessor, pRow->pbData, &hRow);
762 ExitOnFailure(hr, "Failed to insert new row");
763
764 pRow->hRow = hRow;
765 ReleaseNullObject(pRow->pIRowset);
766 pRow->pIRowset = pRow->pTableSchema->pIRowset;
767 pRow->pIRowset->AddRef();
768 }
769 else
770 {
771 hr = pIRowsetChange->SetData(pRow->hRow, hAccessor, pRow->pbData);
772 ExitOnFailure(hr, "Failed to update existing row");
773 }
774
775 if (0 < pRow->pDatabaseInternal->dwTransactionRefcount)
776 {
777 pRow->pDatabaseInternal->fPendingChanges = TRUE;
778 }
779 else
780 {
781 pRow->pDatabaseInternal->fChanges = TRUE;
782 }
783
784LExit:
785 if (DB_NULL_HACCESSOR != hAccessor)
786 {
787 pIAccessor->ReleaseAccessor(hAccessor, NULL);
788 }
789 ReleaseMem(rgBindStatus);
790 ReleaseObject(pIAccessor);
791 ReleaseObject(pIRowsetChange);
792
793 return hr;
794}
795
796extern "C" HRESULT DAPI SceSetColumnBinary(
797 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowHandle,
798 __in DWORD dwColumnIndex,
799 __in_bcount(cbBuffer) const BYTE* pbBuffer,
800 __in SIZE_T cbBuffer
801 )
802{
803 HRESULT hr = S_OK;
804 size_t cbAllocSize = 0;
805 SCE_ROW *pRow = reinterpret_cast<SCE_ROW *>(rowHandle);
806
807 hr = ::SizeTMult(sizeof(DBBINDING), pRow->pTableSchema->cColumns, &cbAllocSize);
808 ExitOnFailure(hr, "Overflow while calculating allocation size for DBBINDING to set binary, columns: %u", pRow->pTableSchema->cColumns);
809
810 if (NULL == pRow->rgBinding)
811 {
812 pRow->rgBinding = static_cast<DBBINDING *>(MemAlloc(cbAllocSize, TRUE));
813 ExitOnNull(pRow->rgBinding, hr, E_OUTOFMEMORY, "Failed to allocate DBBINDINGs for sce row writer");
814 }
815
816 hr = SetColumnValue(pRow->pTableSchema, dwColumnIndex, pbBuffer, cbBuffer, &pRow->rgBinding[pRow->dwBindingIndex++], &pRow->cbOffset, &pRow->pbData);
817 ExitOnFailure(hr, "Failed to set column value as binary");
818
819LExit:
820 return hr;
821}
822
823extern "C" HRESULT DAPI SceSetColumnDword(
824 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowHandle,
825 __in DWORD dwColumnIndex,
826 __in const DWORD dwValue
827 )
828{
829 HRESULT hr = S_OK;
830 size_t cbAllocSize = 0;
831 SCE_ROW *pRow = reinterpret_cast<SCE_ROW *>(rowHandle);
832
833 hr = ::SizeTMult(sizeof(DBBINDING), pRow->pTableSchema->cColumns, &cbAllocSize);
834 ExitOnFailure(hr, "Overflow while calculating allocation size for DBBINDING to set dword, columns: %u", pRow->pTableSchema->cColumns);
835
836 if (NULL == pRow->rgBinding)
837 {
838 pRow->rgBinding = static_cast<DBBINDING *>(MemAlloc(cbAllocSize, TRUE));
839 ExitOnNull(pRow->rgBinding, hr, E_OUTOFMEMORY, "Failed to allocate DBBINDINGs for sce row writer");
840 }
841
842 hr = SetColumnValue(pRow->pTableSchema, dwColumnIndex, reinterpret_cast<const BYTE *>(&dwValue), 4, &pRow->rgBinding[pRow->dwBindingIndex++], &pRow->cbOffset, &pRow->pbData);
843 ExitOnFailure(hr, "Failed to set column value as binary");
844
845LExit:
846 return hr;
847}
848
849extern "C" HRESULT DAPI SceSetColumnQword(
850 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowHandle,
851 __in DWORD dwColumnIndex,
852 __in const DWORD64 qwValue
853 )
854{
855 HRESULT hr = S_OK;
856 size_t cbAllocSize = 0;
857 SCE_ROW *pRow = reinterpret_cast<SCE_ROW *>(rowHandle);
858
859 hr = ::SizeTMult(sizeof(DBBINDING), pRow->pTableSchema->cColumns, &cbAllocSize);
860 ExitOnFailure(hr, "Overflow while calculating allocation size for DBBINDING to set qword, columns: %u", pRow->pTableSchema->cColumns);
861
862 if (NULL == pRow->rgBinding)
863 {
864 pRow->rgBinding = static_cast<DBBINDING *>(MemAlloc(cbAllocSize, TRUE));
865 ExitOnNull(pRow->rgBinding, hr, E_OUTOFMEMORY, "Failed to allocate DBBINDINGs for sce row writer");
866 }
867
868 hr = SetColumnValue(pRow->pTableSchema, dwColumnIndex, reinterpret_cast<const BYTE *>(&qwValue), 8, &pRow->rgBinding[pRow->dwBindingIndex++], &pRow->cbOffset, &pRow->pbData);
869 ExitOnFailure(hr, "Failed to set column value as qword");
870
871LExit:
872 return hr;
873}
874
875extern "C" HRESULT DAPI SceSetColumnBool(
876 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowHandle,
877 __in DWORD dwColumnIndex,
878 __in const BOOL fValue
879 )
880{
881 HRESULT hr = S_OK;
882 size_t cbAllocSize = 0;
883 short int sValue = fValue ? 0xFFFF : 0x0000;
884 SCE_ROW *pRow = reinterpret_cast<SCE_ROW *>(rowHandle);
885
886 hr = ::SizeTMult(sizeof(DBBINDING), pRow->pTableSchema->cColumns, &cbAllocSize);
887 ExitOnFailure(hr, "Overflow while calculating allocation size for DBBINDING to set bool, columns: %u", pRow->pTableSchema->cColumns);
888
889 if (NULL == pRow->rgBinding)
890 {
891 pRow->rgBinding = static_cast<DBBINDING *>(MemAlloc(cbAllocSize, TRUE));
892 ExitOnNull(pRow->rgBinding, hr, E_OUTOFMEMORY, "Failed to allocate DBBINDINGs for sce row writer");
893 }
894
895 hr = SetColumnValue(pRow->pTableSchema, dwColumnIndex, reinterpret_cast<const BYTE *>(&sValue), 2, &pRow->rgBinding[pRow->dwBindingIndex++], &pRow->cbOffset, &pRow->pbData);
896 ExitOnFailure(hr, "Failed to set column value as binary");
897
898LExit:
899 return hr;
900}
901
902extern "C" HRESULT DAPI SceSetColumnString(
903 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowHandle,
904 __in DWORD dwColumnIndex,
905 __in_z_opt LPCWSTR wzValue
906 )
907{
908 HRESULT hr = S_OK;
909 size_t cbAllocSize = 0;
910 SCE_ROW *pRow = reinterpret_cast<SCE_ROW *>(rowHandle);
911 SIZE_T cbSize = (NULL == wzValue) ? 0 : ((lstrlenW(wzValue) + 1) * sizeof(WCHAR));
912
913 hr = ::SizeTMult(sizeof(DBBINDING), pRow->pTableSchema->cColumns, &cbAllocSize);
914 ExitOnFailure(hr, "Overflow while calculating allocation size for DBBINDING to set string, columns: %u", pRow->pTableSchema->cColumns);
915
916 if (NULL == pRow->rgBinding)
917 {
918 pRow->rgBinding = static_cast<DBBINDING *>(MemAlloc(cbAllocSize, TRUE));
919 ExitOnNull(pRow->rgBinding, hr, E_OUTOFMEMORY, "Failed to allocate DBBINDINGs for sce row writer");
920 }
921
922 hr = SetColumnValue(pRow->pTableSchema, dwColumnIndex, reinterpret_cast<const BYTE *>(wzValue), cbSize, &pRow->rgBinding[pRow->dwBindingIndex++], &pRow->cbOffset, &pRow->pbData);
923 ExitOnFailure(hr, "Failed to set column value as string: %ls", wzValue);
924
925LExit:
926 return hr;
927}
928
929extern "C" HRESULT DAPI SceSetColumnNull(
930 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowHandle,
931 __in DWORD dwColumnIndex
932 )
933{
934 HRESULT hr = S_OK;
935 size_t cbAllocSize = 0;
936 SCE_ROW *pRow = reinterpret_cast<SCE_ROW *>(rowHandle);
937
938 hr = ::SizeTMult(sizeof(DBBINDING), pRow->pTableSchema->cColumns, &cbAllocSize);
939 ExitOnFailure(hr, "Overflow while calculating allocation size for DBBINDING to set empty, columns: %u", pRow->pTableSchema->cColumns);
940
941 if (NULL == pRow->rgBinding)
942 {
943 pRow->rgBinding = static_cast<DBBINDING *>(MemAlloc(cbAllocSize, TRUE));
944 ExitOnNull(pRow->rgBinding, hr, E_OUTOFMEMORY, "Failed to allocate DBBINDINGs for sce row writer");
945 }
946
947 hr = SetColumnValue(pRow->pTableSchema, dwColumnIndex, NULL, 0, &pRow->rgBinding[pRow->dwBindingIndex++], &pRow->cbOffset, &pRow->pbData);
948 ExitOnFailure(hr, "Failed to set column value as empty value");
949
950LExit:
951 return hr;
952}
953
954extern "C" HRESULT DAPI SceSetColumnSystemTime(
955 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowHandle,
956 __in DWORD dwColumnIndex,
957 __in const SYSTEMTIME *pst
958 )
959{
960 HRESULT hr = S_OK;
961 size_t cbAllocSize = 0;
962 DBTIMESTAMP dbTimeStamp = { };
963
964 SCE_ROW *pRow = reinterpret_cast<SCE_ROW *>(rowHandle);
965
966 hr = ::SizeTMult(sizeof(DBBINDING), pRow->pTableSchema->cColumns, &cbAllocSize);
967 ExitOnFailure(hr, "Overflow while calculating allocation size for DBBINDING to set systemtime, columns: %u", pRow->pTableSchema->cColumns);
968
969 if (NULL == pRow->rgBinding)
970 {
971 pRow->rgBinding = static_cast<DBBINDING *>(MemAlloc(cbAllocSize, TRUE));
972 ExitOnNull(pRow->rgBinding, hr, E_OUTOFMEMORY, "Failed to allocate DBBINDINGs for sce row writer");
973 }
974
975 dbTimeStamp.year = pst->wYear;
976 dbTimeStamp.month = pst->wMonth;
977 dbTimeStamp.day = pst->wDay;
978 dbTimeStamp.hour = pst->wHour;
979 dbTimeStamp.minute = pst->wMinute;
980 dbTimeStamp.second = pst->wSecond;
981 // Don't use .fraction because milliseconds are not reliable in SQL CE. They are rounded to the nearest 1/300th of a second,
982 // and it is not supported (or at least I can't figure out how) to query for an exact timestamp if milliseconds
983 // are involved (even when rounded the way SQL CE returns them).
984
985 hr = SetColumnValue(pRow->pTableSchema, dwColumnIndex, reinterpret_cast<const BYTE *>(&dbTimeStamp), sizeof(dbTimeStamp), &pRow->rgBinding[pRow->dwBindingIndex++], &pRow->cbOffset, &pRow->pbData);
986 ExitOnFailure(hr, "Failed to set column value as DBTIMESTAMP");
987
988LExit:
989 return hr;
990}
991
992extern "C" HRESULT DAPI SceGetColumnBinary(
993 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowReadHandle,
994 __in DWORD dwColumnIndex,
995 __out_opt BYTE **ppbBuffer,
996 __inout SIZE_T *pcbBuffer
997 )
998{
999 HRESULT hr = S_OK;
1000 SCE_ROW *pRow = reinterpret_cast<SCE_ROW *>(rowReadHandle);
1001
1002 hr = GetColumnValue(pRow, dwColumnIndex, ppbBuffer, pcbBuffer);
1003 if (E_NOTFOUND == hr)
1004 {
1005 ExitFunction();
1006 }
1007 ExitOnFailure(hr, "Failed to get binary data out of column");
1008
1009LExit:
1010 return hr;
1011}
1012
1013extern "C" HRESULT DAPI SceGetColumnDword(
1014 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowReadHandle,
1015 __in DWORD dwColumnIndex,
1016 __out DWORD *pdwValue
1017 )
1018{
1019 HRESULT hr = S_OK;
1020 SCE_ROW *pRow = reinterpret_cast<SCE_ROW *>(rowReadHandle);
1021
1022 hr = GetColumnValueFixed(pRow, dwColumnIndex, 4, reinterpret_cast<BYTE *>(pdwValue));
1023 if (E_NOTFOUND == hr)
1024 {
1025 ExitFunction();
1026 }
1027 ExitOnFailure(hr, "Failed to get dword data out of column");
1028
1029LExit:
1030 return hr;
1031}
1032
1033extern "C" HRESULT DAPI SceGetColumnQword(
1034 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowReadHandle,
1035 __in DWORD dwColumnIndex,
1036 __in DWORD64 *pqwValue
1037 )
1038{
1039 HRESULT hr = S_OK;
1040 SCE_ROW *pRow = reinterpret_cast<SCE_ROW *>(rowReadHandle);
1041
1042 hr = GetColumnValueFixed(pRow, dwColumnIndex, 8, reinterpret_cast<BYTE *>(pqwValue));
1043 if (E_NOTFOUND == hr)
1044 {
1045 ExitFunction();
1046 }
1047 ExitOnFailure(hr, "Failed to get qword data out of column");
1048
1049LExit:
1050 return hr;
1051}
1052
1053extern "C" HRESULT DAPI SceGetColumnBool(
1054 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowReadHandle,
1055 __in DWORD dwColumnIndex,
1056 __out BOOL *pfValue
1057 )
1058{
1059 HRESULT hr = S_OK;
1060 short int sValue = 0;
1061 SCE_ROW *pRow = reinterpret_cast<SCE_ROW *>(rowReadHandle);
1062
1063 hr = GetColumnValueFixed(pRow, dwColumnIndex, 2, reinterpret_cast<BYTE *>(&sValue));
1064 if (E_NOTFOUND == hr)
1065 {
1066 ExitFunction();
1067 }
1068 ExitOnFailure(hr, "Failed to get data out of column");
1069
1070 if (sValue == 0x0000)
1071 {
1072 *pfValue = FALSE;
1073 }
1074 else
1075 {
1076 *pfValue = TRUE;
1077 }
1078
1079LExit:
1080 return hr;
1081}
1082
1083extern "C" HRESULT DAPI SceGetColumnString(
1084 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowReadHandle,
1085 __in DWORD dwColumnIndex,
1086 __out_z LPWSTR *psczValue
1087 )
1088{
1089 HRESULT hr = S_OK;
1090 SCE_ROW *pRow = reinterpret_cast<SCE_ROW *>(rowReadHandle);
1091 SIZE_T cbSize = 0;
1092
1093 hr = GetColumnValue(pRow, dwColumnIndex, reinterpret_cast<BYTE **>(psczValue), &cbSize);
1094 if (E_NOTFOUND == hr)
1095 {
1096 ExitFunction();
1097 }
1098 ExitOnFailure(hr, "Failed to get string data out of column");
1099
1100LExit:
1101 return hr;
1102}
1103
1104extern "C" HRESULT DAPI SceGetColumnSystemTime(
1105 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowReadHandle,
1106 __in DWORD dwColumnIndex,
1107 __out SYSTEMTIME *pst
1108 )
1109{
1110 HRESULT hr = S_OK;
1111 SCE_ROW *pRow = reinterpret_cast<SCE_ROW *>(rowReadHandle);
1112 DBTIMESTAMP dbTimeStamp = { };
1113
1114 hr = GetColumnValueFixed(pRow, dwColumnIndex, sizeof(dbTimeStamp), reinterpret_cast<BYTE *>(&dbTimeStamp));
1115 if (E_NOTFOUND == hr)
1116 {
1117 ExitFunction();
1118 }
1119 ExitOnFailure(hr, "Failed to get string data out of column");
1120
1121 pst->wYear = dbTimeStamp.year;
1122 pst->wMonth = dbTimeStamp.month;
1123 pst->wDay = dbTimeStamp.day;
1124 pst->wHour = dbTimeStamp.hour;
1125 pst->wMinute = dbTimeStamp.minute;
1126 pst->wSecond = dbTimeStamp.second;
1127
1128LExit:
1129 return hr;
1130}
1131
1132extern "C" void DAPI SceCloseTable(
1133 __in SCE_TABLE_SCHEMA *pTable
1134 )
1135{
1136 ReleaseNullObject(pTable->pIRowsetChange);
1137 ReleaseNullObject(pTable->pIRowset);
1138}
1139
1140extern "C" BOOL DAPI SceDatabaseChanged(
1141 __in SCE_DATABASE *pDatabase
1142 )
1143{
1144 SCE_DATABASE_INTERNAL *pDatabaseInternal = reinterpret_cast<SCE_DATABASE_INTERNAL *>(pDatabase->sdbHandle);
1145
1146 return pDatabaseInternal->fChanges;
1147}
1148
1149void DAPI SceResetDatabaseChanged(
1150 __in SCE_DATABASE *pDatabase
1151 )
1152{
1153 SCE_DATABASE_INTERNAL *pDatabaseInternal = reinterpret_cast<SCE_DATABASE_INTERNAL *>(pDatabase->sdbHandle);
1154
1155 pDatabaseInternal->fChanges = FALSE;
1156}
1157
1158extern "C" HRESULT DAPI SceCloseDatabase(
1159 __in SCE_DATABASE *pDatabase
1160 )
1161{
1162 HRESULT hr = S_OK;
1163
1164 ReleaseDatabase(pDatabase);
1165
1166 return hr;
1167}
1168
1169extern "C" HRESULT DAPI SceBeginQuery(
1170 __in SCE_DATABASE *pDatabase,
1171 __in DWORD dwTableIndex,
1172 __in DWORD dwIndex,
1173 __deref_out_bcount(SCE_QUERY_HANDLE_BYTES) SCE_QUERY_HANDLE *psqhHandle
1174 )
1175{
1176 HRESULT hr = S_OK;
1177 size_t cbAllocSize = 0;
1178 SCE_QUERY *psq = static_cast<SCE_QUERY*>(MemAlloc(sizeof(SCE_QUERY), TRUE));
1179 ExitOnNull(psq, hr, E_OUTOFMEMORY, "Failed to allocate new sce query");
1180
1181 psq->pTableSchema = &(pDatabase->pdsSchema->rgTables[dwTableIndex]);
1182 psq->pIndexSchema = &(psq->pTableSchema->rgIndexes[dwIndex]);
1183 psq->pDatabaseInternal = reinterpret_cast<SCE_DATABASE_INTERNAL *>(pDatabase->sdbHandle);
1184
1185 hr = ::SizeTMult(sizeof(DBBINDING), psq->pTableSchema->cColumns, &cbAllocSize);
1186 ExitOnFailure(hr, "Overflow while calculating allocation size for DBBINDING to begin query, columns: %u", psq->pTableSchema->cColumns);
1187
1188 psq->rgBinding = static_cast<DBBINDING *>(MemAlloc(cbAllocSize, TRUE));
1189 ExitOnNull(psq, hr, E_OUTOFMEMORY, "Failed to allocate DBBINDINGs for new sce query");
1190
1191 *psqhHandle = static_cast<SCE_QUERY_HANDLE>(psq);
1192 psq = NULL;
1193
1194LExit:
1195 ReleaseSceQuery(psq);
1196
1197 return hr;
1198}
1199
1200HRESULT DAPI SceSetQueryColumnBinary(
1201 __in_bcount(SCE_QUERY_RESULTS_BYTES) SCE_QUERY_HANDLE sqhHandle,
1202 __in_bcount(cbBuffer) const BYTE* pbBuffer,
1203 __in SIZE_T cbBuffer
1204 )
1205{
1206 HRESULT hr = S_OK;
1207 SCE_QUERY *pQuery = reinterpret_cast<SCE_QUERY *>(sqhHandle);
1208
1209 hr = SetColumnValue(pQuery->pTableSchema, pQuery->pIndexSchema->rgColumns[pQuery->dwBindingIndex], pbBuffer, cbBuffer, &pQuery->rgBinding[pQuery->dwBindingIndex], &pQuery->cbOffset, &pQuery->pbData);
1210 ExitOnFailure(hr, "Failed to set query column value as binary of size: %u", cbBuffer);
1211
1212 ++(pQuery->dwBindingIndex);
1213
1214LExit:
1215 return hr;
1216}
1217
1218HRESULT DAPI SceSetQueryColumnDword(
1219 __in_bcount(SCE_QUERY_RESULTS_BYTES) SCE_QUERY_HANDLE sqhHandle,
1220 __in const DWORD dwValue
1221 )
1222{
1223 HRESULT hr = S_OK;
1224 SCE_QUERY *pQuery = reinterpret_cast<SCE_QUERY *>(sqhHandle);
1225
1226 hr = SetColumnValue(pQuery->pTableSchema, pQuery->pIndexSchema->rgColumns[pQuery->dwBindingIndex], reinterpret_cast<const BYTE *>(&dwValue), 4, &pQuery->rgBinding[pQuery->dwBindingIndex], &pQuery->cbOffset, &pQuery->pbData);
1227 ExitOnFailure(hr, "Failed to set query column value as dword");
1228
1229 ++(pQuery->dwBindingIndex);
1230
1231LExit:
1232 return hr;
1233}
1234
1235HRESULT DAPI SceSetQueryColumnQword(
1236 __in_bcount(SCE_QUERY_RESULTS_BYTES) SCE_QUERY_HANDLE sqhHandle,
1237 __in const DWORD64 qwValue
1238 )
1239{
1240 HRESULT hr = S_OK;
1241 SCE_QUERY *pQuery = reinterpret_cast<SCE_QUERY *>(sqhHandle);
1242
1243 hr = SetColumnValue(pQuery->pTableSchema, pQuery->pIndexSchema->rgColumns[pQuery->dwBindingIndex], reinterpret_cast<const BYTE *>(&qwValue), 8, &pQuery->rgBinding[pQuery->dwBindingIndex], &pQuery->cbOffset, &pQuery->pbData);
1244 ExitOnFailure(hr, "Failed to set query column value as qword");
1245
1246 ++(pQuery->dwBindingIndex);
1247
1248LExit:
1249 return hr;
1250}
1251
1252HRESULT DAPI SceSetQueryColumnBool(
1253 __in_bcount(SCE_QUERY_RESULTS_BYTES) SCE_QUERY_HANDLE sqhHandle,
1254 __in const BOOL fValue
1255 )
1256{
1257 HRESULT hr = S_OK;
1258 short int sValue = fValue ? 1 : 0;
1259 SCE_QUERY *pQuery = reinterpret_cast<SCE_QUERY *>(sqhHandle);
1260
1261 hr = SetColumnValue(pQuery->pTableSchema, pQuery->pIndexSchema->rgColumns[pQuery->dwBindingIndex], reinterpret_cast<const BYTE *>(&sValue), 2, &pQuery->rgBinding[pQuery->dwBindingIndex], &pQuery->cbOffset, &pQuery->pbData);
1262 ExitOnFailure(hr, "Failed to set query column value as boolean");
1263
1264 ++(pQuery->dwBindingIndex);
1265
1266LExit:
1267 return hr;
1268}
1269
1270HRESULT DAPI SceSetQueryColumnString(
1271 __in_bcount(SCE_QUERY_RESULTS_BYTES) SCE_QUERY_HANDLE sqhHandle,
1272 __in_z_opt LPCWSTR wzString
1273 )
1274{
1275 HRESULT hr = S_OK;
1276 SCE_QUERY *pQuery = reinterpret_cast<SCE_QUERY *>(sqhHandle);
1277 SIZE_T cbSize = (NULL == wzString) ? 0 : ((lstrlenW(wzString) + 1) * sizeof(WCHAR));
1278
1279 hr = SetColumnValue(pQuery->pTableSchema, pQuery->pIndexSchema->rgColumns[pQuery->dwBindingIndex], reinterpret_cast<const BYTE *>(wzString), cbSize, &pQuery->rgBinding[pQuery->dwBindingIndex], &pQuery->cbOffset, &pQuery->pbData);
1280 ExitOnFailure(hr, "Failed to set query column value as string");
1281
1282 ++(pQuery->dwBindingIndex);
1283
1284LExit:
1285 return hr;
1286}
1287
1288HRESULT DAPI SceSetQueryColumnSystemTime(
1289 __in_bcount(SCE_QUERY_RESULTS_BYTES) SCE_QUERY_HANDLE sqhHandle,
1290 __in const SYSTEMTIME *pst
1291 )
1292{
1293 HRESULT hr = S_OK;
1294 DBTIMESTAMP dbTimeStamp = { };
1295 SCE_QUERY *pQuery = reinterpret_cast<SCE_QUERY *>(sqhHandle);
1296
1297 dbTimeStamp.year = pst->wYear;
1298 dbTimeStamp.month = pst->wMonth;
1299 dbTimeStamp.day = pst->wDay;
1300 dbTimeStamp.hour = pst->wHour;
1301 dbTimeStamp.minute = pst->wMinute;
1302 dbTimeStamp.second = pst->wSecond;
1303
1304 hr = SetColumnValue(pQuery->pTableSchema, pQuery->pIndexSchema->rgColumns[pQuery->dwBindingIndex], reinterpret_cast<const BYTE *>(&dbTimeStamp), sizeof(dbTimeStamp), &pQuery->rgBinding[pQuery->dwBindingIndex], &pQuery->cbOffset, &pQuery->pbData);
1305 ExitOnFailure(hr, "Failed to set query column value as DBTIMESTAMP");
1306
1307 ++(pQuery->dwBindingIndex);
1308
1309LExit:
1310 return hr;
1311}
1312
1313HRESULT DAPI SceSetQueryColumnEmpty(
1314 __in_bcount(SCE_QUERY_RESULTS_BYTES) SCE_QUERY_HANDLE sqhHandle
1315 )
1316{
1317 HRESULT hr = S_OK;
1318 SCE_QUERY *pQuery = reinterpret_cast<SCE_QUERY *>(sqhHandle);
1319
1320 hr = SetColumnValue(pQuery->pTableSchema, pQuery->pIndexSchema->rgColumns[pQuery->dwBindingIndex], NULL, 0, &pQuery->rgBinding[pQuery->dwBindingIndex], &pQuery->cbOffset, &pQuery->pbData);
1321 ExitOnFailure(hr, "Failed to set query column value as empty value");
1322
1323 ++(pQuery->dwBindingIndex);
1324
1325LExit:
1326 return hr;
1327}
1328
1329HRESULT DAPI SceRunQueryExact(
1330 __in_bcount(SCE_QUERY_RESULTS_BYTES) SCE_QUERY_HANDLE *psqhHandle,
1331 __out_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE *pRowHandle
1332 )
1333{
1334 HRESULT hr = S_OK;
1335 SCE_QUERY_RESULTS *pQueryResults = NULL;
1336
1337 hr = RunQuery(FALSE, *psqhHandle, &pQueryResults);
1338 if (E_NOTFOUND == hr)
1339 {
1340 ExitFunction();
1341 }
1342 ExitOnFailure(hr, "Failed to run query exact");
1343
1344 hr = SceGetNextResultRow(reinterpret_cast<SCE_QUERY_RESULTS_HANDLE>(pQueryResults), pRowHandle);
1345 if (E_NOTFOUND == hr)
1346 {
1347 ExitFunction();
1348 }
1349 ExitOnFailure(hr, "Failed to get next row out of results");
1350
1351LExit:
1352 ReleaseNullSceQuery(*psqhHandle);
1353 ReleaseSceQueryResults(pQueryResults);
1354
1355 return hr;
1356}
1357
1358extern "C" HRESULT DAPI SceRunQueryRange(
1359 __in_bcount(SCE_QUERY_BYTES) SCE_QUERY_HANDLE *psqhHandle,
1360 __deref_out_bcount(SCE_QUERY_RESULTS_BYTES) SCE_QUERY_RESULTS_HANDLE *psqrhHandle
1361 )
1362{
1363 HRESULT hr = S_OK;
1364 SCE_QUERY_RESULTS **ppQueryResults = reinterpret_cast<SCE_QUERY_RESULTS **>(psqrhHandle);
1365
1366 hr = RunQuery(TRUE, *psqhHandle, ppQueryResults);
1367 if (E_NOTFOUND == hr)
1368 {
1369 ExitFunction();
1370 }
1371 ExitOnFailure(hr, "Failed to run query for range");
1372
1373LExit:
1374 ReleaseNullSceQuery(*psqhHandle);
1375
1376 return hr;
1377}
1378
1379extern "C" HRESULT DAPI SceGetNextResultRow(
1380 __in_bcount(SCE_QUERY_RESULTS_BYTES) SCE_QUERY_RESULTS_HANDLE sqrhHandle,
1381 __out_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE *pRowHandle
1382 )
1383{
1384 HRESULT hr = S_OK;
1385 HROW hRow = DB_NULL_HROW;
1386 HROW *phRow = &hRow;
1387 DBCOUNTITEM cRowsObtained = 0;
1388 SCE_ROW *pRow = NULL;
1389 SCE_QUERY_RESULTS *pQueryResults = reinterpret_cast<SCE_QUERY_RESULTS *>(sqrhHandle);
1390
1391 Assert(pRowHandle && (*pRowHandle == NULL));
1392
1393 hr = pQueryResults->pIRowset->GetNextRows(DB_NULL_HCHAPTER, 0, 1, &cRowsObtained, &phRow);
1394 if (DB_S_ENDOFROWSET == hr)
1395 {
1396 ExitFunction1(hr = E_NOTFOUND);
1397 }
1398 ExitOnFailure(hr, "Failed to get next first row");
1399
1400 pRow = reinterpret_cast<SCE_ROW *>(MemAlloc(sizeof(SCE_ROW), TRUE));
1401 ExitOnNull(pRow, hr, E_OUTOFMEMORY, "Failed to allocate SCE_ROW struct");
1402
1403 pRow->pDatabaseInternal = reinterpret_cast<SCE_DATABASE_INTERNAL *>(pQueryResults->pDatabaseInternal);
1404 pRow->hRow = hRow;
1405 pRow->pTableSchema = pQueryResults->pTableSchema;
1406 pRow->pIRowset = pQueryResults->pIRowset;
1407 pRow->pIRowset->AddRef();
1408
1409 *pRowHandle = reinterpret_cast<SCE_ROW_HANDLE>(pRow);
1410 pRow = NULL;
1411 hRow = DB_NULL_HROW;
1412
1413LExit:
1414 if (DB_NULL_HROW != hRow)
1415 {
1416 pQueryResults->pIRowset->ReleaseRows(1, &hRow, NULL, NULL, NULL);
1417 }
1418 ReleaseSceRow(pRow);
1419
1420 return hr;
1421}
1422
1423extern "C" void DAPI SceFreeRow(
1424 __in_bcount(SCE_ROW_HANDLE_BYTES) SCE_ROW_HANDLE rowHandle
1425 )
1426{
1427 SCE_ROW *pRow = reinterpret_cast<SCE_ROW *>(rowHandle);
1428
1429 if (DB_NULL_HROW != pRow->hRow)
1430 {
1431 pRow->pIRowset->ReleaseRows(1, &pRow->hRow, NULL, NULL, NULL);
1432 }
1433 ReleaseObject(pRow->pIRowset);
1434 ReleaseMem(pRow->rgBinding);
1435 ReleaseMem(pRow->pbData);
1436 ReleaseMem(pRow);
1437}
1438
1439void DAPI SceFreeQuery(
1440 __in_bcount(SCE_QUERY_RESULTS_BYTES) SCE_QUERY_HANDLE sqhHandle
1441 )
1442{
1443 SCE_QUERY *pQuery = reinterpret_cast<SCE_QUERY *>(sqhHandle);
1444
1445 ReleaseMem(pQuery->rgBinding);
1446 ReleaseMem(pQuery->pbData);
1447 ReleaseMem(pQuery);
1448}
1449
1450void DAPI SceFreeQueryResults(
1451 __in_bcount(SCE_QUERY_RESULTS_BYTES) SCE_QUERY_RESULTS_HANDLE sqrhHandle
1452 )
1453{
1454 SCE_QUERY_RESULTS *pQueryResults = reinterpret_cast<SCE_QUERY_RESULTS *>(sqrhHandle);
1455
1456 ReleaseObject(pQueryResults->pIRowset);
1457 ReleaseMem(pQueryResults);
1458}
1459
1460// internal function definitions
1461static HRESULT CreateSqlCe(
1462 __in_z_opt LPCWSTR wzSqlCeDllPath,
1463 __out IDBInitialize **ppIDBInitialize,
1464 __out_opt HMODULE *phSqlCeDll
1465 )
1466{
1467 HRESULT hr = S_OK;
1468 LPWSTR sczPath = NULL;
1469 LPWSTR sczDirectory = NULL;
1470 LPWSTR sczDllFullPath = NULL;
1471
1472 if (NULL == wzSqlCeDllPath)
1473 {
1474 hr = CoCreateInstance(CLSID_SQLSERVERCE, 0, CLSCTX_INPROC_SERVER, IID_IDBInitialize, reinterpret_cast<void **>(ppIDBInitialize));
1475 ExitOnFailure(hr, "Failed to get IDBInitialize interface");
1476 }
1477 else
1478 {
1479 // First try loading DLL from the path of our EXE
1480 hr = PathForCurrentProcess(&sczPath, NULL);
1481 ExitOnFailure(hr, "Failed to get path for current process");
1482
1483 hr = PathGetDirectory(sczPath, &sczDirectory);
1484 ExitOnFailure(hr, "Failed to get directory of current process");
1485
1486 hr = PathConcat(sczDirectory, wzSqlCeDllPath, &sczDllFullPath);
1487 ExitOnFailure(hr, "Failed to concatenate current directory and DLL filename");
1488
1489 *phSqlCeDll = ::LoadLibraryW(sczDllFullPath);
1490
1491 // If that failed, fallback to loading from current path
1492 if (NULL == *phSqlCeDll)
1493 {
1494 hr = DirGetCurrent(&sczDirectory);
1495 ExitOnFailure(hr, "Failed to get current directory");
1496
1497 hr = PathConcat(sczDirectory, wzSqlCeDllPath, &sczDllFullPath);
1498 ExitOnFailure(hr, "Failed to concatenate current directory and DLL filename");
1499
1500 *phSqlCeDll = ::LoadLibraryW(sczDllFullPath);
1501 ExitOnNullWithLastError(*phSqlCeDll, hr, "Failed to open Sql CE DLL: %ls", sczDllFullPath);
1502 }
1503
1504 HRESULT (WINAPI *pfnGetFactory)(REFCLSID, REFIID, void**);
1505 pfnGetFactory = (HRESULT (WINAPI *)(REFCLSID, REFIID, void**))GetProcAddress(*phSqlCeDll, "DllGetClassObject");
1506
1507 IClassFactory* pFactory = NULL;
1508 hr = pfnGetFactory(CLSID_SQLSERVERCE, IID_IClassFactory, (void**)&pFactory);
1509 ExitOnFailure(hr, "Failed to get factory for IID_IDBInitialize from DLL: %ls", sczDllFullPath);
1510 ExitOnNull(pFactory, hr, E_UNEXPECTED, "GetFactory returned success, but pFactory was NULL");
1511
1512 hr = pFactory->CreateInstance(NULL, IID_IDBInitialize, (void**)ppIDBInitialize);
1513 pFactory->Release();
1514 }
1515
1516LExit:
1517 ReleaseStr(sczPath);
1518 ReleaseStr(sczDirectory);
1519 ReleaseStr(sczDllFullPath);
1520
1521 return hr;
1522}
1523
1524static HRESULT RunQuery(
1525 __in BOOL fRange,
1526 __in_bcount(SCE_QUERY_BYTES) SCE_QUERY_HANDLE psqhHandle,
1527 __deref_out_bcount(SCE_QUERY_RESULTS_BYTES) SCE_QUERY_RESULTS **ppQueryResults
1528 )
1529{
1530 HRESULT hr = S_OK;
1531 DBID tableID = { };
1532 DBID indexID = { };
1533 IAccessor *pIAccessor = NULL;
1534 IRowsetIndex *pIRowsetIndex = NULL;
1535 IRowset *pIRowset = NULL;
1536 HACCESSOR hAccessor = DB_NULL_HACCESSOR;
1537 SCE_QUERY *pQuery = reinterpret_cast<SCE_QUERY *>(psqhHandle);
1538 SCE_QUERY_RESULTS *pQueryResults = NULL;
1539 DBPROPSET rgdbpIndexPropSet[1];
1540 DBPROP rgdbpIndexProp[1];
1541
1542 rgdbpIndexPropSet[0].cProperties = 1;
1543 rgdbpIndexPropSet[0].guidPropertySet = DBPROPSET_ROWSET;
1544 rgdbpIndexPropSet[0].rgProperties = rgdbpIndexProp;
1545
1546 rgdbpIndexProp[0].dwPropertyID = DBPROP_IRowsetIndex;
1547 rgdbpIndexProp[0].dwOptions = DBPROPOPTIONS_REQUIRED;
1548 rgdbpIndexProp[0].colid = DB_NULLID;
1549 rgdbpIndexProp[0].vValue.vt = VT_BOOL;
1550 rgdbpIndexProp[0].vValue.boolVal = VARIANT_TRUE;
1551
1552 tableID.eKind = DBKIND_NAME;
1553 tableID.uName.pwszName = const_cast<WCHAR *>(pQuery->pTableSchema->wzName);
1554
1555 indexID.eKind = DBKIND_NAME;
1556 indexID.uName.pwszName = const_cast<WCHAR *>(pQuery->pIndexSchema->wzName);
1557
1558 hr = pQuery->pDatabaseInternal->pIOpenRowset->OpenRowset(NULL, &tableID, &indexID, IID_IRowsetIndex, _countof(rgdbpIndexPropSet), rgdbpIndexPropSet, (IUnknown**) &pIRowsetIndex);
1559 ExitOnFailure(hr, "Failed to open IRowsetIndex");
1560
1561 hr = pIRowsetIndex->QueryInterface(IID_IRowset, reinterpret_cast<void **>(&pIRowset));
1562 ExitOnFailure(hr, "Failed to get IRowset interface from IRowsetIndex");
1563
1564 hr = pIRowset->QueryInterface(IID_IAccessor, reinterpret_cast<void **>(&pIAccessor));
1565 ExitOnFailure(hr, "Failed to get IAccessor interface");
1566
1567 hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, pQuery->dwBindingIndex, pQuery->rgBinding, 0, &hAccessor, NULL);
1568 ExitOnFailure(hr, "Failed to create accessor");
1569
1570 if (!fRange)
1571 {
1572 hr = pIRowsetIndex->Seek(hAccessor, pQuery->dwBindingIndex, pQuery->pbData, DBSEEK_FIRSTEQ);
1573 if (DB_E_NOTFOUND == hr)
1574 {
1575 ExitFunction1(hr = E_NOTFOUND);
1576 }
1577 ExitOnFailure(hr, "Failed to seek to record");
1578 }
1579 else
1580 {
1581 // If ALL columns in the index were specified, do a full key match
1582 if (pQuery->dwBindingIndex == pQuery->pIndexSchema->cColumns)
1583 {
1584 hr = pIRowsetIndex->SetRange(hAccessor, pQuery->dwBindingIndex, pQuery->pbData, 0, NULL, DBRANGE_MATCH);
1585 }
1586 else
1587 {
1588 // Otherwise, just match the specified keys.
1589 // We really want to use DBRANGE_MATCH_N_SHIFT here, but SQL CE doesn't appear to support it
1590 // So instead, we set the start and end to the same partial key, and then allow inclusive matching
1591 // This appears to accomplish the same thing
1592 hr = pIRowsetIndex->SetRange(hAccessor, pQuery->dwBindingIndex, pQuery->pbData, pQuery->dwBindingIndex, pQuery->pbData, 0);
1593 }
1594 if (DB_E_NOTFOUND == hr || E_NOTFOUND == hr)
1595 {
1596 ExitFunction1(hr = E_NOTFOUND);
1597 }
1598 ExitOnFailure(hr, "Failed to set range to find records");
1599 }
1600
1601 pQueryResults = reinterpret_cast<SCE_QUERY_RESULTS *>(MemAlloc(sizeof(SCE_QUERY_RESULTS), TRUE));
1602 ExitOnNull(pQueryResults, hr, E_OUTOFMEMORY, "Failed to allocate query results struct");
1603
1604 pQueryResults->pDatabaseInternal = pQuery->pDatabaseInternal;
1605 pQueryResults->pTableSchema = pQuery->pTableSchema;
1606 pQueryResults->pIRowset = pIRowset;
1607 pIRowset = NULL;
1608
1609 *ppQueryResults = pQueryResults;
1610 pQueryResults = NULL;
1611
1612LExit:
1613 if (DB_NULL_HACCESSOR != hAccessor)
1614 {
1615 pIAccessor->ReleaseAccessor(hAccessor, NULL);
1616 }
1617 ReleaseObject(pIAccessor);
1618 ReleaseObject(pIRowset);
1619 ReleaseObject(pIRowsetIndex);
1620 ReleaseMem(pQueryResults);
1621 ReleaseSceQueryResults(pQueryResults);
1622
1623 return hr;
1624}
1625
1626static HRESULT FillOutColumnDescFromSchema(
1627 __in const SCE_COLUMN_SCHEMA *pColumnSchema,
1628 __out DBCOLUMNDESC *pColumnDesc
1629 )
1630{
1631 HRESULT hr = S_OK;
1632 DWORD dwColumnProperties = 0;
1633 DWORD dwColumnPropertyIndex = 0;
1634 BOOL fFixedSize = FALSE;
1635
1636 pColumnDesc->dbcid.eKind = DBKIND_NAME;
1637 pColumnDesc->dbcid.uName.pwszName = (WCHAR *)pColumnSchema->wzName;
1638 pColumnDesc->wType = pColumnSchema->dbtColumnType;
1639 pColumnDesc->ulColumnSize = pColumnSchema->dwLength;
1640 if (0 == pColumnDesc->ulColumnSize && (DBTYPE_WSTR == pColumnDesc->wType || DBTYPE_BYTES == pColumnDesc->wType))
1641 {
1642 fFixedSize = FALSE;
1643 }
1644 else
1645 {
1646 fFixedSize = TRUE;
1647 }
1648
1649 dwColumnProperties = 1;
1650 if (pColumnSchema->fAutoIncrement)
1651 {
1652 ++dwColumnProperties;
1653 }
1654 if (!pColumnSchema->fNullable)
1655 {
1656 ++dwColumnProperties;
1657 }
1658
1659 if (0 < dwColumnProperties)
1660 {
1661 pColumnDesc->cPropertySets = 1;
1662 pColumnDesc->rgPropertySets = reinterpret_cast<DBPROPSET *>(MemAlloc(sizeof(DBPROPSET), TRUE));
1663 ExitOnNull(pColumnDesc->rgPropertySets, hr, E_OUTOFMEMORY, "Failed to allocate propset object while setting up column parameters");
1664
1665 pColumnDesc->rgPropertySets[0].cProperties = dwColumnProperties;
1666 pColumnDesc->rgPropertySets[0].guidPropertySet = DBPROPSET_COLUMN;
1667 pColumnDesc->rgPropertySets[0].rgProperties = reinterpret_cast<DBPROP *>(MemAlloc(sizeof(DBPROP) * dwColumnProperties, TRUE));
1668
1669 dwColumnPropertyIndex = 0;
1670 if (pColumnSchema->fAutoIncrement)
1671 {
1672 pColumnDesc->rgPropertySets[0].rgProperties[dwColumnPropertyIndex].dwPropertyID = DBPROP_COL_AUTOINCREMENT;
1673 pColumnDesc->rgPropertySets[0].rgProperties[dwColumnPropertyIndex].dwOptions = DBPROPOPTIONS_REQUIRED;
1674 pColumnDesc->rgPropertySets[0].rgProperties[dwColumnPropertyIndex].colid = DB_NULLID;
1675 pColumnDesc->rgPropertySets[0].rgProperties[dwColumnPropertyIndex].vValue.vt = VT_BOOL;
1676 pColumnDesc->rgPropertySets[0].rgProperties[dwColumnPropertyIndex].vValue.boolVal = VARIANT_TRUE;
1677 ++dwColumnPropertyIndex;
1678 }
1679 if (!pColumnSchema->fNullable)
1680 {
1681 pColumnDesc->rgPropertySets[0].rgProperties[dwColumnPropertyIndex].dwPropertyID = DBPROP_COL_NULLABLE;
1682 pColumnDesc->rgPropertySets[0].rgProperties[dwColumnPropertyIndex].dwOptions = DBPROPOPTIONS_REQUIRED;
1683 pColumnDesc->rgPropertySets[0].rgProperties[dwColumnPropertyIndex].colid = DB_NULLID;
1684 pColumnDesc->rgPropertySets[0].rgProperties[dwColumnPropertyIndex].vValue.vt = VT_BOOL;
1685 pColumnDesc->rgPropertySets[0].rgProperties[dwColumnPropertyIndex].vValue.boolVal = VARIANT_FALSE;
1686 ++dwColumnPropertyIndex;
1687 }
1688
1689 pColumnDesc->rgPropertySets[0].rgProperties[dwColumnPropertyIndex].dwPropertyID = DBPROP_COL_FIXEDLENGTH;
1690 pColumnDesc->rgPropertySets[0].rgProperties[dwColumnPropertyIndex].dwOptions = DBPROPOPTIONS_REQUIRED;
1691 pColumnDesc->rgPropertySets[0].rgProperties[dwColumnPropertyIndex].colid = DB_NULLID;
1692 pColumnDesc->rgPropertySets[0].rgProperties[dwColumnPropertyIndex].vValue.vt = VT_BOOL;
1693 pColumnDesc->rgPropertySets[0].rgProperties[dwColumnPropertyIndex].vValue.boolVal = fFixedSize ? VARIANT_TRUE : VARIANT_FALSE;
1694 ++dwColumnPropertyIndex;
1695 }
1696
1697LExit:
1698 return hr;
1699}
1700
1701static HRESULT EnsureSchema(
1702 __in SCE_DATABASE *pDatabase,
1703 __in SCE_DATABASE_SCHEMA *pdsSchema
1704 )
1705{
1706 HRESULT hr = S_OK;
1707 size_t cbAllocSize = 0;
1708 BOOL fInTransaction = FALSE;
1709 BOOL fSchemaNeedsSetup = TRUE;
1710 DBID tableID = { };
1711 DBID indexID = { };
1712 DBPROPSET rgdbpIndexPropSet[1];
1713 DBPROPSET rgdbpRowSetPropSet[1];
1714 DBPROP rgdbpIndexProp[1];
1715 DBPROP rgdbpRowSetProp[1];
1716 DBCOLUMNDESC *rgColumnDescriptions = NULL;
1717 DBINDEXCOLUMNDESC *rgIndexColumnDescriptions = NULL;
1718 DWORD cIndexColumnDescriptions = 0;
1719 DWORD dwTableColumnIndex = 0;
1720 SCE_DATABASE_INTERNAL *pDatabaseInternal = reinterpret_cast<SCE_DATABASE_INTERNAL *>(pDatabase->sdbHandle);
1721 ITableDefinition *pTableDefinition = NULL;
1722 IIndexDefinition *pIndexDefinition = NULL;
1723 IRowsetIndex *pIRowsetIndex = NULL;
1724
1725 rgdbpRowSetPropSet[0].cProperties = 1;
1726 rgdbpRowSetPropSet[0].guidPropertySet = DBPROPSET_ROWSET;
1727 rgdbpRowSetPropSet[0].rgProperties = rgdbpRowSetProp;
1728
1729 rgdbpRowSetProp[0].dwPropertyID = DBPROP_IRowsetChange;
1730 rgdbpRowSetProp[0].dwOptions = DBPROPOPTIONS_REQUIRED;
1731 rgdbpRowSetProp[0].colid = DB_NULLID;
1732 rgdbpRowSetProp[0].vValue.vt = VT_BOOL;
1733 rgdbpRowSetProp[0].vValue.boolVal = VARIANT_TRUE;
1734
1735 rgdbpIndexPropSet[0].cProperties = 1;
1736 rgdbpIndexPropSet[0].guidPropertySet = DBPROPSET_INDEX;
1737 rgdbpIndexPropSet[0].rgProperties = rgdbpIndexProp;
1738
1739 rgdbpIndexProp[0].dwPropertyID = DBPROP_INDEX_NULLS;
1740 rgdbpIndexProp[0].dwOptions = DBPROPOPTIONS_REQUIRED;
1741 rgdbpIndexProp[0].colid = DB_NULLID;
1742 rgdbpIndexProp[0].vValue.vt = VT_I4;
1743 rgdbpIndexProp[0].vValue.lVal = DBPROPVAL_IN_DISALLOWNULL;
1744
1745 hr = pDatabaseInternal->pISessionProperties->QueryInterface(IID_ITableDefinition, reinterpret_cast<void **>(&pTableDefinition));
1746 ExitOnFailure(hr, "Failed to get ITableDefinition for table creation");
1747
1748 hr = pDatabaseInternal->pISessionProperties->QueryInterface(IID_IIndexDefinition, reinterpret_cast<void **>(&pIndexDefinition));
1749 ExitOnFailure(hr, "Failed to get IIndexDefinition for index creation");
1750
1751 hr = SceBeginTransaction(pDatabase);
1752 ExitOnFailure(hr, "Failed to start transaction for ensuring schema");
1753 fInTransaction = TRUE;
1754
1755 for (DWORD dwTable = 0; dwTable < pdsSchema->cTables; ++dwTable)
1756 {
1757 tableID.eKind = DBKIND_NAME;
1758 tableID.uName.pwszName = const_cast<WCHAR *>(pdsSchema->rgTables[dwTable].wzName);
1759
1760 // Fill out each column description struct as appropriate, to be used for creating the table, or confirming the table's columns all exist
1761 rgColumnDescriptions = static_cast<DBCOLUMNDESC *>(MemAlloc(sizeof(DBCOLUMNDESC) * pdsSchema->rgTables[dwTable].cColumns, TRUE));
1762 ExitOnNull(rgColumnDescriptions, hr, E_OUTOFMEMORY, "Failed to allocate column description array while creating table");
1763
1764 for (DWORD i = 0; i < pdsSchema->rgTables[dwTable].cColumns; ++i)
1765 {
1766 hr = FillOutColumnDescFromSchema(pdsSchema->rgTables[dwTable].rgColumns + i, rgColumnDescriptions + i);
1767 ExitOnFailure(hr, "Failed to fill out column description from schema");
1768 }
1769
1770 // First try to open the table - or if it doesn't exist, create it
1771 hr = pDatabaseInternal->pIOpenRowset->OpenRowset(NULL, &tableID, NULL, IID_IRowset, _countof(rgdbpRowSetPropSet), rgdbpRowSetPropSet, reinterpret_cast<IUnknown **>(&pdsSchema->rgTables[dwTable].pIRowset));
1772 if (DB_E_NOTABLE == hr)
1773 {
1774 // The table doesn't exist, so let's create it
1775 hr = pTableDefinition->CreateTable(NULL, &tableID, pdsSchema->rgTables[dwTable].cColumns, rgColumnDescriptions, IID_IUnknown, _countof(rgdbpRowSetPropSet), rgdbpRowSetPropSet, NULL, NULL);
1776 ExitOnFailure(hr, "Failed to create table: %ls", pdsSchema->rgTables[dwTable].wzName);
1777 }
1778 else
1779 {
1780 ExitOnFailure(hr, "Failed to open table %ls while ensuring schema", tableID.uName.pwszName);
1781
1782 // Close any rowset we opened
1783 ReleaseNullObject(pdsSchema->rgTables[dwTable].pIRowset);
1784
1785 // If it does exist, make sure all columns are in the table
1786 // Only nullable columns can be added to an existing table
1787 for (DWORD i = 1; i < pdsSchema->rgTables[dwTable].cColumns; ++i)
1788 {
1789 if (pdsSchema->rgTables[dwTable].rgColumns[i].fNullable)
1790 hr = pTableDefinition->AddColumn(&tableID, rgColumnDescriptions + i, NULL);
1791 if (DB_E_DUPLICATECOLUMNID == hr)
1792 {
1793 hr = S_OK;
1794 }
1795 ExitOnFailure(hr, "Failed to add column %ls", pdsSchema->rgTables[dwTable].rgColumns[i].wzName);
1796 }
1797 }
1798
1799#pragma prefast(push)
1800#pragma prefast(disable:26010)
1801 hr = EnsureLocalColumnConstraints(pTableDefinition, &tableID, pdsSchema->rgTables + dwTable);
1802#pragma prefast(pop)
1803 ExitOnFailure(hr, "Failed to ensure local column constraints for table: %ls", pdsSchema->rgTables[dwTable].wzName);
1804
1805 for (DWORD i = 0; i < pdsSchema->rgTables[dwTable].cColumns; ++i)
1806 {
1807 if (NULL != rgColumnDescriptions[i].rgPropertySets)
1808 {
1809 ReleaseMem(rgColumnDescriptions[i].rgPropertySets[0].rgProperties);
1810 ReleaseMem(rgColumnDescriptions[i].rgPropertySets);
1811 }
1812 }
1813
1814 ReleaseNullMem(rgColumnDescriptions);
1815 if (0 < pdsSchema->rgTables[dwTable].cIndexes)
1816 {
1817 // Now create indexes for the table
1818 for (DWORD dwIndex = 0; dwIndex < pdsSchema->rgTables[dwTable].cIndexes; ++dwIndex)
1819 {
1820 indexID.eKind = DBKIND_NAME;
1821 indexID.uName.pwszName = pdsSchema->rgTables[dwTable].rgIndexes[dwIndex].wzName;
1822
1823 // Check if the index exists
1824 hr = pDatabaseInternal->pIOpenRowset->OpenRowset(NULL, &tableID, &indexID, IID_IRowsetIndex, 0, NULL, (IUnknown**) &pIRowsetIndex);
1825 if (SUCCEEDED(hr))
1826 {
1827 // TODO: If one with the same name exists, check if the schema actually matches
1828 ReleaseNullObject(pIRowsetIndex);
1829 continue;
1830 }
1831 hr = S_OK;
1832
1833 hr = ::SizeTMult(sizeof(DBINDEXCOLUMNDESC), pdsSchema->rgTables[dwTable].rgIndexes[dwIndex].cColumns, &cbAllocSize);
1834 ExitOnFailure(hr, "Overflow while calculating allocation size for DBINDEXCOLUMNDESC, columns: %u", pdsSchema->rgTables[dwTable].rgIndexes[dwIndex].cColumns);
1835
1836 rgIndexColumnDescriptions = reinterpret_cast<DBINDEXCOLUMNDESC *>(MemAlloc(cbAllocSize, TRUE));
1837 ExitOnNull(rgIndexColumnDescriptions, hr, E_OUTOFMEMORY, "Failed to allocate structure to hold index column descriptions");
1838 cIndexColumnDescriptions = pdsSchema->rgTables[dwTable].rgIndexes[dwIndex].cColumns;
1839
1840 for (DWORD dwColumnIndex = 0; dwColumnIndex < cIndexColumnDescriptions; ++dwColumnIndex)
1841 {
1842 dwTableColumnIndex = pdsSchema->rgTables[dwTable].rgIndexes[dwIndex].rgColumns[dwColumnIndex];
1843
1844 rgIndexColumnDescriptions[dwColumnIndex].pColumnID = reinterpret_cast<DBID *>(MemAlloc(sizeof(DBID), TRUE));
1845 rgIndexColumnDescriptions[dwColumnIndex].pColumnID->eKind = DBKIND_NAME;
1846 rgIndexColumnDescriptions[dwColumnIndex].pColumnID->uName.pwszName = const_cast<LPOLESTR>(pdsSchema->rgTables[dwTable].rgColumns[dwTableColumnIndex].wzName);
1847 rgIndexColumnDescriptions[dwColumnIndex].eIndexColOrder = pdsSchema->rgTables[dwTable].rgColumns[dwTableColumnIndex].fDescending ? DBINDEX_COL_ORDER_DESC : DBINDEX_COL_ORDER_ASC;
1848 }
1849
1850 hr = pIndexDefinition->CreateIndex(&tableID, &indexID, static_cast<DBORDINAL>(pdsSchema->rgTables[dwTable].rgIndexes[dwIndex].cColumns), rgIndexColumnDescriptions, 1, rgdbpIndexPropSet, NULL);
1851 if (DB_E_DUPLICATEINDEXID == hr)
1852 {
1853 // If the index already exists, no worries
1854 hr = S_OK;
1855 }
1856 ExitOnFailure(hr, "Failed to create index named %ls into table named %ls", pdsSchema->rgTables[dwTable].rgIndexes[dwIndex].wzName, pdsSchema->rgTables[dwTable].wzName);
1857
1858 for (DWORD i = 0; i < cIndexColumnDescriptions; ++i)
1859 {
1860 ReleaseMem(rgIndexColumnDescriptions[i].pColumnID);
1861 }
1862
1863 cIndexColumnDescriptions = 0;
1864 ReleaseNullMem(rgIndexColumnDescriptions);
1865 }
1866 }
1867 }
1868
1869 // Now once all tables have been created, create foreign key relationships
1870 if (fSchemaNeedsSetup)
1871 {
1872 for (DWORD dwTable = 0; dwTable < pdsSchema->cTables; ++dwTable)
1873 {
1874 tableID.eKind = DBKIND_NAME;
1875 tableID.uName.pwszName = const_cast<WCHAR *>(pdsSchema->rgTables[dwTable].wzName);
1876
1877 // Setup any constraints for the table's columns
1878 hr = EnsureForeignColumnConstraints(pTableDefinition, &tableID, pdsSchema->rgTables + dwTable, pdsSchema);
1879 ExitOnFailure(hr, "Failed to ensure foreign column constraints for table: %ls", pdsSchema->rgTables[dwTable].wzName);
1880 }
1881 }
1882
1883 hr = SceCommitTransaction(pDatabase);
1884 ExitOnFailure(hr, "Failed to commit transaction for ensuring schema");
1885 fInTransaction = FALSE;
1886
1887 hr = OpenSchema(pDatabase, pdsSchema);
1888 ExitOnFailure(hr, "Failed to open schema");
1889
1890LExit:
1891 ReleaseObject(pTableDefinition);
1892 ReleaseObject(pIndexDefinition);
1893 ReleaseObject(pIRowsetIndex);
1894
1895 if (fInTransaction)
1896 {
1897 SceRollbackTransaction(pDatabase);
1898 }
1899
1900 for (DWORD i = 0; i < cIndexColumnDescriptions; ++i)
1901 {
1902 ReleaseMem(rgIndexColumnDescriptions[i].pColumnID);
1903 }
1904
1905 ReleaseMem(rgIndexColumnDescriptions);
1906 ReleaseMem(rgColumnDescriptions);
1907
1908 return hr;
1909}
1910
1911static HRESULT OpenSchema(
1912 __in SCE_DATABASE *pDatabase,
1913 __in SCE_DATABASE_SCHEMA *pdsSchema
1914 )
1915{
1916 HRESULT hr = S_OK;
1917 SCE_DATABASE_INTERNAL *pDatabaseInternal = reinterpret_cast<SCE_DATABASE_INTERNAL *>(pDatabase->sdbHandle);
1918 DBID tableID = { };
1919 DBPROPSET rgdbpRowSetPropSet[1];
1920 DBPROP rgdbpRowSetProp[1];
1921
1922 rgdbpRowSetPropSet[0].cProperties = 1;
1923 rgdbpRowSetPropSet[0].guidPropertySet = DBPROPSET_ROWSET;
1924 rgdbpRowSetPropSet[0].rgProperties = rgdbpRowSetProp;
1925
1926 rgdbpRowSetProp[0].dwPropertyID = DBPROP_IRowsetChange;
1927 rgdbpRowSetProp[0].dwOptions = DBPROPOPTIONS_REQUIRED;
1928 rgdbpRowSetProp[0].colid = DB_NULLID;
1929 rgdbpRowSetProp[0].vValue.vt = VT_BOOL;
1930 rgdbpRowSetProp[0].vValue.boolVal = VARIANT_TRUE;
1931
1932 // Finally, open all tables
1933 for (DWORD dwTable = 0; dwTable < pdsSchema->cTables; ++dwTable)
1934 {
1935 tableID.eKind = DBKIND_NAME;
1936 tableID.uName.pwszName = const_cast<WCHAR *>(pdsSchema->rgTables[dwTable].wzName);
1937
1938 // And finally, open the table's standard interfaces
1939 hr = pDatabaseInternal->pIOpenRowset->OpenRowset(NULL, &tableID, NULL, IID_IRowset, _countof(rgdbpRowSetPropSet), rgdbpRowSetPropSet, reinterpret_cast<IUnknown **>(&pdsSchema->rgTables[dwTable].pIRowset));
1940 ExitOnFailure(hr, "Failed to open table %u named %ls after ensuring all indexes and constraints are created", dwTable, pdsSchema->rgTables[dwTable].wzName);
1941
1942 hr = pdsSchema->rgTables[dwTable].pIRowset->QueryInterface(IID_IRowsetChange, reinterpret_cast<void **>(&pdsSchema->rgTables[dwTable].pIRowsetChange));
1943 ExitOnFailure(hr, "Failed to get IRowsetChange object for table: %ls", pdsSchema->rgTables[dwTable].wzName);
1944 }
1945
1946LExit:
1947 return hr;
1948}
1949
1950static HRESULT SetColumnValue(
1951 __in const SCE_TABLE_SCHEMA *pTableSchema,
1952 __in DWORD dwColumnIndex,
1953 __in_bcount_opt(cbSize) const BYTE *pbData,
1954 __in SIZE_T cbSize,
1955 __inout DBBINDING *pBinding,
1956 __inout SIZE_T *pcbOffset,
1957 __inout BYTE **ppbBuffer
1958 )
1959{
1960 HRESULT hr = S_OK;
1961 size_t cbNewOffset = *pcbOffset;
1962
1963 pBinding->iOrdinal = dwColumnIndex + 1; // Skip bookmark column
1964 pBinding->dwMemOwner = DBMEMOWNER_CLIENTOWNED;
1965 pBinding->dwPart = DBPART_VALUE | DBPART_LENGTH | DBPART_STATUS;
1966
1967 pBinding->obLength = cbNewOffset;
1968
1969 hr = ::SizeTAdd(cbNewOffset, sizeof(DBBYTEOFFSET), &cbNewOffset);
1970 ExitOnFailure(hr, "Failed to add sizeof(DBBYTEOFFSET) to alloc size while setting column value");
1971
1972 pBinding->obValue = cbNewOffset;
1973
1974 hr = ::SizeTAdd(cbNewOffset, cbSize, &cbNewOffset);
1975 ExitOnFailure(hr, "Failed to add %u to alloc size while setting column value", cbSize);
1976
1977 pBinding->obStatus = cbNewOffset;
1978 pBinding->eParamIO = DBPARAMIO_INPUT;
1979
1980 hr = ::SizeTAdd(cbNewOffset, sizeof(DBSTATUS), &cbNewOffset);
1981 ExitOnFailure(hr, "Failed to add sizeof(DBSTATUS) to alloc size while setting column value");
1982
1983 pBinding->wType = pTableSchema->rgColumns[dwColumnIndex].dbtColumnType;
1984 pBinding->cbMaxLen = static_cast<DBBYTEOFFSET>(cbSize);
1985
1986 if (NULL == *ppbBuffer)
1987 {
1988 *ppbBuffer = reinterpret_cast<BYTE *>(MemAlloc(cbNewOffset, TRUE));
1989 ExitOnNull(*ppbBuffer, hr, E_OUTOFMEMORY, "Failed to allocate buffer while setting row string");
1990 }
1991 else
1992 {
1993 *ppbBuffer = reinterpret_cast<BYTE *>(MemReAlloc(*ppbBuffer, cbNewOffset, TRUE));
1994 ExitOnNull(*ppbBuffer, hr, E_OUTOFMEMORY, "Failed to reallocate buffer while setting row string");
1995 }
1996
1997 *(reinterpret_cast<DBBYTEOFFSET *>(*ppbBuffer + *pcbOffset)) = static_cast<DBBYTEOFFSET>(cbSize);
1998 *pcbOffset += sizeof(DBBYTEOFFSET);
1999 memcpy(*ppbBuffer + *pcbOffset, pbData, cbSize);
2000 *pcbOffset += cbSize;
2001 if (NULL == pbData)
2002 {
2003 *(reinterpret_cast<DBSTATUS *>(*ppbBuffer + *pcbOffset)) = DBSTATUS_S_ISNULL;
2004 }
2005 *pcbOffset += sizeof(DBSTATUS);
2006
2007LExit:
2008 return hr;
2009}
2010
2011static HRESULT GetColumnValue(
2012 __in SCE_ROW *pRow,
2013 __in DWORD dwColumnIndex,
2014 __out_opt BYTE **ppbData,
2015 __out SIZE_T *pcbSize
2016 )
2017{
2018 HRESULT hr = S_OK;
2019 const SCE_TABLE_SCHEMA *pTable = pRow->pTableSchema;
2020 IAccessor *pIAccessor = NULL;
2021 HACCESSOR hAccessorLength = DB_NULL_HACCESSOR;
2022 HACCESSOR hAccessorValue = DB_NULL_HACCESSOR;
2023 DWORD dwDataSize = 0;
2024 void *pvRawData = NULL;
2025 DBBINDING dbBinding = { };
2026 DBBINDSTATUS dbBindStatus = DBBINDSTATUS_OK;
2027
2028 dbBinding.iOrdinal = dwColumnIndex + 1;
2029 dbBinding.dwMemOwner = DBMEMOWNER_CLIENTOWNED;
2030 dbBinding.dwPart = DBPART_LENGTH;
2031 dbBinding.wType = pTable->rgColumns[dwColumnIndex].dbtColumnType;
2032
2033 pRow->pIRowset->QueryInterface(IID_IAccessor, reinterpret_cast<void **>(&pIAccessor));
2034 ExitOnFailure(hr, "Failed to get IAccessor interface");
2035
2036 hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, 1, &dbBinding, 0, &hAccessorLength, &dbBindStatus);
2037 ExitOnFailure(hr, "Failed to create accessor");
2038
2039 hr = pRow->pIRowset->GetData(pRow->hRow, hAccessorLength, reinterpret_cast<void *>(&dwDataSize));
2040 ExitOnFailure(hr, "Failed to get size of data");
2041
2042 // For variable-length columns, zero data returned means NULL
2043 if (0 == dwDataSize)
2044 {
2045 ExitFunction1(hr = E_NOTFOUND);
2046 }
2047
2048 if (NULL != ppbData)
2049 {
2050 dbBinding.dwPart = DBPART_VALUE;
2051 dbBinding.cbMaxLen = dwDataSize;
2052
2053 hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, 1, &dbBinding, 0, &hAccessorValue, &dbBindStatus);
2054 ExitOnFailure(hr, "Failed to create accessor");
2055
2056 if (DBBINDSTATUS_OK != dbBindStatus)
2057 {
2058 hr = E_INVALIDARG;
2059 ExitOnFailure(hr, "Bad bind status while creating accessor to get value");
2060 }
2061
2062 if (DBTYPE_WSTR == dbBinding.wType)
2063 {
2064 hr = StrAlloc(reinterpret_cast<LPWSTR *>(&pvRawData), dwDataSize / sizeof(WCHAR));
2065 ExitOnFailure(hr, "Failed to allocate space for string data while reading column %u", dwColumnIndex);
2066 }
2067 else
2068 {
2069 pvRawData = MemAlloc(dwDataSize, TRUE);
2070 ExitOnNull(pvRawData, hr, E_OUTOFMEMORY, "Failed to allocate space for data while reading column %u", dwColumnIndex);
2071 }
2072
2073 hr = pRow->pIRowset->GetData(pRow->hRow, hAccessorValue, pvRawData);
2074 ExitOnFailure(hr, "Failed to read data value");
2075
2076 ReleaseMem(*ppbData);
2077 *ppbData = reinterpret_cast<BYTE *>(pvRawData);
2078 pvRawData = NULL;
2079 }
2080
2081 *pcbSize = dwDataSize;
2082
2083LExit:
2084 ReleaseMem(pvRawData);
2085
2086 if (DB_NULL_HACCESSOR != hAccessorLength)
2087 {
2088 pIAccessor->ReleaseAccessor(hAccessorLength, NULL);
2089 }
2090 if (DB_NULL_HACCESSOR != hAccessorValue)
2091 {
2092 pIAccessor->ReleaseAccessor(hAccessorValue, NULL);
2093 }
2094 ReleaseObject(pIAccessor);
2095
2096 return hr;
2097}
2098
2099static HRESULT GetColumnValueFixed(
2100 __in SCE_ROW *pRow,
2101 __in DWORD dwColumnIndex,
2102 __in DWORD cbSize,
2103 __out BYTE *pbData
2104 )
2105{
2106 HRESULT hr = S_OK;
2107 const SCE_TABLE_SCHEMA *pTable = pRow->pTableSchema;
2108 IAccessor *pIAccessor = NULL;
2109 HACCESSOR hAccessorLength = DB_NULL_HACCESSOR;
2110 HACCESSOR hAccessorValue = DB_NULL_HACCESSOR;
2111 DWORD dwDataSize = 0;
2112 DBBINDSTATUS dbBindStatus = DBBINDSTATUS_OK;
2113 DBBINDING dbBinding = { };
2114
2115 dbBinding.iOrdinal = dwColumnIndex + 1;
2116 dbBinding.dwMemOwner = DBMEMOWNER_CLIENTOWNED;
2117 dbBinding.dwPart = DBPART_LENGTH;
2118 dbBinding.wType = pTable->rgColumns[dwColumnIndex].dbtColumnType;
2119
2120 pRow->pIRowset->QueryInterface(IID_IAccessor, reinterpret_cast<void **>(&pIAccessor));
2121 ExitOnFailure(hr, "Failed to get IAccessor interface");
2122
2123 hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, 1, &dbBinding, 0, &hAccessorLength, &dbBindStatus);
2124 ExitOnFailure(hr, "Failed to create accessor");
2125
2126 if (DBBINDSTATUS_OK != dbBindStatus)
2127 {
2128 hr = E_INVALIDARG;
2129 ExitOnFailure(hr, "Bad bind status while creating accessor to get length of value");
2130 }
2131
2132 hr = pRow->pIRowset->GetData(pRow->hRow, hAccessorLength, reinterpret_cast<void *>(&dwDataSize));
2133 ExitOnFailure(hr, "Failed to get size of data");
2134
2135 if (0 == dwDataSize)
2136 {
2137 ExitFunction1(hr = E_NOTFOUND);
2138 }
2139
2140 dbBinding.dwPart = DBPART_VALUE;
2141 dbBinding.cbMaxLen = cbSize;
2142
2143 hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, 1, &dbBinding, 0, &hAccessorValue, &dbBindStatus);
2144 ExitOnFailure(hr, "Failed to create accessor");
2145
2146 if (DBBINDSTATUS_OK != dbBindStatus)
2147 {
2148 hr = E_INVALIDARG;
2149 ExitOnFailure(hr, "Bad bind status while creating accessor to get value");
2150 }
2151
2152 hr = pRow->pIRowset->GetData(pRow->hRow, hAccessorValue, reinterpret_cast<void *>(pbData));
2153 ExitOnFailure(hr, "Failed to read data value");
2154
2155LExit:
2156 if (DB_NULL_HACCESSOR != hAccessorLength)
2157 {
2158 pIAccessor->ReleaseAccessor(hAccessorLength, NULL);
2159 }
2160 if (DB_NULL_HACCESSOR != hAccessorValue)
2161 {
2162 pIAccessor->ReleaseAccessor(hAccessorValue, NULL);
2163 }
2164 ReleaseObject(pIAccessor);
2165
2166 return hr;
2167}
2168
2169static HRESULT EnsureLocalColumnConstraints(
2170 __in ITableDefinition *pTableDefinition,
2171 __in DBID *pTableID,
2172 __in SCE_TABLE_SCHEMA *pTableSchema
2173 )
2174{
2175 HRESULT hr = S_OK;
2176 SCE_COLUMN_SCHEMA *pCurrentColumn = NULL;
2177 DBCONSTRAINTDESC dbcdConstraint = { };
2178 DBID dbConstraintID = { };
2179 DBID dbLocalColumnID = { };
2180 ITableDefinitionWithConstraints *pTableDefinitionWithConstraints = NULL;
2181
2182 hr = pTableDefinition->QueryInterface(IID_ITableDefinitionWithConstraints, reinterpret_cast<void **>(&pTableDefinitionWithConstraints));
2183 ExitOnFailure(hr, "Failed to query for ITableDefinitionWithConstraints interface in order to create column constraints");
2184
2185 for (DWORD i = 0; i < pTableSchema->cColumns; ++i)
2186 {
2187 pCurrentColumn = pTableSchema->rgColumns + i;
2188
2189 // Add a primary key constraint for this column, if one exists
2190 if (pCurrentColumn->fPrimaryKey)
2191 {
2192 // Setup DBID for new constraint
2193 dbConstraintID.eKind = DBKIND_NAME;
2194 dbConstraintID.uName.pwszName = const_cast<LPOLESTR>(L"PrimaryKey");
2195 dbcdConstraint.pConstraintID = &dbConstraintID;
2196
2197 dbcdConstraint.ConstraintType = DBCONSTRAINTTYPE_PRIMARYKEY;
2198
2199 dbLocalColumnID.eKind = DBKIND_NAME;
2200 dbLocalColumnID.uName.pwszName = const_cast<LPOLESTR>(pCurrentColumn->wzName);
2201 dbcdConstraint.cColumns = 1;
2202 dbcdConstraint.rgColumnList = &dbLocalColumnID;
2203
2204 dbcdConstraint.pReferencedTableID = NULL;
2205 dbcdConstraint.cForeignKeyColumns = 0;
2206 dbcdConstraint.rgForeignKeyColumnList = NULL;
2207 dbcdConstraint.pwszConstraintText = NULL;
2208 dbcdConstraint.UpdateRule = DBUPDELRULE_NOACTION;
2209 dbcdConstraint.DeleteRule = DBUPDELRULE_NOACTION;
2210 dbcdConstraint.MatchType = DBMATCHTYPE_NONE;
2211 dbcdConstraint.Deferrability = 0;
2212 dbcdConstraint.cReserved = 0;
2213 dbcdConstraint.rgReserved = NULL;
2214
2215 hr = pTableDefinitionWithConstraints->AddConstraint(pTableID, &dbcdConstraint);
2216 if (DB_E_DUPLICATECONSTRAINTID == hr)
2217 {
2218 hr = S_OK;
2219 }
2220 ExitOnFailure(hr, "Failed to add primary key constraint for column %ls, table %ls", pCurrentColumn->wzName, pTableSchema->wzName);
2221 }
2222 }
2223
2224LExit:
2225 ReleaseObject(pTableDefinitionWithConstraints);
2226
2227 return hr;
2228}
2229
2230static HRESULT EnsureForeignColumnConstraints(
2231 __in ITableDefinition *pTableDefinition,
2232 __in DBID *pTableID,
2233 __in SCE_TABLE_SCHEMA *pTableSchema,
2234 __in SCE_DATABASE_SCHEMA *pDatabaseSchema
2235 )
2236{
2237 HRESULT hr = S_OK;
2238 SCE_COLUMN_SCHEMA *pCurrentColumn = NULL;
2239 DBCONSTRAINTDESC dbcdConstraint = { };
2240 DBID dbConstraintID = { };
2241 DBID dbLocalColumnID = { };
2242 DBID dbForeignTableID = { };
2243 DBID dbForeignColumnID = { };
2244 ITableDefinitionWithConstraints *pTableDefinitionWithConstraints = NULL;
2245
2246 hr = pTableDefinition->QueryInterface(IID_ITableDefinitionWithConstraints, reinterpret_cast<void **>(&pTableDefinitionWithConstraints));
2247 ExitOnFailure(hr, "Failed to query for ITableDefinitionWithConstraints interface in order to create column constraints");
2248
2249 for (DWORD i = 0; i < pTableSchema->cColumns; ++i)
2250 {
2251 pCurrentColumn = pTableSchema->rgColumns + i;
2252
2253 // Add a foreign key constraint for this column, if one exists
2254 if (NULL != pCurrentColumn->wzRelationName)
2255 {
2256 // Setup DBID for new constraint
2257 dbConstraintID.eKind = DBKIND_NAME;
2258 dbConstraintID.uName.pwszName = const_cast<LPOLESTR>(pCurrentColumn->wzRelationName);
2259 dbcdConstraint.pConstraintID = &dbConstraintID;
2260
2261 dbcdConstraint.ConstraintType = DBCONSTRAINTTYPE_FOREIGNKEY;
2262
2263 dbForeignColumnID.eKind = DBKIND_NAME;
2264 dbForeignColumnID.uName.pwszName = const_cast<LPOLESTR>(pDatabaseSchema->rgTables[pCurrentColumn->dwForeignKeyTable].rgColumns[pCurrentColumn->dwForeignKeyColumn].wzName);
2265 dbcdConstraint.cColumns = 1;
2266 dbcdConstraint.rgColumnList = &dbForeignColumnID;
2267
2268 dbForeignTableID.eKind = DBKIND_NAME;
2269 dbForeignTableID.uName.pwszName = const_cast<LPOLESTR>(pDatabaseSchema->rgTables[pCurrentColumn->dwForeignKeyTable].wzName);
2270 dbcdConstraint.pReferencedTableID = &dbForeignTableID;
2271
2272 dbLocalColumnID.eKind = DBKIND_NAME;
2273 dbLocalColumnID.uName.pwszName = const_cast<LPOLESTR>(pCurrentColumn->wzName);
2274 dbcdConstraint.cForeignKeyColumns = 1;
2275 dbcdConstraint.rgForeignKeyColumnList = &dbLocalColumnID;
2276
2277 dbcdConstraint.pwszConstraintText = NULL;
2278 dbcdConstraint.UpdateRule = DBUPDELRULE_NOACTION;
2279 dbcdConstraint.DeleteRule = DBUPDELRULE_NOACTION;
2280 dbcdConstraint.MatchType = DBMATCHTYPE_FULL;
2281 dbcdConstraint.Deferrability = 0;
2282 dbcdConstraint.cReserved = 0;
2283 dbcdConstraint.rgReserved = NULL;
2284
2285 hr = pTableDefinitionWithConstraints->AddConstraint(pTableID, &dbcdConstraint);
2286 if (DB_E_DUPLICATECONSTRAINTID == hr)
2287 {
2288 hr = S_OK;
2289 }
2290 ExitOnFailure(hr, "Failed to add constraint named: %ls to table: %ls", pCurrentColumn->wzRelationName, pTableSchema->wzName);
2291 }
2292 }
2293
2294LExit:
2295 ReleaseObject(pTableDefinitionWithConstraints);
2296
2297 return hr;
2298}
2299
2300static HRESULT SetSessionProperties(
2301 __in ISessionProperties *pISessionProperties
2302 )
2303{
2304 HRESULT hr = S_OK;
2305 DBPROP rgdbpDataSourceProp[1];
2306 DBPROPSET rgdbpDataSourcePropSet[1];
2307
2308 rgdbpDataSourceProp[0].dwPropertyID = DBPROP_SSCE_TRANSACTION_COMMIT_MODE;
2309 rgdbpDataSourceProp[0].dwOptions = DBPROPOPTIONS_REQUIRED;
2310 rgdbpDataSourceProp[0].vValue.vt = VT_I4;
2311 rgdbpDataSourceProp[0].vValue.lVal = DBPROPVAL_SSCE_TCM_FLUSH;
2312
2313 rgdbpDataSourcePropSet[0].guidPropertySet = DBPROPSET_SSCE_SESSION;
2314 rgdbpDataSourcePropSet[0].rgProperties = rgdbpDataSourceProp;
2315 rgdbpDataSourcePropSet[0].cProperties = 1;
2316
2317 hr = pISessionProperties->SetProperties(1, rgdbpDataSourcePropSet);
2318 ExitOnFailure(hr, "Failed to set session properties");
2319
2320LExit:
2321 return hr;
2322}
2323
2324static HRESULT GetDatabaseSchemaInfo(
2325 __in SCE_DATABASE *pDatabase,
2326 __out LPWSTR *psczSchemaType,
2327 __out DWORD *pdwVersion
2328 )
2329{
2330 HRESULT hr = S_OK;
2331 LPWSTR sczSchemaType = NULL;
2332 DWORD dwVersionFound = 0;
2333 SCE_TABLE_SCHEMA schemaTable = SCE_INTERNAL_VERSION_TABLE_SCHEMA[0];
2334 SCE_DATABASE_SCHEMA fullSchema = { 1, &schemaTable};
2335 // Database object with our alternate schema
2336 SCE_DATABASE database = { pDatabase->sdbHandle, &fullSchema };
2337 SCE_ROW_HANDLE sceRow = NULL;
2338
2339 hr = OpenSchema(pDatabase, &fullSchema);
2340 ExitOnFailure(hr, "Failed to ensure internal version schema");
2341
2342 hr = SceGetFirstRow(&database, 0, &sceRow);
2343 ExitOnFailure(hr, "Failed to get first row in internal version schema table");
2344
2345 hr = SceGetColumnString(sceRow, 0, &sczSchemaType);
2346 ExitOnFailure(hr, "Failed to get internal schematype");
2347
2348 hr = SceGetColumnDword(sceRow, 1, &dwVersionFound);
2349 ExitOnFailure(hr, "Failed to get internal version");
2350
2351 *psczSchemaType = sczSchemaType;
2352 sczSchemaType = NULL;
2353 *pdwVersion = dwVersionFound;
2354
2355LExit:
2356 SceCloseTable(&schemaTable); // ignore failure
2357 ReleaseStr(sczSchemaType);
2358 ReleaseSceRow(sceRow);
2359
2360 return hr;
2361}
2362
2363static HRESULT SetDatabaseSchemaInfo(
2364 __in SCE_DATABASE *pDatabase,
2365 __in LPCWSTR wzSchemaType,
2366 __in DWORD dwVersion
2367 )
2368{
2369 HRESULT hr = S_OK;
2370 BOOL fInSceTransaction = FALSE;
2371 SCE_TABLE_SCHEMA schemaTable = SCE_INTERNAL_VERSION_TABLE_SCHEMA[0];
2372 SCE_DATABASE_SCHEMA fullSchema = { 1, &schemaTable};
2373 // Database object with our alternate schema
2374 SCE_DATABASE database = { pDatabase->sdbHandle, &fullSchema };
2375 SCE_ROW_HANDLE sceRow = NULL;
2376
2377 hr = EnsureSchema(pDatabase, &fullSchema);
2378 ExitOnFailure(hr, "Failed to ensure internal version schema");
2379
2380 hr = SceBeginTransaction(&database);
2381 ExitOnFailure(hr, "Failed to begin transaction");
2382 fInSceTransaction = TRUE;
2383
2384 hr = SceGetFirstRow(&database, 0, &sceRow);
2385 if (E_NOTFOUND == hr)
2386 {
2387 hr = ScePrepareInsert(&database, 0, &sceRow);
2388 ExitOnFailure(hr, "Failed to insert only row into internal version schema table");
2389 }
2390 else
2391 {
2392 ExitOnFailure(hr, "Failed to get first row in internal version schema table");
2393 }
2394
2395 hr = SceSetColumnString(sceRow, 0, wzSchemaType);
2396 ExitOnFailure(hr, "Failed to set internal schematype to: %ls", wzSchemaType);
2397
2398 hr = SceSetColumnDword(sceRow, 1, dwVersion);
2399 ExitOnFailure(hr, "Failed to set internal version to: %u", dwVersion);
2400
2401 hr = SceFinishUpdate(sceRow);
2402 ExitOnFailure(hr, "Failed to insert first row in internal version schema table");
2403
2404 hr = SceCommitTransaction(&database);
2405 ExitOnFailure(hr, "Failed to commit transaction");
2406 fInSceTransaction = FALSE;
2407
2408LExit:
2409 SceCloseTable(&schemaTable); // ignore failure
2410 ReleaseSceRow(sceRow);
2411 if (fInSceTransaction)
2412 {
2413 SceRollbackTransaction(&database);
2414 }
2415
2416 return hr;
2417}
2418
2419static void ReleaseDatabase(
2420 SCE_DATABASE *pDatabase
2421 )
2422{
2423 if (NULL != pDatabase)
2424 {
2425 if (NULL != pDatabase->pdsSchema)
2426 {
2427 for (DWORD i = 0; i < pDatabase->pdsSchema->cTables; ++i)
2428 {
2429 SceCloseTable(pDatabase->pdsSchema->rgTables + i);
2430 }
2431 }
2432
2433 if (NULL != pDatabase->sdbHandle)
2434 {
2435 ReleaseDatabaseInternal(reinterpret_cast<SCE_DATABASE_INTERNAL *>(pDatabase->sdbHandle));
2436 }
2437 }
2438 ReleaseMem(pDatabase);
2439}
2440
2441static void ReleaseDatabaseInternal(
2442 SCE_DATABASE_INTERNAL *pDatabaseInternal
2443 )
2444{
2445 HRESULT hr = S_OK;
2446
2447 if (NULL != pDatabaseInternal)
2448 {
2449 ReleaseObject(pDatabaseInternal->pITransactionLocal);
2450 ReleaseObject(pDatabaseInternal->pIOpenRowset);
2451 ReleaseObject(pDatabaseInternal->pISessionProperties);
2452 ReleaseObject(pDatabaseInternal->pIDBCreateSession);
2453 ReleaseObject(pDatabaseInternal->pIDBProperties);
2454
2455 if (NULL != pDatabaseInternal->pIDBInitialize)
2456 {
2457 hr = pDatabaseInternal->pIDBInitialize->Uninitialize();
2458 if (FAILED(hr))
2459 {
2460 TraceError(hr, "Failed to call uninitialize on IDBInitialize");
2461 }
2462 ReleaseObject(pDatabaseInternal->pIDBInitialize);
2463 }
2464
2465 if (NULL != pDatabaseInternal->hSqlCeDll)
2466 {
2467 if (!::FreeLibrary(pDatabaseInternal->hSqlCeDll))
2468 {
2469 hr = HRESULT_FROM_WIN32(::GetLastError());
2470 TraceError(hr, "Failed to free sql ce dll");
2471 }
2472 }
2473 }
2474
2475 // If there was a temp file we copied to (for read-only databases), delete it after close
2476 if (NULL != pDatabaseInternal->sczTempDbFile)
2477 {
2478 hr = FileEnsureDelete(pDatabaseInternal->sczTempDbFile);
2479 if (FAILED(hr))
2480 {
2481 TraceError(hr, "Failed to delete temporary database file (copied here because the database was opened as read-only): %ls", pDatabaseInternal->sczTempDbFile);
2482 }
2483 ReleaseStr(pDatabaseInternal->sczTempDbFile);
2484 }
2485
2486 ReleaseMem(pDatabaseInternal);
2487}
2488
2489#endif // end SKIP_SCE_COMPILE
diff --git a/src/libs/dutil/WixToolset.DUtil/shelutil.cpp b/src/libs/dutil/WixToolset.DUtil/shelutil.cpp
new file mode 100644
index 00000000..2eb9a52a
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/shelutil.cpp
@@ -0,0 +1,342 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define ShelExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_SHELUTIL, x, s, __VA_ARGS__)
8#define ShelExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_SHELUTIL, x, s, __VA_ARGS__)
9#define ShelExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_SHELUTIL, x, s, __VA_ARGS__)
10#define ShelExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_SHELUTIL, x, s, __VA_ARGS__)
11#define ShelExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_SHELUTIL, x, s, __VA_ARGS__)
12#define ShelExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_SHELUTIL, x, s, __VA_ARGS__)
13#define ShelExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_SHELUTIL, p, x, e, s, __VA_ARGS__)
14#define ShelExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_SHELUTIL, p, x, s, __VA_ARGS__)
15#define ShelExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_SHELUTIL, p, x, e, s, __VA_ARGS__)
16#define ShelExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_SHELUTIL, p, x, s, __VA_ARGS__)
17#define ShelExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_SHELUTIL, e, x, s, __VA_ARGS__)
18#define ShelExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_SHELUTIL, g, x, s, __VA_ARGS__)
19
20static PFN_SHELLEXECUTEEXW vpfnShellExecuteExW = ::ShellExecuteExW;
21
22static HRESULT GetDesktopShellView(
23 __in REFIID riid,
24 __out void **ppv
25 );
26static HRESULT GetShellDispatchFromView(
27 __in IShellView *psv,
28 __in REFIID riid,
29 __out void **ppv
30 );
31
32/********************************************************************
33 ShelFunctionOverride - overrides the shell functions. Typically used
34 for unit testing.
35
36*********************************************************************/
37extern "C" void DAPI ShelFunctionOverride(
38 __in_opt PFN_SHELLEXECUTEEXW pfnShellExecuteExW
39 )
40{
41 vpfnShellExecuteExW = pfnShellExecuteExW ? pfnShellExecuteExW : ::ShellExecuteExW;
42}
43
44
45/********************************************************************
46 ShelExec() - executes a target.
47
48*******************************************************************/
49extern "C" HRESULT DAPI ShelExec(
50 __in_z LPCWSTR wzTargetPath,
51 __in_z_opt LPCWSTR wzParameters,
52 __in_z_opt LPCWSTR wzVerb,
53 __in_z_opt LPCWSTR wzWorkingDirectory,
54 __in int nShowCmd,
55 __in_opt HWND hwndParent,
56 __out_opt HANDLE* phProcess
57 )
58{
59 HRESULT hr = S_OK;
60 SHELLEXECUTEINFOW shExecInfo = {};
61
62 shExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
63 shExecInfo.fMask = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS;
64 shExecInfo.hwnd = hwndParent;
65 shExecInfo.lpVerb = wzVerb;
66 shExecInfo.lpFile = wzTargetPath;
67 shExecInfo.lpParameters = wzParameters;
68 shExecInfo.lpDirectory = wzWorkingDirectory;
69 shExecInfo.nShow = nShowCmd;
70
71 if (!vpfnShellExecuteExW(&shExecInfo))
72 {
73 ShelExitWithLastError(hr, "ShellExecEx failed with return code: %d", Dutil_er);
74 }
75
76 if (phProcess)
77 {
78 *phProcess = shExecInfo.hProcess;
79 shExecInfo.hProcess = NULL;
80 }
81
82LExit:
83 ReleaseHandle(shExecInfo.hProcess);
84
85 return hr;
86}
87
88
89/********************************************************************
90 ShelExecUnelevated() - executes a target unelevated.
91
92*******************************************************************/
93extern "C" HRESULT DAPI ShelExecUnelevated(
94 __in_z LPCWSTR wzTargetPath,
95 __in_z_opt LPCWSTR wzParameters,
96 __in_z_opt LPCWSTR wzVerb,
97 __in_z_opt LPCWSTR wzWorkingDirectory,
98 __in int nShowCmd
99 )
100{
101 HRESULT hr = S_OK;
102 BSTR bstrTargetPath = NULL;
103 VARIANT vtParameters = { };
104 VARIANT vtVerb = { };
105 VARIANT vtWorkingDirectory = { };
106 VARIANT vtShow = { };
107 IShellView* psv = NULL;
108 IShellDispatch2* psd = NULL;
109
110 bstrTargetPath = ::SysAllocString(wzTargetPath);
111 ShelExitOnNull(bstrTargetPath, hr, E_OUTOFMEMORY, "Failed to allocate target path BSTR.");
112
113 if (wzParameters && *wzParameters)
114 {
115 vtParameters.vt = VT_BSTR;
116 vtParameters.bstrVal = ::SysAllocString(wzParameters);
117 ShelExitOnNull(bstrTargetPath, hr, E_OUTOFMEMORY, "Failed to allocate parameters BSTR.");
118 }
119
120 if (wzVerb && *wzVerb)
121 {
122 vtVerb.vt = VT_BSTR;
123 vtVerb.bstrVal = ::SysAllocString(wzVerb);
124 ShelExitOnNull(bstrTargetPath, hr, E_OUTOFMEMORY, "Failed to allocate verb BSTR.");
125 }
126
127 if (wzWorkingDirectory && *wzWorkingDirectory)
128 {
129 vtWorkingDirectory.vt = VT_BSTR;
130 vtWorkingDirectory.bstrVal = ::SysAllocString(wzWorkingDirectory);
131 ShelExitOnNull(bstrTargetPath, hr, E_OUTOFMEMORY, "Failed to allocate working directory BSTR.");
132 }
133
134 vtShow.vt = VT_INT;
135 vtShow.intVal = nShowCmd;
136
137 hr = GetDesktopShellView(IID_PPV_ARGS(&psv));
138 ShelExitOnFailure(hr, "Failed to get desktop shell view.");
139
140 hr = GetShellDispatchFromView(psv, IID_PPV_ARGS(&psd));
141 ShelExitOnFailure(hr, "Failed to get shell dispatch from view.");
142
143 hr = psd->ShellExecute(bstrTargetPath, vtParameters, vtWorkingDirectory, vtVerb, vtShow);
144 if (S_FALSE == hr)
145 {
146 hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
147 }
148 ShelExitOnRootFailure(hr, "Failed to launch unelevate executable: %ls", bstrTargetPath);
149
150LExit:
151 ReleaseObject(psd);
152 ReleaseObject(psv);
153 ReleaseBSTR(vtWorkingDirectory.bstrVal);
154 ReleaseBSTR(vtVerb.bstrVal);
155 ReleaseBSTR(vtParameters.bstrVal);
156 ReleaseBSTR(bstrTargetPath);
157
158 return hr;
159}
160
161
162/********************************************************************
163 ShelGetFolder() - gets a folder by CSIDL.
164
165*******************************************************************/
166extern "C" HRESULT DAPI ShelGetFolder(
167 __out_z LPWSTR* psczFolderPath,
168 __in int csidlFolder
169 )
170{
171 HRESULT hr = S_OK;
172 WCHAR wzPath[MAX_PATH];
173
174 hr = ::SHGetFolderPathW(NULL, csidlFolder | CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, wzPath);
175 ShelExitOnFailure(hr, "Failed to get folder path for CSIDL: %d", csidlFolder);
176
177 hr = StrAllocString(psczFolderPath, wzPath, 0);
178 ShelExitOnFailure(hr, "Failed to copy shell folder path: %ls", wzPath);
179
180 hr = PathBackslashTerminate(psczFolderPath);
181 ShelExitOnFailure(hr, "Failed to backslash terminate shell folder path: %ls", *psczFolderPath);
182
183LExit:
184 return hr;
185}
186
187
188/********************************************************************
189 ShelGetKnownFolder() - gets a folder by KNOWNFOLDERID.
190
191 Note: return E_NOTIMPL if called on pre-Vista operating systems.
192*******************************************************************/
193#ifndef REFKNOWNFOLDERID
194#define REFKNOWNFOLDERID REFGUID
195#endif
196
197#ifndef KF_FLAG_CREATE
198#define KF_FLAG_CREATE 0x00008000 // Make sure that the folder already exists or create it and apply security specified in folder definition
199#endif
200
201EXTERN_C typedef HRESULT (STDAPICALLTYPE *PFN_SHGetKnownFolderPath)(
202 REFKNOWNFOLDERID rfid,
203 DWORD dwFlags,
204 HANDLE hToken,
205 PWSTR *ppszPath
206 );
207
208extern "C" HRESULT DAPI ShelGetKnownFolder(
209 __out_z LPWSTR* psczFolderPath,
210 __in REFKNOWNFOLDERID rfidFolder
211 )
212{
213 HRESULT hr = S_OK;
214 HMODULE hShell32Dll = NULL;
215 PFN_SHGetKnownFolderPath pfn = NULL;
216 LPWSTR pwzPath = NULL;
217
218 hr = LoadSystemLibrary(L"shell32.dll", &hShell32Dll);
219 if (E_MODNOTFOUND == hr)
220 {
221 TraceError(hr, "Failed to load shell32.dll");
222 ExitFunction1(hr = E_NOTIMPL);
223 }
224 ShelExitOnFailure(hr, "Failed to load shell32.dll.");
225
226 pfn = reinterpret_cast<PFN_SHGetKnownFolderPath>(::GetProcAddress(hShell32Dll, "SHGetKnownFolderPath"));
227 ShelExitOnNull(pfn, hr, E_NOTIMPL, "Failed to find SHGetKnownFolderPath entry point.");
228
229 hr = pfn(rfidFolder, KF_FLAG_CREATE, NULL, &pwzPath);
230 ShelExitOnFailure(hr, "Failed to get known folder path.");
231
232 hr = StrAllocString(psczFolderPath, pwzPath, 0);
233 ShelExitOnFailure(hr, "Failed to copy shell folder path: %ls", pwzPath);
234
235 hr = PathBackslashTerminate(psczFolderPath);
236 ShelExitOnFailure(hr, "Failed to backslash terminate shell folder path: %ls", *psczFolderPath);
237
238LExit:
239 if (pwzPath)
240 {
241 ::CoTaskMemFree(pwzPath);
242 }
243
244 if (hShell32Dll)
245 {
246 ::FreeLibrary(hShell32Dll);
247 }
248
249 return hr;
250}
251
252
253// Internal functions.
254
255static HRESULT GetDesktopShellView(
256 __in REFIID riid,
257 __out void **ppv
258 )
259{
260 HRESULT hr = S_OK;
261 IShellWindows* psw = NULL;
262 HWND hwnd = NULL;
263 IDispatch* pdisp = NULL;
264 VARIANT vEmpty = {}; // VT_EMPTY
265 IShellBrowser* psb = NULL;
266 IShellFolder* psf = NULL;
267 IShellView* psv = NULL;
268
269 // use the shell view for the desktop using the shell windows automation to find the
270 // desktop web browser and then grabs its view
271 // returns IShellView, IFolderView and related interfaces
272 hr = ::CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&psw));
273 ShelExitOnFailure(hr, "Failed to get shell view.");
274
275 hr = psw->FindWindowSW(&vEmpty, &vEmpty, SWC_DESKTOP, (long*)&hwnd, SWFO_NEEDDISPATCH, &pdisp);
276 if (S_OK == hr)
277 {
278 hr = IUnknown_QueryService(pdisp, SID_STopLevelBrowser, IID_PPV_ARGS(&psb));
279 ShelExitOnFailure(hr, "Failed to get desktop window.");
280
281 hr = psb->QueryActiveShellView(&psv);
282 ShelExitOnFailure(hr, "Failed to get active shell view.");
283
284 hr = psv->QueryInterface(riid, ppv);
285 ShelExitOnFailure(hr, "Failed to query for the desktop shell view.");
286 }
287 else if (S_FALSE == hr)
288 {
289 //Windows XP
290 hr = SHGetDesktopFolder(&psf);
291 ShelExitOnFailure(hr, "Failed to get desktop folder.");
292
293 hr = psf->CreateViewObject(NULL, IID_IShellView, ppv);
294 ShelExitOnFailure(hr, "Failed to query for the desktop shell view.");
295 }
296 else
297 {
298 ShelExitOnFailure(hr, "Failed to get desktop window.");
299 }
300
301LExit:
302 ReleaseObject(psv);
303 ReleaseObject(psb);
304 ReleaseObject(psf);
305 ReleaseObject(pdisp);
306 ReleaseObject(psw);
307
308 return hr;
309}
310
311static HRESULT GetShellDispatchFromView(
312 __in IShellView *psv,
313 __in REFIID riid,
314 __out void **ppv
315 )
316{
317 HRESULT hr = S_OK;
318 IDispatch *pdispBackground = NULL;
319 IShellFolderViewDual *psfvd = NULL;
320 IDispatch *pdisp = NULL;
321
322 // From a shell view object, gets its automation interface and from that get the shell
323 // application object that implements IShellDispatch2 and related interfaces.
324 hr = psv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&pdispBackground));
325 ShelExitOnFailure(hr, "Failed to get the automation interface for shell.");
326
327 hr = pdispBackground->QueryInterface(IID_PPV_ARGS(&psfvd));
328 ShelExitOnFailure(hr, "Failed to get shell folder view dual.");
329
330 hr = psfvd->get_Application(&pdisp);
331 ShelExitOnFailure(hr, "Failed to application object.");
332
333 hr = pdisp->QueryInterface(riid, ppv);
334 ShelExitOnFailure(hr, "Failed to get IShellDispatch2.");
335
336LExit:
337 ReleaseObject(pdisp);
338 ReleaseObject(psfvd);
339 ReleaseObject(pdispBackground);
340
341 return hr;
342}
diff --git a/src/libs/dutil/WixToolset.DUtil/sqlutil.cpp b/src/libs/dutil/WixToolset.DUtil/sqlutil.cpp
new file mode 100644
index 00000000..782c7088
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/sqlutil.cpp
@@ -0,0 +1,1002 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5// okay, this may look a little weird, but sqlutil.h cannot be in the
6// pre-compiled header because we need to #define these things so the
7// correct GUID's get pulled into this object file
8#include <initguid.h>
9#define DBINITCONSTANTS
10#include "sqlutil.h"
11
12
13//Please note that only SQL native client 11 has TLS1.2 support
14#define _SQLNCLI_OLEDB_DEPRECATE_WARNING
15
16#if !defined(SQLNCLI_VER)
17#define SQLNCLI_VER 1100
18#endif
19
20#if SQLNCLI_VER >= 1100
21#if defined(_SQLNCLI_OLEDB_) || !defined(_SQLNCLI_ODBC_)
22#define SQLNCLI_CLSID CLSID_SQLNCLI11
23#endif // defined(_SQLNCLI_OLEDB_) || !defined(_SQLNCLI_ODBC_)
24extern const GUID OLEDBDECLSPEC _SQLNCLI_OLEDB_DEPRECATE_WARNING CLSID_SQLNCLI11 = { 0x397C2819L,0x8272,0x4532,{ 0xAD,0x3A,0xFB,0x5E,0x43,0xBE,0xAA,0x39 } };
25#endif // SQLNCLI_VER >= 1100
26
27// Exit macros
28#define SqlExitTrace(x, s, ...) ExitTraceSource(DUTIL_SOURCE_SQLUTIL, x, s, __VA_ARGS__)
29#define SqlExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_SQLUTIL, x, s, __VA_ARGS__)
30#define SqlExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_SQLUTIL, x, s, __VA_ARGS__)
31#define SqlExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_SQLUTIL, x, s, __VA_ARGS__)
32#define SqlExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_SQLUTIL, x, s, __VA_ARGS__)
33#define SqlExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_SQLUTIL, x, s, __VA_ARGS__)
34#define SqlExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_SQLUTIL, x, s, __VA_ARGS__)
35#define SqlExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_SQLUTIL, p, x, e, s, __VA_ARGS__)
36#define SqlExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_SQLUTIL, p, x, s, __VA_ARGS__)
37#define SqlExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_SQLUTIL, p, x, e, s, __VA_ARGS__)
38#define SqlExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_SQLUTIL, p, x, s, __VA_ARGS__)
39#define SqlExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_SQLUTIL, e, x, s, __VA_ARGS__)
40#define SqlExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_SQLUTIL, g, x, s, __VA_ARGS__)
41
42// private prototypes
43static HRESULT InitializeDatabaseConnection(
44 __in REFCLSID rclsid,
45 __in_z LPCSTR szFriendlyClsidName,
46 __in DBPROPSET rgdbpsetInit[],
47 __in_ecount(rgdbpsetInit) DWORD cdbpsetInit,
48 __out IDBCreateSession** ppidbSession
49 );
50HRESULT DumpErrorRecords();
51static HRESULT FileSpecToString(
52 __in const SQL_FILESPEC* psf,
53 __out LPWSTR* ppwz
54 );
55static HRESULT EscapeSqlIdentifier(
56 __in_z LPCWSTR wzDatabase,
57 __deref_out_z LPWSTR* ppwz
58 );
59
60
61/********************************************************************
62 SqlConnectDatabase - establishes a connection to a database
63
64 NOTE: wzInstance is optional
65 if fIntegratedAuth is set then wzUser and wzPassword are ignored
66********************************************************************/
67extern "C" HRESULT DAPI SqlConnectDatabase(
68 __in_z LPCWSTR wzServer,
69 __in_z LPCWSTR wzInstance,
70 __in_z LPCWSTR wzDatabase,
71 __in BOOL fIntegratedAuth,
72 __in_z LPCWSTR wzUser,
73 __in_z LPCWSTR wzPassword,
74 __out IDBCreateSession** ppidbSession
75 )
76{
77 Assert(wzServer && wzDatabase && *wzDatabase && ppidbSession);
78
79 HRESULT hr = S_OK;
80 LPWSTR pwzServerInstance = NULL;
81 DBPROP rgdbpInit[4] = { };
82 DBPROPSET rgdbpsetInit[1] = { };
83 ULONG cProperties = 0;
84
85 // if there is an instance
86 if (wzInstance && *wzInstance)
87 {
88 hr = StrAllocFormatted(&pwzServerInstance, L"%s\\%s", wzServer, wzInstance);
89 }
90 else
91 {
92 hr = StrAllocString(&pwzServerInstance, wzServer, 0);
93 }
94 SqlExitOnFailure(hr, "failed to allocate memory for the server instance");
95
96 // server[\instance]
97 rgdbpInit[cProperties].dwPropertyID = DBPROP_INIT_DATASOURCE;
98 rgdbpInit[cProperties].dwOptions = DBPROPOPTIONS_REQUIRED;
99 rgdbpInit[cProperties].colid = DB_NULLID;
100 ::VariantInit(&rgdbpInit[cProperties].vValue);
101 rgdbpInit[cProperties].vValue.vt = VT_BSTR;
102 rgdbpInit[cProperties].vValue.bstrVal = ::SysAllocString(pwzServerInstance);
103 ++cProperties;
104
105 // database
106 rgdbpInit[cProperties].dwPropertyID = DBPROP_INIT_CATALOG;
107 rgdbpInit[cProperties].dwOptions = DBPROPOPTIONS_REQUIRED;
108 rgdbpInit[cProperties].colid = DB_NULLID;
109 ::VariantInit(&rgdbpInit[cProperties].vValue);
110 rgdbpInit[cProperties].vValue.vt = VT_BSTR;
111 rgdbpInit[cProperties].vValue.bstrVal= ::SysAllocString(wzDatabase);
112 ++cProperties;
113
114 if (fIntegratedAuth)
115 {
116 // username
117 rgdbpInit[cProperties].dwPropertyID = DBPROP_AUTH_INTEGRATED;
118 rgdbpInit[cProperties].dwOptions = DBPROPOPTIONS_REQUIRED;
119 rgdbpInit[cProperties].colid = DB_NULLID;
120 ::VariantInit(&rgdbpInit[cProperties].vValue);
121 rgdbpInit[cProperties].vValue.vt = VT_BSTR;
122 rgdbpInit[cProperties].vValue.bstrVal = ::SysAllocString(L"SSPI"); // default windows authentication
123 ++cProperties;
124 }
125 else
126 {
127 // username
128 rgdbpInit[cProperties].dwPropertyID = DBPROP_AUTH_USERID;
129 rgdbpInit[cProperties].dwOptions = DBPROPOPTIONS_REQUIRED;
130 rgdbpInit[cProperties].colid = DB_NULLID;
131 ::VariantInit(&rgdbpInit[cProperties].vValue);
132 rgdbpInit[cProperties].vValue.vt = VT_BSTR;
133 rgdbpInit[cProperties].vValue.bstrVal = ::SysAllocString(wzUser);
134 ++cProperties;
135
136 // password
137 rgdbpInit[cProperties].dwPropertyID = DBPROP_AUTH_PASSWORD;
138 rgdbpInit[cProperties].dwOptions = DBPROPOPTIONS_REQUIRED;
139 rgdbpInit[cProperties].colid = DB_NULLID;
140 ::VariantInit(&rgdbpInit[cProperties].vValue);
141 rgdbpInit[cProperties].vValue.vt = VT_BSTR;
142 rgdbpInit[cProperties].vValue.bstrVal = ::SysAllocString(wzPassword);
143 ++cProperties;
144 }
145
146 // put the properties into a set
147 rgdbpsetInit[0].guidPropertySet = DBPROPSET_DBINIT;
148 rgdbpsetInit[0].rgProperties = rgdbpInit;
149 rgdbpsetInit[0].cProperties = cProperties;
150
151 // obtain access to the SQL Native Client provider
152 hr = InitializeDatabaseConnection(SQLNCLI_CLSID, "SQL Native Client", rgdbpsetInit, countof(rgdbpsetInit), ppidbSession);
153 if (FAILED(hr))
154 {
155 SqlExitTrace(hr, "Could not initialize SQL Native Client, falling back to SQL OLE DB...");
156
157 // try OLE DB but if that fails return original error failure
158 HRESULT hr2 = InitializeDatabaseConnection(CLSID_SQLOLEDB, "SQL OLE DB", rgdbpsetInit, countof(rgdbpsetInit), ppidbSession);
159 if (FAILED(hr2))
160 {
161 SqlExitTrace(hr2, "Could not initialize SQL OLE DB either, giving up.");
162 }
163 else
164 {
165 hr = S_OK;
166 }
167 }
168
169LExit:
170 for (; 0 < cProperties; cProperties--)
171 {
172 ::VariantClear(&rgdbpInit[cProperties - 1].vValue);
173 }
174
175 ReleaseStr(pwzServerInstance);
176
177 return hr;
178}
179
180
181/********************************************************************
182 SqlStartTransaction - Starts a new transaction that must be ended
183
184*********************************************************************/
185extern "C" HRESULT DAPI SqlStartTransaction(
186 __in IDBCreateSession* pidbSession,
187 __out IDBCreateCommand** ppidbCommand,
188 __out ITransaction** ppit
189 )
190{
191 Assert(pidbSession && ppit);
192
193 HRESULT hr = S_OK;
194
195 hr = pidbSession->CreateSession(NULL, IID_IDBCreateCommand, (IUnknown**)ppidbCommand);
196 SqlExitOnFailure(hr, "unable to create command from session");
197
198 hr = (*ppidbCommand)->QueryInterface(IID_ITransactionLocal, (LPVOID*)ppit);
199 SqlExitOnFailure(hr, "Unable to QueryInterface session to get ITransactionLocal");
200
201 hr = ((ITransactionLocal*)*ppit)->StartTransaction(ISOLATIONLEVEL_SERIALIZABLE, 0, NULL, NULL);
202
203LExit:
204
205 return hr;
206}
207
208/********************************************************************
209 SqlEndTransaction - Ends the transaction
210
211 NOTE: if fCommit, will commit the transaction, otherwise rolls back
212*********************************************************************/
213extern "C" HRESULT DAPI SqlEndTransaction(
214 __in ITransaction* pit,
215 __in BOOL fCommit
216 )
217{
218 Assert(pit);
219
220 HRESULT hr = S_OK;
221
222 if (fCommit)
223 {
224 hr = pit->Commit(FALSE, XACTTC_SYNC, 0);
225 SqlExitOnFailure(hr, "commit of transaction failed");
226 }
227 else
228 {
229 hr = pit->Abort(NULL, FALSE, FALSE);
230 SqlExitOnFailure(hr, "abort of transaction failed");
231 }
232
233LExit:
234
235 return hr;
236}
237
238
239/********************************************************************
240 SqlDatabaseExists - determines if database exists
241
242 NOTE: wzInstance is optional
243 if fIntegratedAuth is set then wzUser and wzPassword are ignored
244 returns S_OK if database exist
245 returns S_FALSE if database does not exist
246 returns E_* on error
247********************************************************************/
248extern "C" HRESULT DAPI SqlDatabaseExists(
249 __in_z LPCWSTR wzServer,
250 __in_z LPCWSTR wzInstance,
251 __in_z LPCWSTR wzDatabase,
252 __in BOOL fIntegratedAuth,
253 __in_z LPCWSTR wzUser,
254 __in_z LPCWSTR wzPassword,
255 __out_opt BSTR* pbstrErrorDescription
256 )
257{
258 Assert(wzServer && wzDatabase && *wzDatabase);
259
260 HRESULT hr = S_OK;
261 IDBCreateSession* pidbSession = NULL;
262
263 hr = SqlConnectDatabase(wzServer, wzInstance, L"master", fIntegratedAuth, wzUser, wzPassword, &pidbSession);
264 SqlExitOnFailure(hr, "failed to connect to 'master' database on server %ls", wzServer);
265
266 hr = SqlSessionDatabaseExists(pidbSession, wzDatabase, pbstrErrorDescription);
267
268LExit:
269 ReleaseObject(pidbSession);
270
271 return hr;
272}
273
274
275/********************************************************************
276 SqlSessionDatabaseExists - determines if database exists
277
278 NOTE: pidbSession must be connected to master database
279 returns S_OK if database exist
280 returns S_FALSE if database does not exist
281 returns E_* on error
282********************************************************************/
283extern "C" HRESULT DAPI SqlSessionDatabaseExists(
284 __in IDBCreateSession* pidbSession,
285 __in_z LPCWSTR wzDatabase,
286 __out_opt BSTR* pbstrErrorDescription
287 )
288{
289 Assert(pidbSession && wzDatabase && *wzDatabase);
290
291 HRESULT hr = S_OK;
292
293 LPWSTR pwzQuery = NULL;
294 IRowset* pirs = NULL;
295
296 DBCOUNTITEM cRows = 0;
297 HROW rghRows[1];
298 HROW* prow = rghRows;
299
300 //
301 // query to see if the database exists
302 //
303 hr = StrAllocFormatted(&pwzQuery, L"SELECT name FROM sysdatabases WHERE name='%s'", wzDatabase);
304 SqlExitOnFailure(hr, "failed to allocate query string to ensure database exists");
305
306 hr = SqlSessionExecuteQuery(pidbSession, pwzQuery, &pirs, NULL, pbstrErrorDescription);
307 SqlExitOnFailure(hr, "failed to get database list from 'master' database");
308 Assert(pirs);
309
310 //
311 // check to see if the database was returned
312 //
313 hr = pirs->GetNextRows(DB_NULL_HCHAPTER, 0, 1, &cRows, &prow);
314 SqlExitOnFailure(hr, "failed to get row with database name");
315
316 // succeeded but no database
317 if ((DB_S_ENDOFROWSET == hr) || (0 == cRows))
318 {
319 hr = S_FALSE;
320 }
321
322LExit:
323 ReleaseObject(pirs);
324 ReleaseStr(pwzQuery);
325
326 return hr;
327}
328
329
330/********************************************************************
331 SqlDatabaseEnsureExists - creates a database if it does not exist
332
333 NOTE: wzInstance is optional
334 if fIntegratedAuth is set then wzUser and wzPassword are ignored
335********************************************************************/
336extern "C" HRESULT DAPI SqlDatabaseEnsureExists(
337 __in_z LPCWSTR wzServer,
338 __in_z LPCWSTR wzInstance,
339 __in_z LPCWSTR wzDatabase,
340 __in BOOL fIntegratedAuth,
341 __in_z LPCWSTR wzUser,
342 __in_z LPCWSTR wzPassword,
343 __in_opt const SQL_FILESPEC* psfDatabase,
344 __in_opt const SQL_FILESPEC* psfLog,
345 __out_opt BSTR* pbstrErrorDescription
346 )
347{
348 Assert(wzServer && wzDatabase && *wzDatabase);
349
350 HRESULT hr = S_OK;
351 IDBCreateSession* pidbSession = NULL;
352
353 //
354 // connect to the master database to create the new database
355 //
356 hr = SqlConnectDatabase(wzServer, wzInstance, L"master", fIntegratedAuth, wzUser, wzPassword, &pidbSession);
357 SqlExitOnFailure(hr, "failed to connect to 'master' database on server %ls", wzServer);
358
359 hr = SqlSessionDatabaseEnsureExists(pidbSession, wzDatabase, psfDatabase, psfLog, pbstrErrorDescription);
360 SqlExitOnFailure(hr, "failed to create database: %ls", wzDatabase);
361
362 Assert(S_OK == hr);
363LExit:
364 ReleaseObject(pidbSession);
365
366 return hr;
367}
368
369
370/********************************************************************
371 SqlSessionDatabaseEnsureExists - creates a database if it does not exist
372
373 NOTE: pidbSession must be connected to the master database
374********************************************************************/
375extern "C" HRESULT DAPI SqlSessionDatabaseEnsureExists(
376 __in IDBCreateSession* pidbSession,
377 __in_z LPCWSTR wzDatabase,
378 __in_opt const SQL_FILESPEC* psfDatabase,
379 __in_opt const SQL_FILESPEC* psfLog,
380 __out_opt BSTR* pbstrErrorDescription
381 )
382{
383 Assert(pidbSession && wzDatabase && *wzDatabase);
384
385 HRESULT hr = S_OK;
386
387 hr = SqlSessionDatabaseExists(pidbSession, wzDatabase, pbstrErrorDescription);
388 SqlExitOnFailure(hr, "failed to determine if exists, database: %ls", wzDatabase);
389
390 if (S_FALSE == hr)
391 {
392 hr = SqlSessionCreateDatabase(pidbSession, wzDatabase, psfDatabase, psfLog, pbstrErrorDescription);
393 SqlExitOnFailure(hr, "failed to create database: %ls", wzDatabase);
394 }
395 // else database already exists, return S_FALSE
396
397 Assert(S_OK == hr);
398LExit:
399
400 return hr;
401}
402
403
404/********************************************************************
405 SqlCreateDatabase - creates a database on the server
406
407 NOTE: wzInstance is optional
408 if fIntegratedAuth is set then wzUser and wzPassword are ignored
409********************************************************************/
410extern "C" HRESULT DAPI SqlCreateDatabase(
411 __in_z LPCWSTR wzServer,
412 __in_z LPCWSTR wzInstance,
413 __in_z LPCWSTR wzDatabase,
414 __in BOOL fIntegratedAuth,
415 __in_z LPCWSTR wzUser,
416 __in_z LPCWSTR wzPassword,
417 __in_opt const SQL_FILESPEC* psfDatabase,
418 __in_opt const SQL_FILESPEC* psfLog,
419 __out_opt BSTR* pbstrErrorDescription
420 )
421{
422 Assert(wzServer && wzDatabase && *wzDatabase);
423
424 HRESULT hr = S_OK;
425 IDBCreateSession* pidbSession = NULL;
426
427 //
428 // connect to the master database to create the new database
429 //
430 hr = SqlConnectDatabase(wzServer, wzInstance, L"master", fIntegratedAuth, wzUser, wzPassword, &pidbSession);
431 SqlExitOnFailure(hr, "failed to connect to 'master' database on server %ls", wzServer);
432
433 hr = SqlSessionCreateDatabase(pidbSession, wzDatabase, psfDatabase, psfLog, pbstrErrorDescription);
434 SqlExitOnFailure(hr, "failed to create database: %ls", wzDatabase);
435
436 Assert(S_OK == hr);
437LExit:
438 ReleaseObject(pidbSession);
439
440 return hr;
441}
442
443
444/********************************************************************
445 SqlSessionCreateDatabase - creates a database on the server
446
447 NOTE: pidbSession must be connected to the master database
448********************************************************************/
449extern "C" HRESULT DAPI SqlSessionCreateDatabase(
450 __in IDBCreateSession* pidbSession,
451 __in_z LPCWSTR wzDatabase,
452 __in_opt const SQL_FILESPEC* psfDatabase,
453 __in_opt const SQL_FILESPEC* psfLog,
454 __out_opt BSTR* pbstrErrorDescription
455 )
456{
457 HRESULT hr = S_OK;
458 LPWSTR pwzDbFile = NULL;
459 LPWSTR pwzLogFile = NULL;
460 LPWSTR pwzQuery = NULL;
461 LPWSTR pwzDatabaseEscaped = NULL;
462
463 if (psfDatabase)
464 {
465 hr = FileSpecToString(psfDatabase, &pwzDbFile);
466 SqlExitOnFailure(hr, "failed to convert db filespec to string");
467 }
468
469 if (psfLog)
470 {
471 hr = FileSpecToString(psfLog, &pwzLogFile);
472 SqlExitOnFailure(hr, "failed to convert log filespec to string");
473 }
474
475 hr = EscapeSqlIdentifier(wzDatabase, &pwzDatabaseEscaped);
476 SqlExitOnFailure(hr, "failed to escape database string");
477
478 hr = StrAllocFormatted(&pwzQuery, L"CREATE DATABASE %s %s%s %s%s", pwzDatabaseEscaped, pwzDbFile ? L"ON " : L"", pwzDbFile ? pwzDbFile : L"", pwzLogFile ? L"LOG ON " : L"", pwzLogFile ? pwzLogFile : L"");
479 SqlExitOnFailure(hr, "failed to allocate query to create database: %ls", pwzDatabaseEscaped);
480
481 hr = SqlSessionExecuteQuery(pidbSession, pwzQuery, NULL, NULL, pbstrErrorDescription);
482 SqlExitOnFailure(hr, "failed to create database: %ls, Query: %ls", pwzDatabaseEscaped, pwzQuery);
483
484LExit:
485 ReleaseStr(pwzQuery);
486 ReleaseStr(pwzLogFile);
487 ReleaseStr(pwzDbFile);
488 ReleaseStr(pwzDatabaseEscaped);
489
490 return hr;
491}
492
493
494/********************************************************************
495 SqlDropDatabase - removes a database from a server if it exists
496
497 NOTE: wzInstance is optional
498 if fIntegratedAuth is set then wzUser and wzPassword are ignored
499********************************************************************/
500extern "C" HRESULT DAPI SqlDropDatabase(
501 __in_z LPCWSTR wzServer,
502 __in_z LPCWSTR wzInstance,
503 __in_z LPCWSTR wzDatabase,
504 __in BOOL fIntegratedAuth,
505 __in_z LPCWSTR wzUser,
506 __in_z LPCWSTR wzPassword,
507 __out_opt BSTR* pbstrErrorDescription
508 )
509{
510 Assert(wzServer && wzDatabase && *wzDatabase);
511
512 HRESULT hr = S_OK;
513 IDBCreateSession* pidbSession = NULL;
514
515 //
516 // connect to the master database to search for wzDatabase
517 //
518 hr = SqlConnectDatabase(wzServer, wzInstance, L"master", fIntegratedAuth, wzUser, wzPassword, &pidbSession);
519 SqlExitOnFailure(hr, "Failed to connect to 'master' database");
520
521 hr = SqlSessionDropDatabase(pidbSession, wzDatabase, pbstrErrorDescription);
522
523LExit:
524 ReleaseObject(pidbSession);
525
526 return hr;
527}
528
529
530/********************************************************************
531 SqlSessionDropDatabase - removes a database from a server if it exists
532
533 NOTE: pidbSession must be connected to the master database
534********************************************************************/
535extern "C" HRESULT DAPI SqlSessionDropDatabase(
536 __in IDBCreateSession* pidbSession,
537 __in_z LPCWSTR wzDatabase,
538 __out_opt BSTR* pbstrErrorDescription
539 )
540{
541 Assert(pidbSession && wzDatabase && *wzDatabase);
542
543 HRESULT hr = S_OK;
544 LPWSTR pwzQuery = NULL;
545 LPWSTR pwzDatabaseEscaped = NULL;
546
547 hr = SqlSessionDatabaseExists(pidbSession, wzDatabase, pbstrErrorDescription);
548 SqlExitOnFailure(hr, "failed to determine if exists, database: %ls", wzDatabase);
549
550 hr = EscapeSqlIdentifier(wzDatabase, &pwzDatabaseEscaped);
551 SqlExitOnFailure(hr, "failed to escape database string");
552
553 if (S_OK == hr)
554 {
555 hr = StrAllocFormatted(&pwzQuery, L"DROP DATABASE %s", pwzDatabaseEscaped);
556 SqlExitOnFailure(hr, "failed to allocate query to drop database: %ls", pwzDatabaseEscaped);
557
558 hr = SqlSessionExecuteQuery(pidbSession, pwzQuery, NULL, NULL, pbstrErrorDescription);
559 SqlExitOnFailure(hr, "Failed to drop database");
560 }
561
562LExit:
563 ReleaseStr(pwzQuery);
564 ReleaseStr(pwzDatabaseEscaped);
565
566 return hr;
567}
568
569
570/********************************************************************
571 SqlSessionExecuteQuery - executes a query and returns the results if desired
572
573 NOTE: ppirs and pcRoes and pbstrErrorDescription are optional
574********************************************************************/
575extern "C" HRESULT DAPI SqlSessionExecuteQuery(
576 __in IDBCreateSession* pidbSession,
577 __in __sql_command LPCWSTR wzSql,
578 __out_opt IRowset** ppirs,
579 __out_opt DBROWCOUNT* pcRows,
580 __out_opt BSTR* pbstrErrorDescription
581 )
582{
583 Assert(pidbSession);
584
585 HRESULT hr = S_OK;
586 IDBCreateCommand* pidbCommand = NULL;
587 ICommandText* picmdText = NULL;
588 ICommand* picmd = NULL;
589 DBROWCOUNT cRows = 0;
590
591 if (pcRows)
592 {
593 *pcRows = NULL;
594 }
595
596 //
597 // create the command
598 //
599 hr = pidbSession->CreateSession(NULL, IID_IDBCreateCommand, (IUnknown**)&pidbCommand);
600 SqlExitOnFailure(hr, "failed to create database session");
601 hr = pidbCommand->CreateCommand(NULL, IID_ICommand, (IUnknown**)&picmd);
602 SqlExitOnFailure(hr, "failed to create command to execute session");
603
604 //
605 // set the sql text into the command
606 //
607 hr = picmd->QueryInterface(IID_ICommandText, (LPVOID*)&picmdText);
608 SqlExitOnFailure(hr, "failed to get command text object for command");
609 hr = picmdText->SetCommandText(DBGUID_DEFAULT , wzSql);
610 SqlExitOnFailure(hr, "failed to set SQL string: %ls", wzSql);
611
612 //
613 // execute the command
614 //
615 hr = picmd->Execute(NULL, (ppirs) ? IID_IRowset : IID_NULL, NULL, &cRows, reinterpret_cast<IUnknown**>(ppirs));
616 SqlExitOnFailure(hr, "failed to execute SQL string: %ls", wzSql);
617
618 if (DB_S_ERRORSOCCURRED == hr)
619 {
620 hr = E_FAIL;
621 }
622
623 if (pcRows)
624 {
625 *pcRows = cRows;
626 }
627
628LExit:
629
630 if (FAILED(hr) && picmd && pbstrErrorDescription)
631 {
632 HRESULT hrGetErrors = SqlGetErrorInfo(picmd, IID_ICommandText, 0x409, NULL, pbstrErrorDescription); // TODO: use current locale instead of always American-English
633 if (FAILED(hrGetErrors))
634 {
635 ReleaseBSTR(*pbstrErrorDescription);
636 }
637 }
638
639 ReleaseObject(picmd);
640 ReleaseObject(picmdText);
641 ReleaseObject(pidbCommand);
642
643 return hr;
644}
645
646
647/********************************************************************
648 SqlCommandExecuteQuery - executes a SQL command and returns the results if desired
649
650 NOTE: ppirs and pcRoes are optional
651********************************************************************/
652extern "C" HRESULT DAPI SqlCommandExecuteQuery(
653 __in IDBCreateCommand* pidbCommand,
654 __in __sql_command LPCWSTR wzSql,
655 __out IRowset** ppirs,
656 __out DBROWCOUNT* pcRows
657 )
658{
659 Assert(pidbCommand);
660
661 HRESULT hr = S_OK;
662 ICommandText* picmdText = NULL;
663 ICommand* picmd = NULL;
664 DBROWCOUNT cRows = 0;
665
666 if (pcRows)
667 {
668 *pcRows = NULL;
669 }
670
671 //
672 // create the command
673 //
674 hr = pidbCommand->CreateCommand(NULL, IID_ICommand, (IUnknown**)&picmd);
675 SqlExitOnFailure(hr, "failed to create command to execute session");
676
677 //
678 // set the sql text into the command
679 //
680 hr = picmd->QueryInterface(IID_ICommandText, (LPVOID*)&picmdText);
681 SqlExitOnFailure(hr, "failed to get command text object for command");
682 hr = picmdText->SetCommandText(DBGUID_DEFAULT , wzSql);
683 SqlExitOnFailure(hr, "failed to set SQL string: %ls", wzSql);
684
685 //
686 // execute the command
687 //
688 hr = picmd->Execute(NULL, (ppirs) ? IID_IRowset : IID_NULL, NULL, &cRows, reinterpret_cast<IUnknown**>(ppirs));
689 SqlExitOnFailure(hr, "failed to execute SQL string: %ls", wzSql);
690
691 if (DB_S_ERRORSOCCURRED == hr)
692 {
693 hr = E_FAIL;
694 }
695
696 if (pcRows)
697 {
698 *pcRows = cRows;
699 }
700
701LExit:
702 ReleaseObject(picmd);
703 ReleaseObject(picmdText);
704
705 return hr;
706}
707
708
709/********************************************************************
710 SqlGetErrorInfo - gets error information from the last SQL function call
711
712 NOTE: pbstrErrorSource and pbstrErrorDescription are optional
713********************************************************************/
714extern "C" HRESULT DAPI SqlGetErrorInfo(
715 __in IUnknown* pObjectWithError,
716 __in REFIID IID_InterfaceWithError,
717 __in DWORD dwLocaleId,
718 __out_opt BSTR* pbstrErrorSource,
719 __out_opt BSTR* pbstrErrorDescription
720 )
721{
722 HRESULT hr = S_OK;
723 Assert(pObjectWithError);
724
725 // interfaces needed to extract error information out
726 ISupportErrorInfo* pISupportErrorInfo = NULL;
727 IErrorInfo* pIErrorInfoAll = NULL;
728 IErrorRecords* pIErrorRecords = NULL;
729 IErrorInfo* pIErrorInfoRecord = NULL;
730
731 // only ask for error information if the interface supports it.
732 hr = pObjectWithError->QueryInterface(IID_ISupportErrorInfo,(void**)&pISupportErrorInfo);
733 SqlExitOnFailure(hr, "No error information was found for object.");
734
735 hr = pISupportErrorInfo->InterfaceSupportsErrorInfo(IID_InterfaceWithError);
736 SqlExitOnFailure(hr, "InterfaceWithError is not supported for object with error");
737
738 // ignore the return of GetErrorInfo it can succeed and return a NULL pointer in pIErrorInfoAll anyway
739 hr = ::GetErrorInfo(0, &pIErrorInfoAll);
740 SqlExitOnFailure(hr, "failed to get error info");
741
742 if (S_OK == hr && pIErrorInfoAll)
743 {
744 // see if it's a valid OLE DB IErrorInfo interface that exposes a list of records
745 hr = pIErrorInfoAll->QueryInterface(IID_IErrorRecords, (void**)&pIErrorRecords);
746 if (SUCCEEDED(hr))
747 {
748 ULONG cErrors = 0;
749 pIErrorRecords->GetRecordCount(&cErrors);
750
751 // get the error information for each record
752 for (ULONG i = 0; i < cErrors; ++i)
753 {
754 hr = pIErrorRecords->GetErrorInfo(i, dwLocaleId, &pIErrorInfoRecord);
755 if (SUCCEEDED(hr))
756 {
757 if (pbstrErrorSource)
758 {
759 pIErrorInfoRecord->GetSource(pbstrErrorSource);
760 }
761 if (pbstrErrorDescription)
762 {
763 pIErrorInfoRecord->GetDescription(pbstrErrorDescription);
764 }
765
766 ReleaseNullObject(pIErrorInfoRecord);
767
768 break; // TODO: return more than one error in the future!
769 }
770 }
771
772 ReleaseNullObject(pIErrorRecords);
773 }
774 else // we have a simple error record
775 {
776 if (pbstrErrorSource)
777 {
778 pIErrorInfoAll->GetSource(pbstrErrorSource);
779 }
780 if (pbstrErrorDescription)
781 {
782 pIErrorInfoAll->GetDescription(pbstrErrorDescription);
783 }
784 }
785 }
786 else
787 {
788 hr = E_NOMOREITEMS;
789 }
790
791LExit:
792 ReleaseObject(pIErrorInfoRecord);
793 ReleaseObject(pIErrorRecords);
794 ReleaseObject(pIErrorInfoAll);
795 ReleaseObject(pISupportErrorInfo);
796
797 return hr;
798}
799
800
801//
802// private
803//
804
805static HRESULT InitializeDatabaseConnection(
806 __in REFCLSID rclsid,
807 __in_z LPCSTR szFriendlyClsidName,
808 __in DBPROPSET rgdbpsetInit[],
809 __in_ecount(rgdbpsetInit) DWORD cdbpsetInit,
810 __out IDBCreateSession** ppidbSession
811)
812{
813 Unused(szFriendlyClsidName); // only used in DEBUG builds
814
815 HRESULT hr = S_OK;
816 IDBInitialize* pidbInitialize = NULL;
817 IDBProperties* pidbProperties = NULL;
818
819 hr = ::CoCreateInstance(rclsid, NULL, CLSCTX_INPROC_SERVER, IID_IDBInitialize, (LPVOID*)&pidbInitialize);
820 SqlExitOnFailure(hr, "failed to initialize %s", szFriendlyClsidName);
821
822 // create and set the property set
823 hr = pidbInitialize->QueryInterface(IID_IDBProperties, (LPVOID*)&pidbProperties);
824 SqlExitOnFailure(hr, "failed to get IID_IDBProperties for %s", szFriendlyClsidName);
825
826 hr = pidbProperties->SetProperties(cdbpsetInit, rgdbpsetInit);
827 SqlExitOnFailure(hr, "failed to set properties for %s", szFriendlyClsidName);
828
829 // initialize connection to datasource
830 hr = pidbInitialize->Initialize();
831 if (FAILED(hr))
832 {
833 DumpErrorRecords();
834 }
835 SqlExitOnFailure(hr, "failed to initialize connection for %s", szFriendlyClsidName);
836
837 hr = pidbInitialize->QueryInterface(IID_IDBCreateSession, (LPVOID*)ppidbSession);
838 SqlExitOnFailure(hr, "failed to query for connection session for %s", szFriendlyClsidName);
839
840LExit:
841 ReleaseObject(pidbProperties);
842 ReleaseObject(pidbInitialize);
843
844 return hr;
845}
846
847HRESULT DumpErrorRecords()
848{
849 HRESULT hr = S_OK;
850 IErrorInfo* pIErrorInfo = NULL;
851 IErrorRecords* pIErrorRecords = NULL;
852 IErrorInfo* pIErrorInfoRecord = NULL;
853 BSTR bstrDescription = NULL;
854 ULONG i = 0;
855 ULONG cRecords = 0;
856 ERRORINFO ErrorInfo = { };
857
858 // Get IErrorInfo pointer from OLE.
859 hr = ::GetErrorInfo(0, &pIErrorInfo);
860 if (FAILED(hr))
861 {
862 ExitFunction();
863 }
864
865 // QI for IID_IErrorRecords.
866 hr = pIErrorInfo->QueryInterface(IID_IErrorRecords, (void**)&pIErrorRecords);
867 if (FAILED(hr))
868 {
869 ExitFunction();
870 }
871
872 // Get error record count.
873 hr = pIErrorRecords->GetRecordCount(&cRecords);
874 if (FAILED(hr))
875 {
876 ExitFunction();
877 }
878
879 // Loop through the error records.
880 for (i = 0; i < cRecords; i++)
881 {
882 // Get pIErrorInfo from pIErrorRecords.
883 hr = pIErrorRecords->GetErrorInfo(i, 1033, &pIErrorInfoRecord);
884
885 if (SUCCEEDED(hr))
886 {
887 // Get error description and source.
888 hr = pIErrorInfoRecord->GetDescription(&bstrDescription);
889
890 // Retrieve the ErrorInfo structures.
891 hr = pIErrorRecords->GetBasicErrorInfo(i, &ErrorInfo);
892
893 SqlExitTrace(ErrorInfo.hrError, "SQL error %lu/%lu: %ls", i + 1, cRecords, bstrDescription);
894
895 ReleaseNullObject(pIErrorInfoRecord);
896 ReleaseNullBSTR(bstrDescription);
897 }
898 }
899
900LExit:
901 ReleaseNullBSTR(bstrDescription);
902 ReleaseObject(pIErrorInfoRecord);
903 ReleaseObject(pIErrorRecords);
904 ReleaseObject(pIErrorInfo);
905
906 return hr;
907}
908
909/********************************************************************
910 FileSpecToString
911
912*********************************************************************/
913static HRESULT FileSpecToString(
914 __in const SQL_FILESPEC* psf,
915 __out LPWSTR* ppwz
916 )
917{
918 Assert(psf && ppwz);
919
920 HRESULT hr = S_OK;
921 LPWSTR pwz = NULL;
922
923 hr = StrAllocString(&pwz, L"(", 1024);
924 SqlExitOnFailure(hr, "failed to allocate string for database file info");
925
926 SqlExitOnNull(*psf->wzName, hr, E_INVALIDARG, "logical name not specified in database file info");
927 SqlExitOnNull(*psf->wzFilename, hr, E_INVALIDARG, "filename not specified in database file info");
928
929 hr = StrAllocFormatted(&pwz, L"%sNAME=%s", pwz, psf->wzName);
930 SqlExitOnFailure(hr, "failed to format database file info name: %ls", psf->wzName);
931
932 hr = StrAllocFormatted(&pwz, L"%s, FILENAME='%s'", pwz, psf->wzFilename);
933 SqlExitOnFailure(hr, "failed to format database file info filename: %ls", psf->wzFilename);
934
935 if (0 != psf->wzSize[0])
936 {
937 hr = StrAllocFormatted(&pwz, L"%s, SIZE=%s", pwz, psf->wzSize);
938 SqlExitOnFailure(hr, "failed to format database file info size: %ls", psf->wzSize);
939 }
940
941 if (0 != psf->wzMaxSize[0])
942 {
943 hr = StrAllocFormatted(&pwz, L"%s, MAXSIZE=%s", pwz, psf->wzMaxSize);
944 SqlExitOnFailure(hr, "failed to format database file info maxsize: %ls", psf->wzMaxSize);
945 }
946
947 if (0 != psf->wzGrow[0])
948 {
949 hr = StrAllocFormatted(&pwz, L"%s, FILEGROWTH=%s", pwz, psf->wzGrow);
950 SqlExitOnFailure(hr, "failed to format database file info growth: %ls", psf->wzGrow);
951 }
952
953 hr = StrAllocFormatted(&pwz, L"%s)", pwz);
954 SqlExitOnFailure(hr, "failed to allocate string for file spec");
955
956 *ppwz = pwz;
957 pwz = NULL; // null here so it doesn't get freed below
958
959LExit:
960 ReleaseStr(pwz);
961 return hr;
962}
963
964static HRESULT EscapeSqlIdentifier(
965 __in_z LPCWSTR wzIdentifier,
966 __deref_out_z LPWSTR* ppwz
967 )
968{
969 Assert(ppwz);
970
971 HRESULT hr = S_OK;
972 LPWSTR pwz = NULL;
973
974 if (wzIdentifier == NULL)
975 {
976 //Just ignore a NULL identifier and clear out the result
977 ReleaseNullStr(*ppwz);
978 ExitFunction();
979 }
980
981 int cchIdentifier = lstrlenW(wzIdentifier);
982
983 //If an empty string or already escaped just copy
984 if (cchIdentifier == 0 || (wzIdentifier[0] == '[' && wzIdentifier[cchIdentifier-1] == ']'))
985 {
986 hr = StrAllocString(&pwz, wzIdentifier, 0);
987 SqlExitOnFailure(hr, "failed to format database name: %ls", wzIdentifier);
988 }
989 else
990 {
991 //escape it
992 hr = StrAllocFormatted(&pwz, L"[%s]", wzIdentifier);
993 SqlExitOnFailure(hr, "failed to format escaped database name: %ls", wzIdentifier);
994 }
995
996 *ppwz = pwz;
997 pwz = NULL; // null here so it doesn't get freed below
998
999LExit:
1000 ReleaseStr(pwz);
1001 return hr;
1002}
diff --git a/src/libs/dutil/WixToolset.DUtil/srputil.cpp b/src/libs/dutil/WixToolset.DUtil/srputil.cpp
new file mode 100644
index 00000000..e44536cc
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/srputil.cpp
@@ -0,0 +1,252 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define SrpExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_SRPUTIL, x, s, __VA_ARGS__)
8#define SrpExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_SRPUTIL, x, s, __VA_ARGS__)
9#define SrpExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_SRPUTIL, x, s, __VA_ARGS__)
10#define SrpExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_SRPUTIL, x, s, __VA_ARGS__)
11#define SrpExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_SRPUTIL, x, s, __VA_ARGS__)
12#define SrpExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_SRPUTIL, x, s, __VA_ARGS__)
13#define SrpExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_SRPUTIL, p, x, e, s, __VA_ARGS__)
14#define SrpExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_SRPUTIL, p, x, s, __VA_ARGS__)
15#define SrpExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_SRPUTIL, p, x, e, s, __VA_ARGS__)
16#define SrpExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_SRPUTIL, p, x, s, __VA_ARGS__)
17#define SrpExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_SRPUTIL, e, x, s, __VA_ARGS__)
18#define SrpExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_SRPUTIL, g, x, s, __VA_ARGS__)
19
20
21typedef BOOL (WINAPI *PFN_SETRESTOREPTW)(
22 __in PRESTOREPOINTINFOW pRestorePtSpec,
23 __out PSTATEMGRSTATUS pSMgrStatus
24 );
25
26static PFN_SETRESTOREPTW vpfnSRSetRestorePointW = NULL;
27static HMODULE vhSrClientDll = NULL;
28
29
30static HRESULT InitializeComSecurity();
31
32
33DAPI_(HRESULT) SrpInitialize(
34 __in BOOL fInitializeComSecurity
35 )
36{
37 HRESULT hr = S_OK;
38
39 hr = LoadSystemLibrary(L"srclient.dll", &vhSrClientDll);
40 if (FAILED(hr))
41 {
42 ExitFunction1(hr = E_NOTIMPL);
43 }
44
45 vpfnSRSetRestorePointW = reinterpret_cast<PFN_SETRESTOREPTW>(::GetProcAddress(vhSrClientDll, "SRSetRestorePointW"));
46 SrpExitOnNullWithLastError(vpfnSRSetRestorePointW, hr, "Failed to find set restore point proc address.");
47
48 // If allowed, initialize COM security to enable NetworkService,
49 // LocalService and System to make callbacks to the process
50 // calling System Restore. This is required for any process
51 // that calls SRSetRestorePoint.
52 if (fInitializeComSecurity)
53 {
54 hr = InitializeComSecurity();
55 SrpExitOnFailure(hr, "Failed to initialize security for COM to talk to system restore.");
56 }
57
58LExit:
59 if (FAILED(hr) && vhSrClientDll)
60 {
61 SrpUninitialize();
62 }
63
64 return hr;
65}
66
67DAPI_(void) SrpUninitialize()
68{
69 if (vhSrClientDll)
70 {
71 ::FreeLibrary(vhSrClientDll);
72 vhSrClientDll = NULL;
73 vpfnSRSetRestorePointW = NULL;
74 }
75}
76
77DAPI_(HRESULT) SrpCreateRestorePoint(
78 __in_z LPCWSTR wzApplicationName,
79 __in SRP_ACTION action
80 )
81{
82 HRESULT hr = S_OK;
83 RESTOREPOINTINFOW restorePoint = { };
84 STATEMGRSTATUS status = { };
85
86 if (!vpfnSRSetRestorePointW)
87 {
88 ExitFunction1(hr = E_NOTIMPL);
89 }
90
91 restorePoint.dwEventType = BEGIN_SYSTEM_CHANGE;
92 restorePoint.dwRestorePtType = (SRP_ACTION_INSTALL == action) ? APPLICATION_INSTALL : (SRP_ACTION_UNINSTALL == action) ? APPLICATION_UNINSTALL : MODIFY_SETTINGS;
93 ::StringCbCopyW(restorePoint.szDescription, sizeof(restorePoint.szDescription), wzApplicationName);
94
95 if (!vpfnSRSetRestorePointW(&restorePoint, &status))
96 {
97 SrpExitOnWin32Error(status.nStatus, hr, "Failed to create system restore point.");
98 }
99
100LExit:
101 return hr;
102}
103
104
105// internal functions.
106
107static HRESULT InitializeComSecurity()
108{
109 HRESULT hr = S_OK;
110 DWORD er = ERROR_SUCCESS;
111 SECURITY_DESCRIPTOR sd = {0};
112 EXPLICIT_ACCESS ea[5] = {0};
113 ACL* pAcl = NULL;
114 ULONGLONG rgSidBA[(SECURITY_MAX_SID_SIZE+sizeof(ULONGLONG)-1)/sizeof(ULONGLONG)]={0};
115 ULONGLONG rgSidLS[(SECURITY_MAX_SID_SIZE+sizeof(ULONGLONG)-1)/sizeof(ULONGLONG)]={0};
116 ULONGLONG rgSidNS[(SECURITY_MAX_SID_SIZE+sizeof(ULONGLONG)-1)/sizeof(ULONGLONG)]={0};
117 ULONGLONG rgSidPS[(SECURITY_MAX_SID_SIZE+sizeof(ULONGLONG)-1)/sizeof(ULONGLONG)]={0};
118 ULONGLONG rgSidSY[(SECURITY_MAX_SID_SIZE+sizeof(ULONGLONG)-1)/sizeof(ULONGLONG)]={0};
119 DWORD cbSid = 0;
120
121 // Create the security descriptor explicitly as follows because
122 // CoInitializeSecurity() will not accept the relative security descriptors
123 // returned by ConvertStringSecurityDescriptorToSecurityDescriptor().
124 //
125 // The result is a security descriptor that is equivalent to the following
126 // security descriptor definition language (SDDL) string:
127 //
128 // O:BAG:BAD:(A;;0x1;;;LS)(A;;0x1;;;NS)(A;;0x1;;;PS)(A;;0x1;;;SY)(A;;0x1;;;BA)
129 //
130
131 // Initialize the security descriptor.
132 if (!::InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))
133 {
134 SrpExitWithLastError(hr, "Failed to initialize security descriptor for system restore.");
135 }
136
137 // Create an administrator group security identifier (SID).
138 cbSid = sizeof(rgSidBA);
139 if (!::CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, rgSidBA, &cbSid))
140 {
141 SrpExitWithLastError(hr, "Failed to create administrator SID for system restore.");
142 }
143
144 // Create a local service security identifier (SID).
145 cbSid = sizeof(rgSidLS);
146 if (!::CreateWellKnownSid(WinLocalServiceSid, NULL, rgSidLS, &cbSid))
147 {
148 SrpExitWithLastError(hr, "Failed to create local service SID for system restore.");
149 }
150
151 // Create a network service security identifier (SID).
152 cbSid = sizeof(rgSidNS);
153 if (!::CreateWellKnownSid(WinNetworkServiceSid, NULL, rgSidNS, &cbSid))
154 {
155 SrpExitWithLastError(hr, "Failed to create network service SID for system restore.");
156 }
157
158 // Create a personal account security identifier (SID).
159 cbSid = sizeof(rgSidPS);
160 if (!::CreateWellKnownSid(WinSelfSid, NULL, rgSidPS, &cbSid))
161 {
162 SrpExitWithLastError(hr, "Failed to create self SID for system restore.");
163 }
164
165 // Create a local service security identifier (SID).
166 cbSid = sizeof(rgSidSY);
167 if (!::CreateWellKnownSid(WinLocalSystemSid, NULL, rgSidSY, &cbSid))
168 {
169 SrpExitWithLastError(hr, "Failed to create local system SID for system restore.");
170 }
171
172 // Setup the access control entries (ACE) for COM. COM_RIGHTS_EXECUTE and
173 // COM_RIGHTS_EXECUTE_LOCAL are the minimum access rights required.
174 ea[0].grfAccessPermissions = COM_RIGHTS_EXECUTE | COM_RIGHTS_EXECUTE_LOCAL;
175 ea[0].grfAccessMode = SET_ACCESS;
176 ea[0].grfInheritance = NO_INHERITANCE;
177 ea[0].Trustee.pMultipleTrustee = NULL;
178 ea[0].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
179 ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
180 ea[0].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
181 ea[0].Trustee.ptstrName = (LPTSTR)rgSidBA;
182
183 ea[1].grfAccessPermissions = COM_RIGHTS_EXECUTE | COM_RIGHTS_EXECUTE_LOCAL;
184 ea[1].grfAccessMode = SET_ACCESS;
185 ea[1].grfInheritance = NO_INHERITANCE;
186 ea[1].Trustee.pMultipleTrustee = NULL;
187 ea[1].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
188 ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
189 ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
190 ea[1].Trustee.ptstrName = (LPTSTR)rgSidLS;
191
192 ea[2].grfAccessPermissions = COM_RIGHTS_EXECUTE | COM_RIGHTS_EXECUTE_LOCAL;
193 ea[2].grfAccessMode = SET_ACCESS;
194 ea[2].grfInheritance = NO_INHERITANCE;
195 ea[2].Trustee.pMultipleTrustee = NULL;
196 ea[2].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
197 ea[2].Trustee.TrusteeForm = TRUSTEE_IS_SID;
198 ea[2].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
199 ea[2].Trustee.ptstrName = (LPTSTR)rgSidNS;
200
201 ea[3].grfAccessPermissions = COM_RIGHTS_EXECUTE | COM_RIGHTS_EXECUTE_LOCAL;
202 ea[3].grfAccessMode = SET_ACCESS;
203 ea[3].grfInheritance = NO_INHERITANCE;
204 ea[3].Trustee.pMultipleTrustee = NULL;
205 ea[3].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
206 ea[3].Trustee.TrusteeForm = TRUSTEE_IS_SID;
207 ea[3].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
208 ea[3].Trustee.ptstrName = (LPTSTR)rgSidPS;
209
210 ea[4].grfAccessPermissions = COM_RIGHTS_EXECUTE | COM_RIGHTS_EXECUTE_LOCAL;
211 ea[4].grfAccessMode = SET_ACCESS;
212 ea[4].grfInheritance = NO_INHERITANCE;
213 ea[4].Trustee.pMultipleTrustee = NULL;
214 ea[4].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
215 ea[4].Trustee.TrusteeForm = TRUSTEE_IS_SID;
216 ea[4].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
217 ea[4].Trustee.ptstrName = (LPTSTR)rgSidSY;
218
219 // Create an access control list (ACL) using this ACE list.
220 er = ::SetEntriesInAcl(countof(ea), ea, NULL, &pAcl);
221 SrpExitOnWin32Error(er, hr, "Failed to create ACL for system restore.");
222
223 // Set the security descriptor owner to Administrators.
224 if (!::SetSecurityDescriptorOwner(&sd, rgSidBA, FALSE))
225 {
226 SrpExitWithLastError(hr, "Failed to set administrators owner for system restore.");
227 }
228
229 // Set the security descriptor group to Administrators.
230 if (!::SetSecurityDescriptorGroup(&sd, rgSidBA, FALSE))
231 {
232 SrpExitWithLastError(hr, "Failed to set administrators group access for system restore.");
233 }
234
235 // Set the discretionary access control list (DACL) to the ACL.
236 if (!::SetSecurityDescriptorDacl(&sd, TRUE, pAcl, FALSE))
237 {
238 SrpExitWithLastError(hr, "Failed to set DACL for system restore.");
239 }
240
241 // Note that an explicit security descriptor is being passed in.
242 hr= ::CoInitializeSecurity(&sd, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_IMP_LEVEL_IDENTIFY, NULL, EOAC_DISABLE_AAA | EOAC_NO_CUSTOM_MARSHAL, NULL);
243 SrpExitOnFailure(hr, "Failed to initialize COM security for system restore.");
244
245LExit:
246 if (pAcl)
247 {
248 ::LocalFree(pAcl);
249 }
250
251 return hr;
252}
diff --git a/src/libs/dutil/WixToolset.DUtil/strutil.cpp b/src/libs/dutil/WixToolset.DUtil/strutil.cpp
new file mode 100644
index 00000000..550d6169
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/strutil.cpp
@@ -0,0 +1,2824 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define StrExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_STRUTIL, x, s, __VA_ARGS__)
8#define StrExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_STRUTIL, x, s, __VA_ARGS__)
9#define StrExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_STRUTIL, x, s, __VA_ARGS__)
10#define StrExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_STRUTIL, x, s, __VA_ARGS__)
11#define StrExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_STRUTIL, x, s, __VA_ARGS__)
12#define StrExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_STRUTIL, x, s, __VA_ARGS__)
13#define StrExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_STRUTIL, p, x, e, s, __VA_ARGS__)
14#define StrExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_STRUTIL, p, x, s, __VA_ARGS__)
15#define StrExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_STRUTIL, p, x, e, s, __VA_ARGS__)
16#define StrExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_STRUTIL, p, x, s, __VA_ARGS__)
17#define StrExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_STRUTIL, e, x, s, __VA_ARGS__)
18#define StrExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_STRUTIL, g, x, s, __VA_ARGS__)
19
20#define ARRAY_GROWTH_SIZE 5
21
22// Forward declarations.
23static HRESULT AllocHelper(
24 __deref_out_ecount_part(cch, 0) LPWSTR* ppwz,
25 __in SIZE_T cch,
26 __in BOOL fZeroOnRealloc
27 );
28static HRESULT AllocStringHelper(
29 __deref_out_ecount_z(cchSource + 1) LPWSTR* ppwz,
30 __in_z LPCWSTR wzSource,
31 __in SIZE_T cchSource,
32 __in BOOL fZeroOnRealloc
33 );
34static HRESULT AllocConcatHelper(
35 __deref_out_z LPWSTR* ppwz,
36 __in_z LPCWSTR wzSource,
37 __in SIZE_T cchSource,
38 __in BOOL fZeroOnRealloc
39 );
40static HRESULT AllocFormattedArgsHelper(
41 __deref_out_z LPWSTR* ppwz,
42 __in BOOL fZeroOnRealloc,
43 __in __format_string LPCWSTR wzFormat,
44 __in va_list args
45 );
46static HRESULT StrAllocStringMapInvariant(
47 __deref_out_z LPWSTR* pscz,
48 __in_z LPCWSTR wzSource,
49 __in SIZE_T cchSource,
50 __in DWORD dwMapFlags
51 );
52
53/********************************************************************
54StrAlloc - allocates or reuses dynamic string memory
55
56NOTE: caller is responsible for freeing ppwz even if function fails
57********************************************************************/
58extern "C" HRESULT DAPI StrAlloc(
59 __deref_out_ecount_part(cch, 0) LPWSTR* ppwz,
60 __in SIZE_T cch
61 )
62{
63 return AllocHelper(ppwz, cch, FALSE);
64}
65
66/********************************************************************
67StrAllocSecure - allocates or reuses dynamic string memory
68If the memory needs to reallocated, calls SecureZeroMemory on the
69original block of memory after it is moved.
70
71NOTE: caller is responsible for freeing ppwz even if function fails
72********************************************************************/
73extern "C" HRESULT DAPI StrAllocSecure(
74 __deref_out_ecount_part(cch, 0) LPWSTR* ppwz,
75 __in SIZE_T cch
76 )
77{
78 return AllocHelper(ppwz, cch, TRUE);
79}
80
81/********************************************************************
82AllocHelper - allocates or reuses dynamic string memory
83If fZeroOnRealloc is true and the memory needs to reallocated,
84calls SecureZeroMemory on original block of memory after it is moved.
85
86NOTE: caller is responsible for freeing ppwz even if function fails
87********************************************************************/
88static HRESULT AllocHelper(
89 __deref_out_ecount_part(cch, 0) LPWSTR* ppwz,
90 __in SIZE_T cch,
91 __in BOOL fZeroOnRealloc
92 )
93{
94 Assert(ppwz && cch);
95
96 HRESULT hr = S_OK;
97 LPWSTR pwz = NULL;
98
99 if (cch >= MAXDWORD / sizeof(WCHAR))
100 {
101 hr = E_OUTOFMEMORY;
102 StrExitOnFailure(hr, "Not enough memory to allocate string of size: %u", cch);
103 }
104
105 if (*ppwz)
106 {
107 if (fZeroOnRealloc)
108 {
109 LPVOID pvNew = NULL;
110 hr = MemReAllocSecure(*ppwz, sizeof(WCHAR)* cch, FALSE, &pvNew);
111 StrExitOnFailure(hr, "Failed to reallocate string");
112 pwz = static_cast<LPWSTR>(pvNew);
113 }
114 else
115 {
116 pwz = static_cast<LPWSTR>(MemReAlloc(*ppwz, sizeof(WCHAR)* cch, FALSE));
117 }
118 }
119 else
120 {
121 pwz = static_cast<LPWSTR>(MemAlloc(sizeof(WCHAR) * cch, TRUE));
122 }
123
124 StrExitOnNull(pwz, hr, E_OUTOFMEMORY, "failed to allocate string, len: %u", cch);
125
126 *ppwz = pwz;
127LExit:
128 return hr;
129}
130
131
132/********************************************************************
133StrTrimCapacity - Frees any unnecessary memory associated with a string.
134 Purely used for optimization, generally only when a string
135 has been changing size, and will no longer grow.
136
137NOTE: caller is responsible for freeing ppwz even if function fails
138********************************************************************/
139HRESULT DAPI StrTrimCapacity(
140 __deref_out_z LPWSTR* ppwz
141 )
142{
143 Assert(ppwz);
144
145 HRESULT hr = S_OK;
146 SIZE_T cchLen = 0;
147
148 hr = ::StringCchLengthW(*ppwz, STRSAFE_MAX_CCH, reinterpret_cast<UINT_PTR*>(&cchLen));
149 StrExitOnRootFailure(hr, "Failed to calculate length of string");
150
151 ++cchLen; // Add 1 for null-terminator
152
153 hr = StrAlloc(ppwz, cchLen);
154 StrExitOnFailure(hr, "Failed to reallocate string");
155
156LExit:
157 return hr;
158}
159
160
161/********************************************************************
162StrTrimWhitespace - allocates or reuses dynamic string memory and copies
163 in an existing string, excluding whitespace
164
165NOTE: caller is responsible for freeing ppwz even if function fails
166********************************************************************/
167HRESULT DAPI StrTrimWhitespace(
168 __deref_out_z LPWSTR* ppwz,
169 __in_z LPCWSTR wzSource
170 )
171{
172 HRESULT hr = S_OK;
173 size_t i = 0;
174 LPWSTR sczResult = NULL;
175
176 // Ignore beginning whitespace
177 while (L' ' == *wzSource || L'\t' == *wzSource)
178 {
179 wzSource++;
180 }
181
182 hr = ::StringCchLengthW(wzSource, STRSAFE_MAX_CCH, &i);
183 StrExitOnRootFailure(hr, "Failed to get length of string");
184
185 // Overwrite ending whitespace with null characters
186 if (0 < i)
187 {
188 // start from the last non-null-terminator character in the array
189 for (i = i - 1; i > 0; --i)
190 {
191 if (L' ' != wzSource[i] && L'\t' != wzSource[i])
192 {
193 break;
194 }
195 }
196
197 ++i;
198 }
199
200 hr = StrAllocString(&sczResult, wzSource, i);
201 StrExitOnFailure(hr, "Failed to copy result string");
202
203 // Output result
204 *ppwz = sczResult;
205 sczResult = NULL;
206
207LExit:
208 ReleaseStr(sczResult);
209
210 return hr;
211}
212
213
214/********************************************************************
215StrAnsiAlloc - allocates or reuses dynamic ANSI string memory
216
217NOTE: caller is responsible for freeing ppsz even if function fails
218********************************************************************/
219extern "C" HRESULT DAPI StrAnsiAlloc(
220 __deref_out_ecount_part(cch, 0) LPSTR* ppsz,
221 __in SIZE_T cch
222 )
223{
224 Assert(ppsz && cch);
225
226 HRESULT hr = S_OK;
227 LPSTR psz = NULL;
228
229 if (cch >= MAXDWORD / sizeof(WCHAR))
230 {
231 hr = E_OUTOFMEMORY;
232 StrExitOnFailure(hr, "Not enough memory to allocate string of size: %u", cch);
233 }
234
235 if (*ppsz)
236 {
237 psz = static_cast<LPSTR>(MemReAlloc(*ppsz, sizeof(CHAR) * cch, FALSE));
238 }
239 else
240 {
241 psz = static_cast<LPSTR>(MemAlloc(sizeof(CHAR) * cch, TRUE));
242 }
243
244 StrExitOnNull(psz, hr, E_OUTOFMEMORY, "failed to allocate string, len: %u", cch);
245
246 *ppsz = psz;
247LExit:
248 return hr;
249}
250
251
252/********************************************************************
253StrAnsiTrimCapacity - Frees any unnecessary memory associated with a string.
254 Purely used for optimization, generally only when a string
255 has been changing size, and will no longer grow.
256
257NOTE: caller is responsible for freeing ppwz even if function fails
258********************************************************************/
259HRESULT DAPI StrAnsiTrimCapacity(
260 __deref_out_z LPSTR* ppz
261 )
262{
263 Assert(ppz);
264
265 HRESULT hr = S_OK;
266 SIZE_T cchLen = 0;
267
268#pragma prefast(push)
269#pragma prefast(disable:25068)
270 hr = ::StringCchLengthA(*ppz, STRSAFE_MAX_CCH, reinterpret_cast<UINT_PTR*>(&cchLen));
271#pragma prefast(pop)
272 StrExitOnFailure(hr, "Failed to calculate length of string");
273
274 ++cchLen; // Add 1 for null-terminator
275
276 hr = StrAnsiAlloc(ppz, cchLen);
277 StrExitOnFailure(hr, "Failed to reallocate string");
278
279LExit:
280 return hr;
281}
282
283
284/********************************************************************
285StrAnsiTrimWhitespace - allocates or reuses dynamic string memory and copies
286 in an existing string, excluding whitespace
287
288NOTE: caller is responsible for freeing ppz even if function fails
289********************************************************************/
290HRESULT DAPI StrAnsiTrimWhitespace(
291 __deref_out_z LPSTR* ppz,
292 __in_z LPCSTR szSource
293 )
294{
295 HRESULT hr = S_OK;
296 size_t i = 0;
297 LPSTR sczResult = NULL;
298
299 // Ignore beginning whitespace
300 while (' ' == *szSource || '\t' == *szSource)
301 {
302 szSource++;
303 }
304
305 hr = ::StringCchLengthA(szSource, STRSAFE_MAX_CCH, &i);
306 StrExitOnRootFailure(hr, "Failed to get length of string");
307
308 // Overwrite ending whitespace with null characters
309 if (0 < i)
310 {
311 // start from the last non-null-terminator character in the array
312 for (i = i - 1; i > 0; --i)
313 {
314 if (L' ' != szSource[i] && L'\t' != szSource[i])
315 {
316 break;
317 }
318 }
319
320 ++i;
321 }
322
323 hr = StrAnsiAllocStringAnsi(&sczResult, szSource, i);
324 StrExitOnFailure(hr, "Failed to copy result string");
325
326 // Output result
327 *ppz = sczResult;
328 sczResult = NULL;
329
330LExit:
331 ReleaseStr(sczResult);
332
333 return hr;
334}
335
336/********************************************************************
337StrAllocString - allocates or reuses dynamic string memory and copies in an existing string
338
339NOTE: caller is responsible for freeing ppwz even if function fails
340NOTE: cchSource does not have to equal the length of wzSource
341NOTE: if cchSource == 0, length of wzSource is used instead
342********************************************************************/
343extern "C" HRESULT DAPI StrAllocString(
344 __deref_out_ecount_z(cchSource+1) LPWSTR* ppwz,
345 __in_z LPCWSTR wzSource,
346 __in SIZE_T cchSource
347 )
348{
349 return AllocStringHelper(ppwz, wzSource, cchSource, FALSE);
350}
351
352/********************************************************************
353StrAllocStringSecure - allocates or reuses dynamic string memory and
354copies in an existing string. If the memory needs to reallocated,
355calls SecureZeroMemory on original block of memory after it is moved.
356
357NOTE: caller is responsible for freeing ppwz even if function fails
358NOTE: cchSource does not have to equal the length of wzSource
359NOTE: if cchSource == 0, length of wzSource is used instead
360********************************************************************/
361extern "C" HRESULT DAPI StrAllocStringSecure(
362 __deref_out_ecount_z(cchSource + 1) LPWSTR* ppwz,
363 __in_z LPCWSTR wzSource,
364 __in SIZE_T cchSource
365 )
366{
367 return AllocStringHelper(ppwz, wzSource, cchSource, TRUE);
368}
369
370/********************************************************************
371AllocStringHelper - allocates or reuses dynamic string memory and copies in an existing string
372If fZeroOnRealloc is true and the memory needs to reallocated,
373calls SecureZeroMemory on original block of memory after it is moved.
374
375NOTE: caller is responsible for freeing ppwz even if function fails
376NOTE: cchSource does not have to equal the length of wzSource
377NOTE: if cchSource == 0, length of wzSource is used instead
378********************************************************************/
379static HRESULT AllocStringHelper(
380 __deref_out_ecount_z(cchSource + 1) LPWSTR* ppwz,
381 __in_z LPCWSTR wzSource,
382 __in SIZE_T cchSource,
383 __in BOOL fZeroOnRealloc
384 )
385{
386 Assert(ppwz && wzSource); // && *wzSource);
387
388 HRESULT hr = S_OK;
389 SIZE_T cch = 0;
390
391 if (*ppwz)
392 {
393 cch = MemSize(*ppwz); // get the count in bytes so we can check if it failed (returns -1)
394 if (-1 == cch)
395 {
396 hr = E_INVALIDARG;
397 StrExitOnFailure(hr, "failed to get size of destination string");
398 }
399 cch /= sizeof(WCHAR); //convert the count in bytes to count in characters
400 }
401
402 if (0 == cchSource && wzSource)
403 {
404 hr = ::StringCchLengthW(wzSource, STRSAFE_MAX_CCH, reinterpret_cast<size_t*>(&cchSource));
405 StrExitOnRootFailure(hr, "failed to get length of source string");
406 }
407
408 SIZE_T cchNeeded;
409 hr = ::ULongPtrAdd(cchSource, 1, &cchNeeded); // add one for the null terminator
410 StrExitOnRootFailure(hr, "source string is too long");
411
412 if (cch < cchNeeded)
413 {
414 cch = cchNeeded;
415 hr = AllocHelper(ppwz, cch, fZeroOnRealloc);
416 StrExitOnFailure(hr, "failed to allocate string from string.");
417 }
418
419 // copy everything (the NULL terminator will be included)
420 hr = ::StringCchCopyNExW(*ppwz, cch, wzSource, cchSource, NULL, NULL, STRSAFE_FILL_BEHIND_NULL);
421
422LExit:
423 return hr;
424}
425
426
427/********************************************************************
428StrAnsiAllocString - allocates or reuses dynamic ANSI string memory and copies in an existing string
429
430NOTE: caller is responsible for freeing ppsz even if function fails
431NOTE: cchSource must equal the length of wzSource (not including the NULL terminator)
432NOTE: if cchSource == 0, length of wzSource is used instead
433********************************************************************/
434extern "C" HRESULT DAPI StrAnsiAllocString(
435 __deref_out_ecount_z(cchSource+1) LPSTR* ppsz,
436 __in_z LPCWSTR wzSource,
437 __in SIZE_T cchSource,
438 __in UINT uiCodepage
439 )
440{
441 Assert(ppsz && wzSource);
442
443 HRESULT hr = S_OK;
444 LPSTR psz = NULL;
445 SIZE_T cch = 0;
446 SIZE_T cchDest = cchSource; // at least enough
447
448 if (*ppsz)
449 {
450 cch = MemSize(*ppsz); // get the count in bytes so we can check if it failed (returns -1)
451 if (-1 == cch)
452 {
453 hr = E_INVALIDARG;
454 StrExitOnFailure(hr, "failed to get size of destination string");
455 }
456 cch /= sizeof(CHAR); //convert the count in bytes to count in characters
457 }
458
459 if (0 == cchSource)
460 {
461 cchDest = ::WideCharToMultiByte(uiCodepage, 0, wzSource, -1, NULL, 0, NULL, NULL);
462 if (0 == cchDest)
463 {
464 StrExitWithLastError(hr, "failed to get required size for conversion to ANSI: %ls", wzSource);
465 }
466
467 --cchDest; // subtract one because WideChageToMultiByte includes space for the NULL terminator that we track below
468 }
469 else if (L'\0' == wzSource[cchSource - 1]) // if the source already had a null terminator, don't count that in the character count because we track it below
470 {
471 cchDest = cchSource - 1;
472 }
473
474 if (cch < cchDest + 1)
475 {
476 cch = cchDest + 1; // add one for the NULL terminator
477 if (cch >= MAXDWORD / sizeof(WCHAR))
478 {
479 hr = E_OUTOFMEMORY;
480 StrExitOnFailure(hr, "Not enough memory to allocate string of size: %u", cch);
481 }
482
483 if (*ppsz)
484 {
485 psz = static_cast<LPSTR>(MemReAlloc(*ppsz, sizeof(CHAR) * cch, TRUE));
486 }
487 else
488 {
489 psz = static_cast<LPSTR>(MemAlloc(sizeof(CHAR) * cch, TRUE));
490 }
491 StrExitOnNull(psz, hr, E_OUTOFMEMORY, "failed to allocate string, len: %u", cch);
492
493 *ppsz = psz;
494 }
495
496 if (0 == ::WideCharToMultiByte(uiCodepage, 0, wzSource, 0 == cchSource ? -1 : (int)cchSource, *ppsz, (int)cch, NULL, NULL))
497 {
498 StrExitWithLastError(hr, "failed to convert to ansi: %ls", wzSource);
499 }
500 (*ppsz)[cchDest] = L'\0';
501
502LExit:
503 return hr;
504}
505
506
507/********************************************************************
508StrAllocStringAnsi - allocates or reuses dynamic string memory and copies in an existing ANSI string
509
510NOTE: caller is responsible for freeing ppwz even if function fails
511NOTE: cchSource must equal the length of wzSource (not including the NULL terminator)
512NOTE: if cchSource == 0, length of wzSource is used instead
513********************************************************************/
514extern "C" HRESULT DAPI StrAllocStringAnsi(
515 __deref_out_ecount_z(cchSource+1) LPWSTR* ppwz,
516 __in_z LPCSTR szSource,
517 __in SIZE_T cchSource,
518 __in UINT uiCodepage
519 )
520{
521 Assert(ppwz && szSource);
522
523 HRESULT hr = S_OK;
524 LPWSTR pwz = NULL;
525 SIZE_T cch = 0;
526 SIZE_T cchDest = cchSource; // at least enough
527
528 if (*ppwz)
529 {
530 cch = MemSize(*ppwz); // get the count in bytes so we can check if it failed (returns -1)
531 if (-1 == cch)
532 {
533 hr = E_INVALIDARG;
534 StrExitOnFailure(hr, "failed to get size of destination string");
535 }
536 cch /= sizeof(WCHAR); //convert the count in bytes to count in characters
537 }
538
539 if (0 == cchSource)
540 {
541 cchDest = ::MultiByteToWideChar(uiCodepage, 0, szSource, -1, NULL, 0);
542 if (0 == cchDest)
543 {
544 StrExitWithLastError(hr, "failed to get required size for conversion to unicode: %s", szSource);
545 }
546
547 --cchDest; //subtract one because MultiByteToWideChar includes space for the NULL terminator that we track below
548 }
549 else if (L'\0' == szSource[cchSource - 1]) // if the source already had a null terminator, don't count that in the character count because we track it below
550 {
551 cchDest = cchSource - 1;
552 }
553
554 if (cch < cchDest + 1)
555 {
556 cch = cchDest + 1;
557 if (cch >= MAXDWORD / sizeof(WCHAR))
558 {
559 hr = E_OUTOFMEMORY;
560 StrExitOnFailure(hr, "Not enough memory to allocate string of size: %u", cch);
561 }
562
563 if (*ppwz)
564 {
565 pwz = static_cast<LPWSTR>(MemReAlloc(*ppwz, sizeof(WCHAR) * cch, TRUE));
566 }
567 else
568 {
569 pwz = static_cast<LPWSTR>(MemAlloc(sizeof(WCHAR) * cch, TRUE));
570 }
571
572 StrExitOnNull(pwz, hr, E_OUTOFMEMORY, "failed to allocate string, len: %u", cch);
573
574 *ppwz = pwz;
575 }
576
577 if (0 == ::MultiByteToWideChar(uiCodepage, 0, szSource, 0 == cchSource ? -1 : (int)cchSource, *ppwz, (int)cch))
578 {
579 StrExitWithLastError(hr, "failed to convert to unicode: %s", szSource);
580 }
581 (*ppwz)[cchDest] = L'\0';
582
583LExit:
584 return hr;
585}
586
587
588/********************************************************************
589StrAnsiAllocStringAnsi - allocates or reuses dynamic string memory and copies in an existing string
590
591NOTE: caller is responsible for freeing ppsz even if function fails
592NOTE: cchSource does not have to equal the length of wzSource
593NOTE: if cchSource == 0, length of wzSource is used instead
594********************************************************************/
595HRESULT DAPI StrAnsiAllocStringAnsi(
596 __deref_out_ecount_z(cchSource+1) LPSTR* ppsz,
597 __in_z LPCSTR szSource,
598 __in SIZE_T cchSource
599 )
600{
601 Assert(ppsz && szSource); // && *szSource);
602
603 HRESULT hr = S_OK;
604 SIZE_T cch = 0;
605
606 if (*ppsz)
607 {
608 cch = MemSize(*ppsz); // get the count in bytes so we can check if it failed (returns -1)
609 if (-1 == cch)
610 {
611 hr = E_INVALIDARG;
612 StrExitOnRootFailure(hr, "failed to get size of destination string");
613 }
614 cch /= sizeof(CHAR); //convert the count in bytes to count in characters
615 }
616
617 if (0 == cchSource && szSource)
618 {
619 hr = ::StringCchLengthA(szSource, STRSAFE_MAX_CCH, reinterpret_cast<size_t*>(&cchSource));
620 StrExitOnRootFailure(hr, "failed to get length of source string");
621 }
622
623 SIZE_T cchNeeded;
624 hr = ::ULongPtrAdd(cchSource, 1, &cchNeeded); // add one for the null terminator
625 StrExitOnRootFailure(hr, "source string is too long");
626
627 if (cch < cchNeeded)
628 {
629 cch = cchNeeded;
630 hr = StrAnsiAlloc(ppsz, cch);
631 StrExitOnFailure(hr, "failed to allocate string from string.");
632 }
633
634 // copy everything (the NULL terminator will be included)
635#pragma prefast(push)
636#pragma prefast(disable:25068)
637 hr = ::StringCchCopyNExA(*ppsz, cch, szSource, cchSource, NULL, NULL, STRSAFE_FILL_BEHIND_NULL);
638#pragma prefast(pop)
639
640LExit:
641 return hr;
642}
643
644
645/********************************************************************
646StrAllocPrefix - allocates or reuses dynamic string memory and
647 prefixes a string
648
649NOTE: caller is responsible for freeing ppwz even if function fails
650NOTE: cchPrefix does not have to equal the length of wzPrefix
651NOTE: if cchPrefix == 0, length of wzPrefix is used instead
652********************************************************************/
653extern "C" HRESULT DAPI StrAllocPrefix(
654 __deref_out_z LPWSTR* ppwz,
655 __in_z LPCWSTR wzPrefix,
656 __in SIZE_T cchPrefix
657 )
658{
659 Assert(ppwz && wzPrefix);
660
661 HRESULT hr = S_OK;
662 SIZE_T cch = 0;
663 SIZE_T cchLen = 0;
664
665 if (*ppwz)
666 {
667 cch = MemSize(*ppwz); // get the count in bytes so we can check if it failed (returns -1)
668 if (-1 == cch)
669 {
670 hr = E_INVALIDARG;
671 StrExitOnFailure(hr, "failed to get size of destination string");
672 }
673 cch /= sizeof(WCHAR); //convert the count in bytes to count in characters
674
675 hr = ::StringCchLengthW(*ppwz, STRSAFE_MAX_CCH, reinterpret_cast<UINT_PTR*>(&cchLen));
676 StrExitOnFailure(hr, "Failed to calculate length of string");
677 }
678
679 Assert(cchLen <= cch);
680
681 if (0 == cchPrefix)
682 {
683 hr = ::StringCchLengthW(wzPrefix, STRSAFE_MAX_CCH, reinterpret_cast<UINT_PTR*>(&cchPrefix));
684 StrExitOnFailure(hr, "Failed to calculate length of string");
685 }
686
687 if (cch - cchLen < cchPrefix + 1)
688 {
689 cch = cchPrefix + cchLen + 1;
690 hr = StrAlloc(ppwz, cch);
691 StrExitOnFailure(hr, "failed to allocate string from string: %ls", wzPrefix);
692 }
693
694 if (*ppwz)
695 {
696 SIZE_T cb = cch * sizeof(WCHAR);
697 SIZE_T cbPrefix = cchPrefix * sizeof(WCHAR);
698
699 memmove(*ppwz + cchPrefix, *ppwz, cb - cbPrefix);
700 memcpy(*ppwz, wzPrefix, cbPrefix);
701 }
702 else
703 {
704 hr = E_UNEXPECTED;
705 StrExitOnFailure(hr, "for some reason our buffer is still null");
706 }
707
708LExit:
709 return hr;
710}
711
712
713/********************************************************************
714StrAllocConcat - allocates or reuses dynamic string memory and adds an existing string
715
716NOTE: caller is responsible for freeing ppwz even if function fails
717NOTE: cchSource does not have to equal the length of wzSource
718NOTE: if cchSource == 0, length of wzSource is used instead
719********************************************************************/
720extern "C" HRESULT DAPI StrAllocConcat(
721 __deref_out_z LPWSTR* ppwz,
722 __in_z LPCWSTR wzSource,
723 __in SIZE_T cchSource
724 )
725{
726 return AllocConcatHelper(ppwz, wzSource, cchSource, FALSE);
727}
728
729
730/********************************************************************
731StrAllocConcatSecure - allocates or reuses dynamic string memory and
732adds an existing string. If the memory needs to reallocated, calls
733SecureZeroMemory on the original block of memory after it is moved.
734
735NOTE: caller is responsible for freeing ppwz even if function fails
736NOTE: cchSource does not have to equal the length of wzSource
737NOTE: if cchSource == 0, length of wzSource is used instead
738********************************************************************/
739extern "C" HRESULT DAPI StrAllocConcatSecure(
740 __deref_out_z LPWSTR* ppwz,
741 __in_z LPCWSTR wzSource,
742 __in SIZE_T cchSource
743 )
744{
745 return AllocConcatHelper(ppwz, wzSource, cchSource, TRUE);
746}
747
748
749/********************************************************************
750AllocConcatHelper - allocates or reuses dynamic string memory and adds an existing string
751If fZeroOnRealloc is true and the memory needs to reallocated,
752calls SecureZeroMemory on original block of memory after it is moved.
753
754NOTE: caller is responsible for freeing ppwz even if function fails
755NOTE: cchSource does not have to equal the length of wzSource
756NOTE: if cchSource == 0, length of wzSource is used instead
757********************************************************************/
758static HRESULT AllocConcatHelper(
759 __deref_out_z LPWSTR* ppwz,
760 __in_z LPCWSTR wzSource,
761 __in SIZE_T cchSource,
762 __in BOOL fZeroOnRealloc
763 )
764{
765 Assert(ppwz && wzSource); // && *wzSource);
766
767 HRESULT hr = S_OK;
768 SIZE_T cch = 0;
769 SIZE_T cchLen = 0;
770
771 if (*ppwz)
772 {
773 cch = MemSize(*ppwz); // get the count in bytes so we can check if it failed (returns -1)
774 if (-1 == cch)
775 {
776 hr = E_INVALIDARG;
777 StrExitOnFailure(hr, "failed to get size of destination string");
778 }
779 cch /= sizeof(WCHAR); //convert the count in bytes to count in characters
780
781 hr = ::StringCchLengthW(*ppwz, STRSAFE_MAX_CCH, reinterpret_cast<UINT_PTR*>(&cchLen));
782 StrExitOnFailure(hr, "Failed to calculate length of string");
783 }
784
785 Assert(cchLen <= cch);
786
787 if (0 == cchSource)
788 {
789 hr = ::StringCchLengthW(wzSource, STRSAFE_MAX_CCH, reinterpret_cast<UINT_PTR*>(&cchSource));
790 StrExitOnFailure(hr, "Failed to calculate length of string");
791 }
792
793 if (cch - cchLen < cchSource + 1)
794 {
795 cch = (cchSource + cchLen + 1) * 2;
796 hr = AllocHelper(ppwz, cch, fZeroOnRealloc);
797 StrExitOnFailure(hr, "failed to allocate string from string: %ls", wzSource);
798 }
799
800 if (*ppwz)
801 {
802 hr = ::StringCchCatNExW(*ppwz, cch, wzSource, cchSource, NULL, NULL, STRSAFE_FILL_BEHIND_NULL);
803 }
804 else
805 {
806 hr = E_UNEXPECTED;
807 StrExitOnFailure(hr, "for some reason our buffer is still null");
808 }
809
810LExit:
811 return hr;
812}
813
814
815/********************************************************************
816StrAnsiAllocConcat - allocates or reuses dynamic string memory and adds an existing string
817
818NOTE: caller is responsible for freeing ppz even if function fails
819NOTE: cchSource does not have to equal the length of pzSource
820NOTE: if cchSource == 0, length of pzSource is used instead
821********************************************************************/
822extern "C" HRESULT DAPI StrAnsiAllocConcat(
823 __deref_out_z LPSTR* ppz,
824 __in_z LPCSTR pzSource,
825 __in SIZE_T cchSource
826 )
827{
828 Assert(ppz && pzSource); // && *pzSource);
829
830 HRESULT hr = S_OK;
831 SIZE_T cch = 0;
832 SIZE_T cchLen = 0;
833
834 if (*ppz)
835 {
836 cch = MemSize(*ppz); // get the count in bytes so we can check if it failed (returns -1)
837 if (-1 == cch)
838 {
839 hr = E_INVALIDARG;
840 StrExitOnFailure(hr, "failed to get size of destination string");
841 }
842 cch /= sizeof(CHAR); // convert the count in bytes to count in characters
843
844#pragma prefast(push)
845#pragma prefast(disable:25068)
846 hr = ::StringCchLengthA(*ppz, STRSAFE_MAX_CCH, reinterpret_cast<UINT_PTR*>(&cchLen));
847#pragma prefast(pop)
848 StrExitOnFailure(hr, "Failed to calculate length of string");
849 }
850
851 Assert(cchLen <= cch);
852
853 if (0 == cchSource)
854 {
855#pragma prefast(push)
856#pragma prefast(disable:25068)
857 hr = ::StringCchLengthA(pzSource, STRSAFE_MAX_CCH, reinterpret_cast<UINT_PTR*>(&cchSource));
858#pragma prefast(pop)
859 StrExitOnFailure(hr, "Failed to calculate length of string");
860 }
861
862 if (cch - cchLen < cchSource + 1)
863 {
864 cch = (cchSource + cchLen + 1) * 2;
865 hr = StrAnsiAlloc(ppz, cch);
866 StrExitOnFailure(hr, "failed to allocate string from string: %hs", pzSource);
867 }
868
869 if (*ppz)
870 {
871#pragma prefast(push)
872#pragma prefast(disable:25068)
873 hr = ::StringCchCatNExA(*ppz, cch, pzSource, cchSource, NULL, NULL, STRSAFE_FILL_BEHIND_NULL);
874#pragma prefast(pop)
875 }
876 else
877 {
878 hr = E_UNEXPECTED;
879 StrExitOnFailure(hr, "for some reason our buffer is still null");
880 }
881
882LExit:
883 return hr;
884}
885
886
887/********************************************************************
888StrAllocFormatted - allocates or reuses dynamic string memory and formats it
889
890NOTE: caller is responsible for freeing ppwz even if function fails
891********************************************************************/
892extern "C" HRESULT __cdecl StrAllocFormatted(
893 __deref_out_z LPWSTR* ppwz,
894 __in __format_string LPCWSTR wzFormat,
895 ...
896 )
897{
898 Assert(ppwz && wzFormat && *wzFormat);
899
900 HRESULT hr = S_OK;
901 va_list args;
902
903 va_start(args, wzFormat);
904 hr = StrAllocFormattedArgs(ppwz, wzFormat, args);
905 va_end(args);
906
907 return hr;
908}
909
910
911/********************************************************************
912StrAllocConcatFormatted - allocates or reuses dynamic string memory
913and adds a formatted string
914
915NOTE: caller is responsible for freeing ppwz even if function fails
916********************************************************************/
917extern "C" HRESULT __cdecl StrAllocConcatFormatted(
918 __deref_out_z LPWSTR* ppwz,
919 __in __format_string LPCWSTR wzFormat,
920 ...
921 )
922{
923 Assert(ppwz && wzFormat && *wzFormat);
924
925 HRESULT hr = S_OK;
926 LPWSTR sczFormatted = NULL;
927 va_list args;
928
929 va_start(args, wzFormat);
930 hr = StrAllocFormattedArgs(&sczFormatted, wzFormat, args);
931 va_end(args);
932 StrExitOnFailure(hr, "Failed to allocate formatted string");
933
934 hr = StrAllocConcat(ppwz, sczFormatted, 0);
935
936LExit:
937 ReleaseStr(sczFormatted);
938
939 return hr;
940}
941
942
943/********************************************************************
944StrAllocConcatFormattedSecure - allocates or reuses dynamic string
945memory and adds a formatted string. If the memory needs to be
946reallocated, calls SecureZeroMemory on original block of memory after
947it is moved.
948
949NOTE: caller is responsible for freeing ppwz even if function fails
950********************************************************************/
951extern "C" HRESULT __cdecl StrAllocConcatFormattedSecure(
952 __deref_out_z LPWSTR* ppwz,
953 __in __format_string LPCWSTR wzFormat,
954 ...
955 )
956{
957 Assert(ppwz && wzFormat && *wzFormat);
958
959 HRESULT hr = S_OK;
960 LPWSTR sczFormatted = NULL;
961 va_list args;
962
963 va_start(args, wzFormat);
964 hr = StrAllocFormattedArgsSecure(&sczFormatted, wzFormat, args);
965 va_end(args);
966 StrExitOnFailure(hr, "Failed to allocate formatted string");
967
968 hr = StrAllocConcatSecure(ppwz, sczFormatted, 0);
969
970LExit:
971 ReleaseStr(sczFormatted);
972
973 return hr;
974}
975
976
977/********************************************************************
978StrAllocFormattedSecure - allocates or reuses dynamic string memory
979and formats it. If the memory needs to be reallocated,
980calls SecureZeroMemory on original block of memory after it is moved.
981
982NOTE: caller is responsible for freeing ppwz even if function fails
983********************************************************************/
984extern "C" HRESULT __cdecl StrAllocFormattedSecure(
985 __deref_out_z LPWSTR* ppwz,
986 __in __format_string LPCWSTR wzFormat,
987 ...
988 )
989{
990 Assert(ppwz && wzFormat && *wzFormat);
991
992 HRESULT hr = S_OK;
993 va_list args;
994
995 va_start(args, wzFormat);
996 hr = StrAllocFormattedArgsSecure(ppwz, wzFormat, args);
997 va_end(args);
998
999 return hr;
1000}
1001
1002
1003/********************************************************************
1004StrAnsiAllocFormatted - allocates or reuses dynamic ANSI string memory and formats it
1005
1006NOTE: caller is responsible for freeing ppsz even if function fails
1007********************************************************************/
1008extern "C" HRESULT DAPI StrAnsiAllocFormatted(
1009 __deref_out_z LPSTR* ppsz,
1010 __in __format_string LPCSTR szFormat,
1011 ...
1012 )
1013{
1014 Assert(ppsz && szFormat && *szFormat);
1015
1016 HRESULT hr = S_OK;
1017 va_list args;
1018
1019 va_start(args, szFormat);
1020 hr = StrAnsiAllocFormattedArgs(ppsz, szFormat, args);
1021 va_end(args);
1022
1023 return hr;
1024}
1025
1026
1027/********************************************************************
1028StrAllocFormattedArgs - allocates or reuses dynamic string memory
1029and formats it with the passed in args
1030
1031NOTE: caller is responsible for freeing ppwz even if function fails
1032********************************************************************/
1033extern "C" HRESULT DAPI StrAllocFormattedArgs(
1034 __deref_out_z LPWSTR* ppwz,
1035 __in __format_string LPCWSTR wzFormat,
1036 __in va_list args
1037 )
1038{
1039 return AllocFormattedArgsHelper(ppwz, FALSE, wzFormat, args);
1040}
1041
1042
1043/********************************************************************
1044StrAllocFormattedArgsSecure - allocates or reuses dynamic string memory
1045and formats it with the passed in args.
1046
1047If the memory needs to reallocated, calls SecureZeroMemory on the
1048original block of memory after it is moved.
1049
1050NOTE: caller is responsible for freeing ppwz even if function fails
1051********************************************************************/
1052extern "C" HRESULT DAPI StrAllocFormattedArgsSecure(
1053 __deref_out_z LPWSTR* ppwz,
1054 __in __format_string LPCWSTR wzFormat,
1055 __in va_list args
1056 )
1057{
1058 return AllocFormattedArgsHelper(ppwz, TRUE, wzFormat, args);
1059}
1060
1061
1062/********************************************************************
1063AllocFormattedArgsHelper - allocates or reuses dynamic string memory
1064and formats it with the passed in args.
1065
1066If fZeroOnRealloc is true and the memory needs to reallocated,
1067calls SecureZeroMemory on original block of memory after it is moved.
1068
1069NOTE: caller is responsible for freeing ppwz even if function fails
1070********************************************************************/
1071static HRESULT AllocFormattedArgsHelper(
1072 __deref_out_z LPWSTR* ppwz,
1073 __in BOOL fZeroOnRealloc,
1074 __in __format_string LPCWSTR wzFormat,
1075 __in va_list args
1076 )
1077{
1078 Assert(ppwz && wzFormat && *wzFormat);
1079
1080 HRESULT hr = S_OK;
1081 SIZE_T cch = 0;
1082 LPWSTR pwzOriginal = NULL;
1083 SIZE_T cbOriginal = 0;
1084 size_t cchOriginal = 0;
1085
1086 if (*ppwz)
1087 {
1088 cbOriginal = MemSize(*ppwz); // get the count in bytes so we can check if it failed (returns -1)
1089 if (-1 == cbOriginal)
1090 {
1091 hr = E_INVALIDARG;
1092 StrExitOnRootFailure(hr, "failed to get size of destination string");
1093 }
1094
1095 cch = cbOriginal / sizeof(WCHAR); //convert the count in bytes to count in characters
1096
1097 hr = ::StringCchLengthW(*ppwz, STRSAFE_MAX_CCH, &cchOriginal);
1098 StrExitOnRootFailure(hr, "failed to get length of original string");
1099 }
1100
1101 if (0 == cch) // if there is no space in the string buffer
1102 {
1103 cch = 256;
1104
1105 hr = AllocHelper(ppwz, cch, fZeroOnRealloc);
1106 StrExitOnFailure(hr, "failed to allocate string to format: %ls", wzFormat);
1107 }
1108
1109 // format the message (grow until it fits or there is a failure)
1110 do
1111 {
1112 hr = ::StringCchVPrintfW(*ppwz, cch, wzFormat, args);
1113 if (STRSAFE_E_INSUFFICIENT_BUFFER == hr)
1114 {
1115 if (!pwzOriginal)
1116 {
1117 // this allows you to pass the original string as a formatting argument and not crash
1118 // save the original string and free it after the printf is complete
1119 pwzOriginal = *ppwz;
1120 *ppwz = NULL;
1121
1122 // StringCchVPrintfW starts writing to the string...
1123 // NOTE: this hack only works with sprintf(&pwz, "%s ...", pwz, ...);
1124 pwzOriginal[cchOriginal] = 0;
1125 }
1126
1127 cch *= 2;
1128
1129 hr = AllocHelper(ppwz, cch, fZeroOnRealloc);
1130 StrExitOnFailure(hr, "failed to allocate string to format: %ls", wzFormat);
1131
1132 hr = S_FALSE;
1133 }
1134 } while (S_FALSE == hr);
1135 StrExitOnRootFailure(hr, "failed to format string");
1136
1137LExit:
1138 if (pwzOriginal && fZeroOnRealloc)
1139 {
1140 SecureZeroMemory(pwzOriginal, cbOriginal);
1141 }
1142
1143 ReleaseStr(pwzOriginal);
1144
1145 return hr;
1146}
1147
1148
1149/********************************************************************
1150StrAnsiAllocFormattedArgs - allocates or reuses dynamic ANSI string memory
1151and formats it with the passed in args
1152
1153NOTE: caller is responsible for freeing ppsz even if function fails
1154********************************************************************/
1155extern "C" HRESULT DAPI StrAnsiAllocFormattedArgs(
1156 __deref_out_z LPSTR* ppsz,
1157 __in __format_string LPCSTR szFormat,
1158 __in va_list args
1159 )
1160{
1161 Assert(ppsz && szFormat && *szFormat);
1162
1163 HRESULT hr = S_OK;
1164 SIZE_T cch = *ppsz ? MemSize(*ppsz) / sizeof(CHAR) : 0;
1165 LPSTR pszOriginal = NULL;
1166 size_t cchOriginal = 0;
1167
1168 if (*ppsz)
1169 {
1170 cch = MemSize(*ppsz); // get the count in bytes so we can check if it failed (returns -1)
1171 if (-1 == cch)
1172 {
1173 hr = E_INVALIDARG;
1174 StrExitOnRootFailure(hr, "failed to get size of destination string");
1175 }
1176 cch /= sizeof(CHAR); //convert the count in bytes to count in characters
1177
1178 hr = ::StringCchLengthA(*ppsz, STRSAFE_MAX_CCH, &cchOriginal);
1179 StrExitOnRootFailure(hr, "failed to get length of original string");
1180 }
1181
1182 if (0 == cch) // if there is no space in the string buffer
1183 {
1184 cch = 256;
1185 hr = StrAnsiAlloc(ppsz, cch);
1186 StrExitOnFailure(hr, "failed to allocate string to format: %s", szFormat);
1187 }
1188
1189 // format the message (grow until it fits or there is a failure)
1190 do
1191 {
1192#pragma prefast(push)
1193#pragma prefast(disable:25068) // We intentionally don't use the unicode API here
1194 hr = ::StringCchVPrintfA(*ppsz, cch, szFormat, args);
1195#pragma prefast(pop)
1196 if (STRSAFE_E_INSUFFICIENT_BUFFER == hr)
1197 {
1198 if (!pszOriginal)
1199 {
1200 // this allows you to pass the original string as a formatting argument and not crash
1201 // save the original string and free it after the printf is complete
1202 pszOriginal = *ppsz;
1203 *ppsz = NULL;
1204 // StringCchVPrintfW starts writing to the string...
1205 // NOTE: this hack only works with sprintf(&pwz, "%s ...", pwz, ...);
1206 pszOriginal[cchOriginal] = 0;
1207 }
1208 cch *= 2;
1209 hr = StrAnsiAlloc(ppsz, cch);
1210 StrExitOnFailure(hr, "failed to allocate string to format: %hs", szFormat);
1211 hr = S_FALSE;
1212 }
1213 } while (S_FALSE == hr);
1214 StrExitOnRootFailure(hr, "failed to format string");
1215
1216LExit:
1217 ReleaseStr(pszOriginal);
1218
1219 return hr;
1220}
1221
1222
1223/********************************************************************
1224StrAllocFromError - returns the string for a particular error.
1225
1226********************************************************************/
1227extern "C" HRESULT DAPI StrAllocFromError(
1228 __inout LPWSTR *ppwzMessage,
1229 __in HRESULT hrError,
1230 __in_opt HMODULE hModule,
1231 ...
1232 )
1233{
1234 HRESULT hr = S_OK;
1235 DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_MAX_WIDTH_MASK | FORMAT_MESSAGE_FROM_SYSTEM;
1236 LPVOID pvMessage = NULL;
1237 DWORD cchMessage = 0;
1238
1239 if (hModule)
1240 {
1241 dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
1242 }
1243
1244 va_list args;
1245 va_start(args, hModule);
1246 cchMessage = ::FormatMessageW(dwFlags, static_cast<LPCVOID>(hModule), hrError, 0, reinterpret_cast<LPWSTR>(&pvMessage), 0, &args);
1247 va_end(args);
1248
1249 if (0 == cchMessage)
1250 {
1251 StrExitWithLastError(hr, "Failed to format message for error: 0x%x", hrError);
1252 }
1253
1254 hr = StrAllocString(ppwzMessage, reinterpret_cast<LPCWSTR>(pvMessage), cchMessage);
1255 StrExitOnFailure(hr, "Failed to allocate string for message.");
1256
1257LExit:
1258 if (pvMessage)
1259 {
1260 ::LocalFree(pvMessage);
1261 }
1262
1263 return hr;
1264}
1265
1266
1267/********************************************************************
1268StrMaxLength - returns maximum number of characters that can be stored in dynamic string p
1269
1270NOTE: assumes Unicode string
1271********************************************************************/
1272extern "C" HRESULT DAPI StrMaxLength(
1273 __in LPCVOID p,
1274 __out SIZE_T* pcch
1275 )
1276{
1277 Assert(pcch);
1278
1279 HRESULT hr = S_OK;
1280
1281 if (p)
1282 {
1283 *pcch = MemSize(p); // get size of entire buffer
1284 if (-1 == *pcch)
1285 {
1286 ExitFunction1(hr = E_FAIL);
1287 }
1288
1289 *pcch /= sizeof(WCHAR); // reduce to count of characters
1290 }
1291 else
1292 {
1293 *pcch = 0;
1294 }
1295 Assert(S_OK == hr);
1296
1297LExit:
1298 return hr;
1299}
1300
1301
1302/********************************************************************
1303StrSize - returns count of bytes in dynamic string p
1304
1305********************************************************************/
1306extern "C" HRESULT DAPI StrSize(
1307 __in LPCVOID p,
1308 __out SIZE_T* pcb
1309 )
1310{
1311 Assert(p && pcb);
1312
1313 HRESULT hr = S_OK;
1314
1315 *pcb = MemSize(p);
1316 if (-1 == *pcb)
1317 {
1318 hr = E_FAIL;
1319 }
1320
1321 return hr;
1322}
1323
1324/********************************************************************
1325StrFree - releases dynamic string memory allocated by any StrAlloc*() functions
1326
1327********************************************************************/
1328extern "C" HRESULT DAPI StrFree(
1329 __in LPVOID p
1330 )
1331{
1332 Assert(p);
1333
1334 HRESULT hr = MemFree(p);
1335 StrExitOnFailure(hr, "failed to free string");
1336
1337LExit:
1338 return hr;
1339}
1340
1341
1342/****************************************************************************
1343StrReplaceStringAll - Replaces wzOldSubString in ppOriginal with a wzNewSubString.
1344Replaces all instances.
1345
1346****************************************************************************/
1347extern "C" HRESULT DAPI StrReplaceStringAll(
1348 __inout LPWSTR* ppwzOriginal,
1349 __in_z LPCWSTR wzOldSubString,
1350 __in_z LPCWSTR wzNewSubString
1351 )
1352{
1353 HRESULT hr = S_OK;
1354 DWORD dwStartIndex = 0;
1355
1356 do
1357 {
1358 hr = StrReplaceString(ppwzOriginal, &dwStartIndex, wzOldSubString, wzNewSubString);
1359 StrExitOnFailure(hr, "Failed to replace substring");
1360 }
1361 while (S_OK == hr);
1362
1363 hr = (0 == dwStartIndex) ? S_FALSE : S_OK;
1364
1365LExit:
1366 return hr;
1367}
1368
1369
1370/****************************************************************************
1371StrReplaceString - Replaces wzOldSubString in ppOriginal with a wzNewSubString.
1372Search for old substring starts at dwStartIndex. Does only 1 replace.
1373
1374****************************************************************************/
1375extern "C" HRESULT DAPI StrReplaceString(
1376 __inout LPWSTR* ppwzOriginal,
1377 __inout DWORD* pdwStartIndex,
1378 __in_z LPCWSTR wzOldSubString,
1379 __in_z LPCWSTR wzNewSubString
1380 )
1381{
1382 Assert(ppwzOriginal && wzOldSubString && wzNewSubString);
1383
1384 HRESULT hr = S_FALSE;
1385 LPCWSTR wzSubLocation = NULL;
1386 LPWSTR pwzBuffer = NULL;
1387 size_t cchOldSubString = 0;
1388 size_t cchNewSubString = 0;
1389
1390 if (!*ppwzOriginal)
1391 {
1392 ExitFunction();
1393 }
1394
1395 wzSubLocation = wcsstr(*ppwzOriginal + *pdwStartIndex, wzOldSubString);
1396 if (!wzSubLocation)
1397 {
1398 ExitFunction();
1399 }
1400
1401 if (wzOldSubString)
1402 {
1403 hr = ::StringCchLengthW(wzOldSubString, STRSAFE_MAX_CCH, &cchOldSubString);
1404 StrExitOnRootFailure(hr, "Failed to get old string length.");
1405 }
1406
1407 if (wzNewSubString)
1408 {
1409 hr = ::StringCchLengthW(wzNewSubString, STRSAFE_MAX_CCH, &cchNewSubString);
1410 StrExitOnRootFailure(hr, "Failed to get new string length.");
1411 }
1412
1413 hr = ::PtrdiffTToDWord(wzSubLocation - *ppwzOriginal, pdwStartIndex);
1414 StrExitOnRootFailure(hr, "Failed to diff pointers.");
1415
1416 hr = StrAllocString(&pwzBuffer, *ppwzOriginal, wzSubLocation - *ppwzOriginal);
1417 StrExitOnFailure(hr, "Failed to duplicate string.");
1418
1419 pwzBuffer[wzSubLocation - *ppwzOriginal] = '\0';
1420
1421 hr = StrAllocConcat(&pwzBuffer, wzNewSubString, 0);
1422 StrExitOnFailure(hr, "Failed to append new string.");
1423
1424 hr = StrAllocConcat(&pwzBuffer, wzSubLocation + cchOldSubString, 0);
1425 StrExitOnFailure(hr, "Failed to append post string.");
1426
1427 hr = StrFree(*ppwzOriginal);
1428 StrExitOnFailure(hr, "Failed to free original string.");
1429
1430 *ppwzOriginal = pwzBuffer;
1431 *pdwStartIndex = *pdwStartIndex + static_cast<DWORD>(cchNewSubString);
1432 hr = S_OK;
1433
1434LExit:
1435 return hr;
1436}
1437
1438
1439static inline BYTE HexCharToByte(
1440 __in WCHAR wc
1441 )
1442{
1443 Assert(L'0' <= wc && wc <= L'9' || L'a' <= wc && wc <= L'f' || L'A' <= wc && wc <= L'F'); // make sure wc is a hex character
1444
1445 BYTE b;
1446 if (L'0' <= wc && wc <= L'9')
1447 {
1448 b = (BYTE)(wc - L'0');
1449 }
1450 else if ('a' <= wc && wc <= 'f')
1451 {
1452 b = (BYTE)(wc - L'0' - (L'a' - L'9' - 1));
1453 }
1454 else // must be (L'A' <= wc && wc <= L'F')
1455 {
1456 b = (BYTE)(wc - L'0' - (L'A' - L'9' - 1));
1457 }
1458
1459 Assert(0 <= b && b <= 15);
1460 return b;
1461}
1462
1463
1464/****************************************************************************
1465StrHexEncode - converts an array of bytes to a text string
1466
1467NOTE: wzDest must have space for cbSource * 2 + 1 characters
1468****************************************************************************/
1469extern "C" HRESULT DAPI StrHexEncode(
1470 __in_ecount(cbSource) const BYTE* pbSource,
1471 __in SIZE_T cbSource,
1472 __out_ecount(cchDest) LPWSTR wzDest,
1473 __in SIZE_T cchDest
1474 )
1475{
1476 Assert(pbSource && wzDest);
1477
1478 HRESULT hr = S_OK;
1479 DWORD i;
1480 BYTE b;
1481
1482 if (cchDest < 2 * cbSource + 1)
1483 {
1484 ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER));
1485 }
1486
1487 for (i = 0; i < cbSource; ++i)
1488 {
1489 b = (*pbSource) >> 4;
1490 *(wzDest++) = (WCHAR)(L'0' + b + ((b < 10) ? 0 : L'A'-L'9'-1));
1491 b = (*pbSource) & 0xF;
1492 *(wzDest++) = (WCHAR)(L'0' + b + ((b < 10) ? 0 : L'A'-L'9'-1));
1493
1494 ++pbSource;
1495 }
1496
1497 *wzDest = 0;
1498
1499LExit:
1500 return hr;
1501}
1502
1503
1504/****************************************************************************
1505StrAllocHexEncode - converts an array of bytes to an allocated text string
1506
1507****************************************************************************/
1508HRESULT DAPI StrAllocHexEncode(
1509 __in_ecount(cbSource) const BYTE* pbSource,
1510 __in SIZE_T cbSource,
1511 __deref_out_ecount_z(2*(cbSource+1)) LPWSTR* ppwzDest
1512 )
1513{
1514 HRESULT hr = S_OK;
1515 SIZE_T cchSource = sizeof(WCHAR) * (cbSource + 1);
1516
1517 hr = StrAlloc(ppwzDest, cchSource);
1518 StrExitOnFailure(hr, "Failed to allocate hex string.");
1519
1520 hr = StrHexEncode(pbSource, cbSource, *ppwzDest, cchSource);
1521 StrExitOnFailure(hr, "Failed to encode hex string.");
1522
1523LExit:
1524 return hr;
1525}
1526
1527
1528/****************************************************************************
1529StrHexDecode - converts a string of text to array of bytes
1530
1531NOTE: wzSource must contain even number of characters
1532****************************************************************************/
1533extern "C" HRESULT DAPI StrHexDecode(
1534 __in_z LPCWSTR wzSource,
1535 __out_bcount(cbDest) BYTE* pbDest,
1536 __in SIZE_T cbDest
1537 )
1538{
1539 Assert(wzSource && pbDest);
1540
1541 HRESULT hr = S_OK;
1542 size_t cchSource = 0;
1543 size_t i = 0;
1544 BYTE b = 0;
1545
1546 hr = ::StringCchLengthW(wzSource, STRSAFE_MAX_CCH, &cchSource);
1547 StrExitOnRootFailure(hr, "Failed to get length of hex string: %ls", wzSource);
1548
1549 Assert(0 == cchSource % 2);
1550 if (cbDest < cchSource / 2)
1551 {
1552 hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
1553 StrExitOnRootFailure(hr, "Insufficient buffer to decode string '%ls' len: %Iu into %Iu bytes.", wzSource, cchSource, cbDest);
1554 }
1555
1556 for (i = 0; i < cchSource / 2; ++i)
1557 {
1558 b = HexCharToByte(*wzSource++);
1559 (*pbDest) = b << 4;
1560
1561 b = HexCharToByte(*wzSource++);
1562 (*pbDest) |= b & 0xF;
1563
1564 ++pbDest;
1565 }
1566
1567LExit:
1568 return hr;
1569}
1570
1571
1572/****************************************************************************
1573StrAllocHexDecode - allocates a byte array hex-converted from string of text
1574
1575NOTE: wzSource must contain even number of characters
1576****************************************************************************/
1577extern "C" HRESULT DAPI StrAllocHexDecode(
1578 __in_z LPCWSTR wzSource,
1579 __out_bcount(*pcbDest) BYTE** ppbDest,
1580 __out_opt DWORD* pcbDest
1581 )
1582{
1583 Assert(wzSource && *wzSource && ppbDest);
1584
1585 HRESULT hr = S_OK;
1586 size_t cch = 0;
1587 BYTE* pb = NULL;
1588 DWORD cb = 0;
1589
1590 hr = ::StringCchLengthW(wzSource, STRSAFE_MAX_CCH, &cch);
1591 StrExitOnFailure(hr, "Failed to calculate length of source string.");
1592
1593 if (cch % 2)
1594 {
1595 hr = E_INVALIDARG;
1596 StrExitOnFailure(hr, "Invalid source parameter, string must be even length or it cannot be decoded.");
1597 }
1598
1599 cb = static_cast<DWORD>(cch / 2);
1600 pb = static_cast<BYTE*>(MemAlloc(cb, TRUE));
1601 StrExitOnNull(pb, hr, E_OUTOFMEMORY, "Failed to allocate memory for hex decode.");
1602
1603 hr = StrHexDecode(wzSource, pb, cb);
1604 StrExitOnFailure(hr, "Failed to decode source string.");
1605
1606 *ppbDest = pb;
1607 pb = NULL;
1608
1609 if (pcbDest)
1610 {
1611 *pcbDest = cb;
1612 }
1613
1614LExit:
1615 ReleaseMem(pb);
1616
1617 return hr;
1618}
1619
1620
1621/****************************************************************************
1622Base85 encoding/decoding data
1623
1624****************************************************************************/
1625const WCHAR Base85EncodeTable[] = L"!%'()*+,-./0123456789:;?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}~";
1626
1627const BYTE Base85DecodeTable[256] =
1628{
1629 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
1630 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
1631 85, 0, 85, 85, 85, 1, 85, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1632 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 85, 85, 85, 23,
1633 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
1634 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 85, 52, 53, 54,
1635 85, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
1636 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85,
1637 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
1638 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
1639 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
1640 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
1641 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
1642 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
1643 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
1644 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85
1645};
1646
1647const UINT Base85PowerTable[4] = { 1, 85, 85*85, 85*85*85 };
1648
1649
1650/****************************************************************************
1651StrAllocBase85Encode - converts an array of bytes into an XML compatible string
1652
1653****************************************************************************/
1654extern "C" HRESULT DAPI StrAllocBase85Encode(
1655 __in_bcount_opt(cbSource) const BYTE* pbSource,
1656 __in SIZE_T cbSource,
1657 __deref_out_z LPWSTR* pwzDest
1658 )
1659{
1660 HRESULT hr = S_OK;
1661 SIZE_T cchDest = 0;
1662 LPWSTR wzDest;
1663 DWORD_PTR iSource = 0;
1664 DWORD_PTR iDest = 0;
1665
1666 if (!pwzDest || !pbSource)
1667 {
1668 return E_INVALIDARG;
1669 }
1670
1671 // calc actual size of output
1672 cchDest = cbSource / 4;
1673 cchDest += cchDest * 4;
1674 if (cbSource & 3)
1675 {
1676 cchDest += (cbSource & 3) + 1;
1677 }
1678 ++cchDest; // add room for null terminator
1679
1680 hr = StrAlloc(pwzDest, cchDest);
1681 StrExitOnFailure(hr, "failed to allocate destination string");
1682
1683 wzDest = *pwzDest;
1684
1685 // first, encode full words
1686 for (iSource = 0, iDest = 0; (iSource + 4 < cbSource) && (iDest + 5 < cchDest); iSource += 4, iDest += 5)
1687 {
1688 DWORD n = pbSource[iSource] + (pbSource[iSource + 1] << 8) + (pbSource[iSource + 2] << 16) + (pbSource[iSource + 3] << 24);
1689 DWORD k = n / 85;
1690
1691 //Assert(0 <= (n - k * 85) && (n - k * 85) < countof(Base85EncodeTable));
1692 wzDest[iDest] = Base85EncodeTable[n - k * 85];
1693 n = k / 85;
1694
1695 //Assert(0 <= (k - n * 85) && (k - n * 85) < countof(Base85EncodeTable));
1696 wzDest[iDest + 1] = Base85EncodeTable[k - n * 85];
1697 k = n / 85;
1698
1699 //Assert(0 <= (n - k * 85) && (n - k * 85) < countof(Base85EncodeTable));
1700 wzDest[iDest + 2] = Base85EncodeTable[n - k * 85];
1701 n = k / 85;
1702
1703 //Assert(0 <= (k - n * 85) && (k - n * 85) < countof(Base85EncodeTable));
1704 wzDest[iDest + 3] = Base85EncodeTable[k - n * 85];
1705
1706 __assume(n <= DWORD_MAX / 85 / 85 / 85 / 85);
1707
1708 //Assert(0 <= n && n < countof(Base85EncodeTable));
1709 wzDest[iDest + 4] = Base85EncodeTable[n];
1710 }
1711
1712 // encode any remaining bytes
1713 if (iSource < cbSource)
1714 {
1715 DWORD n = 0;
1716 for (DWORD i = 0; iSource + i < cbSource; ++i)
1717 {
1718 n += pbSource[iSource + i] << (i << 3);
1719 }
1720
1721 for (/* iSource already initialized */; iSource < cbSource && iDest < cchDest; ++iSource, ++iDest)
1722 {
1723 DWORD k = n / 85;
1724
1725 //Assert(0 <= (n - k * 85) && (n - k * 85) < countof(Base85EncodeTable));
1726 wzDest[iDest] = Base85EncodeTable[n - k * 85];
1727
1728 n = k;
1729 }
1730
1731 wzDest[iDest] = Base85EncodeTable[n];
1732 ++iDest;
1733 }
1734 Assert(iSource == cbSource);
1735 Assert(iDest == cchDest - 1);
1736
1737 wzDest[iDest] = L'\0';
1738 hr = S_OK;
1739
1740LExit:
1741 return hr;
1742}
1743
1744
1745/****************************************************************************
1746StrAllocBase85Decode - converts a string of text to array of bytes
1747
1748NOTE: Use MemFree() to release the allocated stream of bytes
1749****************************************************************************/
1750extern "C" HRESULT DAPI StrAllocBase85Decode(
1751 __in_z LPCWSTR wzSource,
1752 __deref_out_bcount(*pcbDest) BYTE** ppbDest,
1753 __out SIZE_T* pcbDest
1754 )
1755{
1756 HRESULT hr = S_OK;
1757 size_t cchSource = 0;
1758 DWORD_PTR i, n, k;
1759
1760 BYTE* pbDest = 0;
1761 SIZE_T cbDest = 0;
1762
1763 if (!wzSource || !ppbDest || !pcbDest)
1764 {
1765 ExitFunction1(hr = E_INVALIDARG);
1766 }
1767
1768 hr = ::StringCchLengthW(wzSource, STRSAFE_MAX_CCH, &cchSource);
1769 StrExitOnRootFailure(hr, "failed to get length of base 85 string: %ls", wzSource);
1770
1771 // evaluate size of output and check it
1772 k = cchSource / 5;
1773 cbDest = k << 2;
1774 k = cchSource - k * 5;
1775 if (k)
1776 {
1777 if (1 == k)
1778 {
1779 // decode error -- encoded size cannot equal 1 mod 5
1780 return E_UNEXPECTED;
1781 }
1782
1783 cbDest += k - 1;
1784 }
1785
1786 *ppbDest = static_cast<BYTE*>(MemAlloc(cbDest, FALSE));
1787 StrExitOnNull(*ppbDest, hr, E_OUTOFMEMORY, "failed allocate memory to decode the string");
1788
1789 pbDest = *ppbDest;
1790 *pcbDest = cbDest;
1791
1792 // decode full words first
1793 while (5 <= cchSource)
1794 {
1795 k = Base85DecodeTable[wzSource[0]];
1796 if (85 == k)
1797 {
1798 // illegal symbol
1799 return E_UNEXPECTED;
1800 }
1801 n = k;
1802
1803 k = Base85DecodeTable[wzSource[1]];
1804 if (85 == k)
1805 {
1806 // illegal symbol
1807 return E_UNEXPECTED;
1808 }
1809 n += k * 85;
1810
1811 k = Base85DecodeTable[wzSource[2]];
1812 if (85 == k)
1813 {
1814 // illegal symbol
1815 return E_UNEXPECTED;
1816 }
1817 n += k * (85 * 85);
1818
1819 k = Base85DecodeTable[wzSource[3]];
1820 if (85 == k)
1821 {
1822 // illegal symbol
1823 return E_UNEXPECTED;
1824 }
1825 n += k * (85 * 85 * 85);
1826
1827 k = Base85DecodeTable[wzSource[4]];
1828 if (85 == k)
1829 {
1830 // illegal symbol
1831 return E_UNEXPECTED;
1832 }
1833 k *= (85 * 85 * 85 * 85);
1834
1835 // if (k + n > (1u << 32)) <=> (k > ~n) then decode error
1836 if (k > ~n)
1837 {
1838 // overflow
1839 return E_UNEXPECTED;
1840 }
1841
1842 n += k;
1843
1844 pbDest[0] = (BYTE) n;
1845 pbDest[1] = (BYTE) (n >> 8);
1846 pbDest[2] = (BYTE) (n >> 16);
1847 pbDest[3] = (BYTE) (n >> 24);
1848
1849 wzSource += 5;
1850 pbDest += 4;
1851 cchSource -= 5;
1852 }
1853
1854 if (cchSource)
1855 {
1856 n = 0;
1857 for (i = 0; i < cchSource; ++i)
1858 {
1859 k = Base85DecodeTable[wzSource[i]];
1860 if (85 == k)
1861 {
1862 // illegal symbol
1863 return E_UNEXPECTED;
1864 }
1865
1866 n += k * Base85PowerTable[i];
1867 }
1868
1869 for (i = 1; i < cchSource; ++i)
1870 {
1871 *pbDest++ = (BYTE)n;
1872 n >>= 8;
1873 }
1874
1875 if (0 != n)
1876 {
1877 // decode error
1878 return E_UNEXPECTED;
1879 }
1880 }
1881
1882 hr = S_OK;
1883
1884LExit:
1885 return hr;
1886}
1887
1888
1889/****************************************************************************
1890MultiSzLen - calculates the length of a MULTISZ string including all nulls
1891including the double null terminator at the end of the MULTISZ.
1892
1893NOTE: returns 0 if the multisz in not properly terminated with two nulls
1894****************************************************************************/
1895extern "C" HRESULT DAPI MultiSzLen(
1896 __in_ecount(*pcch) __nullnullterminated LPCWSTR pwzMultiSz,
1897 __out SIZE_T* pcch
1898)
1899{
1900 Assert(pcch);
1901
1902 HRESULT hr = S_OK;
1903 LPCWSTR wz = pwzMultiSz;
1904 DWORD_PTR dwMaxSize = 0;
1905
1906 hr = StrMaxLength(pwzMultiSz, &dwMaxSize);
1907 StrExitOnFailure(hr, "failed to get the max size of a string while calculating MULTISZ length");
1908
1909 *pcch = 0;
1910 while (*pcch < dwMaxSize)
1911 {
1912 if (L'\0' == *wz && L'\0' == *(wz + 1))
1913 {
1914 break;
1915 }
1916
1917 ++wz;
1918 *pcch = *pcch + 1;
1919 }
1920
1921 // Add two for the last 2 NULLs (that we looked ahead at)
1922 *pcch = *pcch + 2;
1923
1924 // If we've walked off the end then the length is 0
1925 if (*pcch > dwMaxSize)
1926 {
1927 *pcch = 0;
1928 }
1929
1930LExit:
1931 return hr;
1932}
1933
1934
1935/****************************************************************************
1936MultiSzPrepend - prepends a string onto the front of a MUTLISZ
1937
1938****************************************************************************/
1939extern "C" HRESULT DAPI MultiSzPrepend(
1940 __deref_inout_ecount(*pcchMultiSz) __nullnullterminated LPWSTR* ppwzMultiSz,
1941 __inout_opt SIZE_T* pcchMultiSz,
1942 __in __nullnullterminated LPCWSTR pwzInsert
1943 )
1944{
1945 Assert(ppwzMultiSz && pwzInsert && *pwzInsert);
1946
1947 HRESULT hr =S_OK;
1948 LPWSTR pwzResult = NULL;
1949 SIZE_T cchResult = 0;
1950 SIZE_T cchInsert = 0;
1951 SIZE_T cchMultiSz = 0;
1952
1953 // Get the lengths of the MULTISZ (and prime it if it's not initialized)
1954 if (pcchMultiSz && 0 != *pcchMultiSz)
1955 {
1956 cchMultiSz = *pcchMultiSz;
1957 }
1958 else
1959 {
1960 hr = MultiSzLen(*ppwzMultiSz, &cchMultiSz);
1961 StrExitOnFailure(hr, "failed to get length of multisz");
1962 }
1963
1964 hr = ::StringCchLengthW(pwzInsert, STRSAFE_MAX_CCH, reinterpret_cast<size_t*>(&cchInsert));
1965 StrExitOnRootFailure(hr, "failed to get length of insert string");
1966
1967 cchResult = cchInsert + cchMultiSz + 1;
1968
1969 // Allocate the result buffer
1970 hr = StrAlloc(&pwzResult, cchResult + 1);
1971 StrExitOnFailure(hr, "failed to allocate result string");
1972
1973 // Prepend
1974 hr = ::StringCchCopyW(pwzResult, cchResult, pwzInsert);
1975 StrExitOnRootFailure(hr, "failed to copy prepend string: %ls", pwzInsert);
1976
1977 // If there was no MULTISZ, double null terminate our result, otherwise, copy the MULTISZ in
1978 if (0 == cchMultiSz)
1979 {
1980 pwzResult[cchResult] = L'\0';
1981 ++cchResult;
1982 }
1983 else
1984 {
1985 // Copy the rest
1986 ::CopyMemory(pwzResult + cchInsert + 1, *ppwzMultiSz, cchMultiSz * sizeof(WCHAR));
1987
1988 // Free the old buffer
1989 ReleaseNullStr(*ppwzMultiSz);
1990 }
1991
1992 // Set the result
1993 *ppwzMultiSz = pwzResult;
1994
1995 if (pcchMultiSz)
1996 {
1997 *pcchMultiSz = cchResult;
1998 }
1999
2000 pwzResult = NULL;
2001
2002LExit:
2003 ReleaseNullStr(pwzResult);
2004
2005 return hr;
2006}
2007
2008/****************************************************************************
2009MultiSzFindSubstring - case insensitive find of a string in a MULTISZ that contains the
2010specified sub string and returns the index of the
2011string in the MULTISZ, the address, neither, or both
2012
2013NOTE: returns S_FALSE if the string is not found
2014****************************************************************************/
2015extern "C" HRESULT DAPI MultiSzFindSubstring(
2016 __in __nullnullterminated LPCWSTR pwzMultiSz,
2017 __in __nullnullterminated LPCWSTR pwzSubstring,
2018 __out_opt DWORD_PTR* pdwIndex,
2019 __deref_opt_out __nullnullterminated LPCWSTR* ppwzFoundIn
2020 )
2021{
2022 Assert(pwzMultiSz && *pwzMultiSz && pwzSubstring && *pwzSubstring);
2023
2024 HRESULT hr = S_FALSE; // Assume we won't find it (the glass is half empty)
2025 LPCWSTR wz = pwzMultiSz;
2026 DWORD_PTR dwIndex = 0;
2027 SIZE_T cchMultiSz = 0;
2028 SIZE_T cchProgress = 0;
2029
2030 hr = MultiSzLen(pwzMultiSz, &cchMultiSz);
2031 StrExitOnFailure(hr, "failed to get the length of a MULTISZ string");
2032
2033 // Find the string containing the sub string
2034 hr = S_OK;
2035 while (NULL == wcsistr(wz, pwzSubstring))
2036 {
2037 // Slide through to the end of the current string
2038 while (L'\0' != *wz && cchProgress < cchMultiSz)
2039 {
2040 ++wz;
2041 ++cchProgress;
2042 }
2043
2044 // If we're done, we're done
2045 if (L'\0' == *(wz + 1) || cchProgress >= cchMultiSz)
2046 {
2047 hr = S_FALSE;
2048 break;
2049 }
2050
2051 // Move on to the next string
2052 ++wz;
2053 ++dwIndex;
2054 }
2055 Assert(S_OK == hr || S_FALSE == hr);
2056
2057 // If we found it give them what they want
2058 if (S_OK == hr)
2059 {
2060 if (pdwIndex)
2061 {
2062 *pdwIndex = dwIndex;
2063 }
2064
2065 if (ppwzFoundIn)
2066 {
2067 *ppwzFoundIn = wz;
2068 }
2069 }
2070
2071LExit:
2072 return hr;
2073}
2074
2075/****************************************************************************
2076MultiSzFindString - finds a string in a MULTISZ and returns the index of
2077the string in the MULTISZ, the address or both
2078
2079NOTE: returns S_FALSE if the string is not found
2080****************************************************************************/
2081extern "C" HRESULT DAPI MultiSzFindString(
2082 __in __nullnullterminated LPCWSTR pwzMultiSz,
2083 __in __nullnullterminated LPCWSTR pwzString,
2084 __out_opt DWORD_PTR* pdwIndex,
2085 __deref_opt_out __nullnullterminated LPCWSTR* ppwzFound
2086 )
2087{
2088 Assert(pwzMultiSz && *pwzMultiSz && pwzString && *pwzString && (pdwIndex || ppwzFound));
2089
2090 HRESULT hr = S_FALSE; // Assume we won't find it
2091 LPCWSTR wz = pwzMultiSz;
2092 DWORD_PTR dwIndex = 0;
2093 SIZE_T cchMutliSz = 0;
2094 SIZE_T cchProgress = 0;
2095
2096 hr = MultiSzLen(pwzMultiSz, &cchMutliSz);
2097 StrExitOnFailure(hr, "failed to get the length of a MULTISZ string");
2098
2099 // Find the string
2100 hr = S_OK;
2101 while (0 != lstrcmpW(wz, pwzString))
2102 {
2103 // Slide through to the end of the current string
2104 while (L'\0' != *wz && cchProgress < cchMutliSz)
2105 {
2106 ++wz;
2107 ++cchProgress;
2108 }
2109
2110 // If we're done, we're done
2111 if (L'\0' == *(wz + 1) || cchProgress >= cchMutliSz)
2112 {
2113 hr = S_FALSE;
2114 break;
2115 }
2116
2117 // Move on to the next string
2118 ++wz;
2119 ++dwIndex;
2120 }
2121 Assert(S_OK == hr || S_FALSE == hr);
2122
2123 // If we found it give them what they want
2124 if (S_OK == hr)
2125 {
2126 if (pdwIndex)
2127 {
2128 *pdwIndex = dwIndex;
2129 }
2130
2131 if (ppwzFound)
2132 {
2133 *ppwzFound = wz;
2134 }
2135 }
2136
2137LExit:
2138 return hr;
2139}
2140
2141/****************************************************************************
2142MultiSzRemoveString - removes string from a MULTISZ at the specified
2143index
2144
2145NOTE: does an in place removal without shrinking the memory allocation
2146
2147NOTE: returns S_FALSE if the MULTISZ has fewer strings than dwIndex
2148****************************************************************************/
2149extern "C" HRESULT DAPI MultiSzRemoveString(
2150 __deref_inout __nullnullterminated LPWSTR* ppwzMultiSz,
2151 __in DWORD_PTR dwIndex
2152 )
2153{
2154 Assert(ppwzMultiSz && *ppwzMultiSz);
2155
2156 HRESULT hr = S_OK;
2157 LPCWSTR wz = *ppwzMultiSz;
2158 LPCWSTR wzNext = NULL;
2159 DWORD_PTR dwCurrentIndex = 0;
2160 SIZE_T cchMultiSz = 0;
2161 SIZE_T cchProgress = 0;
2162
2163 hr = MultiSzLen(*ppwzMultiSz, &cchMultiSz);
2164 StrExitOnFailure(hr, "failed to get the length of a MULTISZ string");
2165
2166 // Find the index we want to remove
2167 hr = S_OK;
2168 while (dwCurrentIndex < dwIndex)
2169 {
2170 // Slide through to the end of the current string
2171 while (L'\0' != *wz && cchProgress < cchMultiSz)
2172 {
2173 ++wz;
2174 ++cchProgress;
2175 }
2176
2177 // If we're done, we're done
2178 if (L'\0' == *(wz + 1) || cchProgress >= cchMultiSz)
2179 {
2180 hr = S_FALSE;
2181 break;
2182 }
2183
2184 // Move on to the next string
2185 ++wz;
2186 ++cchProgress;
2187 ++dwCurrentIndex;
2188 }
2189 Assert(S_OK == hr || S_FALSE == hr);
2190
2191 // If we found the index to be removed
2192 if (S_OK == hr)
2193 {
2194 wzNext = wz;
2195
2196 // Slide through to the end of the current string
2197 while (L'\0' != *wzNext && cchProgress < cchMultiSz)
2198 {
2199 ++wzNext;
2200 ++cchProgress;
2201 }
2202
2203 // Something weird has happened if we're past the end of the MULTISZ
2204 if (cchProgress > cchMultiSz)
2205 {
2206 hr = E_UNEXPECTED;
2207 StrExitOnFailure(hr, "failed to move past the string to be removed from MULTISZ");
2208 }
2209
2210 // Move on to the next character
2211 ++wzNext;
2212 ++cchProgress;
2213
2214 ::MoveMemory((LPVOID)wz, (LPVOID)wzNext, (cchMultiSz - cchProgress) * sizeof(WCHAR));
2215 }
2216
2217LExit:
2218 return hr;
2219}
2220
2221/****************************************************************************
2222MultiSzInsertString - inserts new string at the specified index
2223
2224****************************************************************************/
2225extern "C" HRESULT DAPI MultiSzInsertString(
2226 __deref_inout __nullnullterminated LPWSTR* ppwzMultiSz,
2227 __inout_opt SIZE_T* pcchMultiSz,
2228 __in DWORD_PTR dwIndex,
2229 __in_z LPCWSTR pwzInsert
2230 )
2231{
2232 Assert(ppwzMultiSz && pwzInsert && *pwzInsert);
2233
2234 HRESULT hr = S_OK;
2235 LPCWSTR wz = *ppwzMultiSz;
2236 DWORD_PTR dwCurrentIndex = 0;
2237 SIZE_T cchProgress = 0;
2238 LPWSTR pwzResult = NULL;
2239 SIZE_T cchResult = 0;
2240 SIZE_T cchString = 0;
2241 SIZE_T cchMultiSz = 0;
2242
2243 hr = ::StringCchLengthW(pwzInsert, STRSAFE_MAX_CCH, reinterpret_cast<size_t*>(&cchString));
2244 StrExitOnRootFailure(hr, "failed to get length of insert string");
2245
2246 if (pcchMultiSz && 0 != *pcchMultiSz)
2247 {
2248 cchMultiSz = *pcchMultiSz;
2249 }
2250 else
2251 {
2252 hr = MultiSzLen(*ppwzMultiSz, &cchMultiSz);
2253 StrExitOnFailure(hr, "failed to get the length of a MULTISZ string");
2254 }
2255
2256 // Find the index we want to insert at
2257 hr = S_OK;
2258 while (dwCurrentIndex < dwIndex)
2259 {
2260 // Slide through to the end of the current string
2261 while (L'\0' != *wz && cchProgress < cchMultiSz)
2262 {
2263 ++wz;
2264 ++cchProgress;
2265 }
2266
2267 // If we're done, we're done
2268 if ((dwCurrentIndex + 1 != dwIndex && L'\0' == *(wz + 1)) || cchProgress >= cchMultiSz)
2269 {
2270 hr = HRESULT_FROM_WIN32(ERROR_OBJECT_NOT_FOUND);
2271 StrExitOnRootFailure(hr, "requested to insert into an invalid index: %u in a MULTISZ", dwIndex);
2272 }
2273
2274 // Move on to the next string
2275 ++wz;
2276 ++cchProgress;
2277 ++dwCurrentIndex;
2278 }
2279
2280 //
2281 // Insert the string
2282 //
2283 cchResult = cchMultiSz + cchString + 1;
2284
2285 hr = StrAlloc(&pwzResult, cchResult);
2286 StrExitOnFailure(hr, "failed to allocate result string for MULTISZ insert");
2287
2288 // Copy the part before the insert
2289 ::CopyMemory(pwzResult, *ppwzMultiSz, cchProgress * sizeof(WCHAR));
2290
2291 // Copy the insert part
2292 ::CopyMemory(pwzResult + cchProgress, pwzInsert, (cchString + 1) * sizeof(WCHAR));
2293
2294 // Copy the part after the insert
2295 ::CopyMemory(pwzResult + cchProgress + cchString + 1, wz, (cchMultiSz - cchProgress) * sizeof(WCHAR));
2296
2297 // Free the old buffer
2298 ReleaseNullStr(*ppwzMultiSz);
2299
2300 // Set the result
2301 *ppwzMultiSz = pwzResult;
2302
2303 // If they wanted the resulting length, let 'em have it
2304 if (pcchMultiSz)
2305 {
2306 *pcchMultiSz = cchResult;
2307 }
2308
2309 pwzResult = NULL;
2310
2311LExit:
2312 ReleaseStr(pwzResult);
2313
2314 return hr;
2315}
2316
2317/****************************************************************************
2318MultiSzReplaceString - replaces string at the specified index with a new one
2319
2320****************************************************************************/
2321extern "C" HRESULT DAPI MultiSzReplaceString(
2322 __deref_inout __nullnullterminated LPWSTR* ppwzMultiSz,
2323 __in DWORD_PTR dwIndex,
2324 __in_z LPCWSTR pwzString
2325 )
2326{
2327 Assert(ppwzMultiSz && pwzString && *pwzString);
2328
2329 HRESULT hr = S_OK;
2330
2331 hr = MultiSzRemoveString(ppwzMultiSz, dwIndex);
2332 StrExitOnFailure(hr, "failed to remove string from MULTISZ at the specified index: %u", dwIndex);
2333
2334 hr = MultiSzInsertString(ppwzMultiSz, NULL, dwIndex, pwzString);
2335 StrExitOnFailure(hr, "failed to insert string into MULTISZ at the specified index: %u", dwIndex);
2336
2337LExit:
2338 return hr;
2339}
2340
2341
2342/****************************************************************************
2343wcsistr - case insensitive find a substring
2344
2345****************************************************************************/
2346extern "C" LPCWSTR DAPI wcsistr(
2347 __in_z LPCWSTR wzString,
2348 __in_z LPCWSTR wzCharSet
2349 )
2350{
2351 LPCWSTR wzSource = wzString;
2352 LPCWSTR wzSearch = NULL;
2353 SIZE_T cchSourceIndex = 0;
2354
2355 // Walk through wzString (the source string) one character at a time
2356 while (*wzSource)
2357 {
2358 cchSourceIndex = 0;
2359 wzSearch = wzCharSet;
2360
2361 // Look ahead in the source string until we get a full match or we hit the end of the source
2362 while (L'\0' != wzSource[cchSourceIndex] && L'\0' != *wzSearch && towlower(wzSource[cchSourceIndex]) == towlower(*wzSearch))
2363 {
2364 ++cchSourceIndex;
2365 ++wzSearch;
2366 }
2367
2368 // If we found it, return the point that we found it at
2369 if (L'\0' == *wzSearch)
2370 {
2371 return wzSource;
2372 }
2373
2374 // Walk ahead one character
2375 ++wzSource;
2376 }
2377
2378 return NULL;
2379}
2380
2381/****************************************************************************
2382StrStringToInt16 - converts a string to a signed 16-bit integer.
2383
2384****************************************************************************/
2385extern "C" HRESULT DAPI StrStringToInt16(
2386 __in_z LPCWSTR wzIn,
2387 __in DWORD cchIn,
2388 __out SHORT* psOut
2389 )
2390{
2391 HRESULT hr = S_OK;
2392 LONGLONG ll = 0;
2393
2394 hr = StrStringToInt64(wzIn, cchIn, &ll);
2395 StrExitOnFailure(hr, "Failed to parse int64.");
2396
2397 if (SHORT_MAX < ll || SHORT_MIN > ll)
2398 {
2399 ExitFunction1(hr = DISP_E_OVERFLOW);
2400 }
2401 *psOut = (SHORT)ll;
2402
2403LExit:
2404 return hr;
2405}
2406
2407/****************************************************************************
2408StrStringToUInt16 - converts a string to an unsigned 16-bit integer.
2409
2410****************************************************************************/
2411extern "C" HRESULT DAPI StrStringToUInt16(
2412 __in_z LPCWSTR wzIn,
2413 __in DWORD cchIn,
2414 __out USHORT* pusOut
2415 )
2416{
2417 HRESULT hr = S_OK;
2418 ULONGLONG ull = 0;
2419
2420 hr = StrStringToUInt64(wzIn, cchIn, &ull);
2421 StrExitOnFailure(hr, "Failed to parse uint64.");
2422
2423 if (USHORT_MAX < ull)
2424 {
2425 ExitFunction1(hr = DISP_E_OVERFLOW);
2426 }
2427 *pusOut = (USHORT)ull;
2428
2429LExit:
2430 return hr;
2431}
2432
2433/****************************************************************************
2434StrStringToInt32 - converts a string to a signed 32-bit integer.
2435
2436****************************************************************************/
2437extern "C" HRESULT DAPI StrStringToInt32(
2438 __in_z LPCWSTR wzIn,
2439 __in DWORD cchIn,
2440 __out INT* piOut
2441 )
2442{
2443 HRESULT hr = S_OK;
2444 LONGLONG ll = 0;
2445
2446 hr = StrStringToInt64(wzIn, cchIn, &ll);
2447 StrExitOnFailure(hr, "Failed to parse int64.");
2448
2449 if (INT_MAX < ll || INT_MIN > ll)
2450 {
2451 ExitFunction1(hr = DISP_E_OVERFLOW);
2452 }
2453 *piOut = (INT)ll;
2454
2455LExit:
2456 return hr;
2457}
2458
2459/****************************************************************************
2460StrStringToUInt32 - converts a string to an unsigned 32-bit integer.
2461
2462****************************************************************************/
2463extern "C" HRESULT DAPI StrStringToUInt32(
2464 __in_z LPCWSTR wzIn,
2465 __in DWORD cchIn,
2466 __out UINT* puiOut
2467 )
2468{
2469 HRESULT hr = S_OK;
2470 ULONGLONG ull = 0;
2471
2472 hr = StrStringToUInt64(wzIn, cchIn, &ull);
2473 StrExitOnFailure(hr, "Failed to parse uint64.");
2474
2475 if (UINT_MAX < ull)
2476 {
2477 ExitFunction1(hr = DISP_E_OVERFLOW);
2478 }
2479 *puiOut = (UINT)ull;
2480
2481LExit:
2482 return hr;
2483}
2484
2485/****************************************************************************
2486StrStringToInt64 - converts a string to a signed 64-bit integer.
2487
2488****************************************************************************/
2489extern "C" HRESULT DAPI StrStringToInt64(
2490 __in_z LPCWSTR wzIn,
2491 __in DWORD cchIn,
2492 __out LONGLONG* pllOut
2493 )
2494{
2495 HRESULT hr = S_OK;
2496 DWORD i = 0;
2497 INT iSign = 1;
2498 INT nDigit = 0;
2499 LARGE_INTEGER liValue = { };
2500 size_t cchString = 0;
2501
2502 // get string length if not provided
2503 if (0 >= cchIn)
2504 {
2505 hr = ::StringCchLengthW(wzIn, STRSAFE_MAX_CCH, &cchString);
2506 StrExitOnRootFailure(hr, "Failed to get length of string.");
2507
2508 cchIn = (DWORD)cchString;
2509 if (0 >= cchIn)
2510 {
2511 ExitFunction1(hr = E_INVALIDARG);
2512 }
2513 }
2514
2515 // check sign
2516 if (L'-' == wzIn[0])
2517 {
2518 if (1 >= cchIn)
2519 {
2520 ExitFunction1(hr = E_INVALIDARG);
2521 }
2522 i = 1;
2523 iSign = -1;
2524 }
2525
2526 // read digits
2527 while (i < cchIn)
2528 {
2529 nDigit = wzIn[i] - L'0';
2530 if (0 > nDigit || 9 < nDigit)
2531 {
2532 ExitFunction1(hr = E_INVALIDARG);
2533 }
2534 liValue.QuadPart = liValue.QuadPart * 10 + nDigit * iSign;
2535
2536 if ((liValue.HighPart ^ iSign) & INT_MIN)
2537 {
2538 ExitFunction1(hr = DISP_E_OVERFLOW);
2539 }
2540 ++i;
2541 }
2542
2543 *pllOut = liValue.QuadPart;
2544
2545LExit:
2546 return hr;
2547}
2548
2549/****************************************************************************
2550StrStringToUInt64 - converts a string to an unsigned 64-bit integer.
2551
2552****************************************************************************/
2553extern "C" HRESULT DAPI StrStringToUInt64(
2554 __in_z LPCWSTR wzIn,
2555 __in DWORD cchIn,
2556 __out ULONGLONG* pullOut
2557 )
2558{
2559 HRESULT hr = S_OK;
2560 DWORD i = 0;
2561 DWORD nDigit = 0;
2562 ULONGLONG ullValue = 0;
2563 ULONGLONG ull = 0;
2564 size_t cchString = 0;
2565
2566 // get string length if not provided
2567 if (0 >= cchIn)
2568 {
2569 hr = ::StringCchLengthW(wzIn, STRSAFE_MAX_CCH, &cchString);
2570 StrExitOnRootFailure(hr, "Failed to get length of string.");
2571
2572 cchIn = (DWORD)cchString;
2573 if (0 >= cchIn)
2574 {
2575 ExitFunction1(hr = E_INVALIDARG);
2576 }
2577 }
2578
2579 // read digits
2580 while (i < cchIn)
2581 {
2582 nDigit = wzIn[i] - L'0';
2583 if (9 < nDigit)
2584 {
2585 ExitFunction1(hr = E_INVALIDARG);
2586 }
2587 ull = (ULONGLONG)(ullValue * 10 + nDigit);
2588
2589 if (ull < ullValue)
2590 {
2591 ExitFunction1(hr = DISP_E_OVERFLOW);
2592 }
2593 ullValue = ull;
2594 ++i;
2595 }
2596
2597 *pullOut = ullValue;
2598
2599LExit:
2600 return hr;
2601}
2602
2603/****************************************************************************
2604StrStringToUpper - alters the given string in-place to be entirely uppercase
2605
2606****************************************************************************/
2607void DAPI StrStringToUpper(
2608 __inout_z LPWSTR wzIn
2609 )
2610{
2611 ::CharUpperBuffW(wzIn, lstrlenW(wzIn));
2612}
2613
2614/****************************************************************************
2615StrStringToLower - alters the given string in-place to be entirely lowercase
2616
2617****************************************************************************/
2618void DAPI StrStringToLower(
2619 __inout_z LPWSTR wzIn
2620 )
2621{
2622 ::CharLowerBuffW(wzIn, lstrlenW(wzIn));
2623}
2624
2625/****************************************************************************
2626StrAllocStringToUpperInvariant - creates an upper-case copy of a string.
2627
2628****************************************************************************/
2629extern "C" HRESULT DAPI StrAllocStringToUpperInvariant(
2630 __deref_out_z LPWSTR* pscz,
2631 __in_z LPCWSTR wzSource,
2632 __in SIZE_T cchSource
2633 )
2634{
2635 return StrAllocStringMapInvariant(pscz, wzSource, cchSource, LCMAP_UPPERCASE);
2636}
2637
2638/****************************************************************************
2639StrAllocStringToLowerInvariant - creates an lower-case copy of a string.
2640
2641****************************************************************************/
2642extern "C" HRESULT DAPI StrAllocStringToLowerInvariant(
2643 __deref_out_z LPWSTR* pscz,
2644 __in_z LPCWSTR wzSource,
2645 __in SIZE_T cchSource
2646 )
2647{
2648 return StrAllocStringMapInvariant(pscz, wzSource, cchSource, LCMAP_LOWERCASE);
2649}
2650
2651/****************************************************************************
2652StrArrayAllocString - Allocates a string array.
2653
2654****************************************************************************/
2655extern "C" HRESULT DAPI StrArrayAllocString(
2656 __deref_inout_ecount_opt(*pcStrArray) LPWSTR **prgsczStrArray,
2657 __inout LPUINT pcStrArray,
2658 __in_z LPCWSTR wzSource,
2659 __in SIZE_T cchSource
2660 )
2661{
2662 HRESULT hr = S_OK;
2663 UINT cNewStrArray;
2664
2665 hr = ::UIntAdd(*pcStrArray, 1, &cNewStrArray);
2666 StrExitOnFailure(hr, "Failed to increment the string array element count.");
2667
2668 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(prgsczStrArray), cNewStrArray, sizeof(LPWSTR), ARRAY_GROWTH_SIZE);
2669 StrExitOnFailure(hr, "Failed to allocate memory for the string array.");
2670
2671 hr = StrAllocString(&(*prgsczStrArray)[*pcStrArray], wzSource, cchSource);
2672 StrExitOnFailure(hr, "Failed to allocate and assign the string.");
2673
2674 *pcStrArray = cNewStrArray;
2675
2676LExit:
2677 return hr;
2678}
2679
2680/****************************************************************************
2681StrArrayFree - Frees a string array.
2682
2683Use ReleaseNullStrArray to nullify the arguments.
2684
2685****************************************************************************/
2686extern "C" HRESULT DAPI StrArrayFree(
2687 __in_ecount(cStrArray) LPWSTR *rgsczStrArray,
2688 __in UINT cStrArray
2689 )
2690{
2691 HRESULT hr = S_OK;
2692
2693 for (UINT i = 0; i < cStrArray; ++i)
2694 {
2695 if (NULL != rgsczStrArray[i])
2696 {
2697 hr = StrFree(rgsczStrArray[i]);
2698 StrExitOnFailure(hr, "Failed to free the string at index %u.", i);
2699 }
2700 }
2701
2702 hr = MemFree(rgsczStrArray);
2703 StrExitOnFailure(hr, "Failed to free memory for the string array.");
2704
2705LExit:
2706 return hr;
2707}
2708
2709/****************************************************************************
2710StrSplitAllocArray - Splits a string into an array.
2711
2712****************************************************************************/
2713extern "C" HRESULT DAPI StrSplitAllocArray(
2714 __deref_inout_ecount_opt(*pcStrArray) LPWSTR **prgsczStrArray,
2715 __inout LPUINT pcStrArray,
2716 __in_z LPCWSTR wzSource,
2717 __in_z LPCWSTR wzDelim
2718 )
2719{
2720 HRESULT hr = S_OK;
2721 LPWSTR sczCopy = NULL;
2722 LPWSTR wzContext = NULL;
2723
2724 // Copy wzSource so it is not modified.
2725 hr = StrAllocString(&sczCopy, wzSource, 0);
2726 StrExitOnFailure(hr, "Failed to copy the source string.");
2727
2728 for (LPCWSTR wzToken = ::wcstok_s(sczCopy, wzDelim, &wzContext); wzToken; wzToken = ::wcstok_s(NULL, wzDelim, &wzContext))
2729 {
2730 hr = StrArrayAllocString(prgsczStrArray, pcStrArray, wzToken, 0);
2731 StrExitOnFailure(hr, "Failed to add the string to the string array.");
2732 }
2733
2734LExit:
2735 ReleaseStr(sczCopy);
2736
2737 return hr;
2738}
2739
2740/****************************************************************************
2741StrAllocStringMapInvariant - helper function for the ToUpper and ToLower.
2742
2743Note: Assumes source and destination buffers will be the same.
2744****************************************************************************/
2745static HRESULT StrAllocStringMapInvariant(
2746 __deref_out_z LPWSTR* pscz,
2747 __in_z LPCWSTR wzSource,
2748 __in SIZE_T cchSource,
2749 __in DWORD dwMapFlags
2750 )
2751{
2752 HRESULT hr = S_OK;
2753
2754 hr = StrAllocString(pscz, wzSource, cchSource);
2755 StrExitOnFailure(hr, "Failed to allocate a copy of the source string.");
2756
2757 if (0 == cchSource)
2758 {
2759 // Need the actual string size for LCMapString. This includes the null-terminator
2760 // but LCMapString doesn't care either way.
2761 hr = ::StringCchLengthW(*pscz, INT_MAX, reinterpret_cast<size_t*>(&cchSource));
2762 StrExitOnRootFailure(hr, "Failed to get the length of the string.");
2763 }
2764 else if (INT_MAX < cchSource)
2765 {
2766 StrExitOnRootFailure(hr = E_INVALIDARG, "Source string is too long: %Iu", cchSource);
2767 }
2768
2769 // Convert the copy of the string to upper or lower case in-place.
2770 if (0 == ::LCMapStringW(LOCALE_INVARIANT, dwMapFlags, *pscz, static_cast<int>(cchSource), *pscz, static_cast<int>(cchSource)))
2771 {
2772 StrExitWithLastError(hr, "Failed to convert the string case.");
2773 }
2774
2775LExit:
2776 return hr;
2777}
2778
2779/****************************************************************************
2780StrSecureZeroString - zeroes out string to the make sure the contents
2781don't remain in memory.
2782
2783****************************************************************************/
2784extern "C" DAPI_(HRESULT) StrSecureZeroString(
2785 __in LPWSTR pwz
2786 )
2787{
2788 HRESULT hr = S_OK;
2789 SIZE_T cch;
2790
2791 if (pwz)
2792 {
2793 cch = MemSize(pwz);
2794 if (-1 == cch)
2795 {
2796 hr = E_INVALIDARG;
2797 StrExitOnFailure(hr, "Failed to get size of string");
2798 }
2799 else
2800 {
2801 SecureZeroMemory(pwz, cch);
2802 }
2803 }
2804
2805LExit:
2806 return hr;
2807}
2808
2809/****************************************************************************
2810StrSecureZeroFreeString - zeroes out string to the make sure the contents
2811don't remain in memory, then frees the string.
2812
2813****************************************************************************/
2814extern "C" DAPI_(HRESULT) StrSecureZeroFreeString(
2815 __in LPWSTR pwz
2816 )
2817{
2818 HRESULT hr = S_OK;
2819
2820 hr = StrSecureZeroString(pwz);
2821 ReleaseStr(pwz);
2822
2823 return hr;
2824}
diff --git a/src/libs/dutil/WixToolset.DUtil/svcutil.cpp b/src/libs/dutil/WixToolset.DUtil/svcutil.cpp
new file mode 100644
index 00000000..1a39bfee
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/svcutil.cpp
@@ -0,0 +1,59 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define SvcExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_SVCUTIL, x, s, __VA_ARGS__)
8#define SvcExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_SVCUTIL, x, s, __VA_ARGS__)
9#define SvcExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_SVCUTIL, x, s, __VA_ARGS__)
10#define SvcExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_SVCUTIL, x, s, __VA_ARGS__)
11#define SvcExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_SVCUTIL, x, s, __VA_ARGS__)
12#define SvcExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_SVCUTIL, x, s, __VA_ARGS__)
13#define SvcExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_SVCUTIL, p, x, e, s, __VA_ARGS__)
14#define SvcExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_SVCUTIL, p, x, s, __VA_ARGS__)
15#define SvcExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_SVCUTIL, p, x, e, s, __VA_ARGS__)
16#define SvcExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_SVCUTIL, p, x, s, __VA_ARGS__)
17#define SvcExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_SVCUTIL, e, x, s, __VA_ARGS__)
18#define SvcExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_SVCUTIL, g, x, s, __VA_ARGS__)
19
20/********************************************************************
21SvcQueryConfig - queries the configuration of a service
22
23********************************************************************/
24extern "C" HRESULT DAPI SvcQueryConfig(
25 __in SC_HANDLE sch,
26 __out QUERY_SERVICE_CONFIGW** ppConfig
27 )
28{
29 HRESULT hr = S_OK;
30 QUERY_SERVICE_CONFIGW* pConfig = NULL;
31 DWORD cbConfig = 0;
32
33 if (!::QueryServiceConfigW(sch, NULL, 0, &cbConfig))
34 {
35 DWORD er = ::GetLastError();
36 if (ERROR_INSUFFICIENT_BUFFER == er)
37 {
38 pConfig = static_cast<QUERY_SERVICE_CONFIGW*>(MemAlloc(cbConfig, TRUE));
39 SvcExitOnNull(pConfig, hr, E_OUTOFMEMORY, "Failed to allocate memory to get configuration.");
40
41 if (!::QueryServiceConfigW(sch, pConfig, cbConfig, &cbConfig))
42 {
43 SvcExitWithLastError(hr, "Failed to read service configuration.");
44 }
45 }
46 else
47 {
48 SvcExitOnWin32Error(er, hr, "Failed to query service configuration.");
49 }
50 }
51
52 *ppConfig = pConfig;
53 pConfig = NULL;
54
55LExit:
56 ReleaseMem(pConfig);
57
58 return hr;
59}
diff --git a/src/libs/dutil/WixToolset.DUtil/thmutil.cpp b/src/libs/dutil/WixToolset.DUtil/thmutil.cpp
new file mode 100644
index 00000000..d200a0ea
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/thmutil.cpp
@@ -0,0 +1,5709 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define ThmExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_THMUTIL, x, s, __VA_ARGS__)
8#define ThmExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_THMUTIL, x, s, __VA_ARGS__)
9#define ThmExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_THMUTIL, x, s, __VA_ARGS__)
10#define ThmExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_THMUTIL, x, s, __VA_ARGS__)
11#define ThmExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_THMUTIL, x, s, __VA_ARGS__)
12#define ThmExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_THMUTIL, x, s, __VA_ARGS__)
13#define ThmExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_THMUTIL, p, x, e, s, __VA_ARGS__)
14#define ThmExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_THMUTIL, p, x, s, __VA_ARGS__)
15#define ThmExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_THMUTIL, p, x, e, s, __VA_ARGS__)
16#define ThmExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_THMUTIL, p, x, s, __VA_ARGS__)
17#define ThmExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_THMUTIL, e, x, s, __VA_ARGS__)
18#define ThmExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_THMUTIL, g, x, s, __VA_ARGS__)
19
20// from CommCtrl.h
21#ifndef BS_COMMANDLINK
22#define BS_COMMANDLINK 0x0000000EL
23#endif
24
25#ifndef BCM_SETNOTE
26#define BCM_SETNOTE (BCM_FIRST + 0x0009)
27#endif
28
29#ifndef BCM_SETSHIELD
30#define BCM_SETSHIELD (BCM_FIRST + 0x000C)
31#endif
32
33#ifndef LWS_NOPREFIX
34#define LWS_NOPREFIX 0x0004
35#endif
36
37const DWORD THEME_INVALID_ID = 0xFFFFFFFF;
38const COLORREF THEME_INVISIBLE_COLORREF = 0xFFFFFFFF;
39const DWORD GROW_FONT_INSTANCES = 3;
40const DWORD GROW_WINDOW_TEXT = 250;
41const LPCWSTR THEME_WC_HYPERLINK = L"ThemeHyperLink";
42const LPCWSTR THEME_WC_PANEL = L"ThemePanel";
43const LPCWSTR THEME_WC_STATICOWNERDRAW = L"ThemeStaticOwnerDraw";
44
45static Gdiplus::GdiplusStartupInput vgsi;
46static Gdiplus::GdiplusStartupOutput vgso = { };
47static ULONG_PTR vgdiToken = 0;
48static ULONG_PTR vgdiHookToken = 0;
49static HMODULE vhHyperlinkRegisteredModule = NULL;
50static HMODULE vhPanelRegisteredModule = NULL;
51static HMODULE vhStaticOwnerDrawRegisteredModule = NULL;
52static WNDPROC vpfnStaticOwnerDrawBaseWndProc = NULL;
53static HMODULE vhModuleMsftEdit = NULL;
54static HMODULE vhModuleRichEd = NULL;
55static HCURSOR vhCursorHand = NULL;
56
57enum INTERNAL_CONTROL_STYLE
58{
59 INTERNAL_CONTROL_STYLE_HIDE_WHEN_DISABLED = 0x0001,
60 INTERNAL_CONTROL_STYLE_FILESYSTEM_AUTOCOMPLETE = 0x0002,
61 INTERNAL_CONTROL_STYLE_DISABLED = 0x0004,
62 INTERNAL_CONTROL_STYLE_HIDDEN = 0x0008,
63 INTERNAL_CONTROL_STYLE_OWNER_DRAW = 0x0010,
64};
65
66struct MEMBUFFER_FOR_RICHEDIT
67{
68 BYTE* rgbData;
69 DWORD cbData;
70
71 DWORD iData;
72};
73
74
75// prototypes
76static HRESULT RegisterWindowClasses(
77 __in_opt HMODULE hModule
78 );
79static HRESULT ParseTheme(
80 __in_opt HMODULE hModule,
81 __in_opt LPCWSTR wzRelativePath,
82 __in IXMLDOMDocument* pixd,
83 __out THEME** ppTheme
84 );
85static HRESULT ParseImage(
86 __in_opt HMODULE hModule,
87 __in_z_opt LPCWSTR wzRelativePath,
88 __in IXMLDOMNode* pElement,
89 __out HBITMAP* phImage
90 );
91static HRESULT ParseIcon(
92 __in_opt HMODULE hModule,
93 __in_z_opt LPCWSTR wzRelativePath,
94 __in IXMLDOMNode* pElement,
95 __out HICON* phIcon
96 );
97static HRESULT ParseWindow(
98 __in_opt HMODULE hModule,
99 __in_opt LPCWSTR wzRelativePath,
100 __in IXMLDOMElement* pElement,
101 __in THEME* pTheme
102 );
103static HRESULT GetFontColor(
104 __in IXMLDOMNode* pixn,
105 __in_z LPCWSTR wzAttributeName,
106 __out COLORREF* pColorRef,
107 __out DWORD* pdwSystemColor
108 );
109static HRESULT ParseFonts(
110 __in IXMLDOMElement* pElement,
111 __in THEME* pTheme
112 );
113static HRESULT ParsePages(
114 __in_opt HMODULE hModule,
115 __in_opt LPCWSTR wzRelativePath,
116 __in IXMLDOMNode* pElement,
117 __in THEME* pTheme
118 );
119static HRESULT ParseImageLists(
120 __in_opt HMODULE hModule,
121 __in_opt LPCWSTR wzRelativePath,
122 __in IXMLDOMNode* pElement,
123 __in THEME* pTheme
124 );
125static HRESULT ParseControls(
126 __in_opt HMODULE hModule,
127 __in_opt LPCWSTR wzRelativePath,
128 __in IXMLDOMNode* pElement,
129 __in THEME* pTheme,
130 __in_opt THEME_CONTROL* pParentControl,
131 __in_opt THEME_PAGE* pPage
132 );
133static HRESULT ParseControl(
134 __in_opt HMODULE hModule,
135 __in_opt LPCWSTR wzRelativePath,
136 __in IXMLDOMNode* pixn,
137 __in THEME* pTheme,
138 __in THEME_CONTROL* pControl,
139 __in BOOL fSkipDimensions,
140 __in_opt THEME_PAGE* pPage
141 );
142static HRESULT ParseActions(
143 __in IXMLDOMNode* pixn,
144 __in THEME_CONTROL* pControl
145 );
146static HRESULT ParseColumns(
147 __in IXMLDOMNode* pixn,
148 __in THEME_CONTROL* pControl
149 );
150static HRESULT ParseRadioButtons(
151 __in_opt HMODULE hModule,
152 __in_opt LPCWSTR wzRelativePath,
153 __in IXMLDOMNode* pixn,
154 __in THEME* pTheme,
155 __in_opt THEME_CONTROL* pParentControl,
156 __in THEME_PAGE* pPage
157 );
158static HRESULT ParseTabs(
159 __in IXMLDOMNode* pixn,
160 __in THEME_CONTROL* pControl
161 );
162static HRESULT ParseText(
163 __in IXMLDOMNode* pixn,
164 __in THEME_CONTROL* pControl,
165 __inout BOOL* pfAnyChildren
166);
167static HRESULT ParseTooltips(
168 __in IXMLDOMNode* pixn,
169 __in THEME_CONTROL* pControl,
170 __inout BOOL* pfAnyChildren
171 );
172static HRESULT ParseNotes(
173 __in IXMLDOMNode* pixn,
174 __in THEME_CONTROL* pControl,
175 __out BOOL* pfAnyChildren
176 );
177static HRESULT StopBillboard(
178 __in THEME* pTheme,
179 __in DWORD dwControl
180 );
181static HRESULT StartBillboard(
182 __in THEME* pTheme,
183 __in DWORD dwControl
184 );
185static HRESULT EnsureFontInstance(
186 __in THEME* pTheme,
187 __in THEME_FONT* pFont,
188 __out THEME_FONT_INSTANCE** ppFontInstance
189 );
190static HRESULT FindImageList(
191 __in THEME* pTheme,
192 __in_z LPCWSTR wzImageListName,
193 __out HIMAGELIST *phImageList
194 );
195static HRESULT LoadControls(
196 __in THEME* pTheme,
197 __in_opt THEME_CONTROL* pParentControl,
198 __in_ecount_opt(cAssignControlIds) const THEME_ASSIGN_CONTROL_ID* rgAssignControlIds,
199 __in DWORD cAssignControlIds
200 );
201static HRESULT ShowControl(
202 __in THEME* pTheme,
203 __in THEME_CONTROL* pControl,
204 __in int nCmdShow,
205 __in BOOL fSaveEditboxes,
206 __in THEME_SHOW_PAGE_REASON reason,
207 __in DWORD dwPageId,
208 __out_opt HWND* phwndFocus
209 );
210static HRESULT ShowControls(
211 __in THEME* pTheme,
212 __in_opt const THEME_CONTROL* pParentControl,
213 __in int nCmdShow,
214 __in BOOL fSaveEditboxes,
215 __in THEME_SHOW_PAGE_REASON reason,
216 __in DWORD dwPageId
217 );
218static HRESULT DrawButton(
219 __in THEME* pTheme,
220 __in DRAWITEMSTRUCT* pdis,
221 __in const THEME_CONTROL* pControl
222 );
223static void DrawControlText(
224 __in THEME* pTheme,
225 __in DRAWITEMSTRUCT* pdis,
226 __in const THEME_CONTROL* pControl,
227 __in BOOL fCentered,
228 __in BOOL fDrawFocusRect
229 );
230static HRESULT DrawHyperlink(
231 __in THEME* pTheme,
232 __in DRAWITEMSTRUCT* pdis,
233 __in const THEME_CONTROL* pControl
234 );
235static HRESULT DrawImage(
236 __in THEME* pTheme,
237 __in DRAWITEMSTRUCT* pdis,
238 __in const THEME_CONTROL* pControl
239 );
240static HRESULT DrawProgressBar(
241 __in THEME* pTheme,
242 __in DRAWITEMSTRUCT* pdis,
243 __in const THEME_CONTROL* pControl
244 );
245static BOOL DrawHoverControl(
246 __in THEME* pTheme,
247 __in BOOL fHover
248 );
249static DWORD CALLBACK RichEditStreamFromFileHandleCallback(
250 __in DWORD_PTR dwCookie,
251 __in_bcount(cb) LPBYTE pbBuff,
252 __in LONG cb,
253 __in LONG *pcb
254 );
255static DWORD CALLBACK RichEditStreamFromMemoryCallback(
256 __in DWORD_PTR dwCookie,
257 __in_bcount(cb) LPBYTE pbBuff,
258 __in LONG cb,
259 __in LONG *pcb
260 );
261static void FreeFontInstance(
262 __in THEME_FONT_INSTANCE* pFontInstance
263 );
264static void FreeFont(
265 __in THEME_FONT* pFont
266 );
267static void FreePage(
268 __in THEME_PAGE* pPage
269 );
270static void FreeControl(
271 __in THEME_CONTROL* pControl
272 );
273static void FreeConditionalText(
274 __in THEME_CONDITIONAL_TEXT* pConditionalText
275 );
276static void FreeImageList(
277 __in THEME_IMAGELIST* pImageList
278 );
279static void FreeAction(
280 __in THEME_ACTION* pAction
281 );
282static void FreeColumn(
283 __in THEME_COLUMN* pColumn
284 );
285static void FreeTab(
286 __in THEME_TAB* pTab
287 );
288static void CALLBACK OnBillboardTimer(
289 __in THEME* pTheme,
290 __in HWND hwnd,
291 __in UINT_PTR idEvent
292 );
293static void OnBrowseDirectory(
294 __in THEME* pTheme,
295 __in HWND hWnd,
296 __in const THEME_ACTION* pAction
297 );
298static BOOL OnButtonClicked(
299 __in THEME* pTheme,
300 __in HWND hWnd,
301 __in const THEME_CONTROL* pControl
302 );
303static BOOL OnDpiChanged(
304 __in THEME* pTheme,
305 __in WPARAM wParam,
306 __in LPARAM lParam
307 );
308static void OnNcCreate(
309 __in THEME* pTheme,
310 __in HWND hWnd,
311 __in LPARAM lParam
312 );
313static HRESULT OnRichEditEnLink(
314 __in LPARAM lParam,
315 __in HWND hWndRichEdit,
316 __in HWND hWnd
317 );
318static BOOL ControlIsType(
319 __in const THEME* pTheme,
320 __in DWORD dwControl,
321 __in THEME_CONTROL_TYPE type
322 );
323static const THEME_CONTROL* FindControlFromHWnd(
324 __in const THEME* pTheme,
325 __in HWND hWnd,
326 __in_opt const THEME_CONTROL* pParentControl = NULL
327 );
328static void GetControlDimensions(
329 __in const THEME_CONTROL* pControl,
330 __in const RECT* prcParent,
331 __out int* piWidth,
332 __out int* piHeight,
333 __out int* piX,
334 __out int* piY
335 );
336// Using iWidth as total width of listview, base width of columns, and "Expands" flag on columns
337// calculates final width of each column (storing result in each column's nWidth value)
338static HRESULT SizeListViewColumns(
339 __inout THEME_CONTROL* pControl
340 );
341static LRESULT CALLBACK ControlGroupDefWindowProc(
342 __in_opt THEME* pTheme,
343 __in HWND hWnd,
344 __in UINT uMsg,
345 __in WPARAM wParam,
346 __in LPARAM lParam
347 );
348static LRESULT CALLBACK PanelWndProc(
349 __in HWND hWnd,
350 __in UINT uMsg,
351 __in WPARAM wParam,
352 __in LPARAM lParam
353 );
354static LRESULT CALLBACK StaticOwnerDrawWndProc(
355 __in HWND hWnd,
356 __in UINT uMsg,
357 __in WPARAM wParam,
358 __in LPARAM lParam
359 );
360static HRESULT LocalizeControls(
361 __in DWORD cControls,
362 __in THEME_CONTROL* rgControls,
363 __in const WIX_LOCALIZATION *pWixLoc
364 );
365static HRESULT LocalizeControl(
366 __in THEME_CONTROL* pControl,
367 __in const WIX_LOCALIZATION *pWixLoc
368 );
369static HRESULT LoadControlsString(
370 __in DWORD cControls,
371 __in THEME_CONTROL* rgControls,
372 __in HMODULE hResModule
373 );
374static HRESULT LoadControlString(
375 __in THEME_CONTROL* pControl,
376 __in HMODULE hResModule
377 );
378static void ResizeControls(
379 __in DWORD cControls,
380 __in THEME_CONTROL* rgControls,
381 __in const RECT* prcParent
382 );
383static void ResizeControl(
384 __in THEME_CONTROL* pControl,
385 __in const RECT* prcParent
386 );
387static void ScaleThemeFromWindow(
388 __in THEME* pTheme,
389 __in UINT nDpi,
390 __in int x,
391 __in int y
392 );
393static void ScaleTheme(
394 __in THEME* pTheme,
395 __in UINT nDpi,
396 __in int x,
397 __in int y,
398 __in DWORD dwStyle,
399 __in BOOL fMenu,
400 __in DWORD dwExStyle
401 );
402static void ScaleControls(
403 __in THEME* pTheme,
404 __in DWORD cControls,
405 __in THEME_CONTROL* rgControls,
406 __in UINT nDpi
407 );
408static void ScaleControl(
409 __in THEME* pTheme,
410 __in THEME_CONTROL* pControl,
411 __in UINT nDpi
412 );
413static void GetControls(
414 __in THEME* pTheme,
415 __in_opt THEME_CONTROL* pParentControl,
416 __out DWORD** ppcControls,
417 __out THEME_CONTROL*** pprgControls
418 );
419static void GetControls(
420 __in const THEME* pTheme,
421 __in_opt const THEME_CONTROL* pParentControl,
422 __out DWORD& cControls,
423 __out THEME_CONTROL*& rgControls
424 );
425static void UnloadControls(
426 __in DWORD cControls,
427 __in THEME_CONTROL* rgControls
428 );
429
430
431// Public functions.
432
433DAPI_(HRESULT) ThemeInitialize(
434 __in_opt HMODULE hModule
435 )
436{
437 HRESULT hr = S_OK;
438 INITCOMMONCONTROLSEX icex = { };
439
440 DpiuInitialize();
441
442 hr = XmlInitialize();
443 ThmExitOnFailure(hr, "Failed to initialize XML.");
444
445 hr = RegisterWindowClasses(hModule);
446 ThmExitOnFailure(hr, "Failed to register theme window classes.");
447
448 // Initialize GDI+ and common controls.
449 vgsi.SuppressBackgroundThread = TRUE;
450
451 hr = GdipInitialize(&vgsi, &vgdiToken, &vgso);
452 ThmExitOnFailure(hr, "Failed to initialize GDI+.");
453
454 icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
455 icex.dwICC = ICC_STANDARD_CLASSES | ICC_PROGRESS_CLASS | ICC_LISTVIEW_CLASSES | ICC_TREEVIEW_CLASSES | ICC_TAB_CLASSES | ICC_LINK_CLASS;
456 ::InitCommonControlsEx(&icex);
457
458 (*vgso.NotificationHook)(&vgdiHookToken);
459
460LExit:
461 return hr;
462}
463
464
465DAPI_(void) ThemeUninitialize()
466{
467 if (vhModuleMsftEdit)
468 {
469 ::FreeLibrary(vhModuleMsftEdit);
470 vhModuleMsftEdit = NULL;
471 }
472
473 if (vhModuleRichEd)
474 {
475 ::FreeLibrary(vhModuleRichEd);
476 vhModuleRichEd = NULL;
477 }
478
479 if (vhHyperlinkRegisteredModule)
480 {
481 ::UnregisterClassW(THEME_WC_HYPERLINK, vhHyperlinkRegisteredModule);
482 vhHyperlinkRegisteredModule = NULL;
483 }
484
485 if (vhPanelRegisteredModule)
486 {
487 ::UnregisterClassW(THEME_WC_PANEL, vhPanelRegisteredModule);
488 vhPanelRegisteredModule = NULL;
489 }
490
491 if (vhStaticOwnerDrawRegisteredModule)
492 {
493 ::UnregisterClassW(THEME_WC_STATICOWNERDRAW, vhStaticOwnerDrawRegisteredModule);
494 vhStaticOwnerDrawRegisteredModule = NULL;
495 vpfnStaticOwnerDrawBaseWndProc = NULL;
496 }
497
498 if (vgdiToken)
499 {
500 GdipUninitialize(vgdiToken);
501 vgdiToken = 0;
502 }
503
504 XmlUninitialize();
505 DpiuUninitialize();
506}
507
508
509DAPI_(HRESULT) ThemeLoadFromFile(
510 __in_z LPCWSTR wzThemeFile,
511 __out THEME** ppTheme
512 )
513{
514 HRESULT hr = S_OK;
515 IXMLDOMDocument* pixd = NULL;
516 LPWSTR sczRelativePath = NULL;
517
518 hr = XmlLoadDocumentFromFile(wzThemeFile, &pixd);
519 ThmExitOnFailure(hr, "Failed to load theme resource as XML document.");
520
521 hr = PathGetDirectory(wzThemeFile, &sczRelativePath);
522 ThmExitOnFailure(hr, "Failed to get relative path from theme file.");
523
524 hr = ParseTheme(NULL, sczRelativePath, pixd, ppTheme);
525 ThmExitOnFailure(hr, "Failed to parse theme.");
526
527LExit:
528 ReleaseStr(sczRelativePath);
529 ReleaseObject(pixd);
530
531 return hr;
532}
533
534
535DAPI_(HRESULT) ThemeLoadFromResource(
536 __in_opt HMODULE hModule,
537 __in_z LPCSTR szResource,
538 __out THEME** ppTheme
539 )
540{
541 HRESULT hr = S_OK;
542 LPVOID pvResource = NULL;
543 DWORD cbResource = 0;
544 LPWSTR sczXml = NULL;
545 IXMLDOMDocument* pixd = NULL;
546
547 hr = ResReadData(hModule, szResource, &pvResource, &cbResource);
548 ThmExitOnFailure(hr, "Failed to read theme from resource.");
549
550 hr = StrAllocStringAnsi(&sczXml, reinterpret_cast<LPCSTR>(pvResource), cbResource, CP_UTF8);
551 ThmExitOnFailure(hr, "Failed to convert XML document data from UTF-8 to unicode string.");
552
553 hr = XmlLoadDocument(sczXml, &pixd);
554 ThmExitOnFailure(hr, "Failed to load theme resource as XML document.");
555
556 hr = ParseTheme(hModule, NULL, pixd, ppTheme);
557 ThmExitOnFailure(hr, "Failed to parse theme.");
558
559LExit:
560 ReleaseObject(pixd);
561 ReleaseStr(sczXml);
562
563 return hr;
564}
565
566
567DAPI_(void) ThemeFree(
568 __in THEME* pTheme
569 )
570{
571 if (pTheme)
572 {
573 for (DWORD i = 0; i < pTheme->cFonts; ++i)
574 {
575 FreeFont(pTheme->rgFonts + i);
576 }
577
578 for (DWORD i = 0; i < pTheme->cPages; ++i)
579 {
580 FreePage(pTheme->rgPages + i);
581 }
582
583 for (DWORD i = 0; i < pTheme->cImageLists; ++i)
584 {
585 FreeImageList(pTheme->rgImageLists + i);
586 }
587
588 for (DWORD i = 0; i < pTheme->cControls; ++i)
589 {
590 FreeControl(pTheme->rgControls + i);
591 }
592
593 ReleaseMem(pTheme->rgControls);
594 ReleaseMem(pTheme->rgPages);
595 ReleaseMem(pTheme->rgFonts);
596
597 if (pTheme->hImage)
598 {
599 ::DeleteBitmap(pTheme->hImage);
600 }
601
602 ReleaseStr(pTheme->sczCaption);
603 ReleaseMem(pTheme);
604 }
605}
606
607DAPI_(HRESULT) ThemeRegisterVariableCallbacks(
608 __in THEME* pTheme,
609 __in_opt PFNTHM_EVALUATE_VARIABLE_CONDITION pfnEvaluateCondition,
610 __in_opt PFNTHM_FORMAT_VARIABLE_STRING pfnFormatString,
611 __in_opt PFNTHM_GET_VARIABLE_NUMERIC pfnGetNumericVariable,
612 __in_opt PFNTHM_SET_VARIABLE_NUMERIC pfnSetNumericVariable,
613 __in_opt PFNTHM_GET_VARIABLE_STRING pfnGetStringVariable,
614 __in_opt PFNTHM_SET_VARIABLE_STRING pfnSetStringVariable,
615 __in_opt LPVOID pvContext
616 )
617{
618 HRESULT hr = S_OK;
619 ThmExitOnNull(pTheme, hr, S_FALSE, "Theme must be loaded first.");
620
621 pTheme->pfnEvaluateCondition = pfnEvaluateCondition;
622 pTheme->pfnFormatString = pfnFormatString;
623 pTheme->pfnGetNumericVariable = pfnGetNumericVariable;
624 pTheme->pfnSetNumericVariable = pfnSetNumericVariable;
625 pTheme->pfnGetStringVariable = pfnGetStringVariable;
626 pTheme->pfnSetStringVariable = pfnSetStringVariable;
627 pTheme->pvVariableContext = pvContext;
628
629LExit:
630 return hr;
631}
632
633
634DAPI_(HRESULT) ThemeCreateParentWindow(
635 __in THEME* pTheme,
636 __in DWORD dwExStyle,
637 __in LPCWSTR szClassName,
638 __in LPCWSTR szWindowName,
639 __in DWORD dwStyle,
640 __in int x,
641 __in int y,
642 __in_opt HWND hwndParent,
643 __in_opt HINSTANCE hInstance,
644 __in_opt LPVOID lpParam,
645 __in THEME_WINDOW_INITIAL_POSITION initialPosition,
646 __out_opt HWND* phWnd
647 )
648{
649 HRESULT hr = S_OK;
650 DPIU_MONITOR_CONTEXT* pMonitorContext = NULL;
651 POINT pt = { };
652 RECT* pMonitorRect = NULL;
653 HMENU hMenu = NULL;
654 HWND hWnd = NULL;
655
656 if (pTheme->hwndParent)
657 {
658 ThmExitOnFailure(hr = E_INVALIDSTATE, "ThemeCreateParentWindow called after the theme was loaded.");
659 }
660
661 if (THEME_WINDOW_INITIAL_POSITION_CENTER_MONITOR_FROM_COORDINATES == initialPosition)
662 {
663 pt.x = x;
664 pt.y = y;
665 hr = DpiuGetMonitorContextFromPoint(&pt, &pMonitorContext);
666 if (SUCCEEDED(hr))
667 {
668 pMonitorRect = &pMonitorContext->mi.rcWork;
669 if (pMonitorContext->nDpi != pTheme->nDpi)
670 {
671 ScaleTheme(pTheme, pMonitorContext->nDpi, pMonitorRect->left, pMonitorRect->top, dwStyle, NULL != hMenu, dwExStyle);
672 }
673
674 x = pMonitorRect->left + (pMonitorRect->right - pMonitorRect->left - pTheme->nWindowWidth) / 2;
675 y = pMonitorRect->top + (pMonitorRect->bottom - pMonitorRect->top - pTheme->nWindowHeight) / 2;
676 }
677 else
678 {
679 hr = S_OK;
680 x = CW_USEDEFAULT;
681 y = CW_USEDEFAULT;
682 }
683 }
684
685 hWnd = ::CreateWindowExW(dwExStyle, szClassName, szWindowName, dwStyle, x, y, pTheme->nWindowWidth, pTheme->nWindowHeight, hwndParent, hMenu, hInstance, lpParam);
686 ThmExitOnNullWithLastError(hWnd, hr, "Failed to create theme parent window.");
687 ThmExitOnNull(pTheme->hwndParent, hr, E_INVALIDSTATE, "Theme parent window is not set, make sure ThemeDefWindowProc is called for WM_NCCREATE.");
688 AssertSz(hWnd == pTheme->hwndParent, "Theme parent window does not equal newly created window.");
689
690 if (phWnd)
691 {
692 *phWnd = hWnd;
693 }
694
695LExit:
696 ReleaseMem(pMonitorContext);
697
698 return hr;
699}
700
701
702DAPI_(HRESULT) ThemeLoadControls(
703 __in THEME* pTheme,
704 __in_ecount_opt(cAssignControlIds) const THEME_ASSIGN_CONTROL_ID* rgAssignControlIds,
705 __in DWORD cAssignControlIds
706 )
707{
708 HRESULT hr = S_OK;
709
710 if (!pTheme->hwndParent)
711 {
712 ThmExitOnFailure(hr = E_INVALIDSTATE, "ThemeLoadControls called before theme parent window created.");
713 }
714
715 hr = LoadControls(pTheme, NULL, rgAssignControlIds, cAssignControlIds);
716
717LExit:
718 return hr;
719}
720
721
722DAPI_(void) ThemeUnloadControls(
723 __in THEME* pTheme
724 )
725{
726 UnloadControls(pTheme->cControls, pTheme->rgControls);
727
728 pTheme->hwndHover = NULL;
729 pTheme->hwndParent = NULL;
730}
731
732DAPI_(HRESULT) ThemeLocalize(
733 __in THEME *pTheme,
734 __in const WIX_LOCALIZATION *pWixLoc
735 )
736{
737 HRESULT hr = S_OK;
738 LPWSTR sczCaption = NULL;
739
740 hr = LocLocalizeString(pWixLoc, &pTheme->sczCaption);
741 ThmExitOnFailure(hr, "Failed to localize theme caption.");
742
743 if (pTheme->pfnFormatString)
744 {
745 hr = pTheme->pfnFormatString(pTheme->sczCaption, &sczCaption, pTheme->pvVariableContext);
746 if (SUCCEEDED(hr))
747 {
748 hr = ThemeUpdateCaption(pTheme, sczCaption);
749 }
750 }
751
752 hr = LocalizeControls(pTheme->cControls, pTheme->rgControls, pWixLoc);
753
754LExit:
755 ReleaseStr(sczCaption);
756
757 return hr;
758}
759
760/********************************************************************
761 ThemeLoadStrings - Loads string resources.
762 Must be called after loading a theme and before calling
763 ThemeLoadControls.
764*******************************************************************/
765DAPI_(HRESULT) ThemeLoadStrings(
766 __in THEME* pTheme,
767 __in HMODULE hResModule
768 )
769{
770 HRESULT hr = S_OK;
771 ThmExitOnNull(pTheme, hr, S_FALSE, "Theme must be loaded first.");
772
773 if (UINT_MAX != pTheme->uStringId)
774 {
775 hr = ResReadString(hResModule, pTheme->uStringId, &pTheme->sczCaption);
776 ThmExitOnFailure(hr, "Failed to load theme caption.");
777 }
778
779 hr = LoadControlsString(pTheme->cControls, pTheme->rgControls, hResModule);
780
781LExit:
782 return hr;
783}
784
785
786DAPI_(HRESULT) ThemeLoadRichEditFromFile(
787 __in THEME* pTheme,
788 __in DWORD dwControl,
789 __in_z LPCWSTR wzFileName,
790 __in HMODULE hModule
791 )
792{
793 HRESULT hr = S_OK;
794 LPWSTR sczFile = NULL;
795 HANDLE hFile = INVALID_HANDLE_VALUE;
796 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
797
798 hr = PathRelativeToModule(&sczFile, wzFileName, hModule);
799 ThmExitOnFailure(hr, "Failed to read resource data.");
800
801 hFile = ::CreateFileW(sczFile, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
802 if (INVALID_HANDLE_VALUE == hFile)
803 {
804 ThmExitWithLastError(hr, "Failed to open RTF file.");
805 }
806 else
807 {
808 LONGLONG llRtfSize;
809 hr = FileSizeByHandle(hFile, &llRtfSize);
810 if (SUCCEEDED(hr))
811 {
812 ::SendMessageW(hWnd, EM_EXLIMITTEXT, 0, static_cast<LPARAM>(llRtfSize));
813 }
814
815 EDITSTREAM es = { };
816 es.pfnCallback = RichEditStreamFromFileHandleCallback;
817 es.dwCookie = reinterpret_cast<DWORD_PTR>(hFile);
818
819 ::SendMessageW(hWnd, EM_STREAMIN, SF_RTF, reinterpret_cast<LPARAM>(&es));
820 hr = es.dwError;
821 ThmExitOnFailure(hr, "Failed to update RTF stream.");
822 }
823
824LExit:
825 ReleaseStr(sczFile);
826 ReleaseFile(hFile);
827
828 return hr;
829}
830
831
832DAPI_(HRESULT) ThemeLoadRichEditFromResource(
833 __in THEME* pTheme,
834 __in DWORD dwControl,
835 __in_z LPCSTR szResourceName,
836 __in HMODULE hModule
837 )
838{
839 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
840 return ThemeLoadRichEditFromResourceToHWnd(hWnd, szResourceName, hModule);
841}
842
843DAPI_(HRESULT) ThemeLoadRichEditFromResourceToHWnd(
844 __in HWND hWnd,
845 __in_z LPCSTR szResourceName,
846 __in HMODULE hModule
847 )
848{
849 HRESULT hr = S_OK;
850 MEMBUFFER_FOR_RICHEDIT buffer = { };
851 EDITSTREAM es = { };
852
853 hr = ResReadData(hModule, szResourceName, reinterpret_cast<LPVOID*>(&buffer.rgbData), &buffer.cbData);
854 ThmExitOnFailure(hr, "Failed to read resource data.");
855
856 es.pfnCallback = RichEditStreamFromMemoryCallback;
857 es.dwCookie = reinterpret_cast<DWORD_PTR>(&buffer);
858
859 ::SendMessageW(hWnd, EM_STREAMIN, SF_RTF, reinterpret_cast<LPARAM>(&es));
860 hr = es.dwError;
861 ThmExitOnFailure(hr, "Failed to update RTF stream.");
862
863LExit:
864 return hr;
865}
866
867
868DAPI_(BOOL) ThemeHandleKeyboardMessage(
869 __in_opt THEME* pTheme,
870 __in HWND /*hWnd*/,
871 __in MSG* pMsg
872 )
873{
874 return pTheme ? ::IsDialogMessageW(pTheme->hwndParent, pMsg) : FALSE;
875}
876
877
878extern "C" LRESULT CALLBACK ThemeDefWindowProc(
879 __in_opt THEME* pTheme,
880 __in HWND hWnd,
881 __in UINT uMsg,
882 __in WPARAM wParam,
883 __in LPARAM lParam
884 )
885{
886 RECT rcParent = { };
887 RECT *pRect = NULL;
888
889 if (pTheme)
890 {
891 switch (uMsg)
892 {
893 case WM_NCCREATE:
894 if (pTheme->hwndParent)
895 {
896 AssertSz(FALSE, "WM_NCCREATE called multiple times");
897 }
898 else
899 {
900 OnNcCreate(pTheme, hWnd, lParam);
901 }
902 break;
903
904 case WM_NCHITTEST:
905 if (pTheme->dwStyle & WS_POPUP)
906 {
907 return HTCAPTION; // allow pop-up windows to be moved by grabbing any non-control.
908 }
909 break;
910
911 case WM_DPICHANGED:
912 if (OnDpiChanged(pTheme, wParam, lParam))
913 {
914 return 0;
915 }
916 break;
917
918 case WM_SIZING:
919 if (pTheme->fAutoResize)
920 {
921 pRect = reinterpret_cast<RECT *>(lParam);
922 if (pRect->right - pRect->left < pTheme->nMinimumWidth)
923 {
924 if (wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_LEFT || wParam == WMSZ_TOPLEFT)
925 {
926 pRect->left = pRect->right - pTheme->nMinimumWidth;
927 }
928 else
929 {
930 pRect->right = pRect->left + pTheme->nMinimumWidth;
931 }
932 }
933 if (pRect->bottom - pRect->top < pTheme->nMinimumHeight)
934 {
935 if (wParam == WMSZ_BOTTOM || wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_BOTTOMRIGHT)
936 {
937 pRect->bottom = pRect->top + pTheme->nMinimumHeight;
938 }
939 else
940 {
941 pRect->top = pRect->bottom - pTheme->nMinimumHeight;
942 }
943 }
944
945 return TRUE;
946 }
947 break;
948
949 case WM_SIZE:
950 if (pTheme->fAutoResize || pTheme->fForceResize)
951 {
952 pTheme->fForceResize = FALSE;
953 ::GetClientRect(pTheme->hwndParent, &rcParent);
954 ResizeControls(pTheme->cControls, pTheme->rgControls, &rcParent);
955 return 0;
956 }
957 break;
958 }
959 }
960
961 return ControlGroupDefWindowProc(pTheme, hWnd, uMsg, wParam, lParam);
962}
963
964
965DAPI_(void) ThemeGetPageIds(
966 __in const THEME* pTheme,
967 __in_ecount(cGetPages) LPCWSTR* rgwzFindNames,
968 __inout_ecount(cGetPages) DWORD* rgdwPageIds,
969 __in DWORD cGetPages
970 )
971{
972 for (DWORD i = 0; i < cGetPages; ++i)
973 {
974 LPCWSTR wzFindName = rgwzFindNames[i];
975 for (DWORD j = 0; j < pTheme->cPages; ++j)
976 {
977 LPCWSTR wzPageName = pTheme->rgPages[j].sczName;
978 if (wzPageName && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzPageName, -1, wzFindName, -1))
979 {
980 rgdwPageIds[i] = j + 1; // add one to make the page ids 1-based (so zero is invalid).
981 break;
982 }
983 }
984 }
985}
986
987
988DAPI_(THEME_PAGE*) ThemeGetPage(
989 __in const THEME* pTheme,
990 __in DWORD dwPage
991 )
992{
993 DWORD iPage = dwPage - 1;
994 THEME_PAGE* pPage = NULL;
995
996 if (iPage < pTheme->cPages)
997 {
998 pPage = pTheme->rgPages + iPage;
999 }
1000
1001 return pPage;
1002}
1003
1004
1005DAPI_(HRESULT) ThemeShowPage(
1006 __in THEME* pTheme,
1007 __in DWORD dwPage,
1008 __in int nCmdShow
1009 )
1010{
1011 return ThemeShowPageEx(pTheme, dwPage, nCmdShow, THEME_SHOW_PAGE_REASON_DEFAULT);
1012}
1013
1014
1015DAPI_(HRESULT) ThemeShowPageEx(
1016 __in THEME* pTheme,
1017 __in DWORD dwPage,
1018 __in int nCmdShow,
1019 __in THEME_SHOW_PAGE_REASON reason
1020 )
1021{
1022 HRESULT hr = S_OK;
1023 BOOL fHide = SW_HIDE == nCmdShow;
1024 BOOL fSaveEditboxes = FALSE;
1025 THEME_SAVEDVARIABLE* pSavedVariable = NULL;
1026 THEME_PAGE* pPage = ThemeGetPage(pTheme, dwPage);
1027
1028 if (pPage)
1029 {
1030 if (fHide)
1031 {
1032 switch (reason)
1033 {
1034 case THEME_SHOW_PAGE_REASON_DEFAULT:
1035 // Set the variables in the loop below.
1036 fSaveEditboxes = TRUE;
1037 break;
1038 case THEME_SHOW_PAGE_REASON_CANCEL:
1039 if (pPage->cSavedVariables && pTheme->pfnSetStringVariable)
1040 {
1041 // Best effort to cancel any changes to the variables.
1042 for (DWORD v = 0; v < pPage->cSavedVariables; ++v)
1043 {
1044 pSavedVariable = pPage->rgSavedVariables + v;
1045 if (pSavedVariable->wzName)
1046 {
1047 pTheme->pfnSetStringVariable(pSavedVariable->wzName, pSavedVariable->sczValue, FALSE, pTheme->pvVariableContext);
1048 }
1049 }
1050 }
1051 break;
1052 }
1053
1054 if (THEME_SHOW_PAGE_REASON_REFRESH != reason)
1055 {
1056 pPage->cSavedVariables = 0;
1057 if (pPage->rgSavedVariables)
1058 {
1059 SecureZeroMemory(pPage->rgSavedVariables, MemSize(pPage->rgSavedVariables));
1060 }
1061 }
1062
1063 pTheme->dwCurrentPageId = 0;
1064 }
1065 else
1066 {
1067 if (THEME_SHOW_PAGE_REASON_REFRESH == reason)
1068 {
1069 fSaveEditboxes = TRUE;
1070 }
1071 else
1072 {
1073 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&pPage->rgSavedVariables), pPage->cControlIndices, sizeof(THEME_SAVEDVARIABLE), pPage->cControlIndices);
1074 ThmExitOnFailure(hr, "Failed to allocate memory for saved variables.");
1075
1076 SecureZeroMemory(pPage->rgSavedVariables, MemSize(pPage->rgSavedVariables));
1077 pPage->cSavedVariables = pPage->cControlIndices;
1078
1079 // Save the variables in the loop below.
1080 }
1081
1082 pTheme->dwCurrentPageId = dwPage;
1083 }
1084 }
1085
1086 hr = ShowControls(pTheme, NULL, nCmdShow, fSaveEditboxes, reason, dwPage);
1087 ThmExitOnFailure(hr, "Failed to show page controls.");
1088
1089LExit:
1090 return hr;
1091}
1092
1093
1094DAPI_(BOOL) ThemeControlExists(
1095 __in const THEME* pTheme,
1096 __in DWORD dwControl
1097 )
1098{
1099 BOOL fExists = FALSE;
1100 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1101 if (hWnd)
1102 {
1103 const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, hWnd);
1104 fExists = (pControl && hWnd == pControl->hWnd);
1105 }
1106
1107 return fExists;
1108}
1109
1110
1111DAPI_(void) ThemeControlEnable(
1112 __in THEME* pTheme,
1113 __in DWORD dwControl,
1114 __in BOOL fEnable
1115 )
1116{
1117 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1118 THEME_CONTROL* pControl = const_cast<THEME_CONTROL*>(FindControlFromHWnd(pTheme, hWnd));
1119 if (pControl)
1120 {
1121 pControl->dwInternalStyle = fEnable ? (pControl->dwInternalStyle & ~INTERNAL_CONTROL_STYLE_DISABLED) : (pControl->dwInternalStyle | INTERNAL_CONTROL_STYLE_DISABLED);
1122 ::EnableWindow(hWnd, fEnable);
1123
1124 if (pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_HIDE_WHEN_DISABLED)
1125 {
1126 ::ShowWindow(hWnd, fEnable ? SW_SHOW : SW_HIDE);
1127 }
1128 }
1129}
1130
1131
1132DAPI_(BOOL) ThemeControlEnabled(
1133 __in THEME* pTheme,
1134 __in DWORD dwControl
1135 )
1136{
1137 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1138 const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, hWnd);
1139 return pControl && !(pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_DISABLED);
1140}
1141
1142
1143DAPI_(void) ThemeControlElevates(
1144 __in THEME* pTheme,
1145 __in DWORD dwControl,
1146 __in BOOL fElevates
1147 )
1148{
1149 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1150 ::SendMessageW(hWnd, BCM_SETSHIELD, 0, fElevates);
1151}
1152
1153
1154DAPI_(void) ThemeShowControl(
1155 __in THEME* pTheme,
1156 __in DWORD dwControl,
1157 __in int nCmdShow
1158 )
1159{
1160 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1161 ::ShowWindow(hWnd, nCmdShow);
1162
1163 // Save the control's visible state.
1164 THEME_CONTROL* pControl = const_cast<THEME_CONTROL*>(FindControlFromHWnd(pTheme, hWnd));
1165 if (pControl)
1166 {
1167 pControl->dwInternalStyle = (SW_HIDE == nCmdShow) ? (pControl->dwInternalStyle | INTERNAL_CONTROL_STYLE_HIDDEN) : (pControl->dwInternalStyle & ~INTERNAL_CONTROL_STYLE_HIDDEN);
1168 }
1169}
1170
1171
1172DAPI_(void) ThemeShowControlEx(
1173 __in THEME* pTheme,
1174 __in DWORD dwControl,
1175 __in int nCmdShow
1176 )
1177{
1178 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1179 THEME_CONTROL* pControl = const_cast<THEME_CONTROL*>(FindControlFromHWnd(pTheme, hWnd));
1180 if (pControl)
1181 {
1182 ShowControl(pTheme, pControl, nCmdShow, THEME_CONTROL_TYPE_EDITBOX == pControl->type, THEME_SHOW_PAGE_REASON_REFRESH, 0, NULL);
1183 }
1184}
1185
1186
1187DAPI_(BOOL) ThemeControlVisible(
1188 __in THEME* pTheme,
1189 __in DWORD dwControl
1190 )
1191{
1192 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1193 return ::IsWindowVisible(hWnd);
1194}
1195
1196
1197DAPI_(BOOL) ThemePostControlMessage(
1198 __in THEME* pTheme,
1199 __in DWORD dwControl,
1200 __in UINT Msg,
1201 __in WPARAM wParam,
1202 __in LPARAM lParam
1203 )
1204{
1205 HRESULT hr = S_OK;
1206 UINT er = ERROR_SUCCESS;
1207 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1208
1209 if (!::PostMessageW(hWnd, Msg, wParam, lParam))
1210 {
1211 er = ::GetLastError();
1212 hr = HRESULT_FROM_WIN32(er);
1213 }
1214
1215 return SUCCEEDED(hr);
1216}
1217
1218
1219DAPI_(LRESULT) ThemeSendControlMessage(
1220 __in const THEME* pTheme,
1221 __in DWORD dwControl,
1222 __in UINT Msg,
1223 __in WPARAM wParam,
1224 __in LPARAM lParam
1225 )
1226{
1227 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1228 return ::SendMessageW(hWnd, Msg, wParam, lParam);
1229}
1230
1231
1232DAPI_(HRESULT) ThemeDrawBackground(
1233 __in THEME* pTheme,
1234 __in PAINTSTRUCT* pps
1235 )
1236{
1237 HRESULT hr = S_FALSE;
1238
1239 if (pTheme->hImage && 0 <= pTheme->nSourceX && 0 <= pTheme->nSourceY && pps->fErase)
1240 {
1241 HDC hdcMem = ::CreateCompatibleDC(pps->hdc);
1242 HBITMAP hDefaultBitmap = static_cast<HBITMAP>(::SelectObject(hdcMem, pTheme->hImage));
1243 DWORD dwSourceWidth = pTheme->nDefaultDpiWidth;
1244 DWORD dwSourceHeight = pTheme->nDefaultDpiHeight;
1245
1246 ::StretchBlt(pps->hdc, 0, 0, pTheme->nWidth, pTheme->nHeight, hdcMem, pTheme->nSourceX, pTheme->nSourceY, dwSourceWidth, dwSourceHeight, SRCCOPY);
1247
1248 ::SelectObject(hdcMem, hDefaultBitmap);
1249 ::DeleteDC(hdcMem);
1250
1251 hr = S_OK;
1252 }
1253
1254 return hr;
1255}
1256
1257
1258DAPI_(HRESULT) ThemeDrawControl(
1259 __in THEME* pTheme,
1260 __in DRAWITEMSTRUCT* pdis
1261 )
1262{
1263 HRESULT hr = S_OK;
1264 const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, pdis->hwndItem);
1265
1266 AssertSz(pControl, "Expected control window from owner draw window.");
1267 AssertSz(pControl->hWnd == pdis->hwndItem, "Expected control window to match owner draw window.");
1268 AssertSz(pControl->nWidth < 1 || pControl->nWidth == pdis->rcItem.right - pdis->rcItem.left, "Expected control window width to match owner draw window width.");
1269 AssertSz(pControl->nHeight < 1 || pControl->nHeight == pdis->rcItem.bottom - pdis->rcItem.top, "Expected control window height to match owner draw window height.");
1270
1271 switch (pControl->type)
1272 {
1273 case THEME_CONTROL_TYPE_BUTTON:
1274 hr = DrawButton(pTheme, pdis, pControl);
1275 ThmExitOnFailure(hr, "Failed to draw button.");
1276 break;
1277
1278 case THEME_CONTROL_TYPE_HYPERLINK:
1279 hr = DrawHyperlink(pTheme, pdis, pControl);
1280 ThmExitOnFailure(hr, "Failed to draw hyperlink.");
1281 break;
1282
1283 case THEME_CONTROL_TYPE_IMAGE:
1284 hr = DrawImage(pTheme, pdis, pControl);
1285 ThmExitOnFailure(hr, "Failed to draw image.");
1286 break;
1287
1288 case THEME_CONTROL_TYPE_PROGRESSBAR:
1289 hr = DrawProgressBar(pTheme, pdis, pControl);
1290 ThmExitOnFailure(hr, "Failed to draw progress bar.");
1291 break;
1292
1293 default:
1294 hr = E_UNEXPECTED;
1295 ThmExitOnRootFailure(hr, "Did not specify an owner draw control to draw.");
1296 }
1297
1298LExit:
1299 return hr;
1300}
1301
1302
1303DAPI_(BOOL) ThemeHoverControl(
1304 __in THEME* pTheme,
1305 __in HWND hwndParent,
1306 __in HWND hwndControl
1307 )
1308{
1309 BOOL fHovered = FALSE;
1310 if (hwndControl != pTheme->hwndHover)
1311 {
1312 if (pTheme->hwndHover && pTheme->hwndHover != hwndParent)
1313 {
1314 DrawHoverControl(pTheme, FALSE);
1315 }
1316
1317 pTheme->hwndHover = hwndControl;
1318
1319 if (pTheme->hwndHover && pTheme->hwndHover != hwndParent)
1320 {
1321 fHovered = DrawHoverControl(pTheme, TRUE);
1322 }
1323 }
1324
1325 return fHovered;
1326}
1327
1328
1329DAPI_(BOOL) ThemeIsControlChecked(
1330 __in THEME* pTheme,
1331 __in DWORD dwControl
1332 )
1333{
1334 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1335 return BST_CHECKED == ::SendMessageW(hWnd, BM_GETCHECK, 0, 0);
1336}
1337
1338
1339DAPI_(BOOL) ThemeSetControlColor(
1340 __in THEME* pTheme,
1341 __in HDC hdc,
1342 __in HWND hWnd,
1343 __out HBRUSH* phBackgroundBrush
1344 )
1345{
1346 THEME_FONT* pFont = NULL;
1347 BOOL fHasBackground = FALSE;
1348
1349 *phBackgroundBrush = NULL;
1350
1351 if (hWnd == pTheme->hwndParent)
1352 {
1353 pFont = (THEME_INVALID_ID == pTheme->dwFontId) ? NULL : pTheme->rgFonts + pTheme->dwFontId;
1354 }
1355 else
1356 {
1357 const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, hWnd);
1358 pFont = (!pControl || THEME_INVALID_ID == pControl->dwFontId) ? NULL : pTheme->rgFonts + pControl->dwFontId;
1359 }
1360
1361 if (pFont)
1362 {
1363 if (pFont->hForeground)
1364 {
1365 ::SetTextColor(hdc, pFont->crForeground);
1366 }
1367
1368 if (pFont->hBackground)
1369 {
1370 ::SetBkColor(hdc, pFont->crBackground);
1371
1372 *phBackgroundBrush = pFont->hBackground;
1373 fHasBackground = TRUE;
1374 }
1375 else
1376 {
1377 ::SetBkMode(hdc, TRANSPARENT);
1378 *phBackgroundBrush = static_cast<HBRUSH>(::GetStockObject(NULL_BRUSH));
1379 fHasBackground = TRUE;
1380 }
1381 }
1382
1383 return fHasBackground;
1384}
1385
1386
1387DAPI_(HRESULT) ThemeSetProgressControl(
1388 __in THEME* pTheme,
1389 __in DWORD dwControl,
1390 __in DWORD dwProgressPercentage
1391 )
1392{
1393 HRESULT hr = E_NOTFOUND;
1394 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1395
1396 if (hWnd)
1397 {
1398 THEME_CONTROL* pControl = const_cast<THEME_CONTROL*>(FindControlFromHWnd(pTheme, hWnd));
1399 if (pControl && THEME_CONTROL_TYPE_PROGRESSBAR == pControl->type)
1400 {
1401 DWORD dwCurrentProgress = LOWORD(pControl->dwData);
1402
1403 if (dwCurrentProgress != dwProgressPercentage)
1404 {
1405 DWORD dwColor = HIWORD(pControl->dwData);
1406 pControl->dwData = MAKEDWORD(dwProgressPercentage, dwColor);
1407
1408 if (pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_OWNER_DRAW)
1409 {
1410 if (!::InvalidateRect(hWnd, NULL, FALSE))
1411 {
1412 ThmExitWithLastError(hr, "Failed to invalidate progress bar window.");
1413 }
1414 }
1415 else
1416 {
1417 ::SendMessageW(hWnd, PBM_SETPOS, dwProgressPercentage, 0);
1418 }
1419
1420 hr = S_OK;
1421 }
1422 else
1423 {
1424 hr = S_FALSE;
1425 }
1426 }
1427 }
1428
1429LExit:
1430 return hr;
1431}
1432
1433
1434DAPI_(HRESULT) ThemeSetProgressControlColor(
1435 __in THEME* pTheme,
1436 __in DWORD dwControl,
1437 __in DWORD dwColorIndex
1438 )
1439{
1440 HRESULT hr = S_FALSE;
1441 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1442 if (hWnd)
1443 {
1444 THEME_CONTROL* pControl = const_cast<THEME_CONTROL*>(FindControlFromHWnd(pTheme, hWnd));
1445
1446 // Only set color on owner draw progress bars.
1447 if (pControl && (pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_OWNER_DRAW))
1448 {
1449 DWORD dwCurrentColor = HIWORD(pControl->dwData);
1450
1451 if (dwCurrentColor != dwColorIndex)
1452 {
1453 DWORD dwCurrentProgress = LOWORD(pControl->dwData);
1454 pControl->dwData = MAKEDWORD(dwCurrentProgress, dwColorIndex);
1455
1456 if (!::InvalidateRect(hWnd, NULL, FALSE))
1457 {
1458 ThmExitWithLastError(hr, "Failed to invalidate progress bar window.");
1459 }
1460
1461 hr = S_OK;
1462 }
1463 }
1464 }
1465
1466LExit:
1467 return hr;
1468}
1469
1470
1471DAPI_(HRESULT) ThemeSetTextControl(
1472 __in const THEME* pTheme,
1473 __in DWORD dwControl,
1474 __in_z_opt LPCWSTR wzText
1475 )
1476{
1477 return ThemeSetTextControlEx(pTheme, dwControl, FALSE, wzText);
1478}
1479
1480
1481DAPI_(HRESULT) ThemeSetTextControlEx(
1482 __in const THEME* pTheme,
1483 __in DWORD dwControl,
1484 __in BOOL fUpdate,
1485 __in_z_opt LPCWSTR wzText
1486 )
1487{
1488 HRESULT hr = S_OK;
1489 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1490
1491 if (hWnd)
1492 {
1493 if (fUpdate)
1494 {
1495 ::ShowWindow(hWnd, SW_HIDE);
1496 }
1497
1498 if (!::SetWindowTextW(hWnd, wzText))
1499 {
1500 ThmExitWithLastError(hr, "Failed to set control text.");
1501 }
1502
1503 if (fUpdate)
1504 {
1505 ::ShowWindow(hWnd, SW_SHOW);
1506 }
1507 }
1508
1509LExit:
1510 return hr;
1511}
1512
1513
1514DAPI_(HRESULT) ThemeGetTextControl(
1515 __in const THEME* pTheme,
1516 __in DWORD dwControl,
1517 __inout_z LPWSTR* psczText
1518 )
1519{
1520 HRESULT hr = S_OK;
1521 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1522 SIZE_T cbSize = 0;
1523 DWORD cchText = 0;
1524 DWORD cchTextRead = 0;
1525
1526 // Ensure the string has room for at least one character.
1527 hr = StrMaxLength(*psczText, &cbSize);
1528 ThmExitOnFailure(hr, "Failed to get text buffer length.");
1529
1530 cchText = (DWORD)min(DWORD_MAX, cbSize);
1531
1532 if (!cchText)
1533 {
1534 cchText = GROW_WINDOW_TEXT;
1535
1536 hr = StrAlloc(psczText, cchText);
1537 ThmExitOnFailure(hr, "Failed to grow text buffer.");
1538 }
1539
1540 // Read (and keep growing buffer) until we finally read less than there
1541 // is room in the buffer.
1542 for (;;)
1543 {
1544 cchTextRead = ::GetWindowTextW(hWnd, *psczText, cchText);
1545 if (cchTextRead + 1 < cchText)
1546 {
1547 break;
1548 }
1549 else
1550 {
1551 cchText = cchTextRead + GROW_WINDOW_TEXT;
1552
1553 hr = StrAlloc(psczText, cchText);
1554 ThmExitOnFailure(hr, "Failed to grow text buffer again.");
1555 }
1556 }
1557
1558LExit:
1559 return hr;
1560}
1561
1562
1563DAPI_(HRESULT) ThemeUpdateCaption(
1564 __in THEME* pTheme,
1565 __in_z LPCWSTR wzCaption
1566 )
1567{
1568 HRESULT hr = S_OK;
1569
1570 hr = StrAllocString(&pTheme->sczCaption, wzCaption, 0);
1571 ThmExitOnFailure(hr, "Failed to update theme caption.");
1572
1573LExit:
1574 return hr;
1575}
1576
1577
1578DAPI_(void) ThemeSetFocus(
1579 __in THEME* pTheme,
1580 __in DWORD dwControl
1581 )
1582{
1583 HWND hwndFocus = ::GetDlgItem(pTheme->hwndParent, dwControl);
1584 if (hwndFocus && !ThemeControlEnabled(pTheme, dwControl))
1585 {
1586 hwndFocus = ::GetNextDlgTabItem(pTheme->hwndParent, hwndFocus, FALSE);
1587 }
1588
1589 if (hwndFocus)
1590 {
1591 ::SetFocus(hwndFocus);
1592 }
1593}
1594
1595
1596DAPI_(void) ThemeShowChild(
1597 __in THEME* pTheme,
1598 __in THEME_CONTROL* pParentControl,
1599 __in DWORD dwIndex
1600 )
1601{
1602 // show one child, hide the rest
1603 for (DWORD i = 0; i < pParentControl->cControls; ++i)
1604 {
1605 THEME_CONTROL* pControl = pParentControl->rgControls + i;
1606 ShowControl(pTheme, pControl, dwIndex == i ? SW_SHOW : SW_HIDE, FALSE, THEME_SHOW_PAGE_REASON_DEFAULT, 0, NULL);
1607 }
1608}
1609
1610
1611// Internal functions.
1612
1613static HRESULT RegisterWindowClasses(
1614 __in_opt HMODULE hModule
1615 )
1616{
1617 HRESULT hr = S_OK;
1618 WNDCLASSW wcHyperlink = { };
1619 WNDCLASSW wcPanel = { };
1620 WNDCLASSW wcStaticOwnerDraw = { };
1621 WNDPROC pfnStaticOwnerDrawBaseWndProc = NULL;
1622
1623 vhCursorHand = ::LoadCursorA(NULL, IDC_HAND);
1624
1625 // Base the theme hyperlink class on a button but give it the "hand" icon.
1626 if (!::GetClassInfoW(NULL, WC_BUTTONW, &wcHyperlink))
1627 {
1628 ThmExitWithLastError(hr, "Failed to get button window class.");
1629 }
1630
1631 wcHyperlink.lpszClassName = THEME_WC_HYPERLINK;
1632#pragma prefast(push)
1633#pragma prefast(disable:25068)
1634 wcHyperlink.hCursor = vhCursorHand;
1635#pragma prefast(pop)
1636
1637 if (!::RegisterClassW(&wcHyperlink))
1638 {
1639 ThmExitWithLastError(hr, "Failed to get button window class.");
1640 }
1641 vhHyperlinkRegisteredModule = hModule;
1642
1643 // Panel is its own do-nothing class.
1644 wcPanel.lpfnWndProc = PanelWndProc;
1645 wcPanel.hInstance = hModule;
1646 wcPanel.hCursor = ::LoadCursorW(NULL, (LPCWSTR) IDC_ARROW);
1647 wcPanel.lpszClassName = THEME_WC_PANEL;
1648 if (!::RegisterClassW(&wcPanel))
1649 {
1650 ThmExitWithLastError(hr, "Failed to register window.");
1651 }
1652 vhPanelRegisteredModule = hModule;
1653
1654 if (!::GetClassInfoW(NULL, WC_STATICW, &wcStaticOwnerDraw))
1655 {
1656 ThmExitWithLastError(hr, "Failed to get static window class.");
1657 }
1658
1659 pfnStaticOwnerDrawBaseWndProc = wcStaticOwnerDraw.lpfnWndProc;
1660 wcStaticOwnerDraw.lpfnWndProc = StaticOwnerDrawWndProc;
1661 wcStaticOwnerDraw.hInstance = hModule;
1662 wcStaticOwnerDraw.lpszClassName = THEME_WC_STATICOWNERDRAW;
1663 if (!::RegisterClassW(&wcStaticOwnerDraw))
1664 {
1665 ThmExitWithLastError(hr, "Failed to register OwnerDraw window class.");
1666 }
1667 vhStaticOwnerDrawRegisteredModule = hModule;
1668 vpfnStaticOwnerDrawBaseWndProc = pfnStaticOwnerDrawBaseWndProc;
1669
1670
1671LExit:
1672 return hr;
1673}
1674
1675static HRESULT ParseTheme(
1676 __in_opt HMODULE hModule,
1677 __in_opt LPCWSTR wzRelativePath,
1678 __in IXMLDOMDocument* pixd,
1679 __out THEME** ppTheme
1680 )
1681{
1682 static WORD wThemeId = 0;
1683
1684 HRESULT hr = S_OK;
1685 THEME* pTheme = NULL;
1686 IXMLDOMElement *pThemeElement = NULL;
1687
1688 hr = pixd->get_documentElement(&pThemeElement);
1689 ThmExitOnFailure(hr, "Failed to get theme element.");
1690
1691 pTheme = static_cast<THEME*>(MemAlloc(sizeof(THEME), TRUE));
1692 ThmExitOnNull(pTheme, hr, E_OUTOFMEMORY, "Failed to allocate memory for theme.");
1693
1694 pTheme->wId = ++wThemeId;
1695 pTheme->nDpi = USER_DEFAULT_SCREEN_DPI;
1696
1697 // Parse the optional background resource image.
1698 hr = ParseImage(hModule, wzRelativePath, pThemeElement, &pTheme->hImage);
1699 ThmExitOnFailure(hr, "Failed while parsing theme image.");
1700
1701 // Parse the fonts.
1702 hr = ParseFonts(pThemeElement, pTheme);
1703 ThmExitOnFailure(hr, "Failed to parse theme fonts.");
1704
1705 // Parse the window element.
1706 hr = ParseWindow(hModule, wzRelativePath, pThemeElement, pTheme);
1707 ThmExitOnFailure(hr, "Failed to parse theme window element.");
1708
1709 *ppTheme = pTheme;
1710 pTheme = NULL;
1711
1712LExit:
1713 ReleaseObject(pThemeElement);
1714
1715 if (pTheme)
1716 {
1717 ThemeFree(pTheme);
1718 }
1719
1720 return hr;
1721}
1722
1723static HRESULT ParseImage(
1724 __in_opt HMODULE hModule,
1725 __in_z_opt LPCWSTR wzRelativePath,
1726 __in IXMLDOMNode* pElement,
1727 __out HBITMAP* phImage
1728 )
1729{
1730 HRESULT hr = S_OK;
1731 BSTR bstr = NULL;
1732 LPWSTR sczImageFile = NULL;
1733 int iResourceId = 0;
1734 Gdiplus::Bitmap* pBitmap = NULL;
1735 *phImage = NULL;
1736
1737 hr = XmlGetAttribute(pElement, L"ImageResource", &bstr);
1738 ThmExitOnFailure(hr, "Failed to get image resource attribute.");
1739
1740 if (S_OK == hr)
1741 {
1742 iResourceId = wcstol(bstr, NULL, 10);
1743
1744 hr = GdipBitmapFromResource(hModule, MAKEINTRESOURCE(iResourceId), &pBitmap);
1745 // Don't fail.
1746 }
1747
1748 ReleaseNullBSTR(bstr);
1749
1750 // Parse the optional background image from a given file.
1751 if (!pBitmap)
1752 {
1753 hr = XmlGetAttribute(pElement, L"ImageFile", &bstr);
1754 ThmExitOnFailure(hr, "Failed to get image file attribute.");
1755
1756 if (S_OK == hr)
1757 {
1758 if (wzRelativePath)
1759 {
1760 hr = PathConcat(wzRelativePath, bstr, &sczImageFile);
1761 ThmExitOnFailure(hr, "Failed to combine image file path.");
1762 }
1763 else
1764 {
1765 hr = PathRelativeToModule(&sczImageFile, bstr, hModule);
1766 ThmExitOnFailure(hr, "Failed to get image filename.");
1767 }
1768
1769 hr = GdipBitmapFromFile(sczImageFile, &pBitmap);
1770 // Don't fail.
1771 }
1772 }
1773
1774 // If there is an image, convert it into a bitmap handle.
1775 if (pBitmap)
1776 {
1777 Gdiplus::Color black;
1778 Gdiplus::Status gs = pBitmap->GetHBITMAP(black, phImage);
1779 ThmExitOnGdipFailure(gs, hr, "Failed to convert GDI+ bitmap into HBITMAP.");
1780 }
1781
1782 hr = S_OK;
1783
1784LExit:
1785 if (pBitmap)
1786 {
1787 delete pBitmap;
1788 }
1789
1790 ReleaseStr(sczImageFile);
1791 ReleaseBSTR(bstr);
1792
1793 return hr;
1794}
1795
1796
1797static HRESULT ParseIcon(
1798 __in_opt HMODULE hModule,
1799 __in_z_opt LPCWSTR wzRelativePath,
1800 __in IXMLDOMNode* pElement,
1801 __out HICON* phIcon
1802 )
1803{
1804 HRESULT hr = S_OK;
1805 BSTR bstr = NULL;
1806 LPWSTR sczImageFile = NULL;
1807 int iResourceId = 0;
1808 *phIcon = NULL;
1809
1810 hr = XmlGetAttribute(pElement, L"IconResource", &bstr);
1811 ThmExitOnFailure(hr, "Failed to get icon resource attribute.");
1812
1813 if (S_OK == hr)
1814 {
1815 iResourceId = wcstol(bstr, NULL, 10);
1816
1817 *phIcon = reinterpret_cast<HICON>(::LoadImageW(hModule, MAKEINTRESOURCEW(iResourceId), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE));
1818 ThmExitOnNullWithLastError(*phIcon, hr, "Failed to load icon.");
1819 }
1820 else
1821 {
1822 ReleaseNullBSTR(bstr);
1823
1824 hr = XmlGetAttribute(pElement, L"IconFile", &bstr);
1825 ThmExitOnFailure(hr, "Failed to get icon file attribute.");
1826
1827 if (S_OK == hr)
1828 {
1829 if (wzRelativePath)
1830 {
1831 hr = PathConcat(wzRelativePath, bstr, &sczImageFile);
1832 ThmExitOnFailure(hr, "Failed to combine image file path.");
1833 }
1834 else
1835 {
1836 hr = PathRelativeToModule(&sczImageFile, bstr, hModule);
1837 ThmExitOnFailure(hr, "Failed to get image filename.");
1838 }
1839
1840 *phIcon = reinterpret_cast<HICON>(::LoadImageW(NULL, sczImageFile, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE));
1841 ThmExitOnNullWithLastError(*phIcon, hr, "Failed to load icon: %ls.", sczImageFile);
1842 }
1843 }
1844
1845LExit:
1846 ReleaseStr(sczImageFile);
1847 ReleaseBSTR(bstr);
1848
1849 return hr;
1850}
1851
1852
1853static HRESULT ParseWindow(
1854 __in_opt HMODULE hModule,
1855 __in_opt LPCWSTR wzRelativePath,
1856 __in IXMLDOMElement* pElement,
1857 __in THEME* pTheme
1858 )
1859{
1860 HRESULT hr = S_OK;
1861 IXMLDOMNode* pixn = NULL;
1862 DWORD dwValue = 0;
1863 BSTR bstr = NULL;
1864 LPWSTR sczIconFile = NULL;
1865
1866 hr = XmlSelectSingleNode(pElement, L"Window", &pixn);
1867 if (S_FALSE == hr)
1868 {
1869 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
1870 }
1871 ThmExitOnFailure(hr, "Failed to find window element.");
1872
1873 hr = XmlGetYesNoAttribute(pixn, L"AutoResize", &pTheme->fAutoResize);
1874 if (E_NOTFOUND == hr)
1875 {
1876 hr = S_OK;
1877 }
1878 ThmExitOnFailure(hr, "Failed to get window AutoResize attribute.");
1879
1880 hr = XmlGetAttributeNumber(pixn, L"Width", &dwValue);
1881 if (S_FALSE == hr)
1882 {
1883 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
1884 ThmExitOnRootFailure(hr, "Failed to find window Width attribute.");
1885 }
1886 ThmExitOnFailure(hr, "Failed to get window Width attribute.");
1887
1888 pTheme->nWidth = pTheme->nDefaultDpiWidth = pTheme->nWindowWidth = dwValue;
1889
1890 hr = XmlGetAttributeNumber(pixn, L"Height", &dwValue);
1891 if (S_FALSE == hr)
1892 {
1893 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
1894 ThmExitOnRootFailure(hr, "Failed to find window Height attribute.");
1895 }
1896 ThmExitOnFailure(hr, "Failed to get window Height attribute.");
1897
1898 pTheme->nHeight = pTheme->nDefaultDpiHeight = pTheme->nWindowHeight = dwValue;
1899
1900 hr = XmlGetAttributeNumber(pixn, L"MinimumWidth", &dwValue);
1901 if (S_FALSE == hr)
1902 {
1903 dwValue = 0;
1904 hr = S_OK;
1905 }
1906 ThmExitOnFailure(hr, "Failed to get window MinimumWidth attribute.");
1907
1908 pTheme->nMinimumWidth = pTheme->nDefaultDpiMinimumWidth = dwValue;
1909
1910 hr = XmlGetAttributeNumber(pixn, L"MinimumHeight", &dwValue);
1911 if (S_FALSE == hr)
1912 {
1913 dwValue = 0;
1914 hr = S_OK;
1915 }
1916 ThmExitOnFailure(hr, "Failed to get window MinimumHeight attribute.");
1917
1918 pTheme->nMinimumHeight = pTheme->nDefaultDpiMinimumHeight = dwValue;
1919
1920 hr = XmlGetAttributeNumber(pixn, L"FontId", &pTheme->dwFontId);
1921 if (S_FALSE == hr)
1922 {
1923 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
1924 ThmExitOnRootFailure(hr, "Failed to find window FontId attribute.");
1925 }
1926 ThmExitOnFailure(hr, "Failed to get window FontId attribute.");
1927
1928 // Get the optional window icon from a resource.
1929 hr = XmlGetAttribute(pixn, L"IconResource", &bstr);
1930 ThmExitOnFailure(hr, "Failed to get window IconResource attribute.");
1931
1932 if (S_OK == hr)
1933 {
1934 pTheme->hIcon = ::LoadIconW(hModule, bstr);
1935 ThmExitOnNullWithLastError(pTheme->hIcon, hr, "Failed to load window icon from IconResource.");
1936
1937 ReleaseNullBSTR(bstr);
1938 }
1939
1940 // Get the optional window icon from a file.
1941 hr = XmlGetAttribute(pixn, L"IconFile", &bstr);
1942 ThmExitOnFailure(hr, "Failed to get window IconFile attribute.");
1943
1944 if (S_OK == hr)
1945 {
1946 if (wzRelativePath)
1947 {
1948 hr = PathConcat(wzRelativePath, bstr, &sczIconFile);
1949 ThmExitOnFailure(hr, "Failed to combine icon file path.");
1950 }
1951 else
1952 {
1953 hr = PathRelativeToModule(&sczIconFile, bstr, hModule);
1954 ThmExitOnFailure(hr, "Failed to get icon filename.");
1955 }
1956
1957 pTheme->hIcon = ::LoadImageW(NULL, sczIconFile, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE);
1958 ThmExitOnNullWithLastError(pTheme->hIcon, hr, "Failed to load window icon from IconFile: %ls.", bstr);
1959
1960 ReleaseNullBSTR(bstr);
1961 }
1962
1963 hr = XmlGetAttributeNumber(pixn, L"SourceX", reinterpret_cast<DWORD*>(&pTheme->nSourceX));
1964 if (S_FALSE == hr)
1965 {
1966 pTheme->nSourceX = -1;
1967 }
1968 ThmExitOnFailure(hr, "Failed to get window SourceX attribute.");
1969
1970 hr = XmlGetAttributeNumber(pixn, L"SourceY", reinterpret_cast<DWORD*>(&pTheme->nSourceY));
1971 if (S_FALSE == hr)
1972 {
1973 pTheme->nSourceY = -1;
1974 }
1975 ThmExitOnFailure(hr, "Failed to get window SourceY attribute.");
1976
1977 // Parse the optional window style.
1978 hr = XmlGetAttributeNumberBase(pixn, L"HexStyle", 16, &pTheme->dwStyle);
1979 ThmExitOnFailure(hr, "Failed to get theme window style (Window@HexStyle) attribute.");
1980
1981 if (S_FALSE == hr)
1982 {
1983 pTheme->dwStyle = WS_VISIBLE | WS_MINIMIZEBOX | WS_SYSMENU;
1984 pTheme->dwStyle |= (0 <= pTheme->nSourceX && 0 <= pTheme->nSourceY) ? WS_POPUP : WS_OVERLAPPED;
1985 }
1986
1987 hr = XmlGetAttributeNumber(pixn, L"StringId", reinterpret_cast<DWORD*>(&pTheme->uStringId));
1988 ThmExitOnFailure(hr, "Failed to get window StringId attribute.");
1989
1990 if (S_FALSE == hr)
1991 {
1992 pTheme->uStringId = UINT_MAX;
1993
1994 hr = XmlGetAttribute(pixn, L"Caption", &bstr);
1995 ThmExitOnFailure(hr, "Failed to get window Caption attribute.");
1996
1997 if (S_FALSE == hr)
1998 {
1999 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2000 ThmExitOnRootFailure(hr, "Window elements must contain the Caption or StringId attribute.");
2001 }
2002
2003 hr = StrAllocString(&pTheme->sczCaption, bstr, 0);
2004 ThmExitOnFailure(hr, "Failed to copy window Caption attribute.");
2005 }
2006
2007 // Parse any image lists.
2008 hr = ParseImageLists(hModule, wzRelativePath, pixn, pTheme);
2009 ThmExitOnFailure(hr, "Failed to parse image lists.");
2010
2011 // Parse the pages.
2012 hr = ParsePages(hModule, wzRelativePath, pixn, pTheme);
2013 ThmExitOnFailure(hr, "Failed to parse theme pages.");
2014
2015 // Parse the non-paged controls.
2016 hr = ParseControls(hModule, wzRelativePath, pixn, pTheme, NULL, NULL);
2017 ThmExitOnFailure(hr, "Failed to parse theme controls.");
2018
2019LExit:
2020 ReleaseStr(sczIconFile);
2021 ReleaseBSTR(bstr);
2022 ReleaseObject(pixn);
2023
2024 return hr;
2025}
2026
2027
2028static HRESULT ParseFonts(
2029 __in IXMLDOMElement* pElement,
2030 __in THEME* pTheme
2031 )
2032{
2033 HRESULT hr = S_OK;
2034 IXMLDOMNodeList* pixnl = NULL;
2035 IXMLDOMNode* pixn = NULL;
2036 BSTR bstrName = NULL;
2037 DWORD dwId = 0;
2038 COLORREF crForeground = THEME_INVISIBLE_COLORREF;
2039 COLORREF crBackground = THEME_INVISIBLE_COLORREF;
2040 DWORD dwSystemForegroundColor = FALSE;
2041 DWORD dwSystemBackgroundColor = FALSE;
2042
2043 hr = XmlSelectNodes(pElement, L"Font", &pixnl);
2044 ThmExitOnFailure(hr, "Failed to find font elements.");
2045
2046 hr = pixnl->get_length(reinterpret_cast<long*>(&pTheme->cFonts));
2047 ThmExitOnFailure(hr, "Failed to count the number of theme fonts.");
2048
2049 if (!pTheme->cFonts)
2050 {
2051 ExitFunction1(hr = S_OK);
2052 }
2053
2054 pTheme->rgFonts = static_cast<THEME_FONT*>(MemAlloc(sizeof(THEME_FONT) * pTheme->cFonts, TRUE));
2055 ThmExitOnNull(pTheme->rgFonts, hr, E_OUTOFMEMORY, "Failed to allocate theme fonts.");
2056
2057 while (S_OK == (hr = XmlNextElement(pixnl, &pixn, NULL)))
2058 {
2059 hr = XmlGetAttributeNumber(pixn, L"Id", &dwId);
2060 if (S_FALSE == hr)
2061 {
2062 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2063 }
2064 ThmExitOnFailure(hr, "Failed to find font id.");
2065
2066 if (pTheme->cFonts <= dwId)
2067 {
2068 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2069 ThmExitOnRootFailure(hr, "Invalid theme font id.");
2070 }
2071
2072 THEME_FONT* pFont = pTheme->rgFonts + dwId;
2073 if (pFont->cFontInstances)
2074 {
2075 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2076 ThmExitOnRootFailure(hr, "Theme font id duplicated.");
2077 }
2078
2079 pFont->lfQuality = CLEARTYPE_QUALITY;
2080
2081 hr = XmlGetText(pixn, &bstrName);
2082 if (S_FALSE == hr)
2083 {
2084 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2085 }
2086 ThmExitOnFailure(hr, "Failed to get font name.");
2087
2088 hr = StrAllocString(&pFont->sczFaceName, bstrName, 0);
2089 ThmExitOnFailure(hr, "Failed to copy font name.");
2090
2091 hr = XmlGetAttributeNumber(pixn, L"Height", reinterpret_cast<DWORD*>(&pFont->lfHeight));
2092 if (S_FALSE == hr)
2093 {
2094 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2095 }
2096 ThmExitOnFailure(hr, "Failed to find font height attribute.");
2097
2098 hr = XmlGetAttributeNumber(pixn, L"Weight", reinterpret_cast<DWORD*>(&pFont->lfWeight));
2099 if (S_FALSE == hr)
2100 {
2101 pFont->lfWeight = FW_DONTCARE;
2102 hr = S_OK;
2103 }
2104 ThmExitOnFailure(hr, "Failed to find font weight attribute.");
2105
2106 hr = XmlGetYesNoAttribute(pixn, L"Underline", reinterpret_cast<BOOL*>(&pFont->lfUnderline));
2107 if (E_NOTFOUND == hr)
2108 {
2109 pFont->lfUnderline = FALSE;
2110 hr = S_OK;
2111 }
2112 ThmExitOnFailure(hr, "Failed to find font underline attribute.");
2113
2114 hr = GetFontColor(pixn, L"Foreground", &crForeground, &dwSystemForegroundColor);
2115 ThmExitOnFailure(hr, "Failed to find font foreground color.");
2116
2117 hr = GetFontColor(pixn, L"Background", &crBackground, &dwSystemBackgroundColor);
2118 ThmExitOnFailure(hr, "Failed to find font background color.");
2119
2120 pFont->crForeground = crForeground;
2121 if (THEME_INVISIBLE_COLORREF != pFont->crForeground)
2122 {
2123 pFont->hForeground = dwSystemForegroundColor ? ::GetSysColorBrush(dwSystemForegroundColor) : ::CreateSolidBrush(pFont->crForeground);
2124 ThmExitOnNull(pFont->hForeground, hr, E_OUTOFMEMORY, "Failed to create text foreground brush.");
2125 }
2126
2127 pFont->crBackground = crBackground;
2128 if (THEME_INVISIBLE_COLORREF != pFont->crBackground)
2129 {
2130 pFont->hBackground = dwSystemBackgroundColor ? ::GetSysColorBrush(dwSystemBackgroundColor) : ::CreateSolidBrush(pFont->crBackground);
2131 ThmExitOnNull(pFont->hBackground, hr, E_OUTOFMEMORY, "Failed to create text background brush.");
2132 }
2133
2134 ReleaseNullBSTR(bstrName);
2135 ReleaseNullObject(pixn);
2136 }
2137 ThmExitOnFailure(hr, "Failed to enumerate all fonts.");
2138
2139 if (S_FALSE == hr)
2140 {
2141 hr = S_OK;
2142 }
2143
2144LExit:
2145 ReleaseBSTR(bstrName);
2146 ReleaseObject(pixn);
2147 ReleaseObject(pixnl);
2148
2149 return hr;
2150}
2151
2152
2153static HRESULT GetFontColor(
2154 __in IXMLDOMNode* pixn,
2155 __in_z LPCWSTR wzAttributeName,
2156 __out COLORREF* pColorRef,
2157 __out DWORD* pdwSystemColor
2158 )
2159{
2160 HRESULT hr = S_OK;
2161 BSTR bstr = NULL;
2162
2163 *pdwSystemColor = 0;
2164
2165 hr = XmlGetAttribute(pixn, wzAttributeName, &bstr);
2166 if (S_FALSE == hr)
2167 {
2168 *pColorRef = THEME_INVISIBLE_COLORREF;
2169 ExitFunction1(hr = S_OK);
2170 }
2171 ThmExitOnFailure(hr, "Failed to find font %ls color.", wzAttributeName);
2172
2173 if (pdwSystemColor)
2174 {
2175 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"btnface", -1))
2176 {
2177 *pdwSystemColor = COLOR_BTNFACE;
2178 }
2179 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"btntext", -1))
2180 {
2181 *pdwSystemColor = COLOR_BTNTEXT;
2182 }
2183 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"graytext", -1))
2184 {
2185 *pdwSystemColor = COLOR_GRAYTEXT;
2186 }
2187 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"highlight", -1))
2188 {
2189 *pdwSystemColor = COLOR_HIGHLIGHT;
2190 }
2191 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"highlighttext", -1))
2192 {
2193 *pdwSystemColor = COLOR_HIGHLIGHTTEXT;
2194 }
2195 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"hotlight", -1))
2196 {
2197 *pdwSystemColor = COLOR_HOTLIGHT;
2198 }
2199 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"window", -1))
2200 {
2201 *pdwSystemColor = COLOR_WINDOW;
2202 }
2203 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"windowtext", -1))
2204 {
2205 *pdwSystemColor = COLOR_WINDOWTEXT;
2206 }
2207 else
2208 {
2209 *pColorRef = wcstoul(bstr, NULL, 16);
2210 }
2211
2212 if (*pdwSystemColor)
2213 {
2214 *pColorRef = ::GetSysColor(*pdwSystemColor);
2215 }
2216 }
2217
2218LExit:
2219 ReleaseBSTR(bstr);
2220
2221 return hr;
2222}
2223
2224static HRESULT ParsePages(
2225 __in_opt HMODULE hModule,
2226 __in_opt LPCWSTR wzRelativePath,
2227 __in IXMLDOMNode* pElement,
2228 __in THEME* pTheme
2229 )
2230{
2231 HRESULT hr = S_OK;
2232 IXMLDOMNodeList* pixnl = NULL;
2233 IXMLDOMNode* pixn = NULL;
2234 BSTR bstrType = NULL;
2235 THEME_PAGE* pPage = NULL;
2236 DWORD iPage = 0;
2237
2238 hr = XmlSelectNodes(pElement, L"Page", &pixnl);
2239 ThmExitOnFailure(hr, "Failed to find page elements.");
2240
2241 hr = pixnl->get_length(reinterpret_cast<long*>(&pTheme->cPages));
2242 ThmExitOnFailure(hr, "Failed to count the number of theme pages.");
2243
2244 if (!pTheme->cPages)
2245 {
2246 ExitFunction1(hr = S_OK);
2247 }
2248
2249 pTheme->rgPages = static_cast<THEME_PAGE*>(MemAlloc(sizeof(THEME_PAGE) * pTheme->cPages, TRUE));
2250 ThmExitOnNull(pTheme->rgPages, hr, E_OUTOFMEMORY, "Failed to allocate theme pages.");
2251
2252 while (S_OK == (hr = XmlNextElement(pixnl, &pixn, &bstrType)))
2253 {
2254 pPage = pTheme->rgPages + iPage;
2255
2256 pPage->wId = static_cast<WORD>(iPage + 1);
2257
2258 hr = XmlGetAttributeEx(pixn, L"Name", &pPage->sczName);
2259 if (E_NOTFOUND == hr)
2260 {
2261 hr = S_OK;
2262 }
2263 ThmExitOnFailure(hr, "Failed when querying page Name.");
2264
2265 hr = ParseControls(hModule, wzRelativePath, pixn, pTheme, NULL, pPage);
2266 ThmExitOnFailure(hr, "Failed to parse page controls.");
2267
2268 ++iPage;
2269
2270 ReleaseNullBSTR(bstrType);
2271 ReleaseNullObject(pixn);
2272 }
2273 ThmExitOnFailure(hr, "Failed to enumerate all pages.");
2274
2275 if (S_FALSE == hr)
2276 {
2277 hr = S_OK;
2278 }
2279
2280LExit:
2281 ReleaseBSTR(bstrType);
2282 ReleaseObject(pixn);
2283 ReleaseObject(pixnl);
2284
2285 return hr;
2286}
2287
2288
2289static HRESULT ParseImageLists(
2290 __in_opt HMODULE hModule,
2291 __in_opt LPCWSTR wzRelativePath,
2292 __in IXMLDOMNode* pElement,
2293 __in THEME* pTheme
2294 )
2295{
2296 HRESULT hr = S_OK;
2297 IXMLDOMNodeList* pixnlImageLists = NULL;
2298 IXMLDOMNode* pixnImageList = NULL;
2299 IXMLDOMNodeList* pixnlImages = NULL;
2300 IXMLDOMNode* pixnImage = NULL;
2301 DWORD dwImageListIndex = 0;
2302 DWORD dwImageCount = 0;
2303 HBITMAP hBitmap = NULL;
2304 BITMAP bm = { };
2305 BSTR bstr = NULL;
2306 DWORD i = 0;
2307 int iRetVal = 0;
2308
2309 hr = XmlSelectNodes(pElement, L"ImageList", &pixnlImageLists);
2310 ThmExitOnFailure(hr, "Failed to find ImageList elements.");
2311
2312 hr = pixnlImageLists->get_length(reinterpret_cast<long*>(&pTheme->cImageLists));
2313 ThmExitOnFailure(hr, "Failed to count the number of image lists.");
2314
2315 if (!pTheme->cImageLists)
2316 {
2317 ExitFunction1(hr = S_OK);
2318 }
2319
2320 pTheme->rgImageLists = static_cast<THEME_IMAGELIST*>(MemAlloc(sizeof(THEME_IMAGELIST) * pTheme->cImageLists, TRUE));
2321 ThmExitOnNull(pTheme->rgImageLists, hr, E_OUTOFMEMORY, "Failed to allocate theme image lists.");
2322
2323 while (S_OK == (hr = XmlNextElement(pixnlImageLists, &pixnImageList, NULL)))
2324 {
2325 hr = XmlGetAttribute(pixnImageList, L"Name", &bstr);
2326 if (S_FALSE == hr)
2327 {
2328 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2329 }
2330 ThmExitOnFailure(hr, "Failed to find ImageList/@Name attribute.");
2331
2332 hr = StrAllocString(&pTheme->rgImageLists[dwImageListIndex].sczName, bstr, 0);
2333 ThmExitOnFailure(hr, "Failed to make copy of ImageList name.");
2334
2335 hr = XmlSelectNodes(pixnImageList, L"Image", &pixnlImages);
2336 ThmExitOnFailure(hr, "Failed to select child Image nodes.");
2337
2338 hr = pixnlImages->get_length(reinterpret_cast<long*>(&dwImageCount));
2339 ThmExitOnFailure(hr, "Failed to count the number of images in list.");
2340
2341 if (0 < dwImageCount)
2342 {
2343 i = 0;
2344 while (S_OK == (hr = XmlNextElement(pixnlImages, &pixnImage, NULL)))
2345 {
2346 if (hBitmap)
2347 {
2348 ::DeleteObject(hBitmap);
2349 hBitmap = NULL;
2350 }
2351 hr = ParseImage(hModule, wzRelativePath, pixnImage, &hBitmap);
2352 ThmExitOnFailure(hr, "Failed to parse image: %u", i);
2353
2354 if (0 == i)
2355 {
2356 ::GetObjectW(hBitmap, sizeof(BITMAP), &bm);
2357
2358 pTheme->rgImageLists[dwImageListIndex].hImageList = ImageList_Create(bm.bmWidth, bm.bmHeight, ILC_COLOR24, dwImageCount, 0);
2359 ThmExitOnNullWithLastError(pTheme->rgImageLists[dwImageListIndex].hImageList, hr, "Failed to create image list.");
2360 }
2361
2362 iRetVal = ImageList_Add(pTheme->rgImageLists[dwImageListIndex].hImageList, hBitmap, NULL);
2363 if (-1 == iRetVal)
2364 {
2365 ThmExitWithLastError(hr, "Failed to add image %u to image list.", i);
2366 }
2367
2368 ++i;
2369 }
2370 }
2371 ++dwImageListIndex;
2372 }
2373
2374LExit:
2375 if (hBitmap)
2376 {
2377 ::DeleteObject(hBitmap);
2378 }
2379 ReleaseBSTR(bstr);
2380 ReleaseObject(pixnlImageLists);
2381 ReleaseObject(pixnImageList);
2382 ReleaseObject(pixnlImages);
2383 ReleaseObject(pixnImage);
2384
2385 return hr;
2386}
2387
2388static void GetControls(
2389 __in THEME* pTheme,
2390 __in_opt THEME_CONTROL* pParentControl,
2391 __out DWORD** ppcControls,
2392 __out THEME_CONTROL*** pprgControls
2393 )
2394{
2395 if (pParentControl)
2396 {
2397 *ppcControls = &pParentControl->cControls;
2398 *pprgControls = &pParentControl->rgControls;
2399 }
2400 else
2401 {
2402 *ppcControls = &pTheme->cControls;
2403 *pprgControls = &pTheme->rgControls;
2404 }
2405}
2406
2407static void GetControls(
2408 __in const THEME* pTheme,
2409 __in_opt const THEME_CONTROL* pParentControl,
2410 __out DWORD& cControls,
2411 __out THEME_CONTROL*& rgControls
2412 )
2413{
2414 if (pParentControl)
2415 {
2416 cControls = pParentControl->cControls;
2417 rgControls = pParentControl->rgControls;
2418 }
2419 else
2420 {
2421 cControls = pTheme->cControls;
2422 rgControls = pTheme->rgControls;
2423 }
2424}
2425
2426static HRESULT ParseControls(
2427 __in_opt HMODULE hModule,
2428 __in_opt LPCWSTR wzRelativePath,
2429 __in IXMLDOMNode* pElement,
2430 __in THEME* pTheme,
2431 __in_opt THEME_CONTROL* pParentControl,
2432 __in_opt THEME_PAGE* pPage
2433 )
2434{
2435 HRESULT hr = S_OK;
2436 IXMLDOMNodeList* pixnl = NULL;
2437 IXMLDOMNode* pixn = NULL;
2438 BSTR bstrType = NULL;
2439 DWORD cNewControls = 0;
2440 DWORD iControl = 0;
2441 DWORD iPageControl = 0;
2442 DWORD* pcControls = NULL;
2443 THEME_CONTROL** prgControls = NULL;
2444
2445 GetControls(pTheme, pParentControl, &pcControls, &prgControls);
2446
2447 hr = ParseRadioButtons(hModule, wzRelativePath, pElement, pTheme, pParentControl, pPage);
2448 ThmExitOnFailure(hr, "Failed to parse radio buttons.");
2449
2450 hr = XmlSelectNodes(pElement, L"Billboard|Button|Checkbox|Combobox|CommandLink|Editbox|Hyperlink|Hypertext|ImageControl|Label|ListView|Panel|Progressbar|Richedit|Static|Tabs|TreeView", &pixnl);
2451 ThmExitOnFailure(hr, "Failed to find control elements.");
2452
2453 hr = pixnl->get_length(reinterpret_cast<long*>(&cNewControls));
2454 ThmExitOnFailure(hr, "Failed to count the number of theme controls.");
2455
2456 if (!cNewControls)
2457 {
2458 ExitFunction1(hr = S_OK);
2459 }
2460
2461 hr = MemReAllocArray(reinterpret_cast<LPVOID*>(prgControls), *pcControls, sizeof(THEME_CONTROL), cNewControls);
2462 ThmExitOnFailure(hr, "Failed to reallocate theme controls.");
2463
2464 cNewControls += *pcControls;
2465
2466 if (pPage)
2467 {
2468 iPageControl = pPage->cControlIndices;
2469 pPage->cControlIndices += cNewControls;
2470 }
2471
2472 iControl = *pcControls;
2473 *pcControls = cNewControls;
2474
2475 while (S_OK == (hr = XmlNextElement(pixnl, &pixn, &bstrType)))
2476 {
2477 THEME_CONTROL_TYPE type = THEME_CONTROL_TYPE_UNKNOWN;
2478
2479 if (!bstrType)
2480 {
2481 hr = E_UNEXPECTED;
2482 ThmExitOnFailure(hr, "Null element encountered!");
2483 }
2484
2485 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Billboard", -1))
2486 {
2487 type = THEME_CONTROL_TYPE_BILLBOARD;
2488 }
2489 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Button", -1))
2490 {
2491 type = THEME_CONTROL_TYPE_BUTTON;
2492 }
2493 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Checkbox", -1))
2494 {
2495 type = THEME_CONTROL_TYPE_CHECKBOX;
2496 }
2497 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Combobox", -1))
2498 {
2499 type = THEME_CONTROL_TYPE_COMBOBOX;
2500 }
2501 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"CommandLink", -1))
2502 {
2503 type = THEME_CONTROL_TYPE_COMMANDLINK;
2504 }
2505 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Editbox", -1))
2506 {
2507 type = THEME_CONTROL_TYPE_EDITBOX;
2508 }
2509 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Hyperlink", -1))
2510 {
2511 type = THEME_CONTROL_TYPE_HYPERLINK;
2512 }
2513 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Hypertext", -1))
2514 {
2515 type = THEME_CONTROL_TYPE_HYPERTEXT;
2516 }
2517 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"ImageControl", -1))
2518 {
2519 type = THEME_CONTROL_TYPE_IMAGE;
2520 }
2521 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Label", -1))
2522 {
2523 type = THEME_CONTROL_TYPE_LABEL;
2524 }
2525 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"ListView", -1))
2526 {
2527 type = THEME_CONTROL_TYPE_LISTVIEW;
2528 }
2529 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Panel", -1))
2530 {
2531 type = THEME_CONTROL_TYPE_PANEL;
2532 }
2533 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Progressbar", -1))
2534 {
2535 type = THEME_CONTROL_TYPE_PROGRESSBAR;
2536 }
2537 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Richedit", -1))
2538 {
2539 type = THEME_CONTROL_TYPE_RICHEDIT;
2540 }
2541 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Static", -1))
2542 {
2543 type = THEME_CONTROL_TYPE_STATIC;
2544 }
2545 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Tabs", -1))
2546 {
2547 type = THEME_CONTROL_TYPE_TAB;
2548 }
2549 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"TreeView", -1))
2550 {
2551 type = THEME_CONTROL_TYPE_TREEVIEW;
2552 }
2553
2554 if (THEME_CONTROL_TYPE_UNKNOWN != type)
2555 {
2556 THEME_CONTROL* pControl = *prgControls + iControl;
2557 pControl->type = type;
2558
2559 // billboard children are always the size of the billboard
2560 BOOL fBillboardSizing = pParentControl && THEME_CONTROL_TYPE_BILLBOARD == pParentControl->type;
2561
2562 hr = ParseControl(hModule, wzRelativePath, pixn, pTheme, pControl, fBillboardSizing, pPage);
2563 ThmExitOnFailure(hr, "Failed to parse control.");
2564
2565 if (fBillboardSizing)
2566 {
2567 pControl->nX = pControl->nDefaultDpiX = 0;
2568 pControl->nY = pControl->nDefaultDpiY = 0;
2569 pControl->nWidth = pControl->nDefaultDpiWidth = 0;
2570 pControl->nHeight = pControl->nDefaultDpiHeight = 0;
2571 }
2572
2573 if (pPage)
2574 {
2575 pControl->wPageId = pPage->wId;
2576 ++iPageControl;
2577 }
2578
2579 ++iControl;
2580 }
2581
2582 ReleaseNullBSTR(bstrType);
2583 ReleaseNullObject(pixn);
2584 }
2585 ThmExitOnFailure(hr, "Failed to enumerate all controls.");
2586
2587 if (S_FALSE == hr)
2588 {
2589 hr = S_OK;
2590 }
2591
2592 AssertSz(iControl == cNewControls, "The number of parsed controls didn't match the number of expected controls.");
2593
2594LExit:
2595 ReleaseBSTR(bstrType);
2596 ReleaseObject(pixn);
2597 ReleaseObject(pixnl);
2598
2599 return hr;
2600}
2601
2602
2603static HRESULT ParseControl(
2604 __in_opt HMODULE hModule,
2605 __in_opt LPCWSTR wzRelativePath,
2606 __in IXMLDOMNode* pixn,
2607 __in THEME* pTheme,
2608 __in THEME_CONTROL* pControl,
2609 __in BOOL fSkipDimensions,
2610 __in_opt THEME_PAGE* pPage
2611 )
2612{
2613 HRESULT hr = S_OK;
2614 DWORD dwValue = 0;
2615 BOOL fValue = FALSE;
2616 BSTR bstrText = NULL;
2617 BOOL fAnyTextChildren = FALSE;
2618 BOOL fAnyNoteChildren = FALSE;
2619
2620 hr = XmlGetAttributeEx(pixn, L"Name", &pControl->sczName);
2621 if (E_NOTFOUND == hr)
2622 {
2623 hr = S_OK;
2624 }
2625 ThmExitOnFailure(hr, "Failed when querying control Name attribute.");
2626
2627 hr = XmlGetAttributeEx(pixn, L"EnableCondition", &pControl->sczEnableCondition);
2628 if (E_NOTFOUND == hr)
2629 {
2630 hr = S_OK;
2631 }
2632 ThmExitOnFailure(hr, "Failed when querying control EnableCondition attribute.");
2633
2634 hr = XmlGetAttributeEx(pixn, L"VisibleCondition", &pControl->sczVisibleCondition);
2635 if (E_NOTFOUND == hr)
2636 {
2637 hr = S_OK;
2638 }
2639 ThmExitOnFailure(hr, "Failed when querying control VisibleCondition attribute.");
2640
2641 if (!fSkipDimensions)
2642 {
2643 hr = XmlGetAttributeNumber(pixn, L"X", &dwValue);
2644 if (S_FALSE == hr)
2645 {
2646 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2647 }
2648 ThmExitOnFailure(hr, "Failed to find control X attribute.");
2649
2650 pControl->nX = pControl->nDefaultDpiX = dwValue;
2651
2652 hr = XmlGetAttributeNumber(pixn, L"Y", &dwValue);
2653 if (S_FALSE == hr)
2654 {
2655 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2656 }
2657 ThmExitOnFailure(hr, "Failed to find control Y attribute.");
2658
2659 pControl->nY = pControl->nDefaultDpiY = dwValue;
2660
2661 hr = XmlGetAttributeNumber(pixn, L"Height", &dwValue);
2662 if (S_FALSE == hr)
2663 {
2664 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2665 }
2666 ThmExitOnFailure(hr, "Failed to find control Height attribute.");
2667
2668 pControl->nHeight = pControl->nDefaultDpiHeight = dwValue;
2669
2670 hr = XmlGetAttributeNumber(pixn, L"Width", &dwValue);
2671 if (S_FALSE == hr)
2672 {
2673 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2674 }
2675 ThmExitOnFailure(hr, "Failed to find control Width attribute.");
2676
2677 pControl->nWidth = pControl->nDefaultDpiWidth = dwValue;
2678 }
2679
2680 // Parse the optional background resource image.
2681 hr = ParseImage(hModule, wzRelativePath, pixn, &pControl->hImage);
2682 ThmExitOnFailure(hr, "Failed while parsing control image.");
2683
2684 hr = XmlGetAttributeNumber(pixn, L"SourceX", reinterpret_cast<DWORD*>(&pControl->nSourceX));
2685 if (S_FALSE == hr)
2686 {
2687 pControl->nSourceX = -1;
2688 }
2689 ThmExitOnFailure(hr, "Failed when querying control SourceX attribute.");
2690
2691 hr = XmlGetAttributeNumber(pixn, L"SourceY", reinterpret_cast<DWORD*>(&pControl->nSourceY));
2692 if (S_FALSE == hr)
2693 {
2694 pControl->nSourceY = -1;
2695 }
2696 ThmExitOnFailure(hr, "Failed when querying control SourceY attribute.");
2697
2698 hr = XmlGetAttributeNumber(pixn, L"FontId", &pControl->dwFontId);
2699 if (S_FALSE == hr)
2700 {
2701 pControl->dwFontId = THEME_INVALID_ID;
2702 }
2703 ThmExitOnFailure(hr, "Failed when querying control FontId attribute.");
2704
2705 // Parse the optional window style.
2706 hr = XmlGetAttributeNumberBase(pixn, L"HexStyle", 16, &pControl->dwStyle);
2707 ThmExitOnFailure(hr, "Failed when querying control HexStyle attribute.");
2708
2709 // Parse the tabstop bit "shortcut nomenclature", this could have been set with the style above.
2710 hr = XmlGetYesNoAttribute(pixn, L"TabStop", &fValue);
2711 if (E_NOTFOUND == hr)
2712 {
2713 hr = S_OK;
2714 }
2715 else
2716 {
2717 ThmExitOnFailure(hr, "Failed when querying control TabStop attribute.");
2718
2719 if (fValue)
2720 {
2721 pControl->dwStyle |= WS_TABSTOP;
2722 }
2723 }
2724
2725 hr = XmlGetYesNoAttribute(pixn, L"Visible", &fValue);
2726 if (E_NOTFOUND == hr)
2727 {
2728 hr = S_OK;
2729 }
2730 else
2731 {
2732 ThmExitOnFailure(hr, "Failed when querying control Visible attribute.");
2733
2734 if (fValue)
2735 {
2736 pControl->dwStyle |= WS_VISIBLE;
2737 }
2738 }
2739
2740 hr = XmlGetYesNoAttribute(pixn, L"HideWhenDisabled", &fValue);
2741 if (E_NOTFOUND == hr)
2742 {
2743 hr = S_OK;
2744 }
2745 else
2746 {
2747 ThmExitOnFailure(hr, "Failed when querying control HideWhenDisabled attribute.");
2748
2749 if (fValue)
2750 {
2751 pControl->dwInternalStyle |= INTERNAL_CONTROL_STYLE_HIDE_WHEN_DISABLED;
2752 }
2753 }
2754
2755 hr = XmlGetYesNoAttribute(pixn, L"DisableAutomaticBehavior", &pControl->fDisableVariableFunctionality);
2756 if (E_NOTFOUND == hr)
2757 {
2758 hr = S_OK;
2759 }
2760 else
2761 {
2762 ThmExitOnFailure(hr, "Failed when querying control DisableAutomaticBehavior attribute.");
2763 }
2764
2765 hr = ParseActions(pixn, pControl);
2766 ThmExitOnFailure(hr, "Failed to parse action nodes of the control.");
2767
2768 hr = ParseText(pixn, pControl, &fAnyTextChildren);
2769 ThmExitOnFailure(hr, "Failed to parse text nodes of the control.");
2770
2771 hr = ParseTooltips(pixn, pControl, &fAnyTextChildren);
2772 ThmExitOnFailure(hr, "Failed to parse control Tooltip.");
2773
2774 if (THEME_CONTROL_TYPE_COMMANDLINK == pControl->type)
2775 {
2776 hr = ParseNotes(pixn, pControl, &fAnyNoteChildren);
2777 ThmExitOnFailure(hr, "Failed to parse note text nodes of the control.");
2778 }
2779
2780 if (fAnyTextChildren || fAnyNoteChildren)
2781 {
2782 pControl->uStringId = UINT_MAX;
2783 }
2784 else
2785 {
2786 hr = XmlGetAttributeNumber(pixn, L"StringId", reinterpret_cast<DWORD*>(&pControl->uStringId));
2787 ThmExitOnFailure(hr, "Failed when querying control StringId attribute.");
2788
2789 if (S_FALSE == hr)
2790 {
2791 pControl->uStringId = UINT_MAX;
2792
2793 if (THEME_CONTROL_TYPE_BILLBOARD == pControl->type || THEME_CONTROL_TYPE_PANEL == pControl->type)
2794 {
2795 // Billboards and panels have child elements and we don't want to pick up child element text in the parents.
2796 hr = S_OK;
2797 }
2798 else
2799 {
2800 hr = XmlGetText(pixn, &bstrText);
2801 ThmExitOnFailure(hr, "Failed to get control inner text.");
2802
2803 if (S_OK == hr)
2804 {
2805 hr = StrAllocString(&pControl->sczText, bstrText, 0);
2806 ThmExitOnFailure(hr, "Failed to copy control text.");
2807
2808 ReleaseNullBSTR(bstrText);
2809 }
2810 else if (S_FALSE == hr)
2811 {
2812 hr = S_OK;
2813 }
2814 }
2815 }
2816 }
2817
2818 if (THEME_CONTROL_TYPE_BILLBOARD == pControl->type)
2819 {
2820 hr = XmlGetYesNoAttribute(pixn, L"Loop", &pControl->fBillboardLoops);
2821 if (E_NOTFOUND == hr)
2822 {
2823 hr = S_OK;
2824 }
2825 ThmExitOnFailure(hr, "Failed when querying Billboard/@Loop attribute.");
2826
2827 pControl->wBillboardInterval = 5000;
2828 hr = XmlGetAttributeNumber(pixn, L"Interval", &dwValue);
2829 if (S_OK == hr && dwValue)
2830 {
2831 pControl->wBillboardInterval = static_cast<WORD>(dwValue & 0xFFFF);
2832 }
2833 ThmExitOnFailure(hr, "Failed when querying Billboard/@Interval attribute.");
2834
2835 hr = ParseControls(hModule, wzRelativePath, pixn, pTheme, pControl, pPage);
2836 ThmExitOnFailure(hr, "Failed to parse billboard children.");
2837 }
2838 else if (THEME_CONTROL_TYPE_COMMANDLINK == pControl->type)
2839 {
2840 hr = ParseIcon(hModule, wzRelativePath, pixn, &pControl->hIcon);
2841 ThmExitOnFailure(hr, "Failed while parsing control icon.");
2842 }
2843 else if (THEME_CONTROL_TYPE_EDITBOX == pControl->type)
2844 {
2845 hr = XmlGetYesNoAttribute(pixn, L"FileSystemAutoComplete", &fValue);
2846 if (E_NOTFOUND == hr)
2847 {
2848 hr = S_OK;
2849 }
2850 else
2851 {
2852 ThmExitOnFailure(hr, "Failed when querying Editbox/@FileSystemAutoComplete attribute.");
2853
2854 if (fValue)
2855 {
2856 pControl->dwInternalStyle |= INTERNAL_CONTROL_STYLE_FILESYSTEM_AUTOCOMPLETE;
2857 }
2858 }
2859 }
2860 else if (THEME_CONTROL_TYPE_HYPERLINK == pControl->type || THEME_CONTROL_TYPE_BUTTON == pControl->type)
2861 {
2862 hr = XmlGetAttributeNumber(pixn, L"HoverFontId", &pControl->dwFontHoverId);
2863 if (S_FALSE == hr)
2864 {
2865 pControl->dwFontHoverId = THEME_INVALID_ID;
2866 }
2867 ThmExitOnFailure(hr, "Failed when querying control HoverFontId attribute.");
2868
2869 hr = XmlGetAttributeNumber(pixn, L"SelectedFontId", &pControl->dwFontSelectedId);
2870 if (S_FALSE == hr)
2871 {
2872 pControl->dwFontSelectedId = THEME_INVALID_ID;
2873 }
2874 ThmExitOnFailure(hr, "Failed when querying control SelectedFontId attribute.");
2875 }
2876 else if (THEME_CONTROL_TYPE_LABEL == pControl->type)
2877 {
2878 hr = XmlGetYesNoAttribute(pixn, L"Center", &fValue);
2879 if (E_NOTFOUND == hr)
2880 {
2881 hr = S_OK;
2882 }
2883 else if (fValue)
2884 {
2885 pControl->dwStyle |= SS_CENTER;
2886 }
2887 ThmExitOnFailure(hr, "Failed when querying Label/@Center attribute.");
2888
2889 hr = XmlGetYesNoAttribute(pixn, L"DisablePrefix", &fValue);
2890 if (E_NOTFOUND == hr)
2891 {
2892 hr = S_OK;
2893 }
2894 else if (fValue)
2895 {
2896 pControl->dwStyle |= SS_NOPREFIX;
2897 }
2898 ThmExitOnFailure(hr, "Failed when querying Label/@DisablePrefix attribute.");
2899 }
2900 else if (THEME_CONTROL_TYPE_LISTVIEW == pControl->type)
2901 {
2902 // Parse the optional extended window style.
2903 hr = XmlGetAttributeNumberBase(pixn, L"HexExtendedStyle", 16, &pControl->dwExtendedStyle);
2904 ThmExitOnFailure(hr, "Failed when querying ListView/@HexExtendedStyle attribute.");
2905
2906 hr = XmlGetAttribute(pixn, L"ImageList", &bstrText);
2907 if (S_FALSE != hr)
2908 {
2909 ThmExitOnFailure(hr, "Failed when querying ListView/@ImageList attribute.");
2910
2911 hr = FindImageList(pTheme, bstrText, &pControl->rghImageList[0]);
2912 ThmExitOnFailure(hr, "Failed to find image list %ls while setting ImageList for ListView.", bstrText);
2913 }
2914
2915 hr = XmlGetAttribute(pixn, L"ImageListSmall", &bstrText);
2916 if (S_FALSE != hr)
2917 {
2918 ThmExitOnFailure(hr, "Failed when querying ListView/@ImageListSmall attribute.");
2919
2920 hr = FindImageList(pTheme, bstrText, &pControl->rghImageList[1]);
2921 ThmExitOnFailure(hr, "Failed to find image list %ls while setting ImageListSmall for ListView.", bstrText);
2922 }
2923
2924 hr = XmlGetAttribute(pixn, L"ImageListState", &bstrText);
2925 if (S_FALSE != hr)
2926 {
2927 ThmExitOnFailure(hr, "Failed when querying ListView/@ImageListState attribute.");
2928
2929 hr = FindImageList(pTheme, bstrText, &pControl->rghImageList[2]);
2930 ThmExitOnFailure(hr, "Failed to find image list %ls while setting ImageListState for ListView.", bstrText);
2931 }
2932
2933 hr = XmlGetAttribute(pixn, L"ImageListGroupHeader", &bstrText);
2934 if (S_FALSE != hr)
2935 {
2936 ThmExitOnFailure(hr, "Failed when querying ListView/@ImageListGroupHeader attribute.");
2937
2938 hr = FindImageList(pTheme, bstrText, &pControl->rghImageList[3]);
2939 ThmExitOnFailure(hr, "Failed to find image list %ls while setting ImageListGroupHeader for ListView.", bstrText);
2940 }
2941
2942 hr = ParseColumns(pixn, pControl);
2943 ThmExitOnFailure(hr, "Failed to parse columns.");
2944 }
2945 else if (THEME_CONTROL_TYPE_PANEL == pControl->type)
2946 {
2947 hr = ParseControls(hModule, wzRelativePath, pixn, pTheme, pControl, pPage);
2948 ThmExitOnFailure(hr, "Failed to parse panel children.");
2949 }
2950 else if (THEME_CONTROL_TYPE_RADIOBUTTON == pControl->type)
2951 {
2952 hr = XmlGetAttributeEx(pixn, L"Value", &pControl->sczValue);
2953 if (E_NOTFOUND == hr)
2954 {
2955 hr = S_OK;
2956 }
2957 ThmExitOnFailure(hr, "Failed when querying RadioButton/@Value attribute.");
2958 }
2959 else if (THEME_CONTROL_TYPE_TAB == pControl->type)
2960 {
2961 hr = ParseTabs(pixn, pControl);
2962 ThmExitOnFailure(hr, "Failed to parse tabs");
2963 }
2964 else if (THEME_CONTROL_TYPE_TREEVIEW == pControl->type)
2965 {
2966 pControl->dwStyle |= TVS_DISABLEDRAGDROP;
2967
2968 hr = XmlGetYesNoAttribute(pixn, L"EnableDragDrop", &fValue);
2969 if (E_NOTFOUND == hr)
2970 {
2971 hr = S_OK;
2972 }
2973 else if (fValue)
2974 {
2975 pControl->dwStyle &= ~TVS_DISABLEDRAGDROP;
2976 }
2977 ThmExitOnFailure(hr, "Failed when querying TreeView/@EnableDragDrop attribute.");
2978
2979 hr = XmlGetYesNoAttribute(pixn, L"FullRowSelect", &fValue);
2980 if (E_NOTFOUND == hr)
2981 {
2982 hr = S_OK;
2983 }
2984 else if (fValue)
2985 {
2986 pControl->dwStyle |= TVS_FULLROWSELECT;
2987 }
2988 ThmExitOnFailure(hr, "Failed when querying TreeView/@FullRowSelect attribute.");
2989
2990 hr = XmlGetYesNoAttribute(pixn, L"HasButtons", &fValue);
2991 if (E_NOTFOUND == hr)
2992 {
2993 hr = S_OK;
2994 }
2995 else if (fValue)
2996 {
2997 pControl->dwStyle |= TVS_HASBUTTONS;
2998 }
2999 ThmExitOnFailure(hr, "Failed when querying TreeView/@HasButtons attribute.");
3000
3001 hr = XmlGetYesNoAttribute(pixn, L"AlwaysShowSelect", &fValue);
3002 if (E_NOTFOUND == hr)
3003 {
3004 hr = S_OK;
3005 }
3006 else if (fValue)
3007 {
3008 pControl->dwStyle |= TVS_SHOWSELALWAYS;
3009 }
3010 ThmExitOnFailure(hr, "Failed when querying TreeView/@AlwaysShowSelect attribute.");
3011
3012 hr = XmlGetYesNoAttribute(pixn, L"LinesAtRoot", &fValue);
3013 if (E_NOTFOUND == hr)
3014 {
3015 hr = S_OK;
3016 }
3017 else if (fValue)
3018 {
3019 pControl->dwStyle |= TVS_LINESATROOT;
3020 }
3021 ThmExitOnFailure(hr, "Failed when querying TreeView/@LinesAtRoot attribute.");
3022
3023 hr = XmlGetYesNoAttribute(pixn, L"HasLines", &fValue);
3024 if (E_NOTFOUND == hr)
3025 {
3026 hr = S_OK;
3027 }
3028 else if (fValue)
3029 {
3030 pControl->dwStyle |= TVS_HASLINES;
3031 }
3032 ThmExitOnFailure(hr, "Failed when querying TreeView/@HasLines attribute.");
3033 }
3034
3035LExit:
3036 ReleaseBSTR(bstrText);
3037
3038 return hr;
3039}
3040
3041
3042static HRESULT ParseActions(
3043 __in IXMLDOMNode* pixn,
3044 __in THEME_CONTROL* pControl
3045 )
3046{
3047 HRESULT hr = S_OK;
3048 DWORD i = 0;
3049 IXMLDOMNodeList* pixnl = NULL;
3050 IXMLDOMNode* pixnChild = NULL;
3051 BSTR bstrType = NULL;
3052
3053 hr = XmlSelectNodes(pixn, L"BrowseDirectoryAction|ChangePageAction|CloseWindowAction", &pixnl);
3054 ThmExitOnFailure(hr, "Failed to select child action nodes.");
3055
3056 hr = pixnl->get_length(reinterpret_cast<long*>(&pControl->cActions));
3057 ThmExitOnFailure(hr, "Failed to count the number of action nodes.");
3058
3059 if (0 < pControl->cActions)
3060 {
3061 MemAllocArray(reinterpret_cast<LPVOID*>(&pControl->rgActions), sizeof(THEME_ACTION), pControl->cActions);
3062 ThmExitOnNull(pControl->rgActions, hr, E_OUTOFMEMORY, "Failed to allocate THEME_ACTION structs.");
3063
3064 i = 0;
3065 while (S_OK == (hr = XmlNextElement(pixnl, &pixnChild, &bstrType)))
3066 {
3067 if (!bstrType)
3068 {
3069 hr = E_UNEXPECTED;
3070 ThmExitOnFailure(hr, "Null element encountered!");
3071 }
3072
3073 THEME_ACTION* pAction = pControl->rgActions + i;
3074
3075 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"BrowseDirectoryAction", -1))
3076 {
3077 pAction->type = THEME_ACTION_TYPE_BROWSE_DIRECTORY;
3078
3079 hr = XmlGetAttributeEx(pixnChild, L"VariableName", &pAction->BrowseDirectory.sczVariableName);
3080 ThmExitOnFailure(hr, "Failed when querying BrowseDirectoryAction/@VariableName attribute.");
3081 }
3082 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"ChangePageAction", -1))
3083 {
3084 pAction->type = THEME_ACTION_TYPE_CHANGE_PAGE;
3085
3086 hr = XmlGetAttributeEx(pixnChild, L"Page", &pAction->ChangePage.sczPageName);
3087 ThmExitOnFailure(hr, "Failed when querying ChangePageAction/@Page attribute.");
3088
3089 hr = XmlGetYesNoAttribute(pixnChild, L"Cancel", &pAction->ChangePage.fCancel);
3090 if (E_NOTFOUND != hr)
3091 {
3092 ThmExitOnFailure(hr, "Failed when querying ChangePageAction/@Cancel attribute.");
3093 }
3094 }
3095 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"CloseWindowAction", -1))
3096 {
3097 pAction->type = THEME_ACTION_TYPE_CLOSE_WINDOW;
3098 }
3099 else
3100 {
3101 hr = E_UNEXPECTED;
3102 ThmExitOnFailure(hr, "Unexpected element encountered: %ls", bstrType);
3103 }
3104
3105 hr = XmlGetAttributeEx(pixnChild, L"Condition", &pAction->sczCondition);
3106 if (E_NOTFOUND != hr)
3107 {
3108 ThmExitOnFailure(hr, "Failed when querying %ls/@Condition attribute.", bstrType);
3109 }
3110
3111 if (!pAction->sczCondition)
3112 {
3113 if (pControl->pDefaultAction)
3114 {
3115 hr = E_INVALIDDATA;
3116 ThmExitOnFailure(hr, "Control '%ls' has multiple actions without a condition.", pControl->sczName);
3117 }
3118
3119 pControl->pDefaultAction = pAction;
3120 }
3121
3122 ++i;
3123 ReleaseNullBSTR(bstrType);
3124 ReleaseObject(pixnChild);
3125 }
3126 }
3127
3128LExit:
3129 ReleaseObject(pixnl);
3130 ReleaseObject(pixnChild);
3131 ReleaseBSTR(bstrType);
3132
3133 return hr;
3134}
3135
3136
3137static HRESULT ParseColumns(
3138 __in IXMLDOMNode* pixn,
3139 __in THEME_CONTROL* pControl
3140 )
3141{
3142 HRESULT hr = S_OK;
3143 DWORD i = 0;
3144 IXMLDOMNodeList* pixnl = NULL;
3145 IXMLDOMNode* pixnChild = NULL;
3146 BSTR bstrText = NULL;
3147 DWORD dwValue = 0;
3148
3149 hr = XmlSelectNodes(pixn, L"Column", &pixnl);
3150 ThmExitOnFailure(hr, "Failed to select child column nodes.");
3151
3152 hr = pixnl->get_length(reinterpret_cast<long*>(&pControl->cColumns));
3153 ThmExitOnFailure(hr, "Failed to count the number of control columns.");
3154
3155 if (0 < pControl->cColumns)
3156 {
3157 hr = MemAllocArray(reinterpret_cast<LPVOID*>(&pControl->ptcColumns), sizeof(THEME_COLUMN), pControl->cColumns);
3158 ThmExitOnFailure(hr, "Failed to allocate column structs.");
3159
3160 i = 0;
3161 while (S_OK == (hr = XmlNextElement(pixnl, &pixnChild, NULL)))
3162 {
3163 THEME_COLUMN* pColumn = pControl->ptcColumns + i;
3164
3165 hr = XmlGetText(pixnChild, &bstrText);
3166 ThmExitOnFailure(hr, "Failed to get inner text of column element.");
3167
3168 hr = XmlGetAttributeNumber(pixnChild, L"Width", &dwValue);
3169 if (S_FALSE == hr)
3170 {
3171 dwValue = 100;
3172 }
3173 ThmExitOnFailure(hr, "Failed to get column width attribute.");
3174
3175 pColumn->nBaseWidth = pColumn->nDefaultDpiBaseWidth = dwValue;
3176
3177 hr = XmlGetYesNoAttribute(pixnChild, L"Expands", reinterpret_cast<BOOL*>(&pColumn->fExpands));
3178 if (E_NOTFOUND == hr)
3179 {
3180 hr = S_OK;
3181 }
3182 ThmExitOnFailure(hr, "Failed to get expands attribute.");
3183
3184 hr = StrAllocString(&pColumn->pszName, bstrText, 0);
3185 ThmExitOnFailure(hr, "Failed to copy column name.");
3186
3187 ++i;
3188 ReleaseNullBSTR(bstrText);
3189 }
3190 }
3191
3192LExit:
3193 ReleaseObject(pixnl);
3194 ReleaseObject(pixnChild);
3195 ReleaseBSTR(bstrText);
3196
3197 return hr;
3198}
3199
3200
3201static HRESULT ParseRadioButtons(
3202 __in_opt HMODULE hModule,
3203 __in_opt LPCWSTR wzRelativePath,
3204 __in IXMLDOMNode* pixn,
3205 __in THEME* pTheme,
3206 __in_opt THEME_CONTROL* pParentControl,
3207 __in THEME_PAGE* pPage
3208 )
3209{
3210 HRESULT hr = S_OK;
3211 DWORD cRadioButtons = 0;
3212 DWORD iControl = 0;
3213 DWORD iPageControl = 0;
3214 IXMLDOMNodeList* pixnlRadioButtons = NULL;
3215 IXMLDOMNodeList* pixnl = NULL;
3216 IXMLDOMNode* pixnRadioButtons = NULL;
3217 IXMLDOMNode* pixnChild = NULL;
3218 LPWSTR sczName = NULL;
3219 THEME_CONTROL* pControl = NULL;
3220 BOOL fFirst = FALSE;
3221 DWORD* pcControls = NULL;
3222 THEME_CONTROL** prgControls = NULL;
3223
3224 GetControls(pTheme, pParentControl, &pcControls, &prgControls);
3225
3226 hr = XmlSelectNodes(pixn, L"RadioButtons", &pixnlRadioButtons);
3227 ThmExitOnFailure(hr, "Failed to select RadioButtons nodes.");
3228
3229 while (S_OK == (hr = XmlNextElement(pixnlRadioButtons, &pixnRadioButtons, NULL)))
3230 {
3231 hr = XmlGetAttributeEx(pixnRadioButtons, L"Name", &sczName);
3232 if (E_NOTFOUND == hr)
3233 {
3234 hr = S_OK;
3235 }
3236 ThmExitOnFailure(hr, "Failed when querying RadioButtons Name.");
3237
3238 hr = XmlSelectNodes(pixnRadioButtons, L"RadioButton", &pixnl);
3239 ThmExitOnFailure(hr, "Failed to select RadioButton nodes.");
3240
3241 hr = pixnl->get_length(reinterpret_cast<long*>(&cRadioButtons));
3242 ThmExitOnFailure(hr, "Failed to count the number of RadioButton nodes.");
3243
3244 if (cRadioButtons)
3245 {
3246 if (pPage)
3247 {
3248 iPageControl = pPage->cControlIndices;
3249 pPage->cControlIndices += cRadioButtons;
3250 }
3251
3252 hr = MemReAllocArray(reinterpret_cast<LPVOID*>(prgControls), *pcControls, sizeof(THEME_CONTROL), cRadioButtons);
3253 ThmExitOnFailure(hr, "Failed to reallocate theme controls.");
3254
3255 iControl = *pcControls;
3256 *pcControls += cRadioButtons;
3257
3258 fFirst = TRUE;
3259
3260 while (S_OK == (hr = XmlNextElement(pixnl, &pixnChild, NULL)))
3261 {
3262 pControl = *prgControls + iControl;
3263 pControl->type = THEME_CONTROL_TYPE_RADIOBUTTON;
3264
3265 hr = ParseControl(hModule, wzRelativePath, pixnChild, pTheme, pControl, FALSE, pPage);
3266 ThmExitOnFailure(hr, "Failed to parse control.");
3267
3268 if (fFirst)
3269 {
3270 pControl->dwStyle |= WS_GROUP;
3271 fFirst = FALSE;
3272 }
3273
3274 hr = StrAllocString(&pControl->sczVariable, sczName, 0);
3275 ThmExitOnFailure(hr, "Failed to copy radio button variable.");
3276
3277 if (pPage)
3278 {
3279 pControl->wPageId = pPage->wId;
3280 ++iPageControl;
3281 }
3282
3283 ++iControl;
3284 }
3285
3286 if (!fFirst)
3287 {
3288 pControl->fLastRadioButton = TRUE;
3289 }
3290 }
3291 }
3292
3293LExit:
3294 ReleaseStr(sczName);
3295 ReleaseObject(pixnl);
3296 ReleaseObject(pixnChild);
3297 ReleaseObject(pixnlRadioButtons);
3298 ReleaseObject(pixnRadioButtons);
3299
3300 return hr;
3301}
3302
3303
3304static HRESULT ParseTabs(
3305 __in IXMLDOMNode* pixn,
3306 __in THEME_CONTROL* pControl
3307 )
3308{
3309 HRESULT hr = S_OK;
3310 DWORD i = 0;
3311 IXMLDOMNodeList* pixnl = NULL;
3312 IXMLDOMNode* pixnChild = NULL;
3313 BSTR bstrText = NULL;
3314
3315 hr = XmlSelectNodes(pixn, L"Tab", &pixnl);
3316 ThmExitOnFailure(hr, "Failed to select child tab nodes.");
3317
3318 hr = pixnl->get_length(reinterpret_cast<long*>(&pControl->cTabs));
3319 ThmExitOnFailure(hr, "Failed to count the number of tabs.");
3320
3321 if (0 < pControl->cTabs)
3322 {
3323 hr = MemAllocArray(reinterpret_cast<LPVOID*>(&pControl->pttTabs), sizeof(THEME_TAB), pControl->cTabs);
3324 ThmExitOnFailure(hr, "Failed to allocate tab structs.");
3325
3326 i = 0;
3327 while (S_OK == (hr = XmlNextElement(pixnl, &pixnChild, NULL)))
3328 {
3329 hr = XmlGetText(pixnChild, &bstrText);
3330 ThmExitOnFailure(hr, "Failed to get inner text of tab element.");
3331
3332 hr = StrAllocString(&(pControl->pttTabs[i].pszName), bstrText, 0);
3333 ThmExitOnFailure(hr, "Failed to copy tab name.");
3334
3335 ++i;
3336 ReleaseNullBSTR(bstrText);
3337 }
3338 }
3339
3340LExit:
3341 ReleaseObject(pixnl);
3342 ReleaseObject(pixnChild);
3343 ReleaseBSTR(bstrText);
3344
3345 return hr;
3346}
3347
3348
3349static HRESULT ParseText(
3350 __in IXMLDOMNode* pixn,
3351 __in THEME_CONTROL* pControl,
3352 __inout BOOL* pfAnyChildren
3353 )
3354{
3355 HRESULT hr = S_OK;
3356 DWORD i = 0;
3357 IXMLDOMNodeList* pixnl = NULL;
3358 IXMLDOMNode* pixnChild = NULL;
3359 BSTR bstrText = NULL;
3360
3361 hr = XmlSelectNodes(pixn, L"Text", &pixnl);
3362 ThmExitOnFailure(hr, "Failed to select child Text nodes.");
3363
3364 hr = pixnl->get_length(reinterpret_cast<long*>(&pControl->cConditionalText));
3365 ThmExitOnFailure(hr, "Failed to count the number of Text nodes.");
3366
3367 *pfAnyChildren |= 0 < pControl->cConditionalText;
3368
3369 if (0 < pControl->cConditionalText)
3370 {
3371 MemAllocArray(reinterpret_cast<LPVOID*>(&pControl->rgConditionalText), sizeof(THEME_CONDITIONAL_TEXT), pControl->cConditionalText);
3372 ThmExitOnNull(pControl->rgConditionalText, hr, E_OUTOFMEMORY, "Failed to allocate THEME_CONDITIONAL_TEXT structs.");
3373
3374 i = 0;
3375 while (S_OK == (hr = XmlNextElement(pixnl, &pixnChild, NULL)))
3376 {
3377 THEME_CONDITIONAL_TEXT* pConditionalText = pControl->rgConditionalText + i;
3378
3379 hr = XmlGetAttributeEx(pixnChild, L"Condition", &pConditionalText->sczCondition);
3380 if (E_NOTFOUND == hr)
3381 {
3382 hr = S_OK;
3383 }
3384 ThmExitOnFailure(hr, "Failed when querying Text/@Condition attribute.");
3385
3386 hr = XmlGetText(pixnChild, &bstrText);
3387 ThmExitOnFailure(hr, "Failed to get inner text of Text element.");
3388
3389 if (S_OK == hr)
3390 {
3391 if (pConditionalText->sczCondition)
3392 {
3393 hr = StrAllocString(&pConditionalText->sczText, bstrText, 0);
3394 ThmExitOnFailure(hr, "Failed to copy text to conditional text.");
3395
3396 ++i;
3397 }
3398 else
3399 {
3400 if (pControl->sczText)
3401 {
3402 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
3403 ThmExitOnFailure(hr, "Unconditional text for the '%ls' control is specified multiple times.", pControl->sczName);
3404 }
3405
3406 hr = StrAllocString(&pControl->sczText, bstrText, 0);
3407 ThmExitOnFailure(hr, "Failed to copy text to control.");
3408
3409 // Unconditional text entries aren't stored in the conditional text list.
3410 --pControl->cConditionalText;
3411 }
3412 }
3413
3414 ReleaseNullBSTR(bstrText);
3415 }
3416 }
3417
3418LExit:
3419 ReleaseObject(pixnl);
3420 ReleaseObject(pixnChild);
3421 ReleaseBSTR(bstrText);
3422
3423 return hr;
3424}
3425
3426
3427static HRESULT ParseTooltips(
3428 __in IXMLDOMNode* pixn,
3429 __in THEME_CONTROL* pControl,
3430 __inout BOOL* pfAnyChildren
3431)
3432{
3433 HRESULT hr = S_OK;
3434 IXMLDOMNode* pixnChild = NULL;
3435 BSTR bstrText = NULL;
3436
3437 hr = XmlSelectSingleNode(pixn, L"Tooltip", &pixnChild);
3438 ThmExitOnFailure(hr, "Failed to select child Tooltip node.");
3439
3440 if (S_OK == hr)
3441 {
3442 *pfAnyChildren |= TRUE;
3443
3444 hr = XmlGetText(pixnChild, &bstrText);
3445 ThmExitOnFailure(hr, "Failed to get inner text of Tooltip element.");
3446
3447 if (S_OK == hr)
3448 {
3449 hr = StrAllocString(&pControl->sczTooltip, bstrText, 0);
3450 ThmExitOnFailure(hr, "Failed to copy tooltip text to control.");
3451 }
3452 }
3453
3454LExit:
3455 ReleaseObject(pixnChild);
3456 ReleaseBSTR(bstrText);
3457
3458 return hr;
3459}
3460
3461
3462static HRESULT ParseNotes(
3463 __in IXMLDOMNode* pixn,
3464 __in THEME_CONTROL* pControl,
3465 __out BOOL* pfAnyChildren
3466 )
3467{
3468 HRESULT hr = S_OK;
3469 DWORD i = 0;
3470 IXMLDOMNodeList* pixnl = NULL;
3471 IXMLDOMNode* pixnChild = NULL;
3472 BSTR bstrText = NULL;
3473
3474 hr = XmlSelectNodes(pixn, L"Note", &pixnl);
3475 ThmExitOnFailure(hr, "Failed to select child Note nodes.");
3476
3477 hr = pixnl->get_length(reinterpret_cast<long*>(&pControl->cConditionalNotes));
3478 ThmExitOnFailure(hr, "Failed to count the number of Note nodes.");
3479
3480 if (pfAnyChildren)
3481 {
3482 *pfAnyChildren = 0 < pControl->cConditionalNotes;
3483 }
3484
3485 if (0 < pControl->cConditionalNotes)
3486 {
3487 MemAllocArray(reinterpret_cast<LPVOID*>(&pControl->rgConditionalNotes), sizeof(THEME_CONDITIONAL_TEXT), pControl->cConditionalNotes);
3488 ThmExitOnNull(pControl->rgConditionalNotes, hr, E_OUTOFMEMORY, "Failed to allocate note THEME_CONDITIONAL_TEXT structs.");
3489
3490 i = 0;
3491 while (S_OK == (hr = XmlNextElement(pixnl, &pixnChild, NULL)))
3492 {
3493 THEME_CONDITIONAL_TEXT* pConditionalNote = pControl->rgConditionalNotes + i;
3494
3495 hr = XmlGetAttributeEx(pixnChild, L"Condition", &pConditionalNote->sczCondition);
3496 if (E_NOTFOUND == hr)
3497 {
3498 hr = S_OK;
3499 }
3500 ThmExitOnFailure(hr, "Failed when querying Note/@Condition attribute.");
3501
3502 hr = XmlGetText(pixnChild, &bstrText);
3503 ThmExitOnFailure(hr, "Failed to get inner text of Note element.");
3504
3505 if (S_OK == hr)
3506 {
3507 if (pConditionalNote->sczCondition)
3508 {
3509 hr = StrAllocString(&pConditionalNote->sczText, bstrText, 0);
3510 ThmExitOnFailure(hr, "Failed to copy text to conditional note text.");
3511
3512 ++i;
3513 }
3514 else
3515 {
3516 if (pControl->sczNote)
3517 {
3518 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
3519 ThmExitOnFailure(hr, "Unconditional note text for the '%ls' control is specified multiple times.", pControl->sczName);
3520 }
3521
3522 hr = StrAllocString(&pControl->sczNote, bstrText, 0);
3523 ThmExitOnFailure(hr, "Failed to copy text to command link control.");
3524
3525 // Unconditional note entries aren't stored in the conditional notes list.
3526 --pControl->cConditionalNotes;
3527 }
3528 }
3529
3530 ReleaseNullBSTR(bstrText);
3531 }
3532 }
3533
3534LExit:
3535 ReleaseObject(pixnl);
3536 ReleaseObject(pixnChild);
3537 ReleaseBSTR(bstrText);
3538
3539 return hr;
3540}
3541
3542
3543static HRESULT StartBillboard(
3544 __in THEME* pTheme,
3545 __in DWORD dwControl
3546 )
3547{
3548 HRESULT hr = E_NOTFOUND;
3549 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
3550
3551 if (hWnd)
3552 {
3553 THEME_CONTROL* pControl = const_cast<THEME_CONTROL*>(FindControlFromHWnd(pTheme, hWnd));
3554 if (pControl && THEME_CONTROL_TYPE_BILLBOARD == pControl->type)
3555 {
3556 // kick off
3557 pControl->dwData = 0;
3558 OnBillboardTimer(pTheme, pTheme->hwndParent, dwControl);
3559
3560 if (!::SetTimer(pTheme->hwndParent, pControl->wId, pControl->wBillboardInterval, NULL))
3561 {
3562 ThmExitWithLastError(hr, "Failed to start billboard.");
3563 }
3564
3565 hr = S_OK;
3566 }
3567 }
3568
3569LExit:
3570 return hr;
3571}
3572
3573
3574static HRESULT StopBillboard(
3575 __in THEME* pTheme,
3576 __in DWORD dwControl
3577 )
3578{
3579 HRESULT hr = E_NOTFOUND;
3580 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
3581
3582 if (hWnd)
3583 {
3584 const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, hWnd);
3585 if (pControl && THEME_CONTROL_TYPE_BILLBOARD == pControl->type)
3586 {
3587 ThemeControlEnable(pTheme, dwControl, FALSE);
3588
3589 if (::KillTimer(pTheme->hwndParent, pControl->wId))
3590 {
3591 hr = S_OK;
3592 }
3593 }
3594 }
3595
3596 return hr;
3597}
3598
3599static HRESULT EnsureFontInstance(
3600 __in THEME* pTheme,
3601 __in THEME_FONT* pFont,
3602 __out THEME_FONT_INSTANCE** ppFontInstance
3603 )
3604{
3605 HRESULT hr = S_OK;
3606 THEME_FONT_INSTANCE* pFontInstance = NULL;
3607 LOGFONTW lf = { };
3608
3609 for (DWORD i = 0; i < pFont->cFontInstances; ++i)
3610 {
3611 pFontInstance = pFont->rgFontInstances + i;
3612 if (pTheme->nDpi == pFontInstance->nDpi)
3613 {
3614 *ppFontInstance = pFontInstance;
3615 ExitFunction();
3616 }
3617 }
3618
3619 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&pFont->rgFontInstances), pFont->cFontInstances, sizeof(THEME_FONT_INSTANCE), GROW_FONT_INSTANCES);
3620 ThmExitOnFailure(hr, "Failed to allocate memory for font instances.");
3621
3622 pFontInstance = pFont->rgFontInstances + pFont->cFontInstances;
3623 pFontInstance->nDpi = pTheme->nDpi;
3624
3625 lf.lfHeight = DpiuScaleValue(pFont->lfHeight, pFontInstance->nDpi);
3626 lf.lfWeight = pFont->lfWeight;
3627 lf.lfUnderline = pFont->lfUnderline;
3628 lf.lfQuality = pFont->lfQuality;
3629
3630 hr = ::StringCchCopyW(lf.lfFaceName, countof(lf.lfFaceName), pFont->sczFaceName);
3631 ThmExitOnFailure(hr, "Failed to copy font name to create font.");
3632
3633 pFontInstance->hFont = ::CreateFontIndirectW(&lf);
3634 ThmExitOnNull(pFontInstance->hFont, hr, E_OUTOFMEMORY, "Failed to create DPI specific font.");
3635
3636 ++pFont->cFontInstances;
3637 *ppFontInstance = pFontInstance;
3638
3639LExit:
3640 return hr;
3641}
3642
3643
3644static HRESULT FindImageList(
3645 __in THEME* pTheme,
3646 __in_z LPCWSTR wzImageListName,
3647 __out HIMAGELIST *phImageList
3648 )
3649{
3650 HRESULT hr = S_OK;
3651
3652 for (DWORD i = 0; i < pTheme->cImageLists; ++i)
3653 {
3654 if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pTheme->rgImageLists[i].sczName, -1, wzImageListName, -1))
3655 {
3656 *phImageList = pTheme->rgImageLists[i].hImageList;
3657 ExitFunction1(hr = S_OK);
3658 }
3659 }
3660
3661 hr = E_NOTFOUND;
3662
3663LExit:
3664 return hr;
3665}
3666
3667
3668static HRESULT DrawButton(
3669 __in THEME* pTheme,
3670 __in DRAWITEMSTRUCT* pdis,
3671 __in const THEME_CONTROL* pControl
3672 )
3673{
3674 int nSourceX = pControl->hImage ? 0 : pControl->nSourceX;
3675 int nSourceY = pControl->hImage ? 0 : pControl->nSourceY;
3676 DWORD dwSourceWidth = pControl->nDefaultDpiWidth;
3677 DWORD dwSourceHeight = pControl->nDefaultDpiHeight;
3678
3679 HDC hdcMem = ::CreateCompatibleDC(pdis->hDC);
3680 HBITMAP hDefaultBitmap = static_cast<HBITMAP>(::SelectObject(hdcMem, pControl->hImage ? pControl->hImage : pTheme->hImage));
3681
3682 DWORD_PTR dwStyle = ::GetWindowLongPtrW(pdis->hwndItem, GWL_STYLE);
3683 // "clicked" gets priority
3684 if (ODS_SELECTED & pdis->itemState)
3685 {
3686 nSourceY += pControl->nDefaultDpiHeight * 2;
3687 }
3688 // then hover
3689 else if (pControl->dwData & THEME_CONTROL_DATA_HOVER)
3690 {
3691 nSourceY += pControl->nDefaultDpiHeight;
3692 }
3693 // then focused
3694 else if (WS_TABSTOP & dwStyle && ODS_FOCUS & pdis->itemState)
3695 {
3696 nSourceY += pControl->nDefaultDpiHeight * 3;
3697 }
3698
3699 ::StretchBlt(pdis->hDC, 0, 0, pControl->nWidth, pControl->nHeight, hdcMem, nSourceX, nSourceY, dwSourceWidth, dwSourceHeight, SRCCOPY);
3700
3701 ::SelectObject(hdcMem, hDefaultBitmap);
3702 ::DeleteDC(hdcMem);
3703
3704 DrawControlText(pTheme, pdis, pControl, TRUE, FALSE);
3705
3706 return S_OK;
3707}
3708
3709
3710static HRESULT DrawHyperlink(
3711 __in THEME* pTheme,
3712 __in DRAWITEMSTRUCT* pdis,
3713 __in const THEME_CONTROL* pControl
3714 )
3715{
3716 DrawControlText(pTheme, pdis, pControl, FALSE, TRUE);
3717 return S_OK;
3718}
3719
3720
3721static void DrawControlText(
3722 __in THEME* pTheme,
3723 __in DRAWITEMSTRUCT* pdis,
3724 __in const THEME_CONTROL* pControl,
3725 __in BOOL fCentered,
3726 __in BOOL fDrawFocusRect
3727 )
3728{
3729 HRESULT hr = S_OK;
3730 WCHAR wzText[256] = { };
3731 DWORD cchText = 0;
3732 THEME_FONT* pFont = NULL;
3733 THEME_FONT_INSTANCE* pFontInstance = NULL;
3734 HFONT hfPrev = NULL;
3735
3736 if (0 == (cchText = ::GetWindowTextW(pdis->hwndItem, wzText, countof(wzText))))
3737 {
3738 // nothing to do
3739 return;
3740 }
3741
3742 if (ODS_SELECTED & pdis->itemState)
3743 {
3744 pFont = pTheme->rgFonts + (THEME_INVALID_ID != pControl->dwFontSelectedId ? pControl->dwFontSelectedId : pControl->dwFontId);
3745 }
3746 else if (pControl->dwData & THEME_CONTROL_DATA_HOVER)
3747 {
3748 pFont = pTheme->rgFonts + (THEME_INVALID_ID != pControl->dwFontHoverId ? pControl->dwFontHoverId : pControl->dwFontId);
3749 }
3750 else
3751 {
3752 pFont = pTheme->rgFonts + pControl->dwFontId;
3753 }
3754
3755 hr = EnsureFontInstance(pTheme, pFont, &pFontInstance);
3756 if (SUCCEEDED(hr))
3757 {
3758 hfPrev = SelectFont(pdis->hDC, pFontInstance->hFont);
3759 }
3760
3761 ::DrawTextExW(pdis->hDC, wzText, cchText, &pdis->rcItem, DT_SINGLELINE | (fCentered ? (DT_CENTER | DT_VCENTER) : 0), NULL);
3762
3763 if (fDrawFocusRect && (WS_TABSTOP & ::GetWindowLongPtrW(pdis->hwndItem, GWL_STYLE)) && (ODS_FOCUS & pdis->itemState))
3764 {
3765 ::DrawFocusRect(pdis->hDC, &pdis->rcItem);
3766 }
3767
3768 if (hfPrev)
3769 {
3770 SelectFont(pdis->hDC, hfPrev);
3771 }
3772}
3773
3774
3775static HRESULT DrawImage(
3776 __in THEME* pTheme,
3777 __in DRAWITEMSTRUCT* pdis,
3778 __in const THEME_CONTROL* pControl
3779 )
3780{
3781 DWORD dwHeight = pdis->rcItem.bottom - pdis->rcItem.top;
3782 DWORD dwWidth = pdis->rcItem.right - pdis->rcItem.left;
3783 int nSourceX = pControl->hImage ? 0 : pControl->nSourceX;
3784 int nSourceY = pControl->hImage ? 0 : pControl->nSourceY;
3785 DWORD dwSourceHeight = pControl->nDefaultDpiHeight;
3786 DWORD dwSourceWidth = pControl->nDefaultDpiWidth;
3787
3788 BLENDFUNCTION bf = { };
3789 bf.BlendOp = AC_SRC_OVER;
3790 bf.SourceConstantAlpha = 255;
3791 bf.AlphaFormat = AC_SRC_ALPHA;
3792
3793 HDC hdcMem = ::CreateCompatibleDC(pdis->hDC);
3794 HBITMAP hDefaultBitmap = static_cast<HBITMAP>(::SelectObject(hdcMem, pControl->hImage ? pControl->hImage : pTheme->hImage));
3795
3796 // Try to draw the image with transparency and if that fails (usually because the image has no
3797 // alpha channel) then draw the image as is.
3798 if (!::AlphaBlend(pdis->hDC, 0, 0, dwWidth, dwHeight, hdcMem, nSourceX, nSourceY, dwSourceWidth, dwSourceHeight, bf))
3799 {
3800 ::StretchBlt(pdis->hDC, 0, 0, dwWidth, dwHeight, hdcMem, nSourceX, nSourceY, dwSourceWidth, dwSourceHeight, SRCCOPY);
3801 }
3802
3803 ::SelectObject(hdcMem, hDefaultBitmap);
3804 ::DeleteDC(hdcMem);
3805 return S_OK;
3806}
3807
3808
3809static HRESULT DrawProgressBar(
3810 __in THEME* pTheme,
3811 __in DRAWITEMSTRUCT* pdis,
3812 __in const THEME_CONTROL* pControl
3813 )
3814{
3815 DWORD dwProgressColor = HIWORD(pControl->dwData);
3816 DWORD dwProgressPercentage = LOWORD(pControl->dwData);
3817 DWORD dwHeight = pdis->rcItem.bottom - pdis->rcItem.top;
3818 DWORD dwCenter = (pdis->rcItem.right - 2) * dwProgressPercentage / 100;
3819 DWORD dwSourceHeight = pControl->nDefaultDpiHeight;
3820 int nSourceX = pControl->hImage ? 0 : pControl->nSourceX;
3821 int nSourceY = (pControl->hImage ? 0 : pControl->nSourceY) + (dwProgressColor * dwSourceHeight);
3822
3823 HDC hdcMem = ::CreateCompatibleDC(pdis->hDC);
3824 HBITMAP hDefaultBitmap = static_cast<HBITMAP>(::SelectObject(hdcMem, pControl->hImage ? pControl->hImage : pTheme->hImage));
3825
3826 // Draw the left side of the progress bar.
3827 ::StretchBlt(pdis->hDC, 0, 0, 1, dwHeight, hdcMem, nSourceX, nSourceY, 1, dwSourceHeight, SRCCOPY);
3828
3829 // Draw the filled side of the progress bar, if there is any.
3830 if (0 < dwCenter)
3831 {
3832 ::StretchBlt(pdis->hDC, 1, 0, dwCenter, dwHeight, hdcMem, nSourceX + 1, nSourceY, 1, dwSourceHeight, SRCCOPY);
3833 }
3834
3835 // Draw the unfilled side of the progress bar, if there is any.
3836 if (dwCenter < static_cast<DWORD>(pdis->rcItem.right - 2))
3837 {
3838 ::StretchBlt(pdis->hDC, 1 + dwCenter, 0, pdis->rcItem.right - dwCenter - 1, dwHeight, hdcMem, nSourceX + 2, nSourceY, 1, dwSourceHeight, SRCCOPY);
3839 }
3840
3841 // Draw the right side of the progress bar.
3842 ::StretchBlt(pdis->hDC, pdis->rcItem.right - 1, 0, 1, dwHeight, hdcMem, nSourceX + 3, nSourceY, 1, dwSourceHeight, SRCCOPY);
3843
3844 ::SelectObject(hdcMem, hDefaultBitmap);
3845 ::DeleteDC(hdcMem);
3846 return S_OK;
3847}
3848
3849
3850static BOOL DrawHoverControl(
3851 __in THEME* pTheme,
3852 __in BOOL fHover
3853 )
3854{
3855 BOOL fChangedHover = FALSE;
3856 THEME_CONTROL* pControl = const_cast<THEME_CONTROL*>(FindControlFromHWnd(pTheme, pTheme->hwndHover));
3857
3858 // Only hyperlinks and owner-drawn buttons have hover states.
3859 if (pControl && (THEME_CONTROL_TYPE_HYPERLINK == pControl->type ||
3860 (THEME_CONTROL_TYPE_BUTTON == pControl->type && (pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_OWNER_DRAW))))
3861 {
3862 if (fHover)
3863 {
3864 pControl->dwData |= THEME_CONTROL_DATA_HOVER;
3865 }
3866 else
3867 {
3868 pControl->dwData &= ~THEME_CONTROL_DATA_HOVER;
3869 }
3870
3871 ::InvalidateRect(pControl->hWnd, NULL, FALSE);
3872 fChangedHover = TRUE;
3873 }
3874
3875 return fChangedHover;
3876}
3877
3878
3879static void FreePage(
3880 __in THEME_PAGE* pPage
3881 )
3882{
3883 if (pPage)
3884 {
3885 ReleaseStr(pPage->sczName);
3886
3887 if (pPage->cSavedVariables)
3888 {
3889 for (DWORD i = 0; i < pPage->cSavedVariables; ++i)
3890 {
3891 ReleaseStr(pPage->rgSavedVariables[i].sczValue);
3892 }
3893 }
3894
3895 ReleaseMem(pPage->rgSavedVariables);
3896 }
3897}
3898
3899
3900static void FreeImageList(
3901 __in THEME_IMAGELIST* pImageList
3902 )
3903{
3904 if (pImageList)
3905 {
3906 ReleaseStr(pImageList->sczName);
3907 ImageList_Destroy(pImageList->hImageList);
3908 }
3909}
3910
3911static void FreeControl(
3912 __in THEME_CONTROL* pControl
3913 )
3914{
3915 if (pControl)
3916 {
3917 if (::IsWindow(pControl->hWnd))
3918 {
3919 ::CloseWindow(pControl->hWnd);
3920 pControl->hWnd = NULL;
3921 }
3922
3923 ReleaseStr(pControl->sczName);
3924 ReleaseStr(pControl->sczText);
3925 ReleaseStr(pControl->sczTooltip);
3926 ReleaseStr(pControl->sczNote);
3927 ReleaseStr(pControl->sczEnableCondition);
3928 ReleaseStr(pControl->sczVisibleCondition);
3929 ReleaseStr(pControl->sczValue);
3930 ReleaseStr(pControl->sczVariable);
3931
3932 if (pControl->hImage)
3933 {
3934 ::DeleteBitmap(pControl->hImage);
3935 }
3936
3937 for (DWORD i = 0; i < pControl->cControls; ++i)
3938 {
3939 FreeControl(pControl->rgControls + i);
3940 }
3941
3942 for (DWORD i = 0; i < pControl->cActions; ++i)
3943 {
3944 FreeAction(&(pControl->rgActions[i]));
3945 }
3946
3947 for (DWORD i = 0; i < pControl->cColumns; ++i)
3948 {
3949 FreeColumn(&(pControl->ptcColumns[i]));
3950 }
3951
3952 for (DWORD i = 0; i < pControl->cConditionalText; ++i)
3953 {
3954 FreeConditionalText(&(pControl->rgConditionalText[i]));
3955 }
3956
3957 for (DWORD i = 0; i < pControl->cConditionalNotes; ++i)
3958 {
3959 FreeConditionalText(&(pControl->rgConditionalNotes[i]));
3960 }
3961
3962 for (DWORD i = 0; i < pControl->cTabs; ++i)
3963 {
3964 FreeTab(&(pControl->pttTabs[i]));
3965 }
3966
3967 ReleaseMem(pControl->rgActions)
3968 ReleaseMem(pControl->ptcColumns);
3969 ReleaseMem(pControl->rgConditionalText);
3970 ReleaseMem(pControl->rgConditionalNotes);
3971 ReleaseMem(pControl->pttTabs);
3972 }
3973}
3974
3975
3976static void FreeAction(
3977 __in THEME_ACTION* pAction
3978 )
3979{
3980 switch (pAction->type)
3981 {
3982 case THEME_ACTION_TYPE_BROWSE_DIRECTORY:
3983 ReleaseStr(pAction->BrowseDirectory.sczVariableName);
3984 break;
3985 case THEME_ACTION_TYPE_CHANGE_PAGE:
3986 ReleaseStr(pAction->ChangePage.sczPageName);
3987 break;
3988 }
3989
3990 ReleaseStr(pAction->sczCondition);
3991}
3992
3993
3994static void FreeColumn(
3995 __in THEME_COLUMN* pColumn
3996 )
3997{
3998 ReleaseStr(pColumn->pszName);
3999}
4000
4001
4002static void FreeConditionalText(
4003 __in THEME_CONDITIONAL_TEXT* pConditionalText
4004 )
4005{
4006 ReleaseStr(pConditionalText->sczCondition);
4007 ReleaseStr(pConditionalText->sczText);
4008}
4009
4010
4011static void FreeTab(
4012 __in THEME_TAB* pTab
4013 )
4014{
4015 ReleaseStr(pTab->pszName);
4016}
4017
4018
4019static void FreeFontInstance(
4020 __in THEME_FONT_INSTANCE* pFontInstance
4021 )
4022{
4023 if (pFontInstance->hFont)
4024 {
4025 ::DeleteObject(pFontInstance->hFont);
4026 pFontInstance->hFont = NULL;
4027 }
4028}
4029
4030
4031static void FreeFont(
4032 __in THEME_FONT* pFont
4033 )
4034{
4035 if (pFont)
4036 {
4037 if (pFont->hBackground)
4038 {
4039 ::DeleteObject(pFont->hBackground);
4040 pFont->hBackground = NULL;
4041 }
4042
4043 if (pFont->hForeground)
4044 {
4045 ::DeleteObject(pFont->hForeground);
4046 pFont->hForeground = NULL;
4047 }
4048
4049 for (DWORD i = 0; i < pFont->cFontInstances; ++i)
4050 {
4051 FreeFontInstance(&(pFont->rgFontInstances[i]));
4052 }
4053
4054 ReleaseMem(pFont->rgFontInstances);
4055 ReleaseStr(pFont->sczFaceName);
4056 }
4057}
4058
4059
4060static DWORD CALLBACK RichEditStreamFromFileHandleCallback(
4061 __in DWORD_PTR dwCookie,
4062 __in_bcount(cb) LPBYTE pbBuff,
4063 __in LONG cb,
4064 __in LONG* pcb
4065 )
4066{
4067 HRESULT hr = S_OK;
4068 HANDLE hFile = reinterpret_cast<HANDLE>(dwCookie);
4069
4070 if (!::ReadFile(hFile, pbBuff, cb, reinterpret_cast<DWORD*>(pcb), NULL))
4071 {
4072 ThmExitWithLastError(hr, "Failed to read file");
4073 }
4074
4075LExit:
4076 return hr;
4077}
4078
4079
4080static DWORD CALLBACK RichEditStreamFromMemoryCallback(
4081 __in DWORD_PTR dwCookie,
4082 __in_bcount(cb) LPBYTE pbBuff,
4083 __in LONG cb,
4084 __in LONG* pcb
4085 )
4086{
4087 HRESULT hr = S_OK;
4088 MEMBUFFER_FOR_RICHEDIT* pBuffer = reinterpret_cast<MEMBUFFER_FOR_RICHEDIT*>(dwCookie);
4089 DWORD cbCopy = 0;
4090
4091 if (pBuffer->iData < pBuffer->cbData)
4092 {
4093 cbCopy = min(static_cast<DWORD>(cb), pBuffer->cbData - pBuffer->iData);
4094 memcpy(pbBuff, pBuffer->rgbData + pBuffer->iData, cbCopy);
4095
4096 pBuffer->iData += cbCopy;
4097 Assert(pBuffer->iData <= pBuffer->cbData);
4098 }
4099
4100 *pcb = cbCopy;
4101 return hr;
4102}
4103
4104
4105static void CALLBACK OnBillboardTimer(
4106 __in THEME* pTheme,
4107 __in HWND hwnd,
4108 __in UINT_PTR idEvent
4109 )
4110{
4111 HWND hwndControl = ::GetDlgItem(hwnd, static_cast<int>(idEvent));
4112 if (hwndControl)
4113 {
4114 THEME_CONTROL* pControl = const_cast<THEME_CONTROL*>(FindControlFromHWnd(pTheme, hwndControl));
4115 AssertSz(pControl && THEME_CONTROL_TYPE_BILLBOARD == pControl->type, "Only billboard controls should get billboard timer messages.");
4116
4117 if (pControl)
4118 {
4119 if (pControl->dwData < pControl->cControls)
4120 {
4121 ThemeShowChild(pTheme, pControl, pControl->dwData);
4122 }
4123 else if (pControl->fBillboardLoops)
4124 {
4125 pControl->dwData = 0;
4126 ThemeShowChild(pTheme, pControl, pControl->dwData);
4127 }
4128 else // no more looping
4129 {
4130 ::KillTimer(hwnd, idEvent);
4131 }
4132
4133 ++pControl->dwData;
4134 }
4135 }
4136}
4137
4138static void OnBrowseDirectory(
4139 __in THEME* pTheme,
4140 __in HWND hWnd,
4141 __in const THEME_ACTION* pAction
4142 )
4143{
4144 HRESULT hr = S_OK;
4145 WCHAR wzPath[MAX_PATH] = { };
4146 BROWSEINFOW browseInfo = { };
4147 PIDLIST_ABSOLUTE pidl = NULL;
4148
4149 browseInfo.hwndOwner = hWnd;
4150 browseInfo.pszDisplayName = wzPath;
4151 browseInfo.lpszTitle = pTheme->sczCaption;
4152 browseInfo.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI;
4153 pidl = ::SHBrowseForFolderW(&browseInfo);
4154 if (pidl && ::SHGetPathFromIDListW(pidl, wzPath))
4155 {
4156 // Since editbox changes aren't immediately saved off, we have to treat them differently.
4157 THEME_CONTROL* pTargetControl = NULL;
4158
4159 for (DWORD i = 0; i < pTheme->cControls; ++i)
4160 {
4161 THEME_CONTROL* pControl = pTheme->rgControls + i;
4162
4163 if ((!pControl->wPageId || pControl->wPageId == pTheme->dwCurrentPageId) &&
4164 CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pControl->sczName, -1, pAction->BrowseDirectory.sczVariableName, -1))
4165 {
4166 pTargetControl = pControl;
4167 break;
4168 }
4169 }
4170
4171 if (pTargetControl && THEME_CONTROL_TYPE_EDITBOX == pTargetControl->type && !pTargetControl->fDisableVariableFunctionality)
4172 {
4173 hr = ThemeSetTextControl(pTheme, pTargetControl->wId, wzPath);
4174 ThmExitOnFailure(hr, "Failed to set text on editbox: %ls", pTargetControl->sczName);
4175 }
4176 else if (pTheme->pfnSetStringVariable)
4177 {
4178 hr = pTheme->pfnSetStringVariable(pAction->BrowseDirectory.sczVariableName, wzPath, FALSE, pTheme->pvVariableContext);
4179 ThmExitOnFailure(hr, "Failed to set variable: %ls", pAction->BrowseDirectory.sczVariableName);
4180 }
4181 else if (pTargetControl)
4182 {
4183 hr = ThemeSetTextControl(pTheme, pTargetControl->wId, wzPath);
4184 ThmExitOnFailure(hr, "Failed to set text on control: %ls", pTargetControl->sczName);
4185 }
4186
4187 ThemeShowPageEx(pTheme, pTheme->dwCurrentPageId, SW_SHOW, THEME_SHOW_PAGE_REASON_REFRESH);
4188 }
4189
4190LExit:
4191 if (pidl)
4192 {
4193 ::CoTaskMemFree(pidl);
4194 }
4195}
4196
4197static BOOL OnButtonClicked(
4198 __in THEME* pTheme,
4199 __in HWND hWnd,
4200 __in const THEME_CONTROL* pControl
4201 )
4202{
4203 HRESULT hr = S_OK;
4204 BOOL fHandled = FALSE;
4205
4206 if (THEME_CONTROL_TYPE_BUTTON == pControl->type || THEME_CONTROL_TYPE_COMMANDLINK == pControl->type)
4207 {
4208 if (pControl->cActions)
4209 {
4210 fHandled = TRUE;
4211 THEME_ACTION* pChosenAction = pControl->pDefaultAction;
4212
4213 if (pTheme->pfnEvaluateCondition)
4214 {
4215 // As documented in the xsd, if there are multiple conditions that are true at the same time then the behavior is undefined.
4216 // This is the current implementation and can change at any time.
4217 for (DWORD j = 0; j < pControl->cActions; ++j)
4218 {
4219 THEME_ACTION* pAction = pControl->rgActions + j;
4220
4221 if (pAction->sczCondition)
4222 {
4223 BOOL fCondition = FALSE;
4224
4225 hr = pTheme->pfnEvaluateCondition(pAction->sczCondition, &fCondition, pTheme->pvVariableContext);
4226 ThmExitOnFailure(hr, "Failed to evaluate condition: %ls", pAction->sczCondition);
4227
4228 if (fCondition)
4229 {
4230 pChosenAction = pAction;
4231 break;
4232 }
4233 }
4234 }
4235 }
4236
4237 if (pChosenAction)
4238 {
4239 switch (pChosenAction->type)
4240 {
4241 case THEME_ACTION_TYPE_BROWSE_DIRECTORY:
4242 OnBrowseDirectory(pTheme, hWnd, pChosenAction);
4243 break;
4244
4245 case THEME_ACTION_TYPE_CLOSE_WINDOW:
4246 ::SendMessageW(hWnd, WM_CLOSE, 0, 0);
4247 break;
4248
4249 case THEME_ACTION_TYPE_CHANGE_PAGE:
4250 DWORD dwPageId = 0;
4251 LPCWSTR pPageNames = pChosenAction->ChangePage.sczPageName;
4252 ThemeGetPageIds(pTheme, &pPageNames, &dwPageId, 1);
4253
4254 if (!dwPageId)
4255 {
4256 ThmExitOnFailure(E_INVALIDDATA, "Unknown page: %ls", pChosenAction->ChangePage.sczPageName);
4257 }
4258
4259 ThemeShowPageEx(pTheme, pTheme->dwCurrentPageId, SW_HIDE, pChosenAction->ChangePage.fCancel ? THEME_SHOW_PAGE_REASON_CANCEL : THEME_SHOW_PAGE_REASON_DEFAULT);
4260 ThemeShowPage(pTheme, dwPageId, SW_SHOW);
4261 break;
4262 }
4263 }
4264 }
4265 }
4266 else if (!pControl->fDisableVariableFunctionality && (pTheme->pfnSetNumericVariable || pTheme->pfnSetStringVariable))
4267 {
4268 BOOL fRefresh = FALSE;
4269
4270 switch (pControl->type)
4271 {
4272 case THEME_CONTROL_TYPE_CHECKBOX:
4273 if (pTheme->pfnSetNumericVariable && pControl->sczName && *pControl->sczName)
4274 {
4275 BOOL fChecked = ThemeIsControlChecked(pTheme, pControl->wId);
4276 pTheme->pfnSetNumericVariable(pControl->sczName, fChecked ? 1 : 0, pTheme->pvVariableContext);
4277 fRefresh = TRUE;
4278 }
4279 break;
4280 case THEME_CONTROL_TYPE_RADIOBUTTON:
4281 if (pTheme->pfnSetStringVariable && pControl->sczVariable && *pControl->sczVariable && ThemeIsControlChecked(pTheme, pControl->wId))
4282 {
4283 pTheme->pfnSetStringVariable(pControl->sczVariable, pControl->sczValue, FALSE, pTheme->pvVariableContext);
4284 fRefresh = TRUE;
4285 }
4286 break;
4287 }
4288
4289 if (fRefresh)
4290 {
4291 ThemeShowPageEx(pTheme, pTheme->dwCurrentPageId, SW_SHOW, THEME_SHOW_PAGE_REASON_REFRESH);
4292 fHandled = TRUE;
4293 }
4294 }
4295
4296LExit:
4297 return fHandled;
4298}
4299
4300static BOOL OnDpiChanged(
4301 __in THEME* pTheme,
4302 __in WPARAM wParam,
4303 __in LPARAM lParam
4304 )
4305{
4306 UINT nDpi = HIWORD(wParam);
4307 RECT* pRect = reinterpret_cast<RECT*>(lParam);
4308 BOOL fIgnored = pTheme->nDpi == nDpi;
4309
4310 if (fIgnored)
4311 {
4312 ExitFunction();
4313 }
4314
4315
4316 pTheme->fForceResize = !pTheme->fAutoResize;
4317 ScaleThemeFromWindow(pTheme, nDpi, pRect->left, pRect->top);
4318
4319LExit:
4320 return !fIgnored;
4321}
4322
4323static void OnNcCreate(
4324 __in THEME* pTheme,
4325 __in HWND hWnd,
4326 __in LPARAM lParam
4327 )
4328{
4329 DPIU_WINDOW_CONTEXT windowContext = { };
4330 CREATESTRUCTW* pCreateStruct = reinterpret_cast<CREATESTRUCTW*>(lParam);
4331
4332 pTheme->hwndParent = hWnd;
4333
4334 DpiuGetWindowContext(pTheme->hwndParent, &windowContext);
4335
4336 if (windowContext.nDpi != pTheme->nDpi)
4337 {
4338 ScaleTheme(pTheme, windowContext.nDpi, pCreateStruct->x, pCreateStruct->y, pCreateStruct->style, NULL != pCreateStruct->hMenu, pCreateStruct->dwExStyle);
4339 }
4340}
4341
4342static HRESULT OnRichEditEnLink(
4343 __in LPARAM lParam,
4344 __in HWND hWndRichEdit,
4345 __in HWND hWnd
4346 )
4347{
4348 HRESULT hr = S_OK;
4349 LPWSTR sczLink = NULL;
4350 ENLINK* link = reinterpret_cast<ENLINK*>(lParam);
4351
4352 switch (link->msg)
4353 {
4354 case WM_LBUTTONDOWN:
4355 {
4356 hr = StrAlloc(&sczLink, link->chrg.cpMax - link->chrg.cpMin + 2);
4357 ThmExitOnFailure(hr, "Failed to allocate string for link.");
4358
4359 TEXTRANGEW tr;
4360 tr.chrg.cpMin = link->chrg.cpMin;
4361 tr.chrg.cpMax = link->chrg.cpMax;
4362 tr.lpstrText = sczLink;
4363
4364 if (0 < ::SendMessageW(hWndRichEdit, EM_GETTEXTRANGE, 0, reinterpret_cast<LPARAM>(&tr)))
4365 {
4366 hr = ShelExec(sczLink, NULL, L"open", NULL, SW_SHOWDEFAULT, hWnd, NULL);
4367 ThmExitOnFailure(hr, "Failed to launch link: %ls", sczLink);
4368 }
4369
4370 break;
4371 }
4372
4373 case WM_SETCURSOR:
4374 ::SetCursor(vhCursorHand);
4375 break;
4376 }
4377
4378LExit:
4379 ReleaseStr(sczLink);
4380
4381 return hr;
4382}
4383
4384static BOOL ControlIsType(
4385 __in const THEME* pTheme,
4386 __in DWORD dwControl,
4387 __in const THEME_CONTROL_TYPE type
4388 )
4389{
4390 BOOL fIsType = FALSE;
4391 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
4392 if (hWnd)
4393 {
4394 const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, hWnd);
4395 fIsType = (pControl && type == pControl->type);
4396 }
4397
4398 return fIsType;
4399}
4400
4401static const THEME_CONTROL* FindControlFromHWnd(
4402 __in const THEME* pTheme,
4403 __in HWND hWnd,
4404 __in_opt const THEME_CONTROL* pParentControl
4405 )
4406{
4407 DWORD cControls = 0;
4408 THEME_CONTROL* rgControls = NULL;
4409
4410 GetControls(pTheme, pParentControl, cControls, rgControls);
4411
4412 // As we can't use GWLP_USERDATA (SysLink controls on Windows XP uses it too)...
4413 for (DWORD i = 0; i < cControls; ++i)
4414 {
4415 if (hWnd == rgControls[i].hWnd)
4416 {
4417 return rgControls + i;
4418 }
4419 else if (0 < rgControls[i].cControls)
4420 {
4421 const THEME_CONTROL* pChildControl = FindControlFromHWnd(pTheme, hWnd, rgControls + i);
4422 if (pChildControl)
4423 {
4424 return pChildControl;
4425 }
4426 }
4427 }
4428
4429 return NULL;
4430}
4431
4432static void GetControlDimensions(
4433 __in const THEME_CONTROL* pControl,
4434 __in const RECT* prcParent,
4435 __out int* piWidth,
4436 __out int* piHeight,
4437 __out int* piX,
4438 __out int* piY
4439 )
4440{
4441 *piWidth = pControl->nWidth + (0 < pControl->nWidth ? 0 : prcParent->right - max(0, pControl->nX));
4442 *piHeight = pControl->nHeight + (0 < pControl->nHeight ? 0 : prcParent->bottom - max(0, pControl->nY));
4443 *piX = pControl->nX + (-1 < pControl->nX ? 0 : prcParent->right - *piWidth);
4444 *piY = pControl->nY + (-1 < pControl->nY ? 0 : prcParent->bottom - *piHeight);
4445}
4446
4447static HRESULT SizeListViewColumns(
4448 __inout THEME_CONTROL* pControl
4449 )
4450{
4451 HRESULT hr = S_OK;
4452 RECT rcParent = { };
4453 int cNumExpandingColumns = 0;
4454 int iExtraAvailableSize;
4455
4456 if (!::GetWindowRect(pControl->hWnd, &rcParent))
4457 {
4458 ThmExitWithLastError(hr, "Failed to get window rect of listview control.");
4459 }
4460
4461 iExtraAvailableSize = rcParent.right - rcParent.left;
4462
4463 for (DWORD i = 0; i < pControl->cColumns; ++i)
4464 {
4465 if (pControl->ptcColumns[i].fExpands)
4466 {
4467 ++cNumExpandingColumns;
4468 }
4469
4470 iExtraAvailableSize -= pControl->ptcColumns[i].nBaseWidth;
4471 }
4472
4473 // Leave room for a vertical scroll bar just in case.
4474 iExtraAvailableSize -= ::GetSystemMetrics(SM_CXVSCROLL);
4475
4476 for (DWORD i = 0; i < pControl->cColumns; ++i)
4477 {
4478 if (pControl->ptcColumns[i].fExpands)
4479 {
4480 pControl->ptcColumns[i].nWidth = pControl->ptcColumns[i].nBaseWidth + (iExtraAvailableSize / cNumExpandingColumns);
4481 // In case there is any remainder, use it up the first chance we get.
4482 pControl->ptcColumns[i].nWidth += iExtraAvailableSize % cNumExpandingColumns;
4483 iExtraAvailableSize -= iExtraAvailableSize % cNumExpandingColumns;
4484 }
4485 else
4486 {
4487 pControl->ptcColumns[i].nWidth = pControl->ptcColumns[i].nBaseWidth;
4488 }
4489 }
4490
4491LExit:
4492 return hr;
4493}
4494
4495
4496static HRESULT ShowControl(
4497 __in THEME* pTheme,
4498 __in THEME_CONTROL* pControl,
4499 __in int nCmdShow,
4500 __in BOOL fSaveEditboxes,
4501 __in THEME_SHOW_PAGE_REASON reason,
4502 __in DWORD dwPageId,
4503 __out_opt HWND* phwndFocus
4504 )
4505{
4506 HRESULT hr = S_OK;
4507 DWORD iPageControl = 0;
4508 HWND hwndFocus = NULL;
4509 LPWSTR sczFormatString = NULL;
4510 LPWSTR sczText = NULL;
4511 THEME_SAVEDVARIABLE* pSavedVariable = NULL;
4512 BOOL fHide = SW_HIDE == nCmdShow;
4513 THEME_PAGE* pPage = ThemeGetPage(pTheme, dwPageId);
4514
4515 // Save the editbox value if necessary (other control types save their values immediately).
4516 if (pTheme->pfnSetStringVariable && !pControl->fDisableVariableFunctionality &&
4517 fSaveEditboxes && THEME_CONTROL_TYPE_EDITBOX == pControl->type && pControl->sczName && *pControl->sczName)
4518 {
4519 hr = ThemeGetTextControl(pTheme, pControl->wId, &sczText);
4520 ThmExitOnFailure(hr, "Failed to get the text for control: %ls", pControl->sczName);
4521
4522 hr = pTheme->pfnSetStringVariable(pControl->sczName, sczText, FALSE, pTheme->pvVariableContext);
4523 ThmExitOnFailure(hr, "Failed to set the variable '%ls' to '%ls'", pControl->sczName, sczText);
4524 }
4525
4526 HWND hWnd = pControl->hWnd;
4527
4528 if (fHide && pControl->wPageId)
4529 {
4530 ::ShowWindow(hWnd, SW_HIDE);
4531
4532 if (THEME_CONTROL_TYPE_BILLBOARD == pControl->type)
4533 {
4534 StopBillboard(pTheme, pControl->wId);
4535 }
4536
4537 ExitFunction();
4538 }
4539
4540 BOOL fEnabled = !(pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_DISABLED);
4541 BOOL fVisible = !(pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_HIDDEN);
4542
4543 if (!pControl->fDisableVariableFunctionality)
4544 {
4545 if (pTheme->pfnEvaluateCondition)
4546 {
4547 // If the control has a VisibleCondition, check if it's true.
4548 if (pControl->sczVisibleCondition)
4549 {
4550 hr = pTheme->pfnEvaluateCondition(pControl->sczVisibleCondition, &fVisible, pTheme->pvVariableContext);
4551 ThmExitOnFailure(hr, "Failed to evaluate VisibleCondition: %ls", pControl->sczVisibleCondition);
4552 }
4553
4554 // If the control has an EnableCondition, check if it's true.
4555 if (pControl->sczEnableCondition)
4556 {
4557 hr = pTheme->pfnEvaluateCondition(pControl->sczEnableCondition, &fEnabled, pTheme->pvVariableContext);
4558 ThmExitOnFailure(hr, "Failed to evaluate EnableCondition: %ls", pControl->sczEnableCondition);
4559 }
4560 }
4561
4562 // Try to format each control's text based on context, except for editboxes since their text comes from the user.
4563 if (pTheme->pfnFormatString && ((pControl->sczText && *pControl->sczText) || pControl->cConditionalText) && THEME_CONTROL_TYPE_EDITBOX != pControl->type)
4564 {
4565 LPWSTR wzText = pControl->sczText;
4566 LPWSTR wzNote = pControl->sczNote;
4567
4568 if (pTheme->pfnEvaluateCondition)
4569 {
4570 // As documented in the xsd, if there are multiple conditions that are true at the same time then the behavior is undefined.
4571 // This is the current implementation and can change at any time.
4572 for (DWORD j = 0; j < pControl->cConditionalText; ++j)
4573 {
4574 THEME_CONDITIONAL_TEXT* pConditionalText = pControl->rgConditionalText + j;
4575 wzText = pConditionalText->sczText;
4576
4577 if (pConditionalText->sczCondition)
4578 {
4579 BOOL fCondition = FALSE;
4580
4581 hr = pTheme->pfnEvaluateCondition(pConditionalText->sczCondition, &fCondition, pTheme->pvVariableContext);
4582 ThmExitOnFailure(hr, "Failed to evaluate condition: %ls", pConditionalText->sczCondition);
4583
4584 if (fCondition)
4585 {
4586 wzText = pConditionalText->sczText;
4587 break;
4588 }
4589 }
4590 }
4591
4592 for (DWORD j = 0; j < pControl->cConditionalNotes; ++j)
4593 {
4594 THEME_CONDITIONAL_TEXT* pConditionalNote = pControl->rgConditionalNotes + j;
4595 wzNote = pConditionalNote->sczText;
4596
4597 if (pConditionalNote->sczCondition)
4598 {
4599 BOOL fCondition = FALSE;
4600
4601 hr = pTheme->pfnEvaluateCondition(pConditionalNote->sczCondition, &fCondition, pTheme->pvVariableContext);
4602 ThmExitOnFailure(hr, "Failed to evaluate note condition: %ls", pConditionalNote->sczCondition);
4603
4604 if (fCondition)
4605 {
4606 wzNote = pConditionalNote->sczText;
4607 break;
4608 }
4609 }
4610 }
4611 }
4612
4613 if (wzText && *wzText)
4614 {
4615 hr = pTheme->pfnFormatString(wzText, &sczText, pTheme->pvVariableContext);
4616 ThmExitOnFailure(hr, "Failed to format string: %ls", wzText);
4617 }
4618 else
4619 {
4620 ReleaseNullStr(sczText);
4621 }
4622
4623 ThemeSetTextControl(pTheme, pControl->wId, sczText);
4624
4625 if (wzNote && *wzNote)
4626 {
4627 hr = pTheme->pfnFormatString(wzNote, &sczText, pTheme->pvVariableContext);
4628 ThmExitOnFailure(hr, "Failed to format note: %ls", wzNote);
4629 }
4630 else
4631 {
4632 ReleaseNullStr(sczText);
4633 }
4634
4635 ::SendMessageW(pControl->hWnd, BCM_SETNOTE, 0, reinterpret_cast<WPARAM>(sczText));
4636 }
4637
4638 // If this is a named control, do variable magic.
4639 if (pControl->sczName && *pControl->sczName)
4640 {
4641 // If this is a checkbox control,
4642 // try to set its default state to the state of a matching named variable.
4643 if (pTheme->pfnGetNumericVariable && THEME_CONTROL_TYPE_CHECKBOX == pControl->type)
4644 {
4645 LONGLONG llValue = 0;
4646 hr = pTheme->pfnGetNumericVariable(pControl->sczName, &llValue, pTheme->pvVariableContext);
4647 if (E_NOTFOUND == hr)
4648 {
4649 hr = S_OK;
4650 }
4651 ThmExitOnFailure(hr, "Failed to get numeric variable: %ls", pControl->sczName);
4652
4653 if (THEME_SHOW_PAGE_REASON_REFRESH != reason && pPage && pControl->wPageId)
4654 {
4655 pSavedVariable = pPage->rgSavedVariables + iPageControl;
4656 pSavedVariable->wzName = pControl->sczName;
4657
4658 if (SUCCEEDED(hr))
4659 {
4660 hr = StrAllocFormattedSecure(&pSavedVariable->sczValue, L"%lld", llValue);
4661 ThmExitOnFailure(hr, "Failed to save variable: %ls", pControl->sczName);
4662 }
4663
4664 ++iPageControl;
4665 }
4666
4667 ThemeSendControlMessage(pTheme, pControl->wId, BM_SETCHECK, SUCCEEDED(hr) && llValue ? BST_CHECKED : BST_UNCHECKED, 0);
4668 }
4669
4670 // If this is an editbox control,
4671 // try to set its default state to the state of a matching named variable.
4672 if (pTheme->pfnFormatString && THEME_CONTROL_TYPE_EDITBOX == pControl->type)
4673 {
4674 hr = StrAllocFormatted(&sczFormatString, L"[%ls]", pControl->sczName);
4675 ThmExitOnFailure(hr, "Failed to create format string: '%ls'", pControl->sczName);
4676
4677 hr = pTheme->pfnFormatString(sczFormatString, &sczText, pTheme->pvVariableContext);
4678 ThmExitOnFailure(hr, "Failed to format string: '%ls'", sczFormatString);
4679
4680 if (THEME_SHOW_PAGE_REASON_REFRESH != reason && pPage && pControl->wPageId)
4681 {
4682 pSavedVariable = pPage->rgSavedVariables + iPageControl;
4683 pSavedVariable->wzName = pControl->sczName;
4684
4685 if (SUCCEEDED(hr))
4686 {
4687 hr = StrAllocStringSecure(&pSavedVariable->sczValue, sczText, 0);
4688 ThmExitOnFailure(hr, "Failed to save variable: %ls", pControl->sczName);
4689 }
4690
4691 ++iPageControl;
4692 }
4693
4694 ThemeSetTextControl(pTheme, pControl->wId, sczText);
4695 }
4696 }
4697
4698 // If this is a radio button associated with a variable,
4699 // try to set its default state to the state of the variable.
4700 if (pTheme->pfnGetStringVariable && THEME_CONTROL_TYPE_RADIOBUTTON == pControl->type && pControl->sczVariable && *pControl->sczVariable)
4701 {
4702 hr = pTheme->pfnGetStringVariable(pControl->sczVariable, &sczText, pTheme->pvVariableContext);
4703 if (E_NOTFOUND == hr)
4704 {
4705 ReleaseNullStr(sczText);
4706 }
4707 else
4708 {
4709 ThmExitOnFailure(hr, "Failed to get string variable: %ls", pControl->sczVariable);
4710 }
4711
4712 if (THEME_SHOW_PAGE_REASON_REFRESH != reason && pPage && pControl->wPageId && pControl->fLastRadioButton)
4713 {
4714 pSavedVariable = pPage->rgSavedVariables + iPageControl;
4715 pSavedVariable->wzName = pControl->sczVariable;
4716
4717 if (SUCCEEDED(hr))
4718 {
4719 hr = StrAllocStringSecure(&pSavedVariable->sczValue, sczText, 0);
4720 ThmExitOnFailure(hr, "Failed to save variable: %ls", pControl->sczVariable);
4721 }
4722
4723 ++iPageControl;
4724 }
4725
4726 hr = S_OK;
4727
4728 Button_SetCheck(hWnd, (!sczText && !pControl->sczValue) || (sczText && CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczText, -1, pControl->sczValue, -1)));
4729 }
4730 }
4731
4732 if (!fVisible || (!fEnabled && (pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_HIDE_WHEN_DISABLED)))
4733 {
4734 ::ShowWindow(hWnd, SW_HIDE);
4735 }
4736 else
4737 {
4738 ::EnableWindow(hWnd, !fHide && fEnabled);
4739
4740 if (!hwndFocus && pControl->wPageId && (pControl->dwStyle & WS_TABSTOP))
4741 {
4742 hwndFocus = hWnd;
4743 }
4744
4745 ::ShowWindow(hWnd, nCmdShow);
4746 }
4747
4748 if (0 < pControl->cControls)
4749 {
4750 ShowControls(pTheme, pControl, nCmdShow, fSaveEditboxes, reason, dwPageId);
4751 }
4752
4753 if (THEME_CONTROL_TYPE_BILLBOARD == pControl->type && pControl->wPageId)
4754 {
4755 if (fEnabled)
4756 {
4757 StartBillboard(pTheme, pControl->wId);
4758 }
4759 else
4760 {
4761 StopBillboard(pTheme, pControl->wId);
4762 }
4763 }
4764
4765 if (phwndFocus)
4766 {
4767 *phwndFocus = hwndFocus;
4768 }
4769
4770LExit:
4771 ReleaseStr(sczFormatString);
4772 ReleaseStr(sczText);
4773
4774 return hr;
4775}
4776
4777static HRESULT ShowControls(
4778 __in THEME* pTheme,
4779 __in_opt const THEME_CONTROL* pParentControl,
4780 __in int nCmdShow,
4781 __in BOOL fSaveEditboxes,
4782 __in THEME_SHOW_PAGE_REASON reason,
4783 __in DWORD dwPageId
4784 )
4785{
4786 HRESULT hr = S_OK;
4787 HWND hwndFocus = NULL;
4788 DWORD cControls = 0;
4789 THEME_CONTROL* rgControls = NULL;
4790
4791 GetControls(pTheme, pParentControl, cControls, rgControls);
4792
4793 for (DWORD i = 0; i < cControls; ++i)
4794 {
4795 THEME_CONTROL* pControl = rgControls + i;
4796
4797 // Only look at non-page controls and the specified page's controls.
4798 if (!pControl->wPageId || pControl->wPageId == dwPageId)
4799 {
4800 hr = ShowControl(pTheme, pControl, nCmdShow, fSaveEditboxes, reason, dwPageId, &hwndFocus);
4801 ThmExitOnFailure(hr, "Failed to show control '%ls' at index %d.", pControl->sczName, i);
4802 }
4803 }
4804
4805 if (hwndFocus)
4806 {
4807 ::SetFocus(hwndFocus);
4808 }
4809
4810LExit:
4811 return hr;
4812}
4813
4814
4815static LRESULT CALLBACK ControlGroupDefWindowProc(
4816 __in_opt THEME* pTheme,
4817 __in HWND hWnd,
4818 __in UINT uMsg,
4819 __in WPARAM wParam,
4820 __in LPARAM lParam
4821 )
4822{
4823 if (pTheme)
4824 {
4825 switch (uMsg)
4826 {
4827 case WM_DRAWITEM:
4828 ThemeDrawControl(pTheme, reinterpret_cast<LPDRAWITEMSTRUCT>(lParam));
4829 return TRUE;
4830
4831 case WM_CTLCOLORBTN: __fallthrough;
4832 case WM_CTLCOLORSTATIC:
4833 {
4834 HBRUSH hBrush = NULL;
4835 if (ThemeSetControlColor(pTheme, reinterpret_cast<HDC>(wParam), reinterpret_cast<HWND>(lParam), &hBrush))
4836 {
4837 return reinterpret_cast<LRESULT>(hBrush);
4838 }
4839 }
4840 break;
4841
4842 case WM_SETCURSOR:
4843 if (ThemeHoverControl(pTheme, hWnd, reinterpret_cast<HWND>(wParam)))
4844 {
4845 return TRUE;
4846 }
4847 break;
4848
4849 case WM_PAINT:
4850 if (::GetUpdateRect(hWnd, NULL, FALSE))
4851 {
4852 PAINTSTRUCT ps;
4853 ::BeginPaint(hWnd, &ps);
4854 if (hWnd == pTheme->hwndParent)
4855 {
4856 ThemeDrawBackground(pTheme, &ps);
4857 }
4858 ::EndPaint(hWnd, &ps);
4859 }
4860 return 0;
4861
4862 case WM_TIMER:
4863 OnBillboardTimer(pTheme, hWnd, wParam);
4864 break;
4865
4866 case WM_NOTIFY:
4867 if (lParam)
4868 {
4869 LPNMHDR pnmhdr = reinterpret_cast<LPNMHDR>(lParam);
4870 switch (pnmhdr->code)
4871 {
4872 // Tab/Shift+Tab support for rich-edit control.
4873 case EN_MSGFILTER:
4874 {
4875 MSGFILTER* msgFilter = reinterpret_cast<MSGFILTER*>(lParam);
4876 if (WM_KEYDOWN == msgFilter->msg && VK_TAB == msgFilter->wParam)
4877 {
4878 BOOL fShift = 0x8000 & ::GetKeyState(VK_SHIFT);
4879 HWND hwndFocus = ::GetNextDlgTabItem(hWnd, msgFilter->nmhdr.hwndFrom, fShift);
4880 ::SetFocus(hwndFocus);
4881 return 1;
4882 }
4883 break;
4884 }
4885
4886 // Hyperlink clicks from rich-edit control.
4887 case EN_LINK:
4888 return SUCCEEDED(OnRichEditEnLink(lParam, pnmhdr->hwndFrom, hWnd));
4889
4890 // Clicks on a hypertext/syslink control.
4891 case NM_CLICK: __fallthrough;
4892 case NM_RETURN:
4893 if (ControlIsType(pTheme, static_cast<DWORD>(pnmhdr->idFrom), THEME_CONTROL_TYPE_HYPERTEXT))
4894 {
4895 PNMLINK pnmlink = reinterpret_cast<PNMLINK>(lParam);
4896 LITEM litem = pnmlink->item;
4897 ShelExec(litem.szUrl, NULL, L"open", NULL, SW_SHOWDEFAULT, hWnd, NULL);
4898 return 1;
4899 }
4900
4901 return 0;
4902 }
4903 }
4904 break;
4905
4906 case WM_COMMAND:
4907 switch (HIWORD(wParam))
4908 {
4909 case BN_CLICKED:
4910 if (lParam)
4911 {
4912 const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, (HWND)lParam);
4913 if (pControl && OnButtonClicked(pTheme, hWnd, pControl))
4914 {
4915 return 0;
4916 }
4917 }
4918 break;
4919 }
4920 break;
4921 }
4922 }
4923
4924 return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
4925}
4926
4927
4928static LRESULT CALLBACK PanelWndProc(
4929 __in HWND hWnd,
4930 __in UINT uMsg,
4931 __in WPARAM wParam,
4932 __in LPARAM lParam
4933 )
4934{
4935 LRESULT lres = 0;
4936 THEME* pTheme = reinterpret_cast<THEME*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
4937
4938 switch (uMsg)
4939 {
4940 case WM_NCCREATE:
4941 {
4942 LPCREATESTRUCTW lpcs = reinterpret_cast<LPCREATESTRUCTW>(lParam);
4943 pTheme = reinterpret_cast<THEME*>(lpcs->lpCreateParams);
4944 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pTheme));
4945 }
4946 break;
4947
4948 case WM_NCDESTROY:
4949 lres = ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
4950 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0);
4951 return lres;
4952
4953 case WM_NCHITTEST:
4954 return HTCLIENT;
4955 break;
4956 }
4957
4958 return ControlGroupDefWindowProc(pTheme, hWnd, uMsg, wParam, lParam);
4959}
4960
4961static LRESULT CALLBACK StaticOwnerDrawWndProc(
4962 __in HWND hWnd,
4963 __in UINT uMsg,
4964 __in WPARAM wParam,
4965 __in LPARAM lParam
4966 )
4967{
4968 switch (uMsg)
4969 {
4970 case WM_UPDATEUISTATE:
4971 return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
4972 default:
4973 return (*vpfnStaticOwnerDrawBaseWndProc)(hWnd, uMsg, wParam, lParam);
4974 }
4975}
4976
4977static HRESULT LoadControls(
4978 __in THEME* pTheme,
4979 __in_opt THEME_CONTROL* pParentControl,
4980 __in_ecount_opt(cAssignControlIds) const THEME_ASSIGN_CONTROL_ID* rgAssignControlIds,
4981 __in DWORD cAssignControlIds
4982 )
4983{
4984 HRESULT hr = S_OK;
4985 RECT rcParent = { };
4986 LPWSTR sczText = NULL;
4987 BOOL fStartNewGroup = FALSE;
4988 DWORD cControls = 0;
4989 THEME_CONTROL* rgControls = NULL;
4990 HWND hwndParent = pParentControl ? pParentControl->hWnd : pTheme->hwndParent;
4991 int w = 0;
4992 int h = 0;
4993 int x = 0;
4994 int y = 0;
4995
4996 GetControls(pTheme, pParentControl, cControls, rgControls);
4997 ::GetClientRect(hwndParent, &rcParent);
4998
4999 for (DWORD i = 0; i < cControls; ++i)
5000 {
5001 THEME_CONTROL* pControl = rgControls + i;
5002 THEME_FONT* pControlFont = (pTheme->cFonts > pControl->dwFontId) ? pTheme->rgFonts + pControl->dwFontId : NULL;
5003 THEME_FONT_INSTANCE* pControlFontInstance = NULL;
5004 LPCWSTR wzWindowClass = NULL;
5005 DWORD dwWindowBits = WS_CHILD;
5006 DWORD dwWindowExBits = 0;
5007
5008 if (fStartNewGroup)
5009 {
5010 dwWindowBits |= WS_GROUP;
5011 fStartNewGroup = FALSE;
5012 }
5013
5014 switch (pControl->type)
5015 {
5016 case THEME_CONTROL_TYPE_BILLBOARD:
5017 __fallthrough;
5018 case THEME_CONTROL_TYPE_PANEL:
5019 wzWindowClass = THEME_WC_PANEL;
5020 dwWindowBits |= WS_CHILDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
5021 dwWindowExBits |= WS_EX_TRANSPARENT | WS_EX_CONTROLPARENT;
5022#ifdef DEBUG
5023 StrAllocFormatted(&pControl->sczText, L"Panel '%ls', id: %d", pControl->sczName, pControl->wId);
5024#endif
5025 break;
5026
5027 case THEME_CONTROL_TYPE_CHECKBOX:
5028 dwWindowBits |= BS_AUTOCHECKBOX | BS_MULTILINE; // checkboxes are basically buttons with an extra bit tossed in.
5029 __fallthrough;
5030 case THEME_CONTROL_TYPE_BUTTON:
5031 wzWindowClass = WC_BUTTONW;
5032 if (pControl->hImage || (pTheme->hImage && 0 <= pControl->nSourceX && 0 <= pControl->nSourceY))
5033 {
5034 dwWindowBits |= BS_OWNERDRAW;
5035 pControl->dwInternalStyle |= INTERNAL_CONTROL_STYLE_OWNER_DRAW;
5036 }
5037 break;
5038
5039 case THEME_CONTROL_TYPE_COMBOBOX:
5040 wzWindowClass = WC_COMBOBOXW;
5041 dwWindowBits |= CBS_DROPDOWNLIST | CBS_HASSTRINGS;
5042 break;
5043
5044 case THEME_CONTROL_TYPE_COMMANDLINK:
5045 wzWindowClass = WC_BUTTONW;
5046 dwWindowBits |= BS_COMMANDLINK;
5047 break;
5048
5049 case THEME_CONTROL_TYPE_EDITBOX:
5050 wzWindowClass = WC_EDITW;
5051 dwWindowBits |= ES_LEFT | ES_AUTOHSCROLL;
5052 dwWindowExBits = WS_EX_CLIENTEDGE;
5053 break;
5054
5055 case THEME_CONTROL_TYPE_HYPERLINK: // hyperlinks are basically just owner drawn buttons.
5056 wzWindowClass = THEME_WC_HYPERLINK;
5057 dwWindowBits |= BS_OWNERDRAW | BTNS_NOPREFIX;
5058 break;
5059
5060 case THEME_CONTROL_TYPE_HYPERTEXT:
5061 wzWindowClass = WC_LINK;
5062 dwWindowBits |= LWS_NOPREFIX;
5063 break;
5064
5065 case THEME_CONTROL_TYPE_IMAGE: // images are basically just owner drawn static controls (so we can draw .jpgs and .pngs instead of just bitmaps).
5066 if (pControl->hImage || (pTheme->hImage && 0 <= pControl->nSourceX && 0 <= pControl->nSourceY))
5067 {
5068 wzWindowClass = THEME_WC_STATICOWNERDRAW;
5069 dwWindowBits |= SS_OWNERDRAW;
5070 pControl->dwInternalStyle |= INTERNAL_CONTROL_STYLE_OWNER_DRAW;
5071 }
5072 else
5073 {
5074 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
5075 ThmExitOnRootFailure(hr, "Invalid image or image list coordinates.");
5076 }
5077 break;
5078
5079 case THEME_CONTROL_TYPE_LABEL:
5080 wzWindowClass = WC_STATICW;
5081 break;
5082
5083 case THEME_CONTROL_TYPE_LISTVIEW:
5084 // If thmutil is handling the image list for this listview, tell Windows not to free it when the control is destroyed.
5085 if (pControl->rghImageList[0] || pControl->rghImageList[1] || pControl->rghImageList[2] || pControl->rghImageList[3])
5086 {
5087 pControl->dwStyle |= LVS_SHAREIMAGELISTS;
5088 }
5089 wzWindowClass = WC_LISTVIEWW;
5090 break;
5091
5092 case THEME_CONTROL_TYPE_PROGRESSBAR:
5093 if (pControl->hImage || (pTheme->hImage && 0 <= pControl->nSourceX && 0 <= pControl->nSourceY))
5094 {
5095 wzWindowClass = THEME_WC_STATICOWNERDRAW; // no such thing as an owner drawn progress bar so we'll make our own out of a static control.
5096 dwWindowBits |= SS_OWNERDRAW;
5097 pControl->dwInternalStyle |= INTERNAL_CONTROL_STYLE_OWNER_DRAW;
5098 }
5099 else
5100 {
5101 wzWindowClass = PROGRESS_CLASSW;
5102 }
5103 break;
5104
5105 case THEME_CONTROL_TYPE_RADIOBUTTON:
5106 dwWindowBits |= BS_AUTORADIOBUTTON | BS_MULTILINE;
5107 wzWindowClass = WC_BUTTONW;
5108
5109 if (pControl->fLastRadioButton)
5110 {
5111 fStartNewGroup = TRUE;
5112 }
5113 break;
5114
5115 case THEME_CONTROL_TYPE_RICHEDIT:
5116 if (!vhModuleMsftEdit && !vhModuleRichEd)
5117 {
5118 hr = LoadSystemLibrary(L"Msftedit.dll", &vhModuleMsftEdit);
5119 if (FAILED(hr))
5120 {
5121 hr = LoadSystemLibrary(L"Riched20.dll", &vhModuleRichEd);
5122 ThmExitOnFailure(hr, "Failed to load Rich Edit control library.");
5123 }
5124 }
5125
5126 wzWindowClass = vhModuleMsftEdit ? MSFTEDIT_CLASS : RICHEDIT_CLASSW;
5127 dwWindowBits |= ES_AUTOVSCROLL | ES_MULTILINE | WS_VSCROLL | ES_READONLY;
5128 break;
5129
5130 case THEME_CONTROL_TYPE_STATIC:
5131 wzWindowClass = WC_STATICW;
5132 dwWindowBits |= SS_ETCHEDHORZ;
5133 break;
5134
5135 case THEME_CONTROL_TYPE_TAB:
5136 wzWindowClass = WC_TABCONTROLW;
5137 break;
5138
5139 case THEME_CONTROL_TYPE_TREEVIEW:
5140 wzWindowClass = WC_TREEVIEWW;
5141 break;
5142 }
5143 ThmExitOnNull(wzWindowClass, hr, E_INVALIDDATA, "Failed to configure control %u because of unknown type: %u", i, pControl->type);
5144
5145 // Default control ids to the theme id and its index in the control array, unless there
5146 // is a specific id to assign to a named control.
5147 WORD wControlId = MAKEWORD(i, pTheme->wId);
5148 for (DWORD iAssignControl = 0; pControl->sczName && iAssignControl < cAssignControlIds; ++iAssignControl)
5149 {
5150 if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pControl->sczName, -1, rgAssignControlIds[iAssignControl].wzName, -1))
5151 {
5152 wControlId = rgAssignControlIds[iAssignControl].wId;
5153 break;
5154 }
5155 }
5156
5157 pControl->wId = wControlId;
5158
5159 GetControlDimensions(pControl, &rcParent, &w, &h, &x, &y);
5160
5161 BOOL fVisible = pControl->dwStyle & WS_VISIBLE;
5162 BOOL fDisabled = pControl->dwStyle & WS_DISABLED;
5163
5164 // If the control is supposed to be initially visible and it has a VisibleCondition, check if it's true.
5165 if (fVisible && pControl->sczVisibleCondition && pTheme->pfnEvaluateCondition && !pControl->fDisableVariableFunctionality)
5166 {
5167 hr = pTheme->pfnEvaluateCondition(pControl->sczVisibleCondition, &fVisible, pTheme->pvVariableContext);
5168 ThmExitOnFailure(hr, "Failed to evaluate VisibleCondition: %ls", pControl->sczVisibleCondition);
5169
5170 if (!fVisible)
5171 {
5172 pControl->dwStyle &= ~WS_VISIBLE;
5173 }
5174 }
5175
5176 // Disable controls that aren't visible so their shortcut keys don't trigger.
5177 if (!fVisible)
5178 {
5179 dwWindowBits |= WS_DISABLED;
5180 fDisabled = TRUE;
5181 }
5182
5183 // If the control is supposed to be initially enabled and it has an EnableCondition, check if it's true.
5184 if (!fDisabled && pControl->sczEnableCondition && pTheme->pfnEvaluateCondition && !pControl->fDisableVariableFunctionality)
5185 {
5186 BOOL fEnable = TRUE;
5187
5188 hr = pTheme->pfnEvaluateCondition(pControl->sczEnableCondition, &fEnable, pTheme->pvVariableContext);
5189 ThmExitOnFailure(hr, "Failed to evaluate EnableCondition: %ls", pControl->sczEnableCondition);
5190
5191 fDisabled = !fEnable;
5192 dwWindowBits |= fDisabled ? WS_DISABLED : 0;
5193 }
5194
5195 // Honor the HideWhenDisabled option.
5196 if ((pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_HIDE_WHEN_DISABLED) && fVisible && fDisabled)
5197 {
5198 fVisible = FALSE;
5199 pControl->dwStyle &= ~WS_VISIBLE;
5200 }
5201
5202 pControl->hWnd = ::CreateWindowExW(dwWindowExBits, wzWindowClass, pControl->sczText, pControl->dwStyle | dwWindowBits, x, y, w, h, hwndParent, reinterpret_cast<HMENU>(wControlId), NULL, pTheme);
5203 ThmExitOnNullWithLastError(pControl->hWnd, hr, "Failed to create window.");
5204
5205 if (pControl->sczTooltip)
5206 {
5207 if (!pTheme->hwndTooltip)
5208 {
5209 pTheme->hwndTooltip = ::CreateWindowExW(WS_EX_TOOLWINDOW, TOOLTIPS_CLASSW, NULL, WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON | TTS_NOPREFIX, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwndParent, NULL, NULL, NULL);
5210 }
5211
5212 if (pTheme->hwndTooltip)
5213 {
5214 TOOLINFOW toolinfo = {};
5215 toolinfo.cbSize = sizeof(toolinfo);
5216 toolinfo.hwnd = hwndParent;
5217 toolinfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
5218 toolinfo.uId = reinterpret_cast<UINT_PTR>(pControl->hWnd);
5219 toolinfo.lpszText = pControl->sczTooltip;
5220 ::SendMessageW(pTheme->hwndTooltip, TTM_ADDTOOLW, 0, reinterpret_cast<LPARAM>(&toolinfo));
5221 }
5222 }
5223
5224 if (THEME_CONTROL_TYPE_COMMANDLINK == pControl->type)
5225 {
5226 if (pControl->sczNote)
5227 {
5228 ::SendMessageW(pControl->hWnd, BCM_SETNOTE, 0, reinterpret_cast<WPARAM>(pControl->sczNote));
5229 }
5230
5231 if (pControl->hImage)
5232 {
5233 ::SendMessageW(pControl->hWnd, BM_SETIMAGE, IMAGE_BITMAP, reinterpret_cast<LPARAM>(pControl->hImage));
5234 }
5235 else if (pControl->hIcon)
5236 {
5237 ::SendMessageW(pControl->hWnd, BM_SETIMAGE, IMAGE_ICON, reinterpret_cast<LPARAM>(pControl->hIcon));
5238 }
5239 }
5240 else if (THEME_CONTROL_TYPE_EDITBOX == pControl->type)
5241 {
5242 if (pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_FILESYSTEM_AUTOCOMPLETE)
5243 {
5244 hr = ::SHAutoComplete(pControl->hWnd, SHACF_FILESYS_ONLY);
5245 }
5246 }
5247 else if (THEME_CONTROL_TYPE_LISTVIEW == pControl->type)
5248 {
5249 ::SendMessageW(pControl->hWnd, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, pControl->dwExtendedStyle);
5250
5251 hr = SizeListViewColumns(pControl);
5252 ThmExitOnFailure(hr, "Failed to get size of list view columns.");
5253
5254 for (DWORD j = 0; j < pControl->cColumns; ++j)
5255 {
5256 LVCOLUMNW lvc = { };
5257 lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
5258 lvc.cx = pControl->ptcColumns[j].nWidth;
5259 lvc.iSubItem = j;
5260 lvc.pszText = pControl->ptcColumns[j].pszName;
5261 lvc.fmt = LVCFMT_LEFT;
5262 lvc.cchTextMax = 4;
5263
5264 if (-1 == ::SendMessageW(pControl->hWnd, LVM_INSERTCOLUMNW, (WPARAM) (int) (j), (LPARAM) (const LV_COLUMNW *) (&lvc)))
5265 {
5266 ThmExitWithLastError(hr, "Failed to insert listview column %u into tab control.", j);
5267 }
5268
5269 // Return value tells us the old image list, we don't care.
5270 if (pControl->rghImageList[0])
5271 {
5272 ::SendMessageW(pControl->hWnd, LVM_SETIMAGELIST, static_cast<WPARAM>(LVSIL_NORMAL), reinterpret_cast<LPARAM>(pControl->rghImageList[0]));
5273 }
5274 else if (pControl->rghImageList[1])
5275 {
5276 ::SendMessageW(pControl->hWnd, LVM_SETIMAGELIST, static_cast<WPARAM>(LVSIL_SMALL), reinterpret_cast<LPARAM>(pControl->rghImageList[1]));
5277 }
5278 else if (pControl->rghImageList[2])
5279 {
5280 ::SendMessageW(pControl->hWnd, LVM_SETIMAGELIST, static_cast<WPARAM>(LVSIL_STATE), reinterpret_cast<LPARAM>(pControl->rghImageList[2]));
5281 }
5282 else if (pControl->rghImageList[3])
5283 {
5284 ::SendMessageW(pControl->hWnd, LVM_SETIMAGELIST, static_cast<WPARAM>(LVSIL_GROUPHEADER), reinterpret_cast<LPARAM>(pControl->rghImageList[3]));
5285 }
5286 }
5287 }
5288 else if (THEME_CONTROL_TYPE_RICHEDIT == pControl->type)
5289 {
5290 ::SendMessageW(pControl->hWnd, EM_AUTOURLDETECT, static_cast<WPARAM>(TRUE), 0);
5291 ::SendMessageW(pControl->hWnd, EM_SETEVENTMASK, 0, ENM_KEYEVENTS | ENM_LINK);
5292 }
5293 else if (THEME_CONTROL_TYPE_TAB == pControl->type)
5294 {
5295 ULONG_PTR hbrBackground = 0;
5296 if (THEME_INVALID_ID != pControl->dwFontId)
5297 {
5298 hbrBackground = reinterpret_cast<ULONG_PTR>(pTheme->rgFonts[pControl->dwFontId].hBackground);
5299 }
5300 else
5301 {
5302 hbrBackground = ::GetClassLongPtr(pTheme->hwndParent, GCLP_HBRBACKGROUND);
5303 }
5304 ::SetClassLongPtr(pControl->hWnd, GCLP_HBRBACKGROUND, hbrBackground);
5305
5306 for (DWORD j = 0; j < pControl->cTabs; ++j)
5307 {
5308 TCITEMW tci = { };
5309 tci.mask = TCIF_TEXT | TCIF_IMAGE;
5310 tci.iImage = -1;
5311 tci.pszText = pControl->pttTabs[j].pszName;
5312
5313 if (-1 == ::SendMessageW(pControl->hWnd, TCM_INSERTITEMW, (WPARAM) (int) (j), (LPARAM) (const TC_ITEMW *) (&tci)))
5314 {
5315 ThmExitWithLastError(hr, "Failed to insert tab %u into tab control.", j);
5316 }
5317 }
5318 }
5319
5320 if (pControlFont)
5321 {
5322 hr = EnsureFontInstance(pTheme, pControlFont, &pControlFontInstance);
5323 ThmExitOnFailure(hr, "Failed to get DPI specific font.");
5324
5325 ::SendMessageW(pControl->hWnd, WM_SETFONT, (WPARAM) pControlFontInstance->hFont, FALSE);
5326 }
5327
5328 // Initialize the text on all "application" (non-page) controls, best effort only.
5329 if (pTheme->pfnFormatString && !pControl->wPageId && pControl->sczText && *pControl->sczText)
5330 {
5331 HRESULT hrFormat = pTheme->pfnFormatString(pControl->sczText, &sczText, pTheme->pvVariableContext);
5332 if (SUCCEEDED(hrFormat))
5333 {
5334 ThemeSetTextControl(pTheme, pControl->wId, sczText);
5335 }
5336 }
5337
5338 if (pControl->cControls)
5339 {
5340 hr = LoadControls(pTheme, pControl, rgAssignControlIds, cAssignControlIds);
5341 ThmExitOnFailure(hr, "Failed to load child controls.");
5342 }
5343 }
5344
5345LExit:
5346 ReleaseStr(sczText);
5347
5348 return hr;
5349}
5350
5351static HRESULT LocalizeControls(
5352 __in DWORD cControls,
5353 __in THEME_CONTROL* rgControls,
5354 __in const WIX_LOCALIZATION *pWixLoc
5355 )
5356{
5357 HRESULT hr = S_OK;
5358
5359 for (DWORD i = 0; i < cControls; ++i)
5360 {
5361 THEME_CONTROL* pControl = rgControls + i;
5362 hr = LocalizeControl(pControl, pWixLoc);
5363 ThmExitOnFailure(hr, "Failed to localize control: %ls", pControl->sczName);
5364 }
5365
5366LExit:
5367 return hr;
5368}
5369
5370static HRESULT LocalizeControl(
5371 __in THEME_CONTROL* pControl,
5372 __in const WIX_LOCALIZATION *pWixLoc
5373 )
5374{
5375 HRESULT hr = S_OK;
5376 LOC_CONTROL* pLocControl = NULL;
5377 LPWSTR sczLocStringId = NULL;
5378
5379 if (pControl->sczText && *pControl->sczText)
5380 {
5381 hr = LocLocalizeString(pWixLoc, &pControl->sczText);
5382 ThmExitOnFailure(hr, "Failed to localize control text.");
5383 }
5384 else if (pControl->sczName)
5385 {
5386 LOC_STRING* plocString = NULL;
5387
5388 hr = StrAllocFormatted(&sczLocStringId, L"#(loc.%ls)", pControl->sczName);
5389 ThmExitOnFailure(hr, "Failed to format loc string id: %ls", pControl->sczName);
5390
5391 hr = LocGetString(pWixLoc, sczLocStringId, &plocString);
5392 if (E_NOTFOUND != hr)
5393 {
5394 ThmExitOnFailure(hr, "Failed to get loc string: %ls", pControl->sczName);
5395
5396 hr = StrAllocString(&pControl->sczText, plocString->wzText, 0);
5397 ThmExitOnFailure(hr, "Failed to copy loc string to control: %ls", plocString->wzText);
5398 }
5399 }
5400
5401 if (pControl->sczTooltip && *pControl->sczTooltip)
5402 {
5403 hr = LocLocalizeString(pWixLoc, &pControl->sczTooltip);
5404 ThmExitOnFailure(hr, "Failed to localize control tooltip text.");
5405 }
5406
5407 if (pControl->sczNote && *pControl->sczNote)
5408 {
5409 hr = LocLocalizeString(pWixLoc, &pControl->sczNote);
5410 ThmExitOnFailure(hr, "Failed to localize control note text.");
5411 }
5412
5413 for (DWORD j = 0; j < pControl->cConditionalText; ++j)
5414 {
5415 hr = LocLocalizeString(pWixLoc, &pControl->rgConditionalText[j].sczText);
5416 ThmExitOnFailure(hr, "Failed to localize conditional text.");
5417 }
5418
5419 for (DWORD j = 0; j < pControl->cConditionalNotes; ++j)
5420 {
5421 hr = LocLocalizeString(pWixLoc, &pControl->rgConditionalNotes[j].sczText);
5422 ThmExitOnFailure(hr, "Failed to localize conditional note.");
5423 }
5424
5425 for (DWORD j = 0; j < pControl->cColumns; ++j)
5426 {
5427 hr = LocLocalizeString(pWixLoc, &pControl->ptcColumns[j].pszName);
5428 ThmExitOnFailure(hr, "Failed to localize column text.");
5429 }
5430
5431 for (DWORD j = 0; j < pControl->cTabs; ++j)
5432 {
5433 hr = LocLocalizeString(pWixLoc, &pControl->pttTabs[j].pszName);
5434 ThmExitOnFailure(hr, "Failed to localize tab text.");
5435 }
5436
5437 // Localize control's size, location, and text.
5438 if (pControl->sczName)
5439 {
5440 hr = LocGetControl(pWixLoc, pControl->sczName, &pLocControl);
5441 if (E_NOTFOUND == hr)
5442 {
5443 ExitFunction1(hr = S_OK);
5444 }
5445 ThmExitOnFailure(hr, "Failed to localize control.");
5446
5447 if (LOC_CONTROL_NOT_SET != pLocControl->nX)
5448 {
5449 pControl->nDefaultDpiX = pLocControl->nX;
5450 }
5451
5452 if (LOC_CONTROL_NOT_SET != pLocControl->nY)
5453 {
5454 pControl->nDefaultDpiY = pLocControl->nY;
5455 }
5456
5457 if (LOC_CONTROL_NOT_SET != pLocControl->nWidth)
5458 {
5459 pControl->nDefaultDpiWidth = pLocControl->nWidth;
5460 }
5461
5462 if (LOC_CONTROL_NOT_SET != pLocControl->nHeight)
5463 {
5464 pControl->nDefaultDpiHeight = pLocControl->nHeight;
5465 }
5466
5467 if (pLocControl->wzText && *pLocControl->wzText)
5468 {
5469 hr = StrAllocString(&pControl->sczText, pLocControl->wzText, 0);
5470 ThmExitOnFailure(hr, "Failed to localize control text.");
5471 }
5472 }
5473
5474 hr = LocalizeControls(pControl->cControls, pControl->rgControls, pWixLoc);
5475
5476LExit:
5477 ReleaseStr(sczLocStringId);
5478
5479 return hr;
5480}
5481
5482static HRESULT LoadControlsString(
5483 __in DWORD cControls,
5484 __in THEME_CONTROL* rgControls,
5485 __in HMODULE hResModule
5486 )
5487{
5488 HRESULT hr = S_OK;
5489
5490 for (DWORD i = 0; i < cControls; ++i)
5491 {
5492 THEME_CONTROL* pControl = rgControls + i;
5493 hr = LoadControlString(pControl, hResModule);
5494 ThmExitOnFailure(hr, "Failed to load string for control: %ls", pControl->sczName);
5495 }
5496
5497LExit:
5498 return hr;
5499}
5500
5501static HRESULT LoadControlString(
5502 __in THEME_CONTROL* pControl,
5503 __in HMODULE hResModule
5504 )
5505{
5506 HRESULT hr = S_OK;
5507 if (UINT_MAX != pControl->uStringId)
5508 {
5509 hr = ResReadString(hResModule, pControl->uStringId, &pControl->sczText);
5510 ThmExitOnFailure(hr, "Failed to load control text.");
5511
5512 for (DWORD j = 0; j < pControl->cColumns; ++j)
5513 {
5514 if (UINT_MAX != pControl->ptcColumns[j].uStringId)
5515 {
5516 hr = ResReadString(hResModule, pControl->ptcColumns[j].uStringId, &pControl->ptcColumns[j].pszName);
5517 ThmExitOnFailure(hr, "Failed to load column text.");
5518 }
5519 }
5520
5521 for (DWORD j = 0; j < pControl->cTabs; ++j)
5522 {
5523 if (UINT_MAX != pControl->pttTabs[j].uStringId)
5524 {
5525 hr = ResReadString(hResModule, pControl->pttTabs[j].uStringId, &pControl->pttTabs[j].pszName);
5526 ThmExitOnFailure(hr, "Failed to load tab text.");
5527 }
5528 }
5529 }
5530
5531 hr = LoadControlsString(pControl->cControls, pControl->rgControls, hResModule);
5532
5533LExit:
5534 return hr;
5535}
5536
5537static void ResizeControls(
5538 __in DWORD cControls,
5539 __in THEME_CONTROL* rgControls,
5540 __in const RECT* prcParent
5541 )
5542{
5543 for (DWORD i = 0; i < cControls; ++i)
5544 {
5545 THEME_CONTROL* pControl = rgControls + i;
5546 ResizeControl(pControl, prcParent);
5547 }
5548}
5549
5550static void ResizeControl(
5551 __in THEME_CONTROL* pControl,
5552 __in const RECT* prcParent
5553 )
5554{
5555 int w = 0;
5556 int h = 0;
5557 int x = 0;
5558 int y = 0;
5559 RECT rcControl = { };
5560
5561 GetControlDimensions(pControl, prcParent, &w, &h, &x, &y);
5562 ::SetWindowPos(pControl->hWnd, NULL, x, y, w, h, SWP_NOACTIVATE | SWP_NOZORDER);
5563
5564#ifdef DEBUG
5565 if (THEME_CONTROL_TYPE_BUTTON == pControl->type)
5566 {
5567 Trace(REPORT_STANDARD, "Resizing button (%ls/%ls) to (%d,%d)+(%d,%d) for parent (%d,%d)-(%d,%d)",
5568 pControl->sczName, pControl->sczText, x, y, w, h, prcParent->left, prcParent->top, prcParent->right, prcParent->bottom);
5569 }
5570#endif
5571
5572 if (THEME_CONTROL_TYPE_LISTVIEW == pControl->type)
5573 {
5574 SizeListViewColumns(pControl);
5575
5576 for (DWORD j = 0; j < pControl->cColumns; ++j)
5577 {
5578 if (-1 == ::SendMessageW(pControl->hWnd, LVM_SETCOLUMNWIDTH, (WPARAM) (int) (j), (LPARAM) (pControl->ptcColumns[j].nWidth)))
5579 {
5580 Trace(REPORT_DEBUG, "Failed to resize listview column %u with error %u", j, ::GetLastError());
5581 return;
5582 }
5583 }
5584 }
5585
5586 if (pControl->cControls)
5587 {
5588 ::GetClientRect(pControl->hWnd, &rcControl);
5589 ResizeControls(pControl->cControls, pControl->rgControls, &rcControl);
5590 }
5591}
5592
5593static void ScaleThemeFromWindow(
5594 __in THEME* pTheme,
5595 __in UINT nDpi,
5596 __in int x,
5597 __in int y
5598 )
5599{
5600 DWORD dwStyle = GetWindowStyle(pTheme->hwndParent);
5601 BOOL fMenu = NULL != ::GetMenu(pTheme->hwndParent);
5602 DWORD dwExStyle = GetWindowExStyle(pTheme->hwndParent);
5603
5604 ScaleTheme(pTheme, nDpi, x, y, dwStyle, fMenu, dwExStyle);
5605}
5606
5607static void ScaleTheme(
5608 __in THEME* pTheme,
5609 __in UINT nDpi,
5610 __in int x,
5611 __in int y,
5612 __in DWORD dwStyle,
5613 __in BOOL fMenu,
5614 __in DWORD dwExStyle
5615 )
5616{
5617 RECT rect = { };
5618
5619 pTheme->nDpi = nDpi;
5620
5621 pTheme->nHeight = DpiuScaleValue(pTheme->nDefaultDpiHeight, pTheme->nDpi);
5622 pTheme->nWidth = DpiuScaleValue(pTheme->nDefaultDpiWidth, pTheme->nDpi);
5623 pTheme->nMinimumHeight = DpiuScaleValue(pTheme->nDefaultDpiMinimumHeight, pTheme->nDpi);
5624 pTheme->nMinimumWidth = DpiuScaleValue(pTheme->nDefaultDpiMinimumWidth, pTheme->nDpi);
5625
5626 rect.left = x;
5627 rect.top = y;
5628 rect.right = x + pTheme->nWidth;
5629 rect.bottom = y + pTheme->nHeight;
5630 DpiuAdjustWindowRect(&rect, dwStyle, fMenu, dwExStyle, pTheme->nDpi);
5631 pTheme->nWindowWidth = rect.right - rect.left;
5632 pTheme->nWindowHeight = rect.bottom - rect.top;
5633
5634 ScaleControls(pTheme, pTheme->cControls, pTheme->rgControls, pTheme->nDpi);
5635
5636 if (pTheme->hwndParent)
5637 {
5638 ::SetWindowPos(pTheme->hwndParent, NULL, x, y, pTheme->nWindowWidth, pTheme->nWindowHeight, SWP_NOACTIVATE | SWP_NOZORDER);
5639 }
5640}
5641
5642static void ScaleControls(
5643 __in THEME* pTheme,
5644 __in DWORD cControls,
5645 __in THEME_CONTROL* rgControls,
5646 __in UINT nDpi
5647 )
5648{
5649 for (DWORD i = 0; i < cControls; ++i)
5650 {
5651 THEME_CONTROL* pControl = rgControls + i;
5652 ScaleControl(pTheme, pControl, nDpi);
5653 }
5654}
5655
5656static void ScaleControl(
5657 __in THEME* pTheme,
5658 __in THEME_CONTROL* pControl,
5659 __in UINT nDpi
5660 )
5661{
5662 HRESULT hr = S_OK;
5663 THEME_FONT* pControlFont = (pTheme->cFonts > pControl->dwFontId) ? pTheme->rgFonts + pControl->dwFontId : NULL;
5664 THEME_FONT_INSTANCE* pControlFontInstance = NULL;
5665
5666 if (pControlFont)
5667 {
5668 hr = EnsureFontInstance(pTheme, pControlFont, &pControlFontInstance);
5669 if (SUCCEEDED(hr) && pControl->hWnd)
5670 {
5671 ::SendMessageW(pControl->hWnd, WM_SETFONT, (WPARAM)pControlFontInstance->hFont, FALSE);
5672 }
5673 }
5674
5675 if (THEME_CONTROL_TYPE_LISTVIEW == pControl->type)
5676 {
5677 for (DWORD i = 0; i < pControl->cColumns; ++i)
5678 {
5679 THEME_COLUMN* pColumn = pControl->ptcColumns + i;
5680
5681 pColumn->nBaseWidth = DpiuScaleValue(pColumn->nDefaultDpiBaseWidth, nDpi);
5682 }
5683 }
5684
5685 pControl->nWidth = DpiuScaleValue(pControl->nDefaultDpiWidth, nDpi);
5686 pControl->nHeight = DpiuScaleValue(pControl->nDefaultDpiHeight, nDpi);
5687 pControl->nX = DpiuScaleValue(pControl->nDefaultDpiX, nDpi);
5688 pControl->nY = DpiuScaleValue(pControl->nDefaultDpiY, nDpi);
5689
5690 if (pControl->cControls)
5691 {
5692 ScaleControls(pTheme, pControl->cControls, pControl->rgControls, nDpi);
5693 }
5694}
5695
5696static void UnloadControls(
5697 __in DWORD cControls,
5698 __in THEME_CONTROL* rgControls
5699 )
5700{
5701 for (DWORD i = 0; i < cControls; ++i)
5702 {
5703 THEME_CONTROL* pControl = rgControls + i;
5704 pControl->hWnd = NULL;
5705
5706 UnloadControls(pControl->cControls, pControl->rgControls);
5707 }
5708}
5709
diff --git a/src/libs/dutil/WixToolset.DUtil/timeutil.cpp b/src/libs/dutil/WixToolset.DUtil/timeutil.cpp
new file mode 100644
index 00000000..b7953c94
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/timeutil.cpp
@@ -0,0 +1,385 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define TimeExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_TIMEUTIL, x, s, __VA_ARGS__)
8#define TimeExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_TIMEUTIL, x, s, __VA_ARGS__)
9#define TimeExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_TIMEUTIL, x, s, __VA_ARGS__)
10#define TimeExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_TIMEUTIL, x, s, __VA_ARGS__)
11#define TimeExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_TIMEUTIL, x, s, __VA_ARGS__)
12#define TimeExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_TIMEUTIL, x, s, __VA_ARGS__)
13#define TimeExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_TIMEUTIL, p, x, e, s, __VA_ARGS__)
14#define TimeExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_TIMEUTIL, p, x, s, __VA_ARGS__)
15#define TimeExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_TIMEUTIL, p, x, e, s, __VA_ARGS__)
16#define TimeExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_TIMEUTIL, p, x, s, __VA_ARGS__)
17#define TimeExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_TIMEUTIL, e, x, s, __VA_ARGS__)
18#define TimeExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_TIMEUTIL, g, x, s, __VA_ARGS__)
19
20const LPCWSTR DAY_OF_WEEK[] = { L"Sun", L"Mon", L"Tue", L"Wed", L"Thu", L"Fri", L"Sat" };
21const LPCWSTR MONTH_OF_YEAR[] = { L"None", L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun", L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec" };
22enum TIME_PARSER { DayOfWeek, DayOfMonth, MonthOfYear, Year, Hours, Minutes, Seconds, TimeZone };
23enum TIME_PARSERRFC3339 { RFC3339_Year, RFC3339_Month, RFC3339_Day, RFC3339_Hours, RFC3339_Minutes, RFC3339_Seconds, RFC3339_TimeZone };
24
25// prototypes
26static HRESULT DayFromString(
27 __in_z LPCWSTR wzDay,
28 __out WORD* pwDayOfWeek
29 );
30static HRESULT MonthFromString(
31 __in_z LPCWSTR wzMonth,
32 __out WORD* pwMonthOfYear
33 );
34
35
36/********************************************************************
37 TimeFromString - converts string to FILETIME
38
39*******************************************************************/
40extern "C" HRESULT DAPI TimeFromString(
41 __in_z LPCWSTR wzTime,
42 __out FILETIME* pFileTime
43 )
44{
45 Assert(wzTime && pFileTime);
46
47 HRESULT hr = S_OK;
48 LPWSTR pwzTime = NULL;
49
50 SYSTEMTIME sysTime = { };
51 TIME_PARSER timeParser = DayOfWeek;
52
53 LPCWSTR pwzStart = NULL;
54 LPWSTR pwzEnd = NULL;
55
56 hr = StrAllocString(&pwzTime, wzTime, 0);
57 TimeExitOnFailure(hr, "Failed to copy time.");
58
59 pwzStart = pwzEnd = pwzTime;
60 while (pwzEnd && *pwzEnd)
61 {
62 if (L',' == *pwzEnd || L' ' == *pwzEnd || L':' == *pwzEnd)
63 {
64 *pwzEnd = L'\0'; // null terminate
65 ++pwzEnd;
66
67 while (L' ' == *pwzEnd)
68 {
69 ++pwzEnd; // and skip past the blank space
70 }
71
72 switch (timeParser)
73 {
74 case DayOfWeek:
75 hr = DayFromString(pwzStart, &sysTime.wDayOfWeek);
76 TimeExitOnFailure(hr, "Failed to convert string to day: %ls", pwzStart);
77 break;
78
79 case DayOfMonth:
80 sysTime.wDay = (WORD)wcstoul(pwzStart, NULL, 10);
81 break;
82
83 case MonthOfYear:
84 hr = MonthFromString(pwzStart, &sysTime.wMonth);
85 TimeExitOnFailure(hr, "Failed to convert to month: %ls", pwzStart);
86 break;
87
88 case Year:
89 sysTime.wYear = (WORD)wcstoul(pwzStart, NULL, 10);
90 break;
91
92 case Hours:
93 sysTime.wHour = (WORD)wcstoul(pwzStart, NULL, 10);
94 break;
95
96 case Minutes:
97 sysTime.wMinute = (WORD)wcstoul(pwzStart, NULL, 10);
98 break;
99
100 case Seconds:
101 sysTime.wSecond = (WORD)wcstoul(pwzStart, NULL, 10);
102 break;
103
104 case TimeZone:
105 // TODO: do something with this in the future, but this should only hit outside of the while loop.
106 break;
107
108 default:
109 break;
110 }
111
112 pwzStart = pwzEnd;
113 timeParser = (TIME_PARSER)((int)timeParser + 1);
114 }
115
116 ++pwzEnd;
117 }
118
119
120 if (!::SystemTimeToFileTime(&sysTime, pFileTime))
121 {
122 TimeExitWithLastError(hr, "Failed to convert system time to file time.");
123 }
124
125LExit:
126 ReleaseStr(pwzTime);
127
128 return hr;
129}
130
131/********************************************************************
132 TimeFromString3339 - converts string formated in accorance with RFC3339 to FILETIME
133 http://tools.ietf.org/html/rfc3339
134*******************************************************************/
135extern "C" HRESULT DAPI TimeFromString3339(
136 __in_z LPCWSTR wzTime,
137 __out FILETIME* pFileTime
138 )
139{
140 Assert(wzTime && pFileTime);
141
142 HRESULT hr = S_OK;
143 LPWSTR pwzTime = NULL;
144
145 SYSTEMTIME sysTime = { };
146 TIME_PARSERRFC3339 timeParser = RFC3339_Year;
147
148 LPCWSTR pwzStart = NULL;
149 LPWSTR pwzEnd = NULL;
150
151 hr = StrAllocString(&pwzTime, wzTime, 0);
152 TimeExitOnFailure(hr, "Failed to copy time.");
153
154 pwzStart = pwzEnd = pwzTime;
155 while (pwzEnd && *pwzEnd)
156 {
157 if (L'T' == *pwzEnd || L':' == *pwzEnd || L'-' == *pwzEnd)
158 {
159 *pwzEnd = L'\0'; // null terminate
160 ++pwzEnd;
161
162 switch (timeParser)
163 {
164 case RFC3339_Year:
165 sysTime.wYear = (WORD)wcstoul(pwzStart, NULL, 10);
166 break;
167
168 case RFC3339_Month:
169 sysTime.wMonth = (WORD)wcstoul(pwzStart, NULL, 10);
170 break;
171
172 case RFC3339_Day:
173 sysTime.wDay = (WORD)wcstoul(pwzStart, NULL, 10);
174 break;
175
176 case RFC3339_Hours:
177 sysTime.wHour = (WORD)wcstoul(pwzStart, NULL, 10);
178 break;
179
180 case RFC3339_Minutes:
181 sysTime.wMinute = (WORD)wcstoul(pwzStart, NULL, 10);
182 break;
183
184 case RFC3339_Seconds:
185 sysTime.wSecond = (WORD)wcstoul(pwzStart, NULL, 10);
186 break;
187
188 case RFC3339_TimeZone:
189 // TODO: do something with this in the future, but this should only hit outside of the while loop.
190 break;
191
192 default:
193 break;
194 }
195
196 pwzStart = pwzEnd;
197 timeParser = (TIME_PARSERRFC3339)((int)timeParser + 1);
198 }
199
200 ++pwzEnd;
201 }
202
203
204 if (!::SystemTimeToFileTime(&sysTime, pFileTime))
205 {
206 TimeExitWithLastError(hr, "Failed to convert system time to file time.");
207 }
208
209LExit:
210 ReleaseStr(pwzTime);
211
212 return hr;
213}
214/****************************************************************************
215TimeCurrentTime - gets the current time in string format
216
217****************************************************************************/
218extern "C" HRESULT DAPI TimeCurrentTime(
219 __deref_out_z LPWSTR* ppwz,
220 __in BOOL fGMT
221 )
222{
223 SYSTEMTIME st;
224
225 if (fGMT)
226 {
227 ::GetSystemTime(&st);
228 }
229 else
230 {
231 SYSTEMTIME stGMT;
232 TIME_ZONE_INFORMATION tzi;
233
234 ::GetTimeZoneInformation(&tzi);
235 ::GetSystemTime(&stGMT);
236 ::SystemTimeToTzSpecificLocalTime(&tzi, &stGMT, &st);
237 }
238
239 return StrAllocFormatted(ppwz, L"%02d:%02d:%02d", st.wHour, st.wMinute, st.wSecond);
240}
241
242
243/****************************************************************************
244TimeCurrentDateTime - gets the current date and time in string format,
245 per format described in RFC 3339
246****************************************************************************/
247extern "C" HRESULT DAPI TimeCurrentDateTime(
248 __deref_out_z LPWSTR* ppwz,
249 __in BOOL fGMT
250 )
251{
252 SYSTEMTIME st;
253
254 ::GetSystemTime(&st);
255
256 return TimeSystemDateTime(ppwz, &st, fGMT);
257}
258
259
260/****************************************************************************
261TimeSystemDateTime - converts the provided system time struct to string format,
262 per format described in RFC 3339
263****************************************************************************/
264extern "C" HRESULT DAPI TimeSystemDateTime(
265 __deref_out_z LPWSTR* ppwz,
266 __in const SYSTEMTIME *pst,
267 __in BOOL fGMT
268 )
269{
270 DWORD dwAbsBias = 0;
271
272 if (fGMT)
273 {
274 return StrAllocFormatted(ppwz, L"%04hu-%02hu-%02huT%02hu:%02hu:%02huZ", pst->wYear, pst->wMonth, pst->wDay, pst->wHour, pst->wMinute, pst->wSecond);
275 }
276 else
277 {
278 SYSTEMTIME st;
279 TIME_ZONE_INFORMATION tzi;
280
281 ::GetTimeZoneInformation(&tzi);
282 ::SystemTimeToTzSpecificLocalTime(&tzi, pst, &st);
283 dwAbsBias = abs(tzi.Bias);
284
285 return StrAllocFormatted(ppwz, L"%04hu-%02hu-%02huT%02hu:%02hu:%02hu%c%02u:%02u", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, 0 >= tzi.Bias ? L'+' : L'-', dwAbsBias / 60, dwAbsBias % 60);
286 }
287}
288
289
290/****************************************************************************
291TimeSystemToDateTimeString - converts the provided system time struct to
292 string format representing date and time for the specified locale
293****************************************************************************/
294HRESULT DAPI TimeSystemToDateTimeString(
295 __deref_out_z LPWSTR* ppwz,
296 __in const SYSTEMTIME* pst,
297 __in LCID locale
298 )
299{
300 HRESULT hr = S_OK;
301 const WCHAR * DATE_FORMAT = L"MMM dd',' yyyy',' ";
302 const WCHAR * TIME_FORMAT = L"hh':'mm':'ss tt";
303 int iLenDate = 0;
304 int iLenTime = 0;
305
306 iLenDate = ::GetDateFormatW(locale, 0, pst, DATE_FORMAT, NULL, 0);
307 if (0 >= iLenDate)
308 {
309 TimeExitWithLastError(hr, "Failed to get date format with NULL");
310 }
311
312 iLenTime = ::GetTimeFormatW(locale, 0, pst, TIME_FORMAT, NULL, 0);
313 if (0 >= iLenTime)
314 {
315 TimeExitWithLastError(hr, "Failed to get time format with NULL");
316 }
317
318 // Between both lengths we account for 2 null terminators, and only need one, so we subtract one
319 hr = StrAlloc(ppwz, iLenDate + iLenTime - 1);
320 TimeExitOnFailure(hr, "Failed to allocate string");
321
322 if (!::GetDateFormatW(locale, 0, pst, DATE_FORMAT, *ppwz, iLenDate))
323 {
324 TimeExitWithLastError(hr, "Failed to get date format with buffer");
325 }
326 // Space to separate them
327 (*ppwz)[iLenDate - 1] = ' ';
328
329 if (!::GetTimeFormatW(locale, 0, pst, TIME_FORMAT, (*ppwz) + iLenDate - 1, iLenTime))
330 {
331 TimeExitWithLastError(hr, "Failed to get time format with buffer");
332 }
333
334LExit:
335 return hr;
336}
337
338/********************************************************************
339 DayFromString - converts string to day
340
341*******************************************************************/
342static HRESULT DayFromString(
343 __in_z LPCWSTR wzDay,
344 __out WORD* pwDayOfWeek
345 )
346{
347 HRESULT hr = E_INVALIDARG; // assume we won't find a matching name
348
349 for (WORD i = 0; i < countof(DAY_OF_WEEK); ++i)
350 {
351 if (0 == lstrcmpW(wzDay, DAY_OF_WEEK[i]))
352 {
353 *pwDayOfWeek = i;
354 hr = S_OK;
355 break;
356 }
357 }
358
359 return hr;
360}
361
362
363/********************************************************************
364 MonthFromString - converts string to month
365
366*******************************************************************/
367static HRESULT MonthFromString(
368 __in_z LPCWSTR wzMonth,
369 __out WORD* pwMonthOfYear
370 )
371{
372 HRESULT hr = E_INVALIDARG; // assume we won't find a matching name
373
374 for (WORD i = 0; i < countof(MONTH_OF_YEAR); ++i)
375 {
376 if (0 == lstrcmpW(wzMonth, MONTH_OF_YEAR[i]))
377 {
378 *pwMonthOfYear = i;
379 hr = S_OK;
380 break;
381 }
382 }
383
384 return hr;
385}
diff --git a/src/libs/dutil/WixToolset.DUtil/uncutil.cpp b/src/libs/dutil/WixToolset.DUtil/uncutil.cpp
new file mode 100644
index 00000000..415ea198
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/uncutil.cpp
@@ -0,0 +1,69 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define UncExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_UNCUTIL, x, s, __VA_ARGS__)
8#define UncExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_UNCUTIL, x, s, __VA_ARGS__)
9#define UncExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_UNCUTIL, x, s, __VA_ARGS__)
10#define UncExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_UNCUTIL, x, s, __VA_ARGS__)
11#define UncExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_UNCUTIL, x, s, __VA_ARGS__)
12#define UncExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_UNCUTIL, x, s, __VA_ARGS__)
13#define UncExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_UNCUTIL, p, x, e, s, __VA_ARGS__)
14#define UncExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_UNCUTIL, p, x, s, __VA_ARGS__)
15#define UncExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_UNCUTIL, p, x, e, s, __VA_ARGS__)
16#define UncExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_UNCUTIL, p, x, s, __VA_ARGS__)
17#define UncExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_UNCUTIL, e, x, s, __VA_ARGS__)
18#define UncExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_UNCUTIL, g, x, s, __VA_ARGS__)
19
20DAPI_(HRESULT) UncConvertFromMountedDrive(
21 __inout LPWSTR *psczUNCPath,
22 __in LPCWSTR sczMountedDrivePath
23 )
24{
25 HRESULT hr = S_OK;
26 DWORD dwLength = 0;
27 DWORD er = ERROR_SUCCESS;
28 LPWSTR sczDrive = NULL;
29
30 // Only copy drive letter and colon
31 hr = StrAllocString(&sczDrive, sczMountedDrivePath, 2);
32 UncExitOnFailure(hr, "Failed to copy drive");
33
34 // ERROR_NOT_CONNECTED means it's not a mapped drive
35 er = ::WNetGetConnectionW(sczDrive, NULL, &dwLength);
36 if (ERROR_MORE_DATA == er)
37 {
38 er = ERROR_SUCCESS;
39
40 hr = StrAlloc(psczUNCPath, dwLength);
41 UncExitOnFailure(hr, "Failed to allocate string to get raw UNC path of length %u", dwLength);
42
43 er = ::WNetGetConnectionW(sczDrive, *psczUNCPath, &dwLength);
44 if (ERROR_CONNECTION_UNAVAIL == er)
45 {
46 // This means the drive is remembered but not currently connected, this can mean the location is accessible via UNC path but not via mounted drive path
47 er = ERROR_SUCCESS;
48 }
49 UncExitOnWin32Error(er, hr, "::WNetGetConnectionW() failed with buffer provided on drive %ls", sczDrive);
50
51 // Skip drive letter and colon
52 hr = StrAllocConcat(psczUNCPath, sczMountedDrivePath + 2, 0);
53 UncExitOnFailure(hr, "Failed to copy rest of database path");
54 }
55 else
56 {
57 if (ERROR_SUCCESS == er)
58 {
59 er = ERROR_NO_DATA;
60 }
61
62 UncExitOnWin32Error(er, hr, "::WNetGetConnectionW() failed on drive %ls", sczDrive);
63 }
64
65LExit:
66 ReleaseStr(sczDrive);
67
68 return hr;
69}
diff --git a/src/libs/dutil/WixToolset.DUtil/uriutil.cpp b/src/libs/dutil/WixToolset.DUtil/uriutil.cpp
new file mode 100644
index 00000000..7913c22e
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/uriutil.cpp
@@ -0,0 +1,553 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define UriExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_URIUTIL, x, s, __VA_ARGS__)
8#define UriExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_URIUTIL, x, s, __VA_ARGS__)
9#define UriExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_URIUTIL, x, s, __VA_ARGS__)
10#define UriExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_URIUTIL, x, s, __VA_ARGS__)
11#define UriExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_URIUTIL, x, s, __VA_ARGS__)
12#define UriExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_URIUTIL, x, s, __VA_ARGS__)
13#define UriExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_URIUTIL, p, x, e, s, __VA_ARGS__)
14#define UriExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_URIUTIL, p, x, s, __VA_ARGS__)
15#define UriExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_URIUTIL, p, x, e, s, __VA_ARGS__)
16#define UriExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_URIUTIL, p, x, s, __VA_ARGS__)
17#define UriExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_URIUTIL, e, x, s, __VA_ARGS__)
18#define UriExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_URIUTIL, g, x, s, __VA_ARGS__)
19
20
21//
22// UriCanonicalize - canonicalizes a URI.
23//
24extern "C" HRESULT DAPI UriCanonicalize(
25 __inout_z LPWSTR* psczUri
26 )
27{
28 HRESULT hr = S_OK;
29 WCHAR wz[INTERNET_MAX_URL_LENGTH] = { };
30 DWORD cch = countof(wz);
31
32 if (!::InternetCanonicalizeUrlW(*psczUri, wz, &cch, ICU_DECODE))
33 {
34 UriExitWithLastError(hr, "Failed to canonicalize URI.");
35 }
36
37 hr = StrAllocString(psczUri, wz, cch);
38 UriExitOnFailure(hr, "Failed copy canonicalized URI.");
39
40LExit:
41 return hr;
42}
43
44
45//
46// UriCrack - cracks a URI into constituent parts.
47//
48extern "C" HRESULT DAPI UriCrack(
49 __in_z LPCWSTR wzUri,
50 __out_opt INTERNET_SCHEME* pScheme,
51 __deref_opt_out_z LPWSTR* psczHostName,
52 __out_opt INTERNET_PORT* pPort,
53 __deref_opt_out_z LPWSTR* psczUser,
54 __deref_opt_out_z LPWSTR* psczPassword,
55 __deref_opt_out_z LPWSTR* psczPath,
56 __deref_opt_out_z LPWSTR* psczQueryString
57 )
58{
59 HRESULT hr = S_OK;
60 URL_COMPONENTSW components = { };
61 WCHAR wzHostName[INTERNET_MAX_HOST_NAME_LENGTH + 1];
62 WCHAR wzUserName[INTERNET_MAX_USER_NAME_LENGTH + 1];
63 WCHAR wzPassword[INTERNET_MAX_PASSWORD_LENGTH + 1];
64 WCHAR wzPath[INTERNET_MAX_PATH_LENGTH + 1];
65 WCHAR wzQueryString[INTERNET_MAX_PATH_LENGTH + 1];
66
67 components.dwStructSize = sizeof(URL_COMPONENTSW);
68
69 if (psczHostName)
70 {
71 components.lpszHostName = wzHostName;
72 components.dwHostNameLength = countof(wzHostName);
73 }
74
75 if (psczUser)
76 {
77 components.lpszUserName = wzUserName;
78 components.dwUserNameLength = countof(wzUserName);
79 }
80
81 if (psczPassword)
82 {
83 components.lpszPassword = wzPassword;
84 components.dwPasswordLength = countof(wzPassword);
85 }
86
87 if (psczPath)
88 {
89 components.lpszUrlPath = wzPath;
90 components.dwUrlPathLength = countof(wzPath);
91 }
92
93 if (psczQueryString)
94 {
95 components.lpszExtraInfo = wzQueryString;
96 components.dwExtraInfoLength = countof(wzQueryString);
97 }
98
99 if (!::InternetCrackUrlW(wzUri, 0, ICU_DECODE | ICU_ESCAPE, &components))
100 {
101 UriExitWithLastError(hr, "Failed to crack URI.");
102 }
103
104 if (pScheme)
105 {
106 *pScheme = components.nScheme;
107 }
108
109 if (psczHostName)
110 {
111 hr = StrAllocString(psczHostName, components.lpszHostName, components.dwHostNameLength);
112 UriExitOnFailure(hr, "Failed to copy host name.");
113 }
114
115 if (pPort)
116 {
117 *pPort = components.nPort;
118 }
119
120 if (psczUser)
121 {
122 hr = StrAllocString(psczUser, components.lpszUserName, components.dwUserNameLength);
123 UriExitOnFailure(hr, "Failed to copy user name.");
124 }
125
126 if (psczPassword)
127 {
128 hr = StrAllocString(psczPassword, components.lpszPassword, components.dwPasswordLength);
129 UriExitOnFailure(hr, "Failed to copy password.");
130 }
131
132 if (psczPath)
133 {
134 hr = StrAllocString(psczPath, components.lpszUrlPath, components.dwUrlPathLength);
135 UriExitOnFailure(hr, "Failed to copy path.");
136 }
137
138 if (psczQueryString)
139 {
140 hr = StrAllocString(psczQueryString, components.lpszExtraInfo, components.dwExtraInfoLength);
141 UriExitOnFailure(hr, "Failed to copy query string.");
142 }
143
144LExit:
145 return hr;
146}
147
148
149//
150// UriCrackEx - cracks a URI into URI_INFO.
151//
152extern "C" HRESULT DAPI UriCrackEx(
153 __in_z LPCWSTR wzUri,
154 __in URI_INFO* pUriInfo
155 )
156{
157 HRESULT hr = S_OK;
158
159 hr = UriCrack(wzUri, &pUriInfo->scheme, &pUriInfo->sczHostName, &pUriInfo->port, &pUriInfo->sczUser, &pUriInfo->sczPassword, &pUriInfo->sczPath, &pUriInfo->sczQueryString);
160 UriExitOnFailure(hr, "Failed to crack URI.");
161
162LExit:
163 return hr;
164}
165
166
167//
168// UriInfoUninitialize - frees the memory in a URI_INFO struct.
169//
170extern "C" void DAPI UriInfoUninitialize(
171 __in URI_INFO* pUriInfo
172 )
173{
174 ReleaseStr(pUriInfo->sczHostName);
175 ReleaseStr(pUriInfo->sczUser);
176 ReleaseStr(pUriInfo->sczPassword);
177 ReleaseStr(pUriInfo->sczPath);
178 ReleaseStr(pUriInfo->sczQueryString);
179 memset(pUriInfo, 0, sizeof(URI_INFO));
180}
181
182
183//
184// UriCreate - creates a URI from constituent parts.
185//
186extern "C" HRESULT DAPI UriCreate(
187 __inout_z LPWSTR* psczUri,
188 __in INTERNET_SCHEME scheme,
189 __in_z_opt LPWSTR wzHostName,
190 __in INTERNET_PORT port,
191 __in_z_opt LPWSTR wzUser,
192 __in_z_opt LPWSTR wzPassword,
193 __in_z_opt LPWSTR wzPath,
194 __in_z_opt LPWSTR wzQueryString
195 )
196{
197 HRESULT hr = S_OK;
198 WCHAR wz[INTERNET_MAX_URL_LENGTH] = { };
199 DWORD cch = countof(wz);
200 URL_COMPONENTSW components = { };
201
202 components.dwStructSize = sizeof(URL_COMPONENTSW);
203 components.nScheme = scheme;
204 components.lpszHostName = wzHostName;
205 components.nPort = port;
206 components.lpszUserName = wzUser;
207 components.lpszPassword = wzPassword;
208 components.lpszUrlPath = wzPath;
209 components.lpszExtraInfo = wzQueryString;
210
211 if (!::InternetCreateUrlW(&components, ICU_ESCAPE, wz, &cch))
212 {
213 UriExitWithLastError(hr, "Failed to create URI.");
214 }
215
216 hr = StrAllocString(psczUri, wz, cch);
217 UriExitOnFailure(hr, "Failed copy created URI.");
218
219LExit:
220 return hr;
221}
222
223
224//
225// UriGetServerAndResource - gets the server and resource as independent strings from a URI.
226//
227// NOTE: This function is useful for the InternetConnect/HttpRequest APIs.
228//
229extern "C" HRESULT DAPI UriGetServerAndResource(
230 __in_z LPCWSTR wzUri,
231 __out_z LPWSTR* psczServer,
232 __out_z LPWSTR* psczResource
233 )
234{
235 HRESULT hr = S_OK;
236 INTERNET_SCHEME scheme = INTERNET_SCHEME_UNKNOWN;
237 LPWSTR sczHostName = NULL;
238 INTERNET_PORT port = INTERNET_INVALID_PORT_NUMBER;
239 LPWSTR sczUser = NULL;
240 LPWSTR sczPassword = NULL;
241 LPWSTR sczPath = NULL;
242 LPWSTR sczQueryString = NULL;
243
244 hr = UriCrack(wzUri, &scheme, &sczHostName, &port, &sczUser, &sczPassword, &sczPath, &sczQueryString);
245 UriExitOnFailure(hr, "Failed to crack URI.");
246
247 hr = UriCreate(psczServer, scheme, sczHostName, port, sczUser, sczPassword, NULL, NULL);
248 UriExitOnFailure(hr, "Failed to allocate server URI.");
249
250 hr = UriCreate(psczResource, INTERNET_SCHEME_UNKNOWN, NULL, INTERNET_INVALID_PORT_NUMBER, NULL, NULL, sczPath, sczQueryString);
251 UriExitOnFailure(hr, "Failed to allocate resource URI.");
252
253LExit:
254 ReleaseStr(sczQueryString);
255 ReleaseStr(sczPath);
256 ReleaseStr(sczPassword);
257 ReleaseStr(sczUser);
258 ReleaseStr(sczHostName);
259
260 return hr;
261}
262
263
264//
265// UriFile - returns the file part of the URI.
266//
267extern "C" HRESULT DAPI UriFile(
268 __deref_out_z LPWSTR* psczFile,
269 __in_z LPCWSTR wzUri
270 )
271{
272 HRESULT hr = S_OK;
273 WCHAR wz[MAX_PATH + 1];
274 DWORD cch = countof(wz);
275 URL_COMPONENTSW uc = { };
276
277 uc.dwStructSize = sizeof(uc);
278 uc.lpszUrlPath = wz;
279 uc.dwUrlPathLength = cch;
280
281 if (!::InternetCrackUrlW(wzUri, 0, ICU_DECODE | ICU_ESCAPE, &uc))
282 {
283 UriExitWithLastError(hr, "Failed to crack URI.");
284 }
285
286 // Copy only the file name. Fortunately, PathFile() understands that
287 // forward slashes can be directory separators like backslashes.
288 hr = StrAllocString(psczFile, PathFile(wz), 0);
289 UriExitOnFailure(hr, "Failed to copy file name");
290
291LExit:
292 return hr;
293}
294
295
296/*******************************************************************
297 UriProtocol - determines the protocol of a URI.
298
299********************************************************************/
300extern "C" HRESULT DAPI UriProtocol(
301 __in_z LPCWSTR wzUri,
302 __out URI_PROTOCOL* pProtocol
303 )
304{
305 Assert(wzUri && *wzUri);
306 Assert(pProtocol);
307
308 HRESULT hr = S_OK;
309
310 if ((L'h' == wzUri[0] || L'H' == wzUri[0]) &&
311 (L't' == wzUri[1] || L'T' == wzUri[1]) &&
312 (L't' == wzUri[2] || L'T' == wzUri[2]) &&
313 (L'p' == wzUri[3] || L'P' == wzUri[3]) &&
314 (L's' == wzUri[4] || L'S' == wzUri[4]) &&
315 L':' == wzUri[5] &&
316 L'/' == wzUri[6] &&
317 L'/' == wzUri[7])
318 {
319 *pProtocol = URI_PROTOCOL_HTTPS;
320 }
321 else if ((L'h' == wzUri[0] || L'H' == wzUri[0]) &&
322 (L't' == wzUri[1] || L'T' == wzUri[1]) &&
323 (L't' == wzUri[2] || L'T' == wzUri[2]) &&
324 (L'p' == wzUri[3] || L'P' == wzUri[3]) &&
325 L':' == wzUri[4] &&
326 L'/' == wzUri[5] &&
327 L'/' == wzUri[6])
328 {
329 *pProtocol = URI_PROTOCOL_HTTP;
330 }
331 else if ((L'f' == wzUri[0] || L'F' == wzUri[0]) &&
332 (L't' == wzUri[1] || L'T' == wzUri[1]) &&
333 (L'p' == wzUri[2] || L'P' == wzUri[2]) &&
334 L':' == wzUri[3] &&
335 L'/' == wzUri[4] &&
336 L'/' == wzUri[5])
337 {
338 *pProtocol = URI_PROTOCOL_FTP;
339 }
340 else if ((L'f' == wzUri[0] || L'F' == wzUri[0]) &&
341 (L'i' == wzUri[1] || L'I' == wzUri[1]) &&
342 (L'l' == wzUri[2] || L'L' == wzUri[2]) &&
343 (L'e' == wzUri[3] || L'E' == wzUri[3]) &&
344 L':' == wzUri[4] &&
345 L'/' == wzUri[5] &&
346 L'/' == wzUri[6])
347 {
348 *pProtocol = URI_PROTOCOL_FILE;
349 }
350 else
351 {
352 *pProtocol = URI_PROTOCOL_UNKNOWN;
353 }
354
355 return hr;
356}
357
358
359/*******************************************************************
360 UriRoot - returns the root of the path specified in the URI.
361
362 examples:
363 file:///C:\path\path -> C:\
364 file://server/share/path/path -> \\server\share
365 http://www.example.com/path/path -> http://www.example.com/
366 ftp://ftp.example.com/path/path -> ftp://www.example.com/
367
368 NOTE: This function should only be used on cannonicalized URIs.
369 It does not cannonicalize itself.
370********************************************************************/
371extern "C" HRESULT DAPI UriRoot(
372 __in_z LPCWSTR wzUri,
373 __out LPWSTR* ppwzRoot,
374 __out_opt URI_PROTOCOL* pProtocol
375 )
376{
377 Assert(wzUri && *wzUri);
378 Assert(ppwzRoot);
379
380 HRESULT hr = S_OK;
381 URI_PROTOCOL protocol = URI_PROTOCOL_UNKNOWN;
382 LPCWSTR pwcSlash = NULL;
383
384 hr = UriProtocol(wzUri, &protocol);
385 UriExitOnFailure(hr, "Invalid URI.");
386
387 switch (protocol)
388 {
389 case URI_PROTOCOL_FILE:
390 if (L'/' == wzUri[7]) // file path
391 {
392 if (((L'a' <= wzUri[8] && L'z' >= wzUri[8]) || (L'A' <= wzUri[8] && L'Z' >= wzUri[8])) && L':' == wzUri[9])
393 {
394 hr = StrAlloc(ppwzRoot, 4);
395 UriExitOnFailure(hr, "Failed to allocate string for root of URI.");
396 *ppwzRoot[0] = wzUri[8];
397 *ppwzRoot[1] = L':';
398 *ppwzRoot[2] = L'\\';
399 *ppwzRoot[3] = L'\0';
400 }
401 else
402 {
403 hr = E_INVALIDARG;
404 UriExitOnFailure(hr, "Invalid file path in URI.");
405 }
406 }
407 else // UNC share
408 {
409 pwcSlash = wcschr(wzUri + 8, L'/');
410 if (!pwcSlash)
411 {
412 hr = E_INVALIDARG;
413 UriExitOnFailure(hr, "Invalid server name in URI.");
414 }
415 else
416 {
417 hr = StrAllocString(ppwzRoot, L"\\\\", 64);
418 UriExitOnFailure(hr, "Failed to allocate string for root of URI.");
419
420 pwcSlash = wcschr(pwcSlash + 1, L'/');
421 if (pwcSlash)
422 {
423 hr = StrAllocConcat(ppwzRoot, wzUri + 8, pwcSlash - wzUri - 8);
424 UriExitOnFailure(hr, "Failed to add server/share to root of URI.");
425 }
426 else
427 {
428 hr = StrAllocConcat(ppwzRoot, wzUri + 8, 0);
429 UriExitOnFailure(hr, "Failed to add server/share to root of URI.");
430 }
431
432 // replace all slashes with backslashes to be truly UNC.
433 for (LPWSTR pwc = *ppwzRoot; pwc && *pwc; ++pwc)
434 {
435 if (L'/' == *pwc)
436 {
437 *pwc = L'\\';
438 }
439 }
440 }
441 }
442 break;
443
444 case URI_PROTOCOL_FTP:
445 pwcSlash = wcschr(wzUri + 6, L'/');
446 if (pwcSlash)
447 {
448 hr = StrAllocString(ppwzRoot, wzUri, pwcSlash - wzUri);
449 UriExitOnFailure(hr, "Failed allocate root from URI.");
450 }
451 else
452 {
453 hr = StrAllocString(ppwzRoot, wzUri, 0);
454 UriExitOnFailure(hr, "Failed allocate root from URI.");
455 }
456 break;
457
458 case URI_PROTOCOL_HTTP:
459 pwcSlash = wcschr(wzUri + 7, L'/');
460 if (pwcSlash)
461 {
462 hr = StrAllocString(ppwzRoot, wzUri, pwcSlash - wzUri);
463 UriExitOnFailure(hr, "Failed allocate root from URI.");
464 }
465 else
466 {
467 hr = StrAllocString(ppwzRoot, wzUri, 0);
468 UriExitOnFailure(hr, "Failed allocate root from URI.");
469 }
470 break;
471
472 default:
473 hr = E_INVALIDARG;
474 UriExitOnFailure(hr, "Unknown URI protocol.");
475 }
476
477 if (pProtocol)
478 {
479 *pProtocol = protocol;
480 }
481
482LExit:
483 return hr;
484}
485
486
487extern "C" HRESULT DAPI UriResolve(
488 __in_z LPCWSTR wzUri,
489 __in_opt LPCWSTR wzBaseUri,
490 __out LPWSTR* ppwzResolvedUri,
491 __out_opt URI_PROTOCOL* pResolvedProtocol
492 )
493{
494 UNREFERENCED_PARAMETER(wzUri);
495 UNREFERENCED_PARAMETER(wzBaseUri);
496 UNREFERENCED_PARAMETER(ppwzResolvedUri);
497 UNREFERENCED_PARAMETER(pResolvedProtocol);
498
499 HRESULT hr = E_NOTIMPL;
500#if 0
501 URI_PROTOCOL protocol = URI_PROTOCOL_UNKNOWN;
502
503 hr = UriProtocol(wzUri, &protocol);
504 UriExitOnFailure(hr, "Failed to determine protocol for URL: %ls", wzUri);
505
506 UriExitOnNull(ppwzResolvedUri, hr, E_INVALIDARG, "Failed to resolve URI, because no method of output was provided");
507
508 if (URI_PROTOCOL_UNKNOWN == protocol)
509 {
510 UriExitOnNull(wzBaseUri, hr, E_INVALIDARG, "Failed to resolve URI - base URI provided was NULL");
511
512 if (L'/' == *wzUri || L'\\' == *wzUri)
513 {
514 hr = UriRoot(wzBaseUri, ppwzResolvedUri, &protocol);
515 UriExitOnFailure(hr, "Failed to get root from URI: %ls", wzBaseUri);
516
517 hr = StrAllocConcat(ppwzResolvedUri, wzUri, 0);
518 UriExitOnFailure(hr, "Failed to concat file to base URI.");
519 }
520 else
521 {
522 hr = UriProtocol(wzBaseUri, &protocol);
523 UriExitOnFailure(hr, "Failed to get protocol of base URI: %ls", wzBaseUri);
524
525 LPCWSTR pwcFile = const_cast<LPCWSTR> (UriFile(wzBaseUri));
526 if (!pwcFile)
527 {
528 hr = E_INVALIDARG;
529 UriExitOnFailure(hr, "Failed to get file from base URI: %ls", wzBaseUri);
530 }
531
532 hr = StrAllocString(ppwzResolvedUri, wzBaseUri, pwcFile - wzBaseUri);
533 UriExitOnFailure(hr, "Failed to allocate string for resolved URI.");
534
535 hr = StrAllocConcat(ppwzResolvedUri, wzUri, 0);
536 UriExitOnFailure(hr, "Failed to concat file to resolved URI.");
537 }
538 }
539 else
540 {
541 hr = StrAllocString(ppwzResolvedUri, wzUri, 0);
542 UriExitOnFailure(hr, "Failed to copy resolved URI.");
543 }
544
545 if (pResolvedProtocol)
546 {
547 *pResolvedProtocol = protocol;
548 }
549
550LExit:
551#endif
552 return hr;
553}
diff --git a/src/libs/dutil/WixToolset.DUtil/userutil.cpp b/src/libs/dutil/WixToolset.DUtil/userutil.cpp
new file mode 100644
index 00000000..ca6d5480
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/userutil.cpp
@@ -0,0 +1,300 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// UserExit macros
7#define UserExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_USERUTIL, x, s, __VA_ARGS__)
8#define UserExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_USERUTIL, x, s, __VA_ARGS__)
9#define UserExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_USERUTIL, x, s, __VA_ARGS__)
10#define UserExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_USERUTIL, x, s, __VA_ARGS__)
11#define UserExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_USERUTIL, x, s, __VA_ARGS__)
12#define UserExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_USERUTIL, x, s, __VA_ARGS__)
13#define UserExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_USERUTIL, p, x, e, s, __VA_ARGS__)
14#define UserExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_USERUTIL, p, x, s, __VA_ARGS__)
15#define UserExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_USERUTIL, p, x, e, s, __VA_ARGS__)
16#define UserExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_USERUTIL, p, x, s, __VA_ARGS__)
17#define UserExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_USERUTIL, e, x, s, __VA_ARGS__)
18#define UserExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_USERUTIL, g, x, s, __VA_ARGS__)
19
20static BOOL CheckIsMemberHelper(
21 __in_z LPCWSTR pwzGroupUserDomain,
22 __in_ecount(cguiGroupData) const GROUP_USERS_INFO_0 *pguiGroupData,
23 __in DWORD cguiGroupData
24 );
25
26/*******************************************************************
27 UserBuildDomainUserName - builds a DOMAIN\USERNAME string
28
29********************************************************************/
30extern "C" HRESULT DAPI UserBuildDomainUserName(
31 __out_ecount_z(cchDest) LPWSTR wzDest,
32 __in int cchDest,
33 __in_z LPCWSTR pwzName,
34 __in_z LPCWSTR pwzDomain
35 )
36{
37 HRESULT hr = S_OK;
38 DWORD cchLeft = cchDest;
39 WCHAR* pwz = wzDest;
40 DWORD cchWz = cchDest;
41 DWORD cch;
42
43 cch = lstrlenW(pwzDomain);
44 if (cch >= cchLeft)
45 {
46 hr = ERROR_MORE_DATA;
47 UserExitOnFailure(hr, "Buffer size is not big enough to hold domain name: %ls", pwzDomain);
48 }
49 else if (cch > 0)
50 {
51 // handle the domain case
52
53 hr = ::StringCchCopyNW(pwz, cchWz, pwzDomain, cchLeft - 1); // last parameter does not include '\0'
54 UserExitOnFailure(hr, "Failed to copy Domain onto string.");
55
56 cchLeft -= cch;
57 pwz += cch;
58 cchWz -= cch;
59
60 if (1 >= cchLeft)
61 {
62 hr = ERROR_MORE_DATA;
63 UserExitOnFailure(hr, "Insufficient buffer size while building domain user name");
64 }
65
66 hr = ::StringCchCopyNW(pwz, cchWz, L"\\", cchLeft - 1); // last parameter does not include '\0'
67 UserExitOnFailure(hr, "Failed to copy backslash onto string.");
68
69 --cchLeft;
70 ++pwz;
71 --cchWz;
72 }
73
74 cch = lstrlenW(pwzName);
75 if (cch >= cchLeft)
76 {
77 hr = ERROR_MORE_DATA;
78 UserExitOnFailure(hr, "Buffer size is not big enough to hold user name: %ls", pwzName);
79 }
80
81 hr = ::StringCchCopyNW(pwz, cchWz, pwzName, cchLeft - 1); // last parameter does not include '\0'
82 UserExitOnFailure(hr, "Failed to copy User name onto string.");
83
84LExit:
85 return hr;
86}
87
88
89/*******************************************************************
90 Checks whether a user is a member of a group - outputs the result via lpfMember
91********************************************************************/
92extern "C" HRESULT DAPI UserCheckIsMember(
93 __in_z LPCWSTR pwzName,
94 __in_z LPCWSTR pwzDomain,
95 __in_z LPCWSTR pwzGroupName,
96 __in_z LPCWSTR pwzGroupDomain,
97 __out LPBOOL lpfMember
98 )
99{
100 HRESULT hr = S_OK;
101 UINT er = ERROR_SUCCESS;
102
103 DWORD dwRead = 0;
104 DWORD dwTotal = 0;
105 LPCWSTR wz = NULL;
106 GROUP_USERS_INFO_0 *pguiGroupData = NULL;
107 WCHAR wzGroupUserDomain[MAX_DARWIN_COLUMN + 1]; // GROUPDOMAIN\GROUPNAME
108 WCHAR wzUserDomain[MAX_DARWIN_COLUMN + 1]; // USERDOMAIN\USERNAME
109 BSTR bstrUser = NULL;
110 BSTR bstrGroup = NULL;
111
112 IADsGroup *pGroup = NULL;
113 VARIANT_BOOL vtBoolResult = VARIANT_FALSE;
114
115 hr = UserBuildDomainUserName(wzGroupUserDomain, countof(wzGroupUserDomain), pwzGroupName, pwzGroupDomain);
116 UserExitOnFailure(hr, "Failed to build group name from group domain %ls, group name %ls", pwzGroupDomain, pwzGroupName);
117
118 hr = UserBuildDomainUserName(wzUserDomain, countof(wzUserDomain), pwzName, pwzDomain);
119 UserExitOnFailure(hr, "Failed to build group name from group domain %ls, group name %ls", pwzGroupDomain, pwzGroupName);
120
121 if (pwzDomain && *pwzDomain)
122 {
123 wz = pwzDomain;
124 }
125
126 er = ::NetUserGetGroups(wz, pwzName, 0, (LPBYTE *)&pguiGroupData, MAX_PREFERRED_LENGTH, &dwRead, &dwTotal);
127 // Ignore these errors, and just go to the fallback checks
128 if (ERROR_BAD_NETPATH == er || ERROR_INVALID_NAME == er || NERR_UserNotFound == er)
129 {
130 Trace(REPORT_VERBOSE, "failed to get groups for user %ls from domain %ls with error code 0x%x - continuing", pwzName, (wz != NULL) ? wz : L"", HRESULT_FROM_WIN32(er));
131 er = ERROR_SUCCESS;
132 }
133 UserExitOnWin32Error(er, hr, "Failed to get list of global groups for user while checking group membership information for user: %ls", pwzName);
134
135 if (dwRead != dwTotal)
136 {
137 hr = HRESULT_FROM_WIN32(ERROR_MORE_DATA);
138 UserExitOnRootFailure(hr, "Failed to get entire list of groups (global) for user while checking group membership information for user: %ls", pwzName);
139 }
140
141 if (CheckIsMemberHelper(wzGroupUserDomain, pguiGroupData, dwRead))
142 {
143 *lpfMember = TRUE;
144 ExitFunction1(hr = S_OK);
145 }
146
147 if (NULL != pguiGroupData)
148 {
149 ::NetApiBufferFree(pguiGroupData);
150 pguiGroupData = NULL;
151 }
152
153 // If we fail with the global groups, try again with the local groups
154 er = ::NetUserGetLocalGroups(NULL, wzUserDomain, 0, LG_INCLUDE_INDIRECT, (LPBYTE *)&pguiGroupData, MAX_PREFERRED_LENGTH, &dwRead, &dwTotal);
155 // Ignore these errors, and just go to the fallback checks
156 if (NERR_UserNotFound == er || NERR_DCNotFound == er || RPC_S_SERVER_UNAVAILABLE == er)
157 {
158 Trace(REPORT_VERBOSE, "failed to get local groups for user %ls from domain %ls with error code 0x%x - continuing", pwzName, (wz != NULL) ? wz : L"", HRESULT_FROM_WIN32(er));
159 er = ERROR_SUCCESS;
160 }
161 UserExitOnWin32Error(er, hr, "Failed to get list of groups for user while checking group membership information for user: %ls", pwzName);
162
163 if (dwRead != dwTotal)
164 {
165 hr = HRESULT_FROM_WIN32(ERROR_MORE_DATA);
166 UserExitOnRootFailure(hr, "Failed to get entire list of groups (local) for user while checking group membership information for user: %ls", pwzName);
167 }
168
169 if (CheckIsMemberHelper(wzGroupUserDomain, pguiGroupData, dwRead))
170 {
171 *lpfMember = TRUE;
172 ExitFunction1(hr = S_OK);
173 }
174
175 // If the above methods failed, let's try active directory
176 hr = UserCreateADsPath(pwzDomain, pwzName, &bstrUser);
177 UserExitOnFailure(hr, "failed to create user ADsPath in order to check group membership for group: %ls domain: %ls", pwzName, pwzDomain);
178
179 hr = UserCreateADsPath(pwzGroupDomain, pwzGroupName, &bstrGroup);
180 UserExitOnFailure(hr, "failed to create group ADsPath in order to check group membership for group: %ls domain: %ls", pwzGroupName, pwzGroupDomain);
181
182 if (lstrlenW(pwzGroupDomain) > 0)
183 {
184 hr = ::ADsGetObject(bstrGroup, IID_IADsGroup, reinterpret_cast<void**>(&pGroup));
185 UserExitOnFailure(hr, "Failed to get group '%ls' from active directory.", reinterpret_cast<WCHAR*>(bstrGroup) );
186
187 hr = pGroup->IsMember(bstrUser, &vtBoolResult);
188 UserExitOnFailure(hr, "Failed to check if user %ls is a member of group '%ls' using active directory.", reinterpret_cast<WCHAR*>(bstrUser), reinterpret_cast<WCHAR*>(bstrGroup) );
189 }
190
191 if (vtBoolResult)
192 {
193 *lpfMember = TRUE;
194 ExitFunction1(hr = S_OK);
195 }
196
197 hr = ::ADsGetObject(bstrGroup, IID_IADsGroup, reinterpret_cast<void**>(&pGroup));
198 UserExitOnFailure(hr, "Failed to get group '%ls' from active directory.", reinterpret_cast<WCHAR*>(bstrGroup) );
199
200 hr = pGroup->IsMember(bstrUser, &vtBoolResult);
201 UserExitOnFailure(hr, "Failed to check if user %ls is a member of group '%ls' using active directory.", reinterpret_cast<WCHAR*>(bstrUser), reinterpret_cast<WCHAR*>(bstrGroup) );
202
203 if (vtBoolResult)
204 {
205 *lpfMember = TRUE;
206 ExitFunction1(hr = S_OK);
207 }
208
209LExit:
210 ReleaseObject(pGroup);
211 ReleaseBSTR(bstrUser);
212 ReleaseBSTR(bstrGroup);
213
214 if (NULL != pguiGroupData)
215 {
216 ::NetApiBufferFree(pguiGroupData);
217 }
218
219 return hr;
220}
221
222
223/*******************************************************************
224 Takes a domain and name, and allocates a BSTR which represents
225 DOMAIN\NAME's active directory path. The BSTR this function returns
226 should be released manually using the ReleaseBSTR() macro.
227********************************************************************/
228extern "C" HRESULT DAPI UserCreateADsPath(
229 __in_z LPCWSTR wzObjectDomain,
230 __in_z LPCWSTR wzObjectName,
231 __out BSTR *pbstrAdsPath
232 )
233{
234 Assert(wzObjectDomain && wzObjectName && *wzObjectName);
235
236 HRESULT hr = S_OK;
237 LPWSTR pwzAdsPath = NULL;
238
239 hr = StrAllocString(&pwzAdsPath, L"WinNT://", 0);
240 UserExitOnFailure(hr, "failed to allocate AdsPath string");
241
242 if (*wzObjectDomain)
243 {
244 hr = StrAllocFormatted(&pwzAdsPath, L"%s/%s", wzObjectDomain, wzObjectName);
245 UserExitOnFailure(hr, "failed to allocate AdsPath string");
246 }
247 else if (NULL != wcsstr(wzObjectName, L"\\") || NULL != wcsstr(wzObjectName, L"/"))
248 {
249 hr = StrAllocConcat(&pwzAdsPath, wzObjectName, 0);
250 UserExitOnFailure(hr, "failed to concat objectname: %ls", wzObjectName);
251 }
252 else
253 {
254 hr = StrAllocConcat(&pwzAdsPath, L"Localhost/", 0);
255 UserExitOnFailure(hr, "failed to concat LocalHost/");
256
257 hr = StrAllocConcat(&pwzAdsPath, wzObjectName, 0);
258 UserExitOnFailure(hr, "failed to concat object name: %ls", wzObjectName);
259 }
260
261 *pbstrAdsPath = ::SysAllocString(pwzAdsPath);
262 if (NULL == *pbstrAdsPath)
263 {
264 hr = E_OUTOFMEMORY;
265 }
266
267LExit:
268 ReleaseStr(pwzAdsPath);
269
270 return hr;
271}
272
273
274/*******************************************************************
275 Helper function to check if pwzGroupUserDomain (in form of "domain\username" is
276 a member of a given LOCALGROUP_USERS_INFO_0 structure. Useful to pass in the
277 output from both NetUserGetGroups() and NetUserGetLocalGroups()
278********************************************************************/
279static BOOL CheckIsMemberHelper(
280 __in_z LPCWSTR pwzGroupUserDomain,
281 __in_ecount(cguiGroupData) const GROUP_USERS_INFO_0 *pguiGroupData,
282 __in DWORD cguiGroupData
283 )
284{
285 if (NULL == pguiGroupData)
286 {
287 return FALSE;
288 }
289
290 for (DWORD dwCounter = 0; dwCounter < cguiGroupData; ++dwCounter)
291 {
292 // If the user is a member of the group, set the output flag to true
293 if (0 == lstrcmpiW(pwzGroupUserDomain, pguiGroupData[dwCounter].grui0_name))
294 {
295 return TRUE;
296 }
297 }
298
299 return FALSE;
300}
diff --git a/src/libs/dutil/WixToolset.DUtil/verutil.cpp b/src/libs/dutil/WixToolset.DUtil/verutil.cpp
new file mode 100644
index 00000000..21626f94
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/verutil.cpp
@@ -0,0 +1,647 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5// Exit macros
6#define VerExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__)
7#define VerExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__)
8#define VerExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__)
9#define VerExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__)
10#define VerExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__)
11#define VerExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__)
12#define VerExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_VERUTIL, p, x, e, s, __VA_ARGS__)
13#define VerExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_VERUTIL, p, x, s, __VA_ARGS__)
14#define VerExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_VERUTIL, p, x, e, s, __VA_ARGS__)
15#define VerExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_VERUTIL, p, x, s, __VA_ARGS__)
16#define VerExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_VERUTIL, e, x, s, __VA_ARGS__)
17
18// constants
19const DWORD GROW_RELEASE_LABELS = 3;
20
21// Forward declarations.
22static int CompareDword(
23 __in const DWORD& dw1,
24 __in const DWORD& dw2
25 );
26static HRESULT CompareReleaseLabel(
27 __in const VERUTIL_VERSION_RELEASE_LABEL* p1,
28 __in LPCWSTR wzVersion1,
29 __in const VERUTIL_VERSION_RELEASE_LABEL* p2,
30 __in LPCWSTR wzVersion2,
31 __out int* pnResult
32 );
33static HRESULT CompareVersionSubstring(
34 __in LPCWSTR wzString1,
35 __in int cchCount1,
36 __in LPCWSTR wzString2,
37 __in int cchCount2,
38 __out int* pnResult
39 );
40
41
42DAPI_(HRESULT) VerCompareParsedVersions(
43 __in_opt VERUTIL_VERSION* pVersion1,
44 __in_opt VERUTIL_VERSION* pVersion2,
45 __out int* pnResult
46 )
47{
48 HRESULT hr = S_OK;
49 int nResult = 0;
50 DWORD cMaxReleaseLabels = 0;
51 BOOL fCompareMetadata = FALSE;
52
53 if (pVersion1 && !pVersion1->sczVersion ||
54 pVersion2 && !pVersion2->sczVersion)
55 {
56 ExitFunction1(hr = E_INVALIDARG);
57 }
58
59 if (pVersion1 == pVersion2)
60 {
61 ExitFunction1(nResult = 0);
62 }
63 else if (pVersion1 && !pVersion2)
64 {
65 ExitFunction1(nResult = 1);
66 }
67 else if (!pVersion1 && pVersion2)
68 {
69 ExitFunction1(nResult = -1);
70 }
71
72 nResult = CompareDword(pVersion1->dwMajor, pVersion2->dwMajor);
73 if (0 != nResult)
74 {
75 ExitFunction();
76 }
77
78 nResult = CompareDword(pVersion1->dwMinor, pVersion2->dwMinor);
79 if (0 != nResult)
80 {
81 ExitFunction();
82 }
83
84 nResult = CompareDword(pVersion1->dwPatch, pVersion2->dwPatch);
85 if (0 != nResult)
86 {
87 ExitFunction();
88 }
89
90 nResult = CompareDword(pVersion1->dwRevision, pVersion2->dwRevision);
91 if (0 != nResult)
92 {
93 ExitFunction();
94 }
95
96 if (pVersion1->cReleaseLabels)
97 {
98 if (pVersion2->cReleaseLabels)
99 {
100 cMaxReleaseLabels = max(pVersion1->cReleaseLabels, pVersion2->cReleaseLabels);
101 }
102 else
103 {
104 ExitFunction1(nResult = -1);
105 }
106 }
107 else if (pVersion2->cReleaseLabels)
108 {
109 ExitFunction1(nResult = 1);
110 }
111
112 if (cMaxReleaseLabels)
113 {
114 for (DWORD i = 0; i < cMaxReleaseLabels; ++i)
115 {
116 VERUTIL_VERSION_RELEASE_LABEL* pReleaseLabel1 = pVersion1->cReleaseLabels > i ? pVersion1->rgReleaseLabels + i : NULL;
117 VERUTIL_VERSION_RELEASE_LABEL* pReleaseLabel2 = pVersion2->cReleaseLabels > i ? pVersion2->rgReleaseLabels + i : NULL;
118
119 hr = CompareReleaseLabel(pReleaseLabel1, pVersion1->sczVersion, pReleaseLabel2, pVersion2->sczVersion, &nResult);
120 if (FAILED(hr) || 0 != nResult)
121 {
122 ExitFunction();
123 }
124 }
125 }
126
127 if (pVersion1->fInvalid)
128 {
129 if (!pVersion2->fInvalid)
130 {
131 ExitFunction1(nResult = -1);
132 }
133 else
134 {
135 fCompareMetadata = TRUE;
136 }
137 }
138 else if (pVersion2->fInvalid)
139 {
140 ExitFunction1(nResult = 1);
141 }
142
143 if (fCompareMetadata)
144 {
145 hr = CompareVersionSubstring(pVersion1->sczVersion + pVersion1->cchMetadataOffset, -1, pVersion2->sczVersion + pVersion2->cchMetadataOffset, -1, &nResult);
146 }
147
148LExit:
149 *pnResult = nResult;
150 return hr;
151}
152
153DAPI_(HRESULT) VerCompareStringVersions(
154 __in_z LPCWSTR wzVersion1,
155 __in_z LPCWSTR wzVersion2,
156 __in BOOL fStrict,
157 __out int* pnResult
158 )
159{
160 HRESULT hr = S_OK;
161 VERUTIL_VERSION* pVersion1 = NULL;
162 VERUTIL_VERSION* pVersion2 = NULL;
163 int nResult = 0;
164
165 hr = VerParseVersion(wzVersion1, 0, fStrict, &pVersion1);
166 VerExitOnFailure(hr, "Failed to parse Verutil version '%ls'", wzVersion1);
167
168 hr = VerParseVersion(wzVersion2, 0, fStrict, &pVersion2);
169 VerExitOnFailure(hr, "Failed to parse Verutil version '%ls'", wzVersion2);
170
171 hr = VerCompareParsedVersions(pVersion1, pVersion2, &nResult);
172 VerExitOnFailure(hr, "Failed to compare parsed Verutil versions '%ls' and '%ls'.", wzVersion1, wzVersion2);
173
174LExit:
175 *pnResult = nResult;
176
177 ReleaseVerutilVersion(pVersion1);
178 ReleaseVerutilVersion(pVersion2);
179
180 return hr;
181}
182
183DAPI_(HRESULT) VerCopyVersion(
184 __in VERUTIL_VERSION* pSource,
185 __out VERUTIL_VERSION** ppVersion
186 )
187{
188 HRESULT hr = S_OK;
189 VERUTIL_VERSION* pCopy = NULL;
190
191 pCopy = reinterpret_cast<VERUTIL_VERSION*>(MemAlloc(sizeof(VERUTIL_VERSION), TRUE));
192 VerExitOnNull(pCopy, hr, E_OUTOFMEMORY, "Failed to allocate memory for Verutil version copy.");
193
194 hr = StrAllocString(&pCopy->sczVersion, pSource->sczVersion, 0);
195 VerExitOnFailure(hr, "Failed to copy Verutil version string '%ls'.", pSource->sczVersion);
196
197 pCopy->dwMajor = pSource->dwMajor;
198 pCopy->dwMinor = pSource->dwMinor;
199 pCopy->dwPatch = pSource->dwPatch;
200 pCopy->dwRevision = pSource->dwRevision;
201
202 if (pSource->cReleaseLabels)
203 {
204 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&pCopy->rgReleaseLabels), 0, sizeof(VERUTIL_VERSION_RELEASE_LABEL), pSource->cReleaseLabels);
205 VerExitOnFailure(hr, "Failed to allocate memory for Verutil version release labels copies.");
206
207 pCopy->cReleaseLabels = pSource->cReleaseLabels;
208
209 for (DWORD i = 0; i < pCopy->cReleaseLabels; ++i)
210 {
211 VERUTIL_VERSION_RELEASE_LABEL* pSourceLabel = pSource->rgReleaseLabels + i;
212 VERUTIL_VERSION_RELEASE_LABEL* pCopyLabel = pCopy->rgReleaseLabels + i;
213
214 pCopyLabel->cchLabelOffset = pSourceLabel->cchLabelOffset;
215 pCopyLabel->cchLabel = pSourceLabel->cchLabel;
216 pCopyLabel->fNumeric = pSourceLabel->fNumeric;
217 pCopyLabel->dwValue = pSourceLabel->dwValue;
218 }
219 }
220
221 pCopy->cchMetadataOffset = pSource->cchMetadataOffset;
222 pCopy->fInvalid = pSource->fInvalid;
223
224 *ppVersion = pCopy;
225 pCopy = NULL;
226
227LExit:
228 ReleaseVerutilVersion(pCopy);
229
230 return hr;
231}
232
233DAPI_(void) VerFreeVersion(
234 __in VERUTIL_VERSION* pVersion
235 )
236{
237 if (pVersion)
238 {
239 ReleaseStr(pVersion->sczVersion);
240 ReleaseMem(pVersion->rgReleaseLabels);
241 ReleaseMem(pVersion);
242 }
243}
244
245DAPI_(HRESULT) VerParseVersion(
246 __in_z LPCWSTR wzVersion,
247 __in SIZE_T cchVersion,
248 __in BOOL fStrict,
249 __out VERUTIL_VERSION** ppVersion
250 )
251{
252 HRESULT hr = S_OK;
253 VERUTIL_VERSION* pVersion = NULL;
254 LPCWSTR wzEnd = NULL;
255 LPCWSTR wzPartBegin = NULL;
256 LPCWSTR wzPartEnd = NULL;
257 BOOL fInvalid = FALSE;
258 BOOL fLastPart = FALSE;
259 BOOL fTrailingDot = FALSE;
260 BOOL fParsedVersionNumber = FALSE;
261 BOOL fExpectedReleaseLabels = FALSE;
262 DWORD iPart = 0;
263
264 if (!wzVersion || !ppVersion)
265 {
266 ExitFunction1(hr = E_INVALIDARG);
267 }
268
269 // Get string length if not provided.
270 if (!cchVersion)
271 {
272 hr = ::StringCchLengthW(wzVersion, STRSAFE_MAX_CCH, reinterpret_cast<size_t*>(&cchVersion));
273 VerExitOnRootFailure(hr, "Failed to get length of version string: %ls", wzVersion);
274 }
275 else if (INT_MAX < cchVersion)
276 {
277 VerExitOnRootFailure(hr = E_INVALIDARG, "Version string is too long: %Iu", cchVersion);
278 }
279
280 if (L'v' == *wzVersion || L'V' == *wzVersion)
281 {
282 ++wzVersion;
283 --cchVersion;
284 }
285
286 pVersion = reinterpret_cast<VERUTIL_VERSION*>(MemAlloc(sizeof(VERUTIL_VERSION), TRUE));
287 VerExitOnNull(pVersion, hr, E_OUTOFMEMORY, "Failed to allocate memory for Verutil version '%ls'.", wzVersion);
288
289 hr = StrAllocString(&pVersion->sczVersion, wzVersion, cchVersion);
290 VerExitOnFailure(hr, "Failed to copy Verutil version string '%ls'.", wzVersion);
291
292 wzVersion = wzPartBegin = wzPartEnd = pVersion->sczVersion;
293
294 // Save end pointer.
295 wzEnd = wzVersion + cchVersion;
296
297 // Parse version number
298 while (wzPartBegin < wzEnd)
299 {
300 fTrailingDot = FALSE;
301
302 // Find end of part.
303 for (;;)
304 {
305 if (wzPartEnd >= wzEnd)
306 {
307 fLastPart = TRUE;
308 break;
309 }
310
311 switch (*wzPartEnd)
312 {
313 case L'0':
314 case L'1':
315 case L'2':
316 case L'3':
317 case L'4':
318 case L'5':
319 case L'6':
320 case L'7':
321 case L'8':
322 case L'9':
323 ++wzPartEnd;
324 continue;
325 case L'.':
326 fTrailingDot = TRUE;
327 break;
328 case L'-':
329 case L'+':
330 fLastPart = TRUE;
331 break;
332 default:
333 fInvalid = TRUE;
334 break;
335 }
336
337 break;
338 }
339
340 if (wzPartBegin == wzPartEnd)
341 {
342 fInvalid = TRUE;
343 }
344
345 if (fInvalid)
346 {
347 break;
348 }
349
350 DWORD cchPart = 0;
351 hr = ::PtrdiffTToDWord(wzPartEnd - wzPartBegin, &cchPart);
352 if (FAILED(hr))
353 {
354 fInvalid = TRUE;
355 break;
356 }
357
358 // Parse version part.
359 UINT uPart = 0;
360 hr = StrStringToUInt32(wzPartBegin, cchPart, &uPart);
361 if (FAILED(hr))
362 {
363 fInvalid = TRUE;
364 break;
365 }
366
367 switch (iPart)
368 {
369 case 0:
370 pVersion->dwMajor = uPart;
371 break;
372 case 1:
373 pVersion->dwMinor = uPart;
374 break;
375 case 2:
376 pVersion->dwPatch = uPart;
377 break;
378 case 3:
379 pVersion->dwRevision = uPart;
380 break;
381 }
382
383 if (fTrailingDot)
384 {
385 ++wzPartEnd;
386 }
387 wzPartBegin = wzPartEnd;
388 ++iPart;
389
390 if (4 <= iPart || fLastPart)
391 {
392 fParsedVersionNumber = TRUE;
393 break;
394 }
395 }
396
397 fInvalid |= !fParsedVersionNumber || fTrailingDot;
398
399 if (!fInvalid && wzPartBegin < wzEnd && *wzPartBegin == L'-')
400 {
401 wzPartBegin = wzPartEnd = wzPartBegin + 1;
402 fExpectedReleaseLabels = TRUE;
403 fLastPart = FALSE;
404 }
405
406 while (fExpectedReleaseLabels && wzPartBegin < wzEnd)
407 {
408 fTrailingDot = FALSE;
409
410 // Find end of part.
411 for (;;)
412 {
413 if (wzPartEnd >= wzEnd)
414 {
415 fLastPart = TRUE;
416 break;
417 }
418
419 if (*wzPartEnd >= L'0' && *wzPartEnd <= L'9' ||
420 *wzPartEnd >= L'A' && *wzPartEnd <= L'Z' ||
421 *wzPartEnd >= L'a' && *wzPartEnd <= L'z' ||
422 *wzPartEnd == L'-')
423 {
424 ++wzPartEnd;
425 continue;
426 }
427 else if (*wzPartEnd == L'+')
428 {
429 fLastPart = TRUE;
430 }
431 else if (*wzPartEnd == L'.')
432 {
433 fTrailingDot = TRUE;
434 }
435 else
436 {
437 fInvalid = TRUE;
438 }
439
440 break;
441 }
442
443 if (wzPartBegin == wzPartEnd)
444 {
445 fInvalid = TRUE;
446 }
447
448 if (fInvalid)
449 {
450 break;
451 }
452
453 int cchLabel = 0;
454 hr = ::PtrdiffTToInt32(wzPartEnd - wzPartBegin, &cchLabel);
455 if (FAILED(hr) || 0 > cchLabel)
456 {
457 fInvalid = TRUE;
458 break;
459 }
460
461 hr = MemReAllocArray(reinterpret_cast<LPVOID*>(&pVersion->rgReleaseLabels), pVersion->cReleaseLabels, sizeof(VERUTIL_VERSION_RELEASE_LABEL), GROW_RELEASE_LABELS - (pVersion->cReleaseLabels % GROW_RELEASE_LABELS));
462 VerExitOnFailure(hr, "Failed to allocate memory for Verutil version release labels '%ls'", wzVersion);
463
464 VERUTIL_VERSION_RELEASE_LABEL* pReleaseLabel = pVersion->rgReleaseLabels + pVersion->cReleaseLabels;
465 ++pVersion->cReleaseLabels;
466
467 // Try to parse as number.
468 UINT uLabel = 0;
469 hr = StrStringToUInt32(wzPartBegin, cchLabel, &uLabel);
470 if (SUCCEEDED(hr))
471 {
472 pReleaseLabel->fNumeric = TRUE;
473 pReleaseLabel->dwValue = uLabel;
474 }
475
476 pReleaseLabel->cchLabelOffset = wzPartBegin - pVersion->sczVersion;
477 pReleaseLabel->cchLabel = cchLabel;
478
479 if (fTrailingDot)
480 {
481 ++wzPartEnd;
482 }
483 wzPartBegin = wzPartEnd;
484
485 if (fLastPart)
486 {
487 break;
488 }
489 }
490
491 fInvalid |= fExpectedReleaseLabels && (!pVersion->cReleaseLabels || fTrailingDot);
492
493 if (!fInvalid && wzPartBegin < wzEnd)
494 {
495 if (*wzPartBegin == L'+')
496 {
497 wzPartBegin = wzPartEnd = wzPartBegin + 1;
498 }
499 else
500 {
501 fInvalid = TRUE;
502 }
503 }
504
505 if (fInvalid && fStrict)
506 {
507 ExitFunction1(hr = E_INVALIDARG);
508 }
509
510 pVersion->cchMetadataOffset = min(wzPartBegin, wzEnd) - pVersion->sczVersion;
511 pVersion->fInvalid = fInvalid;
512
513 *ppVersion = pVersion;
514 pVersion = NULL;
515 hr = S_OK;
516
517LExit:
518 ReleaseVerutilVersion(pVersion);
519
520 return hr;
521}
522
523DAPI_(HRESULT) VerVersionFromQword(
524 __in DWORD64 qwVersion,
525 __out VERUTIL_VERSION** ppVersion
526 )
527{
528 HRESULT hr = S_OK;
529 VERUTIL_VERSION* pVersion = NULL;
530
531 pVersion = reinterpret_cast<VERUTIL_VERSION*>(MemAlloc(sizeof(VERUTIL_VERSION), TRUE));
532 VerExitOnNull(pVersion, hr, E_OUTOFMEMORY, "Failed to allocate memory for Verutil version from QWORD.");
533
534 pVersion->dwMajor = (WORD)(qwVersion >> 48 & 0xffff);
535 pVersion->dwMinor = (WORD)(qwVersion >> 32 & 0xffff);
536 pVersion->dwPatch = (WORD)(qwVersion >> 16 & 0xffff);
537 pVersion->dwRevision = (WORD)(qwVersion & 0xffff);
538
539 hr = StrAllocFormatted(&pVersion->sczVersion, L"%lu.%lu.%lu.%lu", pVersion->dwMajor, pVersion->dwMinor, pVersion->dwPatch, pVersion->dwRevision);
540 ExitOnFailure(hr, "Failed to allocate and format the version string.");
541
542 pVersion->cchMetadataOffset = lstrlenW(pVersion->sczVersion);
543
544 *ppVersion = pVersion;
545 pVersion = NULL;
546
547LExit:
548 ReleaseVerutilVersion(pVersion);
549
550 return hr;
551}
552
553
554static int CompareDword(
555 __in const DWORD& dw1,
556 __in const DWORD& dw2
557 )
558{
559 int nResult = 0;
560
561 if (dw1 > dw2)
562 {
563 nResult = 1;
564 }
565 else if (dw1 < dw2)
566 {
567 nResult = -1;
568 }
569
570 return nResult;
571}
572
573static HRESULT CompareReleaseLabel(
574 __in const VERUTIL_VERSION_RELEASE_LABEL* p1,
575 __in LPCWSTR wzVersion1,
576 __in const VERUTIL_VERSION_RELEASE_LABEL* p2,
577 __in LPCWSTR wzVersion2,
578 __out int* pnResult
579 )
580{
581 HRESULT hr = S_OK;
582 int nResult = 0;
583
584 if (p1 == p2)
585 {
586 ExitFunction();
587 }
588 else if (p1 && !p2)
589 {
590 ExitFunction1(nResult = 1);
591 }
592 else if (!p1 && p2)
593 {
594 ExitFunction1(nResult = -1);
595 }
596
597 if (p1->fNumeric)
598 {
599 if (p2->fNumeric)
600 {
601 nResult = CompareDword(p1->dwValue, p2->dwValue);
602 }
603 else
604 {
605 nResult = -1;
606 }
607 }
608 else
609 {
610 if (p2->fNumeric)
611 {
612 nResult = 1;
613 }
614 else
615 {
616 hr = CompareVersionSubstring(wzVersion1 + p1->cchLabelOffset, p1->cchLabel, wzVersion2 + p2->cchLabelOffset, p2->cchLabel, &nResult);
617 }
618 }
619
620LExit:
621 *pnResult = nResult;
622
623 return hr;
624}
625
626static HRESULT CompareVersionSubstring(
627 __in LPCWSTR wzString1,
628 __in int cchCount1,
629 __in LPCWSTR wzString2,
630 __in int cchCount2,
631 __out int* pnResult
632 )
633{
634 HRESULT hr = S_OK;
635 int nResult = 0;
636
637 nResult = ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, wzString1, cchCount1, wzString2, cchCount2);
638 if (!nResult)
639 {
640 VerExitOnLastError(hr, "Failed to compare version substrings");
641 }
642
643LExit:
644 *pnResult = nResult - 2;
645
646 return hr;
647}
diff --git a/src/libs/dutil/WixToolset.DUtil/wiutil.cpp b/src/libs/dutil/WixToolset.DUtil/wiutil.cpp
new file mode 100644
index 00000000..7414ac42
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/wiutil.cpp
@@ -0,0 +1,1629 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define WiuExitTrace(x, s, ...) ExitTraceSource(DUTIL_SOURCE_WIUTIL, x, s, __VA_ARGS__)
8#define WiuExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_WIUTIL, x, s, __VA_ARGS__)
9#define WiuExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_WIUTIL, x, s, __VA_ARGS__)
10#define WiuExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_WIUTIL, x, s, __VA_ARGS__)
11#define WiuExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_WIUTIL, x, s, __VA_ARGS__)
12#define WiuExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_WIUTIL, x, s, __VA_ARGS__)
13#define WiuExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_WIUTIL, x, s, __VA_ARGS__)
14#define WiuExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_WIUTIL, p, x, e, s, __VA_ARGS__)
15#define WiuExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_WIUTIL, p, x, s, __VA_ARGS__)
16#define WiuExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_WIUTIL, p, x, e, s, __VA_ARGS__)
17#define WiuExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_WIUTIL, p, x, s, __VA_ARGS__)
18#define WiuExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_WIUTIL, e, x, s, __VA_ARGS__)
19#define WiuExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_WIUTIL, g, x, s, __VA_ARGS__)
20
21
22// constants
23
24const DWORD WIU_MSI_PROGRESS_INVALID = 0xFFFFFFFF;
25const DWORD WIU_GOOD_ENOUGH_PROPERTY_LENGTH = 64;
26
27
28// structs
29
30
31static PFN_MSIENABLELOGW vpfnMsiEnableLogW = ::MsiEnableLogW;
32static PFN_MSIGETPRODUCTINFOW vpfnMsiGetProductInfoW = ::MsiGetProductInfoW;
33static PFN_MSIQUERYFEATURESTATEW vpfnMsiQueryFeatureStateW = ::MsiQueryFeatureStateW;
34static PFN_MSIGETCOMPONENTPATHW vpfnMsiGetComponentPathW = ::MsiGetComponentPathW;
35static PFN_MSILOCATECOMPONENTW vpfnMsiLocateComponentW = ::MsiLocateComponentW;
36static PFN_MSIINSTALLPRODUCTW vpfnMsiInstallProductW = ::MsiInstallProductW;
37static PFN_MSICONFIGUREPRODUCTEXW vpfnMsiConfigureProductExW = ::MsiConfigureProductExW;
38static PFN_MSIREMOVEPATCHESW vpfnMsiRemovePatchesW = ::MsiRemovePatchesW;
39static PFN_MSISETINTERNALUI vpfnMsiSetInternalUI = ::MsiSetInternalUI;
40static PFN_MSISETEXTERNALUIW vpfnMsiSetExternalUIW = ::MsiSetExternalUIW;
41static PFN_MSIENUMPRODUCTSW vpfnMsiEnumProductsW = ::MsiEnumProductsW;
42static PFN_MSIENUMRELATEDPRODUCTSW vpfnMsiEnumRelatedProductsW = ::MsiEnumRelatedProductsW;
43
44// MSI 3.0+
45static PFN_MSIDETERMINEPATCHSEQUENCEW vpfnMsiDeterminePatchSequenceW = NULL;
46static PFN_MSIDETERMINEAPPLICABLEPATCHESW vpfnMsiDetermineApplicablePatchesW = NULL;
47static PFN_MSIENUMPRODUCTSEXW vpfnMsiEnumProductsExW = NULL;
48static PFN_MSIGETPATCHINFOEXW vpfnMsiGetPatchInfoExW = NULL;
49static PFN_MSIGETPRODUCTINFOEXW vpfnMsiGetProductInfoExW = NULL;
50static PFN_MSISETEXTERNALUIRECORD vpfnMsiSetExternalUIRecord = NULL;
51static PFN_MSISOURCELISTADDSOURCEEXW vpfnMsiSourceListAddSourceExW = NULL;
52
53static HMODULE vhMsiDll = NULL;
54static PFN_MSIDETERMINEPATCHSEQUENCEW vpfnMsiDeterminePatchSequenceWFromLibrary = NULL;
55static PFN_MSIDETERMINEAPPLICABLEPATCHESW vpfnMsiDetermineApplicablePatchesWFromLibrary = NULL;
56static PFN_MSIENUMPRODUCTSEXW vpfnMsiEnumProductsExWFromLibrary = NULL;
57static PFN_MSIGETPATCHINFOEXW vpfnMsiGetPatchInfoExWFromLibrary = NULL;
58static PFN_MSIGETPRODUCTINFOEXW vpfnMsiGetProductInfoExWFromLibrary = NULL;
59static PFN_MSISETEXTERNALUIRECORD vpfnMsiSetExternalUIRecordFromLibrary = NULL;
60static PFN_MSISOURCELISTADDSOURCEEXW vpfnMsiSourceListAddSourceExWFromLibrary = NULL;
61
62// MSI Transactions v4.5+
63static PFN_MSIBEGINTRANSACTIONW vpfnMsiBeginTransaction = NULL;
64static PFN_MSIENDTRANSACTION vpfnMsiEndTransaction = NULL;
65
66static BOOL vfWiuInitialized = FALSE;
67
68// globals
69static DWORD vdwMsiDllMajorMinor = 0;
70static DWORD vdwMsiDllBuildRevision = 0;
71
72
73// internal function declarations
74
75static DWORD CheckForRestartErrorCode(
76 __in DWORD dwErrorCode,
77 __out WIU_RESTART* pRestart
78 );
79static INT CALLBACK InstallEngineCallback(
80 __in LPVOID pvContext,
81 __in UINT uiMessage,
82 __in_z_opt LPCWSTR wzMessage
83 );
84static INT CALLBACK InstallEngineRecordCallback(
85 __in LPVOID pvContext,
86 __in UINT uiMessage,
87 __in_opt MSIHANDLE hRecord
88 );
89static INT HandleInstallMessage(
90 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
91 __in INSTALLMESSAGE mt,
92 __in UINT uiFlags,
93 __in_z LPCWSTR wzMessage,
94 __in_opt MSIHANDLE hRecord
95 );
96static INT HandleInstallProgress(
97 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
98 __in_z_opt LPCWSTR wzMessage,
99 __in_opt MSIHANDLE hRecord
100 );
101static INT SendMsiMessage(
102 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
103 __in INSTALLMESSAGE mt,
104 __in UINT uiFlags,
105 __in_z LPCWSTR wzMessage,
106 __in_opt MSIHANDLE hRecord
107 );
108static INT SendErrorMessage(
109 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
110 __in UINT uiFlags,
111 __in_z LPCWSTR wzMessage,
112 __in_opt MSIHANDLE hRecord
113 );
114static INT SendFilesInUseMessage(
115 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
116 __in_opt MSIHANDLE hRecord,
117 __in BOOL fRestartManagerRequest
118 );
119static INT SendProgressUpdate(
120 __in WIU_MSI_EXECUTE_CONTEXT* pContext
121 );
122static void ResetProgress(
123 __in WIU_MSI_EXECUTE_CONTEXT* pContext
124 );
125static DWORD CalculatePhaseProgress(
126 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
127 __in DWORD dwProgressIndex,
128 __in DWORD dwWeightPercentage
129 );
130void InitializeMessageData(
131 __in_opt MSIHANDLE hRecord,
132 __deref_out_ecount(*pcData) LPWSTR** prgsczData,
133 __out DWORD* pcData
134 );
135void UninitializeMessageData(
136 __in LPWSTR* rgsczData,
137 __in DWORD cData
138 );
139
140
141/********************************************************************
142 WiuInitialize - initializes wiutil
143
144*********************************************************************/
145extern "C" HRESULT DAPI WiuInitialize(
146 )
147{
148 HRESULT hr = S_OK;
149 LPWSTR sczMsiDllPath = NULL;
150
151 hr = LoadSystemLibraryWithPath(L"Msi.dll", &vhMsiDll, &sczMsiDllPath);
152 WiuExitOnFailure(hr, "Failed to load Msi.DLL");
153
154 // Ignore failures
155 FileVersion(sczMsiDllPath, &vdwMsiDllMajorMinor, &vdwMsiDllBuildRevision);
156
157 vpfnMsiDeterminePatchSequenceWFromLibrary = reinterpret_cast<PFN_MSIDETERMINEPATCHSEQUENCEW>(::GetProcAddress(vhMsiDll, "MsiDeterminePatchSequenceW"));
158 if (NULL == vpfnMsiDeterminePatchSequenceW)
159 {
160 vpfnMsiDeterminePatchSequenceW = vpfnMsiDeterminePatchSequenceWFromLibrary;
161 }
162
163 vpfnMsiDetermineApplicablePatchesWFromLibrary = reinterpret_cast<PFN_MSIDETERMINEAPPLICABLEPATCHESW>(::GetProcAddress(vhMsiDll, "MsiDetermineApplicablePatchesW"));
164 if (NULL == vpfnMsiDetermineApplicablePatchesW)
165 {
166 vpfnMsiDetermineApplicablePatchesW = vpfnMsiDetermineApplicablePatchesWFromLibrary;
167 }
168
169 vpfnMsiEnumProductsExWFromLibrary = reinterpret_cast<PFN_MSIENUMPRODUCTSEXW>(::GetProcAddress(vhMsiDll, "MsiEnumProductsExW"));
170 if (NULL == vpfnMsiEnumProductsExW)
171 {
172 vpfnMsiEnumProductsExW = vpfnMsiEnumProductsExWFromLibrary;
173 }
174
175 vpfnMsiGetPatchInfoExWFromLibrary = reinterpret_cast<PFN_MSIGETPATCHINFOEXW>(::GetProcAddress(vhMsiDll, "MsiGetPatchInfoExW"));
176 if (NULL == vpfnMsiGetPatchInfoExW)
177 {
178 vpfnMsiGetPatchInfoExW = vpfnMsiGetPatchInfoExWFromLibrary;
179 }
180
181 vpfnMsiGetProductInfoExWFromLibrary = reinterpret_cast<PFN_MSIGETPRODUCTINFOEXW>(::GetProcAddress(vhMsiDll, "MsiGetProductInfoExW"));
182 if (NULL == vpfnMsiGetProductInfoExW)
183 {
184 vpfnMsiGetProductInfoExW = vpfnMsiGetProductInfoExWFromLibrary;
185 }
186
187 vpfnMsiSetExternalUIRecordFromLibrary = reinterpret_cast<PFN_MSISETEXTERNALUIRECORD>(::GetProcAddress(vhMsiDll, "MsiSetExternalUIRecord"));
188 if (NULL == vpfnMsiSetExternalUIRecord)
189 {
190 vpfnMsiSetExternalUIRecord = vpfnMsiSetExternalUIRecordFromLibrary;
191 }
192
193 //static PFN_MSISOURCELISTADDSOURCEEXW vpfnMsiSourceListAddSourceExW = NULL;
194 vpfnMsiSourceListAddSourceExWFromLibrary = reinterpret_cast<PFN_MSISOURCELISTADDSOURCEEXW>(::GetProcAddress(vhMsiDll, "MsiSourceListAddSourceExW"));
195 if (NULL == vpfnMsiSourceListAddSourceExW)
196 {
197 vpfnMsiSourceListAddSourceExW = vpfnMsiSourceListAddSourceExWFromLibrary;
198 }
199
200 // MSI Transaction functions
201 if (NULL == vpfnMsiBeginTransaction)
202 {
203 vpfnMsiBeginTransaction = reinterpret_cast<PFN_MSIBEGINTRANSACTIONW>(::GetProcAddress(vhMsiDll, "MsiBeginTransactionW"));
204 }
205
206 if (NULL == vpfnMsiEndTransaction)
207 {
208 vpfnMsiEndTransaction = reinterpret_cast<PFN_MSIENDTRANSACTION>(::GetProcAddress(vhMsiDll, "MsiEndTransaction"));
209 }
210
211 vfWiuInitialized = TRUE;
212
213LExit:
214 ReleaseStr(sczMsiDllPath);
215 return hr;
216}
217
218
219/********************************************************************
220 WiuUninitialize - uninitializes wiutil
221
222*********************************************************************/
223extern "C" void DAPI WiuUninitialize(
224 )
225{
226 if (vhMsiDll)
227 {
228 ::FreeLibrary(vhMsiDll);
229 vhMsiDll = NULL;
230 vpfnMsiSetExternalUIRecordFromLibrary = NULL;
231 vpfnMsiGetProductInfoExWFromLibrary = NULL;
232 vpfnMsiGetPatchInfoExWFromLibrary = NULL;
233 vpfnMsiEnumProductsExWFromLibrary = NULL;
234 vpfnMsiDetermineApplicablePatchesWFromLibrary = NULL;
235 vpfnMsiDeterminePatchSequenceWFromLibrary = NULL;
236 vpfnMsiSourceListAddSourceExWFromLibrary = NULL;
237 vpfnMsiBeginTransaction = NULL;
238 vpfnMsiEndTransaction = NULL;
239 }
240
241 vfWiuInitialized = FALSE;
242}
243
244
245/********************************************************************
246 WiuFunctionOverride - overrides the Windows installer functions. Typically used
247 for unit testing.
248
249*********************************************************************/
250extern "C" void DAPI WiuFunctionOverride(
251 __in_opt PFN_MSIENABLELOGW pfnMsiEnableLogW,
252 __in_opt PFN_MSIGETCOMPONENTPATHW pfnMsiGetComponentPathW,
253 __in_opt PFN_MSILOCATECOMPONENTW pfnMsiLocateComponentW,
254 __in_opt PFN_MSIQUERYFEATURESTATEW pfnMsiQueryFeatureStateW,
255 __in_opt PFN_MSIGETPRODUCTINFOW pfnMsiGetProductInfoW,
256 __in_opt PFN_MSIGETPRODUCTINFOEXW pfnMsiGetProductInfoExW,
257 __in_opt PFN_MSIINSTALLPRODUCTW pfnMsiInstallProductW,
258 __in_opt PFN_MSICONFIGUREPRODUCTEXW pfnMsiConfigureProductExW,
259 __in_opt PFN_MSISETINTERNALUI pfnMsiSetInternalUI,
260 __in_opt PFN_MSISETEXTERNALUIW pfnMsiSetExternalUIW,
261 __in_opt PFN_MSIENUMRELATEDPRODUCTSW pfnMsiEnumRelatedProductsW,
262 __in_opt PFN_MSISETEXTERNALUIRECORD pfnMsiSetExternalUIRecord,
263 __in_opt PFN_MSISOURCELISTADDSOURCEEXW pfnMsiSourceListAddSourceExW
264 )
265{
266 vpfnMsiEnableLogW = pfnMsiEnableLogW ? pfnMsiEnableLogW : ::MsiEnableLogW;
267 vpfnMsiGetComponentPathW = pfnMsiGetComponentPathW ? pfnMsiGetComponentPathW : ::MsiGetComponentPathW;
268 vpfnMsiLocateComponentW = pfnMsiLocateComponentW ? pfnMsiLocateComponentW : ::MsiLocateComponentW;
269 vpfnMsiQueryFeatureStateW = pfnMsiQueryFeatureStateW ? pfnMsiQueryFeatureStateW : ::MsiQueryFeatureStateW;
270 vpfnMsiGetProductInfoW = pfnMsiGetProductInfoW ? pfnMsiGetProductInfoW : vpfnMsiGetProductInfoW;
271 vpfnMsiInstallProductW = pfnMsiInstallProductW ? pfnMsiInstallProductW : ::MsiInstallProductW;
272 vpfnMsiConfigureProductExW = pfnMsiConfigureProductExW ? pfnMsiConfigureProductExW : ::MsiConfigureProductExW;
273 vpfnMsiSetInternalUI = pfnMsiSetInternalUI ? pfnMsiSetInternalUI : ::MsiSetInternalUI;
274 vpfnMsiSetExternalUIW = pfnMsiSetExternalUIW ? pfnMsiSetExternalUIW : ::MsiSetExternalUIW;
275 vpfnMsiEnumRelatedProductsW = pfnMsiEnumRelatedProductsW ? pfnMsiEnumRelatedProductsW : ::MsiEnumRelatedProductsW;
276 vpfnMsiGetProductInfoExW = pfnMsiGetProductInfoExW ? pfnMsiGetProductInfoExW : vpfnMsiGetProductInfoExWFromLibrary;
277 vpfnMsiSetExternalUIRecord = pfnMsiSetExternalUIRecord ? pfnMsiSetExternalUIRecord : vpfnMsiSetExternalUIRecordFromLibrary;
278 vpfnMsiSourceListAddSourceExW = pfnMsiSourceListAddSourceExW ? pfnMsiSourceListAddSourceExW : vpfnMsiSourceListAddSourceExWFromLibrary;
279}
280
281
282extern "C" HRESULT DAPI WiuGetComponentPath(
283 __in_z LPCWSTR wzProductCode,
284 __in_z LPCWSTR wzComponentId,
285 __out INSTALLSTATE* pInstallState,
286 __out_z LPWSTR* psczValue
287 )
288{
289 HRESULT hr = S_OK;
290 DWORD cch = WIU_GOOD_ENOUGH_PROPERTY_LENGTH;
291 DWORD cchCompare;
292
293 hr = StrAlloc(psczValue, cch);
294 WiuExitOnFailure(hr, "Failed to allocate string for component path.");
295
296 cchCompare = cch;
297 *pInstallState = vpfnMsiGetComponentPathW(wzProductCode, wzComponentId, *psczValue, &cch);
298 if (INSTALLSTATE_MOREDATA == *pInstallState)
299 {
300 ++cch;
301 hr = StrAlloc(psczValue, cch);
302 WiuExitOnFailure(hr, "Failed to reallocate string for component path.");
303
304 cchCompare = cch;
305 *pInstallState = vpfnMsiGetComponentPathW(wzProductCode, wzComponentId, *psczValue, &cch);
306 }
307
308 if (INSTALLSTATE_INVALIDARG == *pInstallState)
309 {
310 hr = E_INVALIDARG;
311 WiuExitOnRootFailure(hr, "Invalid argument when getting component path.");
312 }
313 else if (INSTALLSTATE_UNKNOWN == *pInstallState)
314 {
315 ExitFunction();
316 }
317
318 // If the actual path length is greater than or equal to the original buffer
319 // allocate a larger buffer and get the path again, just in case we are
320 // missing any part of the path.
321 if (cchCompare <= cch)
322 {
323 ++cch;
324 hr = StrAlloc(psczValue, cch);
325 WiuExitOnFailure(hr, "Failed to reallocate string for component path.");
326
327 *pInstallState = vpfnMsiGetComponentPathW(wzProductCode, wzComponentId, *psczValue, &cch);
328 }
329
330LExit:
331 return hr;
332}
333
334
335extern "C" HRESULT DAPI WiuLocateComponent(
336 __in_z LPCWSTR wzComponentId,
337 __out INSTALLSTATE* pInstallState,
338 __out_z LPWSTR* psczValue
339 )
340{
341 HRESULT hr = S_OK;
342 DWORD cch = WIU_GOOD_ENOUGH_PROPERTY_LENGTH;
343 DWORD cchCompare;
344
345 hr = StrAlloc(psczValue, cch);
346 WiuExitOnFailure(hr, "Failed to allocate string for component path.");
347
348 cchCompare = cch;
349 *pInstallState = vpfnMsiLocateComponentW(wzComponentId, *psczValue, &cch);
350 if (INSTALLSTATE_MOREDATA == *pInstallState)
351 {
352 ++cch;
353 hr = StrAlloc(psczValue, cch);
354 WiuExitOnFailure(hr, "Failed to reallocate string for component path.");
355
356 cchCompare = cch;
357 *pInstallState = vpfnMsiLocateComponentW(wzComponentId, *psczValue, &cch);
358 }
359
360 if (INSTALLSTATE_INVALIDARG == *pInstallState)
361 {
362 hr = E_INVALIDARG;
363 WiuExitOnRootFailure(hr, "Invalid argument when locating component.");
364 }
365 else if (INSTALLSTATE_UNKNOWN == *pInstallState)
366 {
367 ExitFunction();
368 }
369
370 // If the actual path length is greater than or equal to the original buffer
371 // allocate a larger buffer and get the path again, just in case we are
372 // missing any part of the path.
373 if (cchCompare <= cch)
374 {
375 ++cch;
376 hr = StrAlloc(psczValue, cch);
377 WiuExitOnFailure(hr, "Failed to reallocate string for component path.");
378
379 *pInstallState = vpfnMsiLocateComponentW(wzComponentId, *psczValue, &cch);
380 }
381
382LExit:
383 return hr;
384}
385
386
387extern "C" HRESULT DAPI WiuQueryFeatureState(
388 __in_z LPCWSTR wzProduct,
389 __in_z LPCWSTR wzFeature,
390 __out INSTALLSTATE* pInstallState
391 )
392{
393 HRESULT hr = S_OK;
394
395 *pInstallState = vpfnMsiQueryFeatureStateW(wzProduct, wzFeature);
396 if (INSTALLSTATE_INVALIDARG == *pInstallState)
397 {
398 hr = E_INVALIDARG;
399 WiuExitOnRootFailure(hr, "Failed to query state of feature: %ls in product: %ls", wzFeature, wzProduct);
400 }
401
402LExit:
403 return hr;
404}
405
406
407extern "C" HRESULT DAPI WiuGetProductInfo(
408 __in_z LPCWSTR wzProductCode,
409 __in_z LPCWSTR wzProperty,
410 __out LPWSTR* psczValue
411 )
412{
413 HRESULT hr = S_OK;
414 UINT er = ERROR_SUCCESS;
415 DWORD cch = WIU_GOOD_ENOUGH_PROPERTY_LENGTH;
416
417 hr = StrAlloc(psczValue, cch);
418 WiuExitOnFailure(hr, "Failed to allocate string for product info.");
419
420 er = vpfnMsiGetProductInfoW(wzProductCode, wzProperty, *psczValue, &cch);
421 if (ERROR_MORE_DATA == er)
422 {
423 ++cch;
424 hr = StrAlloc(psczValue, cch);
425 WiuExitOnFailure(hr, "Failed to reallocate string for product info.");
426
427 er = vpfnMsiGetProductInfoW(wzProductCode, wzProperty, *psczValue, &cch);
428 }
429 WiuExitOnWin32Error(er, hr, "Failed to get product info.");
430
431LExit:
432 return hr;
433}
434
435
436extern "C" HRESULT DAPI WiuGetProductInfoEx(
437 __in_z LPCWSTR wzProductCode,
438 __in_z_opt LPCWSTR wzUserSid,
439 __in MSIINSTALLCONTEXT dwContext,
440 __in_z LPCWSTR wzProperty,
441 __out LPWSTR* psczValue
442 )
443{
444 HRESULT hr = S_OK;
445 UINT er = ERROR_SUCCESS;
446 DWORD cch = WIU_GOOD_ENOUGH_PROPERTY_LENGTH;
447
448 if (!vpfnMsiGetProductInfoExW)
449 {
450 hr = WiuGetProductInfo(wzProductCode, wzProperty, psczValue);
451 WiuExitOnFailure(hr, "Failed to get product info when extended info was not available.");
452
453 ExitFunction();
454 }
455
456 hr = StrAlloc(psczValue, cch);
457 WiuExitOnFailure(hr, "Failed to allocate string for extended product info.");
458
459 er = vpfnMsiGetProductInfoExW(wzProductCode, wzUserSid, dwContext, wzProperty, *psczValue, &cch);
460 if (ERROR_MORE_DATA == er)
461 {
462 ++cch;
463 hr = StrAlloc(psczValue, cch);
464 WiuExitOnFailure(hr, "Failed to reallocate string for extended product info.");
465
466 er = vpfnMsiGetProductInfoExW(wzProductCode, wzUserSid, dwContext, wzProperty, *psczValue, &cch);
467 }
468 WiuExitOnWin32Error(er, hr, "Failed to get extended product info.");
469
470LExit:
471 return hr;
472}
473
474
475extern "C" HRESULT DAPI WiuGetProductProperty(
476 __in MSIHANDLE hProduct,
477 __in_z LPCWSTR wzProperty,
478 __out LPWSTR* psczValue
479 )
480{
481 HRESULT hr = S_OK;
482 UINT er = ERROR_SUCCESS;
483 DWORD cch = WIU_GOOD_ENOUGH_PROPERTY_LENGTH;
484
485 hr = StrAlloc(psczValue, cch);
486 WiuExitOnFailure(hr, "Failed to allocate string for product property.");
487
488 er = ::MsiGetProductPropertyW(hProduct, wzProperty, *psczValue, &cch);
489 if (ERROR_MORE_DATA == er)
490 {
491 ++cch;
492 hr = StrAlloc(psczValue, cch);
493 WiuExitOnFailure(hr, "Failed to reallocate string for product property.");
494
495 er = ::MsiGetProductPropertyW(hProduct, wzProperty, *psczValue, &cch);
496 }
497 WiuExitOnWin32Error(er, hr, "Failed to get product property.");
498
499LExit:
500 return hr;
501}
502
503
504extern "C" HRESULT DAPI WiuGetPatchInfoEx(
505 __in_z LPCWSTR wzPatchCode,
506 __in_z LPCWSTR wzProductCode,
507 __in_z_opt LPCWSTR wzUserSid,
508 __in MSIINSTALLCONTEXT dwContext,
509 __in_z LPCWSTR wzProperty,
510 __out LPWSTR* psczValue
511 )
512{
513 HRESULT hr = S_OK;
514 UINT er = ERROR_SUCCESS;
515 DWORD cch = WIU_GOOD_ENOUGH_PROPERTY_LENGTH;
516
517 if (!vpfnMsiGetPatchInfoExW)
518 {
519 ExitFunction1(hr = E_NOTIMPL);
520 }
521
522 hr = StrAlloc(psczValue, cch);
523 WiuExitOnFailure(hr, "Failed to allocate string for extended patch info.");
524
525 er = vpfnMsiGetPatchInfoExW(wzPatchCode, wzProductCode, wzUserSid, dwContext, wzProperty, *psczValue, &cch);
526 if (ERROR_MORE_DATA == er)
527 {
528 ++cch;
529 hr = StrAlloc(psczValue, cch);
530 WiuExitOnFailure(hr, "Failed to reallocate string for extended patch info.");
531
532 er = vpfnMsiGetPatchInfoExW(wzPatchCode, wzProductCode, wzUserSid, dwContext, wzProperty, *psczValue, &cch);
533 }
534 WiuExitOnWin32Error(er, hr, "Failed to get extended patch info.");
535
536LExit:
537 return hr;
538}
539
540
541extern "C" HRESULT DAPI WiuDeterminePatchSequence(
542 __in_z LPCWSTR wzProductCode,
543 __in_z_opt LPCWSTR wzUserSid,
544 __in MSIINSTALLCONTEXT context,
545 __in PMSIPATCHSEQUENCEINFOW pPatchInfo,
546 __in DWORD cPatchInfo
547 )
548{
549 HRESULT hr = S_OK;
550 DWORD er = ERROR_SUCCESS;
551
552 if (!vpfnMsiDeterminePatchSequenceW)
553 {
554 ExitFunction1(hr = E_NOTIMPL);
555 }
556
557 er = vpfnMsiDeterminePatchSequenceW(wzProductCode, wzUserSid, context, cPatchInfo, pPatchInfo);
558 WiuExitOnWin32Error(er, hr, "Failed to determine patch sequence for product code.");
559
560LExit:
561 return hr;
562}
563
564
565extern "C" HRESULT DAPI WiuDetermineApplicablePatches(
566 __in_z LPCWSTR wzProductPackagePath,
567 __in PMSIPATCHSEQUENCEINFOW pPatchInfo,
568 __in DWORD cPatchInfo
569 )
570{
571 HRESULT hr = S_OK;
572 DWORD er = ERROR_SUCCESS;
573
574 if (!vpfnMsiDetermineApplicablePatchesW)
575 {
576 ExitFunction1(hr = E_NOTIMPL);
577 }
578
579 er = vpfnMsiDetermineApplicablePatchesW(wzProductPackagePath, cPatchInfo, pPatchInfo);
580 WiuExitOnWin32Error(er, hr, "Failed to determine applicable patches for product package.");
581
582LExit:
583 return hr;
584}
585
586
587extern "C" HRESULT DAPI WiuEnumProducts(
588 __in DWORD iProductIndex,
589 __out_ecount(MAX_GUID_CHARS + 1) LPWSTR wzProductCode
590 )
591{
592 HRESULT hr = S_OK;
593 DWORD er = ERROR_SUCCESS;
594
595 er = vpfnMsiEnumProductsW(iProductIndex, wzProductCode);
596 if (ERROR_NO_MORE_ITEMS == er)
597 {
598 ExitFunction1(hr = HRESULT_FROM_WIN32(er));
599 }
600 WiuExitOnWin32Error(er, hr, "Failed to enumerate products.");
601
602LExit:
603 return hr;
604}
605
606
607extern "C" HRESULT DAPI WiuEnumProductsEx(
608 __in_z_opt LPCWSTR wzProductCode,
609 __in_z_opt LPCWSTR wzUserSid,
610 __in DWORD dwContext,
611 __in DWORD dwIndex,
612 __out_opt WCHAR wzInstalledProductCode[39],
613 __out_opt MSIINSTALLCONTEXT *pdwInstalledContext,
614 __out_opt LPWSTR wzSid,
615 __inout_opt LPDWORD pcchSid
616 )
617{
618 HRESULT hr = S_OK;
619 DWORD er = ERROR_SUCCESS;
620
621 if (!vpfnMsiEnumProductsExW)
622 {
623 ExitFunction1(hr = E_NOTIMPL);
624 }
625
626 er = vpfnMsiEnumProductsExW(wzProductCode, wzUserSid, dwContext, dwIndex, wzInstalledProductCode, pdwInstalledContext, wzSid, pcchSid);
627 if (ERROR_NO_MORE_ITEMS == er)
628 {
629 ExitFunction1(hr = HRESULT_FROM_WIN32(er));
630 }
631 WiuExitOnWin32Error(er, hr, "Failed to enumerate products.");
632
633LExit:
634 return hr;
635}
636
637
638extern "C" HRESULT DAPI WiuEnumRelatedProducts(
639 __in_z LPCWSTR wzUpgradeCode,
640 __in DWORD iProductIndex,
641 __out_ecount(MAX_GUID_CHARS + 1) LPWSTR wzProductCode
642 )
643{
644 HRESULT hr = S_OK;
645 DWORD er = ERROR_SUCCESS;
646
647 er = vpfnMsiEnumRelatedProductsW(wzUpgradeCode, 0, iProductIndex, wzProductCode);
648 if (ERROR_NO_MORE_ITEMS == er)
649 {
650 ExitFunction1(hr = HRESULT_FROM_WIN32(er));
651 }
652 WiuExitOnWin32Error(er, hr, "Failed to enumerate related products for updgrade code: %ls", wzUpgradeCode);
653
654LExit:
655 return hr;
656}
657
658/********************************************************************
659 WiuEnumRelatedProductCodes - Returns an array of related products for a given upgrade code.
660
661 Parameters:
662 wzUpgradeCode - The upgrade code that will be used to find the related products.
663 prgsczProductCodes - Pointer to the array that will contain the product codes.
664 pcRelatedProducts - Returns the count of the number of related products found.
665 fReturnHighestVersionOnly - When set to "TRUE", will only return the product code of the highest version found.
666********************************************************************/
667extern "C" HRESULT DAPI WiuEnumRelatedProductCodes(
668 __in_z LPCWSTR wzUpgradeCode,
669 __deref_out_ecount_opt(*pcRelatedProducts) LPWSTR** prgsczProductCodes,
670 __out DWORD* pcRelatedProducts,
671 __in BOOL fReturnHighestVersionOnly
672 )
673{
674 HRESULT hr = S_OK;
675 WCHAR wzCurrentProductCode[MAX_GUID_CHARS + 1] = { };
676 LPWSTR sczInstalledVersion = NULL;
677 VERUTIL_VERSION* pCurrentVersion = NULL;
678 VERUTIL_VERSION* pHighestVersion = NULL;
679 int nCompare = 0;
680
681 // make sure we start at zero
682 *pcRelatedProducts = 0;
683
684 for (DWORD i = 0; ; ++i)
685 {
686 hr = WiuEnumRelatedProducts(wzUpgradeCode, i, wzCurrentProductCode);
687
688 if (E_NOMOREITEMS == hr)
689 {
690 hr = S_OK;
691 break;
692 }
693 WiuExitOnFailure(hr, "Failed to enumerate related products for upgrade code: %ls", wzUpgradeCode);
694
695 if (fReturnHighestVersionOnly)
696 {
697 // try to get the version but if the product registration is broken
698 // (for whatever reason), skip this product
699 hr = WiuGetProductInfo(wzCurrentProductCode, L"VersionString", &sczInstalledVersion);
700 if (FAILED(hr))
701 {
702 WiuExitTrace(hr, "Could not get product version for product code: %ls, skipping...", wzCurrentProductCode);
703 continue;
704 }
705
706 hr = VerParseVersion(sczInstalledVersion, 0, FALSE, &pCurrentVersion);
707 WiuExitOnFailure(hr, "Failed to parse version: %ls for product code: %ls", sczInstalledVersion, wzCurrentProductCode);
708
709 if (pCurrentVersion->fInvalid)
710 {
711 WiuExitTrace(E_INVALIDDATA, "Enumerated msi package with invalid version, product code: '%1!ls!', version: '%2!ls!'");
712 }
713
714 // if this is the first product found then it is the highest version (for now)
715 if (!pHighestVersion)
716 {
717 pHighestVersion = pCurrentVersion;
718 pCurrentVersion = NULL;
719 }
720 else
721 {
722 hr = VerCompareParsedVersions(pCurrentVersion, pHighestVersion, &nCompare);
723 WiuExitOnFailure(hr, "Failed to compare version '%ls' to highest version: '%ls'", pCurrentVersion->sczVersion, pHighestVersion->sczVersion);
724
725 // if this is the highest version encountered so far then overwrite
726 // the first item in the array (there will never be more than one item)
727 if (nCompare > 0)
728 {
729 ReleaseVerutilVersion(pHighestVersion);
730 pHighestVersion = pCurrentVersion;
731 pCurrentVersion = NULL;
732
733 hr = StrAllocString(prgsczProductCodes[0], wzCurrentProductCode, 0);
734 WiuExitOnFailure(hr, "Failed to update array with higher versioned product code.");
735 }
736 else
737 {
738 ReleaseVerutilVersion(pCurrentVersion);
739 }
740
741 // continue here as we don't want anything else added to the list
742 continue;
743 }
744 }
745
746 hr = StrArrayAllocString(prgsczProductCodes, (LPUINT)(pcRelatedProducts), wzCurrentProductCode, 0);
747 WiuExitOnFailure(hr, "Failed to add product code to array.");
748 }
749
750LExit:
751 ReleaseVerutilVersion(pCurrentVersion);
752 ReleaseVerutilVersion(pHighestVersion);
753 ReleaseStr(sczInstalledVersion);
754 return hr;
755}
756
757
758extern "C" HRESULT DAPI WiuEnableLog(
759 __in DWORD dwLogMode,
760 __in_z LPCWSTR wzLogFile,
761 __in DWORD dwLogAttributes
762 )
763{
764 HRESULT hr = S_OK;
765 DWORD er = ERROR_SUCCESS;
766
767 er = vpfnMsiEnableLogW(dwLogMode, wzLogFile, dwLogAttributes);
768 WiuExitOnWin32Error(er, hr, "Failed to enable MSI internal logging.");
769
770LExit:
771 return hr;
772}
773
774
775extern "C" HRESULT DAPI WiuInitializeInternalUI(
776 __in INSTALLUILEVEL internalUILevel,
777 __in_opt HWND hwndParent,
778 __in WIU_MSI_EXECUTE_CONTEXT* pExecuteContext
779 )
780{
781 HRESULT hr = S_OK;
782
783 memset(pExecuteContext, 0, sizeof(WIU_MSI_EXECUTE_CONTEXT));
784
785 pExecuteContext->previousInstallUILevel = vpfnMsiSetInternalUI(internalUILevel, &hwndParent);
786 pExecuteContext->hwndPreviousParentWindow = hwndParent;
787
788 if (INSTALLUILEVEL_NOCHANGE == pExecuteContext->previousInstallUILevel)
789 {
790 hr = E_INVALIDARG;
791 }
792
793 return hr;
794}
795
796
797extern "C" HRESULT DAPI WiuInitializeExternalUI(
798 __in PFN_MSIEXECUTEMESSAGEHANDLER pfnMessageHandler,
799 __in INSTALLUILEVEL internalUILevel,
800 __in_opt HWND hwndParent,
801 __in LPVOID pvContext,
802 __in BOOL fRollback,
803 __in WIU_MSI_EXECUTE_CONTEXT* pExecuteContext
804 )
805{
806 HRESULT hr = S_OK;
807 DWORD er = ERROR_SUCCESS;
808
809 DWORD dwMessageFilter = INSTALLLOGMODE_INITIALIZE | INSTALLLOGMODE_TERMINATE |
810 INSTALLLOGMODE_FATALEXIT | INSTALLLOGMODE_ERROR | INSTALLLOGMODE_WARNING |
811 INSTALLLOGMODE_RESOLVESOURCE | INSTALLLOGMODE_OUTOFDISKSPACE |
812 INSTALLLOGMODE_ACTIONSTART | INSTALLLOGMODE_ACTIONDATA | INSTALLLOGMODE_COMMONDATA |
813 INSTALLLOGMODE_PROGRESS | INSTALLLOGMODE_FILESINUSE;
814
815 if (MAKEDWORD(0, 4) <= vdwMsiDllMajorMinor)
816 {
817 dwMessageFilter |= INSTALLLOGMODE_RMFILESINUSE;
818 }
819
820 // Wire the internal and external UI handler.
821 hr = WiuInitializeInternalUI(internalUILevel, hwndParent, pExecuteContext);
822 WiuExitOnFailure(hr, "Failed to set internal UI level and window.");
823
824 pExecuteContext->fRollback = fRollback;
825 pExecuteContext->pfnMessageHandler = pfnMessageHandler;
826 pExecuteContext->pvContext = pvContext;
827
828 // If the external UI record is available (MSI version >= 3.1) use it but fall back to the standard external
829 // UI handler if necesary.
830 if (vpfnMsiSetExternalUIRecord)
831 {
832 er = vpfnMsiSetExternalUIRecord(InstallEngineRecordCallback, dwMessageFilter, pExecuteContext, &pExecuteContext->pfnPreviousExternalUIRecord);
833 WiuExitOnWin32Error(er, hr, "Failed to wire up external UI record handler.");
834 pExecuteContext->fSetPreviousExternalUIRecord = TRUE;
835 }
836 else
837 {
838 pExecuteContext->pfnPreviousExternalUI = vpfnMsiSetExternalUIW(InstallEngineCallback, dwMessageFilter, pExecuteContext);
839 pExecuteContext->fSetPreviousExternalUI = TRUE;
840 }
841
842LExit:
843 return hr;
844}
845
846
847extern "C" void DAPI WiuUninitializeExternalUI(
848 __in WIU_MSI_EXECUTE_CONTEXT* pExecuteContext
849 )
850{
851 if (INSTALLUILEVEL_NOCHANGE != pExecuteContext->previousInstallUILevel)
852 {
853 pExecuteContext->previousInstallUILevel = vpfnMsiSetInternalUI(pExecuteContext->previousInstallUILevel, &pExecuteContext->hwndPreviousParentWindow);
854 }
855
856 if (pExecuteContext->fSetPreviousExternalUI) // unset the UI handler
857 {
858 vpfnMsiSetExternalUIW(pExecuteContext->pfnPreviousExternalUI, 0, NULL);
859 }
860
861 if (pExecuteContext->fSetPreviousExternalUIRecord) // unset the UI record handler
862 {
863 vpfnMsiSetExternalUIRecord(pExecuteContext->pfnPreviousExternalUIRecord, 0, NULL, NULL);
864 }
865
866 memset(pExecuteContext, 0, sizeof(WIU_MSI_EXECUTE_CONTEXT));
867}
868
869
870extern "C" HRESULT DAPI WiuConfigureProductEx(
871 __in_z LPCWSTR wzProduct,
872 __in int iInstallLevel,
873 __in INSTALLSTATE eInstallState,
874 __in_z LPCWSTR wzCommandLine,
875 __out WIU_RESTART* pRestart
876 )
877{
878 HRESULT hr = S_OK;
879 DWORD er = ERROR_SUCCESS;
880
881 er = vpfnMsiConfigureProductExW(wzProduct, iInstallLevel, eInstallState, wzCommandLine);
882 er = CheckForRestartErrorCode(er, pRestart);
883 WiuExitOnWin32Error(er, hr, "Failed to configure product: %ls", wzProduct);
884
885LExit:
886 return hr;
887}
888
889
890extern "C" HRESULT DAPI WiuInstallProduct(
891 __in_z LPCWSTR wzPackagePath,
892 __in_z LPCWSTR wzCommandLine,
893 __out WIU_RESTART* pRestart
894 )
895{
896 HRESULT hr = S_OK;
897 DWORD er = ERROR_SUCCESS;
898
899 er = vpfnMsiInstallProductW(wzPackagePath, wzCommandLine);
900 er = CheckForRestartErrorCode(er, pRestart);
901 WiuExitOnWin32Error(er, hr, "Failed to install product: %ls", wzPackagePath);
902
903LExit:
904 return hr;
905}
906
907
908extern "C" HRESULT DAPI WiuRemovePatches(
909 __in_z LPCWSTR wzPatchList,
910 __in_z LPCWSTR wzProductCode,
911 __in_z LPCWSTR wzPropertyList,
912 __out WIU_RESTART* pRestart
913 )
914{
915 HRESULT hr = S_OK;
916 DWORD er = ERROR_SUCCESS;
917
918 er = vpfnMsiRemovePatchesW(wzPatchList, wzProductCode, INSTALLTYPE_SINGLE_INSTANCE, wzPropertyList);
919 er = CheckForRestartErrorCode(er, pRestart);
920 WiuExitOnWin32Error(er, hr, "Failed to remove patches.");
921
922LExit:
923 return hr;
924}
925
926
927extern "C" HRESULT DAPI WiuSourceListAddSourceEx(
928 __in_z LPCWSTR wzProductCodeOrPatchCode,
929 __in_z_opt LPCWSTR wzUserSid,
930 __in MSIINSTALLCONTEXT dwContext,
931 __in DWORD dwCode,
932 __in_z LPCWSTR wzSource,
933 __in_opt DWORD dwIndex
934 )
935{
936 HRESULT hr = S_OK;
937 DWORD er = ERROR_SUCCESS;
938
939 er = vpfnMsiSourceListAddSourceExW(wzProductCodeOrPatchCode, wzUserSid, dwContext, MSISOURCETYPE_NETWORK | dwCode, wzSource, dwIndex);
940 WiuExitOnWin32Error(er, hr, "Failed to add source.");
941
942LExit:
943 return hr;
944}
945
946extern "C" BOOL DAPI WiuIsMsiTransactionSupported(
947 )
948{
949 return vpfnMsiBeginTransaction && vpfnMsiEndTransaction;
950}
951
952extern "C" HRESULT DAPI WiuBeginTransaction(
953 __in_z LPCWSTR szName,
954 __in DWORD dwTransactionAttributes,
955 __out MSIHANDLE * phTransactionHandle,
956 __out HANDLE * phChangeOfOwnerEvent,
957 __in DWORD dwLogMode,
958 __in_z LPCWSTR szLogPath
959 )
960{
961 HRESULT hr = S_OK;
962 DWORD er = ERROR_SUCCESS;
963
964 if (!WiuIsMsiTransactionSupported())
965 {
966 WiuExitOnFailure(hr = E_NOTIMPL, "Msi transactions are not supported");
967 }
968
969 hr = WiuEnableLog(dwLogMode, szLogPath, INSTALLLOGATTRIBUTES_APPEND);
970 WiuExitOnFailure(hr, "Failed to enable logging for MSI transaction");
971
972 er = vpfnMsiBeginTransaction(szName, dwTransactionAttributes, phTransactionHandle, phChangeOfOwnerEvent);
973 WiuExitOnWin32Error(er, hr, "Failed to begin transaction.");
974
975LExit:
976 return hr;
977}
978
979extern "C" HRESULT DAPI WiuEndTransaction(
980 __in DWORD dwTransactionState,
981 __in DWORD dwLogMode,
982 __in_z LPCWSTR szLogPath
983 )
984{
985 HRESULT hr = S_OK;
986 DWORD er = ERROR_SUCCESS;
987
988 if (!WiuIsMsiTransactionSupported())
989 {
990 WiuExitOnFailure(hr = E_NOTIMPL, "Msi transactions are not supported");
991 }
992
993 hr = WiuEnableLog(dwLogMode, szLogPath, INSTALLLOGATTRIBUTES_APPEND);
994 WiuExitOnFailure(hr, "Failed to enable logging for MSI transaction");
995
996 er = vpfnMsiEndTransaction(dwTransactionState);
997 WiuExitOnWin32Error(er, hr, "Failed to end transaction.");
998
999LExit:
1000 return hr;
1001}
1002
1003
1004
1005static DWORD CheckForRestartErrorCode(
1006 __in DWORD dwErrorCode,
1007 __out WIU_RESTART* pRestart
1008 )
1009{
1010 switch (dwErrorCode)
1011 {
1012 case ERROR_SUCCESS_REBOOT_REQUIRED:
1013 case ERROR_SUCCESS_RESTART_REQUIRED:
1014 *pRestart = WIU_RESTART_REQUIRED;
1015 dwErrorCode = ERROR_SUCCESS;
1016 break;
1017
1018 case ERROR_SUCCESS_REBOOT_INITIATED:
1019 case ERROR_INSTALL_SUSPEND:
1020 *pRestart = WIU_RESTART_INITIATED;
1021 dwErrorCode = ERROR_SUCCESS;
1022 break;
1023 }
1024
1025 return dwErrorCode;
1026}
1027
1028static INT CALLBACK InstallEngineCallback(
1029 __in LPVOID pvContext,
1030 __in UINT uiMessage,
1031 __in_z_opt LPCWSTR wzMessage
1032 )
1033{
1034 INT nResult = IDNOACTION;
1035 WIU_MSI_EXECUTE_CONTEXT* pContext = (WIU_MSI_EXECUTE_CONTEXT*)pvContext;
1036 INSTALLMESSAGE mt = static_cast<INSTALLMESSAGE>(0xFF000000 & uiMessage);
1037 UINT uiFlags = 0x00FFFFFF & uiMessage;
1038
1039 if (wzMessage)
1040 {
1041 if (INSTALLMESSAGE_PROGRESS == mt)
1042 {
1043 nResult = HandleInstallProgress(pContext, wzMessage, NULL);
1044 }
1045 else
1046 {
1047 nResult = HandleInstallMessage(pContext, mt, uiFlags, wzMessage, NULL);
1048 }
1049 }
1050
1051 return nResult;
1052}
1053
1054static INT CALLBACK InstallEngineRecordCallback(
1055 __in LPVOID pvContext,
1056 __in UINT uiMessage,
1057 __in_opt MSIHANDLE hRecord
1058 )
1059{
1060 INT nResult = IDNOACTION;
1061 HRESULT hr = S_OK;
1062 WIU_MSI_EXECUTE_CONTEXT* pContext = (WIU_MSI_EXECUTE_CONTEXT*)pvContext;
1063
1064 INSTALLMESSAGE mt = static_cast<INSTALLMESSAGE>(0xFF000000 & uiMessage);
1065 UINT uiFlags = 0x00FFFFFF & uiMessage;
1066 LPWSTR sczMessage = NULL;
1067 DWORD cchMessage = 0;
1068
1069 if (hRecord)
1070 {
1071 if (INSTALLMESSAGE_PROGRESS == mt)
1072 {
1073 nResult = HandleInstallProgress(pContext, NULL, hRecord);
1074 }
1075 else
1076 {
1077 // create formated message string
1078#pragma prefast(push)
1079#pragma prefast(disable:6298) // docs explicitly say this is a valid option for getting the buffer size
1080 DWORD er = ::MsiFormatRecordW(NULL, hRecord, L"", &cchMessage);
1081#pragma prefast(pop)
1082 if (ERROR_MORE_DATA == er || ERROR_SUCCESS == er)
1083 {
1084 hr = StrAlloc(&sczMessage, ++cchMessage);
1085 }
1086 else
1087 {
1088 hr = HRESULT_FROM_WIN32(er);
1089 }
1090 WiuExitOnFailure(hr, "Failed to allocate string for formated message.");
1091
1092 er = ::MsiFormatRecordW(NULL, hRecord, sczMessage, &cchMessage);
1093 WiuExitOnWin32Error(er, hr, "Failed to format message record.");
1094
1095 // Pass to handler including both the formated message and the original record.
1096 nResult = HandleInstallMessage(pContext, mt, uiFlags, sczMessage, hRecord);
1097 }
1098 }
1099
1100LExit:
1101 ReleaseStr(sczMessage);
1102 return nResult;
1103}
1104
1105static INT HandleInstallMessage(
1106 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
1107 __in INSTALLMESSAGE mt,
1108 __in UINT uiFlags,
1109 __in_z LPCWSTR wzMessage,
1110 __in_opt MSIHANDLE hRecord
1111 )
1112{
1113 INT nResult = IDNOACTION;
1114
1115Trace(REPORT_STANDARD, "MSI install[%x]: %ls", pContext->dwCurrentProgressIndex, wzMessage);
1116
1117 // Handle the message.
1118 switch (mt)
1119 {
1120 case INSTALLMESSAGE_INITIALIZE: // this message is received prior to internal UI initialization, no string data
1121 ResetProgress(pContext);
1122 break;
1123
1124 case INSTALLMESSAGE_TERMINATE: // sent after UI termination, no string data
1125 break;
1126
1127 case INSTALLMESSAGE_ACTIONSTART:
1128 if (WIU_MSI_PROGRESS_INVALID != pContext->dwCurrentProgressIndex && pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fEnableActionData)
1129 {
1130 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fEnableActionData = FALSE;
1131 }
1132
1133 nResult = SendMsiMessage(pContext, mt, uiFlags, wzMessage, hRecord);
1134 break;
1135
1136 case INSTALLMESSAGE_ACTIONDATA:
1137 if (WIU_MSI_PROGRESS_INVALID != pContext->dwCurrentProgressIndex && pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fEnableActionData)
1138 {
1139 if (pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fMoveForward)
1140 {
1141 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwCompleted += pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwStep;
1142 }
1143 else // rollback.
1144 {
1145 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwCompleted -= pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwStep;
1146 }
1147
1148 nResult = SendProgressUpdate(pContext);
1149 }
1150 else
1151 {
1152 nResult = SendMsiMessage(pContext, mt, uiFlags, wzMessage, hRecord);
1153 }
1154 break;
1155
1156 case INSTALLMESSAGE_OUTOFDISKSPACE: __fallthrough;
1157 case INSTALLMESSAGE_FATALEXIT: __fallthrough;
1158 case INSTALLMESSAGE_ERROR:
1159 nResult = SendErrorMessage(pContext, uiFlags, wzMessage, hRecord);
1160 break;
1161
1162 case INSTALLMESSAGE_FILESINUSE:
1163 case INSTALLMESSAGE_RMFILESINUSE:
1164 nResult = SendFilesInUseMessage(pContext, hRecord, INSTALLMESSAGE_RMFILESINUSE == mt);
1165 break;
1166
1167/*
1168#if 0
1169 case INSTALLMESSAGE_COMMONDATA:
1170 if (L'1' == wzMessage[0] && L':' == wzMessage[1] && L' ' == wzMessage[2])
1171 {
1172 if (L'0' == wzMessage[3])
1173 {
1174 // TODO: handle the language common data message.
1175 lres = IDOK;
1176 return lres;
1177 }
1178 else if (L'1' == wzMessage[3])
1179 {
1180 // TODO: really handle sending the caption.
1181 lres = ::SendSuxMessage(pInstallContext->pSetupUXInformation, SRM_EXEC_SET_CAPTION, uiFlags, reinterpret_cast<LPARAM>(wzMessage + 3));
1182 return lres;
1183 }
1184 else if (L'2' == wzMessage[3])
1185 {
1186 // TODO: really handle sending the cancel button status.
1187 lres = ::SendSuxMessage(pInstallContext->pSetupUXInformation, SRM_EXEC_SET_CANCEL, uiFlags, reinterpret_cast<LPARAM>(wzMessage + 3));
1188 return lres;
1189 }
1190 }
1191 break;
1192#endif
1193*/
1194
1195 //case INSTALLMESSAGE_WARNING:
1196 //case INSTALLMESSAGE_USER:
1197 //case INSTALLMESSAGE_INFO:
1198 //case INSTALLMESSAGE_SHOWDIALOG: // sent prior to display of authored dialog or wizard
1199 default:
1200 nResult = SendMsiMessage(pContext, mt, uiFlags, wzMessage, hRecord);
1201 break;
1202 }
1203
1204 // Always return "no action" (0) for resolve source messages.
1205 return (INSTALLMESSAGE_RESOLVESOURCE == mt) ? IDNOACTION : nResult;
1206}
1207
1208static INT HandleInstallProgress(
1209 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
1210 __in_z_opt LPCWSTR wzMessage,
1211 __in_opt MSIHANDLE hRecord
1212 )
1213{
1214 HRESULT hr = S_OK;
1215 INT nResult = IDNOACTION;
1216 INT iFields[4] = { };
1217 INT cFields = 0;
1218 LPCWSTR pwz = NULL;
1219 DWORD cch = 0;
1220
1221 // get field values
1222 if (hRecord)
1223 {
1224 cFields = ::MsiRecordGetFieldCount(hRecord);
1225 cFields = min(cFields, countof(iFields)); // avoid buffer overrun if there are more fields than our buffer can hold
1226 for (INT i = 0; i < cFields; ++i)
1227 {
1228 iFields[i] = ::MsiRecordGetInteger(hRecord, i + 1);
1229 }
1230 }
1231 else
1232 {
1233 Assert(wzMessage);
1234
1235 // parse message string
1236 pwz = wzMessage;
1237 while (cFields < 4)
1238 {
1239 // check if we have the start of a valid part
1240 if ((L'1' + cFields) != pwz[0] || L':' != pwz[1] || L' ' != pwz[2])
1241 {
1242 break;
1243 }
1244 pwz += 3;
1245
1246 // find character count of number
1247 cch = 0;
1248 while (pwz[cch] && L' ' != pwz[cch])
1249 {
1250 ++cch;
1251 }
1252
1253 // parse number
1254 hr = StrStringToInt32(pwz, cch, &iFields[cFields]);
1255 WiuExitOnFailure(hr, "Failed to parse MSI message part.");
1256
1257 // increment field count
1258 ++cFields;
1259 }
1260 }
1261
1262#ifdef _DEBUG
1263 WCHAR wz[256];
1264 ::StringCchPrintfW(wz, countof(wz), L"1: %d 2: %d 3: %d 4: %d", iFields[0], iFields[1], iFields[2], iFields[3]);
1265 Trace(REPORT_STANDARD, "MSI progress[%x]: %ls", pContext->dwCurrentProgressIndex, wz);
1266#endif
1267
1268 // Verify that we have the enough field values.
1269 if (1 > cFields)
1270 {
1271 ExitFunction(); // unknown message, bail
1272 }
1273
1274 // Handle based on message type.
1275 switch (iFields[0])
1276 {
1277 case 0: // master progress reset
1278 if (4 > cFields)
1279 {
1280 Trace(REPORT_STANDARD, "INSTALLMESSAGE_PROGRESS - Invalid field count %d, '%ls'", cFields, wzMessage);
1281 ExitFunction();
1282 }
1283 //Trace(REPORT_STANDARD, "INSTALLMESSAGE_PROGRESS - MASTER RESET - %d, %d, %d", iFields[1], iFields[2], iFields[3]);
1284
1285 // Update the index into progress array.
1286 if (WIU_MSI_PROGRESS_INVALID == pContext->dwCurrentProgressIndex)
1287 {
1288 pContext->dwCurrentProgressIndex = 0;
1289 }
1290 else if (pContext->dwCurrentProgressIndex + 1 < countof(pContext->rgMsiProgress))
1291 {
1292 ++pContext->dwCurrentProgressIndex;
1293 }
1294 else
1295 {
1296 hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
1297 WiuExitOnRootFailure(hr, "Insufficient space to hold progress information.");
1298 }
1299
1300 // we only care about the first stage after script execution has started
1301 //if (!pEngineInfo->fMsiProgressScriptInProgress && 1 != iFields[3])
1302 //{
1303 // pEngineInfo->fMsiProgressFinished = TRUE;
1304 //}
1305
1306 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwTotal = iFields[1];
1307 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwCompleted = 0 == iFields[2] ? 0 : iFields[1]; // if forward start at 0, if backwards start at max
1308 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fMoveForward = (0 == iFields[2]);
1309 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fEnableActionData = FALSE;
1310 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fScriptInProgress = (1 == iFields[3]);
1311
1312 if (0 == pContext->dwCurrentProgressIndex)
1313 {
1314 // HACK!!! this is a hack courtesy of the Windows Installer team. It seems the script planning phase
1315 // is always off by "about 50". So we'll toss an extra 50 ticks on so that the standard progress
1316 // doesn't go over 100%. If there are any custom actions, they may blow the total so we'll call this
1317 // "close" and deal with the rest.
1318 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwTotal += 50;
1319 }
1320 break;
1321
1322 case 1: // action info.
1323 if (3 > cFields)
1324 {
1325 Trace(REPORT_STANDARD, "INSTALLMESSAGE_PROGRESS - Invalid field count %d, '%ls'", cFields, wzMessage);
1326 ExitFunction();
1327 }
1328 //Trace(REPORT_STANDARD, "INSTALLMESSAGE_PROGRESS - ACTION INFO - %d, %d, %d", iFields[1], iFields[2], iFields[3]);
1329
1330 if (0 == iFields[2])
1331 {
1332 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fEnableActionData = FALSE;
1333 }
1334 else
1335 {
1336 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fEnableActionData = TRUE;
1337 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwStep = iFields[1];
1338 }
1339 break;
1340
1341 case 2: // progress report.
1342 if (2 > cFields)
1343 {
1344 Trace(REPORT_STANDARD, "INSTALLMESSAGE_PROGRESS - Invalid field count %d, '%ls'", cFields, wzMessage);
1345 break;
1346 }
1347
1348 //Trace(REPORT_STANDARD, "INSTALLMESSAGE_PROGRESS - PROGRESS REPORT - %d, %d, %d", iFields[1], iFields[2], iFields[3]);
1349
1350 if (WIU_MSI_PROGRESS_INVALID == pContext->dwCurrentProgressIndex)
1351 {
1352 break;
1353 }
1354 else if (0 == pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwTotal)
1355 {
1356 break;
1357 }
1358
1359 // Update progress.
1360 if (pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fMoveForward)
1361 {
1362 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwCompleted += iFields[1];
1363 }
1364 else // rollback.
1365 {
1366 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwCompleted -= iFields[1];
1367 }
1368 break;
1369
1370 case 3: // extend the progress bar.
1371 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwTotal += iFields[1];
1372 break;
1373
1374 default:
1375 ExitFunction(); // unknown message, bail
1376 }
1377
1378 // If we have a valid progress index, send an update.
1379 if (WIU_MSI_PROGRESS_INVALID != pContext->dwCurrentProgressIndex)
1380 {
1381 nResult = SendProgressUpdate(pContext);
1382 }
1383
1384LExit:
1385 return nResult;
1386}
1387
1388static INT SendMsiMessage(
1389 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
1390 __in INSTALLMESSAGE mt,
1391 __in UINT uiFlags,
1392 __in_z LPCWSTR wzMessage,
1393 __in_opt MSIHANDLE hRecord
1394 )
1395{
1396 INT nResult = IDNOACTION;
1397 WIU_MSI_EXECUTE_MESSAGE message = { };
1398 LPWSTR* rgsczData = NULL;
1399 DWORD cData = 0;
1400
1401 InitializeMessageData(hRecord, &rgsczData, &cData);
1402
1403 message.type = WIU_MSI_EXECUTE_MESSAGE_MSI_MESSAGE;
1404 message.dwAllowedResults = uiFlags;
1405 message.cData = cData;
1406 message.rgwzData = (LPCWSTR*)rgsczData;
1407 message.msiMessage.mt = mt;
1408 message.msiMessage.wzMessage = wzMessage;
1409 nResult = pContext->pfnMessageHandler(&message, pContext->pvContext);
1410
1411 UninitializeMessageData(rgsczData, cData);
1412 return nResult;
1413}
1414
1415static INT SendErrorMessage(
1416 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
1417 __in UINT uiFlags,
1418 __in_z LPCWSTR wzMessage,
1419 __in_opt MSIHANDLE hRecord
1420 )
1421{
1422 INT nResult = IDNOACTION;
1423 WIU_MSI_EXECUTE_MESSAGE message = { };
1424 DWORD dwErrorCode = 0;
1425 LPWSTR* rgsczData = NULL;
1426 DWORD cData = 0;
1427
1428 if (hRecord)
1429 {
1430 dwErrorCode = ::MsiRecordGetInteger(hRecord, 1);
1431
1432 // Set the recommendation if it's a known error code.
1433 switch (dwErrorCode)
1434 {
1435 case 1605: // continue with install even if there isn't enough room for rollback.
1436 nResult = IDIGNORE;
1437 break;
1438
1439 case 1704: // rollback suspended installs so our install can continue.
1440 nResult = IDOK;
1441 break;
1442 }
1443 }
1444
1445 InitializeMessageData(hRecord, &rgsczData, &cData);
1446
1447 message.type = WIU_MSI_EXECUTE_MESSAGE_ERROR;
1448 message.dwAllowedResults = uiFlags;
1449 message.nResultRecommendation = nResult;
1450 message.cData = cData;
1451 message.rgwzData = (LPCWSTR*)rgsczData;
1452 message.error.dwErrorCode = dwErrorCode;
1453 message.error.wzMessage = wzMessage;
1454 nResult = pContext->pfnMessageHandler(&message, pContext->pvContext);
1455
1456 UninitializeMessageData(rgsczData, cData);
1457 return nResult;
1458}
1459
1460static INT SendFilesInUseMessage(
1461 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
1462 __in_opt MSIHANDLE hRecord,
1463 __in BOOL /*fRestartManagerRequest*/
1464 )
1465{
1466 INT nResult = IDNOACTION;
1467 WIU_MSI_EXECUTE_MESSAGE message = { };
1468 LPWSTR* rgsczData = NULL;
1469 DWORD cData = 0;
1470
1471 InitializeMessageData(hRecord, &rgsczData, &cData);
1472
1473 message.type = WIU_MSI_EXECUTE_MESSAGE_MSI_FILES_IN_USE;
1474 message.dwAllowedResults = WIU_MB_OKIGNORECANCELRETRY;
1475 message.cData = cData;
1476 message.rgwzData = (LPCWSTR*)rgsczData;
1477 message.msiFilesInUse.cFiles = message.cData; // point the files in use information to the message record information.
1478 message.msiFilesInUse.rgwzFiles = message.rgwzData;
1479 nResult = pContext->pfnMessageHandler(&message, pContext->pvContext);
1480
1481 UninitializeMessageData(rgsczData, cData);
1482 return nResult;
1483}
1484
1485static INT SendProgressUpdate(
1486 __in WIU_MSI_EXECUTE_CONTEXT* pContext
1487 )
1488{
1489 int nResult = IDNOACTION;
1490 DWORD dwPercentage = 0; // number representing 0 - 100%
1491 WIU_MSI_EXECUTE_MESSAGE message = { };
1492
1493 //DWORD dwMsiProgressTotal = pEngineInfo->dwMsiProgressTotal;
1494 //DWORD dwMsiProgressComplete = pEngineInfo->dwMsiProgressComplete; //min(dwMsiProgressTotal, pEngineInfo->dwMsiProgressComplete);
1495 //double dProgressGauge = 0;
1496 //double dProgressStageTotal = (double)pEngineInfo->qwProgressStageTotal;
1497
1498 // Calculate progress for the phases of Windows Installer.
1499 // TODO: handle upgrade progress which would add another phase.
1500 dwPercentage += CalculatePhaseProgress(pContext, 0, 15);
1501 dwPercentage += CalculatePhaseProgress(pContext, 1, 80);
1502 dwPercentage += CalculatePhaseProgress(pContext, 2, 5);
1503 dwPercentage = min(dwPercentage, 100); // ensure the percentage never goes over 100%.
1504
1505 if (pContext->fRollback)
1506 {
1507 dwPercentage = 100 - dwPercentage;
1508 }
1509
1510 //if (qwTotal) // avoid "divide by zero" if the MSI range is blank.
1511 //{
1512 // // calculate gauge.
1513 // double dProgressGauge = static_cast<double>(qwCompleted) / static_cast<double>(qwTotal);
1514 // dProgressGauge = (1.0 / (1.0 + exp(3.7 - dProgressGauge * 7.5)) - 0.024127021417669196) / 0.975872978582330804;
1515 // qwCompleted = (DWORD)(dProgressGauge * qwTotal);
1516
1517 // // calculate progress within range
1518 // //qwProgressComplete = (DWORD64)(dwMsiProgressComplete * (dProgressStageTotal / dwMsiProgressTotal));
1519 // //qwProgressComplete = min(qwProgressComplete, pEngineInfo->qwProgressStageTotal);
1520 //}
1521
1522#ifdef _DEBUG
1523 DWORD64 qwCompleted = pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwCompleted;
1524 DWORD64 qwTotal = pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwTotal;
1525 Trace(REPORT_STANDARD, "MSI progress: %I64u/%I64u (%u%%)", qwCompleted, qwTotal, dwPercentage);
1526 //AssertSz(qwCompleted <= qwTotal, "Completed progress is larger than total progress.");
1527#endif
1528
1529 message.type = WIU_MSI_EXECUTE_MESSAGE_PROGRESS;
1530 message.dwAllowedResults = MB_OKCANCEL;
1531 message.progress.dwPercentage = dwPercentage;
1532 nResult = pContext->pfnMessageHandler(&message, pContext->pvContext);
1533
1534 return nResult;
1535}
1536
1537static void ResetProgress(
1538 __in WIU_MSI_EXECUTE_CONTEXT* pContext
1539 )
1540{
1541 memset(pContext->rgMsiProgress, 0, sizeof(pContext->rgMsiProgress));
1542 pContext->dwCurrentProgressIndex = WIU_MSI_PROGRESS_INVALID;
1543}
1544
1545static DWORD CalculatePhaseProgress(
1546 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
1547 __in DWORD dwProgressIndex,
1548 __in DWORD dwWeightPercentage
1549 )
1550{
1551 DWORD dwPhasePercentage = 0;
1552
1553 // If we've already passed this progress index, return the maximum percentage possible (the weight)
1554 if (dwProgressIndex < pContext->dwCurrentProgressIndex)
1555 {
1556 dwPhasePercentage = dwWeightPercentage;
1557 }
1558 else if (dwProgressIndex == pContext->dwCurrentProgressIndex) // have to do the math for the current progress.
1559 {
1560 WIU_MSI_PROGRESS* pProgress = pContext->rgMsiProgress + dwProgressIndex;
1561 if (pProgress->dwTotal)
1562 {
1563 DWORD64 dw64Completed = pProgress->dwCompleted;
1564 dwPhasePercentage = static_cast<DWORD>(dw64Completed * dwWeightPercentage / pProgress->dwTotal);
1565 }
1566 }
1567 // else we're not there yet so it has to be zero.
1568
1569 return dwPhasePercentage;
1570}
1571
1572void InitializeMessageData(
1573 __in_opt MSIHANDLE hRecord,
1574 __deref_out_ecount(*pcData) LPWSTR** prgsczData,
1575 __out DWORD* pcData
1576 )
1577{
1578 DWORD cData = 0;
1579 LPWSTR* rgsczData = NULL;
1580
1581 // If we have a record based message, try to get the extra data.
1582 if (hRecord)
1583 {
1584 cData = ::MsiRecordGetFieldCount(hRecord);
1585 if (cData)
1586 {
1587 rgsczData = (LPWSTR*)MemAlloc(sizeof(LPWSTR*) * cData, TRUE);
1588 }
1589
1590 for (DWORD i = 0; rgsczData && i < cData; ++i)
1591 {
1592 DWORD cch = 0;
1593
1594 // get string from record
1595#pragma prefast(push)
1596#pragma prefast(disable:6298)
1597 DWORD er = ::MsiRecordGetStringW(hRecord, i + 1, L"", &cch);
1598#pragma prefast(pop)
1599 if (ERROR_MORE_DATA == er)
1600 {
1601 HRESULT hr = StrAlloc(&rgsczData[i], ++cch);
1602 if (SUCCEEDED(hr))
1603 {
1604 er = ::MsiRecordGetStringW(hRecord, i + 1, rgsczData[i], &cch);
1605 }
1606 }
1607 }
1608 }
1609
1610 *prgsczData = rgsczData;
1611 *pcData = cData;
1612}
1613
1614void UninitializeMessageData(
1615 __in LPWSTR* rgsczData,
1616 __in DWORD cData
1617 )
1618{
1619 // Clean up if there was any data allocated.
1620 if (rgsczData)
1621 {
1622 for (DWORD i = 0; i < cData; ++i)
1623 {
1624 ReleaseStr(rgsczData[i]);
1625 }
1626
1627 MemFree(rgsczData);
1628 }
1629}
diff --git a/src/libs/dutil/WixToolset.DUtil/wuautil.cpp b/src/libs/dutil/WixToolset.DUtil/wuautil.cpp
new file mode 100644
index 00000000..dfb28818
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/wuautil.cpp
@@ -0,0 +1,104 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define WuaExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_WUAUTIL, x, s, __VA_ARGS__)
8#define WuaExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_WUAUTIL, x, s, __VA_ARGS__)
9#define WuaExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_WUAUTIL, x, s, __VA_ARGS__)
10#define WuaExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_WUAUTIL, x, s, __VA_ARGS__)
11#define WuaExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_WUAUTIL, x, s, __VA_ARGS__)
12#define WuaExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_WUAUTIL, x, s, __VA_ARGS__)
13#define WuaExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_WUAUTIL, p, x, e, s, __VA_ARGS__)
14#define WuaExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_WUAUTIL, p, x, s, __VA_ARGS__)
15#define WuaExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_WUAUTIL, p, x, e, s, __VA_ARGS__)
16#define WuaExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_WUAUTIL, p, x, s, __VA_ARGS__)
17#define WuaExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_WUAUTIL, e, x, s, __VA_ARGS__)
18#define WuaExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_WUAUTIL, g, x, s, __VA_ARGS__)
19
20
21// internal function declarations
22
23static HRESULT GetAutomaticUpdatesService(
24 __out IAutomaticUpdates **ppAutomaticUpdates
25 );
26
27
28// function definitions
29
30extern "C" HRESULT DAPI WuaPauseAutomaticUpdates()
31{
32 HRESULT hr = S_OK;
33 IAutomaticUpdates *pAutomaticUpdates = NULL;
34
35 hr = GetAutomaticUpdatesService(&pAutomaticUpdates);
36 WuaExitOnFailure(hr, "Failed to get the Automatic Updates service.");
37
38 hr = pAutomaticUpdates->Pause();
39 WuaExitOnFailure(hr, "Failed to pause the Automatic Updates service.");
40
41LExit:
42 ReleaseObject(pAutomaticUpdates);
43
44 return hr;
45}
46
47extern "C" HRESULT DAPI WuaResumeAutomaticUpdates()
48{
49 HRESULT hr = S_OK;
50 IAutomaticUpdates *pAutomaticUpdates = NULL;
51
52 hr = GetAutomaticUpdatesService(&pAutomaticUpdates);
53 WuaExitOnFailure(hr, "Failed to get the Automatic Updates service.");
54
55 hr = pAutomaticUpdates->Resume();
56 WuaExitOnFailure(hr, "Failed to resume the Automatic Updates service.");
57
58LExit:
59 ReleaseObject(pAutomaticUpdates);
60
61 return hr;
62}
63
64extern "C" HRESULT DAPI WuaRestartRequired(
65 __out BOOL* pfRestartRequired
66 )
67{
68 HRESULT hr = S_OK;
69 ISystemInformation* pSystemInformation = NULL;
70 VARIANT_BOOL bRestartRequired;
71
72 hr = ::CoCreateInstance(__uuidof(SystemInformation), NULL, CLSCTX_INPROC_SERVER, __uuidof(ISystemInformation), reinterpret_cast<LPVOID*>(&pSystemInformation));
73 WuaExitOnRootFailure(hr, "Failed to get WUA system information interface.");
74
75 hr = pSystemInformation->get_RebootRequired(&bRestartRequired);
76 WuaExitOnRootFailure(hr, "Failed to determine if restart is required from WUA.");
77
78 *pfRestartRequired = (VARIANT_FALSE != bRestartRequired);
79
80LExit:
81 ReleaseObject(pSystemInformation);
82
83 return hr;
84}
85
86
87// internal function definitions
88
89static HRESULT GetAutomaticUpdatesService(
90 __out IAutomaticUpdates **ppAutomaticUpdates
91 )
92{
93 HRESULT hr = S_OK;
94 CLSID clsidAutomaticUpdates = { };
95
96 hr = ::CLSIDFromProgID(L"Microsoft.Update.AutoUpdate", &clsidAutomaticUpdates);
97 WuaExitOnFailure(hr, "Failed to get CLSID for Microsoft.Update.AutoUpdate.");
98
99 hr = ::CoCreateInstance(clsidAutomaticUpdates, NULL, CLSCTX_INPROC_SERVER, IID_IAutomaticUpdates, reinterpret_cast<LPVOID*>(ppAutomaticUpdates));
100 WuaExitOnFailure(hr, "Failed to create instance of Microsoft.Update.AutoUpdate.");
101
102LExit:
103 return hr;
104}
diff --git a/src/libs/dutil/WixToolset.DUtil/xmlutil.cpp b/src/libs/dutil/WixToolset.DUtil/xmlutil.cpp
new file mode 100644
index 00000000..0f1e611d
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/xmlutil.cpp
@@ -0,0 +1,1332 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6// Exit macros
7#define XmlExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_XMLUTIL, x, s, __VA_ARGS__)
8#define XmlExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_XMLUTIL, x, s, __VA_ARGS__)
9#define XmlExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_XMLUTIL, x, s, __VA_ARGS__)
10#define XmlExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_XMLUTIL, x, s, __VA_ARGS__)
11#define XmlExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_XMLUTIL, x, s, __VA_ARGS__)
12#define XmlExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_XMLUTIL, x, s, __VA_ARGS__)
13#define XmlExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_XMLUTIL, p, x, e, s, __VA_ARGS__)
14#define XmlExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_XMLUTIL, p, x, s, __VA_ARGS__)
15#define XmlExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_XMLUTIL, p, x, e, s, __VA_ARGS__)
16#define XmlExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_XMLUTIL, p, x, s, __VA_ARGS__)
17#define XmlExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_XMLUTIL, e, x, s, __VA_ARGS__)
18#define XmlExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_XMLUTIL, g, x, s, __VA_ARGS__)
19
20// intialization globals
21CLSID vclsidXMLDOM = { 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0} };
22static volatile LONG vcXmlInitialized = 0;
23static BOOL vfMsxml40 = FALSE;
24static BOOL fComInitialized = FALSE;
25BOOL vfMsxml30 = FALSE;
26
27/********************************************************************
28 XmlInitialize - finds an appropriate version of the XML DOM
29
30*********************************************************************/
31extern "C" HRESULT DAPI XmlInitialize(
32 )
33{
34 HRESULT hr = S_OK;
35
36 if (!fComInitialized)
37 {
38 hr = ::CoInitialize(0);
39 if (RPC_E_CHANGED_MODE != hr)
40 {
41 XmlExitOnFailure(hr, "failed to initialize COM");
42 fComInitialized = TRUE;
43 }
44 }
45
46 LONG cInitialized = ::InterlockedIncrement(&vcXmlInitialized);
47 if (1 == cInitialized)
48 {
49 // NOTE: 4.0 behaves differently than 3.0 so there may be problems doing this
50#if 0
51 hr = ::CLSIDFromProgID(L"Msxml2.DOMDocument.4.0", &vclsidXMLDOM);
52 if (S_OK == hr)
53 {
54 vfMsxml40 = TRUE;
55 Trace(REPORT_VERBOSE, "found Msxml2.DOMDocument.4.0");
56 ExitFunction();
57 }
58#endif
59 hr = ::CLSIDFromProgID(L"Msxml2.DOMDocument", &vclsidXMLDOM);
60 if (FAILED(hr))
61 {
62 // try to fall back to old MSXML
63 hr = ::CLSIDFromProgID(L"MSXML.DOMDocument", &vclsidXMLDOM);
64 }
65 XmlExitOnFailure(hr, "failed to get CLSID for XML DOM");
66
67 Assert(IsEqualCLSID(vclsidXMLDOM, XmlUtil_CLSID_DOMDocument) ||
68 IsEqualCLSID(vclsidXMLDOM, XmlUtil_CLSID_DOMDocument20) ||
69 IsEqualCLSID(vclsidXMLDOM, XmlUtil_CLSID_DOMDocument26) ||
70 IsEqualCLSID(vclsidXMLDOM, XmlUtil_CLSID_DOMDocument30) ||
71 IsEqualCLSID(vclsidXMLDOM, XmlUtil_CLSID_DOMDocument40) ||
72 IsEqualCLSID(vclsidXMLDOM, XmlUtil_CLSID_DOMDocument50) ||
73 IsEqualCLSID(vclsidXMLDOM, XmlUtil_CLSID_DOMDocument60));
74 }
75
76 hr = S_OK;
77LExit:
78 return hr;
79}
80
81
82/********************************************************************
83 XmUninitialize -
84
85*********************************************************************/
86extern "C" void DAPI XmlUninitialize(
87 )
88{
89 AssertSz(vcXmlInitialized, "XmlUninitialize called when not initialized");
90
91 LONG cInitialized = ::InterlockedDecrement(&vcXmlInitialized);
92
93 if (0 == cInitialized)
94 {
95 memset(&vclsidXMLDOM, 0, sizeof(vclsidXMLDOM));
96
97 if (fComInitialized)
98 {
99 ::CoUninitialize();
100 }
101 }
102}
103
104extern "C" HRESULT DAPI XmlCreateElement(
105 __in IXMLDOMDocument *pixdDocument,
106 __in_z LPCWSTR wzElementName,
107 __out IXMLDOMElement **ppixnElement
108 )
109{
110 if (!ppixnElement || !pixdDocument)
111 {
112 return E_INVALIDARG;
113 }
114
115 HRESULT hr = S_OK;
116 BSTR bstrElementName = ::SysAllocString(wzElementName);
117 XmlExitOnNull(bstrElementName, hr, E_OUTOFMEMORY, "failed SysAllocString");
118 hr = pixdDocument->createElement(bstrElementName, ppixnElement);
119LExit:
120 ReleaseBSTR(bstrElementName);
121 return hr;
122}
123
124
125/********************************************************************
126 XmlCreateDocument -
127
128*********************************************************************/
129extern "C" HRESULT DAPI XmlCreateDocument(
130 __in_opt LPCWSTR pwzElementName,
131 __out IXMLDOMDocument** ppixdDocument,
132 __out_opt IXMLDOMElement** ppixeRootElement
133 )
134{
135 HRESULT hr = S_OK;
136 BOOL (WINAPI *pfnDisableWow64)(__out PVOID* ) = NULL;
137 BOOLEAN (WINAPI *pfnEnableWow64)(__in BOOLEAN ) = NULL;
138 BOOL (WINAPI *pfnRevertWow64)(__in PVOID ) = NULL;
139 BOOL fWow64Available = FALSE;
140 void *pvWow64State = NULL;
141
142 // RELEASEME
143 IXMLDOMElement* pixeRootElement = NULL;
144 IXMLDOMDocument *pixdDocument = NULL;
145
146 // Test if we have access to the Wow64 API, and store the result in fWow64Available
147 HMODULE hKernel32 = ::GetModuleHandleA("kernel32.dll");
148 XmlExitOnNullWithLastError(hKernel32, hr, "failed to get handle to kernel32.dll");
149
150 // This will test if we have access to the Wow64 API
151 if (NULL != GetProcAddress(hKernel32, "IsWow64Process"))
152 {
153 pfnDisableWow64 = (BOOL (WINAPI *)(PVOID *))::GetProcAddress(hKernel32, "Wow64DisableWow64FsRedirection");
154 pfnEnableWow64 = (BOOLEAN (WINAPI *)(BOOLEAN))::GetProcAddress(hKernel32, "Wow64EnableWow64FsRedirection");
155 pfnRevertWow64 = (BOOL (WINAPI *)(PVOID))::GetProcAddress(hKernel32, "Wow64RevertWow64FsRedirection");
156
157 fWow64Available = pfnDisableWow64 && pfnEnableWow64 && pfnRevertWow64;
158 }
159
160 // create the top level XML document
161 AssertSz(vcXmlInitialized, "XmlInitialize() was not called");
162
163 // Enable Wow64 Redirection, if possible
164 if (fWow64Available)
165 {
166 // We want to enable Wow64 redirection, but the Wow64 API requires us to disable it first to get its current state (so we can revert it later)
167 pfnDisableWow64(&pvWow64State);
168 // If we fail to enable it, don't bother trying to disable it later on
169 fWow64Available = pfnEnableWow64(TRUE);
170 }
171
172 hr = ::CoCreateInstance(vclsidXMLDOM, NULL, CLSCTX_INPROC_SERVER, XmlUtil_IID_IXMLDOMDocument, (void**)&pixdDocument);
173 XmlExitOnFailure(hr, "failed to create XML DOM Document");
174 Assert(pixdDocument);
175
176 if (IsEqualCLSID(vclsidXMLDOM, XmlUtil_CLSID_DOMDocument30) || IsEqualCLSID(vclsidXMLDOM, XmlUtil_CLSID_DOMDocument20))
177 {
178 vfMsxml30 = TRUE;
179 }
180
181 if (pwzElementName)
182 {
183 hr = XmlCreateElement(pixdDocument, pwzElementName, &pixeRootElement);
184 XmlExitOnFailure(hr, "failed XmlCreateElement");
185 hr = pixdDocument->appendChild(pixeRootElement, NULL);
186 XmlExitOnFailure(hr, "failed appendChild");
187 }
188
189 *ppixdDocument = pixdDocument;
190 pixdDocument = NULL;
191
192 if (ppixeRootElement)
193 {
194 *ppixeRootElement = pixeRootElement;
195 pixeRootElement = NULL;
196 }
197
198LExit:
199 // Re-disable Wow64 Redirection, if appropriate
200 if (fWow64Available && !pfnRevertWow64(pvWow64State))
201 {
202 // If we expected to be able to revert, and couldn't, fail in the only graceful way we can
203 ::ExitProcess(1);
204 }
205
206 ReleaseObject(pixeRootElement);
207 ReleaseObject(pixdDocument);
208 return hr;
209}
210
211
212/********************************************************************
213 XmlLoadDocument -
214
215*********************************************************************/
216extern "C" HRESULT DAPI XmlLoadDocument(
217 __in_z LPCWSTR wzDocument,
218 __out IXMLDOMDocument** ppixdDocument
219 )
220{
221 return XmlLoadDocumentEx(wzDocument, 0, ppixdDocument);
222}
223
224
225/********************************************************************
226 XmlReportParseError -
227
228*********************************************************************/
229static void XmlReportParseError(
230 __in IXMLDOMParseError* pixpe
231 )
232{
233 HRESULT hr = S_OK;
234 long lNumber = 0;
235 BSTR bstr = NULL;
236
237 Trace(REPORT_STANDARD, "Failed to parse XML. IXMLDOMParseError reports:");
238
239 hr = pixpe->get_errorCode(&lNumber);
240 XmlExitOnFailure(hr, "Failed to query IXMLDOMParseError.errorCode.");
241 Trace(REPORT_STANDARD, "errorCode = 0x%x", lNumber);
242
243 hr = pixpe->get_filepos(&lNumber);
244 XmlExitOnFailure(hr, "Failed to query IXMLDOMParseError.filepos.");
245 Trace(REPORT_STANDARD, "filepos = %d", lNumber);
246
247 hr = pixpe->get_line(&lNumber);
248 XmlExitOnFailure(hr, "Failed to query IXMLDOMParseError.line.");
249 Trace(REPORT_STANDARD, "line = %d", lNumber);
250
251 hr = pixpe->get_linepos(&lNumber);
252 XmlExitOnFailure(hr, "Failed to query IXMLDOMParseError.linepos.");
253 Trace(REPORT_STANDARD, "linepos = %d", lNumber);
254
255 hr = pixpe->get_reason(&bstr);
256 XmlExitOnFailure(hr, "Failed to query IXMLDOMParseError.reason.");
257 Trace(REPORT_STANDARD, "reason = %ls", bstr);
258 ReleaseNullBSTR(bstr);
259
260 hr = pixpe->get_srcText (&bstr);
261 XmlExitOnFailure(hr, "Failed to query IXMLDOMParseError.srcText .");
262 Trace(REPORT_STANDARD, "srcText = %ls", bstr);
263 ReleaseNullBSTR(bstr);
264
265LExit:
266 ReleaseBSTR(bstr);
267}
268
269/********************************************************************
270 XmlLoadDocumentEx -
271
272*********************************************************************/
273extern "C" HRESULT DAPI XmlLoadDocumentEx(
274 __in_z LPCWSTR wzDocument,
275 __in DWORD dwAttributes,
276 __out IXMLDOMDocument** ppixdDocument
277 )
278{
279 HRESULT hr = S_OK;
280 VARIANT_BOOL vbSuccess = 0;
281
282 // RELEASEME
283 IXMLDOMDocument* pixd = NULL;
284 IXMLDOMParseError* pixpe = NULL;
285 BSTR bstrLoad = NULL;
286
287 if (!wzDocument || !*wzDocument)
288 {
289 hr = E_UNEXPECTED;
290 XmlExitOnFailure(hr, "string must be non-null");
291 }
292
293 hr = XmlCreateDocument(NULL, &pixd);
294 if (hr == S_FALSE)
295 {
296 hr = E_FAIL;
297 }
298 XmlExitOnFailure(hr, "failed XmlCreateDocument");
299
300 if (dwAttributes & XML_LOAD_PRESERVE_WHITESPACE)
301 {
302 hr = pixd->put_preserveWhiteSpace(VARIANT_TRUE);
303 XmlExitOnFailure(hr, "failed put_preserveWhiteSpace");
304 }
305
306 // Security issue. Avoid triggering anything external.
307 hr = pixd->put_validateOnParse(VARIANT_FALSE);
308 XmlExitOnFailure(hr, "failed put_validateOnParse");
309 hr = pixd->put_resolveExternals(VARIANT_FALSE);
310 XmlExitOnFailure(hr, "failed put_resolveExternals");
311
312 bstrLoad = ::SysAllocString(wzDocument);
313 XmlExitOnNull(bstrLoad, hr, E_OUTOFMEMORY, "failed to allocate bstr for Load in XmlLoadDocumentEx");
314
315 hr = pixd->loadXML(bstrLoad, &vbSuccess);
316 if (S_FALSE == hr)
317 {
318 hr = HRESULT_FROM_WIN32(ERROR_OPEN_FAILED);
319 }
320
321 if (FAILED(hr) && S_OK == pixd->get_parseError(&pixpe))
322 {
323 XmlReportParseError(pixpe);
324 }
325
326 XmlExitOnFailure(hr, "failed loadXML");
327
328
329 hr = S_OK;
330LExit:
331 if (ppixdDocument)
332 {
333 *ppixdDocument = pixd;
334 pixd = NULL;
335 }
336 ReleaseBSTR(bstrLoad);
337 ReleaseObject(pixd);
338 ReleaseObject(pixpe);
339
340 return hr;
341}
342
343
344/*******************************************************************
345 XmlLoadDocumentFromFile
346
347********************************************************************/
348extern "C" HRESULT DAPI XmlLoadDocumentFromFile(
349 __in_z LPCWSTR wzPath,
350 __out IXMLDOMDocument** ppixdDocument
351 )
352{
353 return XmlLoadDocumentFromFileEx(wzPath, 0, ppixdDocument);
354}
355
356
357/*******************************************************************
358 XmlLoadDocumentFromFileEx
359
360********************************************************************/
361extern "C" HRESULT DAPI XmlLoadDocumentFromFileEx(
362 __in_z LPCWSTR wzPath,
363 __in DWORD dwAttributes,
364 __out IXMLDOMDocument** ppixdDocument
365 )
366{
367 HRESULT hr = S_OK;
368 VARIANT varPath;
369 VARIANT_BOOL vbSuccess = 0;
370
371 IXMLDOMDocument* pixd = NULL;
372 IXMLDOMParseError* pixpe = NULL;
373
374 ::VariantInit(&varPath);
375 varPath.vt = VT_BSTR;
376 varPath.bstrVal = ::SysAllocString(wzPath);
377 XmlExitOnNull(varPath.bstrVal, hr, E_OUTOFMEMORY, "failed to allocate bstr for Path in XmlLoadDocumentFromFileEx");
378
379 hr = XmlCreateDocument(NULL, &pixd);
380 if (hr == S_FALSE)
381 {
382 hr = E_FAIL;
383 }
384 XmlExitOnFailure(hr, "failed XmlCreateDocument");
385
386 if (dwAttributes & XML_LOAD_PRESERVE_WHITESPACE)
387 {
388 hr = pixd->put_preserveWhiteSpace(VARIANT_TRUE);
389 XmlExitOnFailure(hr, "failed put_preserveWhiteSpace");
390 }
391
392 // Avoid triggering anything external.
393 hr = pixd->put_validateOnParse(VARIANT_FALSE);
394 XmlExitOnFailure(hr, "failed put_validateOnParse");
395 hr = pixd->put_resolveExternals(VARIANT_FALSE);
396 XmlExitOnFailure(hr, "failed put_resolveExternals");
397
398 pixd->put_async(VARIANT_FALSE);
399 hr = pixd->load(varPath, &vbSuccess);
400 if (S_FALSE == hr)
401 {
402 hr = HRESULT_FROM_WIN32(ERROR_OPEN_FAILED);
403 }
404
405 if (FAILED(hr) && S_OK == pixd->get_parseError(&pixpe))
406 {
407 XmlReportParseError(pixpe);
408 }
409
410 XmlExitOnFailure(hr, "failed to load XML from: %ls", wzPath);
411
412 if (ppixdDocument)
413 {
414 *ppixdDocument = pixd;
415 pixd = NULL;
416 }
417
418 hr = S_OK;
419LExit:
420 ReleaseVariant(varPath);
421 ReleaseObject(pixd);
422 ReleaseObject(pixpe);
423
424 return hr;
425}
426
427
428/********************************************************************
429 XmlLoadDocumentFromBuffer
430
431*********************************************************************/
432extern "C" HRESULT DAPI XmlLoadDocumentFromBuffer(
433 __in_bcount(cbSource) const BYTE* pbSource,
434 __in SIZE_T cbSource,
435 __out IXMLDOMDocument** ppixdDocument
436 )
437{
438 HRESULT hr = S_OK;
439 IXMLDOMDocument* pixdDocument = NULL;
440 SAFEARRAY sa = { };
441 VARIANT vtXmlSource;
442 VARIANT_BOOL vbSuccess = 0;
443
444 ::VariantInit(&vtXmlSource);
445
446 // create document
447 hr = XmlCreateDocument(NULL, &pixdDocument);
448 if (hr == S_FALSE)
449 {
450 hr = E_FAIL;
451 }
452 XmlExitOnFailure(hr, "failed XmlCreateDocument");
453
454 // Security issue. Avoid triggering anything external.
455 hr = pixdDocument->put_validateOnParse(VARIANT_FALSE);
456 XmlExitOnFailure(hr, "failed put_validateOnParse");
457 hr = pixdDocument->put_resolveExternals(VARIANT_FALSE);
458 XmlExitOnFailure(hr, "failed put_resolveExternals");
459
460 // load document
461 sa.cDims = 1;
462 sa.fFeatures = FADF_STATIC | FADF_FIXEDSIZE;
463 sa.cbElements = 1;
464 sa.pvData = (PVOID)pbSource;
465 sa.rgsabound[0].cElements = (ULONG)cbSource;
466 vtXmlSource.vt = VT_ARRAY | VT_UI1;
467 vtXmlSource.parray = &sa;
468
469 hr = pixdDocument->load(vtXmlSource, &vbSuccess);
470 if (S_FALSE == hr)
471 {
472 hr = HRESULT_FROM_WIN32(ERROR_OPEN_FAILED);
473 }
474 XmlExitOnFailure(hr, "failed loadXML");
475
476 // return value
477 *ppixdDocument = pixdDocument;
478 pixdDocument = NULL;
479
480LExit:
481 ReleaseObject(pixdDocument);
482 return hr;
483}
484
485
486/********************************************************************
487 XmlSetAttribute -
488
489*********************************************************************/
490extern "C" HRESULT DAPI XmlSetAttribute(
491 __in IXMLDOMNode* pixnNode,
492 __in_z LPCWSTR pwzAttribute,
493 __in_z LPCWSTR pwzAttributeValue
494 )
495{
496 HRESULT hr = S_OK;
497 VARIANT varAttributeValue;
498 ::VariantInit(&varAttributeValue);
499
500 // RELEASEME
501 IXMLDOMDocument* pixdDocument = NULL;
502 IXMLDOMNamedNodeMap* pixnnmAttributes = NULL;
503 IXMLDOMAttribute* pixaAttribute = NULL;
504 IXMLDOMNode* pixaNode = NULL;
505 BSTR bstrAttributeName = ::SysAllocString(pwzAttribute);
506 XmlExitOnNull(bstrAttributeName, hr, E_OUTOFMEMORY, "failed to allocate bstr for AttributeName in XmlSetAttribute");
507
508 hr = pixnNode->get_attributes(&pixnnmAttributes);
509 XmlExitOnFailure(hr, "failed get_attributes in XmlSetAttribute(%ls)", pwzAttribute);
510
511 hr = pixnNode->get_ownerDocument(&pixdDocument);
512 if (hr == S_FALSE)
513 {
514 hr = E_FAIL;
515 }
516 XmlExitOnFailure(hr, "failed get_ownerDocument in XmlSetAttribute");
517
518 hr = pixdDocument->createAttribute(bstrAttributeName, &pixaAttribute);
519 XmlExitOnFailure(hr, "failed createAttribute in XmlSetAttribute(%ls)", pwzAttribute);
520
521 varAttributeValue.vt = VT_BSTR;
522 varAttributeValue.bstrVal = ::SysAllocString(pwzAttributeValue);
523 if (!varAttributeValue.bstrVal)
524 {
525 hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
526 }
527 XmlExitOnFailure(hr, "failed SysAllocString in XmlSetAttribute");
528
529 hr = pixaAttribute->put_nodeValue(varAttributeValue);
530 XmlExitOnFailure(hr, "failed put_nodeValue in XmlSetAttribute(%ls)", pwzAttribute);
531
532 hr = pixnnmAttributes->setNamedItem(pixaAttribute, &pixaNode);
533 XmlExitOnFailure(hr, "failed setNamedItem in XmlSetAttribute(%ls)", pwzAttribute);
534
535LExit:
536 ReleaseObject(pixdDocument);
537 ReleaseObject(pixnnmAttributes);
538 ReleaseObject(pixaAttribute);
539 ReleaseObject(pixaNode);
540 ReleaseBSTR(varAttributeValue.bstrVal);
541 ReleaseBSTR(bstrAttributeName);
542
543 return hr;
544}
545
546
547/********************************************************************
548 XmlSelectSingleNode -
549
550*********************************************************************/
551extern "C" HRESULT DAPI XmlSelectSingleNode(
552 __in IXMLDOMNode* pixnParent,
553 __in_z LPCWSTR wzXPath,
554 __out IXMLDOMNode **ppixnChild
555 )
556{
557 HRESULT hr = S_OK;
558
559 BSTR bstrXPath = NULL;
560
561 XmlExitOnNull(pixnParent, hr, E_UNEXPECTED, "pixnParent parameter was null in XmlSelectSingleNode");
562 XmlExitOnNull(ppixnChild, hr, E_UNEXPECTED, "ppixnChild parameter was null in XmlSelectSingleNode");
563
564 bstrXPath = ::SysAllocString(wzXPath ? wzXPath : L"");
565 XmlExitOnNull(bstrXPath, hr, E_OUTOFMEMORY, "failed to allocate bstr for XPath expression in XmlSelectSingleNode");
566
567 hr = pixnParent->selectSingleNode(bstrXPath, ppixnChild);
568
569LExit:
570 ReleaseBSTR(bstrXPath);
571
572 return hr;
573}
574
575
576/********************************************************************
577 XmlCreateTextNode -
578
579*********************************************************************/
580extern "C" HRESULT DAPI XmlCreateTextNode(
581 __in IXMLDOMDocument *pixdDocument,
582 __in_z LPCWSTR wzText,
583 __out IXMLDOMText **ppixnTextNode
584 )
585{
586 if (!ppixnTextNode || !pixdDocument)
587 {
588 return E_INVALIDARG;
589 }
590
591 HRESULT hr = S_OK;
592 BSTR bstrText = ::SysAllocString(wzText);
593 XmlExitOnNull(bstrText, hr, E_OUTOFMEMORY, "failed SysAllocString");
594 hr = pixdDocument->createTextNode(bstrText, ppixnTextNode);
595LExit:
596 ReleaseBSTR(bstrText);
597
598 return hr;
599}
600
601
602/********************************************************************
603 XmlGetText
604
605*********************************************************************/
606extern "C" HRESULT DAPI XmlGetText(
607 __in IXMLDOMNode* pixnNode,
608 __deref_out_z BSTR* pbstrText
609 )
610{
611 return pixnNode->get_text(pbstrText);
612}
613
614
615/********************************************************************
616 XmlGetAttribute
617
618*********************************************************************/
619extern "C" HRESULT DAPI XmlGetAttribute(
620 __in IXMLDOMNode* pixnNode,
621 __in_z LPCWSTR pwzAttribute,
622 __deref_out_z BSTR* pbstrAttributeValue
623 )
624{
625 Assert(pixnNode);
626 HRESULT hr = S_OK;
627
628 // RELEASEME
629 IXMLDOMNamedNodeMap* pixnnmAttributes = NULL;
630 IXMLDOMNode* pixnAttribute = NULL;
631 VARIANT varAttributeValue;
632 BSTR bstrAttribute = SysAllocString(pwzAttribute);
633
634 // INIT
635 ::VariantInit(&varAttributeValue);
636
637 // get attribute value from source
638 hr = pixnNode->get_attributes(&pixnnmAttributes);
639 XmlExitOnFailure(hr, "failed get_attributes");
640
641 hr = XmlGetNamedItem(pixnnmAttributes, bstrAttribute, &pixnAttribute);
642 if (S_FALSE == hr)
643 {
644 // hr = E_FAIL;
645 ExitFunction();
646 }
647 XmlExitOnFailure(hr, "failed getNamedItem in XmlGetAttribute(%ls)", pwzAttribute);
648
649 hr = pixnAttribute->get_nodeValue(&varAttributeValue);
650 XmlExitOnFailure(hr, "failed get_nodeValue in XmlGetAttribute(%ls)", pwzAttribute);
651
652 // steal the BSTR from the VARIANT
653 if (S_OK == hr && pbstrAttributeValue)
654 {
655 *pbstrAttributeValue = varAttributeValue.bstrVal;
656 varAttributeValue.bstrVal = NULL;
657 }
658
659LExit:
660 ReleaseObject(pixnnmAttributes);
661 ReleaseObject(pixnAttribute);
662 ReleaseVariant(varAttributeValue);
663 ReleaseBSTR(bstrAttribute);
664
665 return hr;
666}
667
668
669/********************************************************************
670 XmlGetAttributeEx
671
672*********************************************************************/
673HRESULT DAPI XmlGetAttributeEx(
674 __in IXMLDOMNode* pixnNode,
675 __in_z LPCWSTR wzAttribute,
676 __deref_out_z LPWSTR* psczAttributeValue
677 )
678{
679 Assert(pixnNode);
680 HRESULT hr = S_OK;
681 IXMLDOMNamedNodeMap* pixnnmAttributes = NULL;
682 IXMLDOMNode* pixnAttribute = NULL;
683 VARIANT varAttributeValue;
684 BSTR bstrAttribute = NULL;
685
686 ::VariantInit(&varAttributeValue);
687
688 // get attribute value from source
689 hr = pixnNode->get_attributes(&pixnnmAttributes);
690 XmlExitOnFailure(hr, "Failed get_attributes.");
691
692 bstrAttribute = ::SysAllocString(wzAttribute);
693 XmlExitOnNull(bstrAttribute, hr, E_OUTOFMEMORY, "Failed to allocate attribute name BSTR.");
694
695 hr = XmlGetNamedItem(pixnnmAttributes, bstrAttribute, &pixnAttribute);
696 if (S_FALSE == hr)
697 {
698 ExitFunction1(hr = E_NOTFOUND);
699 }
700 XmlExitOnFailure(hr, "Failed getNamedItem in XmlGetAttribute(%ls)", wzAttribute);
701
702 hr = pixnAttribute->get_nodeValue(&varAttributeValue);
703 if (S_FALSE == hr)
704 {
705 ExitFunction1(hr = E_NOTFOUND);
706 }
707 XmlExitOnFailure(hr, "Failed get_nodeValue in XmlGetAttribute(%ls)", wzAttribute);
708
709 // copy value
710 hr = StrAllocString(psczAttributeValue, varAttributeValue.bstrVal, 0);
711 XmlExitOnFailure(hr, "Failed to copy attribute value.");
712
713LExit:
714 ReleaseObject(pixnnmAttributes);
715 ReleaseObject(pixnAttribute);
716 ReleaseVariant(varAttributeValue);
717 ReleaseBSTR(bstrAttribute);
718
719 return hr;
720}
721
722
723/********************************************************************
724 XmlGetYesNoAttribute
725
726*********************************************************************/
727HRESULT DAPI XmlGetYesNoAttribute(
728 __in IXMLDOMNode* pixnNode,
729 __in_z LPCWSTR wzAttribute,
730 __out BOOL* pfYes
731 )
732{
733 HRESULT hr = S_OK;
734 LPWSTR sczValue = NULL;
735
736 hr = XmlGetAttributeEx(pixnNode, wzAttribute, &sczValue);
737 if (E_NOTFOUND != hr)
738 {
739 XmlExitOnFailure(hr, "Failed to get attribute.");
740
741 *pfYes = CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczValue, -1, L"yes", -1);
742 }
743
744LExit:
745 ReleaseStr(sczValue);
746
747 return hr;
748}
749
750
751
752/********************************************************************
753 XmlGetAttributeNumber
754
755*********************************************************************/
756extern "C" HRESULT DAPI XmlGetAttributeNumber(
757 __in IXMLDOMNode* pixnNode,
758 __in_z LPCWSTR pwzAttribute,
759 __out DWORD* pdwValue
760 )
761{
762 HRESULT hr = XmlGetAttributeNumberBase(pixnNode, pwzAttribute, 10, pdwValue);
763 return hr;
764}
765
766
767/********************************************************************
768 XmlGetAttributeNumberBase
769
770*********************************************************************/
771extern "C" HRESULT DAPI XmlGetAttributeNumberBase(
772 __in IXMLDOMNode* pixnNode,
773 __in_z LPCWSTR pwzAttribute,
774 __in int nBase,
775 __out DWORD* pdwValue
776 )
777{
778 HRESULT hr = S_OK;
779 BSTR bstrPointer = NULL;
780
781 hr = XmlGetAttribute(pixnNode, pwzAttribute, &bstrPointer);
782 XmlExitOnFailure(hr, "Failed to get value from attribute.");
783
784 if (S_OK == hr)
785 {
786 *pdwValue = wcstoul(bstrPointer, NULL, nBase);
787 }
788
789LExit:
790 ReleaseBSTR(bstrPointer);
791 return hr;
792}
793
794
795/********************************************************************
796 XmlGetAttributeLargeNumber
797
798*********************************************************************/
799extern "C" HRESULT DAPI XmlGetAttributeLargeNumber(
800 __in IXMLDOMNode* pixnNode,
801 __in_z LPCWSTR pwzAttribute,
802 __out DWORD64* pdw64Value
803 )
804{
805 HRESULT hr = S_OK;
806 BSTR bstrValue = NULL;
807
808 hr = XmlGetAttribute(pixnNode, pwzAttribute, &bstrValue);
809 XmlExitOnFailure(hr, "failed XmlGetAttribute");
810
811 if (S_OK == hr)
812 {
813 LONGLONG ll = 0;
814 hr = StrStringToInt64(bstrValue, 0, &ll);
815 XmlExitOnFailure(hr, "Failed to treat attribute value as number.");
816
817 *pdw64Value = ll;
818 }
819 else
820 {
821 *pdw64Value = 0;
822 }
823
824LExit:
825 ReleaseBSTR(bstrValue);
826 return hr;
827}
828
829
830/********************************************************************
831 XmlGetNamedItem -
832
833*********************************************************************/
834extern "C" HRESULT DAPI XmlGetNamedItem(
835 __in IXMLDOMNamedNodeMap *pixnmAttributes,
836 __in_opt LPCWSTR wzName,
837 __out IXMLDOMNode **ppixnNamedItem
838 )
839{
840 if (!pixnmAttributes || !ppixnNamedItem)
841 {
842 return E_INVALIDARG;
843 }
844
845 HRESULT hr = S_OK;
846 BSTR bstrName = ::SysAllocString(wzName);
847 XmlExitOnNull(bstrName, hr, E_OUTOFMEMORY, "failed SysAllocString");
848
849 hr = pixnmAttributes->getNamedItem(bstrName, ppixnNamedItem);
850
851LExit:
852 ReleaseBSTR(bstrName);
853 return hr;
854}
855
856
857/********************************************************************
858 XmlSetText -
859
860*********************************************************************/
861extern "C" HRESULT DAPI XmlSetText(
862 __in IXMLDOMNode *pixnNode,
863 __in_z LPCWSTR pwzText
864 )
865{
866 Assert(pixnNode && pwzText);
867 HRESULT hr = S_OK;
868 DOMNodeType dnType;
869
870 // RELEASEME
871 IXMLDOMDocument* pixdDocument = NULL;
872 IXMLDOMNodeList* pixnlNodeList = NULL;
873 IXMLDOMNode* pixnChildNode = NULL;
874 IXMLDOMText* pixtTextNode = NULL;
875 VARIANT varText;
876
877 ::VariantInit(&varText);
878
879 // find the text node
880 hr = pixnNode->get_childNodes(&pixnlNodeList);
881 XmlExitOnFailure(hr, "failed to get child nodes");
882
883 while (S_OK == (hr = pixnlNodeList->nextNode(&pixnChildNode)))
884 {
885 hr = pixnChildNode->get_nodeType(&dnType);
886 XmlExitOnFailure(hr, "failed to get node type");
887
888 if (NODE_TEXT == dnType)
889 break;
890 ReleaseNullObject(pixnChildNode);
891 }
892 if (S_FALSE == hr)
893 {
894 hr = S_OK;
895 }
896
897 if (pixnChildNode)
898 {
899 varText.vt = VT_BSTR;
900 varText.bstrVal = ::SysAllocString(pwzText);
901 if (!varText.bstrVal)
902 {
903 hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
904 }
905 XmlExitOnFailure(hr, "failed SysAllocString in XmlSetText");
906
907 hr = pixnChildNode->put_nodeValue(varText);
908 XmlExitOnFailure(hr, "failed IXMLDOMNode::put_nodeValue");
909 }
910 else
911 {
912 hr = pixnNode->get_ownerDocument(&pixdDocument);
913 if (hr == S_FALSE)
914 {
915 hr = E_FAIL;
916 }
917 XmlExitOnFailure(hr, "failed get_ownerDocument in XmlSetAttribute");
918
919 hr = XmlCreateTextNode(pixdDocument, pwzText, &pixtTextNode);
920 XmlExitOnFailure(hr, "failed createTextNode in XmlSetText(%ls)", pwzText);
921
922 hr = pixnNode->appendChild(pixtTextNode, NULL);
923 XmlExitOnFailure(hr, "failed appendChild in XmlSetText(%ls)", pwzText);
924 }
925
926 hr = *pwzText ? S_OK : S_FALSE;
927
928LExit:
929 ReleaseObject(pixnlNodeList);
930 ReleaseObject(pixnChildNode);
931 ReleaseObject(pixdDocument);
932 ReleaseObject(pixtTextNode);
933 ReleaseVariant(varText);
934 return hr;
935}
936
937
938/********************************************************************
939 XmlSetTextNumber -
940
941*********************************************************************/
942extern "C" HRESULT DAPI XmlSetTextNumber(
943 __in IXMLDOMNode *pixnNode,
944 __in DWORD dwValue
945 )
946{
947 HRESULT hr = S_OK;
948 WCHAR wzValue[12];
949
950 hr = ::StringCchPrintfW(wzValue, countof(wzValue), L"%u", dwValue);
951 XmlExitOnFailure(hr, "Failed to format numeric value as string.");
952
953 hr = XmlSetText(pixnNode, wzValue);
954
955LExit:
956 return hr;
957}
958
959
960/********************************************************************
961 XmlCreateChild -
962
963*********************************************************************/
964extern "C" HRESULT DAPI XmlCreateChild(
965 __in IXMLDOMNode* pixnParent,
966 __in_z LPCWSTR pwzElementType,
967 __out IXMLDOMNode** ppixnChild
968 )
969{
970 HRESULT hr = S_OK;
971
972 // RELEASEME
973 IXMLDOMDocument* pixdDocument = NULL;
974 IXMLDOMNode* pixnChild = NULL;
975
976 hr = pixnParent->get_ownerDocument(&pixdDocument);
977 if (hr == S_FALSE)
978 {
979 hr = E_FAIL;
980 }
981 XmlExitOnFailure(hr, "failed get_ownerDocument");
982
983 hr = XmlCreateElement(pixdDocument, pwzElementType, (IXMLDOMElement**) &pixnChild);
984 if (hr == S_FALSE)
985 {
986 hr = E_FAIL;
987 }
988 XmlExitOnFailure(hr, "failed createElement");
989
990 pixnParent->appendChild(pixnChild,NULL);
991 if (hr == S_FALSE)
992 {
993 hr = E_FAIL;
994 }
995 XmlExitOnFailure(hr, "failed appendChild");
996
997 if (ppixnChild)
998 {
999 *ppixnChild = pixnChild;
1000 pixnChild = NULL;
1001 }
1002
1003LExit:
1004 ReleaseObject(pixdDocument);
1005 ReleaseObject(pixnChild);
1006 return hr;
1007}
1008
1009/********************************************************************
1010 XmlRemoveAttribute -
1011
1012*********************************************************************/
1013extern "C" HRESULT DAPI XmlRemoveAttribute(
1014 __in IXMLDOMNode* pixnNode,
1015 __in_z LPCWSTR pwzAttribute
1016 )
1017{
1018 HRESULT hr = S_OK;
1019
1020 // RELEASEME
1021 IXMLDOMNamedNodeMap* pixnnmAttributes = NULL;
1022 BSTR bstrAttribute = ::SysAllocString(pwzAttribute);
1023 XmlExitOnNull(bstrAttribute, hr, E_OUTOFMEMORY, "failed to allocate bstr for attribute in XmlRemoveAttribute");
1024
1025 hr = pixnNode->get_attributes(&pixnnmAttributes);
1026 XmlExitOnFailure(hr, "failed get_attributes in RemoveXmlAttribute(%ls)", pwzAttribute);
1027
1028 hr = pixnnmAttributes->removeNamedItem(bstrAttribute, NULL);
1029 XmlExitOnFailure(hr, "failed removeNamedItem in RemoveXmlAttribute(%ls)", pwzAttribute);
1030
1031LExit:
1032 ReleaseObject(pixnnmAttributes);
1033 ReleaseBSTR(bstrAttribute);
1034
1035 return hr;
1036}
1037
1038
1039/********************************************************************
1040 XmlSelectNodes -
1041
1042*********************************************************************/
1043extern "C" HRESULT DAPI XmlSelectNodes(
1044 __in IXMLDOMNode* pixnParent,
1045 __in_z LPCWSTR wzXPath,
1046 __out IXMLDOMNodeList **ppixnlChildren
1047 )
1048{
1049 HRESULT hr = S_OK;
1050
1051 BSTR bstrXPath = NULL;
1052
1053 XmlExitOnNull(pixnParent, hr, E_UNEXPECTED, "pixnParent parameter was null in XmlSelectNodes");
1054 XmlExitOnNull(ppixnlChildren, hr, E_UNEXPECTED, "ppixnChild parameter was null in XmlSelectNodes");
1055
1056 bstrXPath = ::SysAllocString(wzXPath ? wzXPath : L"");
1057 XmlExitOnNull(bstrXPath, hr, E_OUTOFMEMORY, "failed to allocate bstr for XPath expression in XmlSelectNodes");
1058
1059 hr = pixnParent->selectNodes(bstrXPath, ppixnlChildren);
1060
1061LExit:
1062 ReleaseBSTR(bstrXPath);
1063 return hr;
1064}
1065
1066
1067/********************************************************************
1068 XmlNextAttribute - returns the next attribute in a node list
1069
1070 NOTE: pbstrAttribute is optional
1071 returns S_OK if found an element
1072 returns S_FALSE if no element found
1073 returns E_* if something went wrong
1074********************************************************************/
1075extern "C" HRESULT DAPI XmlNextAttribute(
1076 __in IXMLDOMNamedNodeMap* pixnnm,
1077 __out IXMLDOMNode** pixnAttribute,
1078 __deref_opt_out_z_opt BSTR* pbstrAttribute
1079 )
1080{
1081 Assert(pixnnm && pixnAttribute);
1082
1083 HRESULT hr = S_OK;
1084 IXMLDOMNode* pixn = NULL;
1085 DOMNodeType nt;
1086
1087 // null out the return values
1088 *pixnAttribute = NULL;
1089 if (pbstrAttribute)
1090 {
1091 *pbstrAttribute = NULL;
1092 }
1093
1094 hr = pixnnm->nextNode(&pixn);
1095 XmlExitOnFailure(hr, "Failed to get next attribute.");
1096
1097 if (S_OK == hr)
1098 {
1099 hr = pixn->get_nodeType(&nt);
1100 XmlExitOnFailure(hr, "failed to get node type");
1101
1102 if (NODE_ATTRIBUTE != nt)
1103 {
1104 hr = E_UNEXPECTED;
1105 XmlExitOnFailure(hr, "Failed to get expected node type back: attribute");
1106 }
1107
1108 // if the caller asked for the attribute name
1109 if (pbstrAttribute)
1110 {
1111 hr = pixn->get_baseName(pbstrAttribute);
1112 XmlExitOnFailure(hr, "failed to get attribute name");
1113 }
1114
1115 *pixnAttribute = pixn;
1116 pixn = NULL;
1117 }
1118
1119LExit:
1120 ReleaseObject(pixn);
1121 return hr;
1122}
1123
1124
1125/********************************************************************
1126 XmlNextElement - returns the next element in a node list
1127
1128 NOTE: pbstrElement is optional
1129 returns S_OK if found an element
1130 returns S_FALSE if no element found
1131 returns E_* if something went wrong
1132********************************************************************/
1133extern "C" HRESULT DAPI XmlNextElement(
1134 __in IXMLDOMNodeList* pixnl,
1135 __out IXMLDOMNode** pixnElement,
1136 __deref_opt_out_z_opt BSTR* pbstrElement
1137 )
1138{
1139 Assert(pixnl && pixnElement);
1140
1141 HRESULT hr = S_OK;
1142 IXMLDOMNode* pixn = NULL;
1143 DOMNodeType nt;
1144
1145 // null out the return values
1146 *pixnElement = NULL;
1147 if (pbstrElement)
1148 {
1149 *pbstrElement = NULL;
1150 }
1151
1152 //
1153 // find the next element in the list
1154 //
1155 while (S_OK == (hr = pixnl->nextNode(&pixn)))
1156 {
1157 hr = pixn->get_nodeType(&nt);
1158 XmlExitOnFailure(hr, "failed to get node type");
1159
1160 if (NODE_ELEMENT == nt)
1161 break;
1162
1163 ReleaseNullObject(pixn);
1164 }
1165 XmlExitOnFailure(hr, "failed to get next element");
1166
1167 // if we have a node and the caller asked for the element name
1168 if (pixn && pbstrElement)
1169 {
1170 hr = pixn->get_baseName(pbstrElement);
1171 XmlExitOnFailure(hr, "failed to get element name");
1172 }
1173
1174 *pixnElement = pixn;
1175 pixn = NULL;
1176
1177 hr = *pixnElement ? S_OK : S_FALSE;
1178LExit:
1179 ReleaseObject(pixn);
1180 return hr;
1181}
1182
1183
1184/********************************************************************
1185 XmlRemoveChildren -
1186
1187*********************************************************************/
1188extern "C" HRESULT DAPI XmlRemoveChildren(
1189 __in IXMLDOMNode* pixnSource,
1190 __in_z LPCWSTR pwzXPath
1191 )
1192{
1193 HRESULT hr = S_OK;
1194
1195 // RELEASEME
1196 IXMLDOMNodeList* pixnlNodeList = NULL;
1197 IXMLDOMNode* pixnNode = NULL;
1198 IXMLDOMNode* pixnRemoveChild = NULL;
1199
1200 if (pwzXPath)
1201 {
1202 hr = XmlSelectNodes(pixnSource, pwzXPath, &pixnlNodeList);
1203 XmlExitOnFailure(hr, "failed XmlSelectNodes");
1204 }
1205 else
1206 {
1207 hr = pixnSource->get_childNodes(&pixnlNodeList);
1208 XmlExitOnFailure(hr, "failed childNodes");
1209 }
1210 if (S_FALSE == hr)
1211 {
1212 ExitFunction();
1213 }
1214
1215 while (S_OK == (hr = pixnlNodeList->nextNode(&pixnNode)))
1216 {
1217 hr = pixnSource->removeChild(pixnNode, &pixnRemoveChild);
1218 XmlExitOnFailure(hr, "failed removeChild");
1219
1220 ReleaseNullObject(pixnRemoveChild);
1221 ReleaseNullObject(pixnNode);
1222 }
1223 if (S_FALSE == hr)
1224 {
1225 hr = S_OK;
1226 }
1227
1228LExit:
1229 ReleaseObject(pixnlNodeList);
1230 ReleaseObject(pixnNode);
1231 ReleaseObject(pixnRemoveChild);
1232
1233 return hr;
1234}
1235
1236
1237/********************************************************************
1238 XmlSaveDocument -
1239
1240*********************************************************************/
1241extern "C" HRESULT DAPI XmlSaveDocument(
1242 __in IXMLDOMDocument* pixdDocument,
1243 __inout LPCWSTR wzPath
1244 )
1245{
1246 HRESULT hr = S_OK;
1247
1248 // RELEASEME
1249 VARIANT varsDestPath;
1250
1251 ::VariantInit(&varsDestPath);
1252 varsDestPath.vt = VT_BSTR;
1253 varsDestPath.bstrVal = ::SysAllocString(wzPath);
1254 if (!varsDestPath.bstrVal)
1255 {
1256 hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
1257 }
1258 XmlExitOnFailure(hr, "failed to create BSTR");
1259
1260 hr = pixdDocument->save(varsDestPath);
1261 if (hr == S_FALSE)
1262 {
1263 hr = E_FAIL;
1264 }
1265 XmlExitOnFailure(hr, "failed save in WriteDocument");
1266
1267LExit:
1268 ReleaseVariant(varsDestPath);
1269 return hr;
1270}
1271
1272
1273/********************************************************************
1274 XmlSaveDocumentToBuffer
1275
1276*********************************************************************/
1277extern "C" HRESULT DAPI XmlSaveDocumentToBuffer(
1278 __in IXMLDOMDocument* pixdDocument,
1279 __deref_out_bcount(*pcbDest) BYTE** ppbDest,
1280 __out DWORD* pcbDest
1281 )
1282{
1283 HRESULT hr = S_OK;
1284 IStream* pStream = NULL;
1285 LARGE_INTEGER li = { };
1286 STATSTG statstg = { };
1287 BYTE* pbDest = NULL;
1288 ULONG cbRead = 0;
1289 VARIANT vtDestination;
1290
1291 ::VariantInit(&vtDestination);
1292
1293 // create stream
1294 hr = ::CreateStreamOnHGlobal(NULL, TRUE, &pStream);
1295 XmlExitOnFailure(hr, "Failed to create stream.");
1296
1297 // write document to stream
1298 vtDestination.vt = VT_UNKNOWN;
1299 vtDestination.punkVal = (IUnknown*)pStream;
1300 hr = pixdDocument->save(vtDestination);
1301 XmlExitOnFailure(hr, "Failed to save document.");
1302
1303 // get stream size
1304 hr = pStream->Stat(&statstg, STATFLAG_NONAME);
1305 XmlExitOnFailure(hr, "Failed to get stream size.");
1306
1307 // allocate buffer
1308 pbDest = static_cast<BYTE*>(MemAlloc(statstg.cbSize.LowPart, TRUE));
1309 XmlExitOnNull(pbDest, hr, E_OUTOFMEMORY, "Failed to allocate destination buffer.");
1310
1311 // read data from stream
1312 li.QuadPart = 0;
1313 hr = pStream->Seek(li, STREAM_SEEK_SET, NULL);
1314 XmlExitOnFailure(hr, "Failed to seek stream.");
1315
1316 hr = pStream->Read(pbDest, statstg.cbSize.LowPart, &cbRead);
1317 if (cbRead < statstg.cbSize.LowPart)
1318 {
1319 hr = E_FAIL;
1320 }
1321 XmlExitOnFailure(hr, "Failed to read stream content to buffer.");
1322
1323 // return value
1324 *ppbDest = pbDest;
1325 pbDest = NULL;
1326 *pcbDest = statstg.cbSize.LowPart;
1327
1328LExit:
1329 ReleaseObject(pStream);
1330 ReleaseMem(pbDest);
1331 return hr;
1332}
diff --git a/src/libs/dutil/WixToolset.DUtil/xsd/thmutil.xsd b/src/libs/dutil/WixToolset.DUtil/xsd/thmutil.xsd
new file mode 100644
index 00000000..46c20e4a
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/xsd/thmutil.xsd
@@ -0,0 +1,1188 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
6 xmlns:xse="http://wixtoolset.org/schemas/XmlSchemaExtension"
7 xmlns:html="http://www.w3.org/1999/xhtml"
8 targetNamespace="http://wixtoolset.org/schemas/v4/thmutil"
9 xmlns="http://wixtoolset.org/schemas/v4/thmutil">
10 <xs:annotation>
11 <xs:documentation>
12 Schema for describing Theme files processed by thmutil.
13 </xs:documentation>
14 </xs:annotation>
15
16 <xs:import namespace="http://www.w3.org/1999/xhtml" />
17
18 <xs:element name="Theme">
19 <xs:annotation>
20 <xs:documentation>
21 This is the top-level container element for every thmutil Theme file.
22 </xs:documentation>
23 </xs:annotation>
24 <xs:complexType>
25 <xs:sequence>
26 <xs:element ref="Font" maxOccurs="unbounded" />
27 <xs:element ref="Window" />
28 </xs:sequence>
29 <xs:attribute name="ImageFile" type="xs:string">
30 <xs:annotation>
31 <xs:documentation>
32 Relative path to an image file that can serve as a single source for images in the rest of the theme.
33 This image is referenced by controls using the SourceX and SourceY attributes.
34 Mutually exclusive with the ImageResource attribute.
35 </xs:documentation>
36 </xs:annotation>
37 </xs:attribute>
38 <xs:attribute name="ImageResource" type="xs:string">
39 <xs:annotation>
40 <xs:documentation>
41 Identifier that references an image resource in the module for the window.
42 Mutually exclusive with the ImageFile attribute.
43 </xs:documentation>
44 </xs:annotation>
45 </xs:attribute>
46 </xs:complexType>
47 </xs:element>
48
49 <xs:element name="Font">
50 <xs:annotation>
51 <xs:documentation>Defines a font including the size and color.</xs:documentation>
52 </xs:annotation>
53 <xs:complexType>
54 <xs:simpleContent>
55 <xs:extension base="xs:string">
56 <xs:annotation>
57 <xs:documentation>Name of the font face (required).</xs:documentation>
58 </xs:annotation>
59 <xs:attribute name="Id" type="xs:nonNegativeInteger" use="required">
60 <xs:annotation>
61 <xs:documentation>Numeric identifier for the font. Due to limitations in thmutil the first Font must start with "0" and each subsequent Font must increment the Id by 1. Failure to ensure the Font identifiers follow this strict ordering will create unexpected behavior or crashes.</xs:documentation>
62 </xs:annotation>
63 </xs:attribute>
64 <xs:attribute name="Height" type="xs:int" use="required">
65 <xs:annotation>
66 <xs:documentation>Font size. Use negative numbers to specify the font in pixels.</xs:documentation>
67 </xs:annotation>
68 </xs:attribute>
69 <xs:attribute name="Weight" type="xs:nonNegativeInteger">
70 <xs:annotation>
71 <xs:documentation>Font weight.</xs:documentation>
72 </xs:annotation>
73 </xs:attribute>
74 <xs:attribute name="Foreground" type="FontColorType">
75 <xs:annotation>
76 <xs:documentation>
77 A system color id or a hexadecimal value representing BGR foreground color of the font.
78 "ffffff" is white, "ff0000" is pure blue, "00ff00" is pure green, "0000ff" is pure red, and "000000" is black.
79 If this attribute is absent the foreground will be transparent.
80 Supported system color ids are: btnface, btntext, graytext, highlight, highlighttext, hotlight, window, and windowtext.
81 </xs:documentation>
82 </xs:annotation>
83 </xs:attribute>
84 <xs:attribute name="Background" type="FontColorType">
85 <xs:annotation>
86 <xs:documentation>
87 A system color id or a hexadecimal value representing BGR background color of the font.
88 "ffffff" is white, "ff0000" is pure blue, "00ff00" is pure green, "0000ff" is pure red, and "000000" is black.
89 If this attribute is absent the background will be transparent.
90 Supported system color ids are: btnface, btntext, graytext, highlight, highlighttext, hotlight, window, and windowtext.
91 </xs:documentation>
92 </xs:annotation>
93 </xs:attribute>
94 <xs:attribute name="Underline" type="YesNoType">
95 <xs:annotation>
96 <xs:documentation>Specifies whether the font is underlined.</xs:documentation>
97 </xs:annotation>
98 </xs:attribute>
99 </xs:extension>
100 </xs:simpleContent>
101 </xs:complexType>
102 </xs:element>
103
104 <xs:element name="ImageList">
105 <xs:annotation>
106 <xs:documentation>List of images which can be shared between multiple controls.</xs:documentation>
107 </xs:annotation>
108 <xs:complexType>
109 <xs:choice maxOccurs="unbounded">
110 <xs:element ref="Image" />
111 </xs:choice>
112 <xs:attribute name="Name" type="xs:string" use="required">
113 <xs:annotation>
114 <xs:documentation>
115 Name of the ImageList, to be referenced by other controls.
116 </xs:documentation>
117 </xs:annotation>
118 </xs:attribute>
119 </xs:complexType>
120 </xs:element>
121
122 <xs:element name="Page">
123 <xs:annotation>
124 <xs:documentation>Named set of controls that can be shown and hidden collectively.</xs:documentation>
125 </xs:annotation>
126 <xs:complexType>
127 <xs:group ref="ControlElements" maxOccurs="unbounded"/>
128 <xs:attribute name="Name" type="xs:string">
129 <xs:annotation>
130 <xs:documentation>
131 Optional name for the page.
132 </xs:documentation>
133 </xs:annotation>
134 </xs:attribute>
135 </xs:complexType>
136 </xs:element>
137
138 <xs:element name="Window">
139 <xs:annotation>
140 <xs:documentation>Defines the overall look of the main window.</xs:documentation>
141 </xs:annotation>
142 <xs:complexType>
143 <xs:choice minOccurs="0" maxOccurs="unbounded">
144 <xs:element ref="ImageList" />
145 <xs:element ref="Page" />
146 <xs:group ref="ControlElements" minOccurs="0" maxOccurs="unbounded" />
147 </xs:choice>
148 <xs:attribute name="AutoResize" type="YesNoType">
149 <xs:annotation>
150 <xs:documentation>Specifies whether the ThmUtil default window proc should process WM_SIZE and WM_SIZING events.</xs:documentation>
151 </xs:annotation>
152 </xs:attribute>
153 <xs:attribute name="Caption" type="xs:string">
154 <xs:annotation>
155 <xs:documentation>
156 Caption for the window.
157 This is required if not using the StringId attribute.
158 </xs:documentation>
159 </xs:annotation>
160 </xs:attribute>
161 <xs:attribute name="FontId" type="xs:nonNegativeInteger" use="required">
162 <xs:annotation>
163 <xs:documentation>Numeric identifier to the Font element that serves as the default font for the window.</xs:documentation>
164 </xs:annotation>
165 </xs:attribute>
166 <xs:attribute name="Height" type="xs:positiveInteger" use="required">
167 <xs:annotation>
168 <xs:documentation>Height of the window's client area.</xs:documentation>
169 </xs:annotation>
170 </xs:attribute>
171 <xs:attribute name="HexStyle" type="xs:hexBinary">
172 <xs:annotation>
173 <xs:documentation>
174 Hexadecimal window style. If this is not specified the default value is: WS_OVERLAPPED | WS_VISIBLE | WS_MINIMIZEBOX | WS_SYSMENU.
175 If SourceX and SourceY are specified, then WS_OVERLAPPED is replaced with WS_POPUP.
176 </xs:documentation>
177 </xs:annotation>
178 </xs:attribute>
179 <xs:attribute name="IconFile" type="xs:string">
180 <xs:annotation>
181 <xs:documentation>Relative path to an icon file for the window. Mutually exclusive with IconResource and SourceX and SourceY attributes.</xs:documentation>
182 </xs:annotation>
183 </xs:attribute>
184 <xs:attribute name="IconResource" type="xs:string">
185 <xs:annotation>
186 <xs:documentation>
187 Identifier that references an icon resource in the module for the icon for the window.
188 Mutually exclusive with IconFile and SourceX and SourceY attributes.
189 </xs:documentation>
190 </xs:annotation>
191 </xs:attribute>
192 <xs:attribute name="MinimumHeight" type="xs:positiveInteger">
193 <xs:annotation>
194 <xs:documentation>Minimum height of the window. Only functions if AutoResize is enabled.</xs:documentation>
195 </xs:annotation>
196 </xs:attribute>
197 <xs:attribute name="MinimumWidth" type="xs:positiveInteger">
198 <xs:annotation>
199 <xs:documentation>Minimum width of the window. Only functions if AutoResize is enabled.</xs:documentation>
200 </xs:annotation>
201 </xs:attribute>
202 <xs:attribute name="SourceX" type="xs:nonNegativeInteger">
203 <xs:annotation>
204 <xs:documentation>X offset of the window background in the Theme/@ImageFile. Mutually exclusive with IconFile and IconResource.</xs:documentation>
205 </xs:annotation>
206 </xs:attribute>
207 <xs:attribute name="SourceY" type="xs:nonNegativeInteger">
208 <xs:annotation>
209 <xs:documentation>Y offset of the window background in the Theme/@ImageFile. Mutually exclusive with IconFile and IconResource.</xs:documentation>
210 </xs:annotation>
211 </xs:attribute>
212 <xs:attribute name="StringId" type="xs:nonNegativeInteger">
213 <xs:annotation>
214 <xs:documentation>
215 Identifier that references a string resource in the module to define the window caption.
216 Mutually exclusive with the Caption attribute.
217 </xs:documentation>
218 </xs:annotation>
219 </xs:attribute>
220 <xs:attribute name="Width" type="xs:positiveInteger" use="required">
221 <xs:annotation>
222 <xs:documentation>Width of the window's client area.</xs:documentation>
223 </xs:annotation>
224 </xs:attribute>
225 </xs:complexType>
226 </xs:element>
227
228 <xs:element name="Billboard">
229 <xs:annotation>
230 <xs:documentation>Defines a control that rotates through a set of images on a specified interval.</xs:documentation>
231 </xs:annotation>
232 <xs:complexType>
233 <xs:sequence>
234 <xs:element ref="Image" />
235 </xs:sequence>
236 <xs:attributeGroup ref="CommonControlAttributes" />
237 <xs:attribute name="Interval" type="xs:positiveInteger">
238 <xs:annotation>
239 <xs:documentation>
240 Specifies the time to wait before showing the next image, in milliseconds.
241 </xs:documentation>
242 </xs:annotation>
243 </xs:attribute>
244 <xs:attribute name="Loop" type="YesNoType">
245 <xs:annotation>
246 <xs:documentation>Specifies whether the billboard should loop through the images infinitely.</xs:documentation>
247 </xs:annotation>
248 </xs:attribute>
249 </xs:complexType>
250 </xs:element>
251
252 <xs:element name="Button">
253 <xs:annotation>
254 <xs:documentation>Defines a button.</xs:documentation>
255 </xs:annotation>
256 <xs:complexType mixed="true">
257 <xs:annotation>
258 <xs:documentation>
259 Text to display in the button.
260 Mutually exclusive with the StringId attribute and child Text elements.
261 </xs:documentation>
262 </xs:annotation>
263 <xs:choice minOccurs="0" maxOccurs="unbounded">
264 <xs:annotation>
265 <xs:documentation>
266 If multiple Action elements are given, the conditions should be mutually exclusive (when multiple conditions are true, the behavior is undefined and could be changed at any time).
267 If none of the conditions of the Action elements are true, then it uses the Action element without the Condition attribute.
268 </xs:documentation>
269 </xs:annotation>
270 <xs:element ref="BrowseDirectoryAction" />
271 <xs:element ref="ChangePageAction" />
272 <xs:element ref="CloseWindowAction" />
273 <xs:element ref="Text" />
274 <xs:element ref="Tooltip" maxOccurs="1" />
275 </xs:choice>
276 <xs:attributeGroup ref="CommonControlAttributes" />
277 <xs:attribute name="FontId" type="xs:nonNegativeInteger" use="required">
278 <xs:annotation>
279 <xs:documentation>Numeric identifier to the Font element that serves as the font for the control. Only valid when using graphic buttons.</xs:documentation>
280 </xs:annotation>
281 </xs:attribute>
282 <xs:attribute name="HoverFontId" type="xs:nonNegativeInteger">
283 <xs:annotation>
284 <xs:documentation>Numeric identifier to the Font element that serves as the font when the control is hovered over. Only valid when using graphic buttons.</xs:documentation>
285 </xs:annotation>
286 </xs:attribute>
287 <xs:attribute name="ImageFile" type="xs:string">
288 <xs:annotation>
289 <xs:documentation>
290 Relative path to an image file to define a graphic button.
291 The image must be 4x the height to represent the button in 4 states: unselected, hover, selected, focused.
292 Mutually exclusive with ImageResource and SourceX and SourceY attributes.
293 </xs:documentation>
294 </xs:annotation>
295 </xs:attribute>
296 <xs:attribute name="ImageResource" type="xs:string">
297 <xs:annotation>
298 <xs:documentation>
299 Identifier that references an image resource in the module to define a graphic button.
300 The image must be 4x the height to represent the button in 4 states: unselected, hover, selected, focused.
301 Mutually exclusive with ImageFile and SourceX and SourceY attributes.
302 </xs:documentation>
303 </xs:annotation>
304 </xs:attribute>
305 <xs:attribute name="SelectedFontId" type="xs:nonNegativeInteger">
306 <xs:annotation>
307 <xs:documentation>Numeric identifier to the Font element that serves as the font when the control is selected. Only valid when using graphic buttons.</xs:documentation>
308 </xs:annotation>
309 </xs:attribute>
310 <xs:attribute name="StringId" type="xs:nonNegativeInteger">
311 <xs:annotation>
312 <xs:documentation>
313 Identifier that references a string resource in the module to define the text for the control.
314 </xs:documentation>
315 </xs:annotation>
316 </xs:attribute>
317 </xs:complexType>
318 </xs:element>
319
320 <xs:element name="BrowseDirectoryAction">
321 <xs:annotation>
322 <xs:documentation>
323 When the button is pressed, a directory browser dialog is shown.
324 </xs:documentation>
325 </xs:annotation>
326 <xs:complexType>
327 <xs:attribute name="Condition" type="xs:string">
328 <xs:annotation>
329 <xs:documentation>
330 The condition that determines if the parent control will execute this action.
331 </xs:documentation>
332 </xs:annotation>
333 </xs:attribute>
334 <xs:attribute name="VariableName" type="xs:string" use="required">
335 <xs:annotation>
336 <xs:documentation>
337 The name of the variable to update when the user selects a directory from the dialog.
338 </xs:documentation>
339 </xs:annotation>
340 </xs:attribute>
341 </xs:complexType>
342 </xs:element>
343
344 <xs:element name="ChangePageAction">
345 <xs:annotation>
346 <xs:documentation>
347 When the button is pressed, the specified page is shown.
348 </xs:documentation>
349 </xs:annotation>
350 <xs:complexType>
351 <xs:attribute name="Cancel" type="YesNoType">
352 <xs:annotation>
353 <xs:documentation>
354 When set to 'yes', none of the variable changes made on the current page are saved.
355 </xs:documentation>
356 </xs:annotation>
357 </xs:attribute>
358 <xs:attribute name="Condition" type="xs:string">
359 <xs:annotation>
360 <xs:documentation>
361 The condition that determines if the parent control will execute this action.
362 </xs:documentation>
363 </xs:annotation>
364 </xs:attribute>
365 <xs:attribute name="Page" type="xs:string" use="required">
366 <xs:annotation>
367 <xs:documentation>
368 The Name of the Page to show.
369 </xs:documentation>
370 </xs:annotation>
371 </xs:attribute>
372 </xs:complexType>
373 </xs:element>
374
375 <xs:element name="CloseWindowAction">
376 <xs:annotation>
377 <xs:documentation>
378 When the button is pressed, the WM_CLOSE message is sent to the window.
379 </xs:documentation>
380 </xs:annotation>
381 <xs:complexType>
382 <xs:attribute name="Condition" type="xs:string">
383 <xs:annotation>
384 <xs:documentation>
385 The condition that determines if the parent control will execute this action.
386 </xs:documentation>
387 </xs:annotation>
388 </xs:attribute>
389 </xs:complexType>
390 </xs:element>
391
392 <xs:element name="Checkbox">
393 <xs:annotation>
394 <xs:documentation>Defines a checkbox.</xs:documentation>
395 </xs:annotation>
396 <xs:complexType mixed="true">
397 <xs:annotation>
398 <xs:documentation>
399 Text to display beside the checkbox.
400 Mutually exclusive with the StringId attribute and child Text elements.
401 </xs:documentation>
402 </xs:annotation>
403 <xs:choice minOccurs="0" maxOccurs="unbounded">
404 <xs:element ref="Text" />
405 <xs:element ref="Tooltip" maxOccurs="1" />
406 </xs:choice>
407 <xs:attributeGroup ref="CommonControlAttributes" />
408 <xs:attribute name="FontId" type="xs:nonNegativeInteger" use="required">
409 <xs:annotation>
410 <xs:documentation>Numeric identifier to the Font element that serves as the font for the control.</xs:documentation>
411 </xs:annotation>
412 </xs:attribute>
413 <xs:attribute name="StringId" type="xs:nonNegativeInteger">
414 <xs:annotation>
415 <xs:documentation>
416 Identifier that references a string resource in the module to define the text for the control.
417 </xs:documentation>
418 </xs:annotation>
419 </xs:attribute>
420 </xs:complexType>
421 </xs:element>
422
423 <xs:element name="Combobox">
424 <xs:annotation>
425 <xs:documentation>Defines a combobox.</xs:documentation>
426 </xs:annotation>
427 <xs:complexType>
428 <xs:attributeGroup ref="CommonControlAttributes" />
429 <xs:attribute name="FontId" type="xs:nonNegativeInteger" use="required">
430 <xs:annotation>
431 <xs:documentation>Numeric identifier to the Font element that serves as the font for the control.</xs:documentation>
432 </xs:annotation>
433 </xs:attribute>
434 </xs:complexType>
435 </xs:element>
436
437 <xs:element name="CommandLink">
438 <xs:annotation>
439 <xs:documentation>Defines a button.</xs:documentation>
440 </xs:annotation>
441 <xs:complexType mixed="true">
442 <xs:annotation>
443 <xs:documentation>
444 Text to display in the button.
445 Mutually exclusive with the StringId attribute and child Text elements.
446 </xs:documentation>
447 </xs:annotation>
448 <xs:choice minOccurs="0" maxOccurs="unbounded">
449 <xs:annotation>
450 <xs:documentation>
451 If multiple Action elements are given, the conditions should be mutually exclusive (when multiple conditions are true, the behavior is undefined and could be changed at any time).
452 If none of the conditions of the Action elements are true, then it uses the Action element without the Condition attribute.
453 </xs:documentation>
454 </xs:annotation>
455 <xs:element ref="BrowseDirectoryAction" />
456 <xs:element ref="ChangePageAction" />
457 <xs:element ref="CloseWindowAction" />
458 <xs:element ref="Note" />
459 <xs:element ref="Text" />
460 </xs:choice>
461 <xs:attributeGroup ref="CommonControlAttributes" />
462 <xs:attribute name="FontId" type="xs:nonNegativeInteger" use="required">
463 <xs:annotation>
464 <xs:documentation>Numeric identifier to the Font element that serves as the font for the control. Only valid when using graphic buttons.</xs:documentation>
465 </xs:annotation>
466 </xs:attribute>
467 <xs:attribute name="IconFile" type="xs:string">
468 <xs:annotation>
469 <xs:documentation>
470 Relative path to an icon file to define a command link glyph.
471 Mutually exclusive with ImageResource and SourceX and SourceY attributes.
472 </xs:documentation>
473 </xs:annotation>
474 </xs:attribute>
475 <xs:attribute name="IconResource" type="xs:string">
476 <xs:annotation>
477 <xs:documentation>
478 Identifier that references an icon resource in the module to define a command link glyph.
479 Mutually exclusive with ImageFile and SourceX and SourceY attributes.
480 </xs:documentation>
481 </xs:annotation>
482 </xs:attribute>
483 <xs:attribute name="ImageFile" type="xs:string">
484 <xs:annotation>
485 <xs:documentation>
486 Relative path to an image file to define a command link glyph.
487 Mutually exclusive with ImageResource and SourceX and SourceY attributes.
488 </xs:documentation>
489 </xs:annotation>
490 </xs:attribute>
491 <xs:attribute name="ImageResource" type="xs:string">
492 <xs:annotation>
493 <xs:documentation>
494 Identifier that references an image resource in the module to define a command link glyph.
495 Mutually exclusive with ImageFile and SourceX and SourceY attributes.
496 </xs:documentation>
497 </xs:annotation>
498 </xs:attribute>
499 <xs:attribute name="StringId" type="xs:nonNegativeInteger">
500 <xs:annotation>
501 <xs:documentation>
502 Identifier that references a string resource in the module to define the text for the control.
503 </xs:documentation>
504 </xs:annotation>
505 </xs:attribute>
506 </xs:complexType>
507 </xs:element>
508
509 <xs:element name="Editbox">
510 <xs:annotation>
511 <xs:documentation>Defines an edit box.</xs:documentation>
512 </xs:annotation>
513 <xs:complexType>
514 <xs:simpleContent>
515 <xs:extension base="xs:string">
516 <xs:annotation>
517 <xs:documentation>
518 Initial text for the control.
519 Mutually exclusive with the StringId attribute.
520 </xs:documentation>
521 </xs:annotation>
522 <xs:attributeGroup ref="CommonControlAttributes" />
523 <xs:attribute name="FileSystemAutoComplete" type="YesNoType">
524 <xs:annotation>
525 <xs:documentation>Specifies whether the edit box should auto-complete with file system paths.</xs:documentation>
526 </xs:annotation>
527 </xs:attribute>
528 <xs:attribute name="FontId" type="xs:nonNegativeInteger" use="required">
529 <xs:annotation>
530 <xs:documentation>Numeric identifier to the Font element that serves as the font for the control.</xs:documentation>
531 </xs:annotation>
532 </xs:attribute>
533 <xs:attribute name="StringId" type="xs:nonNegativeInteger">
534 <xs:annotation>
535 <xs:documentation>
536 Identifier that references a string resource in the module to define the initial text for the control.
537 </xs:documentation>
538 </xs:annotation>
539 </xs:attribute>
540 </xs:extension>
541 </xs:simpleContent>
542 </xs:complexType>
543 </xs:element>
544
545 <xs:element name="Hyperlink">
546 <xs:annotation>
547 <xs:documentation>Defines a hyperlink.</xs:documentation>
548 </xs:annotation>
549 <xs:complexType mixed="true">
550 <xs:annotation>
551 <xs:documentation>
552 Text to display as the link.
553 Mutually exclusive with the StringId attribute and child Text elements.
554 </xs:documentation>
555 </xs:annotation>
556 <xs:choice minOccurs="0" maxOccurs="unbounded">
557 <xs:element ref="Text" />
558 <xs:element ref="Tooltip" maxOccurs="1" />
559 </xs:choice>
560 <xs:attributeGroup ref="CommonControlAttributes" />
561 <xs:attribute name="FontId" type="xs:nonNegativeInteger" use="required">
562 <xs:annotation>
563 <xs:documentation>Numeric identifier to the Font element that serves as the unselected font.</xs:documentation>
564 </xs:annotation>
565 </xs:attribute>
566 <xs:attribute name="HoverFontId" type="xs:nonNegativeInteger" use="required">
567 <xs:annotation>
568 <xs:documentation>Numeric identifier to the Font element that serves as the font when the control is hovered over.</xs:documentation>
569 </xs:annotation>
570 </xs:attribute>
571 <xs:attribute name="SelectedFontId" type="xs:nonNegativeInteger" use="required">
572 <xs:annotation>
573 <xs:documentation>Numeric identifier to the Font element that serves as the font when the control is selected.</xs:documentation>
574 </xs:annotation>
575 </xs:attribute>
576 <xs:attribute name="StringId" type="xs:nonNegativeInteger">
577 <xs:annotation>
578 <xs:documentation>
579 Identifier that references a string resource in the module to define the text for the control.
580 </xs:documentation>
581 </xs:annotation>
582 </xs:attribute>
583 </xs:complexType>
584 </xs:element>
585
586 <xs:element name="Hypertext">
587 <xs:annotation>
588 <xs:documentation>Defines a text block with support for HTML &lt;a&gt; tags.</xs:documentation>
589 </xs:annotation>
590 <xs:complexType mixed="true">
591 <xs:annotation>
592 <xs:documentation>
593 Text to display as the link.
594 Use HTML &lt;a href="URL"&gt; to create a link.
595 Mutually exclusive with the StringId attribute and child Text elements.
596 </xs:documentation>
597 </xs:annotation>
598 <xs:choice minOccurs="0" maxOccurs="unbounded">
599 <xs:element ref="Text" />
600 <xs:element ref="Tooltip" maxOccurs="1" />
601 </xs:choice>
602 <xs:attributeGroup ref="CommonControlAttributes" />
603 <xs:attribute name="FontId" type="xs:nonNegativeInteger" use="required">
604 <xs:annotation>
605 <xs:documentation>Numeric identifier to the Font element that serves as the font for the control.</xs:documentation>
606 </xs:annotation>
607 </xs:attribute>
608 <xs:attribute name="StringId" type="xs:nonNegativeInteger">
609 <xs:annotation>
610 <xs:documentation>
611 Identifier that references a string resource in the module to define the text for the control.
612 </xs:documentation>
613 </xs:annotation>
614 </xs:attribute>
615 </xs:complexType>
616 </xs:element>
617
618 <xs:element name="Image">
619 <xs:annotation>
620 <xs:documentation>Defines an image for an ImageList or Billboard.</xs:documentation>
621 </xs:annotation>
622 <xs:complexType>
623 <xs:attribute name="ImageFile" type="xs:string">
624 <xs:annotation>
625 <xs:documentation>Relative path to an image file. Mutually exclusive with ImageResource.</xs:documentation>
626 </xs:annotation>
627 </xs:attribute>
628 <xs:attribute name="ImageResource" type="xs:string">
629 <xs:annotation>
630 <xs:documentation>Identifier that references an image resource in the module. Mutually exclusive with ImageFile.</xs:documentation>
631 </xs:annotation>
632 </xs:attribute>
633 </xs:complexType>
634 </xs:element>
635
636 <xs:element name="ImageControl">
637 <xs:annotation>
638 <xs:documentation>Defines an image.</xs:documentation>
639 </xs:annotation>
640 <xs:complexType>
641 <xs:attributeGroup ref="CommonControlAttributes" />
642 <xs:attribute name="ImageFile" type="xs:string">
643 <xs:annotation>
644 <xs:documentation>Relative path to an image file. Mutually exclusive with ImageResource and SourceX and SourceY attributes.</xs:documentation>
645 </xs:annotation>
646 </xs:attribute>
647 <xs:attribute name="ImageResource" type="xs:string">
648 <xs:annotation>
649 <xs:documentation>Identifier that references an image resource in the module. Mutually exclusive with ImageFile and SourceX and SourceY attributes.</xs:documentation>
650 </xs:annotation>
651 </xs:attribute>
652 </xs:complexType>
653 </xs:element>
654
655 <xs:element name="Label">
656 <xs:annotation>
657 <xs:documentation>Defines a label.</xs:documentation>
658 </xs:annotation>
659 <xs:complexType mixed="true">
660 <xs:annotation>
661 <xs:documentation>
662 Text for the label to display.
663 Mutually exclusive with the StringId attribute and child Text elements.
664 </xs:documentation>
665 </xs:annotation>
666 <xs:choice minOccurs="0" maxOccurs="unbounded">
667 <xs:element ref="Text" />
668 <xs:element ref="Tooltip" maxOccurs="1" />
669 </xs:choice>
670 <xs:attributeGroup ref="CommonControlAttributes" />
671 <xs:attribute name="Center" type="YesNoType" use="optional">
672 <xs:annotation>
673 <xs:documentation>Specifies whether the text should be centered horizontally in the width of the control. Default is "no".</xs:documentation>
674 </xs:annotation>
675 </xs:attribute>
676 <xs:attribute name="DisablePrefix" type="YesNoType" use="optional">
677 <xs:annotation>
678 <xs:documentation>By default ampersands (&amp;) in the text will underline the next character and treat it as an accelerator key. Set this attribute to "yes" to disable that behavior. Default is "no".</xs:documentation>
679 </xs:annotation>
680 </xs:attribute>
681 <xs:attribute name="FontId" type="xs:nonNegativeInteger" use="required">
682 <xs:annotation>
683 <xs:documentation>Numeric identifier to the Font element that serves as the font for the control.</xs:documentation>
684 </xs:annotation>
685 </xs:attribute>
686 <xs:attribute name="StringId" type="xs:nonNegativeInteger">
687 <xs:annotation>
688 <xs:documentation>
689 Identifier that references a string resource in the module to define the text for the label.
690 </xs:documentation>
691 </xs:annotation>
692 </xs:attribute>
693 </xs:complexType>
694 </xs:element>
695
696 <xs:element name="ListView">
697 <xs:annotation>
698 <xs:documentation>Defines a listview.</xs:documentation>
699 </xs:annotation>
700 <xs:complexType>
701 <xs:choice maxOccurs="unbounded">
702 <xs:element ref="Column" />
703 </xs:choice>
704 <xs:attributeGroup ref="CommonControlAttributes" />
705 <xs:attribute name="FontId" type="xs:nonNegativeInteger">
706 <xs:annotation>
707 <xs:documentation>Numeric identifier to the Font element that serves as the default font for the ListView.</xs:documentation>
708 </xs:annotation>
709 </xs:attribute>
710 <xs:attribute name="HexExtendedStyle" type="xs:hexBinary">
711 <xs:annotation>
712 <xs:documentation>Hexadecimal extended window style.</xs:documentation>
713 </xs:annotation>
714 </xs:attribute>
715 <xs:attribute name="ImageList" type="xs:string">
716 <xs:annotation>
717 <xs:documentation>
718 The name of the ImageList to assign to this listview with type LVSIL_NORMAL.
719 </xs:documentation>
720 </xs:annotation>
721 </xs:attribute>
722 <xs:attribute name="ImageListSmall" type="xs:string">
723 <xs:annotation>
724 <xs:documentation>
725 The name of the ImageList to assign to this listview with type LVSIL_SMALL.
726 </xs:documentation>
727 </xs:annotation>
728 </xs:attribute>
729 <xs:attribute name="ImageListState" type="xs:string">
730 <xs:annotation>
731 <xs:documentation>
732 The name of the ImageList to assign to this listview with type LVSIL_STATE.
733 </xs:documentation>
734 </xs:annotation>
735 </xs:attribute>
736 <xs:attribute name="ImageListGroupHeader" type="xs:string">
737 <xs:annotation>
738 <xs:documentation>
739 The name of the ImageList to assign to this listview with type LVSIL_GROUPHEADER.
740 </xs:documentation>
741 </xs:annotation>
742 </xs:attribute>
743 </xs:complexType>
744 </xs:element>
745
746 <xs:element name="Note">
747 <xs:annotation>
748 <xs:documentation>
749 Defines note text for a command link control based on an optional condition.
750 If multiple Note elements are given for one control, the conditions should be mutually exclusive (when multiple conditions are true, the behavior is undefined and may be changed at any time).
751 If none of the conditions of a control's Note elements are true, then it uses the text of the Note element without the Condition attribute.
752 </xs:documentation>
753 </xs:annotation>
754 <xs:complexType>
755 <xs:simpleContent>
756 <xs:extension base="xs:string">
757 <xs:annotation>
758 <xs:documentation>
759 Note text for the parent command link control.
760 </xs:documentation>
761 </xs:annotation>
762 <xs:attribute name="Condition" type="xs:string">
763 <xs:annotation>
764 <xs:documentation>
765 The condition that determines when the parent control will use this note text.
766 </xs:documentation>
767 </xs:annotation>
768 </xs:attribute>
769 </xs:extension>
770 </xs:simpleContent>
771 </xs:complexType>
772 </xs:element>
773
774 <xs:element name="Panel">
775 <xs:annotation>
776 <xs:documentation>Defines a collection of controls.</xs:documentation>
777 </xs:annotation>
778 <xs:complexType>
779 <xs:group ref="ControlElements" maxOccurs="unbounded"/>
780 <xs:attributeGroup ref="CommonControlAttributes" />
781 </xs:complexType>
782 </xs:element>
783
784 <xs:element name="Progressbar">
785 <xs:annotation>
786 <xs:documentation>Defines a progress bar.</xs:documentation>
787 </xs:annotation>
788 <xs:complexType>
789 <xs:attributeGroup ref="CommonControlAttributes" />
790 <xs:attribute name="ImageFile" type="xs:string">
791 <xs:annotation>
792 <xs:documentation>Relative path to an image file for the control. The image must be 4 pixels wide: left pixel is the left side of progress bar, left middle pixel is progress used, right middle pixel is progress unused, right pixel is right side of progress bar. Mutually exclusive with ImageResource and SourceX and SourceY attributes.</xs:documentation>
793 </xs:annotation>
794 </xs:attribute>
795 <xs:attribute name="ImageResource" type="xs:string">
796 <xs:annotation>
797 <xs:documentation>Identifier that references an image resource in the module for the control. The image must be 4 pixels wide: left pixel is the left side of progress bar, left middle pixel is progress used, right middle pixel is progress unused, right pixel is right side of progress bar. Mutually exclusive with ImageFile and SourceX and SourceY attributes.</xs:documentation>
798 </xs:annotation>
799 </xs:attribute>
800 </xs:complexType>
801 </xs:element>
802
803 <xs:element name="RadioButton">
804 <xs:annotation>
805 <xs:documentation>Defines an individual radio button within a set of radio buttons.</xs:documentation>
806 </xs:annotation>
807 <xs:complexType mixed="true">
808 <xs:annotation>
809 <xs:documentation>
810 Text to display beside the radio button.
811 Mutually exclusive with the StringId attribute and child Text elements.
812 </xs:documentation>
813 </xs:annotation>
814 <xs:choice minOccurs="0" maxOccurs="unbounded">
815 <xs:element ref="Text" />
816 <xs:element ref="Tooltip" maxOccurs="1" />
817 </xs:choice>
818 <xs:attributeGroup ref="CommonControlAttributes" />
819 <xs:attribute name="FontId" type="xs:nonNegativeInteger" use="required">
820 <xs:annotation>
821 <xs:documentation>Numeric identifier to the Font element that serves as the font for the control.</xs:documentation>
822 </xs:annotation>
823 </xs:attribute>
824 <xs:attribute name="StringId" type="xs:nonNegativeInteger">
825 <xs:annotation>
826 <xs:documentation>
827 Identifier that references a string resource in the module to define the text for the control.
828 </xs:documentation>
829 </xs:annotation>
830 </xs:attribute>
831 <xs:attribute name="Value" type="xs:string">
832 <xs:annotation>
833 <xs:documentation>Optional value used when setting the variable associated with the set of radio buttons.</xs:documentation>
834 </xs:annotation>
835 </xs:attribute>
836 </xs:complexType>
837 </xs:element>
838
839 <xs:element name="RadioButtons">
840 <xs:annotation>
841 <xs:documentation>Defines a set of radio buttons.</xs:documentation>
842 </xs:annotation>
843 <xs:complexType>
844 <xs:choice maxOccurs="unbounded">
845 <xs:element ref="RadioButton" />
846 </xs:choice>
847 <xs:attribute name="Name" type="xs:string">
848 <xs:annotation>
849 <xs:documentation>Optional variable name for the set of radio buttons.</xs:documentation>
850 </xs:annotation>
851 </xs:attribute>
852 </xs:complexType>
853 </xs:element>
854
855 <xs:element name="Richedit">
856 <xs:annotation>
857 <xs:documentation>Defines a rich edit control.</xs:documentation>
858 </xs:annotation>
859 <xs:complexType mixed="true">
860 <xs:annotation>
861 <xs:documentation>
862 Initial text for the control.
863 Mutually exclusive with the StringId attribute.
864 </xs:documentation>
865 </xs:annotation>
866 <xs:choice minOccurs="0" maxOccurs="unbounded">
867 <xs:element ref="Text" />
868 <xs:element ref="Tooltip" maxOccurs="1" />
869 </xs:choice>
870 <xs:attributeGroup ref="CommonControlAttributes" />
871 <xs:attribute name="FontId" type="xs:nonNegativeInteger" use="required">
872 <xs:annotation>
873 <xs:documentation>
874 Numeric identifier to the Font element that serves as the font for the control.
875 </xs:documentation>
876 </xs:annotation>
877 </xs:attribute>
878 <xs:attribute name="StringId" type="xs:nonNegativeInteger">
879 <xs:annotation>
880 <xs:documentation>
881 Identifier that references a string resource in the module to define the initial text for the control.
882 </xs:documentation>
883 </xs:annotation>
884 </xs:attribute>
885 </xs:complexType>
886 </xs:element>
887
888 <xs:element name="Static">
889 <xs:annotation>
890 <xs:documentation>Defines a straight line.</xs:documentation>
891 </xs:annotation>
892 <xs:complexType>
893 <xs:attributeGroup ref="CommonControlAttributes" />
894 </xs:complexType>
895 </xs:element>
896
897 <xs:element name="Tab">
898 <xs:annotation>
899 <xs:documentation>Defines an individual tab within a set of tabs.</xs:documentation>
900 </xs:annotation>
901 <xs:complexType>
902 <xs:simpleContent>
903 <xs:extension base="xs:string">
904 <xs:annotation>
905 <xs:documentation>
906 Caption of the tab.
907 Mutually exclusive with the StringId attribute.
908 </xs:documentation>
909 </xs:annotation>
910 <xs:attribute name="StringId" type="xs:nonNegativeInteger">
911 <xs:annotation>
912 <xs:documentation>
913 Identifier that references a string resource in the module to define the caption of the tab.
914 </xs:documentation>
915 </xs:annotation>
916 </xs:attribute>
917 </xs:extension>
918 </xs:simpleContent>
919 </xs:complexType>
920 </xs:element>
921
922 <xs:element name="Tabs">
923 <xs:annotation>
924 <xs:documentation>Defines a set of tabs.</xs:documentation>
925 </xs:annotation>
926 <xs:complexType>
927 <xs:choice maxOccurs="unbounded">
928 <xs:element ref="Tab" />
929 </xs:choice>
930 <xs:attributeGroup ref="CommonControlAttributes" />
931 <xs:attribute name="FontId" type="xs:nonNegativeInteger" use="required">
932 <xs:annotation>
933 <xs:documentation>Numeric identifier to the Font element that serves as the font for the control.</xs:documentation>
934 </xs:annotation>
935 </xs:attribute>
936 </xs:complexType>
937 </xs:element>
938
939 <xs:element name="Text">
940 <xs:annotation>
941 <xs:documentation>
942 Defines text for the parent control based on an optional condition.
943 If multiple Text elements are given for one control, the conditions should be mutually exclusive (when multiple conditions are true, the behavior is undefined and may be changed at any time).
944 If none of the conditions of a control's Text elements are true, then it uses the text of the Text element without the Condition attribute.
945 </xs:documentation>
946 </xs:annotation>
947 <xs:complexType>
948 <xs:simpleContent>
949 <xs:extension base="xs:string">
950 <xs:annotation>
951 <xs:documentation>
952 Text for the parent control.
953 </xs:documentation>
954 </xs:annotation>
955 <xs:attribute name="Condition" type="xs:string">
956 <xs:annotation>
957 <xs:documentation>
958 The condition that determines when the parent control will use this text.
959 </xs:documentation>
960 </xs:annotation>
961 </xs:attribute>
962 </xs:extension>
963 </xs:simpleContent>
964 </xs:complexType>
965 </xs:element>
966
967 <xs:element name="Tooltip">
968 <xs:annotation>
969 <xs:documentation>
970 Defines text for the parent control's tooltip.
971 </xs:documentation>
972 </xs:annotation>
973 <xs:complexType>
974 <xs:simpleContent>
975 <xs:extension base="xs:string">
976 <xs:annotation>
977 <xs:documentation>
978 Text for the parent control's tooltip.
979 </xs:documentation>
980 </xs:annotation>
981 </xs:extension>
982 </xs:simpleContent>
983 </xs:complexType>
984 </xs:element>
985
986 <xs:element name="TreeView">
987 <xs:annotation>
988 <xs:documentation>Defines a treeview.</xs:documentation>
989 </xs:annotation>
990 <xs:complexType>
991 <xs:attributeGroup ref="CommonControlAttributes"/>
992 <xs:attribute name="AlwaysShowSelect">
993 <xs:annotation>
994 <xs:documentation>Specifies whether the row always appears selected even when the treeview has lost focus.</xs:documentation>
995 </xs:annotation>
996 </xs:attribute>
997 <xs:attribute name="EnableDragDrop">
998 <xs:annotation>
999 <xs:documentation>Specifies whether drag and drop is enabled for the treeview.</xs:documentation>
1000 </xs:annotation>
1001 </xs:attribute>
1002 <xs:attribute name="FullRowSelect">
1003 <xs:annotation>
1004 <xs:documentation>Specifies whether an entire row is selected for the treeview.</xs:documentation>
1005 </xs:annotation>
1006 </xs:attribute>
1007 <xs:attribute name="HasButtons">
1008 <xs:annotation>
1009 <xs:documentation>Specifies whether the treeview will show buttons.</xs:documentation>
1010 </xs:annotation>
1011 </xs:attribute>
1012 <xs:attribute name="HasLines">
1013 <xs:annotation>
1014 <xs:documentation>Specifies whether lines appear for all treeview items.</xs:documentation>
1015 </xs:annotation>
1016 </xs:attribute>
1017 <xs:attribute name="LinesAtRoot">
1018 <xs:annotation>
1019 <xs:documentation>Specifies whether the root nodes have lines beside them.</xs:documentation>
1020 </xs:annotation>
1021 </xs:attribute>
1022 </xs:complexType>
1023 </xs:element>
1024
1025 <xs:element name="Column">
1026 <xs:annotation>
1027 <xs:documentation>A column of a list.</xs:documentation>
1028 </xs:annotation>
1029 <xs:complexType>
1030 <xs:simpleContent>
1031 <xs:extension base="xs:string">
1032 <xs:annotation>
1033 <xs:documentation>
1034 Text for the column header.
1035 Mutually exclusive with the StringId attribute.
1036 </xs:documentation>
1037 </xs:annotation>
1038 <xs:attribute name="Width" type="xs:int">
1039 <xs:annotation>
1040 <xs:documentation>Width of the column.</xs:documentation>
1041 </xs:annotation>
1042 </xs:attribute>
1043 <xs:attribute name="Expands" type="YesNoType">
1044 <xs:annotation>
1045 <xs:documentation>
1046 Whether or not this column can grow to fill available width of the listview.
1047 More than one column can be marked with yes - all expandable columns will share available extra space.
1048 This is especially useful if the Window/@AutoResize is yes.
1049 </xs:documentation>
1050 </xs:annotation>
1051 </xs:attribute>
1052 <xs:attribute name="StringId" type="xs:nonNegativeInteger">
1053 <xs:annotation>
1054 <xs:documentation>
1055 Identifier that references a string resource in the module to define the text for the column header.
1056 </xs:documentation>
1057 </xs:annotation>
1058 </xs:attribute>
1059 </xs:extension>
1060 </xs:simpleContent>
1061 </xs:complexType>
1062 </xs:element>
1063
1064 <xs:group name="ControlElements">
1065 <xs:choice>
1066 <xs:element ref="Billboard" />
1067 <xs:element ref="Button" />
1068 <xs:element ref="Checkbox" />
1069 <xs:element ref="Combobox" />
1070 <xs:element ref="CommandLink" />
1071 <xs:element ref="Editbox" />
1072 <xs:element ref="Hyperlink" />
1073 <xs:element ref="Hypertext" />
1074 <xs:element ref="ImageControl" />
1075 <xs:element ref="Label" />
1076 <xs:element ref="ListView" />
1077 <xs:element ref="Panel" />
1078 <xs:element ref="Progressbar" />
1079 <xs:element ref="RadioButtons" />
1080 <xs:element ref="Richedit" />
1081 <xs:element ref="Static" />
1082 <xs:element ref="Tabs" />
1083 <xs:element ref="TreeView" />
1084 </xs:choice>
1085 </xs:group>
1086
1087 <xs:attributeGroup name="CommonControlAttributes">
1088 <xs:attribute name="Name" type="xs:string">
1089 <xs:annotation>
1090 <xs:documentation>Optional name for the control.</xs:documentation>
1091 </xs:annotation>
1092 </xs:attribute>
1093 <xs:attribute name="DisableAutomaticBehavior" type="YesNoType">
1094 <xs:annotation>
1095 <xs:documentation>Set to 'yes' to disable automatic variable getting and setting, EnableCondition, VisibleCondition, and conditional Text elements. The default is 'no'.</xs:documentation>
1096 </xs:annotation>
1097 </xs:attribute>
1098 <xs:attribute name="EnableCondition" type="xs:string">
1099 <xs:annotation>
1100 <xs:documentation>A condition that determines if the control is enabled. If this condition is true or omitted, then the control will be enabled.</xs:documentation>
1101 </xs:annotation>
1102 </xs:attribute>
1103 <xs:attribute name="Height" type="xs:int" use="required">
1104 <xs:annotation>
1105 <xs:documentation>Height of the control. Non-positive values extend the control to the bottom of the window minus the value.</xs:documentation>
1106 </xs:annotation>
1107 </xs:attribute>
1108 <xs:attribute name="HexStyle" type="xs:hexBinary">
1109 <xs:annotation>
1110 <xs:documentation>Hexadecimal window style for the control.</xs:documentation>
1111 </xs:annotation>
1112 </xs:attribute>
1113 <xs:attribute name="HideWhenDisabled" type="YesNoType">
1114 <xs:annotation>
1115 <xs:documentation>Specifies whether the control should be hidden when disabled.</xs:documentation>
1116 </xs:annotation>
1117 </xs:attribute>
1118 <xs:attribute name="TabStop" type="YesNoType">
1119 <xs:annotation>
1120 <xs:documentation>Specifies whether the control is part of the tab sequence of controls.</xs:documentation>
1121 </xs:annotation>
1122 </xs:attribute>
1123 <xs:attribute name="Visible" type="YesNoType">
1124 <xs:annotation>
1125 <xs:documentation>Specifies whether the control is initially visible.</xs:documentation>
1126 </xs:annotation>
1127 </xs:attribute>
1128 <xs:attribute name="VisibleCondition" type="xs:string">
1129 <xs:annotation>
1130 <xs:documentation>
1131 A condition that determines if the control is visible. If this condition is true or omitted, then the control will be visible.
1132 </xs:documentation>
1133 </xs:annotation>
1134 </xs:attribute>
1135 <xs:attribute name="Width" type="xs:int" use="required">
1136 <xs:annotation>
1137 <xs:documentation>Width of the control. Non-positive values extend the control to the right of the window minus the value.</xs:documentation>
1138 </xs:annotation>
1139 </xs:attribute>
1140 <xs:attribute name="X" type="xs:int" use="required">
1141 <xs:annotation>
1142 <xs:documentation>X coordinate for the control from the left of the window. Negative values are coordinates from the right of the window minus the width of the control.</xs:documentation>
1143 </xs:annotation>
1144 </xs:attribute>
1145 <xs:attribute name="Y" type="xs:int" use="required">
1146 <xs:annotation>
1147 <xs:documentation>Y coordinate for the control from the top of the window. Negative values are coordinates from the bottom of the window minus the height of the control.</xs:documentation>
1148 </xs:annotation>
1149 </xs:attribute>
1150 </xs:attributeGroup>
1151
1152 <xs:simpleType name="YesNoType">
1153 <xs:annotation>
1154 <xs:documentation>Values of this type will either be "yes" or "no".</xs:documentation>
1155 </xs:annotation>
1156 <xs:restriction base="xs:NMTOKEN">
1157 <xs:enumeration value="no"/>
1158 <xs:enumeration value="yes"/>
1159 </xs:restriction>
1160 </xs:simpleType>
1161
1162 <xs:simpleType name="SystemColorType">
1163 <xs:annotation>
1164 <xs:documentation>
1165 Indicates a system color for a font.
1166 </xs:documentation>
1167 </xs:annotation>
1168 <xs:restriction base="xs:NMTOKEN">
1169 <xs:enumeration value="btnface" />
1170 <xs:enumeration value="btntext" />
1171 <xs:enumeration value="graytext" />
1172 <xs:enumeration value="highlight" />
1173 <xs:enumeration value="highlighttext" />
1174 <xs:enumeration value="hotlight" />
1175 <xs:enumeration value="window" />
1176 <xs:enumeration value="windowtext" />
1177 </xs:restriction>
1178 </xs:simpleType>
1179
1180 <xs:simpleType name="FontColorType">
1181 <xs:annotation>
1182 <xs:documentation>
1183 Indicates the foreground or background color of a font.
1184 </xs:documentation>
1185 </xs:annotation>
1186 <xs:union memberTypes="SystemColorType xs:string"/>
1187 </xs:simpleType>
1188</xs:schema>
diff --git a/src/libs/dutil/appveyor.cmd b/src/libs/dutil/appveyor.cmd
new file mode 100644
index 00000000..85476b8e
--- /dev/null
+++ b/src/libs/dutil/appveyor.cmd
@@ -0,0 +1,24 @@
1@setlocal
2@pushd %~dp0
3@set _C=Release
4@if /i "%1"=="debug" set _C=Debug
5
6nuget restore || exit /b
7
8msbuild -t:Test -p:Configuration=%_C% src\test\DUtilUnitTest || exit /b
9
10msbuild -p:Configuration=%_C%;Platform=x86;PlatformToolset=v140 || exit /b
11msbuild -p:Configuration=%_C%;Platform=x64;PlatformToolset=v140 || exit /b
12
13msbuild -p:Configuration=%_C%;Platform=x86;PlatformToolset=v141 || exit /b
14msbuild -p:Configuration=%_C%;Platform=x64;PlatformToolset=v141 || exit /b
15msbuild -p:Configuration=%_C%;Platform=ARM64;PlatformToolset=v141 || exit /b
16
17msbuild -p:Configuration=%_C%;Platform=x86;PlatformToolset=v142 || exit /b
18msbuild -p:Configuration=%_C%;Platform=x64;PlatformToolset=v142 || exit /b
19msbuild -p:Configuration=%_C%;Platform=ARM64;PlatformToolset=v142 || exit /b
20
21msbuild -p:Configuration=%_C% -t:PackNative src\dutil\dutil.vcxproj || exit /b
22
23@popd
24@endlocal \ No newline at end of file
diff --git a/src/libs/dutil/appveyor.yml b/src/libs/dutil/appveyor.yml
new file mode 100644
index 00000000..f602d07c
--- /dev/null
+++ b/src/libs/dutil/appveyor.yml
@@ -0,0 +1,44 @@
1# Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2#
3# Do NOT modify this file. Update the canonical version in Home\repo-template\src\appveyor.yml
4# then update all of the repos.
5
6branches:
7 only:
8 - master
9 - develop
10
11image: Visual Studio 2019
12
13version: 0.0.0.{build}
14configuration: Release
15
16environment:
17 DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
18 DOTNET_CLI_TELEMETRY_OPTOUT: 1
19 NUGET_XMLDOC_MODE: skip
20
21build_script:
22 - appveyor.cmd
23
24test: off
25
26pull_requests:
27 do_not_increment_build_number: true
28
29nuget:
30 disable_publish_on_pr: true
31
32skip_branch_with_pr: true
33skip_tags: true
34
35artifacts:
36- path: build\Release\**\*.nupkg
37 name: nuget
38- path: build\Release\**\*.msi
39 name: msi
40
41notifications:
42- provider: Slack
43 incoming_webhook:
44 secure: p5xuu+4x2JHfwGDMDe5KcG1k7gZxqYc4jWVwvyNZv5cvkubPD2waJs5yXMAXZNN7Z63/3PWHb7q4KoY/99AjauYa1nZ4c5qYqRPFRBKTHfA=
diff --git a/src/libs/dutil/dutil.sln b/src/libs/dutil/dutil.sln
new file mode 100644
index 00000000..433f42a5
--- /dev/null
+++ b/src/libs/dutil/dutil.sln
@@ -0,0 +1,47 @@
1
2Microsoft Visual Studio Solution File, Format Version 12.00
3# Visual Studio Version 16
4VisualStudioVersion = 16.0.30711.63
5MinimumVisualStudioVersion = 15.0.26124.0
6Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dutil", "src\dutil\dutil.vcxproj", "{1244E671-F108-4334-BA52-8A7517F26ECD}"
7EndProject
8Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DUtilUnitTest", "src\test\DUtilUnitTest\DUtilUnitTest.vcxproj", "{AB7EE608-E5FB-42A5-831F-0DEEEA141223}"
9EndProject
10Global
11 GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 Debug|ARM64 = Debug|ARM64
13 Debug|x64 = Debug|x64
14 Debug|x86 = Debug|x86
15 Release|ARM64 = Release|ARM64
16 Release|x64 = Release|x64
17 Release|x86 = Release|x86
18 EndGlobalSection
19 GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 {1244E671-F108-4334-BA52-8A7517F26ECD}.Debug|ARM64.ActiveCfg = Debug|ARM64
21 {1244E671-F108-4334-BA52-8A7517F26ECD}.Debug|ARM64.Build.0 = Debug|ARM64
22 {1244E671-F108-4334-BA52-8A7517F26ECD}.Debug|x64.ActiveCfg = Debug|x64
23 {1244E671-F108-4334-BA52-8A7517F26ECD}.Debug|x64.Build.0 = Debug|x64
24 {1244E671-F108-4334-BA52-8A7517F26ECD}.Debug|x86.ActiveCfg = Debug|Win32
25 {1244E671-F108-4334-BA52-8A7517F26ECD}.Debug|x86.Build.0 = Debug|Win32
26 {1244E671-F108-4334-BA52-8A7517F26ECD}.Release|ARM64.ActiveCfg = Release|ARM64
27 {1244E671-F108-4334-BA52-8A7517F26ECD}.Release|ARM64.Build.0 = Release|ARM64
28 {1244E671-F108-4334-BA52-8A7517F26ECD}.Release|x64.ActiveCfg = Release|x64
29 {1244E671-F108-4334-BA52-8A7517F26ECD}.Release|x64.Build.0 = Release|x64
30 {1244E671-F108-4334-BA52-8A7517F26ECD}.Release|x86.ActiveCfg = Release|Win32
31 {1244E671-F108-4334-BA52-8A7517F26ECD}.Release|x86.Build.0 = Release|Win32
32 {AB7EE608-E5FB-42A5-831F-0DEEEA141223}.Debug|ARM64.ActiveCfg = Debug|Win32
33 {AB7EE608-E5FB-42A5-831F-0DEEEA141223}.Debug|x64.ActiveCfg = Debug|Win32
34 {AB7EE608-E5FB-42A5-831F-0DEEEA141223}.Debug|x86.ActiveCfg = Debug|Win32
35 {AB7EE608-E5FB-42A5-831F-0DEEEA141223}.Debug|x86.Build.0 = Debug|Win32
36 {AB7EE608-E5FB-42A5-831F-0DEEEA141223}.Release|ARM64.ActiveCfg = Release|Win32
37 {AB7EE608-E5FB-42A5-831F-0DEEEA141223}.Release|x64.ActiveCfg = Release|Win32
38 {AB7EE608-E5FB-42A5-831F-0DEEEA141223}.Release|x86.ActiveCfg = Release|Win32
39 {AB7EE608-E5FB-42A5-831F-0DEEEA141223}.Release|x86.Build.0 = Release|Win32
40 EndGlobalSection
41 GlobalSection(SolutionProperties) = preSolution
42 HideSolutionNode = FALSE
43 EndGlobalSection
44 GlobalSection(ExtensibilityGlobals) = postSolution
45 SolutionGuid = {DD209744-C40E-4C34-8CB4-BC6B71F9A133}
46 EndGlobalSection
47EndGlobal
diff --git a/src/libs/dutil/nuget.config b/src/libs/dutil/nuget.config
new file mode 100644
index 00000000..d5ef8952
--- /dev/null
+++ b/src/libs/dutil/nuget.config
@@ -0,0 +1,8 @@
1<?xml version="1.0" encoding="utf-8"?>
2<configuration>
3 <packageSources>
4 <clear />
5 <add key="wixbuildtools" value="https://ci.appveyor.com/nuget/wixbuildtools" />
6 <add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
7 </packageSources>
8</configuration> \ No newline at end of file
diff --git a/src/libs/dutil/test/DUtilUnitTest/ApupUtilTests.cpp b/src/libs/dutil/test/DUtilUnitTest/ApupUtilTests.cpp
new file mode 100644
index 00000000..30a45f5a
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/ApupUtilTests.cpp
@@ -0,0 +1,46 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9namespace DutilTests
10{
11 public ref class ApupUtil
12 {
13 public:
14 [Fact]
15 void AllocChainFromAtomSortsDescending()
16 {
17 HRESULT hr = S_OK;
18 ATOM_FEED* pFeed = NULL;
19 APPLICATION_UPDATE_CHAIN* pChain = NULL;
20
21 DutilInitialize(&DutilTestTraceError);
22
23 try
24 {
25 XmlInitialize();
26 NativeAssert::Succeeded(hr, "Failed to initialize Xml.");
27
28 pin_ptr<const wchar_t> feedFilePath = PtrToStringChars(TestData::Get("TestData", "ApupUtilTests", "FeedBv2.0.xml"));
29 hr = AtomParseFromFile(feedFilePath, &pFeed);
30 NativeAssert::Succeeded(hr, "Failed to parse feed: {0}", feedFilePath);
31
32 hr = ApupAllocChainFromAtom(pFeed, &pChain);
33 NativeAssert::Succeeded(hr, "Failed to get chain from feed.");
34
35 Assert::Equal(3ul, pChain->cEntries);
36 NativeAssert::StringEqual(L"Bundle v2.0", pChain->rgEntries[0].wzTitle);
37 NativeAssert::StringEqual(L"Bundle v1.0", pChain->rgEntries[1].wzTitle);
38 NativeAssert::StringEqual(L"Bundle v1.0-preview", pChain->rgEntries[2].wzTitle);
39 }
40 finally
41 {
42 DutilUninitialize();
43 }
44 }
45 };
46}
diff --git a/src/libs/dutil/test/DUtilUnitTest/AssemblyInfo.cpp b/src/libs/dutil/test/DUtilUnitTest/AssemblyInfo.cpp
new file mode 100644
index 00000000..2d527910
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/AssemblyInfo.cpp
@@ -0,0 +1,12 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5using namespace System::Reflection;
6using namespace System::Runtime::CompilerServices;
7using namespace System::Runtime::InteropServices;
8
9[assembly: AssemblyTitleAttribute("Windows Installer XML Dutil unit tests")];
10[assembly: AssemblyDescriptionAttribute("Dutil unit tests")];
11[assembly: AssemblyCultureAttribute("")];
12[assembly: ComVisible(false)];
diff --git a/src/libs/dutil/test/DUtilUnitTest/DUtilTests.cpp b/src/libs/dutil/test/DUtilUnitTest/DUtilTests.cpp
new file mode 100644
index 00000000..55e81d46
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/DUtilTests.cpp
@@ -0,0 +1,35 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9namespace DutilTests
10{
11 public ref class DUtil
12 {
13 public:
14 [Fact]
15 void DUtilTraceErrorSourceFiltersOnTraceLevel()
16 {
17 DutilInitialize(&DutilTestTraceError);
18
19 CallDutilTraceErrorSource();
20
21 Dutil_TraceSetLevel(REPORT_DEBUG, FALSE);
22
23 Action^ action = gcnew Action(this, &DUtil::CallDutilTraceErrorSource);
24 Assert::Throws<Exception^>(action);
25
26 DutilUninitialize();
27 }
28
29 private:
30 void CallDutilTraceErrorSource()
31 {
32 Dutil_TraceErrorSource(__FILE__, __LINE__, REPORT_DEBUG, DUTIL_SOURCE_EXTERNAL, E_FAIL, "Error message");
33 }
34 };
35}
diff --git a/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj b/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj
new file mode 100644
index 00000000..18410e5d
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj
@@ -0,0 +1,99 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
5 <Import Project="..\..\..\packages\WixBuildTools.TestSupport.Native.4.0.47\build\WixBuildTools.TestSupport.Native.props" Condition="Exists('..\..\..\packages\WixBuildTools.TestSupport.Native.4.0.47\build\WixBuildTools.TestSupport.Native.props')" />
6 <ItemGroup Label="ProjectConfigurations">
7 <ProjectConfiguration Include="Debug|Win32">
8 <Configuration>Debug</Configuration>
9 <Platform>Win32</Platform>
10 </ProjectConfiguration>
11 <ProjectConfiguration Include="Release|Win32">
12 <Configuration>Release</Configuration>
13 <Platform>Win32</Platform>
14 </ProjectConfiguration>
15 </ItemGroup>
16
17 <PropertyGroup Label="Globals">
18 <ProjectTypes>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}</ProjectTypes>
19 <ProjectGuid>{AB7EE608-E5FB-42A5-831F-0DEEEA141223}</ProjectGuid>
20 <RootNamespace>DUtilUnitTests</RootNamespace>
21 <Keyword>ManagedCProj</Keyword>
22 <ConfigurationType>DynamicLibrary</ConfigurationType>
23 <CharacterSet>Unicode</CharacterSet>
24 <CLRSupport>true</CLRSupport>
25 <SignOutput>false</SignOutput>
26 </PropertyGroup>
27
28 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
29 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
30
31 <PropertyGroup>
32 <ProjectAdditionalIncludeDirectories>..\..\dutil\inc</ProjectAdditionalIncludeDirectories>
33 <ProjectAdditionalLinkLibraries>rpcrt4.lib;Mpr.lib;Ws2_32.lib;urlmon.lib;wininet.lib</ProjectAdditionalLinkLibraries>
34 </PropertyGroup>
35
36 <ItemGroup>
37 <ClCompile Include="ApupUtilTests.cpp" />
38 <ClCompile Include="AssemblyInfo.cpp" />
39 <ClCompile Include="DictUtilTest.cpp" />
40 <ClCompile Include="DirUtilTests.cpp" />
41 <ClCompile Include="DUtilTests.cpp" />
42 <ClCompile Include="error.cpp" />
43 <ClCompile Include="FileUtilTest.cpp" />
44 <ClCompile Include="GuidUtilTest.cpp" />
45 <ClCompile Include="IniUtilTest.cpp" />
46 <ClCompile Include="MemUtilTest.cpp" />
47 <ClCompile Include="MonUtilTest.cpp" />
48 <ClCompile Include="PathUtilTest.cpp" />
49 <ClCompile Include="precomp.cpp">
50 <PrecompiledHeader>Create</PrecompiledHeader>
51 <!-- Warnings from referencing netstandard dlls -->
52 <DisableSpecificWarnings>4564;4691</DisableSpecificWarnings>
53 </ClCompile>
54 <ClCompile Include="SceUtilTest.cpp" Condition=" Exists('$(SqlCESdkIncludePath)') " />
55 <ClCompile Include="StrUtilTest.cpp" />
56 <ClCompile Include="UriUtilTest.cpp" />
57 <ClCompile Include="VerUtilTests.cpp" />
58 </ItemGroup>
59
60 <ItemGroup>
61 <ClInclude Include="precomp.h" />
62 <ClInclude Include="error.h" />
63 </ItemGroup>
64
65 <ItemGroup>
66 <None Include="packages.config" />
67 <ResourceCompile Include="UnitTest.rc" />
68 </ItemGroup>
69
70 <ItemGroup>
71 <None Include="TestData\ApupUtilTests\FeedBv2.0.xml" CopyToOutputDirectory="PreserveNewest" />
72 </ItemGroup>
73
74 <ItemGroup>
75 <Reference Include="System" />
76 <Reference Include="System.Core" />
77 <Reference Include="WixBuildTools.TestSupport">
78 <HintPath>..\..\..\packages\WixBuildTools.TestSupport.4.0.47\lib\net472\WixBuildTools.TestSupport.dll</HintPath>
79 </Reference>
80 <Reference Include="WixBuildTools.TestSupport.Native">
81 <HintPath>..\..\..\packages\WixBuildTools.TestSupport.Native.4.0.47\lib\net472\WixBuildTools.TestSupport.Native.dll</HintPath>
82 </Reference>
83 </ItemGroup>
84
85 <ItemGroup>
86 <ProjectReference Include="..\..\dutil\dutil.vcxproj">
87 <Project>{1244E671-F108-4334-BA52-8A7517F26ECD}</Project>
88 </ProjectReference>
89 </ItemGroup>
90 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
91 <Import Project="..\..\..\packages\WixBuildTools.TestSupport.Native.4.0.47\build\WixBuildTools.TestSupport.Native.targets" Condition="Exists('..\..\..\packages\WixBuildTools.TestSupport.Native.4.0.47\build\WixBuildTools.TestSupport.Native.targets')" />
92 <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
93 <PropertyGroup>
94 <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
95 </PropertyGroup>
96 <Error Condition="!Exists('..\..\..\packages\WixBuildTools.TestSupport.Native.4.0.47\build\WixBuildTools.TestSupport.Native.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\WixBuildTools.TestSupport.Native.4.0.47\build\WixBuildTools.TestSupport.Native.props'))" />
97 <Error Condition="!Exists('..\..\..\packages\WixBuildTools.TestSupport.Native.4.0.47\build\WixBuildTools.TestSupport.Native.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\WixBuildTools.TestSupport.Native.4.0.47\build\WixBuildTools.TestSupport.Native.targets'))" />
98 </Target>
99</Project> \ No newline at end of file
diff --git a/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters b/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters
new file mode 100644
index 00000000..4df7af89
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters
@@ -0,0 +1,80 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 <ItemGroup>
4 <Filter Include="Source Files">
5 <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
6 <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
7 </Filter>
8 <Filter Include="Header Files">
9 <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
10 <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
11 </Filter>
12 <Filter Include="Resource Files">
13 <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
14 <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
15 </Filter>
16 </ItemGroup>
17 <ItemGroup>
18 <ClCompile Include="ApupUtilTests.cpp">
19 <Filter>Source Files</Filter>
20 </ClCompile>
21 <ClCompile Include="AssemblyInfo.cpp">
22 <Filter>Source Files</Filter>
23 </ClCompile>
24 <ClCompile Include="DictUtilTest.cpp">
25 <Filter>Source Files</Filter>
26 </ClCompile>
27 <ClCompile Include="DirUtilTests.cpp">
28 <Filter>Source Files</Filter>
29 </ClCompile>
30 <ClCompile Include="DUtilTests.cpp">
31 <Filter>Source Files</Filter>
32 </ClCompile>
33 <ClCompile Include="error.cpp">
34 <Filter>Source Files</Filter>
35 </ClCompile>
36 <ClCompile Include="FileUtilTest.cpp">
37 <Filter>Source Files</Filter>
38 </ClCompile>
39 <ClCompile Include="GuidUtilTest.cpp">
40 <Filter>Source Files</Filter>
41 </ClCompile>
42 <ClCompile Include="IniUtilTest.cpp">
43 <Filter>Source Files</Filter>
44 </ClCompile>
45 <ClCompile Include="MemUtilTest.cpp">
46 <Filter>Source Files</Filter>
47 </ClCompile>
48 <ClCompile Include="MonUtilTest.cpp">
49 <Filter>Source Files</Filter>
50 </ClCompile>
51 <ClCompile Include="PathUtilTest.cpp">
52 <Filter>Source Files</Filter>
53 </ClCompile>
54 <ClCompile Include="precomp.cpp">
55 <Filter>Source Files</Filter>
56 </ClCompile>
57 <ClCompile Include="StrUtilTest.cpp">
58 <Filter>Source Files</Filter>
59 </ClCompile>
60 <ClCompile Include="UriUtilTest.cpp">
61 <Filter>Source Files</Filter>
62 </ClCompile>
63 <ClCompile Include="VerUtilTests.cpp">
64 <Filter>Source Files</Filter>
65 </ClCompile>
66 </ItemGroup>
67 <ItemGroup>
68 <ResourceCompile Include="UnitTest.rc">
69 <Filter>Resource Files</Filter>
70 </ResourceCompile>
71 </ItemGroup>
72 <ItemGroup>
73 <ClInclude Include="precomp.h">
74 <Filter>Header Files</Filter>
75 </ClInclude>
76 <ClInclude Include="error.h">
77 <Filter>Header Files</Filter>
78 </ClInclude>
79 </ItemGroup>
80</Project> \ No newline at end of file
diff --git a/src/libs/dutil/test/DUtilUnitTest/DictUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/DictUtilTest.cpp
new file mode 100644
index 00000000..4b4777d7
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/DictUtilTest.cpp
@@ -0,0 +1,191 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9const DWORD numIterations = 100000;
10
11namespace DutilTests
12{
13 struct Value
14 {
15 DWORD dwNum;
16 LPWSTR sczKey;
17 };
18
19 public ref class DictUtil
20 {
21 public:
22 [Fact]
23 void DictUtilTest()
24 {
25 DutilInitialize(&DutilTestTraceError);
26
27 EmbeddedKeyTestHelper(DICT_FLAG_NONE, numIterations);
28
29 EmbeddedKeyTestHelper(DICT_FLAG_CASEINSENSITIVE, numIterations);
30
31 StringListTestHelper(DICT_FLAG_NONE, numIterations);
32
33 StringListTestHelper(DICT_FLAG_CASEINSENSITIVE, numIterations);
34
35 DutilUninitialize();
36 }
37
38 private:
39 void EmbeddedKeyTestHelper(DICT_FLAG dfFlags, DWORD dwNumIterations)
40 {
41 HRESULT hr = S_OK;
42 Value *rgValues = NULL;
43 Value *valueFound = NULL;
44 DWORD cValues = 0;
45 LPWSTR sczExpectedKey = NULL;
46 STRINGDICT_HANDLE sdValues = NULL;
47
48 try
49 {
50 hr = DictCreateWithEmbeddedKey(&sdValues, 0, (void **)&rgValues, offsetof(Value, sczKey), dfFlags);
51 NativeAssert::Succeeded(hr, "Failed to create dictionary of values");
52
53 for (DWORD i = 0; i < dwNumIterations; ++i)
54 {
55 cValues++;
56
57 hr = MemEnsureArraySize((void **)&rgValues, cValues, sizeof(Value), 5);
58 NativeAssert::Succeeded(hr, "Failed to grow value array");
59
60 hr = StrAllocFormatted(&rgValues[i].sczKey, L"%u_a_%u", i, i);
61 NativeAssert::Succeeded(hr, "Failed to allocate key for value {0}", i);
62
63 hr = DictAddValue(sdValues, rgValues + i);
64 NativeAssert::Succeeded(hr, "Failed to add item {0} to dict", i);
65 }
66
67 for (DWORD i = 0; i < dwNumIterations; ++i)
68 {
69 hr = StrAllocFormatted(&sczExpectedKey, L"%u_a_%u", i, i);
70 NativeAssert::Succeeded(hr, "Failed to allocate expected key {0}", i);
71
72 hr = DictGetValue(sdValues, sczExpectedKey, (void **)&valueFound);
73 NativeAssert::Succeeded(hr, "Failed to find value {0}", sczExpectedKey);
74
75 NativeAssert::StringEqual(sczExpectedKey, valueFound->sczKey);
76
77 hr = StrAllocFormatted(&sczExpectedKey, L"%u_A_%u", i, i);
78 NativeAssert::Succeeded(hr, "Failed to allocate uppercase expected key {0}", i);
79
80 hr = DictGetValue(sdValues, sczExpectedKey, (void **)&valueFound);
81
82 if (dfFlags & DICT_FLAG_CASEINSENSITIVE)
83 {
84 NativeAssert::Succeeded(hr, "Failed to find value {0}", sczExpectedKey);
85
86 NativeAssert::StringEqual(sczExpectedKey, valueFound->sczKey, TRUE);
87 }
88 else
89 {
90 if (E_NOTFOUND != hr)
91 {
92 hr = E_FAIL;
93 ExitOnFailure(hr, "This embedded key is case sensitive, but it seemed to have found something case using case insensitivity!: %ls", sczExpectedKey);
94 }
95 }
96
97 hr = StrAllocFormatted(&sczExpectedKey, L"%u_b_%u", i, i);
98 NativeAssert::Succeeded(hr, "Failed to allocate unexpected key {0}", i);
99
100 hr = DictGetValue(sdValues, sczExpectedKey, (void **)&valueFound);
101 if (E_NOTFOUND != hr)
102 {
103 hr = E_FAIL;
104 ExitOnFailure(hr, "Item shouldn't have been found in dictionary: %ls", sczExpectedKey);
105 }
106 }
107 }
108 finally
109 {
110 for (DWORD i = 0; i < cValues; ++i)
111 {
112 ReleaseStr(rgValues[i].sczKey);
113 }
114 ReleaseMem(rgValues);
115 ReleaseStr(sczExpectedKey);
116 ReleaseDict(sdValues);
117 }
118
119 LExit:
120 return;
121 }
122
123 void StringListTestHelper(DICT_FLAG dfFlags, DWORD dwNumIterations)
124 {
125 HRESULT hr = S_OK;
126 LPWSTR sczKey = NULL;
127 LPWSTR sczExpectedKey = NULL;
128 STRINGDICT_HANDLE sdValues = NULL;
129
130 try
131 {
132 hr = DictCreateStringList(&sdValues, 0, dfFlags);
133 NativeAssert::Succeeded(hr, "Failed to create dictionary of keys");
134
135 for (DWORD i = 0; i < dwNumIterations; ++i)
136 {
137 hr = StrAllocFormatted(&sczKey, L"%u_a_%u", i, i);
138 NativeAssert::Succeeded(hr, "Failed to allocate key for value {0}", i);
139
140 hr = DictAddKey(sdValues, sczKey);
141 NativeAssert::Succeeded(hr, "Failed to add key {0} to dict", i);
142 }
143
144 for (DWORD i = 0; i < dwNumIterations; ++i)
145 {
146 hr = StrAllocFormatted(&sczExpectedKey, L"%u_a_%u", i, i);
147 NativeAssert::Succeeded(hr, "Failed to allocate expected key {0}", i);
148
149 hr = DictKeyExists(sdValues, sczExpectedKey);
150 NativeAssert::Succeeded(hr, "Failed to find value {0}", sczExpectedKey);
151
152 hr = StrAllocFormatted(&sczExpectedKey, L"%u_A_%u", i, i);
153 NativeAssert::Succeeded(hr, "Failed to allocate uppercase expected key {0}", i);
154
155 hr = DictKeyExists(sdValues, sczExpectedKey);
156 if (dfFlags & DICT_FLAG_CASEINSENSITIVE)
157 {
158 NativeAssert::Succeeded(hr, "Failed to find value {0}", sczExpectedKey);
159 }
160 else
161 {
162 if (E_NOTFOUND != hr)
163 {
164 hr = E_FAIL;
165 ExitOnFailure(hr, "This stringlist dict is case sensitive, but it seemed to have found something case using case insensitivity!: %ls", sczExpectedKey);
166 }
167 }
168
169 hr = StrAllocFormatted(&sczExpectedKey, L"%u_b_%u", i, i);
170 NativeAssert::Succeeded(hr, "Failed to allocate unexpected key {0}", i);
171
172 hr = DictKeyExists(sdValues, sczExpectedKey);
173 if (E_NOTFOUND != hr)
174 {
175 hr = E_FAIL;
176 ExitOnFailure(hr, "Item shouldn't have been found in dictionary: %ls", sczExpectedKey);
177 }
178 }
179 }
180 finally
181 {
182 ReleaseStr(sczKey);
183 ReleaseStr(sczExpectedKey);
184 ReleaseDict(sdValues);
185 }
186
187 LExit:
188 return;
189 }
190 };
191}
diff --git a/src/libs/dutil/test/DUtilUnitTest/DirUtilTests.cpp b/src/libs/dutil/test/DUtilUnitTest/DirUtilTests.cpp
new file mode 100644
index 00000000..7643366f
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/DirUtilTests.cpp
@@ -0,0 +1,70 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9namespace DutilTests
10{
11 public ref class DirUtil
12 {
13 public:
14 [Fact]
15 void DirUtilTest()
16 {
17 HRESULT hr = S_OK;
18 LPWSTR sczCurrentDir = NULL;
19 LPWSTR sczGuid = NULL;
20 LPWSTR sczFolder = NULL;
21 LPWSTR sczSubFolder = NULL;
22
23 try
24 {
25 hr = GuidCreate(&sczGuid);
26 NativeAssert::Succeeded(hr, "Failed to create guid.");
27
28 hr = DirGetCurrent(&sczCurrentDir);
29 NativeAssert::Succeeded(hr, "Failed to get current directory.");
30
31 hr = PathConcat(sczCurrentDir, sczGuid, &sczFolder);
32 NativeAssert::Succeeded(hr, "Failed to combine current directory: '{0}' with Guid: '{1}'", sczCurrentDir, sczGuid);
33
34 BOOL fExists = DirExists(sczFolder, NULL);
35 Assert::False(fExists == TRUE);
36
37 hr = PathConcat(sczFolder, L"foo", &sczSubFolder);
38 NativeAssert::Succeeded(hr, "Failed to combine folder: '%ls' with subfolder: 'foo'", sczFolder);
39
40 hr = DirEnsureExists(sczSubFolder, NULL);
41 NativeAssert::Succeeded(hr, "Failed to create multiple directories: %ls", sczSubFolder);
42
43 // Test failure to delete non-empty folder.
44 hr = DirEnsureDelete(sczFolder, FALSE, FALSE);
45 Assert::Equal(HRESULT_FROM_WIN32(ERROR_DIR_NOT_EMPTY), hr);
46
47 hr = DirEnsureDelete(sczSubFolder, FALSE, FALSE);
48 NativeAssert::Succeeded(hr, "Failed to delete single directory: %ls", sczSubFolder);
49
50 // Put the directory back and we'll test deleting tree.
51 hr = DirEnsureExists(sczSubFolder, NULL);
52 NativeAssert::Succeeded(hr, "Failed to create single directory: %ls", sczSubFolder);
53
54 hr = DirEnsureDelete(sczFolder, FALSE, TRUE);
55 NativeAssert::Succeeded(hr, "Failed to delete directory tree: %ls", sczFolder);
56
57 // Finally, try to create "C:\" which would normally fail, but we want success
58 hr = DirEnsureExists(L"C:\\", NULL);
59 NativeAssert::Succeeded(hr, "Failed to create C:\\");
60 }
61 finally
62 {
63 ReleaseStr(sczSubFolder);
64 ReleaseStr(sczFolder);
65 ReleaseStr(sczGuid);
66 ReleaseStr(sczCurrentDir);
67 }
68 }
69 };
70}
diff --git a/src/libs/dutil/test/DUtilUnitTest/FileUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/FileUtilTest.cpp
new file mode 100644
index 00000000..ac071ef2
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/FileUtilTest.cpp
@@ -0,0 +1,125 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9namespace DutilTests
10{
11 public ref class FileUtil
12 {
13 public:
14 [Fact(Skip="Skipped until we have a good way to reference ANSI.txt.")]
15 void FileUtilTest()
16 {
17 HRESULT hr = S_OK;
18 LPWSTR sczTempDir = NULL;
19 LPWSTR sczFileDir = NULL;
20
21 DutilInitialize(&DutilTestTraceError);
22
23 try
24 {
25 hr = PathExpand(&sczTempDir, L"%TEMP%\\FileUtilTest\\", PATH_EXPAND_ENVIRONMENT);
26 NativeAssert::Succeeded(hr, "Failed to get temp dir");
27
28 hr = PathExpand(&sczFileDir, L"%WIX_ROOT%\\examples\\data\\TextEncodings\\", PATH_EXPAND_ENVIRONMENT);
29 NativeAssert::Succeeded(hr, "Failed to get path to encodings file dir");
30
31 hr = DirEnsureExists(sczTempDir, NULL);
32 NativeAssert::Succeeded(hr, "Failed to ensure directory exists: {0}", sczTempDir);
33
34 TestFile(sczFileDir, sczTempDir, L"ANSI.txt", 32, FILE_ENCODING_UTF8);
35 // Big endian not supported today!
36 //TestFile(sczFileDir, L"UnicodeBENoBOM.txt", 34);
37 //TestFile(sczFileDir, L"UnicodeBEWithBOM.txt", 34);
38 TestFile(sczFileDir, sczTempDir, L"UnicodeLENoBOM.txt", 34, FILE_ENCODING_UTF16);
39 TestFile(sczFileDir, sczTempDir, L"UnicodeLEWithBOM.txt", 34, FILE_ENCODING_UTF16_WITH_BOM);
40 TestFile(sczFileDir, sczTempDir, L"UTF8WithSignature.txt", 34, FILE_ENCODING_UTF8_WITH_BOM);
41
42 hr = DirEnsureDelete(sczTempDir, TRUE, TRUE);
43 }
44 finally
45 {
46 ReleaseStr(sczTempDir);
47 ReleaseStr(sczFileDir);
48 DutilUninitialize();
49 }
50 }
51
52 private:
53 void TestFile(LPWSTR wzDir, LPCWSTR wzTempDir, LPWSTR wzFileName, size_t cbExpectedStringLength, FILE_ENCODING feExpectedEncoding)
54 {
55 HRESULT hr = S_OK;
56 LPWSTR sczFullPath = NULL;
57 LPWSTR sczContents = NULL;
58 LPWSTR sczOutputPath = NULL;
59 FILE_ENCODING feEncodingFound = FILE_ENCODING_UNSPECIFIED;
60 BYTE *pbFile1 = NULL;
61 DWORD cbFile1 = 0;
62 BYTE *pbFile2 = NULL;
63 DWORD cbFile2 = 0;
64 size_t cbActualStringLength = 0;
65
66 try
67 {
68 hr = PathConcat(wzDir, wzFileName, &sczFullPath);
69 NativeAssert::Succeeded(hr, "Failed to create path to test file: {0}", sczFullPath);
70
71 hr = FileToString(sczFullPath, &sczContents, &feEncodingFound);
72 hr = E_FAIL;
73 NativeAssert::Succeeded(hr, "Failed to read text from file: {0}", sczFullPath);
74
75 if (!sczContents)
76 {
77 hr = E_FAIL;
78 NativeAssert::Succeeded(hr, "FileToString() returned NULL for file: {0}", sczFullPath);
79 }
80
81 hr = ::StringCchLengthW(sczContents, STRSAFE_MAX_CCH, &cbActualStringLength);
82 NativeAssert::Succeeded(hr, "Failed to get length of text from file: {0}", sczFullPath);
83
84 if (cbActualStringLength != cbExpectedStringLength)
85 {
86 hr = E_FAIL;
87 ExitOnFailure(hr, "FileToString() returned wrong size for file: %ls (expected size %Iu, found size %Iu)", sczFullPath, cbExpectedStringLength, cbActualStringLength);
88 }
89
90 if (feEncodingFound != feExpectedEncoding)
91 {
92 hr = E_FAIL;
93 ExitOnFailure(hr, "FileToString() returned unexpected encoding type for file: %ls (expected type %u, found type %u)", sczFullPath, feExpectedEncoding, feEncodingFound);
94 }
95
96 hr = PathConcat(wzTempDir, wzFileName, &sczOutputPath);
97 NativeAssert::Succeeded(hr, "Failed to get output path");
98
99 hr = FileFromString(sczOutputPath, 0, sczContents, feExpectedEncoding);
100 NativeAssert::Succeeded(hr, "Failed to write contents of file back out to disk");
101
102 hr = FileRead(&pbFile1, &cbFile1, sczFullPath);
103 NativeAssert::Succeeded(hr, "Failed to read input file as binary");
104
105 hr = FileRead(&pbFile2, &cbFile2, sczOutputPath);
106 NativeAssert::Succeeded(hr, "Failed to read output file as binary");
107
108 if (cbFile1 != cbFile2 || 0 != memcmp(pbFile1, pbFile2, cbFile1))
109 {
110 hr = E_FAIL;
111 ExitOnFailure(hr, "Outputted file doesn't match input file: \"%ls\" and \"%ls\"", sczFullPath, sczOutputPath);
112 }
113 }
114 finally
115 {
116 ReleaseStr(sczOutputPath);
117 ReleaseStr(sczFullPath);
118 ReleaseStr(sczContents);
119 }
120
121 LExit:
122 return;
123 }
124 };
125}
diff --git a/src/libs/dutil/test/DUtilUnitTest/GuidUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/GuidUtilTest.cpp
new file mode 100644
index 00000000..a6e27a09
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/GuidUtilTest.cpp
@@ -0,0 +1,60 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9namespace DutilTests
10{
11 public ref class GuidUtil
12 {
13 public:
14 [Fact]
15 void GuidCreateTest()
16 {
17 HRESULT hr = S_OK;
18 WCHAR wzGuid1[GUID_STRING_LENGTH];
19 WCHAR wzGuid2[GUID_STRING_LENGTH];
20
21 hr = GuidFixedCreate(wzGuid1);
22 NativeAssert::Succeeded(hr, "Failed to create first guid.");
23 Guid firstGuid = Guid::Parse(gcnew String(wzGuid1));
24
25 hr = GuidFixedCreate(wzGuid2);
26 NativeAssert::Succeeded(hr, "Failed to create second guid.");
27 Guid secondGuid = Guid::Parse(gcnew String(wzGuid2));
28
29 NativeAssert::NotStringEqual(wzGuid1, wzGuid2);
30 NativeAssert::NotEqual(firstGuid, secondGuid);
31 }
32
33 [Fact]
34 void GuidCreateSczTest()
35 {
36 HRESULT hr = S_OK;
37 LPWSTR sczGuid1 = NULL;
38 LPWSTR sczGuid2 = NULL;
39
40 try
41 {
42 hr = GuidCreate(&sczGuid1);
43 NativeAssert::Succeeded(hr, "Failed to create first guid.");
44 Guid firstGuid = Guid::Parse(gcnew String(sczGuid1));
45
46 hr = GuidCreate(&sczGuid2);
47 NativeAssert::Succeeded(hr, "Failed to create second guid.");
48 Guid secondGuid = Guid::Parse(gcnew String(sczGuid2));
49
50 NativeAssert::NotStringEqual(sczGuid1, sczGuid2);
51 NativeAssert::NotEqual(firstGuid, secondGuid);
52 }
53 finally
54 {
55 ReleaseStr(sczGuid1);
56 ReleaseStr(sczGuid2);
57 }
58 }
59 };
60}
diff --git a/src/libs/dutil/test/DUtilUnitTest/IniUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/IniUtilTest.cpp
new file mode 100644
index 00000000..946f19c5
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/IniUtilTest.cpp
@@ -0,0 +1,345 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9typedef HRESULT (__clrcall *IniFormatParameters)(
10 INI_HANDLE
11 );
12
13namespace DutilTests
14{
15 public ref class IniUtil
16 {
17 public:
18 [Fact]
19 void IniUtilTest()
20 {
21 HRESULT hr = S_OK;
22 LPWSTR sczTempIniFilePath = NULL;
23 LPWSTR sczTempIniFileDir = NULL;
24 LPWSTR wzIniContents = L" PlainValue = \t Blah \r\n;CommentHere\r\n[Section1]\r\n ;Another Comment With = Equal Sign\r\nSection1ValueA=Foo\r\n\r\nSection1ValueB=Bar\r\n[Section2]\r\nSection2ValueA=Cha\r\nArray[0]=Arr\r\n";
25 LPWSTR wzScriptContents = L"setf ~PlainValue Blah\r\n;CommentHere\r\n\r\nsetf ~Section1\\Section1ValueA Foo\r\n\r\nsetf ~Section1\\Section1ValueB Bar\r\nsetf ~Section2\\Section2ValueA Cha\r\nsetf ~Section2\\Array[0] Arr\r\n";
26
27 DutilInitialize(&DutilTestTraceError);
28
29 try
30 {
31 hr = PathExpand(&sczTempIniFilePath, L"%TEMP%\\IniUtilTest\\Test.ini", PATH_EXPAND_ENVIRONMENT);
32 NativeAssert::Succeeded(hr, "Failed to get path to temp INI file");
33
34 hr = PathGetDirectory(sczTempIniFilePath, &sczTempIniFileDir);
35 NativeAssert::Succeeded(hr, "Failed to get directory to temp INI file");
36
37 hr = DirEnsureDelete(sczTempIniFileDir, TRUE, TRUE);
38 if (E_PATHNOTFOUND == hr)
39 {
40 hr = S_OK;
41 }
42 NativeAssert::Succeeded(hr, "Failed to delete IniUtilTest directory: {0}", sczTempIniFileDir);
43
44 hr = DirEnsureExists(sczTempIniFileDir, NULL);
45 NativeAssert::Succeeded(hr, "Failed to ensure temp directory exists: {0}", sczTempIniFileDir);
46
47 // Tests parsing, then modifying a regular INI file
48 TestReadThenWrite(sczTempIniFilePath, StandardIniFormat, wzIniContents);
49
50 // Tests programmatically creating from scratch, then parsing an INI file
51 TestWriteThenRead(sczTempIniFilePath, StandardIniFormat);
52
53 // Tests parsing, then modifying a regular INI file
54 TestReadThenWrite(sczTempIniFilePath, ScriptFormat, wzScriptContents);
55
56 // Tests programmatically creating from scratch, then parsing an INI file
57 TestWriteThenRead(sczTempIniFilePath, ScriptFormat);
58 }
59 finally
60 {
61 ReleaseStr(sczTempIniFilePath);
62 ReleaseStr(sczTempIniFileDir);
63 DutilUninitialize();
64 }
65 }
66
67 private:
68 void AssertValue(INI_HANDLE iniHandle, LPCWSTR wzValueName, LPCWSTR wzValue)
69 {
70 HRESULT hr = S_OK;
71 LPWSTR sczValue = NULL;
72
73 try
74 {
75 hr = IniGetValue(iniHandle, wzValueName, &sczValue);
76 NativeAssert::Succeeded(hr, "Failed to get ini value: {0}", wzValueName);
77
78 if (0 != wcscmp(sczValue, wzValue))
79 {
80 hr = E_FAIL;
81 ExitOnFailure(hr, "Expected to find value in INI: '%ls'='%ls' - but found value '%ls' instead", wzValueName, wzValue, sczValue);
82 }
83 }
84 finally
85 {
86 ReleaseStr(sczValue);
87 }
88
89 LExit:
90 return;
91 }
92
93 void AssertNoValue(INI_HANDLE iniHandle, LPCWSTR wzValueName)
94 {
95 HRESULT hr = S_OK;
96 LPWSTR sczValue = NULL;
97
98 try
99 {
100 hr = IniGetValue(iniHandle, wzValueName, &sczValue);
101 if (E_NOTFOUND != hr)
102 {
103 if (SUCCEEDED(hr))
104 {
105 hr = E_FAIL;
106 }
107 ExitOnFailure(hr, "INI value shouldn't have been found: %ls", wzValueName);
108 }
109 }
110 finally
111 {
112 ReleaseStr(sczValue);
113 }
114
115 LExit:
116 return;
117 }
118
119 static HRESULT StandardIniFormat(__inout INI_HANDLE iniHandle)
120 {
121 HRESULT hr = S_OK;
122
123 hr = IniSetOpenTag(iniHandle, L"[", L"]");
124 NativeAssert::Succeeded(hr, "Failed to set open tag settings on ini handle");
125
126 hr = IniSetValueStyle(iniHandle, NULL, L"=");
127 NativeAssert::Succeeded(hr, "Failed to set value separator setting on ini handle");
128
129 hr = IniSetCommentStyle(iniHandle, L";");
130 NativeAssert::Succeeded(hr, "Failed to set comment style setting on ini handle");
131
132 return hr;
133 }
134
135 static HRESULT ScriptFormat(__inout INI_HANDLE iniHandle)
136 {
137 HRESULT hr = S_OK;
138
139 hr = IniSetValueStyle(iniHandle, L"setf ~", L" ");
140 NativeAssert::Succeeded(hr, "Failed to set value separator setting on ini handle");
141
142 return hr;
143 }
144
145 void TestReadThenWrite(LPWSTR wzIniFilePath, IniFormatParameters SetFormat, LPCWSTR wzContents)
146 {
147 HRESULT hr = S_OK;
148 INI_HANDLE iniHandle = NULL;
149 INI_HANDLE iniHandle2 = NULL;
150 INI_VALUE *rgValues = NULL;
151 DWORD cValues = 0;
152
153 try
154 {
155 hr = FileWrite(wzIniFilePath, 0, reinterpret_cast<LPCBYTE>(wzContents), lstrlenW(wzContents) * sizeof(WCHAR), NULL);
156 NativeAssert::Succeeded(hr, "Failed to write out INI file");
157
158 hr = IniInitialize(&iniHandle);
159 NativeAssert::Succeeded(hr, "Failed to initialize INI object");
160
161 hr = SetFormat(iniHandle);
162 NativeAssert::Succeeded(hr, "Failed to set parameters for INI file");
163
164 hr = IniParse(iniHandle, wzIniFilePath, NULL);
165 NativeAssert::Succeeded(hr, "Failed to parse INI file");
166
167 hr = IniGetValueList(iniHandle, &rgValues, &cValues);
168 NativeAssert::Succeeded(hr, "Failed to get list of values in INI");
169
170 NativeAssert::Equal<DWORD>(5, cValues);
171
172 AssertValue(iniHandle, L"PlainValue", L"Blah");
173 AssertNoValue(iniHandle, L"PlainValue2");
174 AssertValue(iniHandle, L"Section1\\Section1ValueA", L"Foo");
175 AssertValue(iniHandle, L"Section1\\Section1ValueB", L"Bar");
176 AssertValue(iniHandle, L"Section2\\Section2ValueA", L"Cha");
177 AssertNoValue(iniHandle, L"Section1\\ValueDoesntExist");
178 AssertValue(iniHandle, L"Section2\\Array[0]", L"Arr");
179
180 hr = IniSetValue(iniHandle, L"PlainValue2", L"Blah2");
181 NativeAssert::Succeeded(hr, "Failed to set value in INI");
182
183 hr = IniSetValue(iniHandle, L"Section1\\CreatedValue", L"Woo");
184 NativeAssert::Succeeded(hr, "Failed to set value in INI");
185
186 hr = IniSetValue(iniHandle, L"Section2\\Array[0]", L"Arrmod");
187 NativeAssert::Succeeded(hr, "Failed to set value in INI");
188
189 hr = IniGetValueList(iniHandle, &rgValues, &cValues);
190 NativeAssert::Succeeded(hr, "Failed to get list of values in INI");
191
192 NativeAssert::Equal<DWORD>(7, cValues);
193
194 AssertValue(iniHandle, L"PlainValue", L"Blah");
195 AssertValue(iniHandle, L"PlainValue2", L"Blah2");
196 AssertValue(iniHandle, L"Section1\\Section1ValueA", L"Foo");
197 AssertValue(iniHandle, L"Section1\\Section1ValueB", L"Bar");
198 AssertValue(iniHandle, L"Section2\\Section2ValueA", L"Cha");
199 AssertNoValue(iniHandle, L"Section1\\ValueDoesntExist");
200 AssertValue(iniHandle, L"Section1\\CreatedValue", L"Woo");
201 AssertValue(iniHandle, L"Section2\\Array[0]", L"Arrmod");
202
203 // Try deleting a value as well
204 hr = IniSetValue(iniHandle, L"Section1\\Section1ValueB", NULL);
205 NativeAssert::Succeeded(hr, "Failed to kill value in INI");
206
207 hr = IniWriteFile(iniHandle, NULL, FILE_ENCODING_UNSPECIFIED);
208 NativeAssert::Succeeded(hr, "Failed to write ini file back out to disk");
209
210 ReleaseNullIni(iniHandle);
211 // Now re-parse the INI we just wrote and make sure it matches the values we expect
212 hr = IniInitialize(&iniHandle2);
213 NativeAssert::Succeeded(hr, "Failed to initialize INI object");
214
215 hr = SetFormat(iniHandle2);
216 NativeAssert::Succeeded(hr, "Failed to set parameters for INI file");
217
218 hr = IniParse(iniHandle2, wzIniFilePath, NULL);
219 NativeAssert::Succeeded(hr, "Failed to parse INI file");
220
221 hr = IniGetValueList(iniHandle2, &rgValues, &cValues);
222 NativeAssert::Succeeded(hr, "Failed to get list of values in INI");
223
224 NativeAssert::Equal<DWORD>(6, cValues);
225
226 AssertValue(iniHandle2, L"PlainValue", L"Blah");
227 AssertValue(iniHandle2, L"PlainValue2", L"Blah2");
228 AssertValue(iniHandle2, L"Section1\\Section1ValueA", L"Foo");
229 AssertNoValue(iniHandle2, L"Section1\\Section1ValueB");
230 AssertValue(iniHandle2, L"Section2\\Section2ValueA", L"Cha");
231 AssertNoValue(iniHandle2, L"Section1\\ValueDoesntExist");
232 AssertValue(iniHandle2, L"Section1\\CreatedValue", L"Woo");
233 AssertValue(iniHandle2, L"Section2\\Array[0]", L"Arrmod");
234 }
235 finally
236 {
237 ReleaseIni(iniHandle);
238 ReleaseIni(iniHandle2);
239 }
240 }
241
242 void TestWriteThenRead(LPWSTR wzIniFilePath, IniFormatParameters SetFormat)
243 {
244 HRESULT hr = S_OK;
245 INI_HANDLE iniHandle = NULL;
246 INI_HANDLE iniHandle2 = NULL;
247 INI_VALUE *rgValues = NULL;
248 DWORD cValues = 0;
249
250 try
251 {
252 hr = FileEnsureDelete(wzIniFilePath);
253 NativeAssert::Succeeded(hr, "Failed to ensure file is deleted");
254
255 hr = IniInitialize(&iniHandle);
256 NativeAssert::Succeeded(hr, "Failed to initialize INI object");
257
258 hr = SetFormat(iniHandle);
259 NativeAssert::Succeeded(hr, "Failed to set parameters for INI file");
260
261 hr = IniGetValueList(iniHandle, &rgValues, &cValues);
262 NativeAssert::Succeeded(hr, "Failed to get list of values in INI");
263
264 NativeAssert::Equal<DWORD>(0, cValues);
265
266 hr = IniSetValue(iniHandle, L"Value1", L"BlahTypo");
267 NativeAssert::Succeeded(hr, "Failed to set value in INI");
268
269 hr = IniSetValue(iniHandle, L"Value2", L"Blah2");
270 NativeAssert::Succeeded(hr, "Failed to set value in INI");
271
272 hr = IniSetValue(iniHandle, L"Section1\\Value1", L"Section1Value1");
273 NativeAssert::Succeeded(hr, "Failed to set value in INI");
274
275 hr = IniSetValue(iniHandle, L"Section1\\Value2", L"Section1Value2");
276 NativeAssert::Succeeded(hr, "Failed to set value in INI");
277
278 hr = IniSetValue(iniHandle, L"Section2\\Value1", L"Section2Value1");
279 NativeAssert::Succeeded(hr, "Failed to set value in INI");
280
281 hr = IniSetValue(iniHandle, L"Section2\\Array[0]", L"Arr");
282 NativeAssert::Succeeded(hr, "Failed to set value in INI");
283
284 hr = IniSetValue(iniHandle, L"Value3", L"Blah3");
285 NativeAssert::Succeeded(hr, "Failed to set value in INI");
286
287 hr = IniSetValue(iniHandle, L"Value4", L"Blah4");
288 NativeAssert::Succeeded(hr, "Failed to set value in INI");
289
290 hr = IniSetValue(iniHandle, L"Value4", NULL);
291 NativeAssert::Succeeded(hr, "Failed to set value in INI");
292
293 hr = IniSetValue(iniHandle, L"Value1", L"Blah1");
294 NativeAssert::Succeeded(hr, "Failed to set value in INI");
295
296 hr = IniGetValueList(iniHandle, &rgValues, &cValues);
297 NativeAssert::Succeeded(hr, "Failed to get list of values in INI");
298
299 NativeAssert::Equal<DWORD>(8, cValues);
300
301 AssertValue(iniHandle, L"Value1", L"Blah1");
302 AssertValue(iniHandle, L"Value2", L"Blah2");
303 AssertValue(iniHandle, L"Value3", L"Blah3");
304 AssertNoValue(iniHandle, L"Value4");
305 AssertValue(iniHandle, L"Section1\\Value1", L"Section1Value1");
306 AssertValue(iniHandle, L"Section1\\Value2", L"Section1Value2");
307 AssertValue(iniHandle, L"Section2\\Value1", L"Section2Value1");
308 AssertValue(iniHandle, L"Section2\\Array[0]", L"Arr");
309
310 hr = IniWriteFile(iniHandle, wzIniFilePath, FILE_ENCODING_UNSPECIFIED);
311 NativeAssert::Succeeded(hr, "Failed to write ini file back out to disk");
312
313 ReleaseNullIni(iniHandle);
314 // Now re-parse the INI we just wrote and make sure it matches the values we expect
315 hr = IniInitialize(&iniHandle2);
316 NativeAssert::Succeeded(hr, "Failed to initialize INI object");
317
318 hr = SetFormat(iniHandle2);
319 NativeAssert::Succeeded(hr, "Failed to set parameters for INI file");
320
321 hr = IniParse(iniHandle2, wzIniFilePath, NULL);
322 NativeAssert::Succeeded(hr, "Failed to parse INI file");
323
324 hr = IniGetValueList(iniHandle2, &rgValues, &cValues);
325 NativeAssert::Succeeded(hr, "Failed to get list of values in INI");
326
327 NativeAssert::Equal<DWORD>(7, cValues);
328
329 AssertValue(iniHandle2, L"Value1", L"Blah1");
330 AssertValue(iniHandle2, L"Value2", L"Blah2");
331 AssertValue(iniHandle2, L"Value3", L"Blah3");
332 AssertNoValue(iniHandle2, L"Value4");
333 AssertValue(iniHandle2, L"Section1\\Value1", L"Section1Value1");
334 AssertValue(iniHandle2, L"Section1\\Value2", L"Section1Value2");
335 AssertValue(iniHandle2, L"Section2\\Value1", L"Section2Value1");
336 AssertValue(iniHandle2, L"Section2\\Array[0]", L"Arr");
337 }
338 finally
339 {
340 ReleaseIni(iniHandle);
341 ReleaseIni(iniHandle2);
342 }
343 }
344 };
345}
diff --git a/src/libs/dutil/test/DUtilUnitTest/MemUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/MemUtilTest.cpp
new file mode 100644
index 00000000..09692bfb
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/MemUtilTest.cpp
@@ -0,0 +1,505 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9namespace DutilTests
10{
11 struct ArrayValue
12 {
13 DWORD dwNum;
14 void *pvNull1;
15 LPWSTR sczString;
16 void *pvNull2;
17 };
18
19 public ref class MemUtil
20 {
21 public:
22 [Fact]
23 void MemUtilAppendTest()
24 {
25 HRESULT hr = S_OK;
26 DWORD dwSize;
27 ArrayValue *rgValues = NULL;
28 DWORD cValues = 0;
29
30 DutilInitialize(&DutilTestTraceError);
31
32 try
33 {
34 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
35 NativeAssert::Succeeded(hr, "Failed to grow array size to 1");
36 ++cValues;
37 SetItem(rgValues + 0, 0);
38
39 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
40 NativeAssert::Succeeded(hr, "Failed to grow array size to 2");
41 ++cValues;
42 SetItem(rgValues + 1, 1);
43
44 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
45 NativeAssert::Succeeded(hr, "Failed to grow array size to 3");
46 ++cValues;
47 SetItem(rgValues + 2, 2);
48
49 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
50 NativeAssert::Succeeded(hr, "Failed to grow array size to 4");
51 ++cValues;
52 SetItem(rgValues + 3, 3);
53
54 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
55 NativeAssert::Succeeded(hr, "Failed to grow array size to 5");
56 ++cValues;
57 SetItem(rgValues + 4, 4);
58
59 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
60 NativeAssert::Succeeded(hr, "Failed to grow array size to 6");
61 ++cValues;
62 SetItem(rgValues + 5, 5);
63
64 // OK, we used growth size 5, so let's try ensuring we have space for 6 (5 + first item) items
65 // and make sure it doesn't grow since we already have enough space
66 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues, sizeof(ArrayValue), 5);
67 NativeAssert::Succeeded(hr, "Failed to ensure array size matches what it should already be");
68 dwSize = MemSize(rgValues);
69 if (dwSize != 6 * sizeof(ArrayValue))
70 {
71 hr = E_FAIL;
72 ExitOnFailure(hr, "MemEnsureArraySize is growing an array that is already big enough!");
73 }
74
75 for (DWORD i = 0; i < cValues; ++i)
76 {
77 CheckItem(rgValues + i, i);
78 }
79
80 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
81 NativeAssert::Succeeded(hr, "Failed to grow array size to 7");
82 ++cValues;
83 SetItem(rgValues + 6, 6);
84
85 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
86 NativeAssert::Succeeded(hr, "Failed to grow array size to 7");
87 ++cValues;
88 SetItem(rgValues + 7, 7);
89
90 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
91 NativeAssert::Succeeded(hr, "Failed to grow array size to 7");
92 ++cValues;
93 SetItem(rgValues + 8, 8);
94
95 for (DWORD i = 0; i < cValues; ++i)
96 {
97 CheckItem(rgValues + i, i);
98 }
99 }
100 finally
101 {
102 ReleaseMem(rgValues);
103 }
104
105 LExit:
106 DutilUninitialize();
107 }
108
109 [Fact]
110 void MemUtilInsertTest()
111 {
112 HRESULT hr = S_OK;
113 ArrayValue *rgValues = NULL;
114 DWORD cValues = 0;
115
116 DutilInitialize(&DutilTestTraceError);
117
118 try
119 {
120 hr = MemInsertIntoArray(reinterpret_cast<LPVOID*>(&rgValues), 0, 1, cValues + 1, sizeof(ArrayValue), 5);
121 NativeAssert::Succeeded(hr, "Failed to insert into beginning of empty array");
122 ++cValues;
123 CheckNullItem(rgValues + 0);
124 SetItem(rgValues + 0, 5);
125
126 hr = MemInsertIntoArray(reinterpret_cast<LPVOID*>(&rgValues), 1, 1, cValues + 1, sizeof(ArrayValue), 5);
127 NativeAssert::Succeeded(hr, "Failed to insert at end of array");
128 ++cValues;
129 CheckNullItem(rgValues + 1);
130 SetItem(rgValues + 1, 6);
131
132 hr = MemInsertIntoArray(reinterpret_cast<LPVOID*>(&rgValues), 0, 1, cValues + 1, sizeof(ArrayValue), 5);
133 NativeAssert::Succeeded(hr, "Failed to insert into beginning of array");
134 ++cValues;
135 CheckNullItem(rgValues + 0);
136 SetItem(rgValues + 0, 4);
137
138 hr = MemInsertIntoArray(reinterpret_cast<LPVOID*>(&rgValues), 0, 1, cValues + 1, sizeof(ArrayValue), 5);
139 NativeAssert::Succeeded(hr, "Failed to insert into beginning of array");
140 ++cValues;
141 CheckNullItem(rgValues + 0);
142 SetItem(rgValues + 0, 3);
143
144 hr = MemInsertIntoArray(reinterpret_cast<LPVOID*>(&rgValues), 0, 1, cValues + 1, sizeof(ArrayValue), 5);
145 NativeAssert::Succeeded(hr, "Failed to insert into beginning of array");
146 ++cValues;
147 CheckNullItem(rgValues + 0);
148 SetItem(rgValues + 0, 1);
149
150 hr = MemInsertIntoArray(reinterpret_cast<LPVOID*>(&rgValues), 1, 1, cValues + 1, sizeof(ArrayValue), 5);
151 NativeAssert::Succeeded(hr, "Failed to insert into beginning of array");
152 ++cValues;
153 CheckNullItem(rgValues + 1);
154 SetItem(rgValues + 1, 2);
155
156 hr = MemInsertIntoArray(reinterpret_cast<LPVOID*>(&rgValues), 0, 1, cValues + 1, sizeof(ArrayValue), 5);
157 NativeAssert::Succeeded(hr, "Failed to insert into beginning of array");
158 ++cValues;
159 CheckNullItem(rgValues + 0);
160 SetItem(rgValues + 0, 0);
161
162 for (DWORD i = 0; i < cValues; ++i)
163 {
164 CheckItem(rgValues + i, i);
165 }
166
167 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
168 NativeAssert::Succeeded(hr, "Failed to grow array size to 7");
169 ++cValues;
170 CheckNullItem(rgValues + 7);
171 SetItem(rgValues + 7, 7);
172
173 hr = MemInsertIntoArray(reinterpret_cast<LPVOID*>(&rgValues), 8, 1, cValues + 1, sizeof(ArrayValue), 5);
174 NativeAssert::Succeeded(hr, "Failed to insert into beginning of array");
175 ++cValues;
176 CheckNullItem(rgValues + 8);
177 SetItem(rgValues + 8, 8);
178
179 for (DWORD i = 0; i < cValues; ++i)
180 {
181 CheckItem(rgValues + i, i);
182 }
183 }
184 finally
185 {
186 ReleaseMem(rgValues);
187 DutilUninitialize();
188 }
189 }
190
191 [Fact]
192 void MemUtilRemovePreserveOrderTest()
193 {
194 HRESULT hr = S_OK;
195 ArrayValue *rgValues = NULL;
196 DWORD cValues = 0;
197
198 DutilInitialize(&DutilTestTraceError);
199
200 try
201 {
202 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), 10, sizeof(ArrayValue), 10);
203 NativeAssert::Succeeded(hr, "Failed to grow array size to 10");
204
205 cValues = 10;
206 for (DWORD i = 0; i < cValues; ++i)
207 {
208 SetItem(rgValues + i, i);
209 }
210
211 // Remove last item
212 MemRemoveFromArray(rgValues, 9, 1, cValues, sizeof(ArrayValue), TRUE);
213 --cValues;
214
215 for (DWORD i = 0; i < cValues; ++i)
216 {
217 CheckItem(rgValues + i, i);
218 }
219
220 // Remove last two items
221 MemRemoveFromArray(rgValues, 7, 2, cValues, sizeof(ArrayValue), TRUE);
222 cValues -= 2;
223
224 for (DWORD i = 0; i < cValues; ++i)
225 {
226 CheckItem(rgValues + i, i);
227 }
228
229 // Remove first item
230 MemRemoveFromArray(rgValues, 0, 1, cValues, sizeof(ArrayValue), TRUE);
231 --cValues;
232
233 for (DWORD i = 0; i < cValues; ++i)
234 {
235 CheckItem(rgValues + i, i + 1);
236 }
237
238
239 // Remove first two items
240 MemRemoveFromArray(rgValues, 0, 2, cValues, sizeof(ArrayValue), TRUE);
241 cValues -= 2;
242
243 for (DWORD i = 0; i < cValues; ++i)
244 {
245 CheckItem(rgValues + i, i + 3);
246 }
247
248 // Remove middle two items
249 MemRemoveFromArray(rgValues, 1, 2, cValues, sizeof(ArrayValue), TRUE);
250 cValues -= 2;
251
252 CheckItem(rgValues, 3);
253 CheckItem(rgValues + 1, 6);
254
255 // Remove last 2 items to ensure we don't crash
256 MemRemoveFromArray(rgValues, 0, 2, cValues, sizeof(ArrayValue), TRUE);
257 cValues -= 2;
258 }
259 finally
260 {
261 ReleaseMem(rgValues);
262 DutilUninitialize();
263 }
264 }
265
266 [Fact]
267 void MemUtilRemoveFastTest()
268 {
269 HRESULT hr = S_OK;
270 ArrayValue *rgValues = NULL;
271 DWORD cValues = 0;
272
273 DutilInitialize(&DutilTestTraceError);
274
275 try
276 {
277 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), 10, sizeof(ArrayValue), 10);
278 NativeAssert::Succeeded(hr, "Failed to grow array size to 10");
279
280 cValues = 10;
281 for (DWORD i = 0; i < cValues; ++i)
282 {
283 SetItem(rgValues + i, i);
284 }
285
286 // Remove last item
287 MemRemoveFromArray(rgValues, 9, 1, cValues, sizeof(ArrayValue), FALSE);
288 --cValues;
289
290 for (DWORD i = 0; i < cValues; ++i)
291 {
292 CheckItem(rgValues + i, i);
293 }
294
295 // Remove last two items
296 MemRemoveFromArray(rgValues, 7, 2, cValues, sizeof(ArrayValue), FALSE);
297 cValues -= 2;
298
299 for (DWORD i = 0; i < cValues; ++i)
300 {
301 CheckItem(rgValues + i, i);
302 }
303
304 // Remove first item
305 MemRemoveFromArray(rgValues, 0, 1, cValues, sizeof(ArrayValue), FALSE);
306 --cValues;
307
308 CheckItem(rgValues, 6);
309 CheckItem(rgValues + 1, 1);
310 CheckItem(rgValues + 2, 2);
311 CheckItem(rgValues + 3, 3);
312 CheckItem(rgValues + 4, 4);
313 CheckItem(rgValues + 5, 5);
314
315 // Remove first two items
316 MemRemoveFromArray(rgValues, 0, 2, cValues, sizeof(ArrayValue), FALSE);
317 cValues -= 2;
318
319 CheckItem(rgValues, 4);
320 CheckItem(rgValues + 1, 5);
321 CheckItem(rgValues + 2, 2);
322 CheckItem(rgValues + 3, 3);
323
324
325 // Remove middle two items
326 MemRemoveFromArray(rgValues, 1, 2, cValues, sizeof(ArrayValue), FALSE);
327 cValues -= 2;
328
329 CheckItem(rgValues, 4);
330 CheckItem(rgValues + 1, 3);
331
332 // Remove last 2 items to ensure we don't crash
333 MemRemoveFromArray(rgValues, 0, 2, cValues, sizeof(ArrayValue), FALSE);
334 cValues -= 2;
335 }
336 finally
337 {
338 ReleaseMem(rgValues);
339 DutilUninitialize();
340 }
341 }
342
343 [Fact]
344 void MemUtilSwapTest()
345 {
346 HRESULT hr = S_OK;
347 ArrayValue *rgValues = NULL;
348 DWORD cValues = 0;
349
350 DutilInitialize(&DutilTestTraceError);
351
352 try
353 {
354 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), 10, sizeof(ArrayValue), 10);
355 NativeAssert::Succeeded(hr, "Failed to grow array size to 10");
356
357 cValues = 10;
358 for (DWORD i = 0; i < cValues; ++i)
359 {
360 SetItem(rgValues + i, i);
361 }
362
363 // Swap first two
364 MemArraySwapItems(rgValues, 0, 1, sizeof(ArrayValue));
365 --cValues;
366
367 CheckItem(rgValues, 1);
368 CheckItem(rgValues + 1, 0);
369 for (DWORD i = 2; i < cValues; ++i)
370 {
371 CheckItem(rgValues + i, i);
372 }
373
374 // Swap them back
375 MemArraySwapItems(rgValues, 0, 1, sizeof(ArrayValue));
376 --cValues;
377
378 for (DWORD i = 0; i < cValues; ++i)
379 {
380 CheckItem(rgValues + i, i);
381 }
382
383 // Swap first and last items (index 0 and 9)
384 MemArraySwapItems(rgValues, 0, 9, sizeof(ArrayValue));
385 --cValues;
386
387 CheckItem(rgValues, 9);
388 CheckItem(rgValues + 9, 0);
389 for (DWORD i = 1; i < cValues - 1; ++i)
390 {
391 CheckItem(rgValues + i, i);
392 }
393
394 // Swap index 1 and 8
395 MemArraySwapItems(rgValues, 1, 8, sizeof(ArrayValue));
396 --cValues;
397
398 CheckItem(rgValues, 9);
399 CheckItem(rgValues + 1, 8);
400 CheckItem(rgValues + 8, 1);
401 CheckItem(rgValues + 9, 0);
402 for (DWORD i = 2; i < cValues - 2; ++i)
403 {
404 CheckItem(rgValues + i, i);
405 }
406
407 // Swap index 2 and 7
408 MemArraySwapItems(rgValues, 2, 7, sizeof(ArrayValue));
409 --cValues;
410
411 CheckItem(rgValues, 9);
412 CheckItem(rgValues + 1, 8);
413 CheckItem(rgValues + 2, 7);
414 CheckItem(rgValues + 7, 2);
415 CheckItem(rgValues + 8, 1);
416 CheckItem(rgValues + 9, 0);
417 for (DWORD i = 3; i < cValues - 3; ++i)
418 {
419 CheckItem(rgValues + i, i);
420 }
421
422 // Swap index 0 and 1
423 MemArraySwapItems(rgValues, 0, 1, sizeof(ArrayValue));
424 --cValues;
425
426 CheckItem(rgValues, 8);
427 CheckItem(rgValues + 1, 9);
428 CheckItem(rgValues + 2, 7);
429 CheckItem(rgValues + 7, 2);
430 CheckItem(rgValues + 8, 1);
431 CheckItem(rgValues + 9, 0);
432 for (DWORD i = 3; i < cValues - 3; ++i)
433 {
434 CheckItem(rgValues + i, i);
435 }
436 }
437 finally
438 {
439 ReleaseMem(rgValues);
440 DutilUninitialize();
441 }
442 }
443
444 private:
445 void SetItem(ArrayValue *pValue, DWORD dwValue)
446 {
447 HRESULT hr = S_OK;
448 pValue->dwNum = dwValue;
449
450 hr = StrAllocFormatted(&pValue->sczString, L"%u", dwValue);
451 NativeAssert::Succeeded(hr, "Failed to allocate string");
452 }
453
454 void CheckItem(ArrayValue *pValue, DWORD dwValue)
455 {
456 HRESULT hr = S_OK;
457 LPWSTR sczTemp = NULL;
458
459 try
460 {
461 NativeAssert::Equal(dwValue, pValue->dwNum);
462
463 hr = StrAllocFormatted(&sczTemp, L"%u", dwValue);
464 NativeAssert::Succeeded(hr, "Failed to allocate temp string");
465
466 NativeAssert::StringEqual(sczTemp, pValue->sczString, TRUE);
467
468 if (pValue->pvNull1 || pValue->pvNull2)
469 {
470 hr = E_FAIL;
471 ExitOnFailure(hr, "One of the expected NULL values wasn't NULL!");
472 }
473 }
474 finally
475 {
476 ReleaseStr(sczTemp);
477 }
478
479 LExit:
480 return;
481 }
482
483 void CheckNullItem(ArrayValue *pValue)
484 {
485 HRESULT hr = S_OK;
486
487 NativeAssert::Equal<DWORD>(0, pValue->dwNum);
488
489 if (pValue->sczString)
490 {
491 hr = E_FAIL;
492 ExitOnFailure(hr, "Item found isn't NULL!");
493 }
494
495 if (pValue->pvNull1 || pValue->pvNull2)
496 {
497 hr = E_FAIL;
498 ExitOnFailure(hr, "One of the expected NULL values wasn't NULL!");
499 }
500
501 LExit:
502 return;
503 }
504 };
505}
diff --git a/src/libs/dutil/test/DUtilUnitTest/MonUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/MonUtilTest.cpp
new file mode 100644
index 00000000..273f2eb6
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/MonUtilTest.cpp
@@ -0,0 +1,487 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4#undef RemoveDirectory
5
6using namespace System;
7using namespace System::Collections::Generic;
8using namespace System::Runtime::InteropServices;
9using namespace Xunit;
10using namespace WixBuildTools::TestSupport;
11
12namespace DutilTests
13{
14 const int PREWAIT = 20;
15 const int POSTWAIT = 480;
16 const int FULLWAIT = 500;
17 const int SILENCEPERIOD = 100;
18
19 struct RegKey
20 {
21 HRESULT hr;
22 HKEY hkRoot;
23 LPCWSTR wzSubKey;
24 REG_KEY_BITNESS kbKeyBitness;
25 BOOL fRecursive;
26 };
27 struct Directory
28 {
29 HRESULT hr;
30 LPCWSTR wzPath;
31 BOOL fRecursive;
32 };
33 struct Results
34 {
35 RegKey *rgRegKeys;
36 DWORD cRegKeys;
37 Directory *rgDirectories;
38 DWORD cDirectories;
39 };
40
41 public delegate void MonGeneralDelegate(HRESULT, LPVOID);
42
43 public delegate void MonDriveStatusDelegate(WCHAR, BOOL, LPVOID);
44
45 public delegate void MonDirectoryDelegate(HRESULT, LPCWSTR, BOOL, LPVOID, LPVOID);
46
47 public delegate void MonRegKeyDelegate(HRESULT, HKEY, LPCWSTR, REG_KEY_BITNESS, BOOL, LPVOID, LPVOID);
48
49 static void MonGeneral(
50 __in HRESULT /*hrResult*/,
51 __in_opt LPVOID /*pvContext*/
52 )
53 {
54 Assert::True(false);
55 }
56
57 static void MonDriveStatus(
58 __in WCHAR /*chDrive*/,
59 __in BOOL /*fArriving*/,
60 __in_opt LPVOID /*pvContext*/
61 )
62 {
63 }
64
65 static void MonDirectory(
66 __in HRESULT hrResult,
67 __in_z LPCWSTR wzPath,
68 __in_z BOOL fRecursive,
69 __in_opt LPVOID pvContext,
70 __in_opt LPVOID pvDirectoryContext
71 )
72 {
73 Assert::Equal(S_OK, hrResult);
74 Assert::Equal<DWORD_PTR>(0, reinterpret_cast<DWORD_PTR>(pvDirectoryContext));
75
76 HRESULT hr = S_OK;
77 Results *pResults = reinterpret_cast<Results *>(pvContext);
78
79 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&pResults->rgDirectories), pResults->cDirectories + 1, sizeof(Directory), 5);
80 NativeAssert::ValidReturnCode(hr, S_OK);
81 ++pResults->cDirectories;
82
83 pResults->rgDirectories[pResults->cDirectories - 1].hr = hrResult;
84 pResults->rgDirectories[pResults->cDirectories - 1].wzPath = wzPath;
85 pResults->rgDirectories[pResults->cDirectories - 1].fRecursive = fRecursive;
86 }
87
88 static void MonRegKey(
89 __in HRESULT hrResult,
90 __in HKEY hkRoot,
91 __in_z LPCWSTR wzSubKey,
92 __in REG_KEY_BITNESS kbKeyBitness,
93 __in_z BOOL fRecursive,
94 __in_opt LPVOID pvContext,
95 __in_opt LPVOID pvRegKeyContext
96 )
97 {
98 Assert::Equal<HRESULT>(S_OK, hrResult);
99 Assert::Equal<DWORD_PTR>(0, reinterpret_cast<DWORD_PTR>(pvRegKeyContext));
100
101 HRESULT hr = S_OK;
102 Results *pResults = reinterpret_cast<Results *>(pvContext);
103
104 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&pResults->rgRegKeys), pResults->cRegKeys + 1, sizeof(RegKey), 5);
105 NativeAssert::ValidReturnCode(hr, S_OK);
106 ++pResults->cRegKeys;
107
108 pResults->rgRegKeys[pResults->cRegKeys - 1].hr = hrResult;
109 pResults->rgRegKeys[pResults->cRegKeys - 1].hkRoot = hkRoot;
110 pResults->rgRegKeys[pResults->cRegKeys - 1].wzSubKey = wzSubKey;
111 pResults->rgRegKeys[pResults->cRegKeys - 1].kbKeyBitness = kbKeyBitness;
112 pResults->rgRegKeys[pResults->cRegKeys - 1].fRecursive = fRecursive;
113 }
114
115 public ref class MonUtil
116 {
117 public:
118 void ClearResults(Results *pResults)
119 {
120 ReleaseNullMem(pResults->rgDirectories);
121 pResults->cDirectories = 0;
122 ReleaseNullMem(pResults->rgRegKeys);
123 pResults->cRegKeys = 0;
124 }
125
126 void RemoveDirectory(LPCWSTR wzPath)
127 {
128 DWORD dwRetryCount = 0;
129 const DWORD c_dwMaxRetryCount = 100;
130 const DWORD c_dwRetryInterval = 50;
131
132 HRESULT hr = DirEnsureDelete(wzPath, TRUE, TRUE);
133
134 // Monitoring a directory opens a handle to that directory, which means delete requests for that directory will succeed
135 // (and deletion will be "pending" until our monitor handle is closed)
136 // but deletion of the directory containing that directory cannot complete until the handle is closed. This means DirEnsureDelete()
137 // can sometimes encounter HRESULT_FROM_WIN32(ERROR_DIR_NOT_EMPTY) failures, which just means it needs to retry a bit later
138 // (after the waiter thread wakes up, it will release the handle)
139 while (HRESULT_FROM_WIN32(ERROR_DIR_NOT_EMPTY) == hr && c_dwMaxRetryCount > dwRetryCount)
140 {
141 ::Sleep(c_dwRetryInterval);
142 ++dwRetryCount;
143 hr = DirEnsureDelete(wzPath, TRUE, TRUE);
144 }
145
146 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE, E_PATHNOTFOUND);
147 }
148
149 void TestDirectory(MON_HANDLE handle, Results *pResults)
150 {
151 HRESULT hr = S_OK;
152 LPWSTR sczShallowPath = NULL;
153 LPWSTR sczParentPath = NULL;
154 LPWSTR sczDeepPath = NULL;
155 LPWSTR sczChildPath = NULL;
156 LPWSTR sczChildFilePath = NULL;
157
158 try
159 {
160 hr = PathExpand(&sczShallowPath, L"%TEMP%\\MonUtilTest\\", PATH_EXPAND_ENVIRONMENT);
161 NativeAssert::ValidReturnCode(hr, S_OK);
162
163 hr = PathExpand(&sczParentPath, L"%TEMP%\\MonUtilTest\\sub\\folder\\that\\might\\not\\", PATH_EXPAND_ENVIRONMENT);
164 NativeAssert::ValidReturnCode(hr, S_OK);
165
166 hr = PathExpand(&sczDeepPath, L"%TEMP%\\MonUtilTest\\sub\\folder\\that\\might\\not\\exist\\", PATH_EXPAND_ENVIRONMENT);
167 NativeAssert::ValidReturnCode(hr, S_OK);
168
169 hr = PathExpand(&sczChildPath, L"%TEMP%\\MonUtilTest\\sub\\folder\\that\\might\\not\\exist\\some\\sub\\folder\\", PATH_EXPAND_ENVIRONMENT);
170 NativeAssert::ValidReturnCode(hr, S_OK);
171
172 hr = PathExpand(&sczChildFilePath, L"%TEMP%\\MonUtilTest\\sub\\folder\\that\\might\\not\\exist\\some\\sub\\folder\\file.txt", PATH_EXPAND_ENVIRONMENT);
173 NativeAssert::ValidReturnCode(hr, S_OK);
174
175 RemoveDirectory(sczShallowPath);
176
177 hr = MonAddDirectory(handle, sczDeepPath, TRUE, SILENCEPERIOD, NULL);
178 NativeAssert::ValidReturnCode(hr, S_OK);
179
180 hr = DirEnsureExists(sczParentPath, NULL);
181 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
182 // Make sure creating the parent directory does nothing, even after silence period
183 ::Sleep(FULLWAIT);
184 Assert::Equal<DWORD>(0, pResults->cDirectories);
185
186 // Now create the target path, no notification until after the silence period
187 hr = DirEnsureExists(sczDeepPath, NULL);
188 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
189 ::Sleep(PREWAIT);
190 Assert::Equal<DWORD>(0, pResults->cDirectories);
191
192 // Now after the full silence period, it should have triggered
193 ::Sleep(POSTWAIT);
194 Assert::Equal<DWORD>(1, pResults->cDirectories);
195 NativeAssert::ValidReturnCode(pResults->rgDirectories[0].hr, S_OK);
196
197 // Now delete the directory, along with a ton of parents. This verifies MonUtil will keep watching the closest parent that still exists.
198 RemoveDirectory(sczShallowPath);
199
200 ::Sleep(FULLWAIT);
201 Assert::Equal<DWORD>(2, pResults->cDirectories);
202 NativeAssert::ValidReturnCode(pResults->rgDirectories[1].hr, S_OK);
203
204 // Create the parent directory again, still should be nothing even after full silence period
205 hr = DirEnsureExists(sczParentPath, NULL);
206 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
207 ::Sleep(FULLWAIT);
208 Assert::Equal<DWORD>(2, pResults->cDirectories);
209
210 hr = DirEnsureExists(sczChildPath, NULL);
211 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
212 ::Sleep(PREWAIT);
213 Assert::Equal<DWORD>(2, pResults->cDirectories);
214
215 ::Sleep(POSTWAIT);
216 Assert::Equal<DWORD>(3, pResults->cDirectories);
217 NativeAssert::ValidReturnCode(pResults->rgDirectories[2].hr, S_OK);
218
219 // Write a file to a deep child subfolder, and make sure it's detected
220 hr = FileFromString(sczChildFilePath, 0, L"contents", FILE_ENCODING_UTF16_WITH_BOM);
221 NativeAssert::ValidReturnCode(hr, S_OK);
222 ::Sleep(PREWAIT);
223 Assert::Equal<DWORD>(3, pResults->cDirectories);
224
225 ::Sleep(POSTWAIT);
226 Assert::Equal<DWORD>(4, pResults->cDirectories);
227 NativeAssert::ValidReturnCode(pResults->rgDirectories[2].hr, S_OK);
228
229 RemoveDirectory(sczParentPath);
230
231 ::Sleep(FULLWAIT);
232 Assert::Equal<DWORD>(5, pResults->cDirectories);
233 NativeAssert::ValidReturnCode(pResults->rgDirectories[3].hr, S_OK);
234
235 // Now remove the directory from the list of things to monitor, and confirm changes are no longer tracked
236 hr = MonRemoveDirectory(handle, sczDeepPath, TRUE);
237 NativeAssert::ValidReturnCode(hr, S_OK);
238 ::Sleep(PREWAIT);
239
240 hr = DirEnsureExists(sczDeepPath, NULL);
241 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
242 ::Sleep(FULLWAIT);
243 Assert::Equal<DWORD>(5, pResults->cDirectories);
244 NativeAssert::ValidReturnCode(pResults->rgDirectories[3].hr, S_OK);
245
246 // Finally, add it back so we can test multiple things to monitor at once
247 hr = MonAddDirectory(handle, sczDeepPath, TRUE, SILENCEPERIOD, NULL);
248 NativeAssert::ValidReturnCode(hr, S_OK);
249 }
250 finally
251 {
252 ReleaseStr(sczShallowPath);
253 ReleaseStr(sczDeepPath);
254 ReleaseStr(sczParentPath);
255 }
256 }
257
258 void TestRegKey(MON_HANDLE handle, Results *pResults)
259 {
260 HRESULT hr = S_OK;
261 LPCWSTR wzShallowRegKey = L"Software\\MonUtilTest\\";
262 LPCWSTR wzParentRegKey = L"Software\\MonUtilTest\\sub\\folder\\that\\might\\not\\";
263 LPCWSTR wzDeepRegKey = L"Software\\MonUtilTest\\sub\\folder\\that\\might\\not\\exist\\";
264 LPCWSTR wzChildRegKey = L"Software\\MonUtilTest\\sub\\folder\\that\\might\\not\\exist\\some\\sub\\folder\\";
265 HKEY hk = NULL;
266
267 try
268 {
269 hr = RegDelete(HKEY_CURRENT_USER, wzShallowRegKey, REG_KEY_32BIT, TRUE);
270 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE, E_PATHNOTFOUND);
271
272 hr = MonAddRegKey(handle, HKEY_CURRENT_USER, wzDeepRegKey, REG_KEY_DEFAULT, TRUE, SILENCEPERIOD, NULL);
273 NativeAssert::ValidReturnCode(hr, S_OK);
274
275 hr = RegCreate(HKEY_CURRENT_USER, wzParentRegKey, KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hk);
276 ReleaseRegKey(hk);
277 // Make sure creating the parent key does nothing, even after silence period
278 ::Sleep(FULLWAIT);
279 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
280 Assert::Equal<DWORD>(0, pResults->cRegKeys);
281
282 // Now create the target path, no notification until after the silence period
283 hr = RegCreate(HKEY_CURRENT_USER, wzDeepRegKey, KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hk);
284 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
285 ReleaseRegKey(hk);
286 ::Sleep(PREWAIT);
287 Assert::Equal<DWORD>(0, pResults->cRegKeys);
288
289 // Now after the full silence period, it should have triggered
290 ::Sleep(POSTWAIT);
291 Assert::Equal<DWORD>(1, pResults->cRegKeys);
292 NativeAssert::ValidReturnCode(pResults->rgRegKeys[0].hr, S_OK);
293
294 // Now delete the directory, along with a ton of parents. This verifies MonUtil will keep watching the closest parent that still exists.
295 hr = RegDelete(HKEY_CURRENT_USER, wzShallowRegKey, REG_KEY_32BIT, TRUE);
296 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE, E_PATHNOTFOUND);
297 ::Sleep(PREWAIT);
298 Assert::Equal<DWORD>(1, pResults->cRegKeys);
299
300 ::Sleep(FULLWAIT);
301 Assert::Equal<DWORD>(2, pResults->cRegKeys);
302 NativeAssert::ValidReturnCode(pResults->rgRegKeys[1].hr, S_OK);
303
304 // Create the parent directory again, still should be nothing even after full silence period
305 hr = RegCreate(HKEY_CURRENT_USER, wzParentRegKey, KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hk);
306 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
307 ReleaseRegKey(hk);
308 ::Sleep(FULLWAIT);
309 Assert::Equal<DWORD>(2, pResults->cRegKeys);
310
311 hr = RegCreate(HKEY_CURRENT_USER, wzChildRegKey, KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hk);
312 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
313 ::Sleep(PREWAIT);
314 Assert::Equal<DWORD>(2, pResults->cRegKeys);
315
316 ::Sleep(FULLWAIT);
317 Assert::Equal<DWORD>(3, pResults->cRegKeys);
318 NativeAssert::ValidReturnCode(pResults->rgRegKeys[2].hr, S_OK);
319
320 // Write a registry value to some deep child subkey, and make sure it's detected
321 hr = RegWriteString(hk, L"valuename", L"testvalue");
322 NativeAssert::ValidReturnCode(hr, S_OK);
323 ReleaseRegKey(hk);
324 ::Sleep(PREWAIT);
325 Assert::Equal<DWORD>(3, pResults->cRegKeys);
326
327 ::Sleep(FULLWAIT);
328 Assert::Equal<DWORD>(4, pResults->cRegKeys);
329 NativeAssert::ValidReturnCode(pResults->rgRegKeys[2].hr, S_OK);
330
331 hr = RegDelete(HKEY_CURRENT_USER, wzDeepRegKey, REG_KEY_32BIT, TRUE);
332 NativeAssert::ValidReturnCode(hr, S_OK);
333
334 ::Sleep(FULLWAIT);
335 Assert::Equal<DWORD>(5, pResults->cRegKeys);
336
337 // Now remove the regkey from the list of things to monitor, and confirm changes are no longer tracked
338 hr = MonRemoveRegKey(handle, HKEY_CURRENT_USER, wzDeepRegKey, REG_KEY_DEFAULT, TRUE);
339 NativeAssert::ValidReturnCode(hr, S_OK);
340
341 hr = RegCreate(HKEY_CURRENT_USER, wzDeepRegKey, KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hk);
342 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
343 ReleaseRegKey(hk);
344 ::Sleep(FULLWAIT);
345 Assert::Equal<DWORD>(5, pResults->cRegKeys);
346 }
347 finally
348 {
349 ReleaseRegKey(hk);
350 }
351 }
352
353 void TestMoreThan64(MON_HANDLE handle, Results *pResults)
354 {
355 HRESULT hr = S_OK;
356 LPWSTR sczBaseDir = NULL;
357 LPWSTR sczDir = NULL;
358 LPWSTR sczFile = NULL;
359
360 try
361 {
362 hr = PathExpand(&sczBaseDir, L"%TEMP%\\ScalabilityTest\\", PATH_EXPAND_ENVIRONMENT);
363 NativeAssert::ValidReturnCode(hr, S_OK);
364
365 for (DWORD i = 0; i < 200; ++i)
366 {
367 hr = StrAllocFormatted(&sczDir, L"%ls%u\\", sczBaseDir, i);
368 NativeAssert::ValidReturnCode(hr, S_OK);
369
370 hr = DirEnsureExists(sczDir, NULL);
371 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
372
373 hr = MonAddDirectory(handle, sczDir, FALSE, SILENCEPERIOD, NULL);
374 NativeAssert::ValidReturnCode(hr, S_OK);
375 }
376
377 hr = PathConcat(sczDir, L"file.txt", &sczFile);
378 NativeAssert::ValidReturnCode(hr, S_OK);
379
380 hr = FileFromString(sczFile, 0, L"contents", FILE_ENCODING_UTF16_WITH_BOM);
381 NativeAssert::ValidReturnCode(hr, S_OK);
382
383 ::Sleep(FULLWAIT);
384 Assert::Equal<DWORD>(1, pResults->cDirectories);
385
386 for (DWORD i = 0; i < 199; ++i)
387 {
388 hr = StrAllocFormatted(&sczDir, L"%ls%u\\", sczBaseDir, i);
389 NativeAssert::ValidReturnCode(hr, S_OK);
390
391 hr = MonRemoveDirectory(handle, sczDir, FALSE);
392 NativeAssert::ValidReturnCode(hr, S_OK);
393 }
394 ::Sleep(FULLWAIT);
395
396 hr = FileFromString(sczFile, 0, L"contents2", FILE_ENCODING_UTF16_WITH_BOM);
397 NativeAssert::ValidReturnCode(hr, S_OK);
398
399 ::Sleep(FULLWAIT);
400 Assert::Equal<DWORD>(2, pResults->cDirectories);
401
402 for (DWORD i = 0; i < 199; ++i)
403 {
404 hr = StrAllocFormatted(&sczDir, L"%ls%u\\", sczBaseDir, i);
405 NativeAssert::ValidReturnCode(hr, S_OK);
406
407 hr = MonAddDirectory(handle, sczDir, FALSE, SILENCEPERIOD, NULL);
408 NativeAssert::ValidReturnCode(hr, S_OK);
409 }
410 ::Sleep(FULLWAIT);
411
412 hr = FileFromString(sczFile, 0, L"contents3", FILE_ENCODING_UTF16_WITH_BOM);
413 NativeAssert::ValidReturnCode(hr, S_OK);
414
415 ::Sleep(FULLWAIT);
416 Assert::Equal<DWORD>(3, pResults->cDirectories);
417 }
418 finally
419 {
420 ReleaseStr(sczBaseDir);
421 ReleaseStr(sczDir);
422 ReleaseStr(sczFile);
423 }
424 }
425
426 [Fact(Skip = "Test demonstrates failure")]
427 void MonUtilTest()
428 {
429 HRESULT hr = S_OK;
430 MON_HANDLE handle = NULL;
431 List<GCHandle>^ gcHandles = gcnew List<GCHandle>();
432 Results *pResults = (Results *)MemAlloc(sizeof(Results), TRUE);
433 Assert::True(NULL != pResults);
434
435 try
436 {
437 // These ensure the function pointers we send point to this thread's appdomain, which helps with assembly binding when running tests within msbuild
438 MonGeneralDelegate^ fpMonGeneral = gcnew MonGeneralDelegate(MonGeneral);
439 GCHandle gchMonGeneral = GCHandle::Alloc(fpMonGeneral);
440 gcHandles->Add(gchMonGeneral);
441 IntPtr ipMonGeneral = Marshal::GetFunctionPointerForDelegate(fpMonGeneral);
442
443 MonDriveStatusDelegate^ fpMonDriveStatus = gcnew MonDriveStatusDelegate(MonDriveStatus);
444 GCHandle gchMonDriveStatus = GCHandle::Alloc(fpMonDriveStatus);
445 gcHandles->Add(gchMonDriveStatus);
446 IntPtr ipMonDriveStatus = Marshal::GetFunctionPointerForDelegate(fpMonDriveStatus);
447
448 MonDirectoryDelegate^ fpMonDirectory = gcnew MonDirectoryDelegate(MonDirectory);
449 GCHandle gchMonDirectory = GCHandle::Alloc(fpMonDirectory);
450 gcHandles->Add(gchMonDirectory);
451 IntPtr ipMonDirectory = Marshal::GetFunctionPointerForDelegate(fpMonDirectory);
452
453 MonRegKeyDelegate^ fpMonRegKey = gcnew MonRegKeyDelegate(MonRegKey);
454 GCHandle gchMonRegKey = GCHandle::Alloc(fpMonRegKey);
455 gcHandles->Add(gchMonRegKey);
456 IntPtr ipMonRegKey = Marshal::GetFunctionPointerForDelegate(fpMonRegKey);
457
458 // "Silence period" is 100 ms
459 hr = MonCreate(&handle, static_cast<PFN_MONGENERAL>(ipMonGeneral.ToPointer()), static_cast<PFN_MONDRIVESTATUS>(ipMonDriveStatus.ToPointer()), static_cast<PFN_MONDIRECTORY>(ipMonDirectory.ToPointer()), static_cast<PFN_MONREGKEY>(ipMonRegKey.ToPointer()), pResults);
460 NativeAssert::ValidReturnCode(hr, S_OK);
461
462 hr = RegInitialize();
463 NativeAssert::ValidReturnCode(hr, S_OK);
464
465 TestDirectory(handle, pResults);
466 ClearResults(pResults);
467 TestRegKey(handle, pResults);
468 ClearResults(pResults);
469 TestMoreThan64(handle, pResults);
470 ClearResults(pResults);
471 }
472 finally
473 {
474 ReleaseMon(handle);
475
476 for each (GCHandle gcHandle in gcHandles)
477 {
478 gcHandle.Free();
479 }
480
481 ReleaseMem(pResults->rgDirectories);
482 ReleaseMem(pResults->rgRegKeys);
483 ReleaseMem(pResults);
484 }
485 }
486 };
487}
diff --git a/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp
new file mode 100644
index 00000000..5a1f06fd
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp
@@ -0,0 +1,80 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9namespace DutilTests
10{
11 public ref class PathUtil
12 {
13 public:
14 [Fact]
15 void PathGetHierarchyArrayTest()
16 {
17 HRESULT hr = S_OK;
18 LPWSTR *rgsczPaths = NULL;
19 UINT cPaths = 0;
20
21 try
22 {
23 hr = PathGetHierarchyArray(L"c:\\foo\\bar\\bas\\a.txt", &rgsczPaths, &cPaths);
24 NativeAssert::Succeeded(hr, "Failed to get parent directories array for regular file path");
25 Assert::Equal<DWORD>(5, cPaths);
26 NativeAssert::StringEqual(L"c:\\", rgsczPaths[0]);
27 NativeAssert::StringEqual(L"c:\\foo\\", rgsczPaths[1]);
28 NativeAssert::StringEqual(L"c:\\foo\\bar\\", rgsczPaths[2]);
29 NativeAssert::StringEqual(L"c:\\foo\\bar\\bas\\", rgsczPaths[3]);
30 NativeAssert::StringEqual(L"c:\\foo\\bar\\bas\\a.txt", rgsczPaths[4]);
31 ReleaseNullStrArray(rgsczPaths, cPaths);
32
33 hr = PathGetHierarchyArray(L"c:\\foo\\bar\\bas\\", &rgsczPaths, &cPaths);
34 NativeAssert::Succeeded(hr, "Failed to get parent directories array for regular directory path");
35 Assert::Equal<DWORD>(4, cPaths);
36 NativeAssert::StringEqual(L"c:\\", rgsczPaths[0]);
37 NativeAssert::StringEqual(L"c:\\foo\\", rgsczPaths[1]);
38 NativeAssert::StringEqual(L"c:\\foo\\bar\\", rgsczPaths[2]);
39 NativeAssert::StringEqual(L"c:\\foo\\bar\\bas\\", rgsczPaths[3]);
40 ReleaseNullStrArray(rgsczPaths, cPaths);
41
42 hr = PathGetHierarchyArray(L"\\\\server\\share\\subdir\\file.txt", &rgsczPaths, &cPaths);
43 NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC file path");
44 Assert::Equal<DWORD>(3, cPaths);
45 NativeAssert::StringEqual(L"\\\\server\\share\\", rgsczPaths[0]);
46 NativeAssert::StringEqual(L"\\\\server\\share\\subdir\\", rgsczPaths[1]);
47 NativeAssert::StringEqual(L"\\\\server\\share\\subdir\\file.txt", rgsczPaths[2]);
48 ReleaseNullStrArray(rgsczPaths, cPaths);
49
50 hr = PathGetHierarchyArray(L"\\\\server\\share\\subdir\\", &rgsczPaths, &cPaths);
51 NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC directory path");
52 Assert::Equal<DWORD>(2, cPaths);
53 NativeAssert::StringEqual(L"\\\\server\\share\\", rgsczPaths[0]);
54 NativeAssert::StringEqual(L"\\\\server\\share\\subdir\\", rgsczPaths[1]);
55 ReleaseNullStrArray(rgsczPaths, cPaths);
56
57 hr = PathGetHierarchyArray(L"Software\\Microsoft\\Windows\\ValueName", &rgsczPaths, &cPaths);
58 NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC directory path");
59 Assert::Equal<DWORD>(4, cPaths);
60 NativeAssert::StringEqual(L"Software\\", rgsczPaths[0]);
61 NativeAssert::StringEqual(L"Software\\Microsoft\\", rgsczPaths[1]);
62 NativeAssert::StringEqual(L"Software\\Microsoft\\Windows\\", rgsczPaths[2]);
63 NativeAssert::StringEqual(L"Software\\Microsoft\\Windows\\ValueName", rgsczPaths[3]);
64 ReleaseNullStrArray(rgsczPaths, cPaths);
65
66 hr = PathGetHierarchyArray(L"Software\\Microsoft\\Windows\\", &rgsczPaths, &cPaths);
67 NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC directory path");
68 Assert::Equal<DWORD>(3, cPaths);
69 NativeAssert::StringEqual(L"Software\\", rgsczPaths[0]);
70 NativeAssert::StringEqual(L"Software\\Microsoft\\", rgsczPaths[1]);
71 NativeAssert::StringEqual(L"Software\\Microsoft\\Windows\\", rgsczPaths[2]);
72 ReleaseNullStrArray(rgsczPaths, cPaths);
73 }
74 finally
75 {
76 ReleaseStrArray(rgsczPaths, cPaths);
77 }
78 }
79 };
80}
diff --git a/src/libs/dutil/test/DUtilUnitTest/SceUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/SceUtilTest.cpp
new file mode 100644
index 00000000..75b9222a
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/SceUtilTest.cpp
@@ -0,0 +1,488 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5#include <sqlce_oledb.h>
6#include <sceutil.h>
7
8using namespace System;
9using namespace Xunit;
10using namespace WixTest;
11
12#define ASSIGN_INDEX_STRUCT(a, b, c) {a.wzName = c; a.rgColumns = b; a.cColumns = countof(b);};
13
14namespace DutilTests
15{
16 enum TABLES
17 {
18 TABLE_A,
19 TABLE_COUNT
20 };
21
22 enum TABLE_A_COLUMNS
23 {
24 TABLE_A_KEY,
25 TABLE_A_BINARY,
26 TABLE_A_DWORD,
27 TABLE_A_QWORD,
28 TABLE_A_BOOL,
29 TABLE_A_STRING,
30 TABLE_A_DWORD_NULLABLE,
31 TABLE_A_INITIAL_COLUMNS,
32
33 TABLE_A_EXTRA_STRING = TABLE_A_INITIAL_COLUMNS,
34 TABLE_A_FINAL_COLUMNS
35 };
36
37 struct TableARowValue
38 {
39 DWORD dwAutoGenKey;
40
41 BYTE *pbBinary;
42 DWORD cBinary;
43
44 DWORD dw;
45 DWORD64 qw;
46 BOOL f;
47 LPWSTR scz;
48
49 BOOL fNullablePresent;
50 DWORD dwNullable;
51
52 BOOL fSchemaV2;
53 LPWSTR sczExtra;
54 };
55
56 public ref class SceUtil
57 {
58 public:
59 void ReleaseSceSchema(SCE_DATABASE_SCHEMA *pdsSchema)
60 {
61 DWORD dwTable;
62
63 for (dwTable = 0; dwTable < pdsSchema->cTables; ++dwTable)
64 {
65 ReleaseNullMem(pdsSchema->rgTables[dwTable].rgColumns);
66 ReleaseNullMem(pdsSchema->rgTables[dwTable].rgIndexes);
67 }
68
69 ReleaseMem(pdsSchema->rgTables);
70
71 return;
72 }
73
74 void SetupSchema(SCE_DATABASE_SCHEMA *pSchema, BOOL fIncludeExtended)
75 {
76 pSchema->cTables = TABLE_COUNT;
77 pSchema->rgTables = static_cast<SCE_TABLE_SCHEMA*>(MemAlloc(TABLE_COUNT * sizeof(SCE_TABLE_SCHEMA), TRUE));
78 NativeAssert::True(pSchema->rgTables != NULL);
79
80 pSchema->rgTables[TABLE_A].wzName = L"TableA";
81 pSchema->rgTables[TABLE_A].cColumns = fIncludeExtended ? TABLE_A_FINAL_COLUMNS : TABLE_A_INITIAL_COLUMNS;
82 pSchema->rgTables[TABLE_A].cIndexes = 2;
83
84 for (DWORD i = 0; i < pSchema->cTables; ++i)
85 {
86 pSchema->rgTables[i].rgColumns = static_cast<SCE_COLUMN_SCHEMA*>(MemAlloc(sizeof(SCE_COLUMN_SCHEMA) * pSchema->rgTables[i].cColumns, TRUE));
87 NativeAssert::True(pSchema->rgTables[i].rgColumns != NULL);
88
89 pSchema->rgTables[i].rgIndexes = static_cast<SCE_INDEX_SCHEMA*>(MemAlloc(sizeof(SCE_COLUMN_SCHEMA) * pSchema->rgTables[i].cIndexes, TRUE));
90 NativeAssert::True(pSchema->rgTables[i].rgIndexes != NULL);
91 }
92
93 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_KEY].wzName = L"Key";
94 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_KEY].dbtColumnType = DBTYPE_I4;
95 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_KEY].fPrimaryKey = TRUE;
96 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_KEY].fAutoIncrement = TRUE;
97 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_BINARY].wzName = L"Binary";
98 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_BINARY].dbtColumnType = DBTYPE_BYTES;
99 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_DWORD].wzName = L"Dword";
100 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_DWORD].dbtColumnType = DBTYPE_I4;
101 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_QWORD].wzName = L"Qword";
102 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_QWORD].dbtColumnType = DBTYPE_I8;
103 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_BOOL].wzName = L"Bool";
104 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_BOOL].dbtColumnType = DBTYPE_BOOL;
105 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_STRING].wzName = L"String";
106 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_STRING].dbtColumnType = DBTYPE_WSTR;
107 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_DWORD_NULLABLE].wzName = L"Nullable";
108 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_DWORD_NULLABLE].dbtColumnType = DBTYPE_I4;
109 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_DWORD_NULLABLE].fNullable = TRUE;
110
111 if (fIncludeExtended)
112 {
113 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_EXTRA_STRING].wzName = L"ExtraString";
114 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_EXTRA_STRING].dbtColumnType = DBTYPE_WSTR;
115 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_EXTRA_STRING].fNullable = TRUE;
116 }
117
118 static DWORD rgdwTableA_Index1[] = { TABLE_A_DWORD, TABLE_A_STRING, TABLE_A_QWORD };
119 static DWORD rgdwTableA_Index2[] = { TABLE_A_DWORD, TABLE_A_STRING };
120
121 ASSIGN_INDEX_STRUCT(pSchema->rgTables[TABLE_A].rgIndexes[0], rgdwTableA_Index1, L"Dword_String_Qword");
122 ASSIGN_INDEX_STRUCT(pSchema->rgTables[TABLE_A].rgIndexes[1], rgdwTableA_Index2, L"Dword_String");
123 }
124
125 void SetStructValues(TableARowValue *pValue, BYTE *pbBinary, DWORD cBinary, DWORD dw, DWORD64 qw, BOOL f, LPWSTR scz, DWORD *pdw, LPWSTR sczExtra)
126 {
127 pValue->pbBinary = pbBinary;
128 pValue->cBinary = cBinary;
129 pValue->dw = dw;
130 pValue->qw = qw;
131 pValue->f = f;
132 pValue->scz = scz;
133
134 if (pdw)
135 {
136 pValue->fNullablePresent = TRUE;
137 pValue->dwNullable = *pdw;
138 }
139 else
140 {
141 pValue->fNullablePresent = FALSE;
142 }
143
144 if (sczExtra)
145 {
146 pValue->fSchemaV2 = TRUE;
147 pValue->sczExtra = sczExtra;
148 }
149 else
150 {
151 pValue->fSchemaV2 = FALSE;
152 }
153 }
154
155 void AssertStructValuesSame(TableARowValue *pValueExpected, TableARowValue *pValueOther)
156 {
157 NativeAssert::Equal(pValueExpected->cBinary, pValueOther->cBinary);
158 NativeAssert::True(0 == memcmp(pValueExpected->pbBinary, pValueOther->pbBinary, pValueOther->cBinary));
159
160 NativeAssert::Equal(pValueExpected->dw, pValueOther->dw);
161 NativeAssert::Equal(pValueExpected->qw, pValueOther->qw);
162 NativeAssert::Equal(pValueExpected->f, pValueOther->f);
163 NativeAssert::True(0 == wcscmp(pValueExpected->scz, pValueOther->scz));
164
165 NativeAssert::Equal(pValueExpected->fNullablePresent, pValueOther->fNullablePresent);
166 if (pValueExpected->fNullablePresent)
167 {
168 NativeAssert::Equal(pValueExpected->dwNullable, pValueOther->dwNullable);
169 }
170
171 NativeAssert::Equal(pValueExpected->fSchemaV2, pValueOther->fSchemaV2);
172 if (pValueExpected->fSchemaV2)
173 {
174 NativeAssert::True(0 == wcscmp(pValueExpected->sczExtra, pValueOther->sczExtra));
175 }
176 }
177
178 void InsertRow(SCE_DATABASE *pDatabase, TableARowValue *pValue, BOOL fRollback)
179 {
180 HRESULT hr = S_OK;
181 SCE_ROW_HANDLE sceRow = NULL;
182
183 hr = SceBeginTransaction(pDatabase);
184 NativeAssert::Succeeded(hr, "Failed to begin transaction");
185
186 hr = ScePrepareInsert(pDatabase, TABLE_A, &sceRow);
187 NativeAssert::Succeeded(hr, "Failed to prepare to insert row");
188
189 hr = SceSetColumnBinary(sceRow, TABLE_A_BINARY, pValue->pbBinary, pValue->cBinary);
190 NativeAssert::Succeeded(hr, "Failed to set binary value");
191
192 hr = SceSetColumnDword(sceRow, TABLE_A_DWORD, pValue->dw);
193 NativeAssert::Succeeded(hr, "Failed to set dword value");
194
195 hr = SceSetColumnQword(sceRow, TABLE_A_QWORD, pValue->qw);
196 NativeAssert::Succeeded(hr, "Failed to set qword value");
197
198 hr = SceSetColumnBool(sceRow, TABLE_A_BOOL, pValue->f);
199 NativeAssert::Succeeded(hr, "Failed to set bool value");
200
201 hr = SceSetColumnString(sceRow, TABLE_A_STRING, pValue->scz);
202 NativeAssert::Succeeded(hr, "Failed to set string value");
203
204 if (pValue->fNullablePresent)
205 {
206 hr = SceSetColumnDword(sceRow, TABLE_A_DWORD_NULLABLE, pValue->dwNullable);
207 NativeAssert::Succeeded(hr, "Failed to set dword value");
208 }
209 else
210 {
211 hr = SceSetColumnNull(sceRow, TABLE_A_DWORD_NULLABLE);
212 NativeAssert::Succeeded(hr, "Failed to set null value");
213 }
214
215 if (pValue->fSchemaV2)
216 {
217 hr = SceSetColumnString(sceRow, TABLE_A_EXTRA_STRING, pValue->sczExtra);
218 NativeAssert::Succeeded(hr, "Failed to set extra string value");
219 }
220
221 hr = SceFinishUpdate(sceRow);
222 NativeAssert::Succeeded(hr, "Failed to finish insert");
223
224 if (fRollback)
225 {
226 hr = SceRollbackTransaction(pDatabase);
227 NativeAssert::Succeeded(hr, "Failed to rollback transaction");
228 }
229 else
230 {
231 hr = SceCommitTransaction(pDatabase);
232 NativeAssert::Succeeded(hr, "Failed to commit transaction");
233
234 hr = SceGetColumnDword(sceRow, TABLE_A_KEY, &pValue->dwAutoGenKey);
235 NativeAssert::Succeeded(hr, "Failed to get autogen key after insert");
236
237 NativeAssert::True(pValue->dwAutoGenKey != 0);
238 }
239
240 ReleaseSceRow(sceRow);
241 }
242
243 void VerifyRow(TableARowValue *pExpectedValue, SCE_ROW_HANDLE sceRow)
244 {
245 HRESULT hr = S_OK;
246 TableARowValue value = {};
247
248 hr = SceGetColumnBinary(sceRow, TABLE_A_BINARY, &value.pbBinary, &value.cBinary);
249 NativeAssert::Succeeded(hr, "Failed to get binary value from result row");
250
251 hr = SceGetColumnDword(sceRow, TABLE_A_DWORD, &value.dw);
252 NativeAssert::Succeeded(hr, "Failed to get dword value from result row");
253
254 hr = SceGetColumnQword(sceRow, TABLE_A_QWORD, &value.qw);
255 NativeAssert::Succeeded(hr, "Failed to get qword value from result row");
256
257 hr = SceGetColumnBool(sceRow, TABLE_A_BOOL, &value.f);
258 NativeAssert::Succeeded(hr, "Failed to get bool value from result row");
259
260 hr = SceGetColumnString(sceRow, TABLE_A_STRING, &value.scz);
261 NativeAssert::Succeeded(hr, "Failed to get string value from result row");
262
263 hr = SceGetColumnDword(sceRow, TABLE_A_DWORD_NULLABLE, &value.dwNullable);
264 if (hr == E_NOTFOUND)
265 {
266 value.fNullablePresent = FALSE;
267 hr = S_OK;
268 }
269 else
270 {
271 NativeAssert::Succeeded(hr, "Failed to get string value from result row");
272 value.fNullablePresent = TRUE;
273 }
274
275 if (pExpectedValue->fSchemaV2)
276 {
277 value.fSchemaV2 = TRUE;
278 hr = SceGetColumnString(sceRow, TABLE_A_EXTRA_STRING, &value.sczExtra);
279 NativeAssert::Succeeded(hr, "Failed to get extra string value from result row");
280 }
281
282 AssertStructValuesSame(pExpectedValue, &value);
283
284 ReleaseNullMem(value.pbBinary);
285 ReleaseNullStr(value.scz);
286 }
287
288 void VerifyQuery(TableARowValue **rgExpectedValues, DWORD cExpectedValues, SCE_QUERY_RESULTS_HANDLE queryResults)
289 {
290 HRESULT hr = S_OK;
291 SCE_ROW_HANDLE sceRow = NULL;
292
293 for (DWORD i = 0; i < cExpectedValues; ++i)
294 {
295 hr = SceGetNextResultRow(queryResults, &sceRow);
296 NativeAssert::Succeeded(hr, "Failed to get next result row");
297
298 VerifyRow(rgExpectedValues[i], sceRow);
299 ReleaseNullSceRow(sceRow);
300 }
301
302 // No more results
303 NativeAssert::True(NULL == queryResults || FAILED(SceGetNextResultRow(queryResults, &sceRow)));
304 }
305
306 void TestIndex(SCE_DATABASE *pDatabase)
307 {
308 HRESULT hr = S_OK;
309 BYTE binary1[50] = { 0x80, 0x70 };
310 BYTE binary2[40] = { 0x90, 0xAB };
311 BYTE binary3[40] = { 0x85, 0x88 };
312 DWORD dwValue1 = 0x55555555, dwValue2 = 0x88888888;
313 TableARowValue value1 = {}, value2 = {}, value3 = {}, value4 = {}, value5 = {};
314 SCE_QUERY_HANDLE query = NULL;
315 SCE_QUERY_RESULTS_HANDLE results = NULL;
316
317 SetStructValues(&value1, static_cast<BYTE *>(binary1), sizeof(binary1), 3, 1, TRUE, L"zzz", &dwValue1, NULL);
318 SetStructValues(&value2, static_cast<BYTE *>(binary2), sizeof(binary2), 3, 2, TRUE, L"yyy", &dwValue2, NULL);
319 SetStructValues(&value3, static_cast<BYTE *>(binary3), sizeof(binary3), 3, 3, TRUE, L"xxx", NULL, NULL);
320 SetStructValues(&value4, static_cast<BYTE *>(binary2), sizeof(binary2), 4, 4, TRUE, L"xyz", &dwValue2, NULL);
321 SetStructValues(&value5, static_cast<BYTE *>(binary3), sizeof(binary3), 3, 1, TRUE, L"yyy", &dwValue2, NULL);
322
323 // Rollback an insert to confirm the insert doesn't happen and database can still be interacted with normally afterwards
324 InsertRow(pDatabase, &value1, TRUE);
325
326 InsertRow(pDatabase, &value1, FALSE);
327 InsertRow(pDatabase, &value2, FALSE);
328 InsertRow(pDatabase, &value3, FALSE);
329 InsertRow(pDatabase, &value4, FALSE);
330 InsertRow(pDatabase, &value5, FALSE);
331
332 NativeAssert::True(value1.dwAutoGenKey != value2.dwAutoGenKey);
333
334 // Test setting 1 column
335 hr = SceBeginQuery(pDatabase, TABLE_A, 0, &query);
336 NativeAssert::Succeeded(hr, "Failed to begin query");
337
338 hr = SceSetQueryColumnDword(query, 3);
339 NativeAssert::Succeeded(hr, "Failed to set query column dword");
340
341 hr = SceRunQueryRange(&query, &results);
342 NativeAssert::Succeeded(hr, "Failed to run query");
343 NativeAssert::True(query == NULL);
344
345 TableARowValue *sortedAfterQuery1[] = { &value3, &value5, &value2, &value1 };
346 VerifyQuery(sortedAfterQuery1, _countof(sortedAfterQuery1), results);
347 ReleaseNullSceQueryResults(results);
348
349 // Test setting 2 columns, third column is unspecified so results are sorted by it
350 hr = SceBeginQuery(pDatabase, TABLE_A, 0, &query);
351 NativeAssert::Succeeded(hr, "Failed to begin query");
352
353 hr = SceSetQueryColumnDword(query, 3);
354 NativeAssert::Succeeded(hr, "Failed to set query column dword");
355
356 hr = SceSetQueryColumnString(query, L"yyy");
357 NativeAssert::Succeeded(hr, "Failed to set query column dword");
358
359 hr = SceRunQueryRange(&query, &results);
360 NativeAssert::Succeeded(hr, "Failed to run query");
361 NativeAssert::True(query == NULL);
362
363 TableARowValue *sortedAfterQuery2[] = { &value5, &value2 };
364 VerifyQuery(sortedAfterQuery2, _countof(sortedAfterQuery2), results);
365 ReleaseNullSceQueryResults(results);
366
367 // Test setting 2 columns, third column of index is unspecified so results are sorted by it
368 hr = SceBeginQuery(pDatabase, TABLE_A, 0, &query);
369 NativeAssert::Succeeded(hr, "Failed to begin query");
370
371 hr = SceSetQueryColumnDword(query, 3);
372 NativeAssert::Succeeded(hr, "Failed to set query column dword");
373
374 hr = SceSetQueryColumnString(query, L"yyy");
375 NativeAssert::Succeeded(hr, "Failed to set query column dword");
376
377 hr = SceRunQueryRange(&query, &results);
378 NativeAssert::Succeeded(hr, "Failed to run query");
379 NativeAssert::True(query == NULL);
380
381 TableARowValue *sortedAfterQuery3[] = { &value5, &value2 };
382 VerifyQuery(sortedAfterQuery3, _countof(sortedAfterQuery3), results);
383 ReleaseNullSceQueryResults(results);
384
385 // Test setting 2 columns in a different (2 column) index, so there is no 3rd column in index to sort by
386 hr = SceBeginQuery(pDatabase, TABLE_A, 1, &query);
387 NativeAssert::Succeeded(hr, "Failed to begin query");
388
389 hr = SceSetQueryColumnDword(query, 3);
390 NativeAssert::Succeeded(hr, "Failed to set query column dword");
391
392 hr = SceSetQueryColumnString(query, L"yyy");
393 NativeAssert::Succeeded(hr, "Failed to set query column dword");
394
395 hr = SceRunQueryRange(&query, &results);
396 NativeAssert::Succeeded(hr, "Failed to run query");
397 NativeAssert::True(query == NULL);
398
399 TableARowValue *sortedAfterQuery4[] = { &value2, &value5 };
400 VerifyQuery(sortedAfterQuery4, _countof(sortedAfterQuery4), results);
401 ReleaseNullSceQueryResults(results);
402 }
403
404 void TestReadWriteSchemaV2(SCE_DATABASE *pDatabase)
405 {
406 HRESULT hr = S_OK;
407 BYTE binary1[40] = { 0x55, 0x44 };
408 DWORD dwValue1 = 58;
409 TableARowValue value1 = {};
410 SCE_QUERY_HANDLE query = NULL;
411 SCE_ROW_HANDLE row = NULL;
412
413 SetStructValues(&value1, static_cast<BYTE *>(binary1), sizeof(binary1), 5, 1, TRUE, L"zzz", &dwValue1, L"newextrastring");
414
415 InsertRow(pDatabase, &value1, FALSE);
416
417 // Test setting 1 column
418 hr = SceBeginQuery(pDatabase, TABLE_A, 0, &query);
419 NativeAssert::Succeeded(hr, "Failed to begin query");
420
421 hr = SceSetQueryColumnDword(query, 5);
422 NativeAssert::Succeeded(hr, "Failed to set query column dword");
423
424 hr = SceRunQueryExact(&query, &row);
425 NativeAssert::Succeeded(hr, "Failed to run query exact");
426
427 VerifyRow(&value1, row);
428 }
429
430 [Fact]
431 void SceUtilTest()
432 {
433 HRESULT hr = S_OK;
434 BOOL fComInitialized = FALSE;
435 LPWSTR sczDbPath = NULL;
436 SCE_DATABASE *pDatabase = NULL;
437 SCE_DATABASE_SCHEMA schema1 = {};
438 SCE_DATABASE_SCHEMA schema2 = {};
439
440 try
441 {
442 hr = ::CoInitialize(0);
443 NativeAssert::Succeeded(hr, "Failed to initialize COM");
444 fComInitialized = TRUE;
445
446 SetupSchema(&schema1, FALSE);
447 SetupSchema(&schema2, TRUE);
448
449 hr = PathExpand(&sczDbPath, L"%TEMP%\\SceUtilTest\\UnitTest.sdf", PATH_EXPAND_ENVIRONMENT);
450 NativeAssert::Succeeded(hr, "Failed to get path to test database");
451
452 FileEnsureDelete(sczDbPath);
453
454 hr = SceEnsureDatabase(sczDbPath, L"sqlceoledb40.dll", L"Test", 1, &schema1, &pDatabase);
455 NativeAssert::Succeeded(hr, "Failed to ensure database schema");
456
457 TestIndex(pDatabase);
458
459 hr = SceCloseDatabase(pDatabase);
460 pDatabase = NULL;
461 NativeAssert::Succeeded(hr, "Failed to close database");
462
463 // Add column to schema
464 hr = SceEnsureDatabase(sczDbPath, L"sqlceoledb40.dll", L"Test", 1, &schema2, &pDatabase);
465 NativeAssert::Succeeded(hr, "Failed to ensure database schema");
466
467 TestReadWriteSchemaV2(pDatabase);
468 }
469 finally
470 {
471 ReleaseSceSchema(&schema1);
472 ReleaseSceSchema(&schema2);
473
474 if (NULL != pDatabase)
475 {
476 hr = SceCloseDatabase(pDatabase);
477 NativeAssert::Succeeded(hr, "Failed to close database");
478 }
479 ReleaseStr(sczDbPath);
480
481 if (fComInitialized)
482 {
483 ::CoUninitialize();
484 }
485 }
486 }
487 };
488}
diff --git a/src/libs/dutil/test/DUtilUnitTest/StrUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/StrUtilTest.cpp
new file mode 100644
index 00000000..94fee280
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/StrUtilTest.cpp
@@ -0,0 +1,192 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9namespace DutilTests
10{
11 public ref class StrUtil
12 {
13 public:
14 [Fact]
15 void StrUtilFormattedTest()
16 {
17 HRESULT hr = S_OK;
18 LPWSTR sczText = NULL;
19
20 try
21 {
22 hr = StrAllocFormatted(&sczText, L"%hs - %ls - %u", "ansi string", L"unicode string", 1234);
23 NativeAssert::Succeeded(hr, "Failed to format string.");
24 NativeAssert::StringEqual(L"ansi string - unicode string - 1234", sczText);
25
26 ReleaseNullStr(sczText);
27
28 hr = StrAllocString(&sczText, L"repeat", 0);
29 NativeAssert::Succeeded(hr, "Failed to allocate string.");
30
31 hr = StrAllocFormatted(&sczText, L"%ls and %ls", sczText, sczText);
32 NativeAssert::Succeeded(hr, "Failed to format string unto itself.");
33 NativeAssert::StringEqual(L"repeat and repeat", sczText);
34 }
35 finally
36 {
37 ReleaseStr(sczText);
38 }
39 }
40
41 [Fact]
42 void StrUtilTrimTest()
43 {
44 TestTrim(L"", L"");
45 TestTrim(L"Blah", L"Blah");
46 TestTrim(L"\t\t\tBlah", L"Blah");
47 TestTrim(L"\t Blah ", L"Blah");
48 TestTrim(L"Blah ", L"Blah");
49 TestTrim(L"\t Spaces \t Between \t", L"Spaces \t Between");
50 TestTrim(L" \t\t\t ", L"");
51
52 TestTrimAnsi("", "");
53 TestTrimAnsi("Blah", "Blah");
54 TestTrimAnsi("\t\t\tBlah", "Blah");
55 TestTrimAnsi(" Blah ", "Blah");
56 TestTrimAnsi("Blah ", "Blah");
57 TestTrimAnsi("\t Spaces \t Between \t", "Spaces \t Between");
58 TestTrimAnsi(" \t\t\t ", "");
59 }
60
61 [Fact]
62 void StrUtilConvertTest()
63 {
64 char a[] = { 'a', 'b', 'C', 'd', '\0', '\0' };
65
66 TestStrAllocStringAnsi(a, 5, L"abCd");
67 TestStrAllocStringAnsi(a, 4, L"abCd");
68 TestStrAllocStringAnsi(a, 3, L"abC");
69 TestStrAllocStringAnsi(a, 2, L"ab");
70 TestStrAllocStringAnsi(a, 1, L"a");
71 TestStrAllocStringAnsi(a, 0, L"abCd");
72
73 wchar_t b[] = { L'a', L'b', L'C', L'd', L'\0', L'\0' };
74
75 TestStrAnsiAllocString(b, 5, "abCd");
76 TestStrAnsiAllocString(b, 4, "abCd");
77 TestStrAnsiAllocString(b, 3, "abC");
78 TestStrAnsiAllocString(b, 2, "ab");
79 TestStrAnsiAllocString(b, 1, "a");
80 TestStrAnsiAllocString(b, 0, "abCd");
81 }
82
83 private:
84 void TestTrim(LPCWSTR wzInput, LPCWSTR wzExpectedResult)
85 {
86 HRESULT hr = S_OK;
87 LPWSTR sczOutput = NULL;
88
89 DutilInitialize(&DutilTestTraceError);
90
91 try
92 {
93 hr = StrTrimWhitespace(&sczOutput, wzInput);
94 NativeAssert::Succeeded(hr, "Failed to trim whitespace from string: {0}", wzInput);
95
96 if (0 != wcscmp(wzExpectedResult, sczOutput))
97 {
98 hr = E_FAIL;
99 ExitOnFailure(hr, "Trimmed string \"%ls\", expected result \"%ls\", actual result \"%ls\"", wzInput, wzExpectedResult, sczOutput);
100 }
101 }
102 finally
103 {
104 ReleaseStr(sczOutput);
105 }
106
107 LExit:
108 DutilUninitialize();
109 }
110
111 void TestTrimAnsi(LPCSTR szInput, LPCSTR szExpectedResult)
112 {
113 HRESULT hr = S_OK;
114 LPSTR sczOutput = NULL;
115
116 DutilInitialize(&DutilTestTraceError);
117
118 try
119 {
120 hr = StrAnsiTrimWhitespace(&sczOutput, szInput);
121 NativeAssert::Succeeded(hr, "Failed to trim whitespace from string: \"{0}\"", szInput);
122
123 if (0 != strcmp(szExpectedResult, sczOutput))
124 {
125 hr = E_FAIL;
126 ExitOnFailure(hr, "Trimmed string \"%hs\", expected result \"%hs\", actual result \"%hs\"", szInput, szExpectedResult, sczOutput);
127 }
128 }
129 finally
130 {
131 ReleaseStr(sczOutput);
132 }
133
134 LExit:
135 DutilUninitialize();
136 }
137
138 void TestStrAllocStringAnsi(LPCSTR szSource, DWORD cchSource, LPCWSTR wzExpectedResult)
139 {
140 HRESULT hr = S_OK;
141 LPWSTR sczOutput = NULL;
142
143 DutilInitialize(&DutilTestTraceError);
144
145 try
146 {
147 hr = StrAllocStringAnsi(&sczOutput, szSource, cchSource, CP_UTF8);
148 NativeAssert::Succeeded(hr, "Failed to call StrAllocStringAnsi on string: \"{0}\"", szSource);
149
150 if (0 != wcscmp(sczOutput, wzExpectedResult))
151 {
152 hr = E_FAIL;
153 ExitOnFailure(hr, "String doesn't match, expected result \"%ls\", actual result \"%ls\"", wzExpectedResult, sczOutput);
154 }
155 }
156 finally
157 {
158 ReleaseStr(sczOutput);
159 }
160
161 LExit:
162 DutilUninitialize();
163 }
164
165 void TestStrAnsiAllocString(LPWSTR wzSource, DWORD cchSource, LPCSTR szExpectedResult)
166 {
167 HRESULT hr = S_OK;
168 LPSTR sczOutput = NULL;
169
170 DutilInitialize(&DutilTestTraceError);
171
172 try
173 {
174 hr = StrAnsiAllocString(&sczOutput, wzSource, cchSource, CP_UTF8);
175 NativeAssert::Succeeded(hr, "Failed to call StrAllocStringAnsi on string: \"{0}\"", wzSource);
176
177 if (0 != strcmp(sczOutput, szExpectedResult))
178 {
179 hr = E_FAIL;
180 ExitOnFailure(hr, "String doesn't match, expected result \"%hs\", actual result \"%hs\"", szExpectedResult, sczOutput);
181 }
182 }
183 finally
184 {
185 ReleaseStr(sczOutput);
186 }
187
188 LExit:
189 DutilUninitialize();
190 }
191 };
192}
diff --git a/src/libs/dutil/test/DUtilUnitTest/TestData/ApupUtilTests/FeedBv2.0.xml b/src/libs/dutil/test/DUtilUnitTest/TestData/ApupUtilTests/FeedBv2.0.xml
new file mode 100644
index 00000000..d9f961fe
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/TestData/ApupUtilTests/FeedBv2.0.xml
@@ -0,0 +1,68 @@
1<?xml version='1.0' ?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<feed xmlns="http://www.w3.org/2005/Atom" xmlns:as="http://appsyndication.org/2006/appsyn">
6 <title type="text">BundleB v2.0</title>
7 <subtitle type="text">Bundle Subtitle.</subtitle>
8 <as:application type="application/exe">1116353B-7C6E-4C29-BFA1-D4A972CD421D</as:application>
9 <updated>2014-07-14T12:39:00.000Z</updated>
10 <id>http://localhost:9999/wix4/BundleB/feed</id>
11 <link rel="self" type="application/atom+xml" href="http://localhost:9999/wix4/BundleB/feed"/>
12 <generator version="0.1">manual build</generator>
13 <entry>
14 <title>Bundle v2.0</title>
15 <id>v2.0</id>
16 <author>
17 <name>Bundle_Author</name>
18 <uri>http://mycompany.com/software</uri>
19 <email>Bundle_Author@mycompany.com</email>
20 </author>
21 <link rel="alternate" href="http://www.mycompany.com/content/view/software"/>
22 <link rel="enclosure" href="http://localhost:9999/wix4/BundleB/2.0/BundleB.exe" length="0" type="application/octet-stream"/>
23 <content type="html">
24 &lt;p&gt;Change list:&lt;/p&gt;&lt;ul&gt;
25 &lt;li&gt;Updated release.&lt;/li&gt;
26 &lt;/ul&gt;
27 </content>
28 <as:version>2.0.0.0</as:version>
29 <updated>2014-11-10T12:39:00.000Z</updated>
30 </entry>
31 <entry>
32 <title>Bundle v1.0</title>
33 <id>v1.0</id>
34 <author>
35 <name>Bundle_Author</name>
36 <uri>http://mycompany.com/software</uri>
37 <email>Bundle_Author@mycompany.com</email>
38 </author>
39 <link rel="alternate" href="http://www.mycompany.com/content/view/software"/>
40 <link rel="enclosure" href="http://localhost:9999/wix4/BundleB/1.0/BundleB.exe" length="0" type="application/octet-stream"/>
41 <content type="html">
42 &lt;p&gt;Change list:&lt;/p&gt;&lt;ul&gt;
43 &lt;li&gt;Initial release.&lt;/li&gt;
44 &lt;/ul&gt;
45 </content>
46 <as:upgrade version="1.0.0.0-preview" />
47 <as:version>1.0.0.0</as:version>
48 <updated>2014-11-09T12:39:00.000Z</updated>
49 </entry>
50 <entry>
51 <title>Bundle v1.0-preview</title>
52 <id>v1.0-preview</id>
53 <author>
54 <name>Bundle_Author</name>
55 <uri>http://mycompany.com/software</uri>
56 <email>Bundle_Author@mycompany.com</email>
57 </author>
58 <link rel="alternate" href="http://www.mycompany.com/content/view/software"/>
59 <link rel="enclosure" href="http://localhost:9999/wix4/BundleB/1.0-preview/BundleB.exe" length="10000" type="application/octet-stream"/>
60 <content type="html">
61 &lt;p&gt;Change list:&lt;/p&gt;&lt;ul&gt;
62 &lt;li&gt;Initial release.&lt;/li&gt;
63 &lt;/ul&gt;
64 </content>
65 <as:version>1.0.0.0</as:version>
66 <updated>2014-11-09T12:39:00.000Z</updated>
67 </entry>
68</feed>
diff --git a/src/libs/dutil/test/DUtilUnitTest/UnitTest.rc b/src/libs/dutil/test/DUtilUnitTest/UnitTest.rc
new file mode 100644
index 00000000..14cebe1a
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/UnitTest.rc
@@ -0,0 +1,6 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#define VER_APP
4#define VER_ORIGINAL_FILENAME "UnitTest.dll"
5#define VER_INTERNAL_NAME "setup"
6#define VER_FILE_DESCRIPTION "WiX Toolset Bootstrapper unit tests"
diff --git a/src/libs/dutil/test/DUtilUnitTest/UriUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/UriUtilTest.cpp
new file mode 100644
index 00000000..b3bf87a2
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/UriUtilTest.cpp
@@ -0,0 +1,98 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace System::Text;
7using namespace System::Collections::Generic;
8using namespace Xunit;
9
10namespace CfgTests
11{
12 public ref class UriUtil
13 {
14 public:
15 [Fact]
16 void UriProtocolTest()
17 {
18 HRESULT hr = S_OK;
19
20 DutilInitialize(&DutilTestTraceError);
21
22 LPCWSTR uri = L"https://localhost/";
23 URI_PROTOCOL uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
24 hr = UriProtocol(uri, &uriProtocol);
25 ExitOnFailure(hr, "Failed to determine UriProtocol");
26 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_HTTPS, (int)uriProtocol);
27
28 uri = L"HTTPS://localhost/";
29 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
30 hr = UriProtocol(uri, &uriProtocol);
31 ExitOnFailure(hr, "Failed to determine UriProtocol");
32 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_HTTPS, (int)uriProtocol);
33
34 uri = L"HtTpS://localhost/";
35 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
36 hr = UriProtocol(uri, &uriProtocol);
37 ExitOnFailure(hr, "Failed to determine UriProtocol");
38 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_HTTPS, (int)uriProtocol);
39
40 uri = L"HTTP://localhost/";
41 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
42 hr = UriProtocol(uri, &uriProtocol);
43 ExitOnFailure(hr, "Failed to determine UriProtocol");
44 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_HTTP, (int)uriProtocol);
45
46 uri = L"http://localhost/";
47 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
48 hr = UriProtocol(uri, &uriProtocol);
49 ExitOnFailure(hr, "Failed to determine UriProtocol");
50 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_HTTP, (int)uriProtocol);
51
52 uri = L"HtTp://localhost/";
53 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
54 hr = UriProtocol(uri, &uriProtocol);
55 ExitOnFailure(hr, "Failed to determine UriProtocol");
56 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_HTTP, (int)uriProtocol);
57
58 uri = L"file://localhost/";
59 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
60 hr = UriProtocol(uri, &uriProtocol);
61 ExitOnFailure(hr, "Failed to determine UriProtocol");
62 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_FILE, (int)uriProtocol);
63
64 uri = L"FILE://localhost/";
65 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
66 hr = UriProtocol(uri, &uriProtocol);
67 ExitOnFailure(hr, "Failed to determine UriProtocol");
68 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_FILE, (int)uriProtocol);
69
70 uri = L"FiLe://localhost/";
71 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
72 hr = UriProtocol(uri, &uriProtocol);
73 ExitOnFailure(hr, "Failed to determine UriProtocol");
74 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_FILE, (int)uriProtocol);
75
76 uri = L"FTP://localhost/";
77 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
78 hr = UriProtocol(uri, &uriProtocol);
79 ExitOnFailure(hr, "Failed to determine UriProtocol");
80 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_FTP, (int)uriProtocol);
81
82 uri = L"ftp://localhost/";
83 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
84 hr = UriProtocol(uri, &uriProtocol);
85 ExitOnFailure(hr, "Failed to determine UriProtocol");
86 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_FTP, (int)uriProtocol);
87
88 uri = L"FtP://localhost/";
89 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
90 hr = UriProtocol(uri, &uriProtocol);
91 ExitOnFailure(hr, "Failed to determine UriProtocol");
92 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_FTP, (int)uriProtocol);
93
94 LExit:
95 DutilUninitialize();
96 }
97 };
98}
diff --git a/src/libs/dutil/test/DUtilUnitTest/VerUtilTests.cpp b/src/libs/dutil/test/DUtilUnitTest/VerUtilTests.cpp
new file mode 100644
index 00000000..8f24ad1a
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/VerUtilTests.cpp
@@ -0,0 +1,933 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9namespace DutilTests
10{
11 public ref class VerUtil
12 {
13 public:
14 [Fact]
15 void VerCompareVersionsTreatsMissingRevisionAsZero()
16 {
17 HRESULT hr = S_OK;
18 VERUTIL_VERSION* pVersion1 = NULL;
19 VERUTIL_VERSION* pVersion2 = NULL;
20 VERUTIL_VERSION* pVersion3 = NULL;
21 LPCWSTR wzVersion1 = L"1.2.3.4";
22 LPCWSTR wzVersion2 = L"1.2.3";
23 LPCWSTR wzVersion3 = L"1.2.3.0";
24
25 try
26 {
27 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
28 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
29
30 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
31 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
32
33 hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3);
34 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3);
35
36 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
37 Assert::Equal<DWORD>(1, pVersion1->dwMajor);
38 Assert::Equal<DWORD>(2, pVersion1->dwMinor);
39 Assert::Equal<DWORD>(3, pVersion1->dwPatch);
40 Assert::Equal<DWORD>(4, pVersion1->dwRevision);
41 Assert::Equal<DWORD>(0, pVersion1->cReleaseLabels);
42 Assert::Equal<DWORD>(7, pVersion1->cchMetadataOffset);
43 Assert::Equal<BOOL>(FALSE, pVersion1->fInvalid);
44
45 NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion);
46 Assert::Equal<DWORD>(1, pVersion2->dwMajor);
47 Assert::Equal<DWORD>(2, pVersion2->dwMinor);
48 Assert::Equal<DWORD>(3, pVersion2->dwPatch);
49 Assert::Equal<DWORD>(0, pVersion2->dwRevision);
50 Assert::Equal<DWORD>(0, pVersion2->cReleaseLabels);
51 Assert::Equal<DWORD>(5, pVersion2->cchMetadataOffset);
52 Assert::Equal<BOOL>(FALSE, pVersion2->fInvalid);
53
54 NativeAssert::StringEqual(wzVersion3, pVersion3->sczVersion);
55 Assert::Equal<DWORD>(1, pVersion3->dwMajor);
56 Assert::Equal<DWORD>(2, pVersion3->dwMinor);
57 Assert::Equal<DWORD>(3, pVersion3->dwPatch);
58 Assert::Equal<DWORD>(0, pVersion3->dwRevision);
59 Assert::Equal<DWORD>(0, pVersion3->cReleaseLabels);
60 Assert::Equal<DWORD>(7, pVersion3->cchMetadataOffset);
61 Assert::Equal<BOOL>(FALSE, pVersion3->fInvalid);
62
63 TestVerutilCompareParsedVersions(pVersion1, pVersion2, 1);
64 TestVerutilCompareParsedVersions(pVersion3, pVersion2, 0);
65 }
66 finally
67 {
68 ReleaseVerutilVersion(pVersion1);
69 ReleaseVerutilVersion(pVersion2);
70 ReleaseVerutilVersion(pVersion3);
71 }
72 }
73
74 [Fact]
75 void VerCompareVersionsTreatsNumericReleaseLabelsAsNumbers()
76 {
77 HRESULT hr = S_OK;
78 VERUTIL_VERSION* pVersion1 = NULL;
79 VERUTIL_VERSION* pVersion2 = NULL;
80 LPCWSTR wzVersion1 = L"1.0-2.0";
81 LPCWSTR wzVersion2 = L"1.0-19";
82
83 try
84 {
85 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
86 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
87
88 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
89 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
90
91 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
92 Assert::Equal<DWORD>(1, pVersion1->dwMajor);
93 Assert::Equal<DWORD>(0, pVersion1->dwMinor);
94 Assert::Equal<DWORD>(0, pVersion1->dwPatch);
95 Assert::Equal<DWORD>(0, pVersion1->dwRevision);
96 Assert::Equal<DWORD>(2, pVersion1->cReleaseLabels);
97
98 Assert::Equal<BOOL>(TRUE, pVersion1->rgReleaseLabels[0].fNumeric);
99 Assert::Equal<DWORD>(2, pVersion1->rgReleaseLabels[0].dwValue);
100 Assert::Equal<DWORD>(1, pVersion1->rgReleaseLabels[0].cchLabel);
101 Assert::Equal<DWORD>(4, pVersion1->rgReleaseLabels[0].cchLabelOffset);
102
103 Assert::Equal<BOOL>(TRUE, pVersion1->rgReleaseLabels[1].fNumeric);
104 Assert::Equal<DWORD>(0, pVersion1->rgReleaseLabels[1].dwValue);
105 Assert::Equal<DWORD>(1, pVersion1->rgReleaseLabels[1].cchLabel);
106 Assert::Equal<DWORD>(6, pVersion1->rgReleaseLabels[1].cchLabelOffset);
107
108 Assert::Equal<DWORD>(7, pVersion1->cchMetadataOffset);
109 Assert::Equal<BOOL>(FALSE, pVersion1->fInvalid);
110
111 NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion);
112 Assert::Equal<DWORD>(1, pVersion2->dwMajor);
113 Assert::Equal<DWORD>(0, pVersion2->dwMinor);
114 Assert::Equal<DWORD>(0, pVersion2->dwPatch);
115 Assert::Equal<DWORD>(0, pVersion2->dwRevision);
116 Assert::Equal<DWORD>(1, pVersion2->cReleaseLabels);
117
118 Assert::Equal<BOOL>(TRUE, pVersion2->rgReleaseLabels[0].fNumeric);
119 Assert::Equal<DWORD>(19, pVersion2->rgReleaseLabels[0].dwValue);
120 Assert::Equal<DWORD>(2, pVersion2->rgReleaseLabels[0].cchLabel);
121 Assert::Equal<DWORD>(4, pVersion2->rgReleaseLabels[0].cchLabelOffset);
122
123 Assert::Equal<DWORD>(6, pVersion2->cchMetadataOffset);
124 Assert::Equal<BOOL>(FALSE, pVersion2->fInvalid);
125
126 TestVerutilCompareParsedVersions(pVersion1, pVersion2, -1);
127 }
128 finally
129 {
130 ReleaseVerutilVersion(pVersion1);
131 ReleaseVerutilVersion(pVersion2);
132 }
133 }
134
135 [Fact]
136 void VerCompareVersionsHandlesNormallyInvalidVersions()
137 {
138 HRESULT hr = S_OK;
139 VERUTIL_VERSION* pVersion1 = NULL;
140 VERUTIL_VERSION* pVersion2 = NULL;
141 VERUTIL_VERSION* pVersion3 = NULL;
142 VERUTIL_VERSION* pVersion4 = NULL;
143 VERUTIL_VERSION* pVersion5 = NULL;
144 VERUTIL_VERSION* pVersion6 = NULL;
145 LPCWSTR wzVersion1 = L"10.-4.0";
146 LPCWSTR wzVersion2 = L"10.-2.0";
147 LPCWSTR wzVersion3 = L"0";
148 LPCWSTR wzVersion4 = L"";
149 LPCWSTR wzVersion5 = L"10-2";
150 LPCWSTR wzVersion6 = L"10-4.@";
151
152 try
153 {
154 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
155 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
156
157 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
158 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
159
160 hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3);
161 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3);
162
163 hr = VerParseVersion(wzVersion4, 0, FALSE, &pVersion4);
164 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion4);
165
166 hr = VerParseVersion(wzVersion5, 0, FALSE, &pVersion5);
167 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion5);
168
169 hr = VerParseVersion(wzVersion6, 0, FALSE, &pVersion6);
170 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion6);
171
172 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
173 Assert::Equal<DWORD>(10, pVersion1->dwMajor);
174 Assert::Equal<DWORD>(0, pVersion1->dwMinor);
175 Assert::Equal<DWORD>(0, pVersion1->dwPatch);
176 Assert::Equal<DWORD>(0, pVersion1->dwRevision);
177 Assert::Equal<DWORD>(0, pVersion1->cReleaseLabels);
178 Assert::Equal<DWORD>(3, pVersion1->cchMetadataOffset);
179 Assert::Equal<BOOL>(TRUE, pVersion1->fInvalid);
180
181 NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion);
182 Assert::Equal<DWORD>(10, pVersion2->dwMajor);
183 Assert::Equal<DWORD>(0, pVersion2->dwMinor);
184 Assert::Equal<DWORD>(0, pVersion2->dwPatch);
185 Assert::Equal<DWORD>(0, pVersion2->dwRevision);
186 Assert::Equal<DWORD>(0, pVersion2->cReleaseLabels);
187 Assert::Equal<DWORD>(3, pVersion2->cchMetadataOffset);
188 Assert::Equal<BOOL>(TRUE, pVersion2->fInvalid);
189
190 NativeAssert::StringEqual(wzVersion3, pVersion3->sczVersion);
191 Assert::Equal<DWORD>(0, pVersion3->dwMajor);
192 Assert::Equal<DWORD>(0, pVersion3->dwMinor);
193 Assert::Equal<DWORD>(0, pVersion3->dwPatch);
194 Assert::Equal<DWORD>(0, pVersion3->dwRevision);
195 Assert::Equal<DWORD>(0, pVersion3->cReleaseLabels);
196 Assert::Equal<DWORD>(1, pVersion3->cchMetadataOffset);
197 Assert::Equal<BOOL>(FALSE, pVersion3->fInvalid);
198
199 NativeAssert::StringEqual(wzVersion4, pVersion4->sczVersion);
200 Assert::Equal<DWORD>(0, pVersion4->dwMajor);
201 Assert::Equal<DWORD>(0, pVersion4->dwMinor);
202 Assert::Equal<DWORD>(0, pVersion4->dwPatch);
203 Assert::Equal<DWORD>(0, pVersion4->dwRevision);
204 Assert::Equal<DWORD>(0, pVersion4->cReleaseLabels);
205 Assert::Equal<DWORD>(0, pVersion4->cchMetadataOffset);
206 Assert::Equal<BOOL>(TRUE, pVersion4->fInvalid);
207
208 NativeAssert::StringEqual(wzVersion5, pVersion5->sczVersion);
209 Assert::Equal<DWORD>(10, pVersion5->dwMajor);
210 Assert::Equal<DWORD>(0, pVersion5->dwMinor);
211 Assert::Equal<DWORD>(0, pVersion5->dwPatch);
212 Assert::Equal<DWORD>(0, pVersion5->dwRevision);
213 Assert::Equal<DWORD>(1, pVersion5->cReleaseLabels);
214
215 Assert::Equal<BOOL>(TRUE, pVersion5->rgReleaseLabels[0].fNumeric);
216 Assert::Equal<DWORD>(2, pVersion5->rgReleaseLabels[0].dwValue);
217 Assert::Equal<DWORD>(1, pVersion5->rgReleaseLabels[0].cchLabel);
218 Assert::Equal<DWORD>(3, pVersion5->rgReleaseLabels[0].cchLabelOffset);
219
220 Assert::Equal<DWORD>(4, pVersion5->cchMetadataOffset);
221 Assert::Equal<BOOL>(FALSE, pVersion5->fInvalid);
222
223 NativeAssert::StringEqual(wzVersion6, pVersion6->sczVersion);
224 Assert::Equal<DWORD>(10, pVersion6->dwMajor);
225 Assert::Equal<DWORD>(0, pVersion6->dwMinor);
226 Assert::Equal<DWORD>(0, pVersion6->dwPatch);
227 Assert::Equal<DWORD>(0, pVersion6->dwRevision);
228 Assert::Equal<DWORD>(1, pVersion6->cReleaseLabels);
229
230 Assert::Equal<BOOL>(TRUE, pVersion6->rgReleaseLabels[0].fNumeric);
231 Assert::Equal<DWORD>(4, pVersion6->rgReleaseLabels[0].dwValue);
232 Assert::Equal<DWORD>(1, pVersion6->rgReleaseLabels[0].cchLabel);
233 Assert::Equal<DWORD>(3, pVersion6->rgReleaseLabels[0].cchLabelOffset);
234
235 Assert::Equal<DWORD>(5, pVersion6->cchMetadataOffset);
236 Assert::Equal<BOOL>(TRUE, pVersion6->fInvalid);
237
238 TestVerutilCompareParsedVersions(pVersion1, pVersion2, 1);
239 TestVerutilCompareParsedVersions(pVersion3, pVersion4, 1);
240 TestVerutilCompareParsedVersions(pVersion5, pVersion6, -1);
241 }
242 finally
243 {
244 ReleaseVerutilVersion(pVersion1);
245 ReleaseVerutilVersion(pVersion2);
246 ReleaseVerutilVersion(pVersion3);
247 ReleaseVerutilVersion(pVersion4);
248 ReleaseVerutilVersion(pVersion5);
249 ReleaseVerutilVersion(pVersion6);
250 }
251 }
252
253 [Fact]
254 void VerCompareVersionsTreatsHyphenAsVersionSeparator()
255 {
256 HRESULT hr = S_OK;
257 VERUTIL_VERSION* pVersion1 = NULL;
258 VERUTIL_VERSION* pVersion2 = NULL;
259 VERUTIL_VERSION* pVersion3 = NULL;
260 LPCWSTR wzVersion1 = L"0.0.1-a";
261 LPCWSTR wzVersion2 = L"0-2";
262 LPCWSTR wzVersion3 = L"1-2";
263
264 try
265 {
266 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
267 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
268
269 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
270 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
271
272 hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3);
273 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3);
274
275 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
276 Assert::Equal<DWORD>(0, pVersion1->dwMajor);
277 Assert::Equal<DWORD>(0, pVersion1->dwMinor);
278 Assert::Equal<DWORD>(1, pVersion1->dwPatch);
279 Assert::Equal<DWORD>(0, pVersion1->dwRevision);
280 Assert::Equal<DWORD>(1, pVersion1->cReleaseLabels);
281
282 Assert::Equal<BOOL>(FALSE, pVersion1->rgReleaseLabels[0].fNumeric);
283 Assert::Equal<DWORD>(1, pVersion1->rgReleaseLabels[0].cchLabel);
284 Assert::Equal<DWORD>(6, pVersion1->rgReleaseLabels[0].cchLabelOffset);
285
286 Assert::Equal<DWORD>(7, pVersion1->cchMetadataOffset);
287 Assert::Equal<BOOL>(FALSE, pVersion1->fInvalid);
288
289 NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion);
290 Assert::Equal<DWORD>(0, pVersion2->dwMajor);
291 Assert::Equal<DWORD>(0, pVersion2->dwMinor);
292 Assert::Equal<DWORD>(0, pVersion2->dwPatch);
293 Assert::Equal<DWORD>(0, pVersion2->dwRevision);
294 Assert::Equal<DWORD>(1, pVersion2->cReleaseLabels);
295
296 Assert::Equal<BOOL>(TRUE, pVersion2->rgReleaseLabels[0].fNumeric);
297 Assert::Equal<DWORD>(2, pVersion2->rgReleaseLabels[0].dwValue);
298 Assert::Equal<DWORD>(1, pVersion2->rgReleaseLabels[0].cchLabel);
299 Assert::Equal<DWORD>(2, pVersion2->rgReleaseLabels[0].cchLabelOffset);
300
301 Assert::Equal<DWORD>(3, pVersion2->cchMetadataOffset);
302 Assert::Equal<BOOL>(FALSE, pVersion2->fInvalid);
303
304 NativeAssert::StringEqual(wzVersion3, pVersion3->sczVersion);
305 Assert::Equal<DWORD>(1, pVersion3->dwMajor);
306 Assert::Equal<DWORD>(0, pVersion3->dwMinor);
307 Assert::Equal<DWORD>(0, pVersion3->dwPatch);
308 Assert::Equal<DWORD>(0, pVersion3->dwRevision);
309 Assert::Equal<DWORD>(1, pVersion3->cReleaseLabels);
310
311 Assert::Equal<BOOL>(TRUE, pVersion3->rgReleaseLabels[0].fNumeric);
312 Assert::Equal<DWORD>(2, pVersion3->rgReleaseLabels[0].dwValue);
313 Assert::Equal<DWORD>(1, pVersion3->rgReleaseLabels[0].cchLabel);
314 Assert::Equal<DWORD>(2, pVersion3->rgReleaseLabels[0].cchLabelOffset);
315
316 Assert::Equal<DWORD>(3, pVersion3->cchMetadataOffset);
317 Assert::Equal<BOOL>(FALSE, pVersion3->fInvalid);
318
319 TestVerutilCompareParsedVersions(pVersion1, pVersion2, 1);
320 TestVerutilCompareParsedVersions(pVersion1, pVersion3, -1);
321 }
322 finally
323 {
324 ReleaseVerutilVersion(pVersion1);
325 ReleaseVerutilVersion(pVersion2);
326 ReleaseVerutilVersion(pVersion3);
327 }
328 }
329
330 [Fact]
331 void VerCompareVersionsIgnoresLeadingZeroes()
332 {
333 HRESULT hr = S_OK;
334 VERUTIL_VERSION* pVersion1 = NULL;
335 VERUTIL_VERSION* pVersion2 = NULL;
336 VERUTIL_VERSION* pVersion3 = NULL;
337 VERUTIL_VERSION* pVersion4 = NULL;
338 LPCWSTR wzVersion1 = L"0.01-a.1";
339 LPCWSTR wzVersion2 = L"0.1.0-a.1";
340 LPCWSTR wzVersion3 = L"0.1-a.b.0";
341 LPCWSTR wzVersion4 = L"0.1.0-a.b.000";
342
343 try
344 {
345 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
346 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
347
348 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
349 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
350
351 hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3);
352 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3);
353
354 hr = VerParseVersion(wzVersion4, 0, FALSE, &pVersion4);
355 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion4);
356
357 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
358 Assert::Equal<DWORD>(0, pVersion1->dwMajor);
359 Assert::Equal<DWORD>(1, pVersion1->dwMinor);
360 Assert::Equal<DWORD>(0, pVersion1->dwPatch);
361 Assert::Equal<DWORD>(0, pVersion1->dwRevision);
362 Assert::Equal<DWORD>(2, pVersion1->cReleaseLabels);
363
364 Assert::Equal<BOOL>(FALSE, pVersion1->rgReleaseLabels[0].fNumeric);
365 Assert::Equal<DWORD>(1, pVersion1->rgReleaseLabels[0].cchLabel);
366 Assert::Equal<DWORD>(5, pVersion1->rgReleaseLabels[0].cchLabelOffset);
367
368 Assert::Equal<BOOL>(TRUE, pVersion1->rgReleaseLabels[1].fNumeric);
369 Assert::Equal<DWORD>(1, pVersion1->rgReleaseLabels[1].dwValue);
370 Assert::Equal<DWORD>(1, pVersion1->rgReleaseLabels[1].cchLabel);
371 Assert::Equal<DWORD>(7, pVersion1->rgReleaseLabels[1].cchLabelOffset);
372
373 Assert::Equal<DWORD>(8, pVersion1->cchMetadataOffset);
374 Assert::Equal<BOOL>(FALSE, pVersion1->fInvalid);
375
376 NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion);
377 Assert::Equal<DWORD>(0, pVersion2->dwMajor);
378 Assert::Equal<DWORD>(1, pVersion2->dwMinor);
379 Assert::Equal<DWORD>(0, pVersion2->dwPatch);
380 Assert::Equal<DWORD>(0, pVersion2->dwRevision);
381 Assert::Equal<DWORD>(2, pVersion2->cReleaseLabels);
382
383 Assert::Equal<BOOL>(FALSE, pVersion2->rgReleaseLabels[0].fNumeric);
384 Assert::Equal<DWORD>(1, pVersion2->rgReleaseLabels[0].cchLabel);
385 Assert::Equal<DWORD>(6, pVersion2->rgReleaseLabels[0].cchLabelOffset);
386
387 Assert::Equal<BOOL>(TRUE, pVersion2->rgReleaseLabels[1].fNumeric);
388 Assert::Equal<DWORD>(1, pVersion2->rgReleaseLabels[1].dwValue);
389 Assert::Equal<DWORD>(1, pVersion2->rgReleaseLabels[1].cchLabel);
390 Assert::Equal<DWORD>(8, pVersion2->rgReleaseLabels[1].cchLabelOffset);
391
392 Assert::Equal<DWORD>(9, pVersion2->cchMetadataOffset);
393 Assert::Equal<BOOL>(FALSE, pVersion2->fInvalid);
394
395 NativeAssert::StringEqual(wzVersion3, pVersion3->sczVersion);
396 Assert::Equal<DWORD>(0, pVersion3->dwMajor);
397 Assert::Equal<DWORD>(1, pVersion3->dwMinor);
398 Assert::Equal<DWORD>(0, pVersion3->dwPatch);
399 Assert::Equal<DWORD>(0, pVersion3->dwRevision);
400 Assert::Equal<DWORD>(3, pVersion3->cReleaseLabels);
401
402 Assert::Equal<BOOL>(FALSE, pVersion3->rgReleaseLabels[0].fNumeric);
403 Assert::Equal<DWORD>(1, pVersion3->rgReleaseLabels[0].cchLabel);
404 Assert::Equal<DWORD>(4, pVersion3->rgReleaseLabels[0].cchLabelOffset);
405
406 Assert::Equal<BOOL>(FALSE, pVersion3->rgReleaseLabels[1].fNumeric);
407 Assert::Equal<DWORD>(1, pVersion3->rgReleaseLabels[1].cchLabel);
408 Assert::Equal<DWORD>(6, pVersion3->rgReleaseLabels[1].cchLabelOffset);
409
410 Assert::Equal<BOOL>(TRUE, pVersion3->rgReleaseLabels[2].fNumeric);
411 Assert::Equal<DWORD>(0, pVersion3->rgReleaseLabels[2].dwValue);
412 Assert::Equal<DWORD>(1, pVersion3->rgReleaseLabels[2].cchLabel);
413 Assert::Equal<DWORD>(8, pVersion3->rgReleaseLabels[2].cchLabelOffset);
414
415 Assert::Equal<DWORD>(9, pVersion3->cchMetadataOffset);
416 Assert::Equal<BOOL>(FALSE, pVersion3->fInvalid);
417
418 NativeAssert::StringEqual(wzVersion4, pVersion4->sczVersion);
419 Assert::Equal<DWORD>(0, pVersion4->dwMajor);
420 Assert::Equal<DWORD>(1, pVersion4->dwMinor);
421 Assert::Equal<DWORD>(0, pVersion4->dwPatch);
422 Assert::Equal<DWORD>(0, pVersion4->dwRevision);
423 Assert::Equal<DWORD>(3, pVersion4->cReleaseLabels);
424
425 Assert::Equal<BOOL>(FALSE, pVersion4->rgReleaseLabels[0].fNumeric);
426 Assert::Equal<DWORD>(1, pVersion4->rgReleaseLabels[0].cchLabel);
427 Assert::Equal<DWORD>(6, pVersion4->rgReleaseLabels[0].cchLabelOffset);
428
429 Assert::Equal<BOOL>(FALSE, pVersion4->rgReleaseLabels[1].fNumeric);
430 Assert::Equal<DWORD>(1, pVersion4->rgReleaseLabels[1].cchLabel);
431 Assert::Equal<DWORD>(8, pVersion4->rgReleaseLabels[1].cchLabelOffset);
432
433 Assert::Equal<BOOL>(TRUE, pVersion4->rgReleaseLabels[2].fNumeric);
434 Assert::Equal<DWORD>(0, pVersion4->rgReleaseLabels[2].dwValue);
435 Assert::Equal<DWORD>(3, pVersion4->rgReleaseLabels[2].cchLabel);
436 Assert::Equal<DWORD>(10, pVersion4->rgReleaseLabels[2].cchLabelOffset);
437
438 Assert::Equal<DWORD>(13, pVersion4->cchMetadataOffset);
439 Assert::Equal<BOOL>(FALSE, pVersion4->fInvalid);
440
441 TestVerutilCompareParsedVersions(pVersion1, pVersion2, 0);
442 TestVerutilCompareParsedVersions(pVersion3, pVersion4, 0);
443 }
444 finally
445 {
446 ReleaseVerutilVersion(pVersion1);
447 ReleaseVerutilVersion(pVersion2);
448 ReleaseVerutilVersion(pVersion3);
449 ReleaseVerutilVersion(pVersion4);
450 }
451 }
452
453 [Fact]
454 void VerCompareVersionsTreatsUnexpectedContentAsMetadata()
455 {
456 HRESULT hr = S_OK;
457 VERUTIL_VERSION* pVersion1 = NULL;
458 VERUTIL_VERSION* pVersion2 = NULL;
459 VERUTIL_VERSION* pVersion3 = NULL;
460 LPCWSTR wzVersion1 = L"1.2.3+abcd";
461 LPCWSTR wzVersion2 = L"1.2.3.abcd";
462 LPCWSTR wzVersion3 = L"1.2.3.-abcd";
463
464 try
465 {
466 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
467 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
468
469 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
470 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
471
472 hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3);
473 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3);
474
475 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
476 Assert::Equal<DWORD>(1, pVersion1->dwMajor);
477 Assert::Equal<DWORD>(2, pVersion1->dwMinor);
478 Assert::Equal<DWORD>(3, pVersion1->dwPatch);
479 Assert::Equal<DWORD>(0, pVersion1->dwRevision);
480 Assert::Equal<DWORD>(0, pVersion1->cReleaseLabels);
481 Assert::Equal<DWORD>(6, pVersion1->cchMetadataOffset);
482 Assert::Equal<BOOL>(FALSE, pVersion1->fInvalid);
483
484 NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion);
485 Assert::Equal<DWORD>(1, pVersion2->dwMajor);
486 Assert::Equal<DWORD>(2, pVersion2->dwMinor);
487 Assert::Equal<DWORD>(3, pVersion2->dwPatch);
488 Assert::Equal<DWORD>(0, pVersion2->dwRevision);
489 Assert::Equal<DWORD>(0, pVersion2->cReleaseLabels);
490 Assert::Equal<DWORD>(6, pVersion2->cchMetadataOffset);
491 Assert::Equal<BOOL>(TRUE, pVersion2->fInvalid);
492
493 NativeAssert::StringEqual(wzVersion3, pVersion3->sczVersion);
494 Assert::Equal<DWORD>(1, pVersion3->dwMajor);
495 Assert::Equal<DWORD>(2, pVersion3->dwMinor);
496 Assert::Equal<DWORD>(3, pVersion3->dwPatch);
497 Assert::Equal<DWORD>(0, pVersion3->dwRevision);
498 Assert::Equal<DWORD>(0, pVersion3->cReleaseLabels);
499 Assert::Equal<DWORD>(6, pVersion3->cchMetadataOffset);
500 Assert::Equal<BOOL>(TRUE, pVersion3->fInvalid);
501
502 TestVerutilCompareParsedVersions(pVersion1, pVersion2, 1);
503 TestVerutilCompareParsedVersions(pVersion1, pVersion3, 1);
504 TestVerutilCompareParsedVersions(pVersion2, pVersion3, -1);
505 }
506 finally
507 {
508 ReleaseVerutilVersion(pVersion1);
509 ReleaseVerutilVersion(pVersion2);
510 ReleaseVerutilVersion(pVersion3);
511 }
512 }
513
514 [Fact]
515 void VerCompareVersionsIgnoresLeadingV()
516 {
517 HRESULT hr = S_OK;
518 VERUTIL_VERSION* pVersion1 = NULL;
519 VERUTIL_VERSION* pVersion2 = NULL;
520 VERUTIL_VERSION* pVersion3 = NULL;
521 LPCWSTR wzVersion1 = L"10.20.30.40";
522 LPCWSTR wzVersion2 = L"v10.20.30.40";
523 LPCWSTR wzVersion3 = L"V10.20.30.40";
524
525 try
526 {
527 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
528 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
529
530 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
531 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
532
533 hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3);
534 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3);
535
536 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
537 Assert::Equal<DWORD>(10, pVersion1->dwMajor);
538 Assert::Equal<DWORD>(20, pVersion1->dwMinor);
539 Assert::Equal<DWORD>(30, pVersion1->dwPatch);
540 Assert::Equal<DWORD>(40, pVersion1->dwRevision);
541 Assert::Equal<DWORD>(0, pVersion1->cReleaseLabels);
542 Assert::Equal<DWORD>(11, pVersion1->cchMetadataOffset);
543 Assert::Equal<BOOL>(FALSE, pVersion1->fInvalid);
544
545 NativeAssert::StringEqual(wzVersion1, pVersion2->sczVersion);
546 Assert::Equal<DWORD>(10, pVersion2->dwMajor);
547 Assert::Equal<DWORD>(20, pVersion2->dwMinor);
548 Assert::Equal<DWORD>(30, pVersion2->dwPatch);
549 Assert::Equal<DWORD>(40, pVersion2->dwRevision);
550 Assert::Equal<DWORD>(0, pVersion2->cReleaseLabels);
551 Assert::Equal<DWORD>(11, pVersion2->cchMetadataOffset);
552 Assert::Equal<BOOL>(FALSE, pVersion2->fInvalid);
553
554 NativeAssert::StringEqual(wzVersion1, pVersion3->sczVersion);
555 Assert::Equal<DWORD>(10, pVersion3->dwMajor);
556 Assert::Equal<DWORD>(20, pVersion3->dwMinor);
557 Assert::Equal<DWORD>(30, pVersion3->dwPatch);
558 Assert::Equal<DWORD>(40, pVersion3->dwRevision);
559 Assert::Equal<DWORD>(0, pVersion3->cReleaseLabels);
560 Assert::Equal<DWORD>(11, pVersion3->cchMetadataOffset);
561 Assert::Equal<BOOL>(FALSE, pVersion3->fInvalid);
562
563 TestVerutilCompareParsedVersions(pVersion1, pVersion2, 0);
564 TestVerutilCompareParsedVersions(pVersion1, pVersion3, 0);
565 }
566 finally
567 {
568 ReleaseVerutilVersion(pVersion1);
569 ReleaseVerutilVersion(pVersion2);
570 ReleaseVerutilVersion(pVersion3);
571 }
572 }
573
574 [Fact]
575 void VerCompareVersionsHandlesTooLargeNumbers()
576 {
577 HRESULT hr = S_OK;
578 VERUTIL_VERSION* pVersion1 = NULL;
579 VERUTIL_VERSION* pVersion2 = NULL;
580 LPCWSTR wzVersion1 = L"4294967295.4294967295.4294967295.4294967295";
581 LPCWSTR wzVersion2 = L"4294967296.4294967296.4294967296.4294967296";
582
583 try
584 {
585 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
586 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
587
588 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
589 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
590
591 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
592 Assert::Equal<DWORD>(4294967295, pVersion1->dwMajor);
593 Assert::Equal<DWORD>(4294967295, pVersion1->dwMinor);
594 Assert::Equal<DWORD>(4294967295, pVersion1->dwPatch);
595 Assert::Equal<DWORD>(4294967295, pVersion1->dwRevision);
596 Assert::Equal<DWORD>(0, pVersion1->cReleaseLabels);
597 Assert::Equal<DWORD>(43, pVersion1->cchMetadataOffset);
598 Assert::Equal<BOOL>(FALSE, pVersion1->fInvalid);
599
600 NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion);
601 Assert::Equal<DWORD>(0, pVersion2->dwMajor);
602 Assert::Equal<DWORD>(0, pVersion2->dwMinor);
603 Assert::Equal<DWORD>(0, pVersion2->dwPatch);
604 Assert::Equal<DWORD>(0, pVersion2->dwRevision);
605 Assert::Equal<DWORD>(0, pVersion2->cReleaseLabels);
606 Assert::Equal<DWORD>(0, pVersion2->cchMetadataOffset);
607 Assert::Equal<BOOL>(TRUE, pVersion2->fInvalid);
608
609 TestVerutilCompareParsedVersions(pVersion1, pVersion2, 1);
610 }
611 finally
612 {
613 ReleaseVerutilVersion(pVersion1);
614 ReleaseVerutilVersion(pVersion2);
615 }
616 }
617
618 [Fact]
619 void VerCompareVersionsIgnoresMetadataForValidVersions()
620 {
621 HRESULT hr = S_OK;
622 VERUTIL_VERSION* pVersion1 = NULL;
623 VERUTIL_VERSION* pVersion2 = NULL;
624 LPCWSTR wzVersion1 = L"1.2.3+abc";
625 LPCWSTR wzVersion2 = L"1.2.3+xyz";
626
627 try
628 {
629 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
630 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
631
632 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
633 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
634
635 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
636 Assert::Equal<DWORD>(1, pVersion1->dwMajor);
637 Assert::Equal<DWORD>(2, pVersion1->dwMinor);
638 Assert::Equal<DWORD>(3, pVersion1->dwPatch);
639 Assert::Equal<DWORD>(0, pVersion1->dwRevision);
640 Assert::Equal<DWORD>(0, pVersion1->cReleaseLabels);
641 Assert::Equal<DWORD>(6, pVersion1->cchMetadataOffset);
642 Assert::Equal<BOOL>(FALSE, pVersion1->fInvalid);
643
644 NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion);
645 Assert::Equal<DWORD>(1, pVersion2->dwMajor);
646 Assert::Equal<DWORD>(2, pVersion2->dwMinor);
647 Assert::Equal<DWORD>(3, pVersion2->dwPatch);
648 Assert::Equal<DWORD>(0, pVersion2->dwRevision);
649 Assert::Equal<DWORD>(0, pVersion2->cReleaseLabels);
650 Assert::Equal<DWORD>(6, pVersion2->cchMetadataOffset);
651 Assert::Equal<BOOL>(FALSE, pVersion2->fInvalid);
652
653 TestVerutilCompareParsedVersions(pVersion1, pVersion2, 0);
654 }
655 finally
656 {
657 ReleaseVerutilVersion(pVersion1);
658 ReleaseVerutilVersion(pVersion2);
659 }
660 }
661
662 [Fact]
663 void VerCopyVersionCopiesVersion()
664 {
665 HRESULT hr = S_OK;
666 LPCWSTR wzVersion = L"1.2.3.4+abc123";
667 VERUTIL_VERSION* pSource = NULL;
668 VERUTIL_VERSION* pCopy = NULL;
669 int nResult = 0;
670
671 try
672 {
673 hr = VerParseVersion(wzVersion, 0, FALSE, &pSource);
674 NativeAssert::Succeeded(hr, "VerParseVersion failed");
675
676 NativeAssert::StringEqual(wzVersion, pSource->sczVersion);
677 Assert::Equal<DWORD>(1, pSource->dwMajor);
678 Assert::Equal<DWORD>(2, pSource->dwMinor);
679 Assert::Equal<DWORD>(3, pSource->dwPatch);
680 Assert::Equal<DWORD>(4, pSource->dwRevision);
681 Assert::Equal<DWORD>(0, pSource->cReleaseLabels);
682
683 Assert::Equal<DWORD>(8, pSource->cchMetadataOffset);
684 Assert::Equal<BOOL>(FALSE, pSource->fInvalid);
685
686 hr = VerCopyVersion(pSource, &pCopy);
687 NativeAssert::Succeeded(hr, "VerCopyVersion failed");
688
689 Assert::False(pSource == pCopy);
690 Assert::False(pSource->sczVersion == pCopy->sczVersion);
691
692 hr = VerCompareParsedVersions(pSource, pCopy, &nResult);
693 NativeAssert::Succeeded(hr, "VerCompareParsedVersions failed");
694
695 Assert::Equal<int>(nResult, 0);
696 }
697 finally
698 {
699 ReleaseVerutilVersion(pCopy);
700 ReleaseVerutilVersion(pSource);
701 }
702 }
703
704 [Fact]
705 void VerCopyVersionCopiesPrereleaseVersion()
706 {
707 HRESULT hr = S_OK;
708 LPCWSTR wzVersion = L"1.2.3.4-a.b.c.d.5.+abc123";
709 VERUTIL_VERSION* pSource = NULL;
710 VERUTIL_VERSION* pCopy = NULL;
711 int nResult = 0;
712
713 try
714 {
715 hr = VerParseVersion(wzVersion, 0, FALSE, &pSource);
716 NativeAssert::Succeeded(hr, "VerParseVersion failed");
717
718 NativeAssert::StringEqual(wzVersion, pSource->sczVersion);
719 Assert::Equal<DWORD>(1, pSource->dwMajor);
720 Assert::Equal<DWORD>(2, pSource->dwMinor);
721 Assert::Equal<DWORD>(3, pSource->dwPatch);
722 Assert::Equal<DWORD>(4, pSource->dwRevision);
723 Assert::Equal<DWORD>(5, pSource->cReleaseLabels);
724
725 Assert::Equal<BOOL>(FALSE, pSource->rgReleaseLabels[0].fNumeric);
726 Assert::Equal<DWORD>(1, pSource->rgReleaseLabels[0].cchLabel);
727 Assert::Equal<DWORD>(8, pSource->rgReleaseLabels[0].cchLabelOffset);
728
729 Assert::Equal<BOOL>(FALSE, pSource->rgReleaseLabels[1].fNumeric);
730 Assert::Equal<DWORD>(1, pSource->rgReleaseLabels[1].cchLabel);
731 Assert::Equal<DWORD>(10, pSource->rgReleaseLabels[1].cchLabelOffset);
732
733 Assert::Equal<BOOL>(FALSE, pSource->rgReleaseLabels[2].fNumeric);
734 Assert::Equal<DWORD>(1, pSource->rgReleaseLabels[2].cchLabel);
735 Assert::Equal<DWORD>(12, pSource->rgReleaseLabels[2].cchLabelOffset);
736
737 Assert::Equal<BOOL>(FALSE, pSource->rgReleaseLabels[3].fNumeric);
738 Assert::Equal<DWORD>(1, pSource->rgReleaseLabels[3].cchLabel);
739 Assert::Equal<DWORD>(14, pSource->rgReleaseLabels[3].cchLabelOffset);
740
741 Assert::Equal<BOOL>(TRUE, pSource->rgReleaseLabels[4].fNumeric);
742 Assert::Equal<DWORD>(5, pSource->rgReleaseLabels[4].dwValue);
743 Assert::Equal<DWORD>(1, pSource->rgReleaseLabels[4].cchLabel);
744 Assert::Equal<DWORD>(16, pSource->rgReleaseLabels[4].cchLabelOffset);
745
746 Assert::Equal<DWORD>(18, pSource->cchMetadataOffset);
747 Assert::Equal<BOOL>(TRUE, pSource->fInvalid);
748
749 hr = VerCopyVersion(pSource, &pCopy);
750 NativeAssert::Succeeded(hr, "VerCopyVersion failed");
751
752 Assert::False(pSource == pCopy);
753 Assert::False(pSource->sczVersion == pCopy->sczVersion);
754 Assert::False(pSource->rgReleaseLabels == pCopy->rgReleaseLabels);
755
756 hr = VerCompareParsedVersions(pSource, pCopy, &nResult);
757 NativeAssert::Succeeded(hr, "VerCompareParsedVersions failed");
758
759 Assert::Equal<int>(nResult, 0);
760 }
761 finally
762 {
763 ReleaseVerutilVersion(pCopy);
764 ReleaseVerutilVersion(pSource);
765 }
766 }
767
768 [Fact]
769 void VerParseVersionTreatsTrailingDotsAsInvalid()
770 {
771 HRESULT hr = S_OK;
772 VERUTIL_VERSION* pVersion1 = NULL;
773 VERUTIL_VERSION* pVersion2 = NULL;
774 VERUTIL_VERSION* pVersion3 = NULL;
775 VERUTIL_VERSION* pVersion4 = NULL;
776 VERUTIL_VERSION* pVersion5 = NULL;
777 VERUTIL_VERSION* pVersion6 = NULL;
778 VERUTIL_VERSION* pVersion7 = NULL;
779 LPCWSTR wzVersion1 = L".";
780 LPCWSTR wzVersion2 = L"1.";
781 LPCWSTR wzVersion3 = L"2.1.";
782 LPCWSTR wzVersion4 = L"3.2.1.";
783 LPCWSTR wzVersion5 = L"4.3.2.1.";
784 LPCWSTR wzVersion6 = L"5-.";
785 LPCWSTR wzVersion7 = L"6-a.";
786
787 try
788 {
789 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
790 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
791
792 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
793 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
794
795 hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3);
796 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3);
797
798 hr = VerParseVersion(wzVersion4, 0, FALSE, &pVersion4);
799 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion4);
800
801 hr = VerParseVersion(wzVersion5, 0, FALSE, &pVersion5);
802 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion5);
803
804 hr = VerParseVersion(wzVersion6, 0, FALSE, &pVersion6);
805 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion6);
806
807 hr = VerParseVersion(wzVersion7, 0, FALSE, &pVersion7);
808 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion7);
809
810 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
811 Assert::Equal<DWORD>(0, pVersion1->dwMajor);
812 Assert::Equal<DWORD>(0, pVersion1->dwMinor);
813 Assert::Equal<DWORD>(0, pVersion1->dwPatch);
814 Assert::Equal<DWORD>(0, pVersion1->dwRevision);
815 Assert::Equal<DWORD>(0, pVersion1->cReleaseLabels);
816 Assert::Equal<DWORD>(0, pVersion1->cchMetadataOffset);
817 Assert::Equal<BOOL>(TRUE, pVersion1->fInvalid);
818
819 NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion);
820 Assert::Equal<DWORD>(1, pVersion2->dwMajor);
821 Assert::Equal<DWORD>(0, pVersion2->dwMinor);
822 Assert::Equal<DWORD>(0, pVersion2->dwPatch);
823 Assert::Equal<DWORD>(0, pVersion2->dwRevision);
824 Assert::Equal<DWORD>(0, pVersion2->cReleaseLabels);
825 Assert::Equal<DWORD>(2, pVersion2->cchMetadataOffset);
826 Assert::Equal<BOOL>(TRUE, pVersion2->fInvalid);
827
828 NativeAssert::StringEqual(wzVersion3, pVersion3->sczVersion);
829 Assert::Equal<DWORD>(2, pVersion3->dwMajor);
830 Assert::Equal<DWORD>(1, pVersion3->dwMinor);
831 Assert::Equal<DWORD>(0, pVersion3->dwPatch);
832 Assert::Equal<DWORD>(0, pVersion3->dwRevision);
833 Assert::Equal<DWORD>(0, pVersion3->cReleaseLabels);
834 Assert::Equal<DWORD>(4, pVersion3->cchMetadataOffset);
835 Assert::Equal<BOOL>(TRUE, pVersion3->fInvalid);
836
837 NativeAssert::StringEqual(wzVersion4, pVersion4->sczVersion);
838 Assert::Equal<DWORD>(3, pVersion4->dwMajor);
839 Assert::Equal<DWORD>(2, pVersion4->dwMinor);
840 Assert::Equal<DWORD>(1, pVersion4->dwPatch);
841 Assert::Equal<DWORD>(0, pVersion4->dwRevision);
842 Assert::Equal<DWORD>(0, pVersion4->cReleaseLabels);
843 Assert::Equal<DWORD>(6, pVersion4->cchMetadataOffset);
844 Assert::Equal<BOOL>(TRUE, pVersion4->fInvalid);
845
846 NativeAssert::StringEqual(wzVersion5, pVersion5->sczVersion);
847 Assert::Equal<DWORD>(4, pVersion5->dwMajor);
848 Assert::Equal<DWORD>(3, pVersion5->dwMinor);
849 Assert::Equal<DWORD>(2, pVersion5->dwPatch);
850 Assert::Equal<DWORD>(1, pVersion5->dwRevision);
851 Assert::Equal<DWORD>(0, pVersion5->cReleaseLabels);
852 Assert::Equal<DWORD>(8, pVersion5->cchMetadataOffset);
853 Assert::Equal<BOOL>(TRUE, pVersion5->fInvalid);
854
855 NativeAssert::StringEqual(wzVersion6, pVersion6->sczVersion);
856 Assert::Equal<DWORD>(5, pVersion6->dwMajor);
857 Assert::Equal<DWORD>(0, pVersion6->dwMinor);
858 Assert::Equal<DWORD>(0, pVersion6->dwPatch);
859 Assert::Equal<DWORD>(0, pVersion6->dwRevision);
860 Assert::Equal<DWORD>(0, pVersion6->cReleaseLabels);
861 Assert::Equal<DWORD>(2, pVersion6->cchMetadataOffset);
862 Assert::Equal<BOOL>(TRUE, pVersion6->fInvalid);
863
864 NativeAssert::StringEqual(wzVersion7, pVersion7->sczVersion);
865 Assert::Equal<DWORD>(6, pVersion7->dwMajor);
866 Assert::Equal<DWORD>(0, pVersion7->dwMinor);
867 Assert::Equal<DWORD>(0, pVersion7->dwPatch);
868 Assert::Equal<DWORD>(0, pVersion7->dwRevision);
869 Assert::Equal<DWORD>(1, pVersion7->cReleaseLabels);
870
871 Assert::Equal<BOOL>(FALSE, pVersion7->rgReleaseLabels[0].fNumeric);
872 Assert::Equal<DWORD>(1, pVersion7->rgReleaseLabels[0].cchLabel);
873 Assert::Equal<DWORD>(2, pVersion7->rgReleaseLabels[0].cchLabelOffset);
874
875 Assert::Equal<DWORD>(4, pVersion7->cchMetadataOffset);
876 Assert::Equal<BOOL>(TRUE, pVersion7->fInvalid);
877 }
878 finally
879 {
880 ReleaseVerutilVersion(pVersion1);
881 ReleaseVerutilVersion(pVersion2);
882 ReleaseVerutilVersion(pVersion3);
883 ReleaseVerutilVersion(pVersion4);
884 ReleaseVerutilVersion(pVersion5);
885 ReleaseVerutilVersion(pVersion6);
886 ReleaseVerutilVersion(pVersion7);
887 }
888 }
889
890 [Fact]
891 void VerVersionFromQwordCreatesVersion()
892 {
893 HRESULT hr = S_OK;
894 VERUTIL_VERSION* pVersion1 = NULL;
895
896 try
897 {
898 hr = VerVersionFromQword(MAKEQWORDVERSION(1, 2, 3, 4), &pVersion1);
899 NativeAssert::Succeeded(hr, "VerVersionFromQword failed");
900
901 NativeAssert::StringEqual(L"1.2.3.4", pVersion1->sczVersion);
902 Assert::Equal<DWORD>(1, pVersion1->dwMajor);
903 Assert::Equal<DWORD>(2, pVersion1->dwMinor);
904 Assert::Equal<DWORD>(3, pVersion1->dwPatch);
905 Assert::Equal<DWORD>(4, pVersion1->dwRevision);
906 Assert::Equal<DWORD>(0, pVersion1->cReleaseLabels);
907 Assert::Equal<DWORD>(7, pVersion1->cchMetadataOffset);
908 Assert::Equal<BOOL>(FALSE, pVersion1->fInvalid);
909 }
910 finally
911 {
912 ReleaseVerutilVersion(pVersion1);
913 }
914 }
915
916 private:
917 void TestVerutilCompareParsedVersions(VERUTIL_VERSION* pVersion1, VERUTIL_VERSION* pVersion2, int nExpectedResult)
918 {
919 HRESULT hr = S_OK;
920 int nResult = 0;
921
922 hr = VerCompareParsedVersions(pVersion1, pVersion2, &nResult);
923 NativeAssert::Succeeded(hr, "Failed to compare versions '{0}' and '{1}'", pVersion1->sczVersion, pVersion2->sczVersion);
924
925 Assert::Equal(nExpectedResult, nResult);
926
927 hr = VerCompareParsedVersions(pVersion2, pVersion1, &nResult);
928 NativeAssert::Succeeded(hr, "Failed to compare versions '{0}' and '{1}'", pVersion1->sczVersion, pVersion2->sczVersion);
929
930 Assert::Equal(nExpectedResult, -nResult);
931 }
932 };
933}
diff --git a/src/libs/dutil/test/DUtilUnitTest/error.cpp b/src/libs/dutil/test/DUtilUnitTest/error.cpp
new file mode 100644
index 00000000..e51971c3
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/error.cpp
@@ -0,0 +1,26 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5const int ERROR_STRING_BUFFER = 1024;
6
7static char szMsg[ERROR_STRING_BUFFER];
8static WCHAR wzMsg[ERROR_STRING_BUFFER];
9
10void CALLBACK DutilTestTraceError(
11 __in_z LPCSTR /*szFile*/,
12 __in int /*iLine*/,
13 __in REPORT_LEVEL /*rl*/,
14 __in UINT source,
15 __in HRESULT hrError,
16 __in_z __format_string LPCSTR szFormat,
17 __in va_list args
18 )
19{
20 if (DUTIL_SOURCE_EXTERNAL == source)
21 {
22 ::StringCchPrintfA(szMsg, countof(szMsg), szFormat, args);
23 MultiByteToWideChar(CP_ACP, 0, szMsg, -1, wzMsg, countof(wzMsg));
24 throw gcnew System::Exception(System::String::Format("hr = 0x{0:X8}, message = {1}", hrError, gcnew System::String(wzMsg)));
25 }
26}
diff --git a/src/libs/dutil/test/DUtilUnitTest/error.h b/src/libs/dutil/test/DUtilUnitTest/error.h
new file mode 100644
index 00000000..b973acaf
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/error.h
@@ -0,0 +1,14 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4#define DUTIL_SOURCE_DEFAULT DUTIL_SOURCE_EXTERNAL
5
6void CALLBACK DutilTestTraceError(
7 __in_z LPCSTR szFile,
8 __in int iLine,
9 __in REPORT_LEVEL rl,
10 __in UINT source,
11 __in HRESULT hrError,
12 __in_z __format_string LPCSTR szFormat,
13 __in va_list args
14 );
diff --git a/src/libs/dutil/test/DUtilUnitTest/packages.config b/src/libs/dutil/test/DUtilUnitTest/packages.config
new file mode 100644
index 00000000..a4fef2bf
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/packages.config
@@ -0,0 +1,13 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<packages>
4 <package id="xunit.abstractions" version="2.0.3" />
5 <package id="xunit.assert" version="2.4.1" />
6 <package id="xunit.core" version="2.4.1" />
7 <package id="xunit.extensibility.core" version="2.4.1" />
8 <package id="xunit.extensibility.execution" version="2.4.1" />
9 <package id="xunit.runner.msbuild" version="2.4.1" />
10 <package id="xunit.runner.visualstudio" version="2.4.1" />
11 <package id="WixBuildTools.TestSupport" version="4.0.47" />
12 <package id="WixBuildTools.TestSupport.Native" version="4.0.47" />
13</packages> \ No newline at end of file
diff --git a/src/libs/dutil/test/DUtilUnitTest/precomp.cpp b/src/libs/dutil/test/DUtilUnitTest/precomp.cpp
new file mode 100644
index 00000000..37664a1c
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/precomp.cpp
@@ -0,0 +1,3 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
diff --git a/src/libs/dutil/test/DUtilUnitTest/precomp.h b/src/libs/dutil/test/DUtilUnitTest/precomp.h
new file mode 100644
index 00000000..e9f8770b
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/precomp.h
@@ -0,0 +1,32 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#include <windows.h>
6#include <strsafe.h>
7#include <ShlObj.h>
8
9// Include error.h before dutil.h
10#include <dutilsources.h>
11#include "error.h"
12#include <dutil.h>
13
14#include <verutil.h>
15#include <atomutil.h>
16#include <dictutil.h>
17#include <dirutil.h>
18#include <fileutil.h>
19#include <guidutil.h>
20#include <iniutil.h>
21#include <memutil.h>
22#include <pathutil.h>
23#include <strutil.h>
24#include <monutil.h>
25#include <regutil.h>
26#include <rssutil.h>
27#include <apuputil.h> // NOTE: this must come after atomutil.h and rssutil.h since it uses them.
28#include <uriutil.h>
29#include <xmlutil.h>
30
31#pragma managed
32#include <vcclr.h>
diff --git a/src/libs/dutil/test/DUtilUnitTest/resource.h b/src/libs/dutil/test/DUtilUnitTest/resource.h
new file mode 100644
index 00000000..bdf252f6
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/resource.h
@@ -0,0 +1,2 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
diff --git a/src/samples/Dtf/DDiff/CabDiffEngine.cs b/src/samples/Dtf/DDiff/CabDiffEngine.cs
new file mode 100644
index 00000000..6100ced8
--- /dev/null
+++ b/src/samples/Dtf/DDiff/CabDiffEngine.cs
@@ -0,0 +1,131 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.IO;
5using System.Collections;
6using System.Collections.Generic;
7using WixToolset.Dtf.Compression.Cab;
8
9namespace WixToolset.Dtf.Samples.DDiff
10{
11 public class CabDiffEngine : IDiffEngine
12 {
13 public CabDiffEngine()
14 {
15 }
16
17 private bool IsCabinetFile(string file)
18 {
19 using(FileStream fileStream = File.OpenRead(file))
20 {
21 return new CabEngine().IsArchive(fileStream);
22 }
23 }
24
25 public virtual float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
26 {
27 if(diffInput1 != null && File.Exists(diffInput1) &&
28 diffInput2 != null && File.Exists(diffInput2) &&
29 (IsCabinetFile(diffInput1) || IsCabinetFile(diffInput2)))
30 {
31 return .80f;
32 }
33 else
34 {
35 return 0;
36 }
37 }
38
39 public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
40 {
41 bool difference = false;
42 IComparer caseInsComp = CaseInsensitiveComparer.Default;
43
44 // TODO: Make this faster by extracting the whole cab at once.
45 // TODO: Optimize for the match case by first comparing the whole cab files.
46
47 CabInfo cab1 = new CabInfo(diffInput1);
48 CabInfo cab2 = new CabInfo(diffInput2);
49 IList<CabFileInfo> cabFilesList1 = cab1.GetFiles();
50 IList<CabFileInfo> cabFilesList2 = cab2.GetFiles();
51 CabFileInfo[] cabFiles1 = new CabFileInfo[cabFilesList1.Count];
52 CabFileInfo[] cabFiles2 = new CabFileInfo[cabFilesList2.Count];
53 cabFilesList1.CopyTo(cabFiles1, 0);
54 cabFilesList2.CopyTo(cabFiles2, 0);
55 string[] files1 = new string[cabFiles1.Length];
56 string[] files2 = new string[cabFiles2.Length];
57 for(int i1 = 0; i1 < cabFiles1.Length; i1++) files1[i1] = cabFiles1[i1].Name;
58 for(int i2 = 0; i2 < cabFiles2.Length; i2++) files2[i2] = cabFiles2[i2].Name;
59 Array.Sort(files1, cabFiles1, caseInsComp);
60 Array.Sort(files2, cabFiles2, caseInsComp);
61
62
63 for(int i1 = 0, i2 = 0; i1 < files1.Length || i2 < files2.Length; )
64 {
65 int comp;
66 if(i1 == files1.Length)
67 {
68 comp = 1;
69 }
70 else if(i2 == files2.Length)
71 {
72 comp = -1;
73 }
74 else
75 {
76 comp = caseInsComp.Compare(files1[i1], files2[i2]);
77 }
78 if(comp < 0)
79 {
80 diffOutput.WriteLine("{0}< {1}", linePrefix, files1[i1]);
81 i1++;
82 difference = true;
83 }
84 else if(comp > 0)
85 {
86 diffOutput.WriteLine("{0}> {1}", linePrefix, files2[i2]);
87 i2++;
88 difference = true;
89 }
90 else
91 {
92 string tempFile1 = Path.GetTempFileName();
93 string tempFile2 = Path.GetTempFileName();
94 cabFiles1[i1].CopyTo(tempFile1, true);
95 cabFiles2[i2].CopyTo(tempFile2, true);
96 IDiffEngine diffEngine = diffFactory.GetDiffEngine(tempFile1, tempFile2, options);
97 StringWriter sw = new StringWriter();
98 if(diffEngine.GetDiff(tempFile1, tempFile2, options, sw, linePrefix + " ", diffFactory))
99 {
100 diffOutput.WriteLine("{0}{1}", linePrefix, files1[i1]);
101 diffOutput.Write(sw.ToString());
102 difference = true;
103 }
104
105 File.SetAttributes(tempFile1, File.GetAttributes(tempFile1) & ~FileAttributes.ReadOnly);
106 File.SetAttributes(tempFile2, File.GetAttributes(tempFile2) & ~FileAttributes.ReadOnly);
107 try
108 {
109 File.Delete(tempFile1);
110 File.Delete(tempFile2);
111 }
112 catch(IOException)
113 {
114#if DEBUG
115 Console.WriteLine("Could not delete temporary files {0} and {1}", tempFile1, tempFile2);
116#endif
117 }
118 i1++;
119 i2++;
120 }
121 }
122
123 return difference;
124 }
125
126 public virtual IDiffEngine Clone()
127 {
128 return new CabDiffEngine();
129 }
130 }
131}
diff --git a/src/samples/Dtf/DDiff/DDiff.cs b/src/samples/Dtf/DDiff/DDiff.cs
new file mode 100644
index 00000000..27a5a782
--- /dev/null
+++ b/src/samples/Dtf/DDiff/DDiff.cs
@@ -0,0 +1,72 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.IO;
5using System.Text;
6
7namespace WixToolset.Dtf.Samples.DDiff
8{
9 public class DDiff
10 {
11 public static void Usage(TextWriter w)
12 {
13 w.WriteLine("Usage: DDiff target1 target2 [options]");
14 w.WriteLine("Example: DDiff d:\\dir1 d:\\dir2");
15 w.WriteLine("Example: DDiff patch1.msp patch2.msp /patchtarget target.msi");
16 w.WriteLine();
17 w.WriteLine("Options:");
18 w.WriteLine(" /o [filename] Output results to text file (UTF8)");
19 w.WriteLine(" /p [package.msi] Diff patches relative to target MSI");
20 }
21
22 public static int Main(string[] args)
23 {
24 if(args.Length < 2)
25 {
26 Usage(Console.Out);
27 return -1;
28 }
29
30 string input1 = args[0];
31 string input2 = args[1];
32 string[] options = new string[args.Length - 2];
33 for(int i = 0; i < options.Length; i++) options[i] = args[i+2];
34
35 TextWriter output = Console.Out;
36
37 for(int i = 0; i < options.Length - 1; i++)
38 {
39 switch(options[i].ToLower())
40 {
41 case "/o": goto case "-output";
42 case "-o": goto case "-output";
43 case "/output": goto case "-output";
44 case "-output": output = new StreamWriter(options[i+1], false, Encoding.UTF8); break;
45 }
46 }
47
48 IDiffEngineFactory diffFactory = new BestQualityDiffEngineFactory(new IDiffEngine[]
49 {
50 new DirectoryDiffEngine(),
51 new FileDiffEngine(),
52 new VersionedFileDiffEngine(),
53 new TextFileDiffEngine(),
54 new MsiDiffEngine(),
55 new CabDiffEngine(),
56 new MspDiffEngine(),
57 });
58
59 IDiffEngine diffEngine = diffFactory.GetDiffEngine(input1, input2, options);
60 if(diffEngine != null)
61 {
62 bool different = diffEngine.GetDiff(input1, input2, options, output, "", diffFactory);
63 return different ? 1 : 0;
64 }
65 else
66 {
67 Console.Error.WriteLine("Dont know how to diff those inputs.");
68 return -1;
69 }
70 }
71 }
72}
diff --git a/src/samples/Dtf/DDiff/DDiff.csproj b/src/samples/Dtf/DDiff/DDiff.csproj
new file mode 100644
index 00000000..332ad4d0
--- /dev/null
+++ b/src/samples/Dtf/DDiff/DDiff.csproj
@@ -0,0 +1,39 @@
1
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
6 <PropertyGroup>
7 <ProjectGuid>{1CDF4242-4C00-4744-BBCD-085128978FF3}</ProjectGuid>
8 <OutputType>Exe</OutputType>
9 <RootNamespace>WixToolset.Dtf.Samples.DDiff</RootNamespace>
10 <AssemblyName>DDiff</AssemblyName>
11 <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
12 <RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
13 </PropertyGroup>
14
15 <ItemGroup>
16 <Compile Include="CabDiffEngine.cs" />
17 <Compile Include="DDiff.cs" />
18 <Compile Include="DirectoryDiffEngine.cs" />
19 <Compile Include="FileDiffEngine.cs" />
20 <Compile Include="IDiffEngine.cs" />
21 <Compile Include="MsiDiffEngine.cs" />
22 <Compile Include="MspDiffEngine.cs" />
23 <Compile Include="TextFileDiffEngine.cs" />
24 <Compile Include="VersionedFileDiffEngine.cs" />
25 </ItemGroup>
26
27 <ItemGroup>
28 <Reference Include="System" />
29 <Reference Include="System.Data" />
30 <Reference Include="System.Xml" />
31 <ProjectReference Include="..\..\Libraries\Compression.Cab\Compression.Cab.csproj" />
32 <ProjectReference Include="..\..\Libraries\Compression.Zip\Compression.Zip.csproj" />
33 <ProjectReference Include="..\..\Libraries\Compression\Compression.csproj" />
34 <ProjectReference Include="..\..\Libraries\WindowsInstaller.Package\WindowsInstaller.Package.csproj" />
35 <ProjectReference Include="..\..\Libraries\WindowsInstaller\WindowsInstaller.csproj" />
36 </ItemGroup>
37
38 <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" />
39</Project>
diff --git a/src/samples/Dtf/DDiff/DirectoryDiffEngine.cs b/src/samples/Dtf/DDiff/DirectoryDiffEngine.cs
new file mode 100644
index 00000000..89e8b47e
--- /dev/null
+++ b/src/samples/Dtf/DDiff/DirectoryDiffEngine.cs
@@ -0,0 +1,154 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.IO;
5using System.Collections;
6
7namespace WixToolset.Dtf.Samples.DDiff
8{
9 public class DirectoryDiffEngine : IDiffEngine
10 {
11 public DirectoryDiffEngine()
12 {
13 }
14
15 public virtual float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
16 {
17 if(diffInput1 != null && Directory.Exists(diffInput1) &&
18 diffInput2 != null && Directory.Exists(diffInput2))
19 {
20 return .70f;
21 }
22 else
23 {
24 return 0;
25 }
26 }
27
28 public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
29 {
30 bool difference = false;
31 IComparer caseInsComp = CaseInsensitiveComparer.Default;
32
33 string[] files1 = Directory.GetFiles(diffInput1);
34 string[] files2 = Directory.GetFiles(diffInput2);
35 for(int i1 = 0; i1 < files1.Length; i1++)
36 {
37 files1[i1] = Path.GetFileName(files1[i1]);
38 }
39 for(int i2 = 0; i2 < files2.Length; i2++)
40 {
41 files2[i2] = Path.GetFileName(files2[i2]);
42 }
43 Array.Sort(files1, caseInsComp);
44 Array.Sort(files2, caseInsComp);
45
46 for(int i1 = 0, i2 = 0; i1 < files1.Length || i2 < files2.Length; )
47 {
48 int comp;
49 if(i1 == files1.Length)
50 {
51 comp = 1;
52 }
53 else if(i2 == files2.Length)
54 {
55 comp = -1;
56 }
57 else
58 {
59 comp = caseInsComp.Compare(files1[i1], files2[i2]);
60 }
61 if(comp < 0)
62 {
63 diffOutput.WriteLine("{0}< {1}", linePrefix, files1[i1]);
64 i1++;
65 difference = true;
66 }
67 else if(comp > 0)
68 {
69 diffOutput.WriteLine("{0}> {1}", linePrefix, files2[i2]);
70 i2++;
71 difference = true;
72 }
73 else
74 {
75 string file1 = Path.Combine(diffInput1, files1[i1]);
76 string file2 = Path.Combine(diffInput2, files2[i2]);
77 IDiffEngine diffEngine = diffFactory.GetDiffEngine(file1, file2, options);
78 StringWriter sw = new StringWriter();
79 if(diffEngine.GetDiff(file1, file2, options, sw, linePrefix + " ", diffFactory))
80 {
81 diffOutput.WriteLine("{0}{1}", linePrefix, files1[i1]);
82 diffOutput.Write(sw.ToString());
83 difference = true;
84 }
85 i1++;
86 i2++;
87 }
88 }
89
90 string[] dirs1 = Directory.GetDirectories(diffInput1);
91 string[] dirs2 = Directory.GetDirectories(diffInput2);
92 for(int i1 = 0; i1 < dirs1.Length; i1++)
93 {
94 dirs1[i1] = Path.GetFileName(dirs1[i1]);
95 }
96 for(int i2 = 0; i2 < dirs2.Length; i2++)
97 {
98 dirs2[i2] = Path.GetFileName(dirs2[i2]);
99 }
100 Array.Sort(dirs1, caseInsComp);
101 Array.Sort(dirs2, caseInsComp);
102
103 for(int i1 = 0, i2 = 0; i1 < dirs1.Length || i2 < dirs2.Length; )
104 {
105 int comp;
106 if(i1 == dirs1.Length)
107 {
108 comp = 1;
109 }
110 else if(i2 == dirs2.Length)
111 {
112 comp = -1;
113 }
114 else
115 {
116 comp = caseInsComp.Compare(dirs1[i1], dirs2[i2]);
117 }
118 if(comp < 0)
119 {
120 diffOutput.WriteLine("{0}< {1}", linePrefix, dirs1[i1]);
121 i1++;
122 difference = true;
123 }
124 else if(comp > 0)
125 {
126 diffOutput.WriteLine("{0}> {1}", linePrefix, dirs2[i2]);
127 i2++;
128 difference = true;
129 }
130 else
131 {
132 string dir1 = Path.Combine(diffInput1, dirs1[i1]);
133 string dir2 = Path.Combine(diffInput2, dirs2[i2]);
134 IDiffEngine diffEngine = diffFactory.GetDiffEngine(dir1, dir2, options);
135 StringWriter sw = new StringWriter();
136 if(diffEngine.GetDiff(dir1, dir2, options, sw, linePrefix + " ", diffFactory))
137 {
138 diffOutput.WriteLine("{0}{1}\\", linePrefix, dirs1[i1]);
139 diffOutput.Write(sw.ToString());
140 difference = true;
141 }
142 i1++;
143 i2++;
144 }
145 }
146 return difference;
147 }
148
149 public virtual IDiffEngine Clone()
150 {
151 return new DirectoryDiffEngine();
152 }
153 }
154}
diff --git a/src/samples/Dtf/DDiff/FileDiffEngine.cs b/src/samples/Dtf/DDiff/FileDiffEngine.cs
new file mode 100644
index 00000000..20ecd857
--- /dev/null
+++ b/src/samples/Dtf/DDiff/FileDiffEngine.cs
@@ -0,0 +1,83 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.IO;
5
6namespace WixToolset.Dtf.Samples.DDiff
7{
8 public class FileDiffEngine : IDiffEngine
9 {
10 public FileDiffEngine()
11 {
12 }
13
14 public virtual float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
15 {
16 if(diffInput1 != null && File.Exists(diffInput1) &&
17 diffInput2 != null && File.Exists(diffInput2))
18 {
19 return .10f;
20 }
21 else
22 {
23 return 0;
24 }
25 }
26
27 public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
28 {
29 bool difference = false;
30
31 FileInfo file1 = new FileInfo(diffInput1);
32 FileInfo file2 = new FileInfo(diffInput2);
33
34 if(file1.Length != file2.Length)
35 {
36 diffOutput.WriteLine("{0}File size: {1} -> {2}", linePrefix, file1.Length, file2.Length);
37 difference = true;
38 }
39 else
40 {
41 FileStream stream1 = file1.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
42 FileStream stream2 = file2.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
43
44 byte[] buf1 = new byte[512];
45 byte[] buf2 = new byte[512];
46
47 while(!difference)
48 {
49 int count1 = stream1.Read(buf1, 0, buf1.Length);
50 int count2 = stream2.Read(buf2, 0, buf2.Length);
51
52 for(int i = 0; i < count1; i++)
53 {
54 if(i == count2 || buf1[i] != buf2[i])
55 {
56 difference = true;
57 break;
58 }
59 }
60 if(count1 < buf1.Length) // EOF
61 {
62 break;
63 }
64 }
65
66 stream1.Close();
67 stream2.Close();
68
69 if(difference)
70 {
71 diffOutput.WriteLine("{0}Files differ.", linePrefix);
72 }
73 }
74
75 return difference;
76 }
77
78 public virtual IDiffEngine Clone()
79 {
80 return new FileDiffEngine();
81 }
82 }
83}
diff --git a/src/samples/Dtf/DDiff/IDiffEngine.cs b/src/samples/Dtf/DDiff/IDiffEngine.cs
new file mode 100644
index 00000000..9895d6ff
--- /dev/null
+++ b/src/samples/Dtf/DDiff/IDiffEngine.cs
@@ -0,0 +1,68 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.IO;
5using System.Collections;
6
7namespace WixToolset.Dtf.Samples.DDiff
8{
9 public interface IDiffEngine
10 {
11 float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory);
12
13 bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory);
14
15 IDiffEngine Clone();
16 }
17
18 public interface IDiffEngineFactory
19 {
20 IDiffEngine GetDiffEngine(string diffInput1, string diffInput2, string[] options);
21 }
22
23 public class BestQualityDiffEngineFactory : IDiffEngineFactory
24 {
25 public virtual IDiffEngine GetDiffEngine(string diffInput1, string diffInput2, string[] options)
26 {
27 float bestDiffQuality = 0;
28 IDiffEngine bestDiffEngine = null;
29
30 foreach(IDiffEngine diffEngine in diffEngines)
31 {
32 float diffQuality = diffEngine.GetDiffQuality(diffInput1, diffInput2, options, this);
33 if(diffQuality > bestDiffQuality)
34 {
35 bestDiffQuality = diffQuality;
36 bestDiffEngine = diffEngine;
37 }
38 }
39 return (bestDiffEngine != null ? bestDiffEngine.Clone() : null);
40 }
41
42 public BestQualityDiffEngineFactory() : this(null) { }
43 public BestQualityDiffEngineFactory(IDiffEngine[] diffEngines)
44 {
45 this.diffEngines = (diffEngines != null ? new ArrayList(diffEngines) : new ArrayList());
46 }
47
48 protected IList diffEngines;
49
50 public virtual void Add(IDiffEngine diffEngine)
51 {
52 diffEngines.Add(diffEngine);
53 }
54
55 public virtual void Remove(IDiffEngine diffEngine)
56 {
57 diffEngines.Remove(diffEngine);
58 }
59
60 public IList DiffEngines
61 {
62 get
63 {
64 return ArrayList.ReadOnly(diffEngines);
65 }
66 }
67 }
68}
diff --git a/src/samples/Dtf/DDiff/MsiDiffEngine.cs b/src/samples/Dtf/DDiff/MsiDiffEngine.cs
new file mode 100644
index 00000000..91bc2969
--- /dev/null
+++ b/src/samples/Dtf/DDiff/MsiDiffEngine.cs
@@ -0,0 +1,276 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.IO;
5using System.Collections;
6using System.Collections.Generic;
7using WixToolset.Dtf.WindowsInstaller;
8using WixToolset.Dtf.WindowsInstaller.Package;
9
10namespace WixToolset.Dtf.Samples.DDiff
11{
12 public class MsiDiffEngine : IDiffEngine
13 {
14 public MsiDiffEngine()
15 {
16 }
17
18 protected bool IsMsiDatabase(string file)
19 {
20 // TODO: use something smarter?
21 switch(Path.GetExtension(file).ToLower())
22 {
23 case ".msi": return true;
24 case ".msm": return true;
25 case ".pcp": return true;
26 default : return false;
27 }
28 }
29
30 protected bool IsMspPatch(string file)
31 {
32 // TODO: use something smarter?
33 switch(Path.GetExtension(file).ToLower())
34 {
35 case ".msp": return true;
36 default : return false;
37 }
38 }
39
40 public virtual float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
41 {
42 if(diffInput1 != null && File.Exists(diffInput1) &&
43 diffInput2 != null && File.Exists(diffInput2) &&
44 (IsMsiDatabase(diffInput1) || IsMsiDatabase(diffInput2)))
45 {
46 return .70f;
47 }
48 else if(diffInput1 != null && File.Exists(diffInput1) &&
49 diffInput2 != null && File.Exists(diffInput2) &&
50 (IsMspPatch(diffInput1) || IsMspPatch(diffInput2)))
51 {
52 return .60f;
53 }
54 else
55 {
56 return 0;
57 }
58 }
59
60 public virtual bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
61 {
62 bool difference = false;
63 Database db1 = new Database(diffInput1, DatabaseOpenMode.ReadOnly);
64 Database db2 = new Database(diffInput2, DatabaseOpenMode.ReadOnly);
65
66 if(GetSummaryInfoDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true;
67 if(GetDatabaseDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true;
68 if(GetStreamsDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true;
69
70 db1.Close();
71 db2.Close();
72 return difference;
73 }
74
75 protected bool GetSummaryInfoDiff(Database db1, Database db2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
76 {
77 bool difference = false;
78
79 SummaryInfo summInfo1 = db1.SummaryInfo;
80 SummaryInfo summInfo2 = db2.SummaryInfo;
81 if(summInfo1.Title != summInfo2.Title ) { diffOutput.WriteLine("{0}SummaryInformation.Title {{{1}}}->{{{2}}}", linePrefix, summInfo1.Title, summInfo2.Title); difference = true; }
82 if(summInfo1.Subject != summInfo2.Subject ) { diffOutput.WriteLine("{0}SummaryInformation.Subject {{{1}}}->{{{2}}}", linePrefix, summInfo1.Subject, summInfo2.Subject); difference = true; }
83 if(summInfo1.Author != summInfo2.Author ) { diffOutput.WriteLine("{0}SummaryInformation.Author {{{1}}}->{{{2}}}", linePrefix, summInfo1.Author, summInfo2.Author); difference = true; }
84 if(summInfo1.Keywords != summInfo2.Keywords ) { diffOutput.WriteLine("{0}SummaryInformation.Keywords {{{1}}}->{{{2}}}", linePrefix, summInfo1.Keywords, summInfo2.Keywords); difference = true; }
85 if(summInfo1.Comments != summInfo2.Comments ) { diffOutput.WriteLine("{0}SummaryInformation.Comments {{{1}}}->{{{2}}}", linePrefix, summInfo1.Comments, summInfo2.Comments); difference = true; }
86 if(summInfo1.Template != summInfo2.Template ) { diffOutput.WriteLine("{0}SummaryInformation.Template {{{1}}}->{{{2}}}", linePrefix, summInfo1.Template, summInfo2.Template); difference = true; }
87 if(summInfo1.LastSavedBy != summInfo2.LastSavedBy ) { diffOutput.WriteLine("{0}SummaryInformation.LastSavedBy {{{1}}}->{{{2}}}", linePrefix, summInfo1.LastSavedBy, summInfo2.LastSavedBy); difference = true; }
88 if(summInfo1.RevisionNumber != summInfo2.RevisionNumber) { diffOutput.WriteLine("{0}SummaryInformation.RevisionNumber {{{1}}}->{{{2}}}", linePrefix, summInfo1.RevisionNumber, summInfo2.RevisionNumber); difference = true; }
89 if(summInfo1.CreatingApp != summInfo2.CreatingApp ) { diffOutput.WriteLine("{0}SummaryInformation.CreatingApp {{{1}}}->{{{2}}}", linePrefix, summInfo1.CreatingApp, summInfo2.CreatingApp); difference = true; }
90 if(summInfo1.LastPrintTime != summInfo2.LastPrintTime ) { diffOutput.WriteLine("{0}SummaryInformation.LastPrintTime {{{1}}}->{{{2}}}", linePrefix, summInfo1.LastPrintTime, summInfo2.LastPrintTime); difference = true; }
91 if(summInfo1.CreateTime != summInfo2.CreateTime ) { diffOutput.WriteLine("{0}SummaryInformation.CreateTime {{{1}}}->{{{2}}}", linePrefix, summInfo1.CreateTime, summInfo2.CreateTime); difference = true; }
92 if(summInfo1.LastSaveTime != summInfo2.LastSaveTime ) { diffOutput.WriteLine("{0}SummaryInformation.LastSaveTime {{{1}}}->{{{2}}}", linePrefix, summInfo1.LastSaveTime, summInfo2.LastSaveTime); difference = true; }
93 if(summInfo1.CodePage != summInfo2.CodePage ) { diffOutput.WriteLine("{0}SummaryInformation.Codepage {{{1}}}->{{{2}}}", linePrefix, summInfo1.CodePage, summInfo2.CodePage); difference = true; }
94 if(summInfo1.PageCount != summInfo2.PageCount ) { diffOutput.WriteLine("{0}SummaryInformation.PageCount {{{1}}}->{{{2}}}", linePrefix, summInfo1.PageCount, summInfo2.PageCount); difference = true; }
95 if(summInfo1.WordCount != summInfo2.WordCount ) { diffOutput.WriteLine("{0}SummaryInformation.WordCount {{{1}}}->{{{2}}}", linePrefix, summInfo1.WordCount, summInfo2.WordCount); difference = true; }
96 if(summInfo1.CharacterCount != summInfo2.CharacterCount) { diffOutput.WriteLine("{0}SummaryInformation.CharacterCount {{{1}}}->{{{2}}}", linePrefix, summInfo1.CharacterCount, summInfo2.CharacterCount); difference = true; }
97 if(summInfo1.Security != summInfo2.Security ) { diffOutput.WriteLine("{0}SummaryInformation.Security {{{1}}}->{{{2}}}", linePrefix, summInfo1.Security, summInfo2.Security); difference = true; }
98 summInfo1.Close();
99 summInfo2.Close();
100
101 return difference;
102 }
103
104 protected bool GetDatabaseDiff(Database db1, Database db2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
105 {
106 bool difference = false;
107
108 string tempFile = Path.GetTempFileName();
109 if(db2.GenerateTransform(db1, tempFile))
110 {
111 difference = true;
112
113 Database db = db1;
114 db.ViewTransform(tempFile);
115
116 string row, column, change;
117 using (View view = db.OpenView("SELECT `Table`, `Column`, `Row`, `Data`, `Current` " +
118 "FROM `_TransformView` ORDER BY `Table`, `Row`"))
119 {
120 view.Execute();
121
122 foreach (Record rec in view) using (rec)
123 {
124 column = String.Format("{0} {1}", rec[1], rec[2]);
125 change = "";
126 if (rec.IsNull(3))
127 {
128 row = "<DDL>";
129 if (!rec.IsNull(4))
130 {
131 change = "[" + rec[5] + "]: " + DecodeColDef(rec.GetInteger(4));
132 }
133 }
134 else
135 {
136 row = "[" + String.Join(",", rec.GetString(3).Split('\t')) + "]";
137 if (rec.GetString(2) != "INSERT" && rec.GetString(2) != "DELETE")
138 {
139 column = String.Format("{0}.{1}", rec[1], rec[2]);
140 change = "{" + rec[5] + "}->{" + rec[4] + "}";
141 }
142 }
143
144 diffOutput.WriteLine("{0}{1,-25} {2} {3}", linePrefix, column, row, change);
145 }
146 }
147 }
148 File.Delete(tempFile);
149
150 return difference;
151 }
152
153 private string DecodeColDef(int colDef)
154 {
155 const int icdLong = 0x0000;
156 const int icdShort = 0x0400;
157 const int icdObject = 0x0800;
158 const int icdString = 0x0C00;
159 const int icdTypeMask = 0x0F00;
160 const int icdNullable = 0x1000;
161 const int icdPrimaryKey = 0x2000;
162
163 string def = "";
164 switch(colDef & (icdTypeMask))
165 {
166 case icdLong : def = "LONG"; break;
167 case icdShort : def = "SHORT"; break;
168 case icdObject: def = "OBJECT"; break;
169 case icdString: def = "CHAR[" + (colDef & 0xFF) + "]"; break;
170 }
171 if((colDef & icdNullable) != 0)
172 {
173 def = def + " NOT NULL";
174 }
175 if((colDef & icdPrimaryKey) != 0)
176 {
177 def = def + " PRIMARY KEY";
178 }
179 return def;
180 }
181
182 protected bool GetStreamsDiff(Database db1, Database db2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
183 {
184 bool difference = false;
185
186 IList<string> streams1List = db1.ExecuteStringQuery("SELECT `Name` FROM `_Streams`");
187 IList<string> streams2List = db2.ExecuteStringQuery("SELECT `Name` FROM `_Streams`");
188 string[] streams1 = new string[streams1List.Count];
189 string[] streams2 = new string[streams2List.Count];
190 streams1List.CopyTo(streams1, 0);
191 streams2List.CopyTo(streams2, 0);
192
193 IComparer caseInsComp = CaseInsensitiveComparer.Default;
194 Array.Sort(streams1, caseInsComp);
195 Array.Sort(streams2, caseInsComp);
196
197 for (int i1 = 0, i2 = 0; i1 < streams1.Length || i2 < streams2.Length; )
198 {
199 int comp;
200 if (i1 == streams1.Length)
201 {
202 comp = 1;
203 }
204 else if (i2 == streams2.Length)
205 {
206 comp = -1;
207 }
208 else
209 {
210 comp = caseInsComp.Compare(streams1[i1], streams2[i2]);
211 }
212 if(comp < 0)
213 {
214 diffOutput.WriteLine("{0}< {1}", linePrefix, streams1[i1]);
215 i1++;
216 difference = true;
217 }
218 else if(comp > 0)
219 {
220 diffOutput.WriteLine("{0}> {1}", linePrefix, streams2[i2]);
221 i2++;
222 difference = true;
223 }
224 else
225 {
226 if(streams1[i1] != ("" + ((char)5) + "SummaryInformation"))
227 {
228 string tempFile1 = Path.GetTempFileName();
229 string tempFile2 = Path.GetTempFileName();
230
231 using (View view = db1.OpenView(String.Format("SELECT `Data` FROM `_Streams` WHERE `Name` = '{0}'", streams1[i1])))
232 {
233 view.Execute();
234
235 using (Record rec = view.Fetch())
236 {
237 rec.GetStream(1, tempFile1);
238 }
239 }
240
241 using (View view = db2.OpenView(String.Format("SELECT `Data` FROM `_Streams` WHERE `Name` = '{0}'", streams2[i2])))
242 {
243 view.Execute();
244
245 using (Record rec = view.Fetch())
246 {
247 rec.GetStream(1, tempFile2);
248 }
249 }
250
251 IDiffEngine diffEngine = diffFactory.GetDiffEngine(tempFile1, tempFile2, options);
252 StringWriter sw = new StringWriter();
253 if(diffEngine.GetDiff(tempFile1, tempFile2, options, sw, linePrefix + " ", diffFactory))
254 {
255 diffOutput.WriteLine("{0}{1}", linePrefix, streams1[i1]);
256 diffOutput.Write(sw.ToString());
257 difference = true;
258 }
259
260 File.Delete(tempFile1);
261 File.Delete(tempFile2);
262 }
263 i1++;
264 i2++;
265 }
266 }
267
268 return difference;
269 }
270
271 public virtual IDiffEngine Clone()
272 {
273 return new MsiDiffEngine();
274 }
275 }
276}
diff --git a/src/samples/Dtf/DDiff/MspDiffEngine.cs b/src/samples/Dtf/DDiff/MspDiffEngine.cs
new file mode 100644
index 00000000..285bc83d
--- /dev/null
+++ b/src/samples/Dtf/DDiff/MspDiffEngine.cs
@@ -0,0 +1,127 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.IO;
5using WixToolset.Dtf.WindowsInstaller;
6using WixToolset.Dtf.WindowsInstaller.Package;
7
8namespace WixToolset.Dtf.Samples.DDiff
9{
10 public class MspDiffEngine : MsiDiffEngine
11 {
12 public MspDiffEngine()
13 {
14 }
15
16 private string GetPatchTargetOption(string[] options)
17 {
18 for(int i = 0; i < options.Length - 1; i++)
19 {
20 switch(options[i].ToLower())
21 {
22 case "/p": goto case "-patchtarget";
23 case "-p": goto case "-patchtarget";
24 case "/patchtarget": goto case "-patchtarget";
25 case "-patchtarget": return options[i+1];
26 }
27 }
28 return null;
29 }
30
31 public override float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
32 {
33 if(diffInput1 != null && File.Exists(diffInput1) &&
34 diffInput2 != null && File.Exists(diffInput2) &&
35 GetPatchTargetOption(options) != null &&
36 (IsMspPatch(diffInput1) && IsMspPatch(diffInput2)))
37 {
38 return .80f;
39 }
40 else if(diffInput1 != null && File.Exists(diffInput1) &&
41 diffInput2 != null && File.Exists(diffInput2) &&
42 GetPatchTargetOption(options) == null &&
43 (IsMspPatch(diffInput1) && IsMsiDatabase(diffInput2)) ||
44 (IsMsiDatabase(diffInput1) && IsMspPatch(diffInput2)))
45 {
46 return .75f;
47 }
48 else
49 {
50 return 0;
51 }
52 }
53
54 public override bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
55 {
56 bool difference = false;
57
58 InstallPackage db1, db2;
59 if(IsMspPatch(diffInput1))
60 {
61 string patchTargetDbFile = GetPatchTargetOption(options);
62 if(patchTargetDbFile == null) patchTargetDbFile = diffInput2;
63 string tempPatchedDbFile = Path.GetTempFileName();
64 File.Copy(patchTargetDbFile, tempPatchedDbFile, true);
65 File.SetAttributes(tempPatchedDbFile, File.GetAttributes(tempPatchedDbFile) & ~System.IO.FileAttributes.ReadOnly);
66 db1 = new InstallPackage(tempPatchedDbFile, DatabaseOpenMode.Direct);
67 db1.ApplyPatch(new PatchPackage(diffInput1), null);
68 db1.Commit();
69 }
70 else
71 {
72 db1 = new InstallPackage(diffInput1, DatabaseOpenMode.ReadOnly);
73 }
74 if(IsMspPatch(diffInput2))
75 {
76 string patchTargetDbFile = GetPatchTargetOption(options);
77 if(patchTargetDbFile == null) patchTargetDbFile = diffInput1;
78 string tempPatchedDbFile = Path.GetTempFileName();
79 File.Copy(patchTargetDbFile, tempPatchedDbFile, true);
80 File.SetAttributes(tempPatchedDbFile, File.GetAttributes(tempPatchedDbFile) & ~System.IO.FileAttributes.ReadOnly);
81 db2 = new InstallPackage(tempPatchedDbFile, DatabaseOpenMode.Direct);
82 db2.ApplyPatch(new PatchPackage(diffInput2), null);
83 db2.Commit();
84 }
85 else
86 {
87 db2 = new InstallPackage(diffInput2, DatabaseOpenMode.ReadOnly);
88 }
89
90 if(GetSummaryInfoDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true;
91 if(GetDatabaseDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true;
92 if(GetStreamsDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true;
93
94 db1.Close();
95 db2.Close();
96
97 try
98 {
99 if(IsMspPatch(diffInput1)) File.Delete(db1.FilePath);
100 if(IsMspPatch(diffInput1)) File.Delete(db2.FilePath);
101 }
102 catch(IOException)
103 {
104 #if DEBUG
105 Console.WriteLine("Could not delete temporary files {0} and {1}", db1.FilePath, db2.FilePath);
106 #endif
107 }
108
109 if(IsMspPatch(diffInput1) && IsMspPatch(diffInput2))
110 {
111 Database dbp1 = new Database(diffInput1, DatabaseOpenMode.ReadOnly);
112 Database dbp2 = new Database(diffInput2, DatabaseOpenMode.ReadOnly);
113
114 if(GetStreamsDiff(dbp1, dbp2, options, diffOutput, linePrefix, diffFactory)) difference = true;
115 dbp1.Close();
116 dbp2.Close();
117 }
118
119 return difference;
120 }
121
122 public override IDiffEngine Clone()
123 {
124 return new MspDiffEngine();
125 }
126 }
127}
diff --git a/src/samples/Dtf/DDiff/TextFileDiffEngine.cs b/src/samples/Dtf/DDiff/TextFileDiffEngine.cs
new file mode 100644
index 00000000..22567023
--- /dev/null
+++ b/src/samples/Dtf/DDiff/TextFileDiffEngine.cs
@@ -0,0 +1,83 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.IO;
5using System.Diagnostics;
6
7namespace WixToolset.Dtf.Samples.DDiff
8{
9 public class TextFileDiffEngine : IDiffEngine
10 {
11 public TextFileDiffEngine()
12 {
13 }
14
15 private bool IsTextFile(string file)
16 {
17 // Guess whether this is a text file by reading the first few bytes and checking for non-ascii chars.
18
19 bool isText = true;
20 FileStream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
21 byte[] buf = new byte[256];
22 int count = stream.Read(buf, 0, buf.Length);
23 for(int i = 0; i < count; i++)
24 {
25 if((buf[i] & 0x80) != 0)
26 {
27 isText = false;
28 break;
29 }
30 }
31 stream.Close();
32 return isText;
33 }
34
35 public float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
36 {
37 if(diffInput1 != null && File.Exists(diffInput1) &&
38 diffInput2 != null && File.Exists(diffInput2) &&
39 (IsTextFile(diffInput1) && IsTextFile(diffInput2)))
40 {
41 return .70f;
42 }
43 else
44 {
45 return 0;
46 }
47 }
48
49 public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
50 {
51 try
52 {
53 bool difference = false;
54 ProcessStartInfo psi = new ProcessStartInfo("diff.exe");
55 psi.Arguments = String.Format("\"{0}\" \"{1}\"", diffInput1, diffInput2);
56 psi.WorkingDirectory = null;
57 psi.UseShellExecute = false;
58 psi.WindowStyle = ProcessWindowStyle.Hidden;
59 psi.RedirectStandardOutput = true;
60 Process proc = Process.Start(psi);
61
62 string line;
63 while((line = proc.StandardOutput.ReadLine()) != null)
64 {
65 diffOutput.WriteLine("{0}{1}", linePrefix, line);
66 difference = true;
67 }
68
69 proc.WaitForExit();
70 return difference;
71 }
72 catch(System.ComponentModel.Win32Exception) // If diff.exe is not found, just compare the bytes
73 {
74 return new FileDiffEngine().GetDiff(diffInput1, diffInput2, options, diffOutput, linePrefix, diffFactory);
75 }
76 }
77
78 public IDiffEngine Clone()
79 {
80 return new TextFileDiffEngine();
81 }
82 }
83}
diff --git a/src/samples/Dtf/DDiff/VersionedFileDiffEngine.cs b/src/samples/Dtf/DDiff/VersionedFileDiffEngine.cs
new file mode 100644
index 00000000..ad4014f3
--- /dev/null
+++ b/src/samples/Dtf/DDiff/VersionedFileDiffEngine.cs
@@ -0,0 +1,90 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.IO;
5using WixToolset.Dtf.WindowsInstaller;
6
7namespace WixToolset.Dtf.Samples.DDiff
8{
9 public class VersionedFileDiffEngine : IDiffEngine
10 {
11 public VersionedFileDiffEngine()
12 {
13 }
14
15 private bool IsVersionedFile(string file)
16 {
17 return Installer.GetFileVersion(file) != "";
18 }
19
20 public float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
21 {
22 if(diffInput1 != null && File.Exists(diffInput1) &&
23 diffInput2 != null && File.Exists(diffInput2) &&
24 (IsVersionedFile(diffInput1) || IsVersionedFile(diffInput2)))
25 {
26 return .20f;
27 }
28 else
29 {
30 return 0;
31 }
32 }
33
34 public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
35 {
36 bool difference = false;
37
38 string ver1 = Installer.GetFileVersion(diffInput1);
39 string ver2 = Installer.GetFileVersion(diffInput2);
40
41 if(ver1 != ver2)
42 {
43 diffOutput.WriteLine("{0}File version: {1} -> {2}", linePrefix, ver1, ver2);
44 difference = true;
45 }
46 else
47 {
48 FileStream stream1 = new FileStream(diffInput1, FileMode.Open, FileAccess.Read, FileShare.Read);
49 FileStream stream2 = new FileStream(diffInput2, FileMode.Open, FileAccess.Read, FileShare.Read);
50
51 byte[] buf1 = new byte[512];
52 byte[] buf2 = new byte[512];
53
54 while(!difference)
55 {
56 int count1 = stream1.Read(buf1, 0, buf1.Length);
57 int count2 = stream2.Read(buf2, 0, buf2.Length);
58
59 for(int i = 0; i < count1; i++)
60 {
61 if(i == count2 || buf1[i] != buf2[i])
62 {
63 difference = true;
64 break;
65 }
66 }
67 if(count1 < buf1.Length) // EOF
68 {
69 break;
70 }
71 }
72
73 stream1.Close();
74 stream2.Close();
75
76 if(difference)
77 {
78 diffOutput.WriteLine("{0}File versions match but bits differ.", linePrefix);
79 }
80 }
81
82 return difference;
83 }
84
85 public IDiffEngine Clone()
86 {
87 return new VersionedFileDiffEngine();
88 }
89 }
90}
diff --git a/src/samples/Dtf/Documents/Guide/Content/about.htm b/src/samples/Dtf/Documents/Guide/Content/about.htm
new file mode 100644
index 00000000..393b5a81
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/about.htm
@@ -0,0 +1,59 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>Deployment Tools Foundation Overview</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">Deployment Tools Foundation</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet"><span class="nolink">Overview</span></span>
15 <span id="languageFilter">v4.0</span>
16 </div>
17 </div>
18 <div id="main">
19 <div id="header">
20 </div>
21 <div class="summary">
22 <p>Deployment Tools Foundation is a rich set of .NET class libraries and
23 related resources that together bring the Windows deployment platform
24 technologies into the .NET world. It is designed to greatly simplify
25 deployment-related development tasks while still exposing the complete
26 functionality of the underlying technology.</p>
27
28 <p>The primary focus of DTF is to provide a foundation for development of
29 various kinds of tools to support deployment throughout the product
30 lifecycle, including setup authoring, building, analysis, debugging, and
31 testing tools. In addition to tools, DTF can also be useful for install-time
32 activities such as setup bootstrappers, external UI, and custom actions,
33 and for application run-time activities that need to access the deployment
34 platform.</p>
35
36 <p>For a description of the the latest changes, see <a
37 href="whatsnew.htm">What's New</a>.</p>
38
39 </div>
40
41 <div id="footer">
42 <p />
43 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
44 wix-users@lists.sourceforge.net</a>
45
46 <script type="text/javascript">
47 var HT_mailLink = document.getElementById("HT_MailLink");
48 var HT_mailLinkText = HT_mailLink.innerHTML;
49 HT_mailLink.href += ": " + document.title;
50 HT_mailLink.innerHTML = HT_mailLinkText;
51 </script>
52
53 <p />
54
55 </div>
56 </div>
57
58</body>
59</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/buildingcas.htm b/src/samples/Dtf/Documents/Guide/Content/buildingcas.htm
new file mode 100644
index 00000000..e88ad552
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/buildingcas.htm
@@ -0,0 +1,94 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>Building Managed Custom Actions</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">Building Managed Custom Actions</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <a href="using.htm">Development Guide</a> &gt;
16 <a href="managedcas.htm">Managed CAs</a> &gt;
17 <span class="nolink">Building</span>
18 </span>
19 </div>
20 </div>
21 <div id="main">
22 <div id="header">
23 </div>
24 <div class="summary">
25
26 <p>The build process for managed CA DLLs is a little complicated becuase of the
27 proxy-wrapper and dll-export requirements. Here's an overview:</p>
28 <ol>
29 <li>
30 <p>Compile your CA assembly, which references WixToolset.Dtf.WindowsInstaller.dll and
31 marks exported custom actions with a CustomActionAttribute.</p>
32 <li>
33 <p>Package the CA assembly, CustomAction.config, WixToolset.Dtf.WindowsInstaller.dll,
34 and any other dependencies using <b>MakeSfxCA.exe</b>. The filenames of CustomAction.config
35 and WixToolset.Dtf.WindowsInstaller.dll must not be changed, since
36 the custom action proxy specifically looks for those files.</p>
37 </ol>
38 <p><br>
39 </p>
40 <p><b>Compiling</b></p>
41 <pre><font face="Consolas, Courier New">
42 csc.exe
43 /target:library
44 /r:$(DTFbin)\WixToolset.Dtf.WindowsInstaller.dll
45 /out:SampleCAs.dll
46 *.cs
47 </font></pre>
48 <p><b>Wrapping</b><pre><font face="Consolas, Courier New">
49 MakeSfxCA.exe
50 $(OutDir)\SampleCAsPackage.dll
51 $(DTFbin)\SfxCA.dll
52 SampleCAs.dll
53 CustomAction.config
54 $(DTFbin)\WixToolset.Dtf.WindowsInstaller.dll
55 </font></pre>
56 </p>
57 <p>Now the resulting package, SampleCAsPackage.dll, is ready to be inserted
58 into the Binary table of the MSI.</p>
59 <p><br/>
60 </p>
61 <p>For a working example of building a managed custom action package
62 you can look at included sample ManagedCAs project.</p>
63 <p><br/>
64 </p>
65
66 <p><br/></p>
67 <p><b>See also:</b></p>
68 <ul>
69 <li><a href="writingcas.htm">Writing Managed Custom Actions</a></li>
70 <li><a href="caconfig.htm">Specifying the Runtime Version</a></li>
71 </ul>
72 <p><br/></p>
73
74 </div>
75
76 <div id="footer">
77 <p />
78 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
79 wix-users@lists.sourceforge.net</a>
80
81 <script type="text/javascript">
82 var HT_mailLink = document.getElementById("HT_MailLink");
83 var HT_mailLinkText = HT_mailLink.innerHTML;
84 HT_mailLink.href += ": " + document.title;
85 HT_mailLink.innerHTML = HT_mailLinkText;
86 </script>
87
88 <p />
89
90 </div>
91 </div>
92
93</body>
94</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/cabpack.htm b/src/samples/Dtf/Documents/Guide/Content/cabpack.htm
new file mode 100644
index 00000000..2d9f725e
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/cabpack.htm
@@ -0,0 +1,63 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>Archive Pack/Unpack Tool</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">Archive Pack/Unpack Tool</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <a href="using.htm">Development Guide</a> &gt;
16 <a href="samples.htm">Samples</a> &gt;
17 <span class="nolink">XPack</span>
18 </span>
19 </div>
20 </div>
21 <div id="main">
22 <div id="header">
23 </div>
24 <div class="summary">
25 <p><pre><font face="Consolas, Courier New">Usage: CabPack.exe &lt;directory&gt; &lt;package.cab&gt;
26Usage: XPack /P &lt;archive.cab&gt; &lt;directory&gt;
27Usage: XPack /P &lt;archive.zip&gt; &lt;directory&gt;
28
29Packs all files in a directory tree into an archive,
30using either the cab or zip format. Any existing archive
31with the same name will be overwritten.
32
33
34Usage: XPack /U &lt;archive.cab&gt; &lt;directory&gt;
35Usage: XPack /U &lt;archive.zip&gt; &lt;directory&gt;
36
37Unpacks all files from a cab or zip archive to the
38specified directory. Any existing files with the same
39names will be overwritten.</font></pre>
40 </p>
41 <p><br/></p>
42
43 </div>
44
45 <div id="footer">
46 <p />
47 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
48 wix-users@lists.sourceforge.net</a>
49
50 <script type="text/javascript">
51 var HT_mailLink = document.getElementById("HT_MailLink");
52 var HT_mailLinkText = HT_mailLink.innerHTML;
53 HT_mailLink.href += ": " + document.title;
54 HT_mailLink.innerHTML = HT_mailLinkText;
55 </script>
56
57 <p />
58
59 </div>
60 </div>
61
62</body>
63</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/cabs.htm b/src/samples/Dtf/Documents/Guide/Content/cabs.htm
new file mode 100644
index 00000000..e88d1e15
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/cabs.htm
@@ -0,0 +1,101 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>Working with Cabinet Files</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">Working with Cabinet Files</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <a href="using.htm">Development Guide</a> &gt;
16 <span class="nolink">Cabinet Files</span>
17 </span>
18 </div>
19 </div>
20 <div id="main">
21 <div id="header">
22 </div>
23 <div class="summary">
24
25 <h3>Creating a cabinet</h3>
26 <pre><font face="Consolas, Courier New"> CabInfo cabInfo = <font color="blue">new</font> CabInfo(<font color="purple">"package.cab"</font>);
27 cabInfo.Pack(<font color="purple">"D:\\FilesToCompress"</font>);</font></pre><br />
28 <p>1.&nbsp; Create a <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_Compression_Cab_CabInfo__ctor_1.htm">new CabInfo</a> instance referring to the (future) location of the .cab file.</p>
29 <p>2.&nbsp; Compress files:</p><ul>
30 <li>Easily compress an entire directory with the <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_Compression_ArchiveInfo_Pack.htm">Pack</a> method.</li>
31 <li>Compress a specific list of exernal and internal filenames with the <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_Compression_ArchiveInfo_PackFiles.htm">PackFiles</a> method.</li>
32 <li>Compress a dictionary mapping of internal to external filenames with the <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_Compression_ArchiveInfo_PackFileSet.htm">PackFileSet</a> method.</li>
33 </ul>
34
35 <p><br/></p>
36 <h3>Listing a cabinet</h3>
37 <pre><font face="Consolas, Courier New"> CabInfo cabInfo = <font color="blue">new</font> CabInfo(<font color="purple">"package.cab"</font>);
38 <font color="blue">foreach</font> (CabFileInfo fileInfo <font color="blue">in</font> cabInfo.GetFiles())
39 Console.WriteLine(fileInfo.Name + <font color="purple">"\t"</font> + fileInfo.Length);</font></pre><br />
40 <p>1.&nbsp; Create a <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_Compression_Cab_CabInfo__ctor_1.htm">new CabInfo</a> instance referring to the location of the .cab file.</p>
41 <p>2.&nbsp; Enumerate files returned by the <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_Compression_Cab_CabInfo_GetFiles.htm">GetFiles</a> method.</p><ul>
42 <li>Each <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_Compression_Cab_CabFileInfo.htm">CabFileInfo</a> instance contains metadata about one file.</li>
43 </ul>
44
45 <p><br/></p>
46 <h3>Extracting a cabinet</h3>
47 <pre><font face="Consolas, Courier New"> CabInfo cabInfo = <font color="blue">new</font> CabInfo(<font color="purple">"package.cab"</font>);
48 cabInfo.Unpack(<font color="purple">"D:\\ExtractedFiles"</font>);</font></pre><br />
49 <p>1.&nbsp; Create a <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_Compression_Cab_CabInfo__ctor_1.htm">new CabInfo</a> instance referring to the location of the .cab file.</p>
50 <p>2.&nbsp; Extract files:</p><ul>
51 <li>Easily extract all files to a directory with the <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_Compression_ArchiveInfo_Unpack.htm">Unpack</a> method.</li>
52 <li>Easily extract a single file with the <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_Compression_ArchiveInfo_UnpackFile.htm">UnpackFile</a> method.</li>
53 <li>Extract a specific list of filenames with the <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_Compression_ArchiveInfo_UnpackFiles.htm">UnpackFiles</a> method.</li>
54 <li>Extract a dictionary mapping of internal to external filenames with the <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_Compression_ArchiveInfo_UnpackFileSet.htm">UnpackFileSet</a> method.</li>
55 </ul>
56
57 <p><br/></p>
58 <h3>Getting progress</h3>
59 Most cabinet operation methods have an overload that allows you to specify a event handler
60 for receiving <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_Compression_ArchiveProgressEventArgs.htm">archive
61 progress events</a>. The <a href="cabpack.htm">XPack sample</a>
62 demonstrates use of the callback to report detailed progress to the console.
63
64 <p><br/></p>
65 <h3>Stream-based compression</h3>
66 The CabEngine class contains static methods for performing compression/decompression operations directly
67 on any kind of Stream. However these methods are more difficult to use, since the caller must implement a
68 <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_Compression_ArchiveFileStreamContext.htm">stream context</a>
69 that provides the file metadata which would otherwise have been provided by the filesystem. The CabInfo class
70 uses the CabEngine class with FileStreams to provide the more traditional file-based interface.
71
72 <p><br/></p>
73 <p><b>See also:</b></p>
74 <ul>
75 <li><a href="DTFAPI.chm::/html/T_Microsoft_Deployment_Compression_Cab_CabInfo.htm">CabInfo class</a></li>
76 <li><a href="DTFAPI.chm::/html/T_Microsoft_Deployment_Compression_Cab_CabEngine.htm">CabEngine class</a></li>
77 <li><a href="cabpack.htm">XPack Sample Tool</a></li>
78 </ul>
79 <p><br/></p>
80
81 </div>
82
83 <div id="footer">
84 <p />
85 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
86 wix-users@lists.sourceforge.net</a>
87
88 <script type="text/javascript">
89 var HT_mailLink = document.getElementById("HT_MailLink");
90 var HT_mailLinkText = HT_mailLink.innerHTML;
91 HT_mailLink.href += ": " + document.title;
92 HT_mailLink.innerHTML = HT_mailLinkText;
93 </script>
94
95 <p />
96
97 </div>
98 </div>
99
100</body>
101</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/cabwrapper.htm b/src/samples/Dtf/Documents/Guide/Content/cabwrapper.htm
new file mode 100644
index 00000000..fd88437c
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/cabwrapper.htm
@@ -0,0 +1,63 @@
1<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
2<html>
3 <head>
4 <title>Managed Wrapper Library for Cabinet APIs</title>
5 <meta http-equiv="Content-Type" content="text/html; charset=Windows-1252">
6 <link rel="stylesheet" type="text/css" href="MSDN.css">
7 </head>
8 <body id="bodyID" class="dtBODY">
9 <div id="nsbanner">
10 <div id="bannerrow1">
11 <table class="bannerparthead" cellspacing="0" id="Table1">
12 <tr id="hdr">
13 <td class="runninghead">Managed Libraries for Windows Installer</td>
14 <td class="product"></td>
15 </tr>
16 </table>
17 </div>
18 <div id="TitleRow">
19 <h1 class="dtH1">Managed Wrapper Library for Cabinet APIs</h1>
20 </div>
21 </div>
22 <div id="nstext">
23 <p>This is a managed library that provides the ability to
24 create and extract cabinet files. It uses cabinet.dll (present on all versions of Windows)
25 to do the actual compression/decompression. It provides access to nearly all
26 cabinet capabilities, including spanning of multiple cab files. It even has support for
27 preserving directory structures and UTF8 paths.</p>
28 <p>There are two ways to use the library. <a href="ms-its:MMLRef.chm::/Microsoft.Cab.CabinetInfo.html">CabinetInfo</a>
29 and <a href="ms-its:MMLRef.chm::/Microsoft.Cab.CabinetFileInfo.html">CabinetFileInfo</a>
30 (similar to DirectoryInfo and FileInfo respectively)
31 provide high-level object-oriented methods for doing common file-based cabinet creation and
32 extraction tasks. On the other hand, the <a href="ms-its:MMLRef.chm::/Microsoft.Cab.Cabinet.html">Cabinet</a>
33 class provides low-level access to all
34 functionality, and operates completely in terms of .NET Streams. The previous two classes use
35 the Cabinet class to do all the actual work.</p>
36 <p>There are also two ways to build the library.
37 Compiling it normally will produce the fully functional
38 library in the <a href="ms-its:MMLRef.chm::/Microsoft.Cab.html">Microsoft.Cab</a>
39 namespace, while compiling it with the <tt>/D:CABMINIMAL
40 /D:CABEXTRACTONLY</tt> flags will create a compact assembly with only the core extraction
41 functionality, in the <a href="ms-its:MMLRef.chm::/Microsoft.Cab.MiniExtract.html">Microsoft.Cab.MiniExtract</a>
42 namespace.</p>
43 <p>The cabinet library interops with native cabinet APIs which use the 'cdecl'
44 calling-convention. When building against .NET Framework versions before 2.0,
45 this library requires a special post-build step to process the UnmanagedFunctionPointerAttribute.
46 If you use this code in another assembly, don't forget to run <a href="augmentil.htm">AugmentIL</a>
47 on it to fix the delegate calling-conventions, otherwise you will encounter a
48 NullReferenceException when attempting to call the cabinet APIs. When building against
49 .NET Framework version 2.0 or later, the UnmanagedFunctionPointerAttribute.cs source file
50 should be omitted.</p>
51
52 <p><br/></p>
53 <p><b>See also:</b></p>
54 <ul>
55 <li><a href="cabs.htm">Working with Cabinet Files</a></li>
56 <li><a href="ms-its:MMLRef.chm::/Microsoft.Cab.CabinetInfoMethods.html">CabinetInfo Methods</a></li>
57 <li><a href="ms-its:MMLRef.chm::/Microsoft.Cab.CabinetMethods.html">Cabinet Methods</a></li>
58 <li><a href="cabpack.htm">CabPack Sample Tool</a></li>
59 </ul>
60 <p><br/></p>
61 </div>
62 </body>
63</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/caconfig.htm b/src/samples/Dtf/Documents/Guide/Content/caconfig.htm
new file mode 100644
index 00000000..a6c97d2b
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/caconfig.htm
@@ -0,0 +1,83 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>Specifying the Runtime Version</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">Specifying the Runtime Version</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <a href="using.htm">Development Guide</a> &gt;
16 <a href="managedcas.htm">Managed CAs</a> &gt;
17 <a href="writingcas.htm">Writing CAs</a> &gt;
18 <span class="nolink">CustomAction.config</span>
19 </span>
20 </div>
21 </div>
22 <div id="main">
23 <div id="header">
24 </div>
25 <div class="summary">
26
27 <p>Every managed custom action package should contain a CustomAction.config file, even though it is not required by the toolset.
28 Here is a sample:</p><pre><font face="Consolas, Courier New">
29&lt;?xml version="1.0" encoding="utf-8" ?&gt;
30&lt;configuration&gt;
31 &lt;startup&gt;
32 &lt;supportedRuntime version=&quot;v2.0.50727&quot;/&gt;
33 &lt;/startup&gt;
34&lt;/configuration&gt;</font></pre><br />
35 <p>The configuration file follows the standard schema for .NET Framework
36 configuration files <a target=_blank href="http://msdn2.microsoft.com/en-us/library/9w519wzk(VS.80).aspx">documented on MSDN</a>.</p>
37 <p><br/></p>
38 <p><b>Supported Runtime Version</b></p>
39 <p>In the startup section, use <a target=_blank href="http://msdn2.microsoft.com/en-us/library/w4atty68(VS.80).aspx">supportedRuntime</a>
40 tags to explicitly specify the version(s) of the .NET Framework that the custom action should run on.
41 If no versions are specified, the chosen version of the .NET Framework will be
42 the "best" match to what WixToolset.Dtf.WindowsInstaller.dll was built against.</p>
43 <p><font color="red"><b>Warning: leaving the version unspecified is dangerous</b></font>
44 as it introduces a risk of compatibility problems with future versions of the .NET Framework.
45 It is highly recommended that you specify only the version(s)
46 of the .NET Framework that you have tested against.</p>
47 <p><br/></p>
48
49 <p><b>Other Configuration</b></p>
50 <p>Various other kinds of configuration settings may also be added to this file, as it is a standard
51 <a target=_blank href="http://msdn2.microsoft.com/en-us/library/kza1yk3a(VS.80).aspx">.NET Framework application config file</a>
52 for the custom action.</p>
53 <p><br/></p>
54
55 <p><b>See also:</b></p>
56 <ul>
57 <li><a href="writingcas.htm">Writing Managed Custom Actions</a></li>
58 <li><a href="buildingcas.htm">Building Managed Custom Actions</a></li>
59 <li><a href="caproxy.htm">Proxy for Managed Custom Actions</a></li>
60 </ul>
61 <p><br/></p>
62
63 </div>
64
65 <div id="footer">
66 <p />
67 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
68 wix-users@lists.sourceforge.net</a>
69
70 <script type="text/javascript">
71 var HT_mailLink = document.getElementById("HT_MailLink");
72 var HT_mailLinkText = HT_mailLink.innerHTML;
73 HT_mailLink.href += ": " + document.title;
74 HT_mailLink.innerHTML = HT_mailLinkText;
75 </script>
76
77 <p />
78
79 </div>
80 </div>
81
82</body>
83</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/caproxy.htm b/src/samples/Dtf/Documents/Guide/Content/caproxy.htm
new file mode 100644
index 00000000..2ee962d5
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/caproxy.htm
@@ -0,0 +1,74 @@
1<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
2<html>
3 <head>
4 <title>Proxy Class for Managed Custom Actions</title>
5 <meta http-equiv="Content-Type" content="text/html; charset=Windows-1252">
6 <link rel="stylesheet" type="text/css" href="MSDN.css">
7 </head>
8 <body id="bodyID" class="dtBODY">
9 <div id="nsbanner">
10 <div id="bannerrow1">
11 <table class="bannerparthead" cellspacing="0" id="Table1">
12 <tr id="hdr">
13 <td class="runninghead">Managed Libraries for Windows Installer</td>
14 <td class="product"></td>
15 </tr>
16 </table>
17 </div>
18 <div id="TitleRow">
19 <h1 class="dtH1">Proxy for Managed Custom Actions</h1>
20 </div>
21 </div>
22 <div id="nstext">
23 <p>The custom action proxy allows an MSI developer to write
24 custom actions in managed code, while maintaing all the advantages of type 1
25 DLL custom actions including full access to installer state, properties,
26 and the session database.</p>
27 <p>There are generally four problems that needed to be
28 solved in order to create a type 1 custom action in managed code:</p>
29 <ol>
30 <li><p><strong>Exporting the CA function as a native entry point callable by
31 MSI:</strong> The Windows Installer engine expects to call a LoadLibrary and
32 GetProcAddress on the custom action DLL, so an unmanaged DLL needs to implement
33 the function that is initially called by MSI and ultimately returns the result.
34 This function acts as a proxy to relay the custom action call into the
35 managed custom action assembly, and relay the result back to the caller. </p>
36 <li><strong>Providing supporting assemblies without
37 requiring them to be installed as files:</strong> If a DLL custom
38 action runs before the product's files are installed, then it is difficult
39 to provide any supporting files, because of the way the CA DLL is singly
40 extracted and executed from a temp file. (This can be a problem for
41 unmanaged CAs as well.) With managed custom actions we have already hit
42 that problem since both the CA assembly and the MSI wrapper assembly
43 need to be loaded. To solve this, the proxy DLL carries an appended
44 cab package. When invoked, it will extract all contents of the
45 cab package to a temporary working directory. This way the cab package can
46 carry any arbitrary dependencies the custom action may require.</li>
47 <li><p><strong>Hosting and configuring the Common Language Runtime:</strong>
48 In order to invoke a method in a managed assembly from a previously
49 unmanaged process, the CLR needs to be "hosted". This involves choosing
50 the correct version of the .NET Framework to use out of the available
51 version(s) on the system, binding that version to the current process, and
52 configuring it to load assemblies from the temporary working directory.</p>
53 <li><p><strong>Converting the integer session handle into a
54 Session object:</strong> The <a href="">Session</a> class in the managed
55 wrapper library has a constructor which takes an integer session handle as
56 its parameter. So the proxy simply instantiates this object before
57 calling the real CA function.</p>
58 </ol>
59 <p>The unmanaged CAPack module, when used in combination with the managed proxy in
60 the
61 Microsoft.WindowsInstaller assembly, accomplishes the tasks above to enable
62 fully-functional managed DLL custom actions.</p>
63 <p><br/></p>
64 <p><b>See also:</b></p>
65 <ul>
66 <li><a href="writingcas.htm">Writing Managed Custom Actions</a></li>
67 <li><a href="caconfig.htm">Writing the CustomAction.config file</a></li>
68 <li><a href="samplecas.htm">Sample C# Custom Actions</a></li>
69 <li><a href="buildingcas.htm">Building Managed Custom Actions</a></li>
70 </ul>
71 <p><br/></p>
72 </div>
73 </body>
74</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/databases.htm b/src/samples/Dtf/Documents/Guide/Content/databases.htm
new file mode 100644
index 00000000..4fe1fba9
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/databases.htm
@@ -0,0 +1,120 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>Working with MSI Databases</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">Working with MSI Databases</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <a href="using.htm">Development Guide</a> &gt;
16 <span class="nolink">MSI Databases</span>
17 </span>
18 </div>
19 </div>
20 <div id="main">
21 <div id="header">
22 </div>
23 <div class="summary">
24
25 <h3>Querying a database</h3>
26 <pre><font face="Consolas, Courier New"> <font color=blue>using</font> (Database db = <font color=blue>new</font> Database(<font color="purple">"product.msi"</font>, DatabaseOpenMode.ReadOnly))
27 {
28 <font color=blue>string</font> value = (<font color=blue>string</font>) db.ExecuteScalar(
29 <font color="purple">"SELECT `Value` FROM `Property` WHERE `Property` = '{0}'"</font>, propName);
30 }</font></pre><br />
31 <p>1.&nbsp; Create a <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_WindowsInstaller_Database__ctor.htm">new Database</a>
32 instance referring to the location of the .msi or .msm file.</p>
33 <p>2.&nbsp; Execute the query:</p><ul>
34 <li>The <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_WindowsInstaller_Database_ExecuteScalar.htm">ExecuteScalar</a>
35 method is a shortcut for opening a view, executing the view, and fetching a single value.</li>
36 <li>The <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_WindowsInstaller_Database_ExecuteQuery.htm">ExecuteQuery</a>
37 method is a shortcut for opening a view, executing the view, and fetching all values.</li>
38 <li>Or do it all manually with <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_WindowsInstaller_Database_OpenView.htm">Database.OpenView</a>,
39 <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_WindowsInstaller_View_Execute.htm">View.Execute</a>, and
40 <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_WindowsInstaller_View_Fetch.htm">View.Fetch</a>.</li>
41 </ul>
42
43 <p><br/></p>
44 <h3>Updating a binary</h3>
45 <pre><font face="Consolas, Courier New"> Database db = <font color=blue>null</font>;
46 View view = <font color=blue>null</font>;
47 Record rec = <font color=blue>null</font>;
48 <font color=blue>try</font>
49 {
50 db = <font color=blue>new</font> Database(<font color="purple">"product.msi"</font>, DatabaseOpenMode.Direct);
51 view = db.OpenView(<font color="purple">"UPDATE `Binary` SET `Data` = ? WHERE `Name` = '{0}'"</font>, binName))
52 rec = <font color=blue>new</font> Record(1);
53 rec.SetStream(1, binFile);
54 view.Execute(rec);
55 db.Commit();
56 }
57 <font color=blue>finally</font>
58 {
59 <font color=blue>if</font> (rec != <font color=blue>null</font>) rec.Close();
60 <font color=blue>if</font> (view != <font color=blue>null</font>) view.Close();
61 <font color=blue>if</font> (db != <font color=blue>null</font>) db.Close();
62 }</font></pre><br />
63 <p>1.&nbsp; Create a <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_WindowsInstaller_Database__ctor.htm">new Database</a>
64 instance referring to the location of the .msi or .msm file.</p>
65 <p>2.&nbsp; Open a view by calling one of the <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_WindowsInstaller_Database_OpenView.htm">Database.OpenView</a> overloads.</p><ul>
66 <li>Parameters can be substituted in the SQL string using the String.Format syntax.</li>
67 </ul>
68 <p>3.&nbsp; Create a record with one field containing the new binary value.</p>
69 <p>4.&nbsp; Execute the view by calling one of the <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_WindowsInstaller_View_Execute.htm">View.Execute</a> overloads.</p><ul>
70 <li>A record can be supplied for substitution of field tokens (?) in the query.</li>
71 </ul>
72 <p>5.&nbsp; <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_WindowsInstaller_Database_Commit.htm">Commit</a> the Database.</p>
73 <p>6.&nbsp; <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_WindowsInstaller_InstallerHandle_Close.htm">Close</a> the handles.</p>
74
75 <p><br/></p>
76 <h3>About handles</h3>
77 <p>Handle objects (Database, View, Record, SummaryInfo, Session) will remain open until
78 they are explicitly closed or until the objects are collected by the GC. So for the tightest
79 code, handle objects should be explicitly closed when they are no longer needed,
80 since closing them can release significant resources, and too many unnecessary
81 open handles can degrade performance. This is especially important within a loop
82 construct: for example when iterating over all the Records in a table, it is much cleaner
83 and faster to close each Record after it is used.</p>
84 <p>The handle classes in the managed library all extend the
85 <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_InstallerHandle.htm">InstallerHandle</a>
86 class, which implements the IDisposable interface. This makes them easily managed with C#'s
87 using statement. Alternatively, they can be closed in a finally block.</p>
88 <p>As a general rule, <i>methods</i> in the library return new handle objects that should be managed
89 and closed by the calling code, while <i>properties</i> only return a reference to a prexisting handle
90 object.</p>
91
92 <p><br/></p>
93 <p><b>See also:</b></p>
94 <ul>
95 <li><a href="powerdiff.htm">MSI Diff Sample Tool</a></li>
96 <li><a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_Database.htm">Database Class</a></li>
97 </ul>
98 <p><br/></p>
99
100 </div>
101
102 <div id="footer">
103 <p />
104 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
105 wix-users@lists.sourceforge.net</a>
106
107 <script type="text/javascript">
108 var HT_mailLink = document.getElementById("HT_MailLink");
109 var HT_mailLinkText = HT_mailLink.innerHTML;
110 HT_mailLink.href += ": " + document.title;
111 HT_mailLink.innerHTML = HT_mailLinkText;
112 </script>
113
114 <p />
115
116 </div>
117 </div>
118
119</body>
120</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/debuggingcas.htm b/src/samples/Dtf/Documents/Guide/Content/debuggingcas.htm
new file mode 100644
index 00000000..ca1be161
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/debuggingcas.htm
@@ -0,0 +1,66 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>Debugging Managed Custom Actions</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">Debugging Managed Custom Actions</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <a href="using.htm">Development Guide</a> &gt;
16 <a href="managedcas.htm">Managed CAs</a> &gt;
17 <span class="nolink">Debugging</span>
18 </span>
19 </div>
20 </div>
21 <div id="main">
22 <div id="header">
23 </div>
24 <div class="summary">
25 <p>There are two ways to attach a debugger to a managed custom action.</p>
26 <p><b>Attach to message-box:</b> Add some temporary code to your custom action to display a
27 message box. Then when the message box pops up at install time, you can attch your
28 debugger to that process (usually identifiable by the title of the message box).
29 Once attached, you can ensure that symbols are loaded if necessary (they will be automatically
30 loaded if PDB files were embedded in the CA assembly at build time), then set breakpoints
31 anywhere in the custom action code.</p>
32 <p><b>MMsiBreak environment variable:</b> When debugging <i>managed</i> custom actions,
33 you should use the MMsiBreak environment variable instead of MsiBreak. Set the MMsiBreak
34 variable to the custom action entrypoint name. (Remember this might be different from
35 the method name if it was overridden by the CustomActionAttribute.) When the CA proxy
36 finds a matching name, the CLR JIT-debugging dialog
37 will appear with text similar to "An exception 'Launch for user' has occurred
38 in <i>YourCustomActionName</i>." The debug break occurs after the custom
39 action assembly has been loaded, but just before custom action method is invoked.
40 Once attached, you can ensure that symbols are loaded if necessary,
41 then set breakpoints anywhere in the custom action code. Note: the MMsiBreak
42 environment variable can also accept a comma-separated list of action names, any of
43 which will cause a break when hit.</p>
44 <p><br/></p>
45
46 </div>
47
48 <div id="footer">
49 <p />
50 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
51 wix-users@lists.sourceforge.net</a>
52
53 <script type="text/javascript">
54 var HT_mailLink = document.getElementById("HT_MailLink");
55 var HT_mailLinkText = HT_mailLink.innerHTML;
56 HT_mailLink.href += ": " + document.title;
57 HT_mailLink.innerHTML = HT_mailLinkText;
58 </script>
59
60 <p />
61
62 </div>
63 </div>
64
65</body>
66</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/dependencies.htm b/src/samples/Dtf/Documents/Guide/Content/dependencies.htm
new file mode 100644
index 00000000..cfec5880
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/dependencies.htm
@@ -0,0 +1,88 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>Dependencies</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">Dependencies</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <a href="about.htm">Overview</a> &gt;
16 <span class="nolink">Dependencies</span>
17 </span>
18 </div>
19 </div>
20 <div id="main">
21 <div id="header">
22 </div>
23 <div class="summary">
24
25 <p>This page lists all the components that the DTF project depends on, at build time and at run-time.</p>
26
27 <h3>Build-time Dependencies</h3>
28 <ul>
29 <li><p><b>Visual Studio / .NET Framework</b> - Most of DTF can be built with Visual Studio 2005 &
30 .NET Framework 2.0. However, the LINQ project requires VS 2008 & .NET Framework 3.5.</p></li>
31
32 <li><p><b>Sandcastle</b> - .NET documentation build engine from Microsoft, used to process all the XML doc-comments
33 in DTF libraries into DTFAPI.chm.
34 <a href="http://www.codeplex.com/Sandcastle/" target="_blank">(official site)</a></p></li>
35
36 <li><p><b>Sandcastle Builder</b> - Sandcastle by itself is complex and difficult to use; this free tool
37 from Codeplex provides an easy-to-use project system around it to automate the documentation build process.
38 <a href="http://www.codeplex.com/SHFB/" target="_blank">(project link)</a></p></li>
39
40 <li><p><b>HTML Help Workshop</b> - Tools for building HTML Help 1.x (CHM files). Used to build DTF.chm.
41 <a href="http://msdn2.microsoft.com/en-us/library/ms669985.aspx" target="_blank">(download link)</a></p></li>
42 </ul>
43
44 <h3>Run-time Dependencies</h3>
45 <ul>
46 <li><p><b>.NET Framework</b> - Most of DTF requires .NET Framework 2.0. (.NET 1.1 is no longer supported.)
47 The only exception is the LINQ assembly which requires .NET Framework 3.5.</p></li>
48
49 <li><p><b>Windows Installer</b> - Windows Installer introduced new APIs and capabilities with each successive
50 version. Obviously, the corresponding functionality in the managed APIs is only available when the required
51 version of the Windows Instaler (msi.dll) is installed on the system. Use the Installer.Version property
52 to easily check the currently installed MSI version. Attempting to use an API not supported by the current
53 version will result in an EntryPointNotFoundException. To check what version is required for a particular API,
54 see the documentation link to the corresponding unmanaged API in MSI.chm.</p>
55 <p>In some instances when a newer version of MSI provides an "Ex" alternative to a function, only the "Ex"
56 function is used by the managed library. This may hide some functionality that would have otherwise been
57 available on a system with an older version of MSI.</p></li>
58
59 <li><p><b>cabinet.dll</b> - The DTF cabinet compression library uses cabinet.dll to implement the
60 low-level cabinet compression and decompression. This DLL is part of all versions of Windows,
61 located in the system directory.</p></li>
62
63 <li><p><b>System.IO.Compression.DeflateStream</b> - The DTF zip compression library uses this class
64 to implement the low-level zip compression and decompression. This class is part of .NET Framework
65 2.0 and later.</p></li>
66 </ul>
67
68 </div>
69
70 <div id="footer">
71 <p />
72 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
73 wix-users@lists.sourceforge.net</a>
74
75 <script type="text/javascript">
76 var HT_mailLink = document.getElementById("HT_MailLink");
77 var HT_mailLinkText = HT_mailLink.innerHTML;
78 HT_mailLink.href += ": " + document.title;
79 HT_mailLink.innerHTML = HT_mailLinkText;
80 </script>
81
82 <p />
83
84 </div>
85 </div>
86
87</body>
88</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/filepatchwrapper.htm b/src/samples/Dtf/Documents/Guide/Content/filepatchwrapper.htm
new file mode 100644
index 00000000..6bab69b5
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/filepatchwrapper.htm
@@ -0,0 +1,34 @@
1<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
2<html>
3 <head>
4 <title>Managed Wrapper for Binary File Patch APIs</title>
5 <meta http-equiv="Content-Type" content="text/html; charset=Windows-1252">
6 <link rel="stylesheet" type="text/css" href="MSDN.css">
7 </head>
8 <body id="bodyID" class="dtBODY">
9 <div id="nsbanner">
10 <div id="bannerrow1">
11 <table class="bannerparthead" cellspacing="0" id="Table1">
12 <tr id="hdr">
13 <td class="runninghead">Managed Libraries for Windows Installer</td>
14 <td class="product"></td>
15 </tr>
16 </table>
17 </div>
18 <div id="TitleRow">
19 <h1 class="dtH1">Managed Wrapper for Binary File Patch APIs</h1>
20 </div>
21 </div>
22 <div id="nstext">
23 <p>The binary file patch creation and application APIs (supplied by MsPatchC.dll and
24 MsPatchA.dll) are wrapped in the Microsoft.WindowsInstaller.FilePatch.dll assembly.</p>
25
26 <p><br/></p>
27 <p><b>See also:</b></p>
28 <ul>
29 <li><a href="ms-its:MMLRef.chm::/Microsoft.WindowsInstaller.FilePatch.html">FilePatch Class</a></li>
30 </ul>
31 <p><br/></p>
32 </div>
33 </body>
34</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/history.htm b/src/samples/Dtf/Documents/Guide/Content/history.htm
new file mode 100644
index 00000000..704ce875
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/history.htm
@@ -0,0 +1,437 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>Change History</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">Change History</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <a href="about.htm">Overview</a> &gt;
16 <span class="nolink">Change History</span>
17 </span>
18 </div>
19 </div>
20 <div id="main">
21 <div id="header">
22 </div>
23 <div class="summary">
24
25<h3><b>2007-07-03</b></h3>
26<i>See <a href="whatsnew.htm">What's New?</a></i><br />&nbsp;<br />&nbsp;<br />
27<hr size="2"/>
28<h3><b>2005-03-30</b></h3>
29
30<ul>
31<li>New custom action proxy<ul>
32 <li><b>Managed custom actions use an XML config file to specify the CLR version.</b></li>
33 <li>New CAPack module is an unmanaged self-extracting CA DLL that can wrap both
34 managed and unmanaged custom actions. (The old managed CAProxy module is obsolete.)</li>
35 <li>Custom action build process is different but still complicated --
36 see documentation for details.</li>
37 <li>CustomActionAttribute no longer accepts the optional NativeDependencies
38 parameter since it does not apply to the new proxy (all packaged files
39 are always extracted and available when the CA executes). </li>
40</ul></li>
41
42<li>64bit support<ul>
43 <li>Various code fixes to pointer/handle types and structure alignments.</li>
44 <li>Cabinet and MSI libraries tested on AMD64 CLR.</li>
45 <li>Unmanaged and managed parts of custom action proxy tested on AMD64.</li>
46</ul></li>
47
48<li>MSI 3.1 APIs added:<ul>
49 <li>Installer.SetExternalUI(ExternalUIRecordHandler)</li>
50 <li>Installer.NotifySidChange</li>
51</ul></li>
52
53<li>Code builds easier with .NET Famework 2.0<ul>
54 <li>AugmentIL post-build step is no longer necessary when compiling the cabinet code
55 against .NET Framework 2.0, which has builtin support for cdecl delegates.</li>
56 <li>All C# code compiles against .NET Framework 2.0 without obsolete warnings,
57 when setting the NETFX2 preprocessor define.</li>
58 <li>Same code is still compatible with .NET Framework 1.0 + AugmentIL.</li>
59</ul></li>
60
61<li>Miscellaneous bugfixes/changes:<ul>
62 <li>InstallPackage.ExtractFiles could fail in some cominations of
63 compressed/uncompressed files - fixed.</li>
64 <li>Installer.DeterminePatchSequence was broken due to an incorrect interop struct - fixed.</li>
65 <li>CabinetInfo and CabinetFileInfo classes made serializable.</li>
66 <li>Added Session.FormatString method to simplify formatting a string with
67 property substitutions.</li>
68</ul></li>
69<li>Documentation updates:<ul>
70 <li>Updated all documentation for new CA proxy.</li>
71 <li>Added new topic discussing InstallUtil.</li>
72</ul></li>
73</ul>
74
75<hr size="2"/>
76<h3><b>2004-04-13</b></h3>
77
78<ul>
79<li>Documentation<ul>
80 <li>Consolidated all documentation into a single CHM file.</li>
81 <li>Added new topics about working with MSI databases & cabinet files,
82 to help new users get oriented more easily.</li>
83</ul></li>
84
85<li>WindowsInstaller<ul>
86 <li>Removed [Beta] tags from MSI 3.0 APIs, but otherwise there
87 have been no changes since 3.0 Beta 1.<ul>
88 <li>Be warned these are still the least-tested parts of
89 the library, so early users may encounter bugs.</li>
90 </ul></li>
91</ul></li>
92
93<li>InstallPackage<ul>
94 <li>Fixed InstallPackage.ExtractFiles() bug when directory doesn't exist.</li>
95 <li>Added ability to handle uncompressed files in a package marked as compressed.</li>
96</ul></li>
97
98<li>Cabinet<ul>
99 <li>Fixed improper handling of file attributes.<ul>
100 <li>This bug caused some packages to not be extractable by other tools.</li>
101 </ul></li>
102 <li>Added support for UTF filenames.<ul>
103 <li>Non-ASCII filenames will automatically be stored as UTF-8.
104 (But note most other tools don't know how to extract them.)</li>
105 </ul></li>
106</ul></li>
107</ul>
108
109<hr size="2"/>
110<h3><b>2003-10-13</b></h3>
111
112<ul>
113<li>Cab<ul>
114 <li>Fixed a bug introduced in v2.4.0 that caused files to be left in the %TEMP%
115 directory after creating a cab.</li>
116 <li>Unsealed the CabinetInfo, CabinetFileInfo, CabinetStatus classes and made a few methods
117 protected and virtual.</li>
118</ul></li>
119
120<li>AugmentIL<ul>
121 <li>Fixed a bug that sometimes caused a crash when specifying a relative output path
122 on the command-line.</li>
123 <li>Fixed a bug that sometimes caused the Win32 version to be missing from the output file.</li>
124</ul></li>
125
126<li>Samples\Diff: added new sample tool<ul>
127 <li>Recursively diffs directories, MSIs, MSPs, CABs, other files.</li>
128</ul></li>
129</ul>
130
131<hr size="2"/>
132<h3><b>2003-09-23</b></h3>
133
134<ul>
135<li>Cab<ul>
136 <li>Fixed a bug that caused compressing very large files/file sets to use way too
137 much memory. Performance on large inputs is now within a few % of native cab tools
138 (sometimes even a little faster!) for the same compression level.</li>
139</ul></li>
140
141<li>WindowsInstaller<ul>
142 <li>All the new MSI 3.0 beta APIs are wrapped, resulting in the following additions:<ul>
143 <li>New classes - Product, Patch (for accessing sourcelist and other config)</li>
144 <li>New methods on Install class - GetProducts, GetPatches, RemovePatches,
145 ApplyMultiplePatches, DetermineApplicablePatches, ExtractPatchXmlData</li>
146 <li>New enumerations - InstallContext, PatchStates, SourceType</li>
147 <li>Additional InstallProperty values</li>
148 </ul></li>
149 <li>Note, MSI 3.0 support should be considered preliminary for now,
150 as APIs (both native and managed) are subject to change.</li>
151 <li>For MSI 2.0 compatibility, developers should not use any classes or
152 methods that are marked as [MSI 3.0 beta] in the documentation.</li>
153 <li>And unrelated to 3.0, a few additional enums have been added:
154 DialogAttributes, ControlAttributes, CustomActionTypes,
155 IniFileAction, RegistryRoot, RemoveFileInstallMode,
156 ServiceControlEvents, ServiceInstallFlags, TextStyles,
157 UpgradeAttributes, LocatorType</li>
158 <li>Also made a few minor non-breaking changes to keep the library FxCop-clean.</li>
159</ul></li>
160
161<li>AugmentIL<ul>
162 <li>Added support for strongname signing and delay-signing. AugmentIL tries to
163 locate the keyfile using the AssemblyKeyFileAttribute, or you may specify the
164 path with the new /key option.</li>
165 <li>All &quot;released&quot; assemblies will now be strongname-signed
166 (with an unofficial key).</li>
167</ul></li>
168
169<li>CAProxy<ul>
170 <li>Added support for NativeDependencies property on CustomActionAttribute. This enables
171 custom actions to P/Invoke into native DLLs that are carried with them.</li>
172</ul></li>
173
174<li>Samples\SampleCAs<ul>
175 <li>In SampleCA2, changed MessageBox.Show(&quot;&quot;) to session.Message(User,&quot;&quot;),
176 because generally it is a bad practice for CAs to show independent UI.</li>
177 <li>Added test of CustomActionAttribute.NativeDependencies functionality.</li>
178</ul></li>
179
180<li>Samples\CabPack: added new sample<ul>
181 <li>Demonstrates &amp; tests the cab library by creating self-extracting packages</li>
182</ul></li>
183
184<li>Samples\Inventory: added new sample<ul>
185 <li>Shows a hierarchical, relational, searchable view of all of the product,
186 feature, component, file, and patch data managed by MSI, for all products
187 installed on the system.</li>
188</ul></li>
189</ul>
190
191<hr size="2"/>
192<h3><b>2003-09-12</b></h3>
193
194<ul>
195<li>Cab:<ul>
196 <li>Added CabinetInfo.CompressDirectory method, capable of compressing an
197 entire directory tree structure.</li>
198 <li>Updated documentation of various methods concerning support of directory
199 structure inside cabs.</li>
200 <li>CabinetInfo case-sensitivity was inconsistent -
201 now it is case-insensitive by default, though case is still preserved</li>
202 <li>Separated assembly attributes into assembly.cs</li>
203</ul></li>
204<li>Msi:<ul>
205 <li>InstallerException and subclasses automatically get extended error data
206 from MSI's last-error-record when available. The data is stored
207 in the exception and made available through the GetErrorRecord()
208 method, and the exception's Message includes the formatted error
209 message and data. This makes most exceptions extremely informative!</li>
210 <li>Added View.GetValidationErrors() method, and supporting ValidationErrorInfo
211 struct and ValidationError enum. This wrapper for the MsiViewGetError
212 API had been accidentally left out.</li>
213 <li>Session.Message() now supports message-box flags to specify buttons &amp; icon</li>
214 <li>Added doc remarks to various methods about closing handles.</li>
215 <li>Separated assembly attributes into assembly.cs</li>
216</ul></li>
217<li>AugmentIL:<ul>
218 <li>Recent builds of ildasm v2.0.* have a slightly different output format,
219 which could break AugmentIL in some cases - fixed</li>
220</ul></li>
221<li>SampleCAs:<ul>
222 <li>Removed 'using' clause from SampleCA1 -- there's no need to close the session's active database handle</li>
223</ul></li>
224<li>Documentation:<ul>
225 <li>Added note to ReadMe about compiling the cab source into another assembly</li>
226</ul></li>
227</ul>
228
229<hr size="2"/>
230<h3><b>2003-08-07</b></h3>
231
232<ul>
233<li>Cab:<ul>
234 <li>CabinetInfo.IsValid() usually returned false even for valid cabs - fixed</li>
235 <li>Extracting cab files with null timestamps generated exception - fixed</li>
236</ul></li>
237<li>Msi:<ul>
238 <li>Added InstallCanceledException, subclass of InstallerException;
239 Methods which may be canceled by the user can throw this exception</li>
240 <li>Added MessageResult enumeration;
241 Used by Session.Message() and ExternalUIHandler delegate</li>
242 <li>Installer.EnableLog() now supports extended attributes correctly:
243 Append mode and flush-every-line</li>
244 <li>Added Session.DoActionSequence() -
245 This wrapper for the MsiSequence API had been accidentally left out</li>
246</ul></li>
247<li>CAProxy:<ul>
248 <li>Catches InstallCanceledException, returns ERROR_INSTALL_USEREXIT
249 so CA developer doesn't necessarily have to handle the exception</li>
250</ul></li>
251<li>Msi\Package:<ul>
252 <li>Added TransformInfo class: metadata about an individual patch transform</li>
253 <li>Added PatchPackage.GetTransform*() methods which return TransformInfo</li>
254</ul></li>
255<li>Documentation:<ul>
256 <li>Added section to ReadMe.htm about building managed custom actions</li>
257</ul></li>
258</ul>
259
260<hr size="2"/>
261<h3><b>2003-06-02</b></h3>
262
263<ul>
264<li>Msi:<ul>
265 <li>Validation didn't work on merge modules - fixed</li>
266</ul></li>
267<li>CAProxy:<ul>
268 <li>Was broken in 2.1 - fixed</li>
269</ul></li>
270</ul>
271
272<hr size="2"/>
273<h3><b>2003-05-14</b></h3>
274
275<ul>
276<li>Msi:<ul>
277 <li>External UI handler didn't survive a garbage collection - fixed</li>
278 <li>Validation engine was completely broken - now it should work
279 at least for MSIs which are already mostly valid</li>
280 <li>Added DynamicLoad property to CustomActionAttribute<br />
281 Usage: set DynamicLoad=false when using XmlSerialization; default is true</li>
282</ul></li>
283<li>Msi\Package:<ul>
284 <li>File extraction and update methods didn't work on merge modules - fixed</li>
285 <li>Made file update code slightly more robust</li>
286 <li>Removed hard-reference to the FilePatch assembly - now it is only
287 loaded if working with binary file patches</li>
288</ul></li>
289<li>AugmentIL:<ul>
290 <li>AugmentIL would crash if some input files had read-only attr - fixed</li>
291 <li>Made /verbose switch slightly more verbose</li>
292</ul></li>
293<li>CAProxy:<ul>
294 <li>Added support for the DynamicLoad property of CustomActionAttribute</li>
295 <li>Added MMsiBreak debugging functionality - see doc</li>
296</ul></li>
297<li>Samples\WiFile:<ul>
298 <li>Added /l (list files) switch</li>
299</ul></li>
300<li>Samples\SampleCAs:<ul>
301 <li>In the makefile the comments about debug builds had an error;
302 Now the sample builds debug packages (correctly) by default.</li>
303</ul></li>
304<li>Documentation:<ul>
305 <li>Wrote AugmentIL.htm describing the AugmentIL tool and its options.</li>
306 <li>Wrote WiFile.htm describing the WiFile sample tool.</li>
307 <li>Added section to ReadMe.htm about debugging managed custom actions.</li>
308</ul></li>
309</ul>
310
311<hr size="2"/>
312<h3><b>2003-03-31</b></h3>
313
314<ul>
315<li>Msi: Implemented the remaining APIs, also minor improvements and bugfixes<ul>
316 <li>All published APIs are wrapped, with the exception of four:
317 MsiGetFileSignatureInformation (because I don't know of a .NET analog
318 for the returned certificate structure), and 3 APIs for previewing UI</li>
319 <li>Database.OpenView and Database.Execute* now take String.Format style params</li>
320 <li>Database.ApplyTransform can optionally use the error-suppression flags
321 stored in the transform summary info</li>
322 <li>Added a few supporting enumerations and structures for the remaining APIs</li>
323 <li>InstallerException gets a descriptive message for any MSI or system error</li>
324 <li>Fixed a bug in InstallerException which would usually report &quot;error 0&quot;</li>
325 <li>Added optimization for setting a Record field to a MemoryStream</li>
326 <li>Record.GetStream is capable of extracting substorages</li>
327 <li>Moved InstallPath class to Microsoft.WindowsInstaller.Package assembly</li>
328</ul></li>
329<li>Msi\FilePatch: added new project<ul>
330 <li>Binary file patch API wrapper</li>
331</ul></li>
332<li>Msi\Package: added new project<ul>
333 <li>Helper classes for working with MSI and MSP packages</li>
334</ul></li>
335<li>Cab: some minor bugfixes<ul>
336 <li>Cabinet.Extract(stream, name) threw a NullReferenceException if the file
337 didn't exist in the cab -- now it returns null</li>
338 <li>CabinetInfo.CompressFileSet() was broken -- fixed</li>
339 <li>If a Cabinet callback throws an exception, it is propogated as the
340 inner-exception of the CabinetException</li>
341</ul></li>
342<li>Samples\WiFile: added new sample<ul>
343 <li>Demonstrates some features of InstallPackage class in Msi\Package project</li>
344</ul></li>
345</ul>
346
347<hr size="2"/>
348<h3><b>2003-03-20</b></h3>
349
350Documentation!<ul>
351 <li>Msi and Cab sources include complete C# XML documentation.</li>
352 <li>Msi and Cab makefiles generate XML documentation files.</li>
353 <li>Reference CHM compiled from XML documentation with NDoc.</li>
354</ul>
355
356<p>I am aware that exceptions are still not documented in most areas.
357 Other than that, feel free to send me a note if it's still not clear
358 how to use parts of the API after reading the documentation.</p>
359
360<p>Version is still 1.1 because there are no code changes in this release.</p>
361
362<hr size="2"/>
363<h3><b>2003-03-13</b></h3>
364
365<ul>
366<li>Msi: lots of small improvements for usability and consistency<ul>
367 <li>Reworked ExternalUIHandler support</li>
368 <li>Added Installer properties/methods:<ul>
369 <li>Components</li>
370 <li>ComponentClients()</li>
371 <li>ComponentState()</li>
372 <li>ComponentPath()</li>
373 <li>EnableLog()</li>
374 </ul></li>
375 <li>Added Session.EvaluateCondition() method</li>
376 <li>Improved exception-handling in many methods in Installer, Database,
377 &amp; Session classes</li>
378 <li>Added extensive XML doc-comments to Installer, Database, View,
379 &amp; Session classes</li>
380 <li>A few breaking changes:<ul>
381 <li>View.ModifyMode enumeration moved outside View and
382 renamed ViewModifyMode</li>
383 <li>InstallLogMode enumeration renamed to InstallLogModes
384 (naming convention for bitfields)</li>
385 <li>Record constructor takes arbitrary number of parameters</li>
386 </ul></li>
387</ul></li>
388<li>AugmentIL: almost completely rewritten<ul>
389 <li>Ildasm/ilasm steps are built-in<ul>
390 <li>The round-trip can be done in one step</li>
391 <li>IL source input/output is still supported</li>
392 </ul></li>
393 <li>Never throws an unhandled exception</li>
394 <li>Organized command-line options, consistent with other .NET tools</li>
395 <li>Uses a plugin architecture to allow additional augmentations</li>
396</ul></li>
397<li>CAProxy: Added AIL_CAProxy.cs - AugmentIL plugin generates CA proxy methods</li>
398
399<li>SampleCAs: Updated makefile for new AugmentIL usage</li>
400</ul>
401
402<hr size="2"/>
403<h3><b>2003-01-16</b></h3>
404
405<ul>
406<li>ReadMe.htm: Added section on writing managed CAs</li>
407<li>SampleCAs: Added missing reference to System.Windows.Forms.dll to the makefile</li>
408<li>AugmentIL: Added specific warning messages for when CA method has wrong signature</li>
409<li>Put sources in Toolbox-hosted Source Depot.</li>
410</ul>
411
412<hr size="2"/>
413<h3><b>2003-01-14</b></h3>
414Initial posting to http://toolbox
415
416<p>&nbsp;</p>
417 </div>
418
419 <div id="footer">
420 <p />
421 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
422 wix-users@lists.sourceforge.net</a>
423
424 <script type="text/javascript">
425 var HT_mailLink = document.getElementById("HT_MailLink");
426 var HT_mailLinkText = HT_mailLink.innerHTML;
427 HT_mailLink.href += ": " + document.title;
428 HT_mailLink.innerHTML = HT_mailLinkText;
429 </script>
430
431 <p />
432
433 </div>
434 </div>
435
436</body>
437</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/installutil.htm b/src/samples/Dtf/Documents/Guide/Content/installutil.htm
new file mode 100644
index 00000000..e235a7b6
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/installutil.htm
@@ -0,0 +1,94 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>About InstallUtil</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">About InstallUtil</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <a href="using.htm">Development Guide</a> &gt;
16 <a href="managedcas.htm">Managed CAs</a> &gt;
17 <span class="nolink">InstallUtil</span>
18 </span>
19 </div>
20 </div>
21 <div id="main">
22 <div id="header">
23 </div>
24 <div class="summary">
25 <p>
26 InstallUtil is often considered as another option for executing MSI custom actions
27 written in managed code. But in most cases it is not the best solution, for a number
28 of reasons.</p>
29 <p>
30 InstallUtil (in either InstallUtil.exe or InstallUtilLib.dll form) is a .NET Framework
31 tool for executing the System.Configuration.Installer classes that are implemented
32 in an assembly. That way the assembly can contain any special code required to install
33 itself and uninstall itself. Essentially it is the .NET replacement for COM self-registration
34 aka DllRegisterServer.</p>
35 <p>
36 Self-reg or System.Configuration.Installer is convenient for development use in
37 order to test code without creating an actual setup package, or for an IDE which
38 wants to generate self-installing code. But experienced setup developers and the
39 <a href="MSI.chm::/setup/selfreg_table.htm">Windows Installer documentation</a>
40 all agree that self-reg is a bad practice for a
41 production-quality setup. The current theory of state-of-the-art setup is that it
42 should be as data-driven as possible. That is, the setup package describes as fully
43 as possible the desired state of the system, and then the installer engine calculates
44 the necessary actions to install, uninstall, patch, etc.</p>
45 <p>
46 S.C.I encourages developers to write code for things such as registering services
47 or registering COM classes or other things which are more appropriately done using
48 built-in MSI functionality (the ServiceInstall and Registry tables). The Visual
49 Studio .NET wizards also tend to generate this kind of install code. Again, that
50 is nice for development but not good for real installations. You end up with similar
51 but slightly different code in many places for doing the same thing. And that code
52 is a black-box to the installer engine.</p>
53 <p>
54 An ideal MSI custom action is a logical extension of the setup engine, meaning it
55 is data-driven and written in a very generic way to read from existing or custom
56 tables in the MSI database, following a very similar pattern to the built-in actions.
57 This makes the CA re-usable, and makes the installation more transparent. S.C.I
58 custom actions invoked by InstallUtil cannot be data-driven because they don't have
59 full access to the install session or database. They also cannot write to the install
60 session's regular MSI log, but instead use a separate log which is bad for supportability.</p>
61 <p>
62 InstallUtil also requires that the assembly be installed before the CA is able to
63 execute. This is a problem for CAs that need to execute during the UI phase, or
64 gather information before installation. For that purpose MSI allows custom action
65 binaries to be embedded as non-installed files, but InstallUtil cannot make use
66 of those.</p>
67 <p>
68 Custom actions developed with DTF have none of the limitations of InstallUtil,
69 giving a setup developer full capabilities to write well-designed custom actions,
70 only now in managed code.
71 </p>
72
73 <p>&nbsp;</p>
74 </div>
75
76 <div id="footer">
77 <p />
78 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
79 wix-users@lists.sourceforge.net</a>
80
81 <script type="text/javascript">
82 var HT_mailLink = document.getElementById("HT_MailLink");
83 var HT_mailLinkText = HT_mailLink.innerHTML;
84 HT_mailLink.href += ": " + document.title;
85 HT_mailLink.innerHTML = HT_mailLinkText;
86 </script>
87
88 <p />
89
90 </div>
91 </div>
92
93</body>
94</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/inventory.htm b/src/samples/Dtf/Documents/Guide/Content/inventory.htm
new file mode 100644
index 00000000..40a6ef74
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/inventory.htm
@@ -0,0 +1,78 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>Windows Installer System Inventory Viewer</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">Windows Installer System Inventory Viewer</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <a href="using.htm">Development Guide</a> &gt;
16 <a href="samples.htm">Samples</a> &gt;
17 <span class="nolink">Inventory</span>
18 </span>
19 </div>
20 </div>
21 <div id="main">
22 <div id="header">
23 </div>
24 <div class="summary">
25 <p>This application shows a hierarchical, relational, searchable
26 view of all of the product, feature, component, file, and patch
27 data managed by MSI, for all products installed on the system.</p>
28 <p><br/></p>
29
30 <h4>Navigation</h4>
31 <ol>
32 <li><p>The tree on the left is self-explanatory.</p></li>
33 <li><p>Click on a row-header (grey box on the left side of the
34 grid) to jump to a table with more details about the item referred
35 to by that row. For example, clicking on a row-header of a
36 table that lists components will take you to a table that lists
37 the files in that component. Not every table has this ability,
38 but the cursor will turn to a hand shape to indicate when this is
39 possible.</p></li>
40 <li><p>Also you can navigate back and forward through your history
41 using the buttons in the application or mouse buttons 4 and 5.</p></li>
42 </ol>
43 <p><br/></p>
44
45 <h4>Searching</h4>
46 <p>The search feature is not hard to find. By default, searches
47 are limited to the current table. However, if you choose to find
48 &quot;In Subtree&quot; by checking the box, the search will include
49 the current table as well as all tables under the current location in
50 the tree. While this can take a long time if there is a lot of
51 data under the current node, you can stop the search at any time with
52 the stop button. The search pauses when a match is found, but
53 clicking &quot;Find&quot; again will continue the same search from that
54 point (unless you uncheck the &quot;Continue&quot; checkbox or change
55 the search string).</p>
56
57 <p><br/></p>
58 </div>
59
60 <div id="footer">
61 <p />
62 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
63 wix-users@lists.sourceforge.net</a>
64
65 <script type="text/javascript">
66 var HT_mailLink = document.getElementById("HT_MailLink");
67 var HT_mailLinkText = HT_mailLink.innerHTML;
68 HT_mailLink.href += ": " + document.title;
69 HT_mailLink.innerHTML = HT_mailLinkText;
70 </script>
71
72 <p />
73
74 </div>
75 </div>
76
77</body>
78</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/managedcas.htm b/src/samples/Dtf/Documents/Guide/Content/managedcas.htm
new file mode 100644
index 00000000..9cce0432
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/managedcas.htm
@@ -0,0 +1,53 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>Managed Custom Actions</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">Managed Custom Actions</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <a href="using.htm">Development Guide</a> &gt;
16 <span class="nolink">Managed CAs</span>
17 </span>
18 </div>
19 </div>
20 <div id="main">
21 <div id="header">
22 </div>
23 <div class="summary">
24
25 <ul>
26 <li><a href="writingcas.htm">Writing Managed Custom Actions</a></li>
27 <li><a href="caconfig.htm">Specifying the Runtime Version</a></li>
28 <li><a href="buildingcas.htm">Building Managed Custom Actions</a></li>
29 <li><a href="debuggingcas.htm">Debugging Managed Custom Actions</a></li>
30 <li><a href="installutil.htm">About InstallUtil</a></li>
31 </ul>
32
33 </div>
34
35 <div id="footer">
36 <p />
37 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
38 wix-users@lists.sourceforge.net</a>
39
40 <script type="text/javascript">
41 var HT_mailLink = document.getElementById("HT_MailLink");
42 var HT_mailLinkText = HT_mailLink.innerHTML;
43 HT_mailLink.href += ": " + document.title;
44 HT_mailLink.innerHTML = HT_mailLinkText;
45 </script>
46
47 <p />
48
49 </div>
50 </div>
51
52</body>
53</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/msihelper.htm b/src/samples/Dtf/Documents/Guide/Content/msihelper.htm
new file mode 100644
index 00000000..c1493117
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/msihelper.htm
@@ -0,0 +1,59 @@
1<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
2<html>
3 <head>
4 <title>Included Components</title>
5 <meta http-equiv="Content-Type" content="text/html; charset=Windows-1252">
6 <link rel="stylesheet" type="text/css" href="MSDN.css">
7 </head>
8 <body id="bodyID" class="dtBODY">
9 <div id="nsbanner">
10 <div id="bannerrow1">
11 <table class="bannerparthead" cellspacing="0" id="Table1">
12 <tr id="hdr">
13 <td class="runninghead">Managed Libraries for Windows Installer</td>
14 <td class="product"></td>
15 </tr>
16 </table>
17 </div>
18 <div id="TitleRow">
19 <h1 class="dtH1">Helper Classes for Windows Installer Packages</h1>
20 </div>
21 </div>
22 <div id="nstext">
23 <p>Included are some useful helper classes for working with
24 MSI and MSP packages:</p>
25 <ul>
26 <li><p><a href="ms-its:MMLRef.chm::/Microsoft.WindowsInstaller.InstallPackage.html"
27 ><strong>InstallPackage</strong></a> - extends the Database class to provide powerful
28 package-based operations such as:</p>
29 <ul>
30 <li>direct extraction of files to uncompressed source
31 path
32 <li>updating files from uncompressed source path back
33 into the compressed source for the package (including updating file
34 metadata)
35 <li>applying a patch directly to the package
36 <li>consolidating a package with uncompressed source files or multiple msm-cabs
37 into a package with a single compressed cabinet</li>
38 </ul>
39 <P></P>
40 <li><p><a href="ms-its:MMLRef.chm::/Microsoft.WindowsInstaller.InstallPathMap.html"
41 ><strong>InstallPathMap</strong>, <a href="ms-its:MMLRef.chm::/Microsoft.WindowsInstaller.InstallPath.html"
42 ><strong>InstallPath</strong></a> - represent the directory structure
43 of an installation package, including file, component, and directory source and target
44 install paths. Accessible by file, component, or directory keys; searchable by
45 filename.</p>
46 <li><p><a href="ms-its:MMLRef.chm::/Microsoft.WindowsInstaller.PatchPackage.html"
47 ><strong>PatchPackage</strong></a> - allows convenient access to patch properties,
48 and analysis and extraction of transforms</p></li>
49 </ul>
50 <p><br/></p>
51 <p>These classes are in the Microsoft.WindowsInstaller.Package.dll assembly.</p>
52 <p><br/></p>
53 <p><b>See also:</b></p>
54 <p>The <a href="wifile.htm">WiFile</a> sample tool demonstrates some usage of the
55 InstallPackage class.</p>
56 <p><br/></p>
57 </div>
58 </body>
59</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/msiwrapper.htm b/src/samples/Dtf/Documents/Guide/Content/msiwrapper.htm
new file mode 100644
index 00000000..70190ac4
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/msiwrapper.htm
@@ -0,0 +1,80 @@
1<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
2<html>
3 <head>
4 <title>Included Components</title>
5 <meta http-equiv="Content-Type" content="text/html; charset=Windows-1252">
6 <link rel="stylesheet" type="text/css" href="MSDN.css">
7 </head>
8 <body id="bodyID" class="dtBODY">
9 <div id="nsbanner">
10 <div id="bannerrow1">
11 <table class="bannerparthead" cellspacing="0" id="Table1">
12 <tr id="hdr">
13 <td class="runninghead">Managed Libraries for Windows Installer</td>
14 <td class="product"></td>
15 </tr>
16 </table>
17 </div>
18 <div id="TitleRow">
19 <h1 class="dtH1">Managed wrapper library for Windows Installer APIs</h1>
20 </div>
21 </div>
22 <div id="nstext">
23 <p>Microsoft.WindowsInstaller.dll is a complete .NET wrapper for the
24 Windows Installer APIs. It provides a convenient object model that is
25 comfortable to .NET developers and still familiar to anyone who has used
26 the MSI scripting object model.</p>
27 <h3>Notes</h3>
28 <ul>
29 <li><p>All published MSI APIs are wrapped, with the exception of four:
30 MsiGetFileSignatureInformation (because I don't know of a .NET analog for the
31 returned certificate structure), and three APIs for previewing UI dialogs.
32 Other than that, you should be able to do everything that you can
33 do via the C APIs or the COM automation interface.</p>
34 <li><p>Some parts of this code have never had a formal test
35 pass, so use at your own risk. But much of the code is exercised daily, used
36 by the Developer Division Sustaining Engineering team's BRIQS system to build
37 and test patches. And it has been in use by many other teams for over two
38 years now with only a few minor fixes, so it can be considered very stable.</p>
39 <li><p>Despite its official-sounding namespace, this assembly is not officially
40 sanctioned by the Windows Installer team. But currently there are not any
41 plans for an official set of managed even in Longhorn, so I don't see a
42 conflict for now.</p></li>
43 </ul>
44 <h3>Why rewrite it?</h3>
45 <p>It is possible to access MSI's COM Automation interfaces via C# and VB.NET.
46 So why create yet another wrapper? Here are some of my reasons:</p>
47 <ul>
48 <li><p>One of the primary things I wanted to be able to do
49 was write custom actions in C#. The automation interface was not usable in
50 that case, because there is no way to convert the integer session handle
51 (received as a parameter to the type 1 custom action function) into a Session
52 automation object.</p>
53 <li><p>The automation interface does not provide a way to
54 specify an external UI handler. Besides external UI, this is also needed
55 to do validation.</p>
56 <li><p>The automation interface does not provide a way to
57 explicitly close handles (other than Views). I ran into this problem when I
58 wanted to programmatically delete a database that I'd just finished using, but
59 couldn't because it was still open!</p>
60 <li><p>Finally, COM Automation is somewhat slower than invoking
61 the APIs directly.</p></li>
62 </ul>
63
64 <p><br/></p>
65 <p><b>See also:</b></p>
66 <ul>
67 <li><a href="ms-its:MMLRef.chm::/Microsoft.WindowsInstaller.html">Microsoft.WindowsInstaller Namespace</a></li>
68 <ul>
69 <li><a href="ms-its:MMLRef.chm::/Microsoft.WindowsInstaller.Installer.html">Installer Class</a></li>
70 <li><a href="ms-its:MMLRef.chm::/Microsoft.WindowsInstaller.Database.html">Database Class</a></li>
71 <li><a href="ms-its:MMLRef.chm::/Microsoft.WindowsInstaller.Session.html">Session Class</a></li>
72 </ul>
73 <li><a href="msihelper.htm">Helper Classes for Windows Installer Packages</a></li>
74 <li><a href="writingcas.htm">Writing Managed Custom Actions</a></li>
75 <li><a href="databases.htm">Working with MSI Databases</a></li>
76 </ul>
77 <p><br/></p>
78 </div>
79 </body>
80</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/packages.htm b/src/samples/Dtf/Documents/Guide/Content/packages.htm
new file mode 100644
index 00000000..aa521685
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/packages.htm
@@ -0,0 +1,86 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>Working with Install Packages</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">Working with Install Packages</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <a href="using.htm">Development Guide</a> &gt;
16 <span class="nolink">Install Packages</span>
17 </span>
18 </div>
19 </div>
20 <div id="main">
21 <div id="header">
22 </div>
23 <div class="summary">
24
25 <h3>Updating files in a product layout</h3>
26 <p>The InstallPackage class makes it easy to work with files and cabinets
27 in the context of a compressed or uncompressed product layout.</p>
28 <p>This hypothetical example takes an IDictionary 'files' which maps file keys to file paths. Each
29 file is to be updated in the package layout; cabinets are even recompressed if necessary to include the new files.</p>
30 <pre><font face="Consolas, Courier New"> <font color=blue>using</font> (InstallPackage pkg = <font color=blue>new</font> InstallPackage(<font color=purple>"d:\builds\product.msi"</font>,
31 DatabaseOpenMode.Transact))
32 {
33 pkg.WorkingDirectory = Path.Combine(Path.GetTempFolder(), <font color=purple>"pkgtmp"</font>);
34 <font color=blue>foreach</font> (string fileKey in files.Keys)
35 {
36 <font color=blue>string</font> sourceFilePath = files[fileKey];
37 <font color=blue>string</font> destFilePath = pkg.Files[fileKey].SourcePath;
38 destFilePath = Path.Combine(pkg.WorkingDirectory, destFilePath);
39 File.Copy(sourceFilePath, destFilePath, <font color=blue>true</font>);
40 }
41 pkg.UpdateFiles(<font color=blue>new</font> ArrayList(files.Keys));
42 pkg.Commit();
43 Directory.Delete(pkg.WorkingDirectory, <font color=blue>true</font>);
44 }</font></pre><br />
45 <p>1.&nbsp; Create a <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_WindowsInstaller_Package_InstallPackage__ctor.htm">new InstallPackage</a>
46 instance referring to the location of the .msi. This is actually just a specialized subclass of a Database.</p>
47 <p>2.&nbsp; Set the <a href="DTFAPI.chm::/html/P_Microsoft_Deployment_WindowsInstaller_Package_InstallPackage_WorkingDirectory.htm">WorkingDirectory</a>.
48 This is the root directory where the package expects to find the new source files.</p>
49 <p>3.&nbsp; Copy each file to its proper location in the working directory. The
50 <a href="DTFAPI.chm::/html/P_Microsoft_Deployment_WindowsInstaller_Package_InstallPackage_Files.htm">InstallPackage.Files</a>
51 property is used to look up the relative source path of each file.</p>
52 <p>4.&nbsp; Call <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_WindowsInstaller_Package_InstallPackage_UpdateFiles.htm">InstallPackage.UpdateFiles</a>
53 with the list of file keys. This will re-compress and package the files if necessary, and also update the
54 following data: File.FileSize, File.Version, File.Language, MsiFileHash.HashPart*.</p>
55 <p>5.&nbsp; Commit the database changes and cleanup the working directory.</p>
56 </ul>
57
58 <p><br/></p>
59 <p><b>See also:</b></p>
60 <ul>
61 <li><a href="wifile.htm">WiFile Sample Tool</a> - a more complete tool that expands on the above example.</li>
62 <li><a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_Package_InstallPackage.htm">InstallPackage Class</a></li>
63 </ul>
64 <p><br/></p>
65
66 </div>
67
68 <div id="footer">
69 <p />
70 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
71 wix-users@lists.sourceforge.net</a>
72
73 <script type="text/javascript">
74 var HT_mailLink = document.getElementById("HT_MailLink");
75 var HT_mailLinkText = HT_mailLink.innerHTML;
76 HT_mailLink.href += ": " + document.title;
77 HT_mailLink.innerHTML = HT_mailLinkText;
78 </script>
79
80 <p />
81
82 </div>
83 </div>
84
85</body>
86</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/powerdiff.htm b/src/samples/Dtf/Documents/Guide/Content/powerdiff.htm
new file mode 100644
index 00000000..f420b47e
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/powerdiff.htm
@@ -0,0 +1,71 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>MSI, MSP, CAB Diff Tool</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">MSI, MSP, CAB Diff Tool</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <a href="using.htm">Development Guide</a> &gt;
16 <a href="samples.htm">Samples</a> &gt;
17 <span class="nolink">DDiff</span>
18 </span>
19 </div>
20 </div>
21 <div id="main">
22 <div id="header">
23 </div>
24 <div class="summary">
25 <h2>MSI, MSP, CAB Diff Tool</h2>
26
27 <p><pre><font face="Consolas, Courier New">Usage: DDiff target1 target2 [options]
28Example: DDiff d:\dir1 d:\dir2
29Example: DDiff setup1.msi setup2.msi
30Example: DDiff patch1.msp patch2.msp -patchtarget target.msi
31Example: DDiff package1.cab package2.cab
32
33Options:
34 /o [filename] Output results to text file (UTF8)
35 /p [package.msi] Diff patches relative to target MSI</font></pre>
36 </p>
37 <p><br/></p>
38
39 <p>The following types of inputs can be diffed:
40 <ul>
41 <li><b>Directories</b>: files and subdirectories are compared.</li>
42 <li><b>Cab files</b>: internal file list and files are compared.</li>
43 <li><b>MSI/MSM database files</b>: summary info, tables, and embedded binary and cab streams are compared.</li>
44 <li><b>MSP files</b>: summary info and embedded file cab are compared. When a patch target MSI is provided, the MSP's tables are also compared.</li>
45 <li><b>Versioned files</b>: Win32 file version is compared.</li>
46 <li><b>Text files</b>: if diff.exe is in the path, it is used to get a line-by-line diff.</li>
47 <li><b>Other files</b>: file size and bytes are compared.</li>
48 </ul>
49 All processing is done recursively. So a versioned file within a cab within an MSI within a directory will have meaningful diff results.</p>
50
51 <p><br/></p>
52 </div>
53 <div id="footer">
54 <p />
55 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
56 wix-users@lists.sourceforge.net</a>
57
58 <script type="text/javascript">
59 var HT_mailLink = document.getElementById("HT_MailLink");
60 var HT_mailLinkText = HT_mailLink.innerHTML;
61 HT_mailLink.href += ": " + document.title;
62 HT_mailLink.innerHTML = HT_mailLinkText;
63 </script>
64
65 <p />
66
67 </div>
68 </div>
69
70</body>
71</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/samplecas.htm b/src/samples/Dtf/Documents/Guide/Content/samplecas.htm
new file mode 100644
index 00000000..4dfed6f0
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/samplecas.htm
@@ -0,0 +1,84 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>Sample C# Custom Action</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">Sample C# Custom Action</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <a href="using.htm">Development Guide</a> &gt;
16 <a href="managedcas.htm">Managed CAs</a> &gt;
17 <a href="writingcas.htm">Writing CAs</a> &gt;
18 <span class="nolink">C# Sample</span>
19 </span>
20 </div>
21 </div>
22 <div id="main">
23 <div id="header">
24 </div>
25 <div class="summary">
26
27 <p>MSI custom actions are MUCH easier to write in C# than
28 in C++!</p><pre><font face="Consolas, Courier New"> [CustomAction]
29 <font color=blue>public</font> <font color=blue>static</font> ActionResult SampleCustomAction1(Session session)
30 {
31 session.Log(<font color="purple">"Hello from SampleCA1"</font>);
32
33 <font color=blue>string</font> testProp = session[<font color="purple">"SampleCATest"</font>];
34 <font color=blue>string</font> testProp2;
35 testProp2 = (<font color="blue">string</font>) session.Database.ExecuteScalar(
36 <font color="purple">"SELECT `Value` FROM `Property` WHERE `Property` = 'SampleCATest'"</font>);
37
38 <font color=blue>if</font>(testProp == testProp2)
39 {
40 session.Log(<font color="purple">"Simple property test passed."</font>);
41 <font color=blue>return</font> ActionResult.Success;
42 }
43 <font color=blue>else</font>
44 <font color=blue>return</font> ActionResult.Failure;
45 }
46 </font></pre>
47 <p>A sample CA project with two CAs is included in the
48 Samples\ManagedCA directory.&nbsp; Running the CustomActionTest project will package the CA and insert
49 it into a test MSI. The MSI will invoke the custom actions, but it will not install anything
50 since the second sample CA returns ActionResult.UserExit.
51 </p>
52
53 <p><br/></p>
54 <p><b>See also:</b></p>
55 <ul>
56 <li><a href="writingcas.htm">Writing Managed Custom Actions</a></li>
57 <li><a href="caconfig.htm">Specifying the Runtime Version</a></li>
58 <li><a href="databases.htm">Working with MSI Databases</a></li>
59 <li><a href="buildingcas.htm">Building Managed Custom Actions</a></li>
60 <li><a href="debuggingcas.htm">Debugging Managed Custom Actions</a></li>
61 </ul>
62 <p><br/></p>
63
64 </div>
65
66 <div id="footer">
67 <p />
68 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
69 wix-users@lists.sourceforge.net</a>
70
71 <script type="text/javascript">
72 var HT_mailLink = document.getElementById("HT_MailLink");
73 var HT_mailLinkText = HT_mailLink.innerHTML;
74 HT_mailLink.href += ": " + document.title;
75 HT_mailLink.innerHTML = HT_mailLinkText;
76 </script>
77
78 <p />
79
80 </div>
81 </div>
82
83</body>
84</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/samples.htm b/src/samples/Dtf/Documents/Guide/Content/samples.htm
new file mode 100644
index 00000000..3bcd379a
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/samples.htm
@@ -0,0 +1,59 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>Sample Applications</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">Sample Applications</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <a href="using.htm">Development Guide</a> &gt;
16 <span class="nolink">Samples</span>
17 </span>
18 </div>
19 </div>
20 <div id="main">
21 <div id="header">
22 </div>
23 <div class="summary">
24 <p>Besides the simple managed custom action sample, there are three functional
25 and useful sample tools included in this distribution:</p>
26 <p><a href="Inventory.htm"><b>MSI Inventory</b></a><br/>
27 Shows a hierarchical, relational, searchable view of all of the product,
28 feature, component, file, and patch data managed by MSI, for all products
29 installed on the system.</p>
30 <p><a href="WiFile.htm"><b>WiFile</b></a><br/>
31 Extracts and updates cabbed files in an MSI setup.</p>
32 <p><a href="CabPack.htm"><b>CabPack</b></a><br/>
33 Creates simple self-extracting cab packages. OK, so this one isn't
34 especially useful as a tool, but the code should be helpful.</p>
35 <p><a href="PowerDiff.htm"><b>DDiff</b></a><br/>
36 Recursively diffs MSI, MSP, CAB, and other files and directories.
37 Much more thorough than widiffdb.vbs.</p>
38 <p><br/></p>
39 </div>
40
41 <div id="footer">
42 <p />
43 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
44 wix-users@lists.sourceforge.net</a>
45
46 <script type="text/javascript">
47 var HT_mailLink = document.getElementById("HT_MailLink");
48 var HT_mailLinkText = HT_mailLink.innerHTML;
49 HT_mailLink.href += ": " + document.title;
50 HT_mailLink.innerHTML = HT_mailLinkText;
51 </script>
52
53 <p />
54
55 </div>
56 </div>
57
58</body>
59</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/support.htm b/src/samples/Dtf/Documents/Guide/Content/support.htm
new file mode 100644
index 00000000..89acbadf
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/support.htm
@@ -0,0 +1,52 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>Support/Bugs</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">Support/Bugs</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <a href="about.htm">Overview</a> &gt;
16 <span class="nolink">Support/Bugs</span>
17 </span>
18 </div>
19 </div>
20 <div id="main">
21 <div id="header">
22 </div>
23 <div class="summary">
24 <p>Please send general support questions or comments to the
25 <a href="mailto:wix-users@sourceforge.net">wix-users</a> discussion list.</p>
26
27 <p>Bugs, suggestions, or feature requests can be submitted at the
28 <a href="http://wix.sourceforge.net/">WiX project</a>
29 on Sourceforge.net.</p>
30
31 <p><br/></p>
32 </div>
33
34 <div id="footer">
35 <p />
36 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
37 wix-users@lists.sourceforge.net</a>
38
39 <script type="text/javascript">
40 var HT_mailLink = document.getElementById("HT_MailLink");
41 var HT_mailLinkText = HT_mailLink.innerHTML;
42 HT_mailLink.href += ": " + document.title;
43 HT_mailLink.innerHTML = HT_mailLinkText;
44 </script>
45
46 <p />
47
48 </div>
49 </div>
50
51</body>
52</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/using.htm b/src/samples/Dtf/Documents/Guide/Content/using.htm
new file mode 100644
index 00000000..6fe960e8
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/using.htm
@@ -0,0 +1,50 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>Deployment Tools Foundation Development Guide</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">Deployment Tools Foundation Development Guide</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <span class="nolink">Development Guide</span>
16 </span>
17 </div>
18 </div>
19 <div id="main">
20 <div id="header">
21 </div>
22 <div class="summary">
23 <ul>
24 <li><a href="managedcas.htm">Managed Custom Actions</a></li>
25 <li><a href="databases.htm">Working with MSI Databases</a></li>
26 <li><a href="cabs.htm">Working with Cabinet Files</a></li>
27 <li><a href="packages.htm">Working with Install Packages</a></li>
28 <li><a href="samples.htm">Sample Applications</a></li>
29 </ul>
30 </div>
31
32 <div id="footer">
33 <p />
34 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
35 wix-users@lists.sourceforge.net</a>
36
37 <script type="text/javascript">
38 var HT_mailLink = document.getElementById("HT_MailLink");
39 var HT_mailLinkText = HT_mailLink.innerHTML;
40 HT_mailLink.href += ": " + document.title;
41 HT_mailLink.innerHTML = HT_mailLinkText;
42 </script>
43
44 <p />
45
46 </div>
47 </div>
48
49</body>
50</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/whatsnew.htm b/src/samples/Dtf/Documents/Guide/Content/whatsnew.htm
new file mode 100644
index 00000000..3efe67bd
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/whatsnew.htm
@@ -0,0 +1,257 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>What's New?</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">What's New?</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <a href="about.htm">Overview</a> &gt;
16 <span class="nolink">What's New?</span>
17 </span>
18 <span id="languageFilter">2007-07-03</span>
19 </div>
20 </div>
21 <div id="main">
22 <div id="header">
23 </div>
24 <div class="summary">
25
26 <h3>Highlights</h3>
27 <ul>
28 <li><p>New project name name "Deployment Tools Foundation", and
29 new namespaces <font face="Consolas, Courier New">WixToolset.Dtf.*</font></p></li>
30 <li><p>Added ZIP compression library</p></li>
31 <li><p>Added library for reading/writing Win32 resources including file versions</p></li>
32 <li><p>Managed custom action improvements:</p><ul>
33 <li><p>Simplified authoring and building -- new MakeSfxCA tool
34 automatically maps DLL entrypoints to CA methods.</p></li>
35 <li><p>Managed custom action DLLs now run in a separate process for
36 better reliability with respect to CLR versions, but still have
37 full access to the MSI session.</p></li>
38 </ul></li>
39 <li><p>Found and fixed many bugs with extensive unit test suite</p></li>
40 <li><p>LINQ to MSI ! (preview)</p></li>
41 </ul>
42
43 <p>Unfortunately, all these changes do mean that migrating tools and applications from
44 the previous release can be a moderate amount of work.</p>
45
46 <h3>Breaking Changes</h3>
47 <p>For the first time since v1.0, this release contains some major breaking
48 changes, due to a significant redesign and cleanup effort that has been a
49 long time coming. The overall purpose of the changes is to bring the class
50 libraries much closer to ship-quality.</p>
51 <ul>
52 <li><p>All libraries use a new namespace hierarchy
53 under <font face="Consolas, Courier New">WixToolset.Dtf</font>.
54 The new namespace aligns with the new project name, gives all the various
55 libraries an identity that makes them obviously related to the DTF project,
56 and mostly avoids "taking over" a namespace that might be rightfully owned
57 by the platform technology owner.</p></li>
58
59 <li><p>Assemblies are also renamed to follow namespaces.</p></li>
60
61 <li><p>A new unified compression framework forms the basis for the new ZIP
62 library and a redesigned CAB library. Additional archive formats can
63 be plugged into the framework. The stream-based compression APIs have
64 been redesigned to be more object-oriented and easier to use. The file-based
65 APIs are mostly unchanged from the old cabinet library, although some names
66 have changed in order to fit into the new unified framework.</p></li>
67
68 <li><p>Large parts of the WindowsInstaller library have been redesigned
69 to be more object-oriented and to better follow .NET Framework design
70 guidelines. And various APIs throughout the library have naming or other
71 changes for better consistency and to follow conventions and best
72 pratices as enforced by FxCop.</p></li>
73
74 <li><p>The WindowsInstaller APIs no longer make any attempt to mimic the
75 MSI COM automation interfaces. The naming and object patterns in the
76 automation interfaces often conflicted with with best practices for
77 .NET Framework class libraries. Since most people start using DTF
78 without having ever experienced MSI scripting, there is little
79 reason to match the scripting object model. Making the APIs more
80 consistent with .NET conventions will make them much easier to use
81 for people already experienced with the .NET Framework.</p></li>
82
83 <li><p>APIs in all class libraries use generics where appropriate, especially
84 the generic collection interfaces. This means .NET Framework 2.0 or later
85 is required.</p></li>
86
87 <li><p>The FilePatch library is missing from this release. An updated
88 and redesigned file-delta library is in development.</p></li>
89 </ul>
90
91 <h3>Other Changes</h3>
92 <ul>
93 <li><p>New MakeSfxCA tool for building managed custom action packages: In addition to
94 packaging the CA DLL and dependencies, it automatically detects managed CA methods
95 and generates corresponding unmanaged DLL entrypoints in the CA host DLL (SfxCA.dll),
96 where they are called by MSI. Previously it was necessary to either provide this
97 mapping in a CustomAction.config file, or live with the generic "ManagedCustomActionN"
98 names when authoring the CustomAction table in the MSI. For more info, see the
99 help topic on building managed custom actions.</p></li>
100
101 <li><p>Out-of-proc managed custom action DLLs:
102 When a managed custom action runs, it normally requests a specific major
103 version of the CLR via CustomAction.config. However in the previous implementation,
104 the request could be ignored if there was already a different version of the CLR
105 loaded into the MSI process, either from a previous custom action or by some other
106 means. (The CLR doesn't allow side-by-side versions within the same process.)
107 While there have been no reports of this issue causing setup failures in practice,
108 it may be only a matter of time, as new CLR versions keep coming out.</p>
109
110 <p>The redesigned native host for managed custom actions, SfxCA.dll, re-launches
111 itself in a separate process before loading the CLR and invoking the managed CA.
112 This ensures that the desired CLR version is always loaded, assuming it is available
113 on the system. It also sets up a named-pipe remoting channel between the two processes.
114 All session-related MSI API calls are routed through that channel, so that the
115 custom action has full access to the installer session just as if it were
116 running in-process.</p></li>
117
118 <li><p>The new zip compression library supports nearly all features of the zip
119 file format. This includes the ZIP64 extensions for archives greater than 4GB,
120 as well as disk-spanning capabilities. Zip encryption is not supported. The zip
121 library has been tested against a variety of third-party zip tools; please
122 report any issues with incompatible packages.</p>
123
124 <p>Currently only the basic DEFLATE compression algorithm is supported
125 (via System.IO.Compression.DeflateStream), and the compression level is not adjustable
126 when packing an archive. The zip file format has a mechanism for plugging in arbitrary
127 compression algorithms, and that capability is exposed: you can provide a Stream object
128 capable of compressing and decompressing bytes as an alternative to DeflateStream.</p></li>
129
130 <li><p>Added support for the few APIs new in MSI 4.0:</p>
131 <ul>
132 <li><font face="Consolas, Courier New">Installer.GetPatchFileList()</font></li>
133 <li><font face="Consolas, Courier New">InstallLogModes.RMFilesInUse</font></li>
134 <li><font face="Consolas, Courier New">ComponentAttributes.DisableRegistryReflection</font></li>
135 <li><font face="Consolas, Courier New">ControlAttributes.ElevationShield</font></li>
136 </ul>&nbsp;<br /></li>
137
138 <li><p>The documentation is now built with the
139 <a href="http://msdn2.microsoft.com/en-us/vstudio/bb608422.aspx" target="_blank">Sandcastle</a> doc build engine,
140 with help from the <a href="http://www.codeplex.com/SHFB" target="_blank">Sandcastle
141 Help File Builder</a>. (The old CHM was built with NDoc.)</p></li>
142
143 <li><p>The documentation includes detailed class diagrams for the
144 WindowsInstaller and Compression namespaces.</p></li>
145
146 <li><p>WindowsInstaller API doc topics now link straight to the corresponding
147 unmanaged MSI API topics in MSDN. If you know an unmanaged MSI API you want to
148 use but don't know the managed equivalent, you can search for it and find what
149 managed APIs link to it.</p></li>
150
151 <li><p>Unit tests cover about 90% of the Compression, Compression.Zip, and
152 Compression.Cab assemblies -- basically everything except some rare
153 error-handling cases.</p></li>
154
155 <li><p>Unit tests along with samples cover over 50% of the WindowsInstaller and
156 WindowsInstaller.Package assemblies (including custom action functionality). More
157 test cases are still being added.</p></li>
158 </ul>
159
160 <h3>Bugfixes</h3>
161 <p>In addition to the extensive cleanup due to redesigns and unit tests, the following
162 reported bugs have been fixed:</p>
163 <ul>
164 <li><p>Managed custom actions could in rare instances fail to load with error 183
165 (directory already exists)</p></li>
166 <li><p>Timestamps of files in a cabinet could be incorrectly offset based on the timezone.
167 (This was due to a behavior change in the DateTime class between .NET 1.1 and 2.0.)</p></li>
168 <li><p>Unicode file paths for cabbed files could be handled incorrectly in some cases</p></li>
169 <li><p>Installer.DetermineApplicablePatches just didn't work</p></li>
170 <li><p>InstallPackage.ApplyPatch couldn't handle applying multiple patches to the same layout</p></li>
171 </ul>
172
173 <h3>LINQ to MSI</h3>
174 <p><i>You'll never want to write MSI SQL again!</i></p>
175 <p>Language INtegrated Query is a new feature in .NET Framework 3.5 and C# 3.0. Through
176 a combination of intuitive language syntax and powerful query operations, LINQ provides
177 a whole new level of productivity for working with data in your code. While the .NET
178 Framework 3.5 provides LINQ capability for SQL databases and XML data, now you
179 can write LINQ queries to fetch and even update data in MSI databases!</p>
180
181 <p>Look at the following example:<br />
182
183<pre><font face="Consolas, Courier New"> <font color="blue">var</font> actions = <font color="blue">from</font> a <font color="blue">in</font> db.InstallExecuteSequences
184 <font color="blue">join</font> ca <font color="blue">in</font> db.CustomActions <font color="blue">on</font> a.Action <font color="blue">equals</font> ca.Action
185 <font color="blue">where</font> ca.Type == CustomActionTypes.Dll
186 <font color="blue">orderby</font> a.Sequence
187 <font color="blue">select new</font> {
188 Name = a.Action,
189 Target = ca.Target,
190 Sequence = a.Sequence };
191
192 <font color="blue">foreach</font> (<font color="blue">var</font> a <font color="blue">in</font> actions)
193 {
194 Console.WriteLine(a);
195 }
196 </font></pre>
197
198 The query above gets automatically translated to MSI SQL:</p>
199
200 <p><font face="Consolas, Courier New">&nbsp;&nbsp;&nbsp;&nbsp;SELECT `InstallExecuteSequence`.`Action`,
201 `CustomAction`.`Target`, `InstallExecuteSequence`.`Sequence` FROM `InstallExecuteSequence`, `CustomAction`
202 WHERE `InstallExecuteSequence`.Action` = `CustomAction`.`Action` ORDER BY `InstallExecuteSequence`.`Sequence`</font></p>
203
204 <p>But the query is not executed until the <font face="Consolas, Courier New">foreach</font>
205 enumeration. Then records are fetched from the results incrementally as the enumeration progresses.
206 The objects fetched are actually of an anonymous type created there in the query with exactly
207 the desired fields. So the result of this code will be to print the Action, Target, and Sequence
208 of all Type 1 custom actions.</p>
209
210 <p>The query functionality is currently limited by the capabilities of the MSI SQL engine. For
211 example, a query can't use <font face="Consolas, Courier New">where (ca.Type &amp;
212 CustomActionTypes.Dll) != 0</font> because the bitwise-and operator is not supported by
213 MSI SQL. The preview version of LINQ to MSI will throw an exception for cases like that, but
214 the eventual goal is to have it automatically move the data and operation outside of MSI when
215 necessary, so that any arbitrary expressions are supported in the query.</p>
216
217 <p>Note there are no MSI handles (or <font face="Consolas, Courier New">IDisposable</font>s)
218 to worry about! Handles are all managed internally and closed deterministically. Also,
219 with the entity object model for common tables, the compiler will tell you if you get a
220 column name wrong or misspelled. The entity objects even support easy inserting, updating,
221 and deleting (not shown here).</p>
222
223 <p>For more examples, see the LinqTest project in the source. More documentation
224 is being written.</p>
225
226 <p>Obviously, LINQ to MSI requires .NET Framework 3.5. Everything else
227 in DTF requires only .NET Framework 2.0.</p>
228
229 <p><font color="red">Note: The LINQ functionality in this DTF release is of preview quality only
230 and should not be used in production. While there are unit tests covering a wide
231 variety of queries, using advanced queries outside what is covered by the tests
232 is likely to result in unexpected exceptions, and retrieved data might possibly be
233 incorrect or incomplete. An updated LINQ to MSI library is in development.</font></p>
234
235 <p>&nbsp;</p>
236
237 </div>
238
239 <div id="footer">
240 <p />
241 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
242 wix-users@lists.sourceforge.net</a>
243
244 <script type="text/javascript">
245 var HT_mailLink = document.getElementById("HT_MailLink");
246 var HT_mailLinkText = HT_mailLink.innerHTML;
247 HT_mailLink.href += ": " + document.title;
248 HT_mailLink.innerHTML = HT_mailLinkText;
249 </script>
250
251 <p />
252
253 </div>
254 </div>
255
256</body>
257</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/wifile.htm b/src/samples/Dtf/Documents/Guide/Content/wifile.htm
new file mode 100644
index 00000000..20998b73
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/wifile.htm
@@ -0,0 +1,73 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>Windows Installer Package File Manipulation Tool</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">Windows Installer Package File Manipulation Tool</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <a href="using.htm">Development Guide</a> &gt;
16 <a href="samples.htm">Samples</a> &gt;
17 <span class="nolink">WiFile</span>
18 </span>
19 </div>
20 </div>
21 <div id="main">
22 <div id="header">
23 </div>
24 <div class="summary">
25 <p><pre><font face="Consolas, Courier New">Usage: WiFile.exe package.msi /l [filename,filename2,...]
26Usage: WiFile.exe package.msi /x [filename,filename2,...]
27Usage: WiFile.exe package.msi /u [filename,filename2,...]
28
29Lists (/l), extracts (/x) or updates (/u) files in an MSI or MSM.
30Files are extracted using their source path relative to the package.
31Specified filenames do not include paths.
32Filenames may be a pattern such as *.exe or file?.dll</font></pre>
33 </p>
34 <p><br/></p>
35
36 <h4>Example</h4>
37 <p>The most powerful use of WiFile.exe is to do a round-trip update of files in a
38 compressed MSI/MSM package. It works like this:<ol>
39 <li>Extract specific file(s) or all files from the package:
40 <tt>WiFile.exe package.msi /x *</tt></li>
41 <li>The files are now expanded into their directory structure. You can edit/update
42 the files however you like.</li>
43 <li>Update the package with the changed files: <tt>WiFile.exe package.msi /u *</tt>&nbsp;
44 This also updates the file metadata in the MSI including the file version, size, and hash.</li>
45 </ol></p>
46 <p><br/></p>
47
48 <h4>Notes</h4>
49 <ul>
50 <li><p>Also works with packages that have multiple and/or external cab(s).</p></li>
51 </ul>
52
53 <p><br/></p>
54 </div>
55 <div id="footer">
56 <p />
57 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
58 wix-users@lists.sourceforge.net</a>
59
60 <script type="text/javascript">
61 var HT_mailLink = document.getElementById("HT_MailLink");
62 var HT_mailLinkText = HT_mailLink.innerHTML;
63 HT_mailLink.href += ": " + document.title;
64 HT_mailLink.innerHTML = HT_mailLinkText;
65 </script>
66
67 <p />
68
69 </div>
70 </div>
71
72</body>
73</html>
diff --git a/src/samples/Dtf/Documents/Guide/Content/writingcas.htm b/src/samples/Dtf/Documents/Guide/Content/writingcas.htm
new file mode 100644
index 00000000..6beccf5f
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/Content/writingcas.htm
@@ -0,0 +1,114 @@
1<html xmlns="http://www.w3.org/1999/xhtml">
2<head>
3 <title>Writing Managed Custom Actions</title>
4 <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6</head>
7
8<body>
9
10 <div id="control">
11 <span class="productTitle">Deployment Tools Foundation</span><br />
12 <span class="topicTitle">Writing Managed Custom Actions</span><br />
13 <div id="toolbar">
14 <span id="chickenFeet">
15 <a href="using.htm">Development Guide</a> &gt;
16 <a href="managedcas.htm">Managed CAs</a> &gt;
17 <span class="nolink">Writing CAs</span>
18 </span>
19 </div>
20 </div>
21 <div id="main">
22 <div id="header">
23 </div>
24 <div class="summary">
25 <p><b>Caveats</b></p>
26 <p>Before choosing to write a custom action in managed code instead of
27 traditional native C++ code, you should carefully consider the following:</p>
28 <ul>
29 <li><p>Obviously, it introduces a dependency on the .NET Framework. Your
30 MSI package should probably have a LaunchCondition to check for the presence
31 of the correct version of the .NET Framework before anything else happens.</p></li>
32 <li><p>If the custom action runs at uninstall time, then even the uninstall of
33 your product may fail if the .NET Framework is not present. This means a
34 user could run into a problem if they uninstall the .NET Framework before
35 your product.</p></li>
36 <li><p>A managed custom action should be configured to run against a specific
37 version of the .NET Framework, and that version should match the version your
38 actual product runs against. Allowing the version to "float" to the latest
39 installed .NET Framework is likely to lead to compatibility problems with
40 future versions. The .NET Framework provides side-by-side functionality for
41 good reason -- use it.</p></li>
42
43 </ul>
44 <p><br/></p>
45 <p><b>How To</b></p>
46 <ul>
47 <li><p>A custom action function needs to be declared as
48 <tt>public static</tt> (aka <tt>Public Shared</tt> in VB.NET). It takes one parameter which is
49 a <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_Session.htm">Session</a> object, and returns a
50 <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_ActionResult.htm">ActionResult</a> enumeration.</p>
51 <pre><font face="Consolas, Courier New"> [CustomAction]
52 <font color=blue>public</font> <font color=blue>static</font> ActionResult MyCustomAction(Session session)</font></pre><br />
53 <li><p>The function must have a
54 <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_CustomActionAttribute.htm"
55 >CustomActionAttribute</a>, which enables it to be
56 linked to a proxy function. The attribute can take an optional
57 &quot;name&quot; parameter, which is the name of the entrypoint
58 that is exported from the custom action DLL.</p>
59 <li><p>Fill in MSI CustomAction table entries just like you
60 would for a normal type 1 native-DLL CA. Managed CAs can also work just
61 as well in deferred, rollback, and commit modes.</p>
62 <li><p>If the custom action function throws any kind of
63 Exception that isn't handled internally, then it will be caught by the proxy
64 function. The Exception message and stack trace will be printed to the
65 MSI log if logging is enabled, and the CA will return a failure code.</p>
66 <li><p>To be technically correct, any MSI handles obtained should be
67 closed before a custom action function exits -- otherwise a warning
68 gets printed to the log. The handle classes in the managed library
69 (<a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_Database.htm">Database</a>,
70 <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_View.htm">View</a>,
71 <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_Record.htm">Record</a>,
72 <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_SummaryInfo.htm">SummaryInfo</a>)
73 all implement the IDisposable interface,
74 which makes them easily managed with C#'s <tt>using</tt>
75 statement. Alternatively, they can be closed in a finally block.
76 As a general rule, <i>methods</i> return new handle objects that should be
77 managed and closed by the user code, while <i>properties</i> only return a reference
78 to a prexisting handle object.</p></li>
79 <li><p>Don't forget to use a <a href="caconfig.htm">CustomAction.config</a> file to
80 specify what version of the .NET Framework the custom action should run against.</p></li>
81 </ul>
82
83 <p><br/></p>
84 <p><b>See also:</b></p>
85 <ul>
86 <li><a href="samplecas.htm">Sample C# Custom Actions</a></li>
87 <li><a href="caconfig.htm">Specifying the Runtime Version</a></li>
88 <li><a href="databases.htm">Working with MSI Databases</a></li>
89 <li><a href="buildingcas.htm">Building Managed Custom Actions</a></li>
90 <li><a href="debuggingcas.htm">Debugging Managed Custom Actions</a></li>
91 </ul>
92 <p><br/></p>
93
94 </div>
95
96 <div id="footer">
97 <p />
98 Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
99 wix-users@lists.sourceforge.net</a>
100
101 <script type="text/javascript">
102 var HT_mailLink = document.getElementById("HT_MailLink");
103 var HT_mailLinkText = HT_mailLink.innerHTML;
104 HT_mailLink.href += ": " + document.title;
105 HT_mailLink.innerHTML = HT_mailLinkText;
106 </script>
107
108 <p />
109
110 </div>
111 </div>
112
113</body>
114</html>
diff --git a/src/samples/Dtf/Documents/Guide/DTF.hhc b/src/samples/Dtf/Documents/Guide/DTF.hhc
new file mode 100644
index 00000000..bf43e447
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/DTF.hhc
@@ -0,0 +1,132 @@
1<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
2<HTML>
3<HEAD>
4<!-- Sitemap 1.0 -->
5</HEAD><BODY>
6<UL>
7 <LI><OBJECT type="text/sitemap">
8 <param name="Name" value="Deployment Tools Foundation Overview">
9 <param name="Local" value="Content\about.htm">
10 </OBJECT>
11 <UL>
12 <LI><OBJECT type="text/sitemap">
13 <param name="Name" value="What's New?">
14 <param name="Local" value="Content\whatsnew.htm">
15 </OBJECT>
16 </LI>
17 <LI><OBJECT type="text/sitemap">
18 <param name="Name" value="Change History">
19 <param name="Local" value="Content\history.htm">
20 </OBJECT>
21 </LI>
22 <LI><OBJECT type="text/sitemap">
23 <param name="Name" value="Dependencies">
24 <param name="Local" value="Content\dependencies.htm">
25 </OBJECT>
26 </LI>
27 <LI><OBJECT type="text/sitemap">
28 <param name="Name" value="Support/Bugs">
29 <param name="Local" value="Content\support.htm">
30 </OBJECT>
31 </LI>
32 </UL>
33 </LI>
34 <LI><OBJECT type="text/sitemap">
35 <param name="Name" value="Deployment Tools Foundation Development Guide">
36 <param name="Local" value="Content\using.htm">
37 </OBJECT>
38 <UL>
39 <LI><OBJECT type="text/sitemap">
40 <param name="Name" value="Managed Custom Actions">
41 <param name="Local" value="Content\managedcas.htm">
42 </OBJECT>
43 <UL>
44 <LI><OBJECT type="text/sitemap">
45 <param name="Name" value="Writing Managed Custom Actions">
46 <param name="Local" value="Content\writingcas.htm">
47 </OBJECT>
48 <UL>
49 <LI><OBJECT type="text/sitemap">
50 <param name="Name" value="Specifying the Runtime Version">
51 <param name="Local" value="Content\caconfig.htm">
52 </OBJECT>
53 </LI>
54 <LI><OBJECT type="text/sitemap">
55 <param name="Name" value="Sample C# Custom Actions">
56 <param name="Local" value="Content\samplecas.htm">
57 </OBJECT>
58 </LI>
59 </UL>
60 </LI>
61 <LI><OBJECT type="text/sitemap">
62 <param name="Name" value="Building Managed Custom Actions">
63 <param name="Local" value="Content\buildingcas.htm">
64 </OBJECT>
65 </LI>
66 <LI><OBJECT type="text/sitemap">
67 <param name="Name" value="Debugging Managed Custom Actions">
68 <param name="Local" value="Content\debuggingcas.htm">
69 </OBJECT>
70 </LI>
71 <LI><OBJECT type="text/sitemap">
72 <param name="Name" value="InstallUtil Notes">
73 <param name="Local" value="Content\installutil.htm">
74 </OBJECT>
75 </LI>
76 </UL>
77 </LI>
78 <LI><OBJECT type="text/sitemap">
79 <param name="Name" value="Working with MSI Databases">
80 <param name="Local" value="Content\databases.htm">
81 </OBJECT>
82 </LI>
83 <LI><OBJECT type="text/sitemap">
84 <param name="Name" value="Working with Cabinet Files">
85 <param name="Local" value="Content\cabs.htm">
86 </OBJECT>
87 </LI>
88 <LI><OBJECT type="text/sitemap">
89 <param name="Name" value="Working with Install Packages">
90 <param name="Local" value="Content\packages.htm">
91 </OBJECT>
92 </LI>
93 <LI><OBJECT type="text/sitemap">
94 <param name="Name" value="Sample Applications">
95 <param name="Local" value="Content\samples.htm">
96 </OBJECT>
97 <UL>
98 <LI><OBJECT type="text/sitemap">
99 <param name="Name" value="MSI Inventory">
100 <param name="Local" value="Content\inventory.htm">
101 </OBJECT>
102 </LI>
103 <LI><OBJECT type="text/sitemap">
104 <param name="Name" value="WiFile">
105 <param name="Local" value="Content\wifile.htm">
106 </OBJECT>
107 </LI>
108 <LI><OBJECT type="text/sitemap">
109 <param name="Name" value="XPack">
110 <param name="Local" value="Content\cabpack.htm">
111 </OBJECT>
112 </LI>
113 <LI><OBJECT type="text/sitemap">
114 <param name="Name" value="DDiff">
115 <param name="Local" value="Content\powerdiff.htm">
116 </OBJECT>
117 </LI>
118 </UL>
119 </LI>
120 </UL>
121 </LI>
122 <LI><OBJECT type="text/sitemap">
123 <param name="Name" value="Deployment Tools Foundation Reference">
124 <param name="Local" value="DTFAPI.chm::/html/R_Project.htm">
125 </OBJECT>
126 <OBJECT type="text/sitemap">
127 <param name="Merge" value="DTFAPI.chm::/DTFAPI.hhc">
128 </OBJECT>
129 </LI>
130</UL>
131</BODY>
132</HTML>
diff --git a/src/samples/Dtf/Documents/Guide/DTF.hhk b/src/samples/Dtf/Documents/Guide/DTF.hhk
new file mode 100644
index 00000000..bc6e49b3
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/DTF.hhk
@@ -0,0 +1,126 @@
1<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
2<HTML>
3<HEAD>
4<!-- Sitemap 1.0 -->
5</HEAD><BODY>
6<UL>
7 <LI><OBJECT type="text/sitemap">
8 <param name="Name" value="Deployment Tools Foundation Overview">
9 <param name="Local" value="Content\about.htm">
10 </OBJECT>
11 </LI>
12 <LI><OBJECT type="text/sitemap">
13 <param name="Name" value="What's New?">
14 <param name="Local" value="Content\whatsnew.htm">
15 </OBJECT>
16 </LI>
17 <LI><OBJECT type="text/sitemap">
18 <param name="Name" value="Dependencies">
19 <param name="Local" value="Content\dependencies.htm">
20 </OBJECT>
21 </LI>
22 <LI><OBJECT type="text/sitemap">
23 <param name="Name" value="Sample Applications">
24 <param name="Local" value="Content\samples.htm">
25 </OBJECT>
26 </LI>
27 <LI><OBJECT type="text/sitemap">
28 <param name="Name" value="Inventory Sample Application">
29 <param name="Local" value="Content\inventory.htm">
30 </OBJECT>
31 </LI>
32 <LI><OBJECT type="text/sitemap">
33 <param name="Name" value="WiFile Sample Tool">
34 <param name="Local" value="Content\wifile.htm">
35 </OBJECT>
36 </LI>
37 <LI><OBJECT type="text/sitemap">
38 <param name="Name" value="CabPack Sample Tool">
39 <param name="Local" value="Content\cabpack.htm">
40 </OBJECT>
41 </LI>
42 <LI><OBJECT type="text/sitemap">
43 <param name="Name" value="DDiff Sample Tool">
44 <param name="Local" value="Content\powerdiff.htm">
45 </OBJECT>
46 </LI>
47 <LI><OBJECT type="text/sitemap">
48 <param name="Name" value="Support/Bugs">
49 <param name="Local" value="Content\support.htm">
50 </OBJECT>
51 </LI>
52 <LI><OBJECT type="text/sitemap">
53 <param name="Name" value="Change History">
54 <param name="Local" value="Content\history.htm">
55 </OBJECT>
56 </LI>
57 <LI><OBJECT type="text/sitemap">
58 <param name="Name" value="Using Deployment Tools Foundation">
59 <param name="Local" value="Content\using.htm">
60 </OBJECT>
61 </LI>
62 <LI><OBJECT type="text/sitemap">
63 <param name="Name" value="Custom Actions">
64 <param name="Local" value="Content\managedcas.htm">
65 </OBJECT>
66 <UL>
67 <LI><OBJECT type="text/sitemap">
68 <param name="Name" value="Writing">
69 <param name="Local" value="Content\writingcas.htm">
70 </OBJECT>
71 </LI>
72 <LI>
73 <OBJECT type="text/sitemap">
74 <param name="Name" value="CustomAction.config">
75 <param name="Local" value="Content\caconfig.htm">
76 </OBJECT>
77 </LI>
78 <LI><OBJECT type="text/sitemap">
79 <param name="Name" value="Building">
80 <param name="Local" value="Content\buildingcas.htm">
81 </OBJECT>
82 </LI>
83 <LI><OBJECT type="text/sitemap">
84 <param name="Name" value="Debugging">
85 <param name="Local" value="Content\debuggingcas.htm">
86 </OBJECT>
87 </LI>
88 <LI><OBJECT type="text/sitemap">
89 <param name="Name" value="Samples">
90 <param name="Local" value="Content\samplecas.htm">
91 </OBJECT>
92 </LI>
93 </UL>
94 </LI>
95 <LI><OBJECT type="text/sitemap">
96 <param name="Name" value="InstallUtil">
97 <param name="Local" value="Content\installutil.htm">
98 </OBJECT>
99 </LI>
100 <LI><OBJECT type="text/sitemap">
101 <param name="Name" value="CustomAction.config file">
102 <param name="Local" value="Content\caconfig.htm">
103 </OBJECT>
104 </LI>
105 <LI><OBJECT type="text/sitemap">
106 <param name="Name" value="Sample C# Custom Actions">
107 <param name="Local" value="Content\samplecas.htm">
108 </OBJECT>
109 </LI>
110 <LI><OBJECT type="text/sitemap">
111 <param name="Name" value="Databases, Working with">
112 <param name="Local" value="Content\databases.htm">
113 </OBJECT>
114 </LI>
115 <LI><OBJECT type="text/sitemap">
116 <param name="Name" value="Cabinets, Working with">
117 <param name="Local" value="Content\cabs.htm">
118 </OBJECT>
119 </LI>
120 <LI><OBJECT type="text/sitemap">
121 <param name="Name" value="Packages, Working with">
122 <param name="Local" value="Content\packages.htm">
123 </OBJECT>
124 </LI>
125</UL>
126</BODY></HTML>
diff --git a/src/samples/Dtf/Documents/Guide/DTF.hhp b/src/samples/Dtf/Documents/Guide/DTF.hhp
new file mode 100644
index 00000000..e9b8ad90
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/DTF.hhp
@@ -0,0 +1,49 @@
1[OPTIONS]
2Auto Index=Yes
3Compatibility=1.1 or later
4Compiled file=DTF.chm
5Contents file=DTF.hhc
6Default Window=DTF
7Default topic=Content\about.htm
8Display compile progress=Yes
9Error log file=DTFhelp.log
10Full-text search=Yes
11Index file=DTF.hhk
12Language=0x409 English (United States)
13Title=Deployment Tools Foundation
14
15[WINDOWS]
16DTF="Deployment Tools Foundation","DTF.hhc","DTF.hhk","Content\about.htm","Content\about.htm",,,,,0x22520,,0x384e,[143,72,937,601],,,,,,,0
17
18
19[FILES]
20styles\presentation.css
21Content\about.htm
22Content\whatsnew.htm
23Content\dependencies.htm
24Content\using.htm
25DTF.hhc
26DTF.hhk
27Content\samples.htm
28Content\support.htm
29Content\history.htm
30Content\inventory.htm
31Content\wifile.htm
32Content\cabpack.htm
33Content\powerdiff.htm
34Content\cabs.htm
35Content\databases.htm
36Content\packages.htm
37Content\managedcas.htm
38Content\samplecas.htm
39Content\writingcas.htm
40Content\buildingcas.htm
41Content\debuggingcas.htm
42Content\caproxy.htm
43Content\caconfig.htm
44Content\installutil.htm
45
46[MERGE FILES]
47DTFAPI.chm
48
49[INFOTYPES]
diff --git a/src/samples/Dtf/Documents/Guide/dtfguide.helpproj b/src/samples/Dtf/Documents/Guide/dtfguide.helpproj
new file mode 100644
index 00000000..4df2765d
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/dtfguide.helpproj
@@ -0,0 +1,29 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
6 <PropertyGroup>
7 <ProjectGuid>{3CFD8620-B41C-470C-ABEF-9D38076A2A8D}</ProjectGuid>
8 <TargetName>dtf</TargetName>
9 </PropertyGroup>
10
11 <ItemGroup>
12 <HelpProjectFile Include="dtf.hhp" />
13 <HelpProjectContent Include="DTF.hhc" />
14 <HelpProjectContent Include="DTF.hhk" />
15 <HelpProjectContent Include="Content\*.*" />
16 <HelpProjectContent Include="styles\*.*" />
17 <HelpProjectContent Include="DTFAPI.chm">
18 <SourcePath>$(OutputPath)DTFAPI.chm</SourcePath>
19 </HelpProjectContent>
20 </ItemGroup>
21
22 <ItemGroup>
23 <ProjectReference Include="..\Reference\dtfref.shfbproj">
24 <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
25 </ProjectReference>
26 </ItemGroup>
27
28 <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" />
29</Project>
diff --git a/src/samples/Dtf/Documents/Guide/styles/presentation.css b/src/samples/Dtf/Documents/Guide/styles/presentation.css
new file mode 100644
index 00000000..b71c8582
--- /dev/null
+++ b/src/samples/Dtf/Documents/Guide/styles/presentation.css
@@ -0,0 +1,394 @@
1
2/* page style */
3
4body {
5 margin: 0;
6 background-color: #FFFFFF;
7 padding: 0;
8 font-size: 80%;
9 font-family: verdana, sans-serif;
10 color: #000000;
11}
12
13table {
14 /* this is a trick to force tables to inherit the body font size */
15 font-size: 100%;
16}
17
18/* non-scrolling (control) region style */
19
20div#control {
21 margin: 0;
22 background-color: #D4DFFF;
23 padding: 4px;
24 width: 100%;
25 border-bottom-color: #C8CDDE;
26 border-bottom-style: solid;
27 border-bottom-width: 1;
28}
29
30span.productTitle {
31 font-size: 80%;
32}
33
34span.topicTitle {
35 font-size: 140%;
36 font-weight: bold;
37 color: #003399;
38}
39
40span#chickenFeet {
41 float: left;
42}
43
44span#languageFilter {
45 float: right;
46}
47
48/* scrolling (content) region style */
49
50div#main {
51 margin: 0;
52 padding: 1em;
53 width: 100%;
54 clear: both;
55}
56
57/* sections */
58
59div#header {
60 font-size: 70%;
61 color: #666666;
62 margin-bottom: 0.5em;
63}
64
65div.section {
66 margin-bottom: 1em;
67}
68
69div.sectionTitle {
70 display: inline;
71 font-size: 120%;
72 font-weight: bold;
73 color: #003399;
74}
75
76div.sectionContent {
77 margin-top: 0.2em;
78}
79
80span.subsectionTitle {
81 font-weight: bold;
82}
83
84div#footer {
85 margin-top: 1em;
86 border-top: thin solid #003399;
87 padding-top: 0.5em;
88}
89
90/* authored content (block) */
91
92p {
93 margin-top: 0;
94 margin-bottom: 1em;
95}
96
97dl {
98 margin-top: 0;
99 margin-bottom: 1em;
100}
101
102div.code {
103 clear: both;
104 width: 100%;
105 background: #F7F7FF;
106 padding: 0.4em;
107 font-family: "Andale Mono";
108 /* font-family: "Courier New"; */
109 /* font-family: "This is not a monospace font", monospace; */
110 font-size: inherit;
111 margin-bottom: 1em;
112}
113
114pre {
115 margin: 0;
116 padding: 0;
117}
118
119table.authoredTable {
120 table-layout: fixed;
121 width: 100%;
122 margin-bottom: 1em;
123}
124
125table.authoredTable th {
126 border-bottom-color: #C8CDDE;
127 border-bottom-style: solid;
128 border-bottom-width: 1;
129 background: #EFEFF7;
130 padding: 0.2em;
131 text-align: left;
132 color: #000066;
133 font-weight: bold;
134}
135
136table.authoredTable td {
137 border-bottom-style: solid;
138 border-bottom-color: #C8CDDE;
139 border-bottom-width: 1px;
140 background: #F7F7FF;
141 padding: 0.2em;
142 vertical-align: top;
143}
144
145div.alert {
146 border: 1px solid #C8CDDE;
147 background: #F7F7FF;
148}
149
150div.media {
151 text-align: center;
152 margin-bottom: 1em;
153}
154
155
156/* authored content (inline) */
157
158span.keyword {
159 font-weight: bold;
160}
161
162span.code {
163 font-family: "Andale Mono", "Courier New", Courier, monospace;
164}
165
166/* auto-generated controls */
167
168div.langTabs {
169 width: 100%;
170}
171
172div.langTab {
173 float: left;
174 width: 16%;
175 border-top: 1px solid #C8CDDE;
176 border-left: 1px solid #C8CDDE;
177 border-right: 1px solid #C8CDDE;
178 background: #F7F7FF;
179 padding: 0.2em;
180 text-align: left;
181 color: #000066;
182 font-weight: normal;
183}
184
185div.activeLangTab {
186 float: left;
187 width: 16%;
188 border-top: 1px solid #C8CDDE;
189 border-left: 1px solid #C8CDDE;
190 border-right: 1px solid #C8CDDE;
191 background: #EFEFF7;
192 padding: 0.2em;
193 text-align: left;
194 color: #000066;
195 font-weight: bold;
196}
197
198table.members {
199 table-layout: fixed;
200 width: 100%;
201}
202
203table.members th.iconColumn {
204 width: 60px;
205}
206
207table.members th.nameColumn {
208 width: 33%;
209}
210
211table.members th.descriptionColumn {
212 width: 66%;
213}
214
215table.members th {
216 border-bottom-color: #C8CDDE;
217 border-bottom-style: solid;
218 border-bottom-width: 1;
219 background: #EFEFF7;
220 padding: 0.2em;
221 text-align: left;
222 color: #000066;
223 font-weight: bold;
224}
225
226table.members td {
227 border-bottom-style: solid;
228 border-bottom-color: #C8CDDE;
229 border-bottom-width: 1px;
230 background: #F7F7FF;
231 padding: 0.2em;
232 vertical-align: top;
233 overflow: hidden;
234}
235
236table.exceptions {
237 table-layout: fixed;
238 width: 100%;
239}
240
241
242table.exceptions th.exceptionNameColumn {
243 width: 33%;
244}
245
246table.exceptions th.exceptionConditionColumn {
247 width: 66%;
248}
249
250table.exceptions th {
251 border-bottom-color: #C8CDDE;
252 border-bottom-style: solid;
253 border-bottom-width: 1;
254 background: #EFEFF7;
255 padding: 0.2em;
256 text-align: left;
257 color: #000066;
258 font-weight: bold;
259}
260
261table.exceptions td {
262 border-bottom-style: solid;
263 border-bottom-color: #C8CDDE;
264 border-bottom-width: 1px;
265 background: #F7F7FF;
266 padding: 0.2em;
267 vertical-align: top;
268}
269
270table.permissions {
271 table-layout: fixed;
272 width: 100%;
273}
274
275
276table.permissions th.permissionNameColumn {
277 width: 33%;
278}
279
280table.permissions th.permissionConditionColumn {
281 width: 66%;
282}
283
284table.permissions th {
285 border-bottom-color: #C8CDDE;
286 border-bottom-style: solid;
287 border-bottom-width: 1;
288 background: #EFEFF7;
289 padding: 0.2em;
290 text-align: left;
291 color: #000066;
292 font-weight: bold;
293}
294
295table.permissions td {
296 border-bottom-style: solid;
297 border-bottom-color: #C8CDDE;
298 border-bottom-width: 1px;
299 background: #F7F7FF;
300 padding: 0.2em;
301 vertical-align: top;
302}
303
304span.obsolete {
305 color: red;
306}
307
308span.cs {
309 display: inline;
310}
311
312span.vb {
313 display: none;
314}
315
316span.cpp {
317 display: none;
318}
319/* syntax styling */
320
321div.code span.identifier {
322 font-size: 120%;
323 font-weight: bold;
324}
325
326div.code span.keyword {
327 color: green;
328}
329
330div.code span.parameter {
331 font-style: italic;
332 color: purple;
333}
334
335div.code span.literal {
336 color: purple;
337}
338
339div.code span.comment {
340 color: red;
341}
342
343span.foreignPhrase {
344 font-style: italic;
345}
346
347span.placeholder {
348 font-style: italic;
349}
350
351a {
352 color: blue;
353 font-weight: bold;
354 text-decoration: none;
355}
356
357MSHelp\:link {
358 color: blue;
359 font-weight: bold;
360 hoverColor: #3366ff;
361}
362
363span.nolink {
364 font-weight: bold;
365}
366
367table.filter {
368 table-layout: fixed;
369}
370
371tr.tabs td.tab {
372 width: 10em;
373 background: #F7F7FF;
374 padding: 0.2em;
375 text-align: left;
376 color: #000066;
377 font-weight: normal;
378 overflow: hidden;
379 cursor: pointer;
380}
381
382tr.tabs td.activeTab {
383 width: 10em;
384 background: #EFEFF7;
385 padding: 0.2em;
386 text-align: left;
387 color: #000066;
388 font-weight: bold;
389 overflow: hidden;
390}
391
392td.line {
393 background: #EFEFF7;
394}
diff --git a/src/samples/Dtf/Documents/Reference/Compression.htm b/src/samples/Dtf/Documents/Reference/Compression.htm
new file mode 100644
index 00000000..7782bea1
--- /dev/null
+++ b/src/samples/Dtf/Documents/Reference/Compression.htm
@@ -0,0 +1,13 @@
1<html>
2<head>
3 <title>Class Diagram: WixToolset.Dtf.Compression</title>
4</head>
5<body>
6
7<h3><font face="Verdana">WixToolset.Dtf.Compression Namespace</font></h3>
8
9<img src="Compression1.png" width="870" height="596" border="0" />
10<img src="Compression2.png" width="870" height="596" border="0" />
11
12</body>
13</html>
diff --git a/src/samples/Dtf/Documents/Reference/Compression1.png b/src/samples/Dtf/Documents/Reference/Compression1.png
new file mode 100644
index 00000000..5b2e177f
--- /dev/null
+++ b/src/samples/Dtf/Documents/Reference/Compression1.png
Binary files differ
diff --git a/src/samples/Dtf/Documents/Reference/Compression2.png b/src/samples/Dtf/Documents/Reference/Compression2.png
new file mode 100644
index 00000000..394a5f18
--- /dev/null
+++ b/src/samples/Dtf/Documents/Reference/Compression2.png
Binary files differ
diff --git a/src/samples/Dtf/Documents/Reference/WindowsInstaller.htm b/src/samples/Dtf/Documents/Reference/WindowsInstaller.htm
new file mode 100644
index 00000000..28990ce4
--- /dev/null
+++ b/src/samples/Dtf/Documents/Reference/WindowsInstaller.htm
@@ -0,0 +1,14 @@
1<html>
2<head>
3 <title>Class Diagram: WixToolset.Dtf.WindowsInstaller</title>
4</head>
5<body>
6
7<h3><font face="Verdana">WixToolset.Dtf.WindowsInstaller Namespace</font></h3>
8
9<img src="WindowsInstaller1.png" width="1136" height="1247" border="0" />
10<img src="WindowsInstaller2.png" width="1108" height="1247" border="0" />
11<img src="WindowsInstaller3.png" width="866" height="1247" border="0" />
12
13</body>
14</html>
diff --git a/src/samples/Dtf/Documents/Reference/WindowsInstaller1.png b/src/samples/Dtf/Documents/Reference/WindowsInstaller1.png
new file mode 100644
index 00000000..cc769cc7
--- /dev/null
+++ b/src/samples/Dtf/Documents/Reference/WindowsInstaller1.png
Binary files differ
diff --git a/src/samples/Dtf/Documents/Reference/WindowsInstaller2.png b/src/samples/Dtf/Documents/Reference/WindowsInstaller2.png
new file mode 100644
index 00000000..0c11e501
--- /dev/null
+++ b/src/samples/Dtf/Documents/Reference/WindowsInstaller2.png
Binary files differ
diff --git a/src/samples/Dtf/Documents/Reference/WindowsInstaller3.png b/src/samples/Dtf/Documents/Reference/WindowsInstaller3.png
new file mode 100644
index 00000000..68acd7d8
--- /dev/null
+++ b/src/samples/Dtf/Documents/Reference/WindowsInstaller3.png
Binary files differ
diff --git a/src/samples/Dtf/Documents/Reference/dtfref.shfbproj b/src/samples/Dtf/Documents/Reference/dtfref.shfbproj
new file mode 100644
index 00000000..e45d2a07
--- /dev/null
+++ b/src/samples/Dtf/Documents/Reference/dtfref.shfbproj
@@ -0,0 +1,75 @@
1<?xml version='1.0' encoding='utf-8'?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
6 <PropertyGroup>
7 <ProjectGuid>{27C20359-3910-423D-8058-6403935B98C6}</ProjectGuid>
8
9 <Name>Documentation</Name>
10
11 <!-- SHFB properties -->
12 <SHFBSchemaVersion>1.9.9.0</SHFBSchemaVersion>
13 <HtmlHelpName>DTFAPI</HtmlHelpName>
14 <MissingTags>Namespace, TypeParameter</MissingTags>
15 <VisibleItems>InheritedMembers, InheritedFrameworkMembers, Protected, ProtectedInternalAsProtected, SealedProtected</VisibleItems>
16
17 <RootNamespaceTitle>Deployment Tools Foundation Namespaces</RootNamespaceTitle>
18 <HelpTitle>Deployment Tools Foundation</HelpTitle>
19 <FeedbackEMailAddress>wix-users%40lists.sourceforge.net</FeedbackEMailAddress>
20 <FooterText>&amp;lt%3bscript src=&amp;quot%3bhelplink.js&amp;quot%3b&amp;gt%3b&amp;lt%3b/script&amp;gt%3b</FooterText>
21 <PresentationStyle>Prototype</PresentationStyle>
22 <NamingMethod>MemberName</NamingMethod>
23 <FrameworkVersion>.NET Framework 3.5</FrameworkVersion>
24 </PropertyGroup>
25
26 <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.props" />
27 <PropertyGroup>
28 <NamespaceSummaries>
29 <NamespaceSummaryItem name="(global)" isDocumented="False" xmlns="" />
30 <NamespaceSummaryItem name="WixToolset.Dtf.Compression" isDocumented="True" xmlns="">Framework for archive packing and unpacking.</NamespaceSummaryItem>
31 <NamespaceSummaryItem name="WixToolset.Dtf.Compression.Cab" isDocumented="True" xmlns="">Implements cabinet archive packing and unpacking.</NamespaceSummaryItem>
32 <NamespaceSummaryItem name="WixToolset.Dtf.Compression.Zip" isDocumented="True" xmlns="">Implements zip archive packing and unpacking.</NamespaceSummaryItem>
33 <NamespaceSummaryItem name="WixToolset.Dtf.Resources" isDocumented="True" xmlns="">Classes for reading and writing resource data in executable files.</NamespaceSummaryItem>
34 <NamespaceSummaryItem name="WixToolset.Dtf.WindowsInstaller" isDocumented="True" xmlns="">Complete class library for the Windows Installer APIs.</NamespaceSummaryItem>
35 <NamespaceSummaryItem name="WixToolset.Dtf.WindowsInstaller.Linq" isDocumented="True" xmlns="">LINQ extensions for querying Windows Installer databases (experimental).</NamespaceSummaryItem>
36 <NamespaceSummaryItem name="WixToolset.Dtf.WindowsInstaller.Linq.Entities" isDocumented="False" xmlns="" />
37 <NamespaceSummaryItem name="WixToolset.Dtf.WindowsInstaller.Package" isDocumented="True" xmlns="">Extended classes for working with Windows Installer installation and patch packages.</NamespaceSummaryItem>
38 </NamespaceSummaries>
39
40 <DocumentationSources>
41 <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.Compression.dll" xmlns="" />
42 <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.Compression.xml" xmlns="" />
43 <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.Compression.Cab.dll" xmlns="" />
44 <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.Compression.Cab.xml" xmlns="" />
45 <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.Compression.Zip.dll" xmlns="" />
46 <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.Compression.Zip.xml" xmlns="" />
47 <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.Resources.dll" xmlns="" />
48 <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.Resources.xml" xmlns="" />
49 <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.WindowsInstaller.dll" xmlns="" />
50 <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.WindowsInstaller.xml" xmlns="" />
51 <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.WindowsInstaller.Package.dll" xmlns="" />
52 <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.WindowsInstaller.Package.xml" xmlns="" />
53 <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.WindowsInstaller.Linq.dll" xmlns="" />
54 <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.WindowsInstaller.Linq.xml" xmlns="" />
55 </DocumentationSources>
56 </PropertyGroup>
57
58 <ItemGroup>
59 <Content Include="helplink.js" />
60 <Content Include="Compression2.png" />
61 <Content Include="Compression1.png" />
62 <Content Include="Compression.htm" />
63 <Content Include="WindowsInstaller.htm" />
64 <Content Include="WindowsInstaller3.png" />
65 <Content Include="WindowsInstaller2.png" />
66 <Content Include="WindowsInstaller1.png" />
67 </ItemGroup>
68
69 <ItemGroup>
70 <Reference Include="System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
71 <Reference Include="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
72 </ItemGroup>
73
74 <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" />
75</Project>
diff --git a/src/samples/Dtf/Documents/Reference/helplink.js b/src/samples/Dtf/Documents/Reference/helplink.js
new file mode 100644
index 00000000..a4989824
--- /dev/null
+++ b/src/samples/Dtf/Documents/Reference/helplink.js
@@ -0,0 +1,184 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3FixHelpLinks();
4
5function GetHelpCode(apiName)
6{
7 switch (apiName.toLowerCase())
8 {
9 case "msiadvertiseproduct": return 370056;
10 case "msiadvertiseproductex": return 370057;
11 case "msiapplymultiplepatches": return 370059;
12 case "msiapplypatch": return 370060;
13 case "msibegintransaction": return 736312;
14 case "msiclosehandle": return 370067;
15 case "msicollectuserinfo": return 370068;
16 case "msiconfigurefeature": return 370069;
17 case "msiconfigureproduct": return 370070;
18 case "msiconfigureproductex": return 370071;
19 case "msicreaterecord": return 370072;
20 case "msicreatetransformsummaryinfo": return 370073;
21 case "msidatabaseapplytransform": return 370074;
22 case "msidatabasecommit": return 370075;
23 case "msidatabaseexport": return 370076;
24 case "msidatabasegeneratetransform": return 370077;
25 case "msidatabasegetprimarykeys": return 370078;
26 case "msidatabaseimport": return 370079;
27 case "msidatabaseistablepersistent": return 370080;
28 case "msidatabasemerge": return 370081;
29 case "msidatabaseopenview": return 370082;
30 case "msidetermineapplicablepatches": return 370084;
31 case "msideterminepatchsequence": return 370085;
32 case "msidoaction": return 370090;
33 case "msienablelog": return 370091;
34 case "msiendtransaction": return 736318;
35 case "msienumclients": return 370094;
36 case "msienumcomponentcosts": return 370095;
37 case "msienumcomponentqualifiers": return 370096;
38 case "msienumcomponents": return 370097;
39 case "msienumfeatures": return 370098;
40 case "msienumpatches": return 370099;
41 case "msienumpatchesex": return 370100;
42 case "msienumproducts": return 370101;
43 case "msienumproductsex": return 370102;
44 case "msienumrelatedproducts": return 370103;
45 case "msievaluatecondition": return 370104;
46 case "msiextractpatchxmldata": return 370105;
47 case "msiformatrecord": return 370109;
48 case "msigetactivedatabase": return 370110;
49 case "msigetcomponentpath": return 370112;
50 case "msigetcomponentstate": return 370113;
51 case "msigetdatabasestate": return 370114;
52 case "msigetfeaturecost": return 370115;
53 case "msigetfeatureinfo": return 370116;
54 case "msigetfeaturestate": return 370117;
55 case "msigetfeatureusage": return 370118;
56 case "msigetfeaturevalidstates": return 370119;
57 case "msigetfilehash": return 370120;
58 case "msigetfileversion": return 370122;
59 case "msigetlanguage": return 370123;
60 case "msigetlasterrorrecord": return 370124;
61 case "msigetmode": return 370125;
62 case "msigetpatchfilelist": return 370126;
63 case "msigetpatchinfo": return 370127;
64 case "msigetpatchinfoex": return 370128;
65 case "msigetproductcode": return 370129;
66 case "msigetproductinfo": return 370130;
67 case "msigetproductinfoex": return 370131;
68 case "msigetproductinfofromscript": return 370132;
69 case "msigetproductproperty": return 370133;
70 case "msigetproperty": return 370134;
71 case "msigetshortcuttarget": return 370299;
72 case "msigetsourcepath": return 370300;
73 case "msigetsummaryinformation": return 370301;
74 case "msigettargetpath": return 370303;
75 case "msiinstallmissingcomponent": return 370311;
76 case "msiinstallmissingfile": return 370313;
77 case "msiinstallproduct": return 370315;
78 case "msijointransaction": return 736319;
79 case "msilocatecomponent": return 370320;
80 case "msinotifysidchange": return 370328;
81 case "msiopendatabase": return 370338;
82 case "msiopenpackage": return 370339;
83 case "msiopenpackageex": return 370340;
84 case "msiopenproduct": return 370341;
85 case "msiprocessadvertisescript": return 370353;
86 case "msiprocessmessage": return 370354;
87 case "msiprovideassembly": return 370355;
88 case "msiprovidecomponent": return 370356;
89 case "msiprovidequalifiedcomponent": return 370357;
90 case "msiprovidequalifiedcomponentex":return 370358;
91 case "msiquerycomponnetstate": return 370360;
92 case "msiqueryfeaturestate": return 370361;
93 case "msiqueryfeaturestateex": return 370362;
94 case "msiqueryproductstate": return 370363;
95 case "msirecordcleardata": return 370364;
96 case "msirecorddatasize": return 370365;
97 case "msirecordgetfieldcount": return 370366;
98 case "msirecordgetinteger": return 370367;
99 case "msirecordgetstring": return 370368;
100 case "msirecordisnull": return 370369;
101 case "msirecordreadstream": return 370370;
102 case "msirecordsetinteger": return 370371;
103 case "msirecordsetstream": return 370372;
104 case "msirecordsetstring": return 370373;
105 case "msireinstallfeature": return 370374;
106 case "msireinstallproduct": return 370375;
107 case "msiremovepatches": return 370376;
108 case "msisequence": return 370382;
109 case "msisetcomponentstate": return 370383;
110 case "msisetexternalui": return 370384;
111 case "msisetexternaluirecord": return 370385;
112 case "msisetfeatureattributes": return 370386;
113 case "msisetfeaturestate": return 370387;
114 case "msisetinstalllevel": return 370388;
115 case "msisetinternalui": return 370389;
116 case "msisetmode": return 370390;
117 case "msisetproperty": return 370391;
118 case "msisettargetpath": return 370392;
119 case "msisourcelistaddmediadisk": return 370394;
120 case "msisourcelistaddsource": return 370395;
121 case "msisourcelistaddsourceex": return 370396;
122 case "msisourcelistclearall": return 370397;
123 case "msisourcelistclearallex": return 370398;
124 case "msisourcelistclearmediadisk": return 370399;
125 case "msisourcelistclearsource": return 370401;
126 case "msisourcelistenummediadisks": return 370402;
127 case "msisourcelistenumsources": return 370403;
128 case "msisourcelistforceresolution": return 370404;
129 case "msisourcelistforceresolutionex":return 370405;
130 case "msisourcelistgetinfo": return 370406;
131 case "msisourcelistsetinfo": return 370407;
132 case "msisummaryinfogetproperty": return 370409;
133 case "msisummaryinfopersist": return 370490;
134 case "msisummaryinfosetproperty": return 370491;
135 case "msiusefeature": return 370502;
136 case "msiusefeatureex": return 370503;
137 case "msiverifydiskspace": return 370506;
138 case "msiverifypackage": return 370508;
139 case "msiviewexecute": return 370513;
140 case "msiviewfetch": return 370514;
141 case "msiviewgetcolumninfo": return 370516;
142 case "msiviewgeterror": return 370518;
143 case "msiviewmodify": return 370519;
144 case "productid": return 370855;
145 default:
146 return 0;
147 }
148}
149
150function GetHelpLink(apiName)
151{
152 var helpCode = GetHelpCode(apiName);
153 if (helpCode != 0)
154 {
155 // Found a direct link!
156 var prefix = (helpCode < 500000 ? "aa" : "bb");
157 return "http://msdn2.microsoft.com/en-us/library/" + prefix + helpCode + ".aspx";
158 }
159 else
160 {
161 // This link works, but goes through an annoying 5-sec redirect page.
162 return "http://msdn.microsoft.com/library/en-us/msi/setup/" + apiName.toLowerCase() + ".asp";
163 }
164}
165
166// Change any MSI API help links from indirect MSDN references to direct references.
167function FixHelpLinks()
168{
169 var msiLinkRegex = /msdn\.microsoft\.com\/library\/en-us\/msi\/setup\/([a-z]+)\.asp/i;
170 var links = document.body.all.tags("a");
171 var i;
172 for (i = 0; i < links.length; i++)
173 {
174 var linkElem = links(i);
175 var match = msiLinkRegex.exec(linkElem.href);
176 if (match)
177 {
178 var apiName = match[1];
179 linkElem.href = GetHelpLink(apiName);
180 linkElem.target = "_blank";
181 linkElem.title = "MSDN Library";
182 }
183 }
184}
diff --git a/src/samples/Dtf/EmbeddedUI/AssemblyInfo.cs b/src/samples/Dtf/EmbeddedUI/AssemblyInfo.cs
new file mode 100644
index 00000000..7a2fa039
--- /dev/null
+++ b/src/samples/Dtf/EmbeddedUI/AssemblyInfo.cs
@@ -0,0 +1,5 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System.Reflection;
4
5[assembly: AssemblyDescription("Sample managed embedded external UI")]
diff --git a/src/samples/Dtf/EmbeddedUI/EmbeddedUI.csproj b/src/samples/Dtf/EmbeddedUI/EmbeddedUI.csproj
new file mode 100644
index 00000000..e4c52a26
--- /dev/null
+++ b/src/samples/Dtf/EmbeddedUI/EmbeddedUI.csproj
@@ -0,0 +1,56 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
4 <PropertyGroup>
5 <ProjectGuid>{864B8C50-7895-4485-AC89-900D86FD8C0D}</ProjectGuid>
6 <OutputType>Library</OutputType>
7 <RootNamespace>WixToolset.Dtf.Samples.EmbeddedUI</RootNamespace>
8 <AssemblyName>WixToolset.Dtf.Samples.EmbeddedUI</AssemblyName>
9 <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
10 <FileAlignment>512</FileAlignment>
11 </PropertyGroup>
12 <ItemGroup>
13 <Compile Include="AssemblyInfo.cs" />
14 <Compile Include="InstallProgressCounter.cs" />
15 <Compile Include="SampleEmbeddedUI.cs" />
16 <Compile Include="SetupWizard.xaml.cs">
17 <DependentUpon>SetupWizard.xaml</DependentUpon>
18 </Compile>
19 </ItemGroup>
20 <ItemGroup>
21 <Page Include="SetupWizard.xaml">
22 <Generator>MSBuild:Compile</Generator>
23 <SubType>Designer</SubType>
24 </Page>
25 </ItemGroup>
26 <ItemGroup>
27 <Reference Include="PresentationCore">
28 <RequiredTargetFramework>3.0</RequiredTargetFramework>
29 </Reference>
30 <Reference Include="PresentationFramework">
31 <RequiredTargetFramework>3.0</RequiredTargetFramework>
32 </Reference>
33 <Reference Include="System" />
34 <Reference Include="System.Core">
35 <RequiredTargetFramework>3.5</RequiredTargetFramework>
36 </Reference>
37 <Reference Include="System.Xml" />
38 <Reference Include="WindowsBase">
39 <RequiredTargetFramework>3.0</RequiredTargetFramework>
40 </Reference>
41 </ItemGroup>
42 <ItemGroup>
43 <ProjectReference Include="..\..\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj">
44 <Project>{24121677-0ed0-41b5-833f-1b9a18e87bf4}</Project>
45 <Name>WixToolset.Dtf.WindowsInstaller</Name>
46 </ProjectReference>
47 </ItemGroup>
48
49 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
50<!--
51 <PropertyGroup>
52 <PostBuildEvent>"$(TargetDir)..\x86\MakeSfxCA.exe" "$(TargetPath)" "$(TargetDir)SfxCA.dll" "$(IntermediateOutputPath)$(TargetFileName)" "$(TargetDir)WixToolset.Dtf.WindowsInstaller.dll"</PostBuildEvent>
53 </PropertyGroup>
54-->
55
56</Project>
diff --git a/src/samples/Dtf/EmbeddedUI/InstallProgressCounter.cs b/src/samples/Dtf/EmbeddedUI/InstallProgressCounter.cs
new file mode 100644
index 00000000..df77e106
--- /dev/null
+++ b/src/samples/Dtf/EmbeddedUI/InstallProgressCounter.cs
@@ -0,0 +1,176 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Samples.EmbeddedUI
4{
5 using System;
6 using WixToolset.Dtf.WindowsInstaller;
7
8 /// <summary>
9 /// Tracks MSI progress messages and converts them to usable progress.
10 /// </summary>
11 public class InstallProgressCounter
12 {
13 private int total;
14 private int completed;
15 private int step;
16 private bool moveForward;
17 private bool enableActionData;
18 private int progressPhase;
19 private double scriptPhaseWeight;
20
21 public InstallProgressCounter() : this(0.3)
22 {
23 }
24
25 public InstallProgressCounter(double scriptPhaseWeight)
26 {
27 if (!(0 <= scriptPhaseWeight && scriptPhaseWeight <= 1))
28 {
29 throw new ArgumentOutOfRangeException("scriptPhaseWeight");
30 }
31
32 this.scriptPhaseWeight = scriptPhaseWeight;
33 }
34
35 /// <summary>
36 /// Gets a number between 0 and 1 that indicates the overall installation progress.
37 /// </summary>
38 public double Progress { get; private set; }
39
40 public void ProcessMessage(InstallMessage messageType, Record messageRecord)
41 {
42 // This MSI progress-handling code was mostly borrowed from burn and translated from C++ to C#.
43
44 switch (messageType)
45 {
46 case InstallMessage.ActionStart:
47 if (this.enableActionData)
48 {
49 this.enableActionData = false;
50 }
51 break;
52
53 case InstallMessage.ActionData:
54 if (this.enableActionData)
55 {
56 if (this.moveForward)
57 {
58 this.completed += this.step;
59 }
60 else
61 {
62 this.completed -= this.step;
63 }
64
65 this.UpdateProgress();
66 }
67 break;
68
69 case InstallMessage.Progress:
70 this.ProcessProgressMessage(messageRecord);
71 break;
72 }
73 }
74
75 private void ProcessProgressMessage(Record progressRecord)
76 {
77 // This MSI progress-handling code was mostly borrowed from burn and translated from C++ to C#.
78
79 if (progressRecord == null || progressRecord.FieldCount == 0)
80 {
81 return;
82 }
83
84 int fieldCount = progressRecord.FieldCount;
85 int progressType = progressRecord.GetInteger(1);
86 string progressTypeString = String.Empty;
87 switch (progressType)
88 {
89 case 0: // Master progress reset
90 if (fieldCount < 4)
91 {
92 return;
93 }
94
95 this.progressPhase++;
96
97 this.total = progressRecord.GetInteger(2);
98 if (this.progressPhase == 1)
99 {
100 // HACK!!! this is a hack courtesy of the Windows Installer team. It seems the script planning phase
101 // is always off by "about 50". So we'll toss an extra 50 ticks on so that the standard progress
102 // doesn't go over 100%. If there are any custom actions, they may blow the total so we'll call this
103 // "close" and deal with the rest.
104 this.total += 50;
105 }
106
107 this.moveForward = (progressRecord.GetInteger(3) == 0);
108 this.completed = (this.moveForward ? 0 : this.total); // if forward start at 0, if backwards start at max
109 this.enableActionData = false;
110
111 this.UpdateProgress();
112 break;
113
114 case 1: // Action info
115 if (fieldCount < 3)
116 {
117 return;
118 }
119
120 if (progressRecord.GetInteger(3) == 0)
121 {
122 this.enableActionData = false;
123 }
124 else
125 {
126 this.enableActionData = true;
127 this.step = progressRecord.GetInteger(2);
128 }
129 break;
130
131 case 2: // Progress report
132 if (fieldCount < 2 || this.total == 0 || this.progressPhase == 0)
133 {
134 return;
135 }
136
137 if (this.moveForward)
138 {
139 this.completed += progressRecord.GetInteger(2);
140 }
141 else
142 {
143 this.completed -= progressRecord.GetInteger(2);
144 }
145
146 this.UpdateProgress();
147 break;
148
149 case 3: // Progress total addition
150 this.total += progressRecord.GetInteger(2);
151 break;
152 }
153 }
154
155 private void UpdateProgress()
156 {
157 if (this.progressPhase < 1 || this.total == 0)
158 {
159 this.Progress = 0;
160 }
161 else if (this.progressPhase == 1)
162 {
163 this.Progress = this.scriptPhaseWeight * Math.Min(this.completed, this.total) / this.total;
164 }
165 else if (this.progressPhase == 2)
166 {
167 this.Progress = this.scriptPhaseWeight +
168 (1 - this.scriptPhaseWeight) * Math.Min(this.completed, this.total) / this.total;
169 }
170 else
171 {
172 this.Progress = 1;
173 }
174 }
175 }
176}
diff --git a/src/samples/Dtf/EmbeddedUI/SampleEmbeddedUI.cs b/src/samples/Dtf/EmbeddedUI/SampleEmbeddedUI.cs
new file mode 100644
index 00000000..9b26bef5
--- /dev/null
+++ b/src/samples/Dtf/EmbeddedUI/SampleEmbeddedUI.cs
@@ -0,0 +1,132 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Samples.EmbeddedUI
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Configuration;
8 using System.Threading;
9 using System.Windows;
10 using System.Windows.Threading;
11 using WixToolset.Dtf.WindowsInstaller;
12 using Application = System.Windows.Application;
13
14 public class SampleEmbeddedUI : IEmbeddedUI
15 {
16 private Thread appThread;
17 private Application app;
18 private SetupWizard setupWizard;
19 private ManualResetEvent installStartEvent;
20 private ManualResetEvent installExitEvent;
21
22 /// <summary>
23 /// Initializes the embedded UI.
24 /// </summary>
25 /// <param name="session">Handle to the installer which can be used to get and set properties.
26 /// The handle is only valid for the duration of this method call.</param>
27 /// <param name="resourcePath">Path to the directory that contains all the files from the MsiEmbeddedUI table.</param>
28 /// <param name="internalUILevel">On entry, contains the current UI level for the installation. After this
29 /// method returns, the installer resets the UI level to the returned value of this parameter.</param>
30 /// <returns>True if the embedded UI was successfully initialized; false if the installation
31 /// should continue without the embedded UI.</returns>
32 /// <exception cref="InstallCanceledException">The installation was canceled by the user.</exception>
33 /// <exception cref="InstallerException">The embedded UI failed to initialize and
34 /// causes the installation to fail.</exception>
35 public bool Initialize(Session session, string resourcePath, ref InstallUIOptions internalUILevel)
36 {
37 if (session != null)
38 {
39 if ((internalUILevel & InstallUIOptions.Full) != InstallUIOptions.Full)
40 {
41 // Don't show custom UI when the UI level is set to basic.
42 return false;
43
44 // An embedded UI could display an alternate dialog sequence for reduced or
45 // basic modes, but it's not implemented here. We'll just fall back to the
46 // built-in MSI basic UI.
47 }
48
49 if (String.Equals(session["REMOVE"], "All", StringComparison.OrdinalIgnoreCase))
50 {
51 // Don't show custom UI when uninstalling.
52 return false;
53
54 // An embedded UI could display an uninstall wizard, it's just not imlemented here.
55 }
56 }
57
58 // Start the setup wizard on a separate thread.
59 this.installStartEvent = new ManualResetEvent(false);
60 this.installExitEvent = new ManualResetEvent(false);
61 this.appThread = new Thread(this.Run);
62 this.appThread.SetApartmentState(ApartmentState.STA);
63 this.appThread.Start();
64
65 // Wait for the setup wizard to either kickoff the install or prematurely exit.
66 int waitResult = WaitHandle.WaitAny(new WaitHandle[] { this.installStartEvent, this.installExitEvent });
67 if (waitResult == 1)
68 {
69 // The setup wizard set the exit event instead of the start event. Cancel the installation.
70 throw new InstallCanceledException();
71 }
72 else
73 {
74 // Start the installation with a silenced internal UI.
75 // This "embedded external UI" will handle message types except for source resolution.
76 internalUILevel = InstallUIOptions.NoChange | InstallUIOptions.SourceResolutionOnly;
77 return true;
78 }
79 }
80
81 /// <summary>
82 /// Processes information and progress messages sent to the user interface.
83 /// </summary>
84 /// <param name="messageType">Message type.</param>
85 /// <param name="messageRecord">Record that contains message data.</param>
86 /// <param name="buttons">Message box buttons.</param>
87 /// <param name="icon">Message box icon.</param>
88 /// <param name="defaultButton">Message box default button.</param>
89 /// <returns>Result of processing the message.</returns>
90 public MessageResult ProcessMessage(InstallMessage messageType, Record messageRecord,
91 MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton)
92 {
93 // Synchronously send the message to the setup wizard window on its thread.
94 object result = this.setupWizard.Dispatcher.Invoke(DispatcherPriority.Send,
95 new Func<MessageResult>(delegate()
96 {
97 return this.setupWizard.ProcessMessage(messageType, messageRecord, buttons, icon, defaultButton);
98 }));
99 return (MessageResult) result;
100 }
101
102 /// <summary>
103 /// Shuts down the embedded UI at the end of the installation.
104 /// </summary>
105 /// <remarks>
106 /// If the installation was canceled during initialization, this method will not be called.
107 /// If the installation was canceled or failed at any later point, this method will be called at the end.
108 /// </remarks>
109 public void Shutdown()
110 {
111 // Wait for the user to exit the setup wizard.
112 this.setupWizard.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
113 new Action(delegate()
114 {
115 this.setupWizard.EnableExit();
116 }));
117 this.appThread.Join();
118 }
119
120 /// <summary>
121 /// Creates the setup wizard and runs the application thread.
122 /// </summary>
123 private void Run()
124 {
125 this.app = new Application();
126 this.setupWizard = new SetupWizard(this.installStartEvent);
127 this.setupWizard.InitializeComponent();
128 this.app.Run(this.setupWizard);
129 this.installExitEvent.Set();
130 }
131 }
132}
diff --git a/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml b/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml
new file mode 100644
index 00000000..a43059e8
--- /dev/null
+++ b/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml
@@ -0,0 +1,17 @@
1
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<Window x:Class="WixToolset.Dtf.Samples.EmbeddedUI.SetupWizard"
6 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
7 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
8 Title="Sample Embedded UI" Height="400" Width="540" Visibility="Visible">
9 <Grid>
10 <TextBox Margin="8,8,8,63" Name="messagesTextBox" IsReadOnly="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Auto" FontFamily="Lucida Console" FontSize="10" />
11 <Button Height="23" HorizontalAlignment="Right" Name="installButton" VerticalAlignment="Bottom" Width="75" Click="installButton_Click" Margin="0,0,91,8">Install</Button>
12 <Button Height="23" HorizontalAlignment="Right" Name="exitButton" VerticalAlignment="Bottom" Width="75" Visibility="Hidden" Click="exitButton_Click" Margin="0,0,8,8">Exit</Button>
13 <Button Height="23" Margin="0,0,8,8" Name="cancelButton" VerticalAlignment="Bottom" Width="75" HorizontalAlignment="Right" Click="cancelButton_Click">Cancel</Button>
14 <ProgressBar Height="16" Margin="8,0,8,39" Name="progressBar" VerticalAlignment="Bottom" Visibility="Hidden" IsIndeterminate="False" />
15 <Label Height="28" HorizontalAlignment="Left" Margin="8,0,0,4.48" Name="progressLabel" VerticalAlignment="Bottom" Width="120" Visibility="Hidden">0%</Label>
16 </Grid>
17</Window>
diff --git a/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml.cs b/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml.cs
new file mode 100644
index 00000000..b25b8a9e
--- /dev/null
+++ b/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml.cs
@@ -0,0 +1,111 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Samples.EmbeddedUI
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using System.Text;
9 using System.Threading;
10 using System.Windows;
11 using System.Windows.Controls;
12 using System.Windows.Data;
13 using System.Windows.Documents;
14 using System.Windows.Input;
15 using System.Windows.Media;
16 using System.Windows.Media.Imaging;
17 using System.Windows.Navigation;
18 using System.Windows.Shapes;
19 using WixToolset.Dtf.WindowsInstaller;
20
21 /// <summary>
22 /// Interaction logic for SetupWizard.xaml
23 /// </summary>
24 public partial class SetupWizard : Window
25 {
26 private ManualResetEvent installStartEvent;
27 private InstallProgressCounter progressCounter;
28 private bool canceled;
29
30 public SetupWizard(ManualResetEvent installStartEvent)
31 {
32 this.installStartEvent = installStartEvent;
33 this.progressCounter = new InstallProgressCounter(0.5);
34 }
35
36 public MessageResult ProcessMessage(InstallMessage messageType, Record messageRecord,
37 MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton)
38 {
39 try
40 {
41 this.progressCounter.ProcessMessage(messageType, messageRecord);
42 this.progressBar.Value = this.progressBar.Minimum +
43 this.progressCounter.Progress * (this.progressBar.Maximum - this.progressBar.Minimum);
44 this.progressLabel.Content = "" + (int) Math.Round(100 * this.progressCounter.Progress) + "%";
45
46 switch (messageType)
47 {
48 case InstallMessage.Error:
49 case InstallMessage.Warning:
50 case InstallMessage.Info:
51 string message = String.Format("{0}: {1}", messageType, messageRecord);
52 this.LogMessage(message);
53 break;
54 }
55
56 if (this.canceled)
57 {
58 this.canceled = false;
59 return MessageResult.Cancel;
60 }
61 }
62 catch (Exception ex)
63 {
64 this.LogMessage(ex.ToString());
65 this.LogMessage(ex.StackTrace);
66 }
67
68 return MessageResult.OK;
69 }
70
71 private void LogMessage(string message)
72 {
73 this.messagesTextBox.Text += Environment.NewLine + message;
74 this.messagesTextBox.ScrollToEnd();
75 }
76
77 internal void EnableExit()
78 {
79 this.progressBar.Visibility = Visibility.Hidden;
80 this.progressLabel.Visibility = Visibility.Hidden;
81 this.cancelButton.Visibility = Visibility.Hidden;
82 this.exitButton.Visibility = Visibility.Visible;
83 }
84
85 private void installButton_Click(object sender, RoutedEventArgs e)
86 {
87 this.installButton.Visibility = Visibility.Hidden;
88 this.progressBar.Visibility = Visibility.Visible;
89 this.progressLabel.Visibility = Visibility.Visible;
90 this.installStartEvent.Set();
91 }
92
93 private void exitButton_Click(object sender, RoutedEventArgs e)
94 {
95 this.Close();
96 }
97
98 private void cancelButton_Click(object sender, RoutedEventArgs e)
99 {
100 if (this.installButton.Visibility == Visibility.Visible)
101 {
102 this.Close();
103 }
104 else
105 {
106 this.canceled = true;
107 this.cancelButton.IsEnabled = false;
108 }
109 }
110 }
111}
diff --git a/src/samples/Dtf/Inventory/Columns.resx b/src/samples/Dtf/Inventory/Columns.resx
new file mode 100644
index 00000000..cfeb11e3
--- /dev/null
+++ b/src/samples/Dtf/Inventory/Columns.resx
@@ -0,0 +1,252 @@
1<?xml version="1.0" encoding="utf-8"?>
2<root>
3 <!--
4 Microsoft ResX Schema
5
6 Version 2.0
7
8 The primary goals of this format is to allow a simple XML format
9 that is mostly human readable. The generation and parsing of the
10 various data types are done through the TypeConverter classes
11 associated with the data types.
12
13 Example:
14
15 ... ado.net/XML headers & schema ...
16 <resheader name="resmimetype">text/microsoft-resx</resheader>
17 <resheader name="version">2.0</resheader>
18 <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
19 <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
20 <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
21 <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
22 <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
23 <value>[base64 mime encoded serialized .NET Framework object]</value>
24 </data>
25 <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
26 <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
27 <comment>This is a comment</comment>
28 </data>
29
30 There are any number of "resheader" rows that contain simple
31 name/value pairs.
32
33 Each data row contains a name, and value. The row also contains a
34 type or mimetype. Type corresponds to a .NET class that support
35 text/value conversion through the TypeConverter architecture.
36 Classes that don't support this are serialized and stored with the
37 mimetype set.
38
39 The mimetype is used for serialized objects, and tells the
40 ResXResourceReader how to depersist the object. This is currently not
41 extensible. For a given mimetype the value must be set accordingly:
42
43 Note - application/x-microsoft.net.object.binary.base64 is the format
44 that the ResXResourceWriter will generate, however the reader can
45 read any of the formats listed below.
46
47 mimetype: application/x-microsoft.net.object.binary.base64
48 value : The object must be serialized with
49 : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
50 : and then encoded with base64 encoding.
51
52 mimetype: application/x-microsoft.net.object.soap.base64
53 value : The object must be serialized with
54 : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
55 : and then encoded with base64 encoding.
56
57 mimetype: application/x-microsoft.net.object.bytearray.base64
58 value : The object must be serialized into a byte array
59 : using a System.ComponentModel.TypeConverter
60 : and then encoded with base64 encoding.
61 -->
62 <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
63 <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
64 <xsd:element name="root" msdata:IsDataSet="true">
65 <xsd:complexType>
66 <xsd:choice maxOccurs="unbounded">
67 <xsd:element name="metadata">
68 <xsd:complexType>
69 <xsd:sequence>
70 <xsd:element name="value" type="xsd:string" minOccurs="0" />
71 </xsd:sequence>
72 <xsd:attribute name="name" use="required" type="xsd:string" />
73 <xsd:attribute name="type" type="xsd:string" />
74 <xsd:attribute name="mimetype" type="xsd:string" />
75 <xsd:attribute ref="xml:space" />
76 </xsd:complexType>
77 </xsd:element>
78 <xsd:element name="assembly">
79 <xsd:complexType>
80 <xsd:attribute name="alias" type="xsd:string" />
81 <xsd:attribute name="name" type="xsd:string" />
82 </xsd:complexType>
83 </xsd:element>
84 <xsd:element name="data">
85 <xsd:complexType>
86 <xsd:sequence>
87 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
88 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
89 </xsd:sequence>
90 <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
91 <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
92 <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
93 <xsd:attribute ref="xml:space" />
94 </xsd:complexType>
95 </xsd:element>
96 <xsd:element name="resheader">
97 <xsd:complexType>
98 <xsd:sequence>
99 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
100 </xsd:sequence>
101 <xsd:attribute name="name" type="xsd:string" use="required" />
102 </xsd:complexType>
103 </xsd:element>
104 </xsd:choice>
105 </xsd:complexType>
106 </xsd:element>
107 </xsd:schema>
108 <resheader name="resmimetype">
109 <value>text/microsoft-resx</value>
110 </resheader>
111 <resheader name="version">
112 <value>2.0</value>
113 </resheader>
114 <resheader name="reader">
115 <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
116 </resheader>
117 <resheader name="writer">
118 <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119 </resheader>
120 <data name="ProductsProductName" xml:space="preserve">
121 <value>Product Name,250</value>
122 </data>
123 <data name="ProductsProductCode" xml:space="preserve">
124 <value>Product Code,250</value>
125 </data>
126 <data name="ProductPropertiesProperty" xml:space="preserve">
127 <value>Property,100</value>
128 </data>
129 <data name="ProductPropertiesValue" xml:space="preserve">
130 <value>Value,300</value>
131 </data>
132 <data name="ProductFeaturesFeatureTitle" xml:space="preserve">
133 <value>Feature Title,230</value>
134 </data>
135 <data name="ProductFeaturesFeatureName" xml:space="preserve">
136 <value>Feature,200</value>
137 </data>
138 <data name="ProductFeaturesInstallState" xml:space="preserve">
139 <value>Install State,70</value>
140 </data>
141 <data name="ProductFeatureComponentsComponentName" xml:space="preserve">
142 <value>Component,250</value>
143 </data>
144 <data name="ProductFeatureComponentsComponentID" xml:space="preserve">
145 <value>Component ID,250</value>
146 </data>
147 <data name="ProductComponentsComponentName" xml:space="preserve">
148 <value>Component,180</value>
149 </data>
150 <data name="ProductComponentsComponentID" xml:space="preserve">
151 <value>Component ID,250</value>
152 </data>
153 <data name="ProductComponentsInstallState" xml:space="preserve">
154 <value>Install State,70</value>
155 </data>
156 <data name="ComponentProductsProductName" xml:space="preserve">
157 <value>Product Name,250</value>
158 </data>
159 <data name="ComponentProductsProductCode" xml:space="preserve">
160 <value>Product Code,250</value>
161 </data>
162 <data name="ComponentProductsComponentPath" xml:space="preserve">
163 <value>Component Path,300</value>
164 </data>
165 <data name="ProductComponentItemsIsKey" xml:space="preserve">
166 <value>Key,35</value>
167 </data>
168 <data name="ProductComponentItemsKey" xml:space="preserve">
169 <value>Name,250</value>
170 </data>
171 <data name="ProductComponentItemsPath" xml:space="preserve">
172 <value>Install Path,350</value>
173 </data>
174 <data name="ProductComponentItemsExists" xml:space="preserve">
175 <value>Exists,40</value>
176 </data>
177 <data name="ProductComponentItemsDbVersion" xml:space="preserve">
178 <value>Version in Database,100</value>
179 </data>
180 <data name="ProductComponentItemsInstalledVersion" xml:space="preserve">
181 <value>Version Installed,100</value>
182 </data>
183 <data name="ProductComponentItemsInstalledMatch" xml:space="preserve">
184 <value>Match,40</value>
185 </data>
186 <data name="ProductFilesIsKey" xml:space="preserve">
187 <value>Key,35</value>
188 </data>
189 <data name="ProductFilesKey" xml:space="preserve">
190 <value>Name,250</value>
191 </data>
192 <data name="ProductFilesPath" xml:space="preserve">
193 <value>Install Path,350</value>
194 </data>
195 <data name="ProductFilesExists" xml:space="preserve">
196 <value>Exists,40</value>
197 </data>
198 <data name="ProductFilesDbVersion" xml:space="preserve">
199 <value>Version in Database,120</value>
200 </data>
201 <data name="ProductFilesInstalledVersion" xml:space="preserve">
202 <value>Version Installed,120</value>
203 </data>
204 <data name="ProductFilesInstalledMatch" xml:space="preserve">
205 <value>Match,40</value>
206 </data>
207 <data name="ProductFilesComponentID" xml:space="preserve">
208 <value>Component ID,250</value>
209 </data>
210 <data name="ProductRegistryIsKey" xml:space="preserve">
211 <value>Key,35</value>
212 </data>
213 <data name="ProductRegistryKey" xml:space="preserve">
214 <value>Name,250</value>
215 </data>
216 <data name="ProductRegistryPath" xml:space="preserve">
217 <value>Install Path,350</value>
218 </data>
219 <data name="ProductRegistryExists" xml:space="preserve">
220 <value>Exists,40</value>
221 </data>
222 <data name="ProductRegistryDbVersion" xml:space="preserve">
223 <value>Value in Database,120</value>
224 </data>
225 <data name="ProductRegistryInstalledVersion" xml:space="preserve">
226 <value>Value Installed,120</value>
227 </data>
228 <data name="ProductRegistryInstalledMatch" xml:space="preserve">
229 <value>Match,40</value>
230 </data>
231 <data name="ProductRegistryComponentID" xml:space="preserve">
232 <value>Component ID,250</value>
233 </data>
234 <data name="PatchesPatchCode" xml:space="preserve">
235 <value>Patch Code,250</value>
236 </data>
237 <data name="ProductPatchesPatchCode" xml:space="preserve">
238 <value>Patch Code,250</value>
239 </data>
240 <data name="PatchPropertiesProperty" xml:space="preserve">
241 <value>Property,130</value>
242 </data>
243 <data name="PatchPropertiesValue" xml:space="preserve">
244 <value>Value,360</value>
245 </data>
246 <data name="PatchTargetsProductName" xml:space="preserve">
247 <value>Product Name,360</value>
248 </data>
249 <data name="PatchTargetsProductCode" xml:space="preserve">
250 <value>Product Code,360</value>
251 </data>
252</root> \ No newline at end of file
diff --git a/src/samples/Dtf/Inventory/Features.cs b/src/samples/Dtf/Inventory/Features.cs
new file mode 100644
index 00000000..c114da86
--- /dev/null
+++ b/src/samples/Dtf/Inventory/Features.cs
@@ -0,0 +1,107 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.IO;
5using System.Data;
6using System.Collections;
7using System.Collections.Generic;
8using System.Globalization;
9using System.Windows.Forms;
10using WixToolset.Dtf.WindowsInstaller;
11
12namespace WixToolset.Dtf.Samples.Inventory
13{
14 /// <summary>
15 /// Provides inventory data about features of products installed on the system.
16 /// </summary>
17 public class FeaturesInventory : IInventoryDataProvider
18 {
19 private static object syncRoot = new object();
20
21 public FeaturesInventory()
22 {
23 }
24
25 public string Description
26 {
27 get { return "Features of installed products"; }
28 }
29
30 public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback)
31 {
32 statusCallback(0, @"Products\...\Features");
33 ArrayList nodes = new ArrayList();
34 foreach (ProductInstallation product in ProductInstallation.AllProducts)
35 {
36 nodes.Add(String.Format(@"Products\{0}\Features", MsiUtils.GetProductName(product.ProductCode)));
37 }
38 statusCallback(nodes.Count, String.Empty);
39 return (string[]) nodes.ToArray(typeof(string));
40 }
41
42 public bool IsNodeSearchable(string searchRoot, string searchNode)
43 {
44 return true;
45 }
46
47 public DataView GetData(string nodePath)
48 {
49 string[] path = nodePath.Split('\\');
50
51 if(path.Length == 3 && path[0] == "Products" && path[2] == "Features")
52 {
53 return GetProductFeaturesData(MsiUtils.GetProductCode(path[1]));
54 }
55 return null;
56 }
57
58 public DataView GetProductFeaturesData(string productCode)
59 {
60 DataTable table = new DataTable("ProductFeatures");
61 table.Locale = CultureInfo.InvariantCulture;
62 table.Columns.Add("ProductFeaturesFeatureTitle", typeof(string));
63 table.Columns.Add("ProductFeaturesFeatureName", typeof(string));
64 table.Columns.Add("ProductFeaturesInstallState", typeof(string));
65
66 try
67 {
68 IntPtr hWnd = IntPtr.Zero;
69 Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
70 lock(syncRoot) // Only one Installer session can be active at a time
71 {
72 using(Session session = Installer.OpenProduct(productCode))
73 {
74 session.DoAction("CostInitialize");
75 session.DoAction("FileCost");
76 session.DoAction("CostFinalize");
77
78 IList<string> featuresAndTitles = session.Database.ExecuteStringQuery(
79 "SELECT `Title`, `Feature` FROM `Feature`");
80
81 for(int i = 0; i < featuresAndTitles.Count; i += 2)
82 {
83 InstallState featureState = session.Features[featuresAndTitles[i + 1]].CurrentState;
84 table.Rows.Add(new object[] { featuresAndTitles[i], featuresAndTitles[i+1],
85 (featureState == InstallState.Advertised ? "Advertised" : featureState.ToString()) });
86 }
87 }
88 }
89 return new DataView(table, "", "ProductFeaturesFeatureTitle ASC", DataViewRowState.CurrentRows);
90 }
91 catch(InstallerException) { }
92 catch(IOException) { }
93 return null;
94 }
95
96 public string GetLink(string nodePath, DataRow row)
97 {
98 string[] path = nodePath.Split('\\');
99
100 if(path.Length == 3 && path[0] == "Products" && path[2] == "Features")
101 {
102 return String.Format(@"Products\{0}\Features\{1}", path[1], row["ProductFeaturesFeatureName"]);
103 }
104 return null;
105 }
106 }
107}
diff --git a/src/samples/Dtf/Inventory/IInventoryDataProvider.cs b/src/samples/Dtf/Inventory/IInventoryDataProvider.cs
new file mode 100644
index 00000000..23f2c187
--- /dev/null
+++ b/src/samples/Dtf/Inventory/IInventoryDataProvider.cs
@@ -0,0 +1,67 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.Data;
5
6namespace WixToolset.Dtf.Samples.Inventory
7{
8 /// <summary>
9 /// Reports the total number of items loaded so far by <see cref="IInventoryDataProvider.GetNodes"/>.
10 /// </summary>
11 public delegate void InventoryDataLoadStatusCallback(int itemsLoaded, string currentNode);
12
13 /// <summary>
14 /// Inventory data providers implement this interface to provide a particular type of data.
15 /// Implementors must provide a parameterless constructor.
16 /// </summary>
17 public interface IInventoryDataProvider
18 {
19 /// <summary>
20 /// Gets a description of the data provided. This description allows
21 /// the user to choose what type of data to gather.
22 /// </summary>
23 string Description { get; }
24
25 /// <summary>
26 /// Gets the paths of all nodes for which this object provides data.
27 /// </summary>
28 /// <param name="statusCallback">Callback for reporting status.
29 /// The callback should not necessarily be invoked for every individual
30 /// node loaded, rather only every significant chunk.</param>
31 /// <returns>An array of node paths. The parts of the node paths
32 /// are delimited by backslashes (\).</returns>
33 string[] GetNodes(InventoryDataLoadStatusCallback statusCallback);
34
35 /// <summary>
36 /// When related nodes of a tree consist of duplicate data, it's
37 /// inefficient to search them all. This method indicates which
38 /// nodes should be search and which should be ignored.
39 /// </summary>
40 /// <param name="searchRoot">Root node of the subtree-search.</param>
41 /// <param name="searchNode">Node which may or may not be searched.</param>
42 /// <returns>True if the node should be searched, false otherwise.</returns>
43 bool IsNodeSearchable(string searchRoot, string searchNode);
44
45 /// <summary>
46 /// Gets the data for a particular node.
47 /// </summary>
48 /// <param name="nodePath">Path of the node for which data is requested.
49 /// This is one of the paths returned by <see cref="GetNodes"/>.</param>
50 /// <returns>DataView of a table filled with data, or null if data is
51 /// not available.</returns>
52 DataView GetData(string nodePath);
53
54 /// <summary>
55 /// Gets the path of another node which provides more details about
56 /// a particular data row.
57 /// </summary>
58 /// <param name="nodePath">Path of the node containing the data
59 /// row being queried.</param>
60 /// <param name="row">Data row being queried.</param>
61 /// <returns>Path to another node. This is not necessarily
62 /// one of the nodes returned by <see cref="GetNodes"/>. If the
63 /// node path is unknown, it will be ignored. This method may
64 /// return null if there is no detail node for the row.</returns>
65 string GetLink(string nodePath, DataRow row);
66 }
67}
diff --git a/src/samples/Dtf/Inventory/Inventory.cs b/src/samples/Dtf/Inventory/Inventory.cs
new file mode 100644
index 00000000..02793be8
--- /dev/null
+++ b/src/samples/Dtf/Inventory/Inventory.cs
@@ -0,0 +1,1231 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.IO;
5using System.Drawing;
6using System.Collections;
7using System.ComponentModel;
8using System.Diagnostics.CodeAnalysis;
9using System.Windows.Forms;
10using System.Globalization;
11using System.Reflection;
12using System.Resources;
13using System.Threading;
14using System.Security.Permissions;
15using System.Data;
16
17
18[assembly: AssemblyDescription("Shows a hierarchical, relational, searchable " +
19 " view of all of the product, feature, component, file, and patch data managed " +
20 "by MSI, for all products installed on the system.")]
21
22[assembly: SecurityPermission(SecurityAction.RequestMinimum, UnmanagedCode=true)]
23
24
25namespace WixToolset.Dtf.Samples.Inventory
26{
27 public class Inventory : System.Windows.Forms.Form
28 {
29 [STAThread]
30 public static void Main()
31 {
32 if (WixToolset.Dtf.WindowsInstaller.Installer.Version < new Version(3, 0))
33 {
34 MessageBox.Show("This application requires Windows Installer version 3.0 or later.",
35 "Inventory", MessageBoxButtons.OK, MessageBoxIcon.Error);
36 return;
37 }
38
39 Application.Run(new Inventory());
40 }
41
42 private IInventoryDataProvider[] dataProviders;
43 private Hashtable dataProviderMap;
44 private Hashtable data;
45 private ArrayList tablesLoading;
46 private bool searching;
47 private bool stopSearch;
48 private bool navigating;
49 private string continueSearchRoot;
50 private string continueSearchPath;
51 private DataGridCell continueSearchCell;
52 private DataGridCell continueSearchEndCell;
53 private bool mouseOverGridLink = false;
54 private Stack historyBack;
55 private Stack historyForward;
56 private Stack cellHistoryBack;
57 private Stack cellHistoryForward;
58 private static readonly DataGridCell anyCell = new DataGridCell(-1,-1);
59 private static readonly DataGridCell zeroCell = new DataGridCell(0,0);
60 private static object syncRoot = new object();
61
62 private System.Windows.Forms.DataGrid dataGrid;
63 private System.Windows.Forms.TreeView treeView;
64 private System.Windows.Forms.Panel toolPanel;
65 private System.Windows.Forms.Splitter splitter;
66 private System.Windows.Forms.Panel dataPanel;
67 private System.Windows.Forms.Button backButton;
68 private System.Windows.Forms.Button forwardButton;
69 private System.Windows.Forms.Button findButton;
70 private System.Windows.Forms.TextBox findTextBox;
71 private System.Windows.Forms.Button refreshButton;
72 private System.Windows.Forms.Button findStopButton;
73 private System.Windows.Forms.CheckBox searchTreeCheckBox;
74 private System.Windows.Forms.ToolTip gridLinkTip;
75 private System.ComponentModel.IContainer components;
76
77 public Inventory()
78 {
79 InitializeComponent();
80
81 this.gridLinkTip.InitialDelay = 0;
82 this.gridLinkTip.ReshowDelay = 0;
83
84 this.dataProviderMap = new Hashtable();
85 this.data = new Hashtable();
86 this.tablesLoading = new ArrayList();
87 this.historyBack = new Stack();
88 this.historyForward = new Stack();
89 this.cellHistoryBack = new Stack();
90 this.cellHistoryForward = new Stack();
91 }
92
93 protected override void Dispose(bool disposing)
94 {
95 if(disposing)
96 {
97 if(components != null)
98 {
99 components.Dispose();
100 }
101 }
102 base.Dispose(disposing);
103 }
104
105 #region Windows Form Designer generated code
106 /// <summary>
107 /// Required method for Designer support - do not modify
108 /// the contents of this method with the code editor.
109 /// </summary>
110 private void InitializeComponent()
111 {
112 this.components = new System.ComponentModel.Container();
113 this.dataGrid = new System.Windows.Forms.DataGrid();
114 this.treeView = new System.Windows.Forms.TreeView();
115 this.toolPanel = new System.Windows.Forms.Panel();
116 this.findStopButton = new System.Windows.Forms.Button();
117 this.findButton = new System.Windows.Forms.Button();
118 this.searchTreeCheckBox = new System.Windows.Forms.CheckBox();
119 this.findTextBox = new System.Windows.Forms.TextBox();
120 this.refreshButton = new System.Windows.Forms.Button();
121 this.forwardButton = new System.Windows.Forms.Button();
122 this.backButton = new System.Windows.Forms.Button();
123 this.dataPanel = new System.Windows.Forms.Panel();
124 this.splitter = new System.Windows.Forms.Splitter();
125 this.gridLinkTip = new System.Windows.Forms.ToolTip(this.components);
126 ((System.ComponentModel.ISupportInitialize)(this.dataGrid)).BeginInit();
127 this.toolPanel.SuspendLayout();
128 this.dataPanel.SuspendLayout();
129 this.SuspendLayout();
130 //
131 // dataGrid
132 //
133 this.dataGrid.DataMember = "";
134 this.dataGrid.Dock = System.Windows.Forms.DockStyle.Fill;
135 this.dataGrid.HeaderForeColor = System.Drawing.SystemColors.ControlText;
136 this.dataGrid.Location = new System.Drawing.Point(230, 0);
137 this.dataGrid.Name = "dataGrid";
138 this.dataGrid.ReadOnly = true;
139 this.dataGrid.SelectionBackColor = System.Drawing.SystemColors.Highlight;
140 this.dataGrid.Size = new System.Drawing.Size(562, 432);
141 this.dataGrid.TabIndex = 1;
142 this.dataGrid.KeyDown += new System.Windows.Forms.KeyEventHandler(this.dataGrid_KeyDown);
143 this.dataGrid.MouseDown += new System.Windows.Forms.MouseEventHandler(this.dataGrid_MouseDown);
144 this.dataGrid.KeyUp += new System.Windows.Forms.KeyEventHandler(this.dataGrid_KeyUp);
145 this.dataGrid.MouseMove += new System.Windows.Forms.MouseEventHandler(this.dataGrid_MouseMove);
146 this.dataGrid.MouseLeave += new System.EventHandler(this.dataGrid_MouseLeave);
147 //
148 // treeView
149 //
150 this.treeView.Dock = System.Windows.Forms.DockStyle.Left;
151 this.treeView.HideSelection = false;
152 this.treeView.ImageIndex = -1;
153 this.treeView.Location = new System.Drawing.Point(0, 0);
154 this.treeView.Name = "treeView";
155 this.treeView.SelectedImageIndex = -1;
156 this.treeView.Size = new System.Drawing.Size(224, 432);
157 this.treeView.TabIndex = 0;
158 this.treeView.KeyDown += new System.Windows.Forms.KeyEventHandler(this.treeView_KeyDown);
159 this.treeView.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Inventory_MouseDown);
160 this.treeView.KeyUp += new System.Windows.Forms.KeyEventHandler(this.treeView_KeyUp);
161 this.treeView.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.treeView_AfterSelect);
162 //
163 // toolPanel
164 //
165 this.toolPanel.Controls.Add(this.findStopButton);
166 this.toolPanel.Controls.Add(this.findButton);
167 this.toolPanel.Controls.Add(this.searchTreeCheckBox);
168 this.toolPanel.Controls.Add(this.findTextBox);
169 this.toolPanel.Controls.Add(this.refreshButton);
170 this.toolPanel.Controls.Add(this.forwardButton);
171 this.toolPanel.Controls.Add(this.backButton);
172 this.toolPanel.Dock = System.Windows.Forms.DockStyle.Top;
173 this.toolPanel.Location = new System.Drawing.Point(0, 0);
174 this.toolPanel.Name = "toolPanel";
175 this.toolPanel.Size = new System.Drawing.Size(792, 40);
176 this.toolPanel.TabIndex = 2;
177 //
178 // findStopButton
179 //
180 this.findStopButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
181 this.findStopButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
182 this.findStopButton.Location = new System.Drawing.Point(704, 8);
183 this.findStopButton.Name = "findStopButton";
184 this.findStopButton.Size = new System.Drawing.Size(72, 25);
185 this.findStopButton.TabIndex = 6;
186 this.findStopButton.Text = "Stop";
187 this.findStopButton.Visible = false;
188 this.findStopButton.Click += new System.EventHandler(this.findStopButton_Click);
189 //
190 // findButton
191 //
192 this.findButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
193 this.findButton.Enabled = false;
194 this.findButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
195 this.findButton.Location = new System.Drawing.Point(624, 8);
196 this.findButton.Name = "findButton";
197 this.findButton.Size = new System.Drawing.Size(72, 25);
198 this.findButton.TabIndex = 4;
199 this.findButton.Text = "Find";
200 this.findButton.Click += new System.EventHandler(this.findButton_Click);
201 //
202 // searchTreeCheckBox
203 //
204 this.searchTreeCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
205 this.searchTreeCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.System;
206 this.searchTreeCheckBox.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
207 this.searchTreeCheckBox.Location = new System.Drawing.Point(704, 10);
208 this.searchTreeCheckBox.Name = "searchTreeCheckBox";
209 this.searchTreeCheckBox.Size = new System.Drawing.Size(80, 22);
210 this.searchTreeCheckBox.TabIndex = 5;
211 this.searchTreeCheckBox.Text = "In Subtree";
212 this.searchTreeCheckBox.CheckedChanged += new System.EventHandler(this.searchTreeCheckBox_CheckedChanged);
213 //
214 // findTextBox
215 //
216 this.findTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
217 this.findTextBox.Location = new System.Drawing.Point(344, 10);
218 this.findTextBox.Name = "findTextBox";
219 this.findTextBox.Size = new System.Drawing.Size(272, 20);
220 this.findTextBox.TabIndex = 3;
221 this.findTextBox.Text = "";
222 this.findTextBox.TextChanged += new System.EventHandler(this.findTextBox_TextChanged);
223 this.findTextBox.Enter += new System.EventHandler(this.findTextBox_Enter);
224 //
225 // refreshButton
226 //
227 this.refreshButton.Enabled = false;
228 this.refreshButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
229 this.refreshButton.Location = new System.Drawing.Point(160, 8);
230 this.refreshButton.Name = "refreshButton";
231 this.refreshButton.Size = new System.Drawing.Size(72, 25);
232 this.refreshButton.TabIndex = 2;
233 this.refreshButton.Text = "Refresh";
234 this.refreshButton.Click += new System.EventHandler(this.refreshButton_Click);
235 //
236 // forwardButton
237 //
238 this.forwardButton.Enabled = false;
239 this.forwardButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
240 this.forwardButton.Location = new System.Drawing.Point(80, 8);
241 this.forwardButton.Name = "forwardButton";
242 this.forwardButton.Size = new System.Drawing.Size(72, 25);
243 this.forwardButton.TabIndex = 1;
244 this.forwardButton.Text = "Forward";
245 this.forwardButton.Click += new System.EventHandler(this.forwardButton_Click);
246 //
247 // backButton
248 //
249 this.backButton.Enabled = false;
250 this.backButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
251 this.backButton.Location = new System.Drawing.Point(8, 8);
252 this.backButton.Name = "backButton";
253 this.backButton.Size = new System.Drawing.Size(72, 25);
254 this.backButton.TabIndex = 0;
255 this.backButton.Text = "Back";
256 this.backButton.Click += new System.EventHandler(this.backButton_Click);
257 //
258 // dataPanel
259 //
260 this.dataPanel.Controls.Add(this.dataGrid);
261 this.dataPanel.Controls.Add(this.splitter);
262 this.dataPanel.Controls.Add(this.treeView);
263 this.dataPanel.Dock = System.Windows.Forms.DockStyle.Fill;
264 this.dataPanel.Location = new System.Drawing.Point(0, 40);
265 this.dataPanel.Name = "dataPanel";
266 this.dataPanel.Size = new System.Drawing.Size(792, 432);
267 this.dataPanel.TabIndex = 1;
268 //
269 // splitter
270 //
271 this.splitter.Location = new System.Drawing.Point(224, 0);
272 this.splitter.Name = "splitter";
273 this.splitter.Size = new System.Drawing.Size(6, 432);
274 this.splitter.TabIndex = 2;
275 this.splitter.TabStop = false;
276 //
277 // Inventory
278 //
279 this.AcceptButton = this.findButton;
280 this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
281 this.ClientSize = new System.Drawing.Size(792, 472);
282 this.Controls.Add(this.dataPanel);
283 this.Controls.Add(this.toolPanel);
284 this.MinimumSize = new System.Drawing.Size(700, 0);
285 this.Name = "Inventory";
286 this.Text = "MSI Inventory";
287 this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Inventory_KeyDown);
288 this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Inventory_MouseDown);
289 this.Load += new System.EventHandler(this.Inventory_Load);
290 this.KeyUp += new System.Windows.Forms.KeyEventHandler(this.Inventory_KeyUp);
291 ((System.ComponentModel.ISupportInitialize)(this.dataGrid)).EndInit();
292 this.toolPanel.ResumeLayout(false);
293 this.dataPanel.ResumeLayout(false);
294 this.ResumeLayout(false);
295
296 }
297 #endregion
298
299
300 #region DataProviders
301
302 private IInventoryDataProvider[] DataProviders
303 {
304 get
305 {
306 if(this.dataProviders == null)
307 {
308 ArrayList providerList = new ArrayList();
309 providerList.AddRange(FindDataProviders(Assembly.GetExecutingAssembly()));
310
311 Uri codebase = new Uri(Assembly.GetExecutingAssembly().CodeBase);
312 if(codebase.IsFile)
313 {
314 foreach(string module in Directory.GetFiles(Path.GetDirectoryName(codebase.LocalPath), "*Inventory.dll"))
315 {
316 try
317 {
318 providerList.AddRange(FindDataProviders(Assembly.LoadFrom(module)));
319 }
320 catch(Exception) { }
321 }
322 }
323
324 this.dataProviders = (IInventoryDataProvider[]) providerList.ToArray(typeof(IInventoryDataProvider));
325 }
326 return this.dataProviders;
327 }
328 }
329
330 private static IList FindDataProviders(Assembly assembly)
331 {
332 ArrayList providerList = new ArrayList();
333 foreach(Type type in assembly.GetTypes())
334 {
335 if(type.IsClass)
336 {
337 foreach(Type implementedInterface in type.GetInterfaces())
338 {
339 if(implementedInterface.Equals(typeof(IInventoryDataProvider)))
340 {
341 try
342 {
343 providerList.Add(assembly.CreateInstance(type.FullName));
344 }
345 catch(Exception)
346 {
347 // Data provider's constructor threw an exception for some reason.
348 // Well, now we can't get any data from that one.
349 }
350 }
351 }
352 }
353 }
354 return providerList;
355 }
356
357 #endregion
358
359 private void GoTo(string nodePath, DataGridCell cell)
360 {
361 lock(syncRoot)
362 {
363 if(this.tablesLoading == null) return; // The tree is being loaded
364 if(this.navigating) return; // This method is already on the callstack
365
366 DataView table = (DataView) this.data[nodePath];
367 if(table != null && table == this.dataGrid.DataSource)
368 {
369 // Grid is already in view
370 if(!cell.Equals(anyCell)) this.dataGrid.CurrentCell = cell;
371 return;
372 }
373 if(cell.Equals(anyCell)) cell = zeroCell;
374
375 if(this.historyBack.Count == 0 || nodePath != (string) this.historyBack.Peek())
376 {
377 this.historyBack.Push(nodePath);
378 if(this.cellHistoryBack.Count > 0 && this.historyForward != null)
379 {
380 this.cellHistoryBack.Pop();
381 this.cellHistoryBack.Push(this.dataGrid.CurrentCell);
382 }
383 this.cellHistoryBack.Push(cell);
384 }
385 if(this.historyForward != null)
386 {
387 this.historyForward.Clear();
388 this.cellHistoryForward.Clear();
389 }
390
391 if(table != null || nodePath.Length == 0 || this.dataProviderMap[nodePath] == null)
392 {
393 this.dataGrid.CaptionText = nodePath;
394 this.dataGrid.CaptionBackColor = SystemColors.ActiveCaption;
395 this.dataGrid.CaptionForeColor = SystemColors.ActiveCaptionText;
396 this.dataGrid.DataSource = table;
397 this.dataGrid.CurrentCell = cell;
398 this.dataGrid.Focus();
399 }
400 else
401 {
402 this.dataGrid.CaptionText = nodePath + " (loading...)";
403 this.dataGrid.CaptionBackColor = SystemColors.InactiveCaption;
404 this.dataGrid.CaptionForeColor = SystemColors.InactiveCaptionText;
405 this.dataGrid.DataSource = table;
406 if(!this.tablesLoading.Contains(nodePath))
407 {
408 this.tablesLoading.Add(nodePath);
409 this.SetCursor();
410 #if SINGLETHREAD
411 this.LoadTable(nodePath);
412 #else
413 new WaitCallback(this.LoadTable).BeginInvoke(nodePath, null, null);
414 #endif
415 }
416 }
417
418 this.findButton.Enabled = this.findTextBox.Text.Length > 0 && !searching;
419
420 TreeNode treeNode = this.FindNode(nodePath);
421 if(treeNode != this.treeView.SelectedNode)
422 {
423 this.navigating = true;
424 this.treeView.SelectedNode = treeNode;
425 this.navigating = false;
426 }
427 }
428 }
429
430 private void LoadTable(object nodePathObj)
431 {
432 string nodePath = (string) nodePathObj;
433 IInventoryDataProvider dataProvider = (IInventoryDataProvider) this.dataProviderMap[nodePath];
434 DataView table = null;
435 if(dataProvider != null)
436 {
437 try
438 {
439 table = dataProvider.GetData(nodePath);
440 }
441 catch(Exception)
442 {
443 // Data provider threw an exception for some reason.
444 // Treat it like it returned no data.
445 }
446 }
447
448 lock(syncRoot)
449 {
450 if(this.tablesLoading == null || !tablesLoading.Contains(nodePath)) return;
451 if(table == null)
452 {
453 this.dataProviderMap.Remove(nodePath);
454 }
455 else
456 {
457 this.data[nodePath] = table;
458 }
459 this.tablesLoading.Remove(nodePath);
460 }
461 #if SINGLETHREAD
462 this.TableLoaded(nodePath);
463 #else
464 this.Invoke(new WaitCallback(this.TableLoaded), new object[] { nodePath });
465 #endif
466 }
467
468 private void TableLoaded(object nodePathObj)
469 {
470 string nodePath = (string) nodePathObj;
471 lock(syncRoot)
472 {
473 this.LoadTableStyle(nodePath);
474 if(nodePath == this.CurrentNodePath)
475 {
476 this.dataGrid.CaptionBackColor = SystemColors.ActiveCaption;
477 this.dataGrid.CaptionForeColor = SystemColors.ActiveCaptionText;
478 this.dataGrid.CaptionText = nodePath;
479 this.dataGrid.DataSource = this.CurrentTable;
480 this.dataGrid.CurrentCell = (DataGridCell) this.cellHistoryBack.Peek();
481 this.dataGrid.Focus();
482 }
483 this.SetCursor();
484 }
485 }
486
487 private void RefreshData()
488 {
489 lock(syncRoot)
490 {
491 this.GoTo("", zeroCell);
492 this.treeView.Nodes.Clear();
493 this.dataGrid.TableStyles.Clear();
494 this.dataGrid.CaptionBackColor = SystemColors.InactiveCaption;
495 this.dataGrid.CaptionForeColor = SystemColors.InactiveCaptionText;
496 this.SetControlsEnabled(false);
497 this.treeView.BeginUpdate();
498 #if SINGLETHREAD
499 this.LoadTree();
500 #else
501 new ThreadStart(this.LoadTree).BeginInvoke(null, null);
502 #endif
503 }
504 }
505
506 private void SetControlsEnabled(bool enabled)
507 {
508 this.backButton.Enabled = enabled && this.historyBack.Count > 1;
509 this.forwardButton.Enabled = enabled && this.historyForward.Count > 0;
510 this.refreshButton.Enabled = enabled;
511 this.findButton.Enabled = enabled && this.findTextBox.Text.Length > 0 && !searching;
512 }
513
514 private WaitCallback treeStatusCallback;
515 private int treeNodesLoaded;
516 private int treeNodesLoadedBase;
517 private string treeNodesLoading;
518 private void TreeLoadDataProviderStatus(int status, string currentNode)
519 {
520 if (currentNode != null)
521 {
522 this.treeNodesLoading = currentNode;
523 }
524
525 this.treeNodesLoaded = treeNodesLoadedBase + status;
526 string statusString = String.Format("Loading tree... " + this.treeNodesLoaded);
527 if (!String.IsNullOrEmpty(this.treeNodesLoading))
528 {
529 statusString += ": " + treeNodesLoading;
530 }
531
532 #if SINGLETHREAD
533 treeStatusCallback(statusString);
534 #else
535 this.Invoke(treeStatusCallback, new object[] { statusString });
536 #endif
537 }
538
539 private void UpdateTreeLoadStatus(object status)
540 {
541 if(status == null)
542 {
543 // Loading is complete.
544 this.treeView.EndUpdate();
545 this.SetCursor();
546 this.GoTo("Products", new DataGridCell(0, 0));
547 this.SetControlsEnabled(true);
548 }
549 else
550 {
551 this.dataGrid.CaptionText = (string) status;
552 }
553 }
554
555 private void LoadTree()
556 {
557 lock(syncRoot)
558 {
559 if(this.tablesLoading == null) return;
560 this.tablesLoading = null;
561 this.dataProviderMap.Clear();
562 this.data.Clear();
563 this.Invoke(new ThreadStart(this.SetCursor));
564 }
565
566 this.treeStatusCallback = new WaitCallback(UpdateTreeLoadStatus);
567 this.LoadTreeNodes();
568 this.RenderTreeNodes();
569
570 lock(syncRoot)
571 {
572 this.tablesLoading = new ArrayList();
573 }
574 // Use a status of null to signal loading complete.
575 #if SINGLETHREAD
576 this.UpdateTreeLoadStatus(null);
577 #else
578 this.Invoke(new WaitCallback(this.UpdateTreeLoadStatus), new object[] { null });
579 #endif
580 }
581
582 private void LoadTreeNodes()
583 {
584 #if SINGLETHREAD
585 this.treeStatusCallback("Loading tree... ");
586 #else
587 this.Invoke(this.treeStatusCallback, new object[] { "Loading tree... " });
588 #endif
589 this.treeNodesLoaded = 0;
590 this.treeNodesLoading = null;
591 foreach(IInventoryDataProvider dataProvider in this.DataProviders)
592 {
593 this.treeNodesLoadedBase = this.treeNodesLoaded;
594 string[] nodePaths = null;
595 try
596 {
597 nodePaths = dataProvider.GetNodes(new InventoryDataLoadStatusCallback(this.TreeLoadDataProviderStatus));
598 }
599 catch(Exception)
600 {
601 // Data provider threw an exception for some reason.
602 // Treat it like it returned no data.
603 }
604 if(nodePaths != null)
605 {
606 foreach(string nodePath in nodePaths)
607 {
608 if(!this.dataProviderMap.Contains(nodePath))
609 {
610 this.dataProviderMap.Add(nodePath, dataProvider);
611 }
612 }
613 }
614 }
615 }
616
617 private void RenderTreeNodes()
618 {
619 #if SINGLETHREAD
620 this.treeStatusCallback("Rendering tree... ");
621 #else
622 this.Invoke(this.treeStatusCallback, new object[] { "Rendering tree... " });
623 #endif
624 this.treeNodesLoaded = 0;
625 foreach(DictionaryEntry nodePathAndProvider in this.dataProviderMap)
626 {
627 string nodePath = (string) nodePathAndProvider.Key;
628 #if SINGLETHREAD
629 this.AddNode(nodePath);
630 #else
631 this.Invoke(new WaitCallback(this.AddNode), new object[] { nodePath });
632 #endif
633 }
634 }
635
636 private void LoadTableStyle(string nodePath)
637 {
638 DataView table = (DataView) this.data[nodePath];
639 if(table != null)
640 {
641 DataGridTableStyle tableStyle = this.dataGrid.TableStyles[table.Table.TableName];
642 if(tableStyle == null)
643 {
644 tableStyle = new DataGridTableStyle();
645 tableStyle.MappingName = table.Table.TableName;
646 tableStyle.RowHeadersVisible = true;
647 this.dataGrid.TableStyles.Add(tableStyle);
648 }
649 foreach(DataColumn column in table.Table.Columns)
650 {
651 if(!tableStyle.GridColumnStyles.Contains(column.ColumnName))
652 {
653 string colStyle = (string) ColumnResources.GetObject(column.ColumnName, CultureInfo.InvariantCulture);
654 if(colStyle != null)
655 {
656 string[] colStyleParts = colStyle.Split(',');
657 DataGridColumnStyle columnStyle = (colStyleParts.Length > 2 && colStyleParts[2] == "bool"
658 ? (DataGridColumnStyle) new DataGridBoolColumn() : (DataGridColumnStyle) new DataGridTextBoxColumn());
659 try { if(colStyleParts.Length > 1) columnStyle.Width = Int32.Parse(colStyleParts[1]); }
660 catch(FormatException) { }
661 columnStyle.HeaderText = colStyleParts[0];
662 columnStyle.MappingName = column.ColumnName;
663 tableStyle.GridColumnStyles.Add(columnStyle);
664 }
665 }
666 }
667 }
668 }
669
670 private static ResourceManager ColumnResources
671 {
672 get
673 {
674 if(columnResources == null)
675 {
676 columnResources = new ResourceManager(typeof(Inventory).Name + ".Columns", typeof(Inventory).Assembly);
677 }
678 return columnResources;
679 }
680 }
681 private static ResourceManager columnResources;
682
683 private void AddNode(object nodePathObj)
684 {
685 string nodePath = (string) nodePathObj;
686 string[] path = nodePath.Split('\\');
687 TreeNodeCollection nodes = this.treeView.Nodes;
688 TreeNode node = null;
689 foreach(string pathPart in path)
690 {
691 node = null;
692 for(int i = 0; i < nodes.Count; i++)
693 {
694 int c = string.CompareOrdinal(nodes[i].Text, pathPart);
695 if(c == 0)
696 {
697 node = nodes[i];
698 break;
699 }
700 else if(c > 0)
701 {
702 node = new TreeNode(pathPart);
703 nodes.Insert(i, node);
704 break;
705 }
706 }
707 if(node == null)
708 {
709 node = new TreeNode(pathPart);
710 nodes.Add(node);
711 }
712 nodes = node.Nodes;
713 }
714 if(++this.treeNodesLoaded % 1000 == 0)
715 {
716 this.UpdateTreeLoadStatus("Rendering tree... " +
717 (100 * this.treeNodesLoaded / this.dataProviderMap.Count) + "%");
718 }
719 }
720
721 public string CurrentNodePath
722 {
723 get
724 {
725 TreeNode currentNode = this.treeView.SelectedNode;
726 return currentNode != null ? currentNode.FullPath : null;
727 }
728 }
729
730 public DataView CurrentTable
731 {
732 get
733 {
734 string currentNodePath = this.CurrentNodePath;
735 return currentNodePath != null ? (DataView) this.data[this.CurrentNodePath] : null;
736 }
737 }
738
739 private TreeNode FindNode(string nodePath)
740 {
741 if(nodePath == null) return null;
742 string[] path = nodePath.Split('\\');
743 TreeNodeCollection nodes = this.treeView.Nodes;
744 TreeNode node = null;
745 foreach(string pathPart in path)
746 {
747 node = null;
748 for(int i = 0; i < nodes.Count; i++)
749 {
750 if(nodes[i].Text == pathPart)
751 {
752 node = nodes[i];
753 break;
754 }
755 }
756 if(node != null)
757 {
758 nodes = node.Nodes;
759 }
760 }
761 return node;
762 }
763
764 private void dataGrid_MouseDown(object sender, MouseEventArgs e)
765 {
766 Keys modKeys = Control.ModifierKeys;
767 if(e.Button == MouseButtons.Left && (modKeys & (Keys.Shift | Keys.Control)) == 0)
768 {
769 DataGrid.HitTestInfo hit = this.dataGrid.HitTest(e.X, e.Y);
770 string link = this.GetLinkForGridHit(hit);
771 if(link != null)
772 {
773 TreeNode node = this.FindNode(link);
774 if(node != null)
775 {
776 this.treeView.SelectedNode = node;
777 node.Expand();
778 }
779 }
780 }
781 this.Inventory_MouseDown(sender, e);
782 }
783
784 private void dataGrid_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
785 {
786 //this.gridLinkTip.SetToolTip(this.dataGrid, null);
787 DataGrid.HitTestInfo hit = this.dataGrid.HitTest(e.X, e.Y);
788 if(hit.Type == DataGrid.HitTestType.RowHeader)
789 {
790 string link = this.GetLinkForGridHit(hit);
791 if(link != null)
792 {
793 this.mouseOverGridLink = true;
794 this.SetCursor();
795 return;
796 }
797 }
798 else if(this.mouseOverGridLink)
799 {
800 this.mouseOverGridLink = false;
801 this.SetCursor();
802 }
803 }
804
805 private void dataGrid_MouseLeave(object sender, System.EventArgs e)
806 {
807 this.mouseOverGridLink = false;
808 this.SetCursor();
809 }
810
811 private string GetLinkForGridHit(DataGrid.HitTestInfo hit)
812 {
813 if(hit.Type == DataGrid.HitTestType.RowHeader && this.tablesLoading != null)
814 {
815 string nodePath = this.CurrentNodePath;
816 DataView table = (DataView) this.data[nodePath];
817 if(table != null)
818 {
819 DataRow row = table[hit.Row].Row;
820 IInventoryDataProvider dataProvider = (IInventoryDataProvider) this.dataProviderMap[nodePath];
821 return dataProvider.GetLink(nodePath, table[hit.Row].Row);
822 }
823 }
824 return null;
825 }
826
827 private void HistoryBack()
828 {
829 lock(syncRoot)
830 {
831 if(this.historyBack.Count > 1)
832 {
833 string nodePath = (string) this.historyBack.Pop();
834 this.cellHistoryBack.Pop();
835 DataGridCell cell = this.dataGrid.CurrentCell;
836 Stack saveForward = this.historyForward;
837 this.historyForward = null;
838 this.GoTo((string) this.historyBack.Pop(), (DataGridCell) this.cellHistoryBack.Pop());
839 this.historyForward = saveForward;
840 this.historyForward.Push(nodePath);
841 this.cellHistoryForward.Push(cell);
842 this.backButton.Enabled = this.historyBack.Count > 1;
843 this.forwardButton.Enabled = this.historyForward.Count > 0;
844 }
845 }
846 }
847
848 private void HistoryForward()
849 {
850 lock(syncRoot)
851 {
852 if(this.historyForward.Count > 0)
853 {
854 string nodePath = (string) this.historyForward.Pop();
855 DataGridCell cell = (DataGridCell) this.cellHistoryForward.Pop();
856 Stack saveForward = this.historyForward;
857 this.historyForward = null;
858 this.GoTo(nodePath, cell);
859 this.historyForward = saveForward;
860 this.backButton.Enabled = this.historyBack.Count > 1;
861 this.forwardButton.Enabled = this.historyForward.Count > 0;
862 }
863 }
864 }
865
866 #region Find
867
868 private void Find()
869 {
870 this.BeginFind();
871 object[] findNextArgs = new object[] { this.CurrentNodePath, this.dataGrid.CurrentCell, this.treeView.SelectedNode };
872 #if SINGLETHREAD
873 this.FindNext(findNextArgs);
874 #else
875 new WaitCallback(this.FindNext).BeginInvoke(findNextArgs, null, null);
876 #endif
877 }
878
879 [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
880 private void FindNext(object start)
881 {
882 string nodePath = (string) ((object[]) start)[0];
883 DataGridCell startCell = (DataGridCell) ((object[]) start)[1];
884 TreeNode searchNode = (TreeNode) ((object[]) start)[2];
885 DataGridCell endCell = startCell;
886
887 string searchString = this.findTextBox.Text;
888 if(searchString.Length == 0) return;
889
890 bool ignoreCase = true; // TODO: make this a configurable option?
891 if(ignoreCase) searchString = searchString.ToLowerInvariant();
892
893 if(!this.searchTreeCheckBox.Checked)
894 {
895 DataGridCell foundCell;
896 startCell.ColumnNumber++;
897 if(FindInTable((DataView) this.data[nodePath], searchString, ignoreCase,
898 startCell, startCell, true, out foundCell))
899 {
900 #if SINGLETHREAD
901 this.EndFind(new object[] { nodePath, foundCell });
902 #else
903 this.Invoke(new WaitCallback(this.EndFind), new object[] { new object[] { nodePath, foundCell } });
904 #endif
905 return;
906 }
907 }
908 else
909 {
910 if(this.continueSearchRoot != null)
911 {
912 searchNode = this.FindNode(this.continueSearchRoot);
913 startCell = this.continueSearchCell;
914 endCell = this.continueSearchEndCell;
915 }
916 else
917 {
918 this.continueSearchRoot = searchNode.FullPath;
919 this.continueSearchPath = this.continueSearchRoot;
920 this.continueSearchEndCell = endCell;
921 }
922 //if(searchNode == null) return;
923 ArrayList nodesList = new ArrayList();
924 nodesList.Add(searchNode);
925 this.GetFlatTreeNodes(searchNode.Nodes, nodesList, true, this.continueSearchRoot);
926 TreeNode[] nodes = (TreeNode[]) nodesList.ToArray(typeof(TreeNode));
927 int startNode = nodesList.IndexOf(this.FindNode(this.continueSearchPath));
928 DataGridCell foundCell;
929 startCell.ColumnNumber++;
930 for(int i = startNode; i < nodes.Length; i++)
931 {
932 if(this.stopSearch) break;
933 DataGridCell startCellOnThisNode = zeroCell;
934 if(i == startNode) startCellOnThisNode = startCell;
935 DataView table = this.GetTableForSearch(nodes[i].FullPath);
936 if(table != null)
937 {
938 if(FindInTable(table, searchString, ignoreCase, startCellOnThisNode, zeroCell, false, out foundCell))
939 {
940 #if SINGLETHREAD
941 this.EndFind(new object[] { nodes[i].FullPath, foundCell });
942 #else
943 this.Invoke(new WaitCallback(this.EndFind), new object[] { new object[] { nodes[i].FullPath, foundCell } });
944 #endif
945 return;
946 }
947 }
948 }
949 if(!this.stopSearch)
950 {
951 DataView table = this.GetTableForSearch(searchNode.FullPath);
952 if(table != null)
953 {
954 if(FindInTable(table, searchString, ignoreCase, zeroCell, endCell, false, out foundCell))
955 {
956 #if SINGLETHREAD
957 this.EndFind(new object[] { searchNode.FullPath, foundCell });
958 #else
959 this.Invoke(new WaitCallback(this.EndFind), new object[] { new object[] { searchNode.FullPath, foundCell } });
960 #endif
961 return;
962 }
963 }
964 }
965 }
966 #if SINGLETHREAD
967 this.EndFind(null);
968 #else
969 this.Invoke(new WaitCallback(this.EndFind), new object[] { null });
970 #endif
971 }
972
973 private DataView GetTableForSearch(string nodePath)
974 {
975 DataView table = (DataView) this.data[nodePath];
976 string status = nodePath;
977 if(table == null) status = status + " (loading)";
978 #if SINGLETHREAD
979 this.FindStatus(nodePath);
980 #else
981 this.Invoke(new WaitCallback(this.FindStatus), new object[] { status });
982 #endif
983 if(table == null)
984 {
985 this.tablesLoading.Add(nodePath);
986 this.Invoke(new ThreadStart(this.SetCursor));
987 this.LoadTable(nodePath);
988 table = (DataView) this.data[nodePath];
989 }
990 return table;
991 }
992
993 private void GetFlatTreeNodes(TreeNodeCollection nodes, IList resultsList, bool searchable, string searchRoot)
994 {
995 foreach(TreeNode node in nodes)
996 {
997 string nodePath = node.FullPath;
998 IInventoryDataProvider dataProvider = (IInventoryDataProvider) this.dataProviderMap[nodePath];
999 if(!searchable || (dataProvider != null && dataProvider.IsNodeSearchable(searchRoot, nodePath)))
1000 {
1001 resultsList.Add(node);
1002 }
1003 GetFlatTreeNodes(node.Nodes, resultsList, searchable, searchRoot);
1004 }
1005 }
1006
1007 [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
1008 private bool FindInTable(DataView table, string searchString, bool lowerCase,
1009 DataGridCell startCell, DataGridCell endCell, bool wrap, out DataGridCell foundCell)
1010 {
1011 foundCell = new DataGridCell(-1, -1);
1012 if(table == null) return false;
1013 if(startCell.RowNumber < 0) startCell.RowNumber = 0;
1014 if(startCell.ColumnNumber < 0) startCell.ColumnNumber = 0;
1015 for(int searchRow = startCell.RowNumber; searchRow < table.Count; searchRow++)
1016 {
1017 if(this.stopSearch) break;
1018 if(endCell.RowNumber > startCell.RowNumber && searchRow > endCell.RowNumber) break;
1019
1020 DataRowView tableRow = table[searchRow];
1021 for(int searchCol = (searchRow == startCell.RowNumber
1022 ? startCell.ColumnNumber : 0); searchCol < table.Table.Columns.Count; searchCol++)
1023 {
1024 if(this.stopSearch) break;
1025 if(endCell.RowNumber > startCell.RowNumber && searchRow == endCell.RowNumber
1026 && searchCol >= endCell.ColumnNumber) break;
1027
1028 string value = tableRow[searchCol].ToString();
1029 if(lowerCase) value = value.ToLowerInvariant();
1030 if(value.IndexOf(searchString, StringComparison.Ordinal) >= 0)
1031 {
1032 foundCell.RowNumber = searchRow;
1033 foundCell.ColumnNumber = searchCol;
1034 return true;
1035 }
1036 }
1037 }
1038 if(wrap)
1039 {
1040 for(int searchRow = 0; searchRow <= endCell.RowNumber; searchRow++)
1041 {
1042 if(this.stopSearch) break;
1043 DataRowView tableRow = table[searchRow];
1044 for(int searchCol = 0; searchCol < (searchRow == endCell.RowNumber
1045 ? endCell.ColumnNumber : table.Table.Columns.Count); searchCol++)
1046 {
1047 if(this.stopSearch) break;
1048 string value = tableRow[searchCol].ToString();
1049 if(lowerCase) value = value.ToLowerInvariant();
1050 if(value.IndexOf(searchString, StringComparison.Ordinal) >= 0)
1051 {
1052 foundCell.RowNumber = searchRow;
1053 foundCell.ColumnNumber = searchCol;
1054 return true;
1055 }
1056 }
1057 }
1058 }
1059 return false;
1060 }
1061
1062 private void BeginFind()
1063 {
1064 lock(syncRoot)
1065 {
1066 this.findButton.Enabled = false;
1067 this.findButton.Text = "Searching...";
1068 this.findTextBox.Enabled = false;
1069 this.searchTreeCheckBox.Visible = false;
1070 this.findStopButton.Visible = true;
1071 this.refreshButton.Enabled = false;
1072 this.searching = true;
1073 this.stopSearch = false;
1074 this.SetCursor();
1075 }
1076 }
1077
1078 private void FindStatus(object status)
1079 {
1080 lock(syncRoot)
1081 {
1082 this.dataGrid.CaptionText = "Searching... " + (string) status;
1083 this.dataGrid.CaptionBackColor = SystemColors.InactiveCaption;
1084 this.dataGrid.CaptionForeColor = SystemColors.InactiveCaptionText;
1085 }
1086 }
1087
1088 private void EndFind(object result)
1089 {
1090 lock(syncRoot)
1091 {
1092 this.searching = false;
1093 this.refreshButton.Enabled = true;
1094 this.findStopButton.Visible = false;
1095 this.searchTreeCheckBox.Visible = true;
1096 this.findTextBox.Enabled = true;
1097 this.findButton.Text = "Find";
1098 this.findButton.Enabled = true;
1099 this.dataGrid.CaptionBackColor = SystemColors.ActiveCaption;
1100 this.dataGrid.CaptionForeColor = SystemColors.ActiveCaptionText;
1101 this.dataGrid.CaptionText = this.CurrentNodePath;
1102 if(result != null)
1103 {
1104 string nodePath = (string) ((object[]) result)[0];
1105 DataGridCell foundCell = (DataGridCell) ((object[]) result)[1];
1106 this.GoTo(nodePath, foundCell);
1107 this.dataGrid.Focus();
1108 this.continueSearchPath = nodePath;
1109 this.continueSearchCell = foundCell;
1110 if(this.searchTreeCheckBox.Checked) this.searchTreeCheckBox.Text = "Continue";
1111 }
1112 else
1113 {
1114 this.continueSearchRoot = null;
1115 this.continueSearchPath = null;
1116 this.searchTreeCheckBox.Text = "In Subtree";
1117 }
1118 this.SetCursor();
1119 }
1120 }
1121
1122 private void SetCursor()
1123 {
1124 if(this.mouseOverGridLink)
1125 {
1126 Keys modKeys = Control.ModifierKeys;
1127 if((modKeys & (Keys.Shift | Keys.Control)) == 0)
1128 {
1129 this.Cursor = Cursors.Hand;
1130 return;
1131 }
1132 }
1133 if(this.tablesLoading == null || this.tablesLoading.Count > 0 || this.searching)
1134 {
1135 this.Cursor = Cursors.AppStarting;
1136 return;
1137 }
1138 this.Cursor = Cursors.Arrow;
1139 }
1140
1141 #endregion
1142
1143 #region EventHandlers
1144
1145 private void Inventory_Load(object sender, System.EventArgs e)
1146 {
1147 this.RefreshData();
1148 }
1149 private void refreshButton_Click(object sender, System.EventArgs e)
1150 {
1151 this.RefreshData();
1152 }
1153 private void Inventory_MouseDown(object sender, MouseEventArgs e)
1154 {
1155 if(e.Button == MouseButtons.XButton1) this.HistoryBack();
1156 else if(e.Button == MouseButtons.XButton2) this.HistoryForward();
1157 }
1158 private void Inventory_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
1159 {
1160 this.SetCursor();
1161 if(e.KeyCode == Keys.F3) this.Find();
1162 else if(e.KeyCode == Keys.F && (e.Modifiers | Keys.Control) != 0) this.findTextBox.Focus();
1163 else if(e.KeyCode == Keys.BrowserBack) this.HistoryBack();
1164 else if(e.KeyCode == Keys.BrowserForward) this.HistoryForward();
1165 else return;
1166 e.Handled = true;
1167 }
1168 private void treeView_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
1169 {
1170 this.Inventory_KeyDown(sender, e);
1171 }
1172 private void dataGrid_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
1173 {
1174 this.Inventory_KeyDown(sender, e);
1175 }
1176 private void Inventory_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
1177 {
1178 this.SetCursor();
1179 }
1180 private void treeView_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
1181 {
1182 this.Inventory_KeyDown(sender, e);
1183 }
1184 private void dataGrid_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
1185 {
1186 this.Inventory_KeyDown(sender, e);
1187 }
1188 private void treeView_AfterSelect(object sender, System.Windows.Forms.TreeViewEventArgs e)
1189 {
1190 this.GoTo(e.Node.FullPath, anyCell);
1191 }
1192 private void backButton_Click(object sender, System.EventArgs e)
1193 {
1194 this.HistoryBack();
1195 }
1196 private void forwardButton_Click(object sender, System.EventArgs e)
1197 {
1198 this.HistoryForward();
1199 }
1200 private void findTextBox_TextChanged(object sender, System.EventArgs e)
1201 {
1202 this.findButton.Enabled = this.findTextBox.Text.Length > 0 &&
1203 this.tablesLoading != null && this.treeView.SelectedNode != null && !searching;
1204 this.searchTreeCheckBox.Text = "In Subtree";
1205 this.continueSearchRoot = null;
1206 }
1207 private void findButton_Click(object sender, System.EventArgs e)
1208 {
1209 this.Find();
1210 }
1211 private void findTextBox_Enter(object sender, System.EventArgs e)
1212 {
1213 findTextBox.SelectAll();
1214 }
1215 private void findStopButton_Click(object sender, System.EventArgs e)
1216 {
1217 this.stopSearch = true;
1218 }
1219
1220 private void searchTreeCheckBox_CheckedChanged(object sender, System.EventArgs e)
1221 {
1222 if(!searchTreeCheckBox.Checked && searchTreeCheckBox.Text == "Continue")
1223 {
1224 this.searchTreeCheckBox.Text = "In Subtree";
1225 }
1226 }
1227
1228 #endregion
1229
1230 }
1231}
diff --git a/src/samples/Dtf/Inventory/Inventory.csproj b/src/samples/Dtf/Inventory/Inventory.csproj
new file mode 100644
index 00000000..6dc1cfd3
--- /dev/null
+++ b/src/samples/Dtf/Inventory/Inventory.csproj
@@ -0,0 +1,42 @@
1
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
6 <PropertyGroup>
7 <ProjectGuid>{51480F8E-B80F-42DC-91E7-3542C1F12F8C}</ProjectGuid>
8 <OutputType>WinExe</OutputType>
9 <RootNamespace>WixToolset.Dtf.Samples.Inventory</RootNamespace>
10 <AssemblyName>Inventory</AssemblyName>
11 <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
12 <ApplicationIcon>Inventory.ico</ApplicationIcon>
13 <RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
14 </PropertyGroup>
15
16 <ItemGroup>
17 <Compile Include="components.cs" />
18 <Compile Include="Features.cs" />
19 <Compile Include="IInventoryDataProvider.cs" />
20 <Compile Include="Inventory.cs">
21 <SubType>Form</SubType>
22 </Compile>
23 <Compile Include="msiutils.cs" />
24 <Compile Include="patches.cs" />
25 <Compile Include="products.cs" />
26 </ItemGroup>
27
28 <ItemGroup>
29 <Content Include="Inventory.ico" />
30 </ItemGroup>
31
32 <ItemGroup>
33 <Reference Include="System" />
34 <Reference Include="System.Data" />
35 <Reference Include="System.Drawing" />
36 <Reference Include="System.Windows.Forms" />
37 <Reference Include="System.Xml" />
38 <ProjectReference Include="..\..\Libraries\WindowsInstaller\WindowsInstaller.csproj" />
39 </ItemGroup>
40
41 <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" />
42</Project>
diff --git a/src/samples/Dtf/Inventory/Inventory.ico b/src/samples/Dtf/Inventory/Inventory.ico
new file mode 100644
index 00000000..d5757f7a
--- /dev/null
+++ b/src/samples/Dtf/Inventory/Inventory.ico
Binary files differ
diff --git a/src/samples/Dtf/Inventory/Inventory.resx b/src/samples/Dtf/Inventory/Inventory.resx
new file mode 100644
index 00000000..9aeb4d2c
--- /dev/null
+++ b/src/samples/Dtf/Inventory/Inventory.resx
@@ -0,0 +1,265 @@
1<?xml version="1.0" encoding="utf-8"?>
2<root>
3 <!--
4 Microsoft ResX Schema
5
6 Version 1.3
7
8 The primary goals of this format is to allow a simple XML format
9 that is mostly human readable. The generation and parsing of the
10 various data types are done through the TypeConverter classes
11 associated with the data types.
12
13 Example:
14
15 ... ado.net/XML headers & schema ...
16 <resheader name="resmimetype">text/microsoft-resx</resheader>
17 <resheader name="version">1.3</resheader>
18 <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
19 <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
20 <data name="Name1">this is my long string</data>
21 <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
22 <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
23 [base64 mime encoded serialized .NET Framework object]
24 </data>
25 <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
26 [base64 mime encoded string representing a byte array form of the .NET Framework object]
27 </data>
28
29 There are any number of "resheader" rows that contain simple
30 name/value pairs.
31
32 Each data row contains a name, and value. The row also contains a
33 type or mimetype. Type corresponds to a .NET class that support
34 text/value conversion through the TypeConverter architecture.
35 Classes that don't support this are serialized and stored with the
36 mimetype set.
37
38 The mimetype is used forserialized objects, and tells the
39 ResXResourceReader how to depersist the object. This is currently not
40 extensible. For a given mimetype the value must be set accordingly:
41
42 Note - application/x-microsoft.net.object.binary.base64 is the format
43 that the ResXResourceWriter will generate, however the reader can
44 read any of the formats listed below.
45
46 mimetype: application/x-microsoft.net.object.binary.base64
47 value : The object must be serialized with
48 : System.Serialization.Formatters.Binary.BinaryFormatter
49 : and then encoded with base64 encoding.
50
51 mimetype: application/x-microsoft.net.object.soap.base64
52 value : The object must be serialized with
53 : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
54 : and then encoded with base64 encoding.
55
56 mimetype: application/x-microsoft.net.object.bytearray.base64
57 value : The object must be serialized into a byte array
58 : using a System.ComponentModel.TypeConverter
59 : and then encoded with base64 encoding.
60 -->
61 <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
62 <xsd:element name="root" msdata:IsDataSet="true">
63 <xsd:complexType>
64 <xsd:choice maxOccurs="unbounded">
65 <xsd:element name="data">
66 <xsd:complexType>
67 <xsd:sequence>
68 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
69 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
70 </xsd:sequence>
71 <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
72 <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
73 <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
74 </xsd:complexType>
75 </xsd:element>
76 <xsd:element name="resheader">
77 <xsd:complexType>
78 <xsd:sequence>
79 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
80 </xsd:sequence>
81 <xsd:attribute name="name" type="xsd:string" use="required" />
82 </xsd:complexType>
83 </xsd:element>
84 </xsd:choice>
85 </xsd:complexType>
86 </xsd:element>
87 </xsd:schema>
88 <resheader name="resmimetype">
89 <value>text/microsoft-resx</value>
90 </resheader>
91 <resheader name="version">
92 <value>1.3</value>
93 </resheader>
94 <resheader name="reader">
95 <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
96 </resheader>
97 <resheader name="writer">
98 <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
99 </resheader>
100 <data name="dataGrid.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
101 <value>False</value>
102 </data>
103 <data name="dataGrid.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
104 <value>Private</value>
105 </data>
106 <data name="dataGrid.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
107 <value>Private</value>
108 </data>
109 <data name="treeView.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
110 <value>Private</value>
111 </data>
112 <data name="treeView.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
113 <value>Private</value>
114 </data>
115 <data name="treeView.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
116 <value>False</value>
117 </data>
118 <data name="toolPanel.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
119 <value>False</value>
120 </data>
121 <data name="toolPanel.SnapToGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
122 <value>True</value>
123 </data>
124 <data name="toolPanel.DrawGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
125 <value>True</value>
126 </data>
127 <data name="toolPanel.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
128 <value>Private</value>
129 </data>
130 <data name="toolPanel.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
131 <value>Private</value>
132 </data>
133 <data name="toolPanel.GridSize" type="System.Drawing.Size, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
134 <value>8, 8</value>
135 </data>
136 <data name="findStopButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
137 <value>False</value>
138 </data>
139 <data name="findStopButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
140 <value>Private</value>
141 </data>
142 <data name="findStopButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
143 <value>Private</value>
144 </data>
145 <data name="findButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
146 <value>False</value>
147 </data>
148 <data name="findButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
149 <value>Private</value>
150 </data>
151 <data name="findButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
152 <value>Private</value>
153 </data>
154 <data name="searchTreeCheckBox.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
155 <value>False</value>
156 </data>
157 <data name="searchTreeCheckBox.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
158 <value>Private</value>
159 </data>
160 <data name="searchTreeCheckBox.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
161 <value>Private</value>
162 </data>
163 <data name="findTextBox.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
164 <value>Private</value>
165 </data>
166 <data name="findTextBox.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
167 <value>False</value>
168 </data>
169 <data name="findTextBox.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
170 <value>Private</value>
171 </data>
172 <data name="refreshButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
173 <value>False</value>
174 </data>
175 <data name="refreshButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
176 <value>Private</value>
177 </data>
178 <data name="refreshButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
179 <value>Private</value>
180 </data>
181 <data name="forwardButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
182 <value>False</value>
183 </data>
184 <data name="forwardButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
185 <value>Private</value>
186 </data>
187 <data name="forwardButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
188 <value>Private</value>
189 </data>
190 <data name="backButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
191 <value>False</value>
192 </data>
193 <data name="backButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
194 <value>Private</value>
195 </data>
196 <data name="backButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
197 <value>Private</value>
198 </data>
199 <data name="dataPanel.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
200 <value>False</value>
201 </data>
202 <data name="dataPanel.SnapToGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
203 <value>True</value>
204 </data>
205 <data name="dataPanel.DrawGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
206 <value>True</value>
207 </data>
208 <data name="dataPanel.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
209 <value>Private</value>
210 </data>
211 <data name="dataPanel.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
212 <value>Private</value>
213 </data>
214 <data name="dataPanel.GridSize" type="System.Drawing.Size, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
215 <value>8, 8</value>
216 </data>
217 <data name="splitter.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
218 <value>False</value>
219 </data>
220 <data name="splitter.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
221 <value>Private</value>
222 </data>
223 <data name="splitter.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
224 <value>Private</value>
225 </data>
226 <data name="gridLinkTip.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
227 <value>Private</value>
228 </data>
229 <data name="gridLinkTip.Location" type="System.Drawing.Point, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
230 <value>17, 17</value>
231 </data>
232 <data name="gridLinkTip.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
233 <value>Private</value>
234 </data>
235 <data name="$this.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
236 <value>False</value>
237 </data>
238 <data name="$this.Language" type="System.Globalization.CultureInfo, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
239 <value>(Default)</value>
240 </data>
241 <data name="$this.TrayLargeIcon" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
242 <value>False</value>
243 </data>
244 <data name="$this.Localizable" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
245 <value>False</value>
246 </data>
247 <data name="$this.GridSize" type="System.Drawing.Size, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
248 <value>8, 8</value>
249 </data>
250 <data name="$this.DrawGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
251 <value>True</value>
252 </data>
253 <data name="$this.TrayHeight" type="System.Int32, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
254 <value>80</value>
255 </data>
256 <data name="$this.SnapToGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
257 <value>True</value>
258 </data>
259 <data name="$this.Name">
260 <value>Inventory</value>
261 </data>
262 <data name="$this.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
263 <value>Private</value>
264 </data>
265</root> \ No newline at end of file
diff --git a/src/samples/Dtf/Inventory/components.cs b/src/samples/Dtf/Inventory/components.cs
new file mode 100644
index 00000000..c5147084
--- /dev/null
+++ b/src/samples/Dtf/Inventory/components.cs
@@ -0,0 +1,626 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.IO;
5using System.Data;
6using System.Text;
7using System.Collections;
8using System.Collections.Generic;
9using System.Globalization;
10using System.Windows.Forms;
11using Microsoft.Win32;
12using WixToolset.Dtf.WindowsInstaller;
13using View = WixToolset.Dtf.WindowsInstaller.View;
14
15namespace WixToolset.Dtf.Samples.Inventory
16{
17 /// <summary>
18 /// Provides inventory data about components of products installed on the system.
19 /// </summary>
20 public class ComponentsInventory : IInventoryDataProvider
21 {
22 private static object syncRoot = new object();
23
24 public ComponentsInventory()
25 {
26 }
27
28 public string Description
29 {
30 get { return "Components of installed products"; }
31 }
32
33 private Hashtable componentProductsMap;
34
35 public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback)
36 {
37 ArrayList nodes = new ArrayList();
38 componentProductsMap = new Hashtable();
39 foreach(ProductInstallation product in ProductInstallation.AllProducts)
40 {
41 string productName = MsiUtils.GetProductName(product.ProductCode);
42 statusCallback(nodes.Count, String.Format(@"Products\{0}", productName));
43
44 try
45 {
46 IntPtr hWnd = IntPtr.Zero;
47 Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
48 lock(syncRoot) // Only one Installer session can be active at a time
49 {
50 using (Session session = Installer.OpenProduct(product.ProductCode))
51 {
52 statusCallback(nodes.Count, String.Format(@"Products\{0}\Features", productName));
53 IList<string> features = session.Database.ExecuteStringQuery("SELECT `Feature` FROM `Feature`");
54 string[] featuresArray = new string[features.Count];
55 features.CopyTo(featuresArray, 0);
56 Array.Sort(featuresArray, 0, featuresArray.Length, StringComparer.OrdinalIgnoreCase);
57 foreach (string feature in featuresArray)
58 {
59 nodes.Add(String.Format(@"Products\{0}\Features\{1}", productName, feature));
60 }
61 statusCallback(nodes.Count, String.Format(@"Products\{0}\Components", productName));
62 nodes.Add(String.Format(@"Products\{0}\Components", productName));
63 IList<string> components = session.Database.ExecuteStringQuery("SELECT `ComponentId` FROM `Component`");
64 for (int i = 0; i < components.Count; i++)
65 {
66 string component = components[i];
67 if (component.Length > 0)
68 {
69 nodes.Add(String.Format(@"Products\{0}\Components\{1}", productName, component));
70 ArrayList sharingProducts = (ArrayList) componentProductsMap[component];
71 if (sharingProducts == null)
72 {
73 sharingProducts = new ArrayList();
74 componentProductsMap[component] = sharingProducts;
75 }
76 sharingProducts.Add(product.ProductCode);
77 }
78 if (i % 100 == 0) statusCallback(nodes.Count, null);
79 }
80 nodes.Add(String.Format(@"Products\{0}\Files", productName));
81 nodes.Add(String.Format(@"Products\{0}\Registry", productName));
82 statusCallback(nodes.Count, String.Empty);
83 }
84 }
85 }
86 catch(InstallerException) { }
87 }
88 statusCallback(nodes.Count, @"Products\...\Components\...\Sharing");
89 foreach (DictionaryEntry componentProducts in componentProductsMap)
90 {
91 string component = (string) componentProducts.Key;
92 ArrayList products = (ArrayList) componentProducts.Value;
93 if(products.Count > 1)
94 {
95 foreach(string productCode in products)
96 {
97 nodes.Add(String.Format(@"Products\{0}\Components\{1}\Sharing", MsiUtils.GetProductName(productCode), component));
98 }
99 }
100 }
101 statusCallback(nodes.Count, String.Empty);
102 return (string[]) nodes.ToArray(typeof(string));
103 }
104
105 public bool IsNodeSearchable(string searchRoot, string searchNode)
106 {
107 string[] rootPath = searchRoot.Split('\\');
108 string[] nodePath = searchNode.Split('\\');
109 if(rootPath.Length < 3 && nodePath.Length >= 3 && nodePath[0] == "Products" && nodePath[2] == "Components")
110 {
111 // When searching an entire product, don't search the "Components" subtree --
112 // it just has duplicate data from the Files and Registry table. And if you
113 // really want to know about the component, it's only a click away from
114 // those other tables.
115 return false;
116 }
117 return true;
118 }
119
120 public DataView GetData(string nodePath)
121 {
122 string[] path = nodePath.Split('\\');
123
124 if(path.Length == 4 && path[0] == "Products" && path[2] == "Features")
125 {
126 return GetFeatureComponentsData(MsiUtils.GetProductCode(path[1]), path[3]);
127 }
128 else if(path.Length == 3 && path[0] == "Products" && path[2] == "Components")
129 {
130 return GetProductComponentsData(MsiUtils.GetProductCode(path[1]));
131 }
132 else if(path.Length == 4 && path[0] == "Products" && path[2] == "Components")
133 {
134 return GetComponentData(MsiUtils.GetProductCode(path[1]), path[3]);
135 }
136 else if(path.Length == 5 && path[0] == "Products" && path[2] == "Components" && path[4] == "Sharing")
137 {
138 return GetComponentProductsData(path[3]);
139 }
140 else if(path.Length == 3 && path[0] == "Products" && path[2] == "Files")
141 {
142 return GetProductFilesData(MsiUtils.GetProductCode(path[1]));
143 }
144 else if(path.Length == 3 && path[0] == "Products" && path[2] == "Registry")
145 {
146 return GetProductRegistryData(MsiUtils.GetProductCode(path[1]));
147 }
148 return null;
149 }
150
151 public DataView GetComponentData(string productCode, string componentCode)
152 {
153 DataTable table = new DataTable("ProductComponentItems");
154 table.Locale = CultureInfo.InvariantCulture;
155 table.Columns.Add("ProductComponentItemsIsKey", typeof(bool));
156 table.Columns.Add("ProductComponentItemsKey", typeof(string));
157 table.Columns.Add("ProductComponentItemsPath", typeof(string));
158 table.Columns.Add("ProductComponentItemsExists", typeof(bool));
159 table.Columns.Add("ProductComponentItemsDbVersion", typeof(string));
160 table.Columns.Add("ProductComponentItemsInstalledVersion", typeof(string));
161 table.Columns.Add("ProductComponentItemsInstalledMatch", typeof(bool));
162 try
163 {
164 IntPtr hWnd = IntPtr.Zero;
165 Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
166 lock(syncRoot) // Only one Installer session can be active at a time
167 {
168 using(Session session = Installer.OpenProduct(productCode))
169 {
170 session.DoAction("CostInitialize");
171 session.DoAction("FileCost");
172 session.DoAction("CostFinalize");
173
174 foreach(object[] row in this.GetComponentFilesRows(productCode, componentCode, session, false))
175 {
176 table.Rows.Add(row);
177 }
178 foreach(object[] row in this.GetComponentRegistryRows(productCode, componentCode, session, false))
179 {
180 table.Rows.Add(row);
181 }
182 }
183 }
184 return new DataView(table, "", "ProductComponentItemsPath ASC", DataViewRowState.CurrentRows);
185 }
186 catch(InstallerException) { }
187 return null;
188 }
189
190 private object[][] GetComponentFilesRows(string productCode, string componentCode, Session session, bool includeComponent)
191 {
192 ArrayList rows = new ArrayList();
193 string componentPath = new ComponentInstallation(componentCode, productCode).Path;
194
195 string componentKey = (string) session.Database.ExecuteScalar(
196 "SELECT `Component` FROM `Component` WHERE `ComponentId` = '{0}'", componentCode);
197 if(componentKey == null) return null;
198 int attributes = Convert.ToInt32(session.Database.ExecuteScalar(
199 "SELECT `Attributes` FROM `Component` WHERE `Component` = '{0}'", componentKey));
200 bool registryKeyPath = (attributes & (int) ComponentAttributes.RegistryKeyPath) != 0;
201 if(!registryKeyPath && componentPath.Length > 0) componentPath = Path.GetDirectoryName(componentPath);
202 string keyPath = (string) session.Database.ExecuteScalar(
203 "SELECT `KeyPath` FROM `Component` WHERE `Component` = '{0}'", componentKey);
204
205 using (View view = session.Database.OpenView("SELECT `File`, `FileName`, `Version`, `Language`, " +
206 "`Attributes` FROM `File` WHERE `Component_` = '{0}'", componentKey))
207 {
208 view.Execute();
209
210 foreach (Record rec in view) using (rec)
211 {
212 string fileKey = (string) rec["File"];
213 bool isKey = !registryKeyPath && keyPath == fileKey;
214
215 string dbVersion = (string) rec["Version"];
216 bool versionedFile = dbVersion.Length != 0;
217 if(versionedFile)
218 {
219 string language = (string) rec["Language"];
220 if(language.Length > 0)
221 {
222 dbVersion = dbVersion + " (" + language + ")";
223 }
224 }
225 else if(session.Database.Tables.Contains("MsiFileHash"))
226 {
227 IList<int> hash = session.Database.ExecuteIntegerQuery("SELECT `HashPart1`, `HashPart2`, " +
228 "`HashPart3`, `HashPart4` FROM `MsiFileHash` WHERE `File_` = '{0}'", fileKey);
229 if(hash != null && hash.Count == 4)
230 {
231 dbVersion = this.GetFileHashString(hash);
232 }
233 }
234
235 string filePath = GetLongFileName((string) rec["FileName"]);
236 bool exists = false;
237 bool installedMatch = false;
238 string installedVersion = "";
239 if(!registryKeyPath && componentPath.Length > 0)
240 {
241 filePath = Path.Combine(componentPath, filePath);
242
243 if(File.Exists(filePath))
244 {
245 exists = true;
246 if(versionedFile)
247 {
248 installedVersion = Installer.GetFileVersion(filePath);
249 string language = Installer.GetFileLanguage(filePath);
250 if(language.Length > 0)
251 {
252 installedVersion = installedVersion + " (" + language + ")";
253 }
254 }
255 else
256 {
257 int[] hash = new int[4];
258 Installer.GetFileHash(filePath, hash);
259 installedVersion = this.GetFileHashString(hash);
260 }
261 installedMatch = installedVersion == dbVersion;
262 }
263 }
264
265 object[] row;
266 if(includeComponent) row = new object[] { isKey, fileKey, filePath, exists, dbVersion, installedVersion, installedMatch, componentCode };
267 else row = new object[] { isKey, fileKey, filePath, exists, dbVersion, installedVersion, installedMatch };
268 rows.Add(row);
269 }
270 }
271
272 return (object[][]) rows.ToArray(typeof(object[]));
273 }
274
275 private string GetLongFileName(string fileName)
276 {
277 string[] fileNames = fileName.Split('|');
278 return fileNames.Length == 1? fileNames[0] : fileNames[1];
279 }
280
281 private string GetFileHashString(IList<int> hash)
282 {
283 return String.Format("{0:X8}{1:X8}{2:X8}{3:X8}", (uint) hash[0], (uint) hash[1], (uint) hash[2], (uint) hash[3]);
284 }
285
286 private object[][] GetComponentRegistryRows(string productCode, string componentCode, Session session, bool includeComponent)
287 {
288 ArrayList rows = new ArrayList();
289 string componentPath = new ComponentInstallation(componentCode, productCode).Path;
290
291 string componentKey = (string) session.Database.ExecuteScalar(
292 "SELECT `Component` FROM `Component` WHERE `ComponentId` = '{0}'", componentCode);
293 if(componentKey == null) return null;
294 int attributes = Convert.ToInt32(session.Database.ExecuteScalar(
295 "SELECT `Attributes` FROM `Component` WHERE `Component` = '{0}'", componentKey));
296 bool registryKeyPath = (attributes & (int) ComponentAttributes.RegistryKeyPath) != 0;
297 if(!registryKeyPath && componentPath.Length > 0) componentPath = Path.GetDirectoryName(componentPath);
298 string keyPath = (string) session.Database.ExecuteScalar(
299 "SELECT `KeyPath` FROM `Component` WHERE `Component` = '{0}'", componentKey);
300
301 using (View view = session.Database.OpenView("SELECT `Registry`, `Root`, `Key`, `Name`, " +
302 "`Value` FROM `Registry` WHERE `Component_` = '{0}'", componentKey))
303 {
304 view.Execute();
305
306 foreach (Record rec in view) using (rec)
307 {
308 string regName = (string) rec["Name"];
309 if(regName == "-") continue; // Don't list deleted keys
310
311 string regTableKey = (string) rec["Registry"];
312 bool isKey = registryKeyPath && keyPath == regTableKey;
313 string regPath = this.GetRegistryPath(session, (RegistryRoot) Convert.ToInt32(rec["Root"]),
314 (string) rec["Key"], (string) rec["Name"]);
315
316 string dbValue;
317 using(Record formatRec = new Record(0))
318 {
319 formatRec[0] = rec["Value"];
320 dbValue = session.FormatRecord(formatRec);
321 }
322
323 string installedValue = this.GetRegistryValue(regPath);
324 bool exists = installedValue != null;
325 if(!exists) installedValue = "";
326 bool match = installedValue == dbValue;
327
328 object[] row;
329 if(includeComponent) row = new object[] { isKey, regTableKey, regPath, exists, dbValue, installedValue, match, componentCode };
330 else row = new object[] { isKey, regTableKey, regPath, exists, dbValue, installedValue, match };
331 rows.Add(row);
332 }
333 }
334
335 return (object[][]) rows.ToArray(typeof(object[]));
336 }
337
338 private string GetRegistryPath(Session session, RegistryRoot root, string key, string name)
339 {
340 bool allUsers = session.EvaluateCondition("ALLUSERS = 1", true);
341 string rootName = "????";
342 switch(root)
343 {
344 case RegistryRoot.LocalMachine : rootName = "HKLM"; break;
345 case RegistryRoot.CurrentUser : rootName = "HKCU"; break;
346 case RegistryRoot.Users : rootName = "HKU"; break;
347 case RegistryRoot.UserOrMachine: rootName = (allUsers ? "HKLM" : "HKCU"); break;
348 case RegistryRoot.ClassesRoot : rootName = (allUsers ? @"HKLM\Software\Classes" : @"HKCU\Software\Classes"); break;
349 // TODO: Technically, RegistryRoot.ClassesRoot should be under HKLM on NT4.
350 }
351 if(name.Length == 0) name = "(Default)";
352 if(name == "+" || name == "*") name = "";
353 else name = " : " + name;
354 using(Record formatRec = new Record(0))
355 {
356 formatRec[0] = String.Format(@"{0}\{1}{2}", rootName, key, name);
357 return session.FormatRecord(formatRec);
358 }
359 }
360
361 private string GetRegistryValue(string regPath)
362 {
363 string valueName = null;
364 int iColon = regPath.IndexOf(" : ", StringComparison.Ordinal) + 1;
365 if(iColon > 0)
366 {
367 valueName = regPath.Substring(iColon + 2);
368 regPath = regPath.Substring(0, iColon - 1);
369 }
370 if(valueName == "(Default)") valueName = "";
371
372 RegistryKey root;
373 if(regPath.StartsWith(@"HKLM\", StringComparison.Ordinal))
374 {
375 root = Registry.LocalMachine;
376 regPath = regPath.Substring(5);
377 }
378 else if(regPath.StartsWith(@"HKCU\", StringComparison.Ordinal))
379 {
380 root = Registry.CurrentUser;
381 regPath = regPath.Substring(5);
382 }
383 else if(regPath.StartsWith(@"HKU\", StringComparison.Ordinal))
384 {
385 root = Registry.Users;
386 regPath = regPath.Substring(4);
387 }
388 else return null;
389
390 using(RegistryKey regKey = root.OpenSubKey(regPath))
391 {
392 if(regKey != null)
393 {
394 if(valueName == null)
395 {
396 // Just checking for the existence of the key.
397 return "";
398 }
399 object value = regKey.GetValue(valueName);
400 if(value is string[])
401 {
402 value = String.Join("[~]", (string[]) value);
403 }
404 else if(value is int)
405 {
406 value = "#" + value.ToString();
407 }
408 else if(value is byte[])
409 {
410 byte[] valueBytes = (byte[]) value;
411 StringBuilder byteString = new StringBuilder("#x");
412 for(int i = 0; i < valueBytes.Length; i++)
413 {
414 byteString.Append(valueBytes[i].ToString("x2"));
415 }
416 value = byteString.ToString();
417 }
418 return (value != null ? value.ToString() : null);
419 }
420 }
421 return null;
422 }
423
424 public DataView GetProductFilesData(string productCode)
425 {
426 DataTable table = new DataTable("ProductFiles");
427 table.Locale = CultureInfo.InvariantCulture;
428 table.Columns.Add("ProductFilesIsKey", typeof(bool));
429 table.Columns.Add("ProductFilesKey", typeof(string));
430 table.Columns.Add("ProductFilesPath", typeof(string));
431 table.Columns.Add("ProductFilesExists", typeof(bool));
432 table.Columns.Add("ProductFilesDbVersion", typeof(string));
433 table.Columns.Add("ProductFilesInstalledVersion", typeof(string));
434 table.Columns.Add("ProductFilesInstalledMatch", typeof(bool));
435 table.Columns.Add("ProductFilesComponentID", typeof(string));
436 try
437 {
438 IntPtr hWnd = IntPtr.Zero;
439 Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
440 lock(syncRoot) // Only one Installer session can be active at a time
441 {
442 using(Session session = Installer.OpenProduct(productCode))
443 {
444 session.DoAction("CostInitialize");
445 session.DoAction("FileCost");
446 session.DoAction("CostFinalize");
447
448 foreach(string componentCode in session.Database.ExecuteStringQuery("SELECT `ComponentId` FROM `Component`"))
449 {
450 foreach(object[] row in this.GetComponentFilesRows(productCode, componentCode, session, true))
451 {
452 table.Rows.Add(row);
453 }
454 }
455 }
456 }
457 return new DataView(table, "", "ProductFilesPath ASC", DataViewRowState.CurrentRows);
458 }
459 catch(InstallerException) { }
460 return null;
461 }
462
463 public DataView GetProductRegistryData(string productCode)
464 {
465 DataTable table = new DataTable("ProductRegistry");
466 table.Locale = CultureInfo.InvariantCulture;
467 table.Columns.Add("ProductRegistryIsKey", typeof(bool));
468 table.Columns.Add("ProductRegistryKey", typeof(string));
469 table.Columns.Add("ProductRegistryPath", typeof(string));
470 table.Columns.Add("ProductRegistryExists", typeof(bool));
471 table.Columns.Add("ProductRegistryDbVersion", typeof(string));
472 table.Columns.Add("ProductRegistryInstalledVersion", typeof(string));
473 table.Columns.Add("ProductRegistryInstalledMatch", typeof(bool));
474 table.Columns.Add("ProductRegistryComponentID", typeof(string));
475 try
476 {
477 IntPtr hWnd = IntPtr.Zero;
478 Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
479 lock(syncRoot) // Only one Installer session can be active at a time
480 {
481 using(Session session = Installer.OpenProduct(productCode))
482 {
483 session.DoAction("CostInitialize");
484 session.DoAction("FileCost");
485 session.DoAction("CostFinalize");
486
487 foreach(string componentCode in session.Database.ExecuteStringQuery("SELECT `ComponentId` FROM `Component`"))
488 {
489 foreach(object[] row in this.GetComponentRegistryRows(productCode, componentCode, session, true))
490 {
491 table.Rows.Add(row);
492 }
493 }
494 }
495 }
496 return new DataView(table, "", "ProductRegistryPath ASC", DataViewRowState.CurrentRows);
497 }
498 catch(InstallerException) { }
499 return null;
500 }
501
502 public DataView GetComponentProductsData(string componentCode)
503 {
504 DataTable table = new DataTable("ComponentProducts");
505 table.Locale = CultureInfo.InvariantCulture;
506 table.Columns.Add("ComponentProductsProductName", typeof(string));
507 table.Columns.Add("ComponentProductsProductCode", typeof(string));
508 table.Columns.Add("ComponentProductsComponentPath", typeof(string));
509
510 if(this.componentProductsMap != null)
511 {
512 ArrayList componentProducts = (ArrayList) this.componentProductsMap[componentCode];
513 foreach(string productCode in componentProducts)
514 {
515 string productName = MsiUtils.GetProductName(productCode);
516 string componentPath = new ComponentInstallation(componentCode, productCode).Path;
517 table.Rows.Add(new object[] { productName, productCode, componentPath });
518 }
519 return new DataView(table, "", "ComponentProductsProductName ASC", DataViewRowState.CurrentRows);
520 }
521 return null;
522 }
523
524 public DataView GetProductComponentsData(string productCode)
525 {
526 DataTable table = new DataTable("ProductComponents");
527 table.Locale = CultureInfo.InvariantCulture;
528 table.Columns.Add("ProductComponentsComponentName", typeof(string));
529 table.Columns.Add("ProductComponentsComponentID", typeof(string));
530 table.Columns.Add("ProductComponentsInstallState", typeof(string));
531
532 try
533 {
534 IntPtr hWnd = IntPtr.Zero;
535 Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
536 lock(syncRoot) // Only one Installer session can be active at a time
537 {
538 using(Session session = Installer.OpenProduct(productCode))
539 {
540 session.DoAction("CostInitialize");
541 session.DoAction("FileCost");
542 session.DoAction("CostFinalize");
543
544 IList<string> componentsAndIds = session.Database.ExecuteStringQuery(
545 "SELECT `Component`, `ComponentId` FROM `Component`");
546
547 for (int i = 0; i < componentsAndIds.Count; i += 2)
548 {
549 if(componentsAndIds[i+1] == "Temporary Id") continue;
550 InstallState compState = session.Components[componentsAndIds[i]].CurrentState;
551 table.Rows.Add(new object[] { componentsAndIds[i], componentsAndIds[i+1],
552 (compState == InstallState.Advertised ? "Advertised" : compState.ToString())});
553 }
554 }
555 }
556 return new DataView(table, "", "ProductComponentsComponentName ASC", DataViewRowState.CurrentRows);
557 }
558 catch(InstallerException) { }
559 return null;
560 }
561
562 public DataView GetFeatureComponentsData(string productCode, string feature)
563 {
564 DataTable table = new DataTable("ProductFeatureComponents");
565 table.Locale = CultureInfo.InvariantCulture;
566 table.Columns.Add("ProductFeatureComponentsComponentName", typeof(string));
567 table.Columns.Add("ProductFeatureComponentsComponentID", typeof(string));
568
569 try
570 {
571 IntPtr hWnd = IntPtr.Zero;
572 Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
573 lock(syncRoot) // Only one Installer session can be active at a time
574 {
575 using(Session session = Installer.OpenProduct(productCode))
576 {
577 IList<string> componentsAndIds = session.Database.ExecuteStringQuery(
578 "SELECT `FeatureComponents`.`Component_`, " +
579 "`Component`.`ComponentId` FROM `FeatureComponents`, `Component` " +
580 "WHERE `FeatureComponents`.`Component_` = `Component`.`Component` " +
581 "AND `FeatureComponents`.`Feature_` = '{0}'", feature);
582 for (int i = 0; i < componentsAndIds.Count; i += 2)
583 {
584 table.Rows.Add(new object[] { componentsAndIds[i], componentsAndIds[i+1] });
585 }
586 }
587 }
588 return new DataView(table, "", "ProductFeatureComponentsComponentName ASC", DataViewRowState.CurrentRows);
589 }
590 catch(InstallerException) { }
591 return null;
592 }
593
594 public string GetLink(string nodePath, DataRow row)
595 {
596 string[] path = nodePath.Split('\\');
597
598 if(path.Length == 3 && path[0] == "Products" && path[2] == "Components")
599 {
600 string component = (string) row["ProductComponentsComponentID"];
601 return String.Format(@"Products\{0}\Components\{1}", path[1], component);
602 }
603 else if(path.Length == 4 && path[0] == "Products" && path[2] == "Features")
604 {
605 string component = (string) row["ProductFeatureComponentsComponentID"];
606 return String.Format(@"Products\{0}\Components\{1}", path[1], component);
607 }
608 else if(path.Length == 3 && path[0] == "Products" && path[2] == "Files")
609 {
610 string component = (string) row["ProductFilesComponentID"];
611 return String.Format(@"Products\{0}\Components\{1}", path[1], component);
612 }
613 else if(path.Length == 3 && path[0] == "Products" && path[2] == "Registry")
614 {
615 string component = (string) row["ProductRegistryComponentID"];
616 return String.Format(@"Products\{0}\Components\{1}", path[1], component);
617 }
618 else if(path.Length == 5 && path[0] == "Products" && path[2] == "Components" && path[4] == "Sharing")
619 {
620 string product = (string) row["ComponentProductsProductCode"];
621 return String.Format(@"Products\{0}\Components\{1}", MsiUtils.GetProductName(product), path[3]);
622 }
623 return null;
624 }
625 }
626}
diff --git a/src/samples/Dtf/Inventory/msiutils.cs b/src/samples/Dtf/Inventory/msiutils.cs
new file mode 100644
index 00000000..a345e194
--- /dev/null
+++ b/src/samples/Dtf/Inventory/msiutils.cs
@@ -0,0 +1,46 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.Collections;
5using WixToolset.Dtf.WindowsInstaller;
6
7
8namespace WixToolset.Dtf.Samples.Inventory
9{
10 public class MsiUtils
11 {
12 private static Hashtable productCodesToNames = new Hashtable();
13 private static Hashtable productNamesToCodes = new Hashtable();
14
15 public static string GetProductName(string productCode)
16 {
17 string productName = (string) productCodesToNames[productCode];
18 if(productName == null)
19 {
20 productName = new ProductInstallation(productCode).ProductName;
21 productName = productName.Replace('\\', ' ');
22 if(productNamesToCodes.Contains(productName))
23 {
24 string modifiedProductName = null;
25 for(int i = 2; i < Int32.MaxValue; i++)
26 {
27 modifiedProductName = productName + " [" + i + "]";
28 if(!productNamesToCodes.Contains(modifiedProductName)) break;
29 }
30 productName = modifiedProductName;
31 }
32 productCodesToNames[productCode] = productName;
33 productNamesToCodes[productName] = productCode;
34 }
35 return productName;
36 }
37
38 // Assumes GetProductName() has already been called for this product.
39 public static string GetProductCode(string productName)
40 {
41 return (string) productNamesToCodes[productName];
42 }
43
44 private MsiUtils() { }
45 }
46}
diff --git a/src/samples/Dtf/Inventory/patches.cs b/src/samples/Dtf/Inventory/patches.cs
new file mode 100644
index 00000000..f01a4798
--- /dev/null
+++ b/src/samples/Dtf/Inventory/patches.cs
@@ -0,0 +1,227 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.IO;
5using System.Security;
6using System.Data;
7using System.Collections;
8using System.Globalization;
9using System.Windows.Forms;
10using WixToolset.Dtf.WindowsInstaller;
11
12namespace WixToolset.Dtf.Samples.Inventory
13{
14 /// <summary>
15 /// Provides inventory data about patches installed on the system.
16 /// </summary>
17 public class PatchesInventory : IInventoryDataProvider
18 {
19 public PatchesInventory()
20 {
21 }
22
23 public string Description
24 {
25 get { return "Installed patches"; }
26 }
27
28 public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback)
29 {
30 ArrayList nodes = new ArrayList();
31 statusCallback(nodes.Count, @"Products\...\Patches");
32 foreach (ProductInstallation product in ProductInstallation.AllProducts)
33 {
34 string productName = MsiUtils.GetProductName(product.ProductCode);
35
36 bool addedRoot = false;
37 foreach (PatchInstallation productPatch in PatchInstallation.GetPatches(null, product.ProductCode, null, UserContexts.All, PatchStates.Applied))
38 {
39 if (!addedRoot) nodes.Add(String.Format(@"Products\{0}\Patches", productName));
40 nodes.Add(String.Format(@"Products\{0}\Patches\{1}", productName, productPatch.PatchCode));
41 }
42 }
43
44 statusCallback(nodes.Count, "Patches");
45
46 string[] allPatches = GetAllPatchesList();
47 if(allPatches.Length > 0)
48 {
49 nodes.Add("Patches");
50 foreach(string patchCode in allPatches)
51 {
52 nodes.Add(String.Format(@"Patches\{0}", patchCode));
53 nodes.Add(String.Format(@"Patches\{0}\Patched Products", patchCode));
54 }
55 statusCallback(nodes.Count, String.Empty);
56 }
57 return (string[]) nodes.ToArray(typeof(string));
58 }
59
60 public bool IsNodeSearchable(string searchRoot, string searchNode)
61 {
62 return true;
63 }
64
65 public DataView GetData(string nodePath)
66 {
67 string[] path = nodePath.Split('\\');
68
69 if(path.Length == 3 && path[0] == "Products" && path[2] == "Patches")
70 {
71 return this.GetProductPatchData(path[1]);
72 }
73 else if(path.Length == 4 && path[0] == "Products" && path[2] == "Patches")
74 {
75 return this.GetPatchData(path[3]);
76 }
77 else if(path.Length == 1 && path[0] == "Patches")
78 {
79 return this.GetAllPatchesData();
80 }
81 else if(path.Length == 2 && path[0] == "Patches")
82 {
83 return this.GetPatchData(path[1]);
84 }
85 else if(path.Length == 3 && path[0] == "Patches" && path[2] == "Patched Products")
86 {
87 return this.GetPatchTargetData(path[1]);
88 }
89 return null;
90 }
91
92 private string[] GetAllPatchesList()
93 {
94 ArrayList patchList = new ArrayList();
95 foreach(PatchInstallation patch in PatchInstallation.AllPatches)
96 {
97 if(!patchList.Contains(patch.PatchCode))
98 {
99 patchList.Add(patch.PatchCode);
100 }
101 }
102 string[] patchArray = (string[]) patchList.ToArray(typeof(string));
103 Array.Sort(patchArray, 0, patchArray.Length, StringComparer.Ordinal);
104 return patchArray;
105 }
106
107 private DataView GetAllPatchesData()
108 {
109 DataTable table = new DataTable("Patches");
110 table.Locale = CultureInfo.InvariantCulture;
111 table.Columns.Add("PatchesPatchCode", typeof(string));
112
113 foreach(string patchCode in GetAllPatchesList())
114 {
115 table.Rows.Add(new object[] { patchCode });
116 }
117 return new DataView(table, "", "PatchesPatchCode ASC", DataViewRowState.CurrentRows);
118 }
119
120 private DataView GetProductPatchData(string productCode)
121 {
122 DataTable table = new DataTable("ProductPatches");
123 table.Locale = CultureInfo.InvariantCulture;
124 table.Columns.Add("ProductPatchesPatchCode", typeof(string));
125
126 foreach(PatchInstallation patch in PatchInstallation.GetPatches(null, productCode, null, UserContexts.All, PatchStates.Applied))
127 {
128 table.Rows.Add(new object[] { patch.PatchCode });
129 }
130 return new DataView(table, "", "ProductPatchesPatchCode ASC", DataViewRowState.CurrentRows);
131 }
132
133 private DataView GetPatchData(string patchCode)
134 {
135 DataTable table = new DataTable("PatchProperties");
136 table.Locale = CultureInfo.InvariantCulture;
137 table.Columns.Add("PatchPropertiesProperty", typeof(string));
138 table.Columns.Add("PatchPropertiesValue", typeof(string));
139
140 table.Rows.Add(new object[] { "PatchCode", patchCode });
141
142 PatchInstallation patch = new PatchInstallation(patchCode, null);
143
144 string localPackage = null;
145 foreach(string property in new string[]
146 {
147 "InstallDate",
148 "LocalPackage",
149 "State",
150 "Transforms",
151 "Uninstallable",
152 })
153 {
154 try
155 {
156 string value = patch[property];
157 table.Rows.Add(new object[] { property, (value != null ? value : "") });
158 if(property == "LocalPackage") localPackage = value;
159 }
160 catch(InstallerException iex)
161 {
162 table.Rows.Add(new object[] { property, iex.Message });
163 }
164 catch(ArgumentException) { }
165 }
166
167 if(localPackage != null)
168 {
169 try
170 {
171 using(SummaryInfo patchSummaryInfo = new SummaryInfo(localPackage, false))
172 {
173 table.Rows.Add(new object[] { "Title", patchSummaryInfo.Title });
174 table.Rows.Add(new object[] { "Subject", patchSummaryInfo.Subject });
175 table.Rows.Add(new object[] { "Author", patchSummaryInfo.Author });
176 table.Rows.Add(new object[] { "Comments", patchSummaryInfo.Comments });
177 table.Rows.Add(new object[] { "TargetProductCodes", patchSummaryInfo.Template });
178 string obsoletedPatchCodes = patchSummaryInfo.RevisionNumber.Substring(patchSummaryInfo.RevisionNumber.IndexOf('}') + 1);
179 table.Rows.Add(new object[] { "ObsoletedPatchCodes", obsoletedPatchCodes });
180 table.Rows.Add(new object[] { "TransformNames", patchSummaryInfo.LastSavedBy });
181 }
182 }
183 catch(InstallerException) { }
184 catch(IOException) { }
185 catch(SecurityException) { }
186 }
187 return new DataView(table, "", "PatchPropertiesProperty ASC", DataViewRowState.CurrentRows);
188 }
189
190 private DataView GetPatchTargetData(string patchCode)
191 {
192 DataTable table = new DataTable("PatchTargets");
193 table.Locale = CultureInfo.InvariantCulture;
194 table.Columns.Add("PatchTargetsProductName", typeof(string));
195 table.Columns.Add("PatchTargetsProductCode", typeof(string));
196
197 foreach (PatchInstallation patch in PatchInstallation.GetPatches(patchCode, null, null, UserContexts.All, PatchStates.Applied))
198 {
199 if(patch.PatchCode == patchCode)
200 {
201 string productName = MsiUtils.GetProductName(patch.ProductCode);
202 table.Rows.Add(new object[] { productName, patch.ProductCode });
203 }
204 }
205 return new DataView(table, "", "PatchTargetsProductName ASC", DataViewRowState.CurrentRows);
206 }
207
208 public string GetLink(string nodePath, DataRow row)
209 {
210 string[] path = nodePath.Split('\\');
211
212 if(path.Length == 3 && path[0] == "Products" && path[2] == "Patches")
213 {
214 return String.Format(@"Patches\{0}", row["ProductPatchesPatchCode"]);
215 }
216 else if(path.Length == 1 && path[0] == "Patches")
217 {
218 return String.Format(@"Patches\{0}", row["PatchesPatchCode"]);
219 }
220 else if(path.Length == 3 && path[0] == "Patches" && path[2] == "Patched Products")
221 {
222 return String.Format(@"Products\{0}", MsiUtils.GetProductCode((string) row["PatchTargetsProductCode"]));
223 }
224 return null;
225 }
226 }
227}
diff --git a/src/samples/Dtf/Inventory/products.cs b/src/samples/Dtf/Inventory/products.cs
new file mode 100644
index 00000000..872c56c3
--- /dev/null
+++ b/src/samples/Dtf/Inventory/products.cs
@@ -0,0 +1,145 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.Data;
5using System.Globalization;
6using System.Collections;
7using System.Windows.Forms;
8using WixToolset.Dtf.WindowsInstaller;
9
10namespace WixToolset.Dtf.Samples.Inventory
11{
12 /// <summary>
13 /// Provides inventory data about products installed or advertised on the system.
14 /// </summary>
15 public class ProductsInventory : IInventoryDataProvider
16 {
17 public ProductsInventory()
18 {
19 }
20
21 public string Description
22 {
23 get { return "Installed products"; }
24 }
25
26 public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback)
27 {
28 statusCallback(0, "Products");
29 ArrayList nodes = new ArrayList();
30 nodes.Add("Products");
31 foreach(ProductInstallation product in ProductInstallation.AllProducts)
32 {
33 nodes.Add("Products\\" + MsiUtils.GetProductName(product.ProductCode));
34 }
35 statusCallback(nodes.Count, String.Empty);
36 return (string[]) nodes.ToArray(typeof(string));
37 }
38
39 public bool IsNodeSearchable(string searchRoot, string searchNode)
40 {
41 return true;
42 }
43
44 public DataView GetData(string nodePath)
45 {
46 string[] path = nodePath.Split('\\');
47
48 if(path.Length == 1 && path[0] == "Products")
49 {
50 return this.GetAllProductsData();
51 }
52 else if(path.Length == 2 && path[0] == "Products")
53 {
54 return this.GetProductData(MsiUtils.GetProductCode(path[1]));
55 }
56 return null;
57 }
58
59 private DataView GetAllProductsData()
60 {
61 DataTable table = new DataTable("Products");
62 table.Locale = CultureInfo.InvariantCulture;
63 table.Columns.Add("ProductsProductName", typeof(string));
64 table.Columns.Add("ProductsProductCode", typeof(string));
65
66 foreach (ProductInstallation product in ProductInstallation.AllProducts)
67 {
68 string productName = MsiUtils.GetProductName(product.ProductCode);
69 table.Rows.Add(new object[] { productName, product.ProductCode });
70 }
71 return new DataView(table, "", "ProductsProductName ASC", DataViewRowState.CurrentRows);
72 }
73
74 private DataView GetProductData(string productCode)
75 {
76 DataTable table = new DataTable("ProductProperties");
77 table.Locale = CultureInfo.InvariantCulture;
78 table.Columns.Add("ProductPropertiesProperty", typeof(string));
79 table.Columns.Add("ProductPropertiesValue", typeof(string));
80
81 // Add a fake "ProductCode" install property, just for display convenience.
82 table.Rows.Add(new object[] { "ProductCode", productCode });
83
84 ProductInstallation product = new ProductInstallation(productCode);
85
86 foreach(string property in new string[]
87 {
88 "AssignmentType",
89 "DiskPrompt",
90 "HelpLink",
91 "HelpTelephone",
92 "InstalledProductName",
93 "InstallDate",
94 "InstallLocation",
95 "InstallSource",
96 "Language",
97 "LastUsedSource",
98 "LastUsedType",
99 "LocalPackage",
100 "MediaPackagePath",
101 "PackageCode",
102 "PackageName",
103 "ProductIcon",
104 "ProductID",
105 "ProductName",
106 "Publisher",
107 "RegCompany",
108 "RegOwner",
109 "State",
110 "transforms",
111 "Uninstallable",
112 "UrlInfoAbout",
113 "UrlUpdateInfo",
114 "Version",
115 "VersionMinor",
116 "VersionMajor",
117 "VersionString"
118 })
119 {
120 try
121 {
122 string value = product[property];
123 table.Rows.Add(new object[] { property, (value != null ? value : "") });
124 }
125 catch(InstallerException iex)
126 {
127 table.Rows.Add(new object[] { property, iex.Message });
128 }
129 catch(ArgumentException) { }
130 }
131 return new DataView(table, "", "ProductPropertiesProperty ASC", DataViewRowState.CurrentRows);
132 }
133
134 public string GetLink(string nodePath, DataRow row)
135 {
136 string[] path = nodePath.Split('\\');
137
138 if(path.Length == 1 && path[0] == "Products")
139 {
140 return String.Format(@"Products\{0}", MsiUtils.GetProductName((string) row["ProductsProductCode"]));
141 }
142 return null;
143 }
144 }
145}
diff --git a/src/samples/Dtf/Inventory/xp.manifest b/src/samples/Dtf/Inventory/xp.manifest
new file mode 100644
index 00000000..34d61fea
--- /dev/null
+++ b/src/samples/Dtf/Inventory/xp.manifest
@@ -0,0 +1,15 @@
1<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
6 <dependency>
7 <dependentAssembly>
8 <assemblyIdentity type="win32"
9 name="Microsoft.Windows.Common-Controls"
10 version="6.0.0.0" language="*"
11 processorArchitecture="X86"
12 publicKeyToken="6595b64144ccf1df" />
13 </dependentAssembly>
14 </dependency>
15</assembly>
diff --git a/src/samples/Dtf/ManagedCA/AssemblyInfo.cs b/src/samples/Dtf/ManagedCA/AssemblyInfo.cs
new file mode 100644
index 00000000..75be36b2
--- /dev/null
+++ b/src/samples/Dtf/ManagedCA/AssemblyInfo.cs
@@ -0,0 +1,5 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System.Reflection;
4
5[assembly: AssemblyDescription("Sample managed custom actions")]
diff --git a/src/samples/Dtf/ManagedCA/ManagedCA.csproj b/src/samples/Dtf/ManagedCA/ManagedCA.csproj
new file mode 100644
index 00000000..7fb32ad4
--- /dev/null
+++ b/src/samples/Dtf/ManagedCA/ManagedCA.csproj
@@ -0,0 +1,33 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 <PropertyGroup>
4 <ProjectGuid>{DB9E5F02-8241-440A-9B60-980EB5B42B13}</ProjectGuid>
5 <OutputType>Library</OutputType>
6 <RootNamespace>WixToolset.Dtf.Samples.ManagedCA</RootNamespace>
7 <AssemblyName>WixToolset.Dtf.Samples.ManagedCA</AssemblyName>
8 <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
9 <RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
10 </PropertyGroup>
11 <ItemGroup>
12 <Compile Include="AssemblyInfo.cs" />
13 <Compile Include="SampleCAs.cs" />
14 </ItemGroup>
15 <ItemGroup>
16 <Reference Include="System" />
17 </ItemGroup>
18 <ItemGroup>
19 <ProjectReference Include="..\..\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj">
20 <Project>{24121677-0ed0-41b5-833f-1b9a18e87bf4}</Project>
21 <Name>WixToolset.Dtf.WindowsInstaller</Name>
22 </ProjectReference>
23 </ItemGroup>
24
25 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
26
27<!--
28 <PropertyGroup>
29 <PostBuildEvent>"$(TargetDir)..\x86\MakeSfxCA.exe" "$(TargetPath)" "$(TargetDir)SfxCA.dll" "$(IntermediateOutputPath)$(TargetFileName)" WixToolset.Dtf.WindowsInstaller.dll="$(TargetDir)WixToolset.Dtf.WindowsInstaller.dll" testsub\SampleCAs.cs="$(ProjectDir)\SampleCAs.cs"</PostBuildEvent>
30 </PropertyGroup>
31-->
32
33</Project>
diff --git a/src/samples/Dtf/ManagedCA/SampleCAs.cs b/src/samples/Dtf/ManagedCA/SampleCAs.cs
new file mode 100644
index 00000000..645131c8
--- /dev/null
+++ b/src/samples/Dtf/ManagedCA/SampleCAs.cs
@@ -0,0 +1,127 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Samples.ManagedCA
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using WixToolset.Dtf.WindowsInstaller;
9
10 public class SampleCAs
11 {
12 [CustomAction]
13 public static ActionResult SampleCA1(Session session)
14 {
15 using (Record msgRec = new Record(0))
16 {
17 msgRec[0] = "Hello from SampleCA1!" +
18 "\r\nCLR version is v" + Environment.Version;
19 session.Message(InstallMessage.Info, msgRec);
20 session.Message(InstallMessage.User, msgRec);
21 }
22
23 session.Log("Testing summary info...");
24 SummaryInfo summInfo = session.Database.SummaryInfo;
25 session.Log("MSI PackageCode = {0}", summInfo.RevisionNumber);
26 session.Log("MSI ModifyDate = {0}", summInfo.LastSaveTime);
27
28 string testProp = session["SampleCATest"];
29 session.Log("Simple property test: [SampleCATest]={0}.", testProp);
30
31 session.Log("Testing subdirectory extraction...");
32 string testFilePath = "testsub\\SampleCAs.cs";
33 if (!File.Exists(testFilePath))
34 {
35 session.Log("Subdirectory extraction failed. File not found: " + testFilePath);
36 return ActionResult.Failure;
37 }
38 else
39 {
40 session.Log("Found file extracted in subdirectory.");
41 }
42
43 session.Log("Testing record stream extraction...");
44 string tempFile = null;
45 try
46 {
47 tempFile = Path.GetTempFileName();
48 using (View binView = session.Database.OpenView(
49 "SELECT `Binary`.`Data` FROM `Binary`, `CustomAction` " +
50 "WHERE `CustomAction`.`Target` = 'SampleCA1' AND " +
51 "`CustomAction`.`Source` = `Binary`.`Name`"))
52 {
53 binView.Execute();
54 using (Record binRec = binView.Fetch())
55 {
56 binRec.GetStream(1, tempFile);
57 }
58 }
59
60 session.Log("CA binary file size: {0}", new FileInfo(tempFile).Length);
61 string binFileVersion = Installer.GetFileVersion(tempFile);
62 session.Log("CA binary file version: {0}", binFileVersion);
63 }
64 finally
65 {
66 if (tempFile != null && File.Exists(tempFile))
67 {
68 File.Delete(tempFile);
69 }
70 }
71
72 session.Log("Testing record stream reading...");
73 using (View binView2 = session.Database.OpenView("SELECT `Data` FROM `Binary` WHERE `Name` = 'TestData'"))
74 {
75 binView2.Execute();
76 using (Record binRec2 = binView2.Fetch())
77 {
78 Stream stream = binRec2.GetStream("Data");
79 string testData = new StreamReader(stream, System.Text.Encoding.UTF8).ReadToEnd();
80 session.Log("Test data: " + testData);
81 }
82 }
83
84 session.Log("Listing components");
85 using (View compView = session.Database.OpenView(
86 "SELECT `Component` FROM `Component`"))
87 {
88 compView.Execute();
89 foreach (Record compRec in compView)
90 {
91 using (compRec)
92 {
93 session.Log("\t{0}", compRec["Component"]);
94 }
95 }
96 }
97
98 session.Log("Testing the ability to access an external MSI database...");
99 string tempDbFile = Path.GetTempFileName();
100 using (Database tempDb = new Database(tempDbFile, DatabaseOpenMode.CreateDirect))
101 {
102 // Just create an empty database.
103 }
104 using (Database tempDb2 = new Database(tempDbFile))
105 {
106 // See if we can open and query the database.
107 IList<string> tables = tempDb2.ExecuteStringQuery("SELECT `Name` FROM `_Tables`");
108 session.Log("Found " + tables.Count + " tables in the newly created database.");
109 }
110 File.Delete(tempDbFile);
111
112 return ActionResult.Success;
113 }
114
115 [CustomAction("SampleCA2")]
116 public static ActionResult SampleCustomAction2(Session session)
117 {
118 using (Record msgRec = new Record(0))
119 {
120 msgRec[0] = "Hello from SampleCA2!";
121 session.Message(InstallMessage.Info, msgRec);
122 session.Message(InstallMessage.User, msgRec);
123 }
124 return ActionResult.UserExit;
125 }
126 }
127}
diff --git a/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs
new file mode 100644
index 00000000..76ff79b3
--- /dev/null
+++ b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs
@@ -0,0 +1,711 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Tools.MakeSfxCA
4{
5 using System;
6 using System.IO;
7 using System.Collections.Generic;
8 using System.Security;
9 using System.Text;
10 using System.Reflection;
11 using Compression;
12 using Compression.Cab;
13 using Resources;
14 using ResourceCollection = Resources.ResourceCollection;
15
16 /// <summary>
17 /// Command-line tool for building self-extracting custom action packages.
18 /// Appends cabbed CA binaries to SfxCA.dll and fixes up the result's
19 /// entry-points and file version to look like the CA module.
20 /// </summary>
21 public static class MakeSfxCA
22 {
23 private const string REQUIRED_WI_ASSEMBLY = "WixToolset.Dtf.WindowsInstaller.dll";
24
25 private static TextWriter log;
26
27 /// <summary>
28 /// Prints usage text for the tool.
29 /// </summary>
30 /// <param name="w">Console text writer.</param>
31 public static void Usage(TextWriter w)
32 {
33 w.WriteLine("Deployment Tools Foundation custom action packager version {0}",
34 Assembly.GetExecutingAssembly().GetName().Version);
35 w.WriteLine("Copyright (C) .NET Foundation and contributors. All rights reserved.");
36 w.WriteLine();
37 w.WriteLine("Usage: MakeSfxCA <outputca.dll> SfxCA.dll <inputca.dll> [support files ...]");
38 w.WriteLine();
39 w.WriteLine("Makes a self-extracting managed MSI CA or UI DLL package.");
40 w.WriteLine("Support files must include " + MakeSfxCA.REQUIRED_WI_ASSEMBLY);
41 w.WriteLine("Support files optionally include CustomAction.config/EmbeddedUI.config");
42 }
43
44 /// <summary>
45 /// Runs the MakeSfxCA command-line tool.
46 /// </summary>
47 /// <param name="args">Command-line arguments.</param>
48 /// <returns>0 on success, nonzero on failure.</returns>
49 public static int Main(string[] args)
50 {
51 if (args.Length < 3)
52 {
53 Usage(Console.Out);
54 return 1;
55 }
56
57 var output = args[0];
58 var sfxDll = args[1];
59 var inputs = new string[args.Length - 2];
60 Array.Copy(args, 2, inputs, 0, inputs.Length);
61
62 try
63 {
64 Build(output, sfxDll, inputs, Console.Out);
65 return 0;
66 }
67 catch (ArgumentException ex)
68 {
69 Console.Error.WriteLine("Error: Invalid argument: " + ex.Message);
70 return 1;
71 }
72 catch (FileNotFoundException ex)
73 {
74 Console.Error.WriteLine("Error: Cannot find file: " + ex.Message);
75 return 1;
76 }
77 catch (Exception ex)
78 {
79 Console.Error.WriteLine("Error: Unexpected error: " + ex);
80 return 1;
81 }
82 }
83
84 /// <summary>
85 /// Packages up all the inputs to the output location.
86 /// </summary>
87 /// <exception cref="Exception">Various exceptions are thrown
88 /// if things go wrong.</exception>
89 public static void Build(string output, string sfxDll, IList<string> inputs, TextWriter log)
90 {
91 MakeSfxCA.log = log;
92
93 if (string.IsNullOrEmpty(output))
94 {
95 throw new ArgumentNullException("output");
96 }
97
98 if (string.IsNullOrEmpty(sfxDll))
99 {
100 throw new ArgumentNullException("sfxDll");
101 }
102
103 if (inputs == null || inputs.Count == 0)
104 {
105 throw new ArgumentNullException("inputs");
106 }
107
108 if (!File.Exists(sfxDll))
109 {
110 throw new FileNotFoundException(sfxDll);
111 }
112
113 var customActionAssembly = inputs[0];
114 if (!File.Exists(customActionAssembly))
115 {
116 throw new FileNotFoundException(customActionAssembly);
117 }
118
119 inputs = MakeSfxCA.SplitList(inputs);
120
121 var inputsMap = MakeSfxCA.GetPackFileMap(inputs);
122
123 var foundWIAssembly = false;
124 foreach (var input in inputsMap.Keys)
125 {
126 if (string.Compare(input, MakeSfxCA.REQUIRED_WI_ASSEMBLY,
127 StringComparison.OrdinalIgnoreCase) == 0)
128 {
129 foundWIAssembly = true;
130 }
131 }
132
133 if (!foundWIAssembly)
134 {
135 throw new ArgumentException(MakeSfxCA.REQUIRED_WI_ASSEMBLY +
136 " must be included in the list of support files. " +
137 "If using the MSBuild targets, make sure the assembly reference " +
138 "has the Private (Copy Local) flag set.");
139 }
140
141 MakeSfxCA.ResolveDependentAssemblies(inputsMap, Path.GetDirectoryName(customActionAssembly));
142
143 var entryPoints = MakeSfxCA.FindEntryPoints(customActionAssembly);
144 var uiClass = MakeSfxCA.FindEmbeddedUIClass(customActionAssembly);
145
146 if (entryPoints.Count == 0 && uiClass == null)
147 {
148 throw new ArgumentException(
149 "No CA or UI entry points found in module: " + customActionAssembly);
150 }
151 else if (entryPoints.Count > 0 && uiClass != null)
152 {
153 throw new NotSupportedException(
154 "CA and UI entry points cannot be in the same assembly: " + customActionAssembly);
155 }
156
157 var dir = Path.GetDirectoryName(output);
158 if (dir.Length > 0 && !Directory.Exists(dir))
159 {
160 Directory.CreateDirectory(dir);
161 }
162
163 using (Stream outputStream = File.Create(output))
164 {
165 MakeSfxCA.WriteEntryModule(sfxDll, outputStream, entryPoints, uiClass);
166 }
167
168 MakeSfxCA.CopyVersionResource(customActionAssembly, output);
169
170 MakeSfxCA.PackInputFiles(output, inputsMap);
171
172 log.WriteLine("MakeSfxCA finished: " + new FileInfo(output).FullName);
173 }
174
175 /// <summary>
176 /// Splits any list items delimited by semicolons into separate items.
177 /// </summary>
178 /// <param name="list">Read-only input list.</param>
179 /// <returns>New list with resulting split items.</returns>
180 private static IList<string> SplitList(IList<string> list)
181 {
182 var newList = new List<string>(list.Count);
183
184 foreach (var item in list)
185 {
186 if (!string.IsNullOrEmpty(item))
187 {
188 foreach (var splitItem in item.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
189 {
190 newList.Add(splitItem);
191 }
192 }
193 }
194
195 return newList;
196 }
197
198 /// <summary>
199 /// Sets up a reflection-only assembly-resolve-handler to handle loading dependent assemblies during reflection.
200 /// </summary>
201 /// <param name="inputFiles">List of input files which include non-GAC dependent assemblies.</param>
202 /// <param name="inputDir">Directory to auto-locate additional dependent assemblies.</param>
203 /// <remarks>
204 /// Also searches the assembly's directory for unspecified dependent assemblies, and adds them
205 /// to the list of input files if found.
206 /// </remarks>
207 private static void ResolveDependentAssemblies(IDictionary<string, string> inputFiles, string inputDir)
208 {
209 AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += delegate(object sender, ResolveEventArgs args)
210 {
211 AssemblyName resolveName = new AssemblyName(args.Name);
212 Assembly assembly = null;
213
214 // First, try to find the assembly in the list of input files.
215 foreach (var inputFile in inputFiles.Values)
216 {
217 var inputName = Path.GetFileNameWithoutExtension(inputFile);
218 var inputExtension = Path.GetExtension(inputFile);
219 if (string.Equals(inputName, resolveName.Name, StringComparison.OrdinalIgnoreCase) &&
220 (string.Equals(inputExtension, ".dll", StringComparison.OrdinalIgnoreCase) ||
221 string.Equals(inputExtension, ".exe", StringComparison.OrdinalIgnoreCase)))
222 {
223 assembly = MakeSfxCA.TryLoadDependentAssembly(inputFile);
224
225 if (assembly != null)
226 {
227 break;
228 }
229 }
230 }
231
232 // Second, try to find the assembly in the input directory.
233 if (assembly == null && inputDir != null)
234 {
235 string assemblyPath = null;
236 if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".dll"))
237 {
238 assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".dll";
239 }
240 else if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".exe"))
241 {
242 assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".exe";
243 }
244
245 if (assemblyPath != null)
246 {
247 assembly = MakeSfxCA.TryLoadDependentAssembly(assemblyPath);
248
249 if (assembly != null)
250 {
251 // Add this detected dependency to the list of files to be packed.
252 inputFiles.Add(Path.GetFileName(assemblyPath), assemblyPath);
253 }
254 }
255 }
256
257 // Third, try to load the assembly from the GAC.
258 if (assembly == null)
259 {
260 try
261 {
262 assembly = Assembly.ReflectionOnlyLoad(args.Name);
263 }
264 catch (FileNotFoundException)
265 {
266 }
267 }
268
269 if (assembly != null)
270 {
271 if (string.Equals(assembly.GetName().ToString(), resolveName.ToString()))
272 {
273 log.WriteLine(" Loaded dependent assembly: " + assembly.Location);
274 return assembly;
275 }
276
277 log.WriteLine(" Warning: Loaded mismatched dependent assembly: " + assembly.Location);
278 log.WriteLine(" Loaded assembly : " + assembly.GetName());
279 log.WriteLine(" Reference assembly: " + resolveName);
280 }
281 else
282 {
283 log.WriteLine(" Error: Dependent assembly not supplied: " + resolveName);
284 }
285
286 return null;
287 };
288 }
289
290 /// <summary>
291 /// Attempts a reflection-only load of a dependent assembly, logging the error if the load fails.
292 /// </summary>
293 /// <param name="assemblyPath">Path of the assembly file to laod.</param>
294 /// <returns>Loaded assembly, or null if the load failed.</returns>
295 private static Assembly TryLoadDependentAssembly(string assemblyPath)
296 {
297 Assembly assembly = null;
298 try
299 {
300 assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath);
301 }
302 catch (IOException ex)
303 {
304 log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message);
305 }
306 catch (BadImageFormatException ex)
307 {
308 log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message);
309 }
310 catch (SecurityException ex)
311 {
312 log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message);
313 }
314
315 return assembly;
316 }
317
318 /// <summary>
319 /// Searches the types in the input assembly for a type that implements IEmbeddedUI.
320 /// </summary>
321 /// <param name="module"></param>
322 /// <returns></returns>
323 private static string FindEmbeddedUIClass(string module)
324 {
325 log.WriteLine("Searching for an embedded UI class in {0}", Path.GetFileName(module));
326
327 string uiClass = null;
328
329 var assembly = Assembly.ReflectionOnlyLoadFrom(module);
330
331 foreach (var type in assembly.GetExportedTypes())
332 {
333 if (!type.IsAbstract)
334 {
335 foreach (var interfaceType in type.GetInterfaces())
336 {
337 if (interfaceType.FullName == "WixToolset.Dtf.WindowsInstaller.IEmbeddedUI")
338 {
339 if (uiClass == null)
340 {
341 uiClass = assembly.GetName().Name + "!" + type.FullName;
342 }
343 else
344 {
345 throw new ArgumentException("Multiple IEmbeddedUI implementations found.");
346 }
347 }
348 }
349 }
350 }
351
352 return uiClass;
353 }
354
355 /// <summary>
356 /// Reflects on an input CA module to locate custom action entry-points.
357 /// </summary>
358 /// <param name="module">Assembly module with CA entry-points.</param>
359 /// <returns>Mapping from entry-point names to assembly!class.method paths.</returns>
360 private static IDictionary<string, string> FindEntryPoints(string module)
361 {
362 log.WriteLine("Searching for custom action entry points " +
363 "in {0}", Path.GetFileName(module));
364
365 var entryPoints = new Dictionary<string, string>();
366
367 var assembly = Assembly.ReflectionOnlyLoadFrom(module);
368
369 foreach (var type in assembly.GetExportedTypes())
370 {
371 foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static))
372 {
373 var entryPointName = MakeSfxCA.GetEntryPoint(method);
374 if (entryPointName != null)
375 {
376 var entryPointPath = string.Format(
377 "{0}!{1}.{2}",
378 Path.GetFileNameWithoutExtension(module),
379 type.FullName,
380 method.Name);
381 entryPoints.Add(entryPointName, entryPointPath);
382
383 log.WriteLine(" {0}={1}", entryPointName, entryPointPath);
384 }
385 }
386 }
387
388 return entryPoints;
389 }
390
391 /// <summary>
392 /// Check for a CustomActionAttribute and return the entrypoint name for the method if it is a CA method.
393 /// </summary>
394 /// <param name="method">A public static method.</param>
395 /// <returns>Entrypoint name for the method as specified by the custom action attribute or just the method name,
396 /// or null if the method is not a custom action method.</returns>
397 private static string GetEntryPoint(MethodInfo method)
398 {
399 IList<CustomAttributeData> attributes;
400 try
401 {
402 attributes = CustomAttributeData.GetCustomAttributes(method);
403 }
404 catch (FileLoadException)
405 {
406 // Already logged load failures in the assembly-resolve-handler.
407 return null;
408 }
409
410 foreach (CustomAttributeData attribute in attributes)
411 {
412 if (attribute.ToString().StartsWith(
413 "[WixToolset.Dtf.WindowsInstaller.CustomActionAttribute(",
414 StringComparison.Ordinal))
415 {
416 string entryPointName = null;
417 foreach (var argument in attribute.ConstructorArguments)
418 {
419 // The entry point name is the first positional argument, if specified.
420 entryPointName = (string) argument.Value;
421 break;
422 }
423
424 if (string.IsNullOrEmpty(entryPointName))
425 {
426 entryPointName = method.Name;
427 }
428
429 return entryPointName;
430 }
431 }
432
433 return null;
434 }
435
436 /// <summary>
437 /// Counts the number of template entrypoints in SfxCA.dll.
438 /// </summary>
439 /// <remarks>
440 /// Depending on the requirements, SfxCA.dll might be built with
441 /// more entrypoints than the default.
442 /// </remarks>
443 private static int GetEntryPointSlotCount(byte[] fileBytes, string entryPointFormat)
444 {
445 for (var count = 0; ; count++)
446 {
447 var templateName = string.Format(entryPointFormat, count);
448 var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName);
449
450 var nameOffset = FindBytes(fileBytes, templateAsciiBytes);
451 if (nameOffset < 0)
452 {
453 return count;
454 }
455 }
456 }
457
458 /// <summary>
459 /// Writes a modified version of SfxCA.dll to the output stream,
460 /// with the template entry-points mapped to the CA entry-points.
461 /// </summary>
462 /// <remarks>
463 /// To avoid having to recompile SfxCA.dll for every different set of CAs,
464 /// this method looks for a preset number of template entry-points in the
465 /// binary file and overwrites their entrypoint name and string data with
466 /// CA-specific values.
467 /// </remarks>
468 private static void WriteEntryModule(
469 string sfxDll, Stream outputStream, IDictionary<string, string> entryPoints, string uiClass)
470 {
471 log.WriteLine("Modifying SfxCA.dll stub");
472
473 byte[] fileBytes;
474 using (var readStream = File.OpenRead(sfxDll))
475 {
476 fileBytes = new byte[(int) readStream.Length];
477 readStream.Read(fileBytes, 0, fileBytes.Length);
478 }
479
480 const string ENTRYPOINT_FORMAT = "CustomActionEntryPoint{0:d03}";
481 const int MAX_ENTRYPOINT_NAME = 72;
482 const int MAX_ENTRYPOINT_PATH = 160;
483 //var emptyBytes = new byte[0];
484
485 var slotCount = MakeSfxCA.GetEntryPointSlotCount(fileBytes, ENTRYPOINT_FORMAT);
486
487 if (slotCount == 0)
488 {
489 throw new ArgumentException("Invalid SfxCA.dll file.");
490 }
491
492 if (entryPoints.Count > slotCount)
493 {
494 throw new ArgumentException(string.Format(
495 "The custom action assembly has {0} entrypoints, which is more than the maximum ({1}). " +
496 "Refactor the custom actions or add more entrypoint slots in SfxCA\\EntryPoints.h.",
497 entryPoints.Count, slotCount));
498 }
499
500 var slotSort = new string[slotCount];
501 for (var i = 0; i < slotCount - entryPoints.Count; i++)
502 {
503 slotSort[i] = string.Empty;
504 }
505
506 entryPoints.Keys.CopyTo(slotSort, slotCount - entryPoints.Count);
507 Array.Sort<string>(slotSort, slotCount - entryPoints.Count, entryPoints.Count, StringComparer.Ordinal);
508
509 for (var i = 0; ; i++)
510 {
511 var templateName = string.Format(ENTRYPOINT_FORMAT, i);
512 var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName);
513 var templateUniBytes = Encoding.Unicode.GetBytes(templateName);
514
515 var nameOffset = MakeSfxCA.FindBytes(fileBytes, templateAsciiBytes);
516 if (nameOffset < 0)
517 {
518 break;
519 }
520
521 var pathOffset = MakeSfxCA.FindBytes(fileBytes, templateUniBytes);
522 if (pathOffset < 0)
523 {
524 break;
525 }
526
527 var entryPointName = slotSort[i];
528 var entryPointPath = entryPointName.Length > 0 ?
529 entryPoints[entryPointName] : string.Empty;
530
531 if (entryPointName.Length > MAX_ENTRYPOINT_NAME)
532 {
533 throw new ArgumentException(string.Format(
534 "Entry point name exceeds limit of {0} characters: {1}",
535 MAX_ENTRYPOINT_NAME,
536 entryPointName));
537 }
538
539 if (entryPointPath.Length > MAX_ENTRYPOINT_PATH)
540 {
541 throw new ArgumentException(string.Format(
542 "Entry point path exceeds limit of {0} characters: {1}",
543 MAX_ENTRYPOINT_PATH,
544 entryPointPath));
545 }
546
547 var replaceNameBytes = Encoding.ASCII.GetBytes(entryPointName);
548 var replacePathBytes = Encoding.Unicode.GetBytes(entryPointPath);
549
550 MakeSfxCA.ReplaceBytes(fileBytes, nameOffset, MAX_ENTRYPOINT_NAME, replaceNameBytes);
551 MakeSfxCA.ReplaceBytes(fileBytes, pathOffset, MAX_ENTRYPOINT_PATH * 2, replacePathBytes);
552 }
553
554 if (entryPoints.Count == 0 && uiClass != null)
555 {
556 // Remove the zzz prefix from exported EmbeddedUI entry-points.
557 foreach (var export in new string[] { "InitializeEmbeddedUI", "EmbeddedUIHandler", "ShutdownEmbeddedUI" })
558 {
559 var exportNameBytes = Encoding.ASCII.GetBytes("zzz" + export);
560
561 var exportOffset = MakeSfxCA.FindBytes(fileBytes, exportNameBytes);
562 if (exportOffset < 0)
563 {
564 throw new ArgumentException("Input SfxCA.dll does not contain exported entry-point: " + export);
565 }
566
567 var replaceNameBytes = Encoding.ASCII.GetBytes(export);
568 MakeSfxCA.ReplaceBytes(fileBytes, exportOffset, exportNameBytes.Length, replaceNameBytes);
569 }
570
571 if (uiClass.Length > MAX_ENTRYPOINT_PATH)
572 {
573 throw new ArgumentException(string.Format(
574 "UI class full name exceeds limit of {0} characters: {1}",
575 MAX_ENTRYPOINT_PATH,
576 uiClass));
577 }
578
579 var templateBytes = Encoding.Unicode.GetBytes("InitializeEmbeddedUI_FullClassName");
580 var replaceBytes = Encoding.Unicode.GetBytes(uiClass);
581
582 // Fill in the embedded UI implementor class so the proxy knows which one to load.
583 var replaceOffset = MakeSfxCA.FindBytes(fileBytes, templateBytes);
584 if (replaceOffset >= 0)
585 {
586 MakeSfxCA.ReplaceBytes(fileBytes, replaceOffset, MAX_ENTRYPOINT_PATH * 2, replaceBytes);
587 }
588 }
589
590 outputStream.Write(fileBytes, 0, fileBytes.Length);
591 }
592
593 /// <summary>
594 /// Searches for a sub-array of bytes within a larger array of bytes.
595 /// </summary>
596 private static int FindBytes(byte[] source, byte[] find)
597 {
598 for (var i = 0; i < source.Length; i++)
599 {
600 int j;
601 for (j = 0; j < find.Length; j++)
602 {
603 if (source[i + j] != find[j])
604 {
605 break;
606 }
607 }
608
609 if (j == find.Length)
610 {
611 return i;
612 }
613 }
614
615 return -1;
616 }
617
618 /// <summary>
619 /// Replaces a range of bytes with new bytes, padding any extra part
620 /// of the range with zeroes.
621 /// </summary>
622 private static void ReplaceBytes(
623 byte[] source, int offset, int length, byte[] replace)
624 {
625 for (var i = 0; i < length; i++)
626 {
627 if (i < replace.Length)
628 {
629 source[offset + i] = replace[i];
630 }
631 else
632 {
633 source[offset + i] = 0;
634 }
635 }
636 }
637
638 /// <summary>
639 /// Print the name of one file as it is being packed into the cab.
640 /// </summary>
641 private static void PackProgress(object source, ArchiveProgressEventArgs e)
642 {
643 if (e.ProgressType == ArchiveProgressType.StartFile && log != null)
644 {
645 log.WriteLine(" {0}", e.CurrentFileName);
646 }
647 }
648
649 /// <summary>
650 /// Gets a mapping from filenames as they will be in the cab to filenames
651 /// as they are currently on disk.
652 /// </summary>
653 /// <remarks>
654 /// By default, all files will be placed in the root of the cab. But inputs may
655 /// optionally include an alternate inside-cab file path before an equals sign.
656 /// </remarks>
657 private static IDictionary<string, string> GetPackFileMap(IList<string> inputs)
658 {
659 var fileMap = new Dictionary<string, string>();
660 foreach (var inputFile in inputs)
661 {
662 if (inputFile.IndexOf('=') > 0)
663 {
664 var parse = inputFile.Split('=');
665 if (!fileMap.ContainsKey(parse[0]))
666 {
667 fileMap.Add(parse[0], parse[1]);
668 }
669 }
670 else
671 {
672 var fileName = Path.GetFileName(inputFile);
673 if (!fileMap.ContainsKey(fileName))
674 {
675 fileMap.Add(fileName, inputFile);
676 }
677 }
678 }
679 return fileMap;
680 }
681
682 /// <summary>
683 /// Packs the input files into a cab that is appended to the
684 /// output SfxCA.dll.
685 /// </summary>
686 private static void PackInputFiles(string outputFile, IDictionary<string, string> fileMap)
687 {
688 log.WriteLine("Packaging files");
689
690 var cabInfo = new CabInfo(outputFile);
691 cabInfo.PackFileSet(null, fileMap, CompressionLevel.Max, PackProgress);
692 }
693
694 /// <summary>
695 /// Copies the version resource information from the CA module to
696 /// the CA package. This gives the package the file version and
697 /// description of the CA module, instead of the version and
698 /// description of SfxCA.dll.
699 /// </summary>
700 private static void CopyVersionResource(string sourceFile, string destFile)
701 {
702 log.WriteLine("Copying file version info from {0} to {1}",
703 sourceFile, destFile);
704
705 var rc = new ResourceCollection();
706 rc.Find(sourceFile, ResourceType.Version);
707 rc.Load(sourceFile);
708 rc.Save(destFile);
709 }
710 }
711}
diff --git a/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.csproj b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.csproj
new file mode 100644
index 00000000..c6982532
--- /dev/null
+++ b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.csproj
@@ -0,0 +1,33 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <TargetFrameworks>netcoreapp3.1;net461</TargetFrameworks>
7 <OutputType>Exe</OutputType>
8 <RootNamespace>WixToolset.Dtf.Tools.MakeSfxCA</RootNamespace>
9 <AssemblyName>MakeSfxCA</AssemblyName>
10 <DebugType>embedded</DebugType>
11 <AppConfig>app.config</AppConfig>
12 <ApplicationManifest>MakeSfxCA.exe.manifest</ApplicationManifest>
13 <RollForward>Major</RollForward>
14 <RuntimeIdentifier>win-x86</RuntimeIdentifier>
15 </PropertyGroup>
16
17 <ItemGroup>
18 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
19 </ItemGroup>
20
21 <ItemGroup>
22 <ProjectReference Include="..\..\WixToolset.Dtf.Compression.Cab\WixToolset.Dtf.Compression.Cab.csproj" />
23 <ProjectReference Include="..\..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" />
24 <ProjectReference Include="..\..\WixToolset.Dtf.Resources\WixToolset.Dtf.Resources.csproj" />
25 </ItemGroup>
26
27 <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
28 <PropertyGroup>
29 <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
30 </PropertyGroup>
31 <Error Condition="!Exists('..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets'))" />
32 </Target>
33</Project>
diff --git a/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.exe.manifest b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.exe.manifest
new file mode 100644
index 00000000..49b074e0
--- /dev/null
+++ b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.exe.manifest
@@ -0,0 +1,20 @@
1<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
6 <assemblyIdentity name="WixToolset.Dtf.Tools.MakeSfxCA" version="4.0.0.0" processorArchitecture="x86" type="win32"/>
7 <description>WiX Toolset Compiler</description>
8 <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
9 <security>
10 <requestedPrivileges>
11 <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
12 </requestedPrivileges>
13 </security>
14 </trustInfo>
15 <application xmlns="urn:schemas-microsoft-com:asm.v3">
16 <windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
17 <ws2:longPathAware>true</ws2:longPathAware>
18 </windowsSettings>
19 </application>
20</assembly>
diff --git a/src/samples/Dtf/Tools/MakeSfxCA/app.config b/src/samples/Dtf/Tools/MakeSfxCA/app.config
new file mode 100644
index 00000000..65d3d6c3
--- /dev/null
+++ b/src/samples/Dtf/Tools/MakeSfxCA/app.config
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<configuration>
6 <runtime>
7 <loadFromRemoteSources enabled="true"/>
8 <AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false;Switch.System.IO.BlockLongPaths=false" />
9 </runtime>
10</configuration>
diff --git a/src/samples/Dtf/Tools/SfxCA/ClrHost.cpp b/src/samples/Dtf/Tools/SfxCA/ClrHost.cpp
new file mode 100644
index 00000000..1988fb2a
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/ClrHost.cpp
@@ -0,0 +1,262 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...);
6
7//---------------------------------------------------------------------
8// CLR HOSTING
9//---------------------------------------------------------------------
10
11/// <summary>
12/// Binds to the CLR after determining the appropriate version.
13/// </summary>
14/// <param name="hSession">Handle to the installer session,
15/// used just for logging.</param>
16/// <param name="version">Specific version of the CLR to load.
17/// If null, then the config file and/or primary assembly are
18/// used to determine the version.</param>
19/// <param name="szConfigFile">XML .config file which may contain
20/// a startup section to direct which version of the CLR to use.
21/// May be NULL.</param>
22/// <param name="szPrimaryAssembly">Assembly to be used to determine
23/// the version of the CLR in the absence of other configuration.
24/// May be NULL.</param>
25/// <param name="ppHost">Returned runtime host interface.</param>
26/// <returns>True if the CLR was loaded successfully, false if
27/// there was some error.</returns>
28/// <remarks>
29/// If szPrimaryAssembly is NULL and szConfigFile is also NULL or
30/// does not contain any version configuration, the CLR will not be loaded.
31/// </remarks>
32bool LoadCLR(MSIHANDLE hSession, const wchar_t* szVersion, const wchar_t* szConfigFile,
33 const wchar_t* szPrimaryAssembly, ICorRuntimeHost** ppHost)
34{
35 typedef HRESULT (__stdcall *PGetRequestedRuntimeInfo)(LPCWSTR pExe, LPCWSTR pwszVersion,
36 LPCWSTR pConfigurationFile, DWORD startupFlags, DWORD runtimeInfoFlags,
37 LPWSTR pDirectory, DWORD dwDirectory, DWORD *dwDirectoryLength,
38 LPWSTR pVersion, DWORD cchBuffer, DWORD* dwlength);
39 typedef HRESULT (__stdcall *PCorBindToRuntimeEx)(LPCWSTR pwszVersion, LPCWSTR pwszBuildFlavor,
40 DWORD startupFlags, REFCLSID rclsid, REFIID riid, LPVOID FAR *ppv);
41
42 HMODULE hmodMscoree = LoadLibrary(L"mscoree.dll");
43 if (hmodMscoree == NULL)
44 {
45 Log(hSession, L"Failed to load mscoree.dll (Error code %d). This custom action "
46 L"requires the .NET Framework to be installed.", GetLastError());
47 return false;
48 }
49 PGetRequestedRuntimeInfo pGetRequestedRuntimeInfo = (PGetRequestedRuntimeInfo)
50 GetProcAddress(hmodMscoree, "GetRequestedRuntimeInfo");
51 PCorBindToRuntimeEx pCorBindToRuntimeEx = (PCorBindToRuntimeEx)
52 GetProcAddress(hmodMscoree, "CorBindToRuntimeEx");
53 if (pGetRequestedRuntimeInfo == NULL || pCorBindToRuntimeEx == NULL)
54 {
55 Log(hSession, L"Failed to locate functions in mscoree.dll (Error code %d). This custom action "
56 L"requires the .NET Framework to be installed.", GetLastError());
57 FreeLibrary(hmodMscoree);
58 return false;
59 }
60
61 wchar_t szClrVersion[20];
62 HRESULT hr;
63
64 if (szVersion != NULL && szVersion[0] != L'\0')
65 {
66 wcsncpy_s(szClrVersion, 20, szVersion, 20);
67 }
68 else
69 {
70 wchar_t szVersionDir[MAX_PATH];
71 hr = pGetRequestedRuntimeInfo(szPrimaryAssembly, NULL,
72 szConfigFile, 0, 0, szVersionDir, MAX_PATH, NULL, szClrVersion, 20, NULL);
73 if (FAILED(hr))
74 {
75 Log(hSession, L"Failed to get requested CLR info. Error code 0x%x", hr);
76 Log(hSession, L"Ensure that the proper version of the .NET Framework is installed, or "
77 L"that there is a matching supportedRuntime element in CustomAction.config. "
78 L"If you are binding to .NET 4 or greater add "
79 L"useLegacyV2RuntimeActivationPolicy=true to the <startup> element.");
80 FreeLibrary(hmodMscoree);
81 return false;
82 }
83 }
84
85 Log(hSession, L"Binding to CLR version %s", szClrVersion);
86
87 ICorRuntimeHost* pHost;
88 hr = pCorBindToRuntimeEx(szClrVersion, NULL,
89 STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN,
90 CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (void**) &pHost);
91 if (FAILED(hr))
92 {
93 Log(hSession, L"Failed to bind to the CLR. Error code 0x%X", hr);
94 FreeLibrary(hmodMscoree);
95 return false;
96 }
97 hr = pHost->Start();
98 if (FAILED(hr))
99 {
100 Log(hSession, L"Failed to start the CLR. Error code 0x%X", hr);
101 pHost->Release();
102 FreeLibrary(hmodMscoree);
103 return false;
104 }
105 *ppHost = pHost;
106 FreeLibrary(hmodMscoree);
107 return true;
108}
109
110/// <summary>
111/// Creates a new CLR application domain.
112/// </summary>
113/// <param name="hSession">Handle to the installer session,
114/// used just for logging</param>
115/// <param name="pHost">Interface to the runtime host where the
116/// app domain will be created.</param>
117/// <param name="szName">Name of the app domain to create.</param>
118/// <param name="szAppBase">Application base directory path, where
119/// the app domain will look first to load its assemblies.</param>
120/// <param name="szConfigFile">Optional XML .config file containing any
121/// configuration for thae app domain.</param>
122/// <param name="ppAppDomain">Returned app domain interface.</param>
123/// <returns>True if the app domain was created successfully, false if
124/// there was some error.</returns>
125bool CreateAppDomain(MSIHANDLE hSession, ICorRuntimeHost* pHost,
126 const wchar_t* szName, const wchar_t* szAppBase,
127 const wchar_t* szConfigFile, _AppDomain** ppAppDomain)
128{
129 IUnknown* punkAppDomainSetup = NULL;
130 IAppDomainSetup* pAppDomainSetup = NULL;
131 HRESULT hr = pHost->CreateDomainSetup(&punkAppDomainSetup);
132 if (SUCCEEDED(hr))
133 {
134 hr = punkAppDomainSetup->QueryInterface(__uuidof(IAppDomainSetup), (void**) &pAppDomainSetup);
135 punkAppDomainSetup->Release();
136 }
137 if (FAILED(hr))
138 {
139 Log(hSession, L"Failed to create app domain setup. Error code 0x%X", hr);
140 return false;
141 }
142
143 const wchar_t* szUrlPrefix = L"file:///";
144 size_t cchApplicationBase = wcslen(szUrlPrefix) + wcslen(szAppBase);
145 wchar_t* szApplicationBase = (wchar_t*) _alloca((cchApplicationBase + 1) * sizeof(wchar_t));
146 if (szApplicationBase == NULL) hr = E_OUTOFMEMORY;
147 else
148 {
149 StringCchCopy(szApplicationBase, cchApplicationBase + 1, szUrlPrefix);
150 StringCchCat(szApplicationBase, cchApplicationBase + 1, szAppBase);
151 BSTR bstrApplicationBase = SysAllocString(szApplicationBase);
152 if (bstrApplicationBase == NULL) hr = E_OUTOFMEMORY;
153 else
154 {
155 hr = pAppDomainSetup->put_ApplicationBase(bstrApplicationBase);
156 SysFreeString(bstrApplicationBase);
157 }
158 }
159
160 if (SUCCEEDED(hr) && szConfigFile != NULL)
161 {
162 BSTR bstrConfigFile = SysAllocString(szConfigFile);
163 if (bstrConfigFile == NULL) hr = E_OUTOFMEMORY;
164 else
165 {
166 hr = pAppDomainSetup->put_ConfigurationFile(bstrConfigFile);
167 SysFreeString(bstrConfigFile);
168 }
169 }
170
171 if (FAILED(hr))
172 {
173 Log(hSession, L"Failed to configure app domain setup. Error code 0x%X", hr);
174 pAppDomainSetup->Release();
175 return false;
176 }
177
178 IUnknown* punkAppDomain;
179 hr = pHost->CreateDomainEx(szName, pAppDomainSetup, NULL, &punkAppDomain);
180 pAppDomainSetup->Release();
181 if (SUCCEEDED(hr))
182 {
183 hr = punkAppDomain->QueryInterface(__uuidof(_AppDomain), (void**) ppAppDomain);
184 punkAppDomain->Release();
185 }
186
187 if (FAILED(hr))
188 {
189 Log(hSession, L"Failed to create app domain. Error code 0x%X", hr);
190 return false;
191 }
192
193 return true;
194}
195
196/// <summary>
197/// Locates a specific method in a specific class and assembly.
198/// </summary>
199/// <param name="hSession">Handle to the installer session,
200/// used just for logging</param>
201/// <param name="pAppDomain">Application domain in which to
202/// load assemblies.</param>
203/// <param name="szAssembly">Display name of the assembly
204/// containing the method.</param>
205/// <param name="szClass">Fully-qualified name of the class
206/// containing the method.</param>
207/// <param name="szMethod">Name of the method.</param>
208/// <param name="ppMethod">Returned method interface.</param>
209/// <returns>True if the method was located, otherwise false.</returns>
210/// <remarks>Only public static methods are searched. Method
211/// parameter types are not considered; if there are multiple
212/// matching methods with different parameters, an error results.</remarks>
213bool GetMethod(MSIHANDLE hSession, _AppDomain* pAppDomain,
214 const wchar_t* szAssembly, const wchar_t* szClass,
215 const wchar_t* szMethod, _MethodInfo** ppMethod)
216{
217 HRESULT hr;
218 _Assembly* pAssembly = NULL;
219 BSTR bstrAssemblyName = SysAllocString(szAssembly);
220 if (bstrAssemblyName == NULL) hr = E_OUTOFMEMORY;
221 else
222 {
223 hr = pAppDomain->Load_2(bstrAssemblyName, &pAssembly);
224 SysFreeString(bstrAssemblyName);
225 }
226 if (FAILED(hr))
227 {
228 Log(hSession, L"Failed to load assembly %s. Error code 0x%X", szAssembly, hr);
229 return false;
230 }
231
232 _Type* pType = NULL;
233 BSTR bstrClass = SysAllocString(szClass);
234 if (bstrClass == NULL) hr = E_OUTOFMEMORY;
235 else
236 {
237 hr = pAssembly->GetType_2(bstrClass, &pType);
238 SysFreeString(bstrClass);
239 }
240 pAssembly->Release();
241 if (FAILED(hr) || pType == NULL)
242 {
243 Log(hSession, L"Failed to load class %s. Error code 0x%X", szClass, hr);
244 return false;
245 }
246
247 BSTR bstrMethod = SysAllocString(szMethod);
248 if (bstrMethod == NULL) hr = E_OUTOFMEMORY;
249 else
250 {
251 hr = pType->GetMethod_2(bstrMethod,
252 (BindingFlags) (BindingFlags_Public | BindingFlags_Static), ppMethod);
253 SysFreeString(bstrMethod);
254 }
255 pType->Release();
256 if (FAILED(hr) || *ppMethod == NULL)
257 {
258 Log(hSession, L"Failed to get method %s. Error code 0x%X", szMethod, hr);
259 return false;
260 }
261 return true;
262}
diff --git a/src/samples/Dtf/Tools/SfxCA/EmbeddedUI.cpp b/src/samples/Dtf/Tools/SfxCA/EmbeddedUI.cpp
new file mode 100644
index 00000000..a49cdeec
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/EmbeddedUI.cpp
@@ -0,0 +1,281 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4#include "SfxUtil.h"
5
6// Globals for keeping track of things across UI messages.
7static const wchar_t* g_szWorkingDir;
8static ICorRuntimeHost* g_pClrHost;
9static _AppDomain* g_pAppDomain;
10static _MethodInfo* g_pProcessMessageMethod;
11static _MethodInfo* g_pShutdownMethod;
12
13// Reserve extra space for strings to be replaced at build time.
14#define NULLSPACE \
15L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
16L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
17L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
18L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
19
20// Prototypes for local functions.
21// See the function definitions for comments.
22
23bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession,
24 const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult);
25
26/// <summary>
27/// First entry-point for the UI DLL when loaded and called by MSI.
28/// Extracts the payload, hosts the CLR, and invokes the managed
29/// initialize method.
30/// </summary>
31/// <param name="hSession">Handle to the installer session,
32/// used for logging errors and to be passed on to the managed initialize method.</param>
33/// <param name="szResourcePath">Path the directory where resources from the MsiEmbeddedUI table
34/// have been extracted, and where additional payload from this package will be extracted.</param>
35/// <param name="pdwInternalUILevel">MSI install UI level passed to and returned from
36/// the managed initialize method.</param>
37extern "C"
38UINT __stdcall InitializeEmbeddedUI(MSIHANDLE hSession, LPCWSTR szResourcePath, LPDWORD pdwInternalUILevel)
39{
40 // If the managed initialize method cannot be called, continue the installation in BASIC UI mode.
41 UINT uiResult = INSTALLUILEVEL_BASIC;
42
43 const wchar_t* szClassName = L"InitializeEmbeddedUI_FullClassName" NULLSPACE;
44
45 g_szWorkingDir = szResourcePath;
46
47 wchar_t szModule[MAX_PATH];
48 DWORD cchCopied = GetModuleFileName(g_hModule, szModule, MAX_PATH - 1);
49 if (cchCopied == 0)
50 {
51 Log(hSession, L"Failed to get module path. Error code %d.", GetLastError());
52 return uiResult;
53 }
54 else if (cchCopied == MAX_PATH - 1)
55 {
56 Log(hSession, L"Failed to get module path -- path is too long.");
57 return uiResult;
58 }
59
60 Log(hSession, L"Extracting embedded UI to temporary directory: %s", g_szWorkingDir);
61 int err = ExtractCabinet(szModule, g_szWorkingDir);
62 if (err != 0)
63 {
64 Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err);
65 Log(hSession, L"Ensure that no MsiEmbeddedUI.FileName values are the same as "
66 L"any file contained in the embedded UI package.");
67 return uiResult;
68 }
69
70 wchar_t szConfigFilePath[MAX_PATH + 20];
71 StringCchCopy(szConfigFilePath, MAX_PATH + 20, g_szWorkingDir);
72 StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\EmbeddedUI.config");
73
74 const wchar_t* szConfigFile = szConfigFilePath;
75 if (!PathFileExists(szConfigFilePath))
76 {
77 szConfigFile = NULL;
78 }
79
80 wchar_t szWIAssembly[MAX_PATH + 50];
81 StringCchCopy(szWIAssembly, MAX_PATH + 50, g_szWorkingDir);
82 StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll");
83
84 if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &g_pClrHost))
85 {
86 if (CreateAppDomain(hSession, g_pClrHost, L"EmbeddedUI", g_szWorkingDir,
87 szConfigFile, &g_pAppDomain))
88 {
89 const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller";
90 const wchar_t* szProxyClass = L"WixToolset.Dtf.WindowsInstaller.EmbeddedUIProxy";
91 const wchar_t* szInitMethod = L"Initialize";
92 const wchar_t* szProcessMessageMethod = L"ProcessMessage";
93 const wchar_t* szShutdownMethod = L"Shutdown";
94
95 if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName,
96 szProxyClass, szProcessMessageMethod, &g_pProcessMessageMethod) &&
97 GetMethod(hSession, g_pAppDomain, szMsiAssemblyName,
98 szProxyClass, szShutdownMethod, &g_pShutdownMethod))
99 {
100 _MethodInfo* pInitMethod;
101 if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName,
102 szProxyClass, szInitMethod, &pInitMethod))
103 {
104 bool invokeSuccess = InvokeInitializeMethod(pInitMethod, hSession, szClassName, pdwInternalUILevel, &uiResult);
105 pInitMethod->Release();
106 if (invokeSuccess)
107 {
108 if (uiResult == 0)
109 {
110 return ERROR_SUCCESS;
111 }
112 else if (uiResult == ERROR_INSTALL_USEREXIT)
113 {
114 // InitializeEmbeddedUI is not allowed to return ERROR_INSTALL_USEREXIT.
115 // So return success here and then IDCANCEL on the next progress message.
116 uiResult = 0;
117 *pdwInternalUILevel = INSTALLUILEVEL_NONE;
118 Log(hSession, L"Initialization canceled by user.");
119 }
120 }
121 }
122 }
123
124 g_pProcessMessageMethod->Release();
125 g_pProcessMessageMethod = NULL;
126 g_pShutdownMethod->Release();
127 g_pShutdownMethod = NULL;
128
129 g_pClrHost->UnloadDomain(g_pAppDomain);
130 g_pAppDomain->Release();
131 g_pAppDomain = NULL;
132 }
133 g_pClrHost->Stop();
134 g_pClrHost->Release();
135 g_pClrHost = NULL;
136 }
137
138 return uiResult;
139}
140
141/// <summary>
142/// Entry-point for UI progress messages received from the MSI engine during an active installation.
143/// Forwards the progress messages to the managed handler method and returns its result.
144/// </summary>
145extern "C"
146INT __stdcall EmbeddedUIHandler(UINT uiMessageType, MSIHANDLE hRecord)
147{
148 if (g_pProcessMessageMethod == NULL)
149 {
150 // Initialization was canceled.
151 return IDCANCEL;
152 }
153
154 VARIANT vResult;
155 VariantInit(&vResult);
156
157 VARIANT vNull;
158 vNull.vt = VT_EMPTY;
159
160 SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 2);
161 VARIANT vMessageType;
162 vMessageType.vt = VT_I4;
163 vMessageType.lVal = (LONG) uiMessageType;
164 LONG index = 0;
165 HRESULT hr = SafeArrayPutElement(saArgs, &index, &vMessageType);
166 if (FAILED(hr)) goto LExit;
167 VARIANT vRecord;
168 vRecord.vt = VT_I4;
169 vRecord.lVal = (LONG) hRecord;
170 index = 1;
171 hr = SafeArrayPutElement(saArgs, &index, &vRecord);
172 if (FAILED(hr)) goto LExit;
173
174 hr = g_pProcessMessageMethod->Invoke_3(vNull, saArgs, &vResult);
175
176LExit:
177 SafeArrayDestroy(saArgs);
178 if (SUCCEEDED(hr))
179 {
180 return vResult.intVal;
181 }
182 else
183 {
184 return -1;
185 }
186}
187
188/// <summary>
189/// Entry-point for the UI shutdown message received from the MSI engine after installation has completed.
190/// Forwards the shutdown message to the managed shutdown method, then shuts down the CLR.
191/// </summary>
192extern "C"
193DWORD __stdcall ShutdownEmbeddedUI()
194{
195 if (g_pShutdownMethod != NULL)
196 {
197 VARIANT vNull;
198 vNull.vt = VT_EMPTY;
199 SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0);
200 g_pShutdownMethod->Invoke_3(vNull, saArgs, NULL);
201 SafeArrayDestroy(saArgs);
202
203 g_pClrHost->UnloadDomain(g_pAppDomain);
204 g_pAppDomain->Release();
205 g_pClrHost->Stop();
206 g_pClrHost->Release();
207 }
208
209 return 0;
210}
211
212/// <summary>
213/// Loads and invokes the managed portion of the proxy.
214/// </summary>
215/// <param name="pInitMethod">Managed initialize method to be invoked.</param>
216/// <param name="hSession">Handle to the installer session,
217/// used for logging errors and to be passed on to the managed initialize method.</param>
218/// <param name="szClassName">Name of the UI class to be loaded.
219/// This must be of the form: AssemblyName!Namespace.Class</param>
220/// <param name="pdwInternalUILevel">MSI install UI level passed to and returned from
221/// the managed initialize method.</param>
222/// <param name="puiResult">Return value of the invoked initialize method.</param>
223/// <returns>True if the managed proxy was invoked successfully, or an
224/// error code if there was some error. Note the initialize method itself may
225/// return an error via puiResult while this method still returns true
226/// since the invocation was successful.</returns>
227bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession, const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult)
228{
229 VARIANT vResult;
230 VariantInit(&vResult);
231
232 VARIANT vNull;
233 vNull.vt = VT_EMPTY;
234
235 SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3);
236 VARIANT vSessionHandle;
237 vSessionHandle.vt = VT_I4;
238 vSessionHandle.lVal = (LONG) hSession;
239 LONG index = 0;
240 HRESULT hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle);
241 if (FAILED(hr)) goto LExit;
242 VARIANT vEntryPoint;
243 vEntryPoint.vt = VT_BSTR;
244 vEntryPoint.bstrVal = SysAllocString(szClassName);
245 if (vEntryPoint.bstrVal == NULL)
246 {
247 hr = E_OUTOFMEMORY;
248 goto LExit;
249 }
250 index = 1;
251 hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint);
252 if (FAILED(hr)) goto LExit;
253 VARIANT vUILevel;
254 vUILevel.vt = VT_I4;
255 vUILevel.ulVal = *pdwInternalUILevel;
256 index = 2;
257 hr = SafeArrayPutElement(saArgs, &index, &vUILevel);
258 if (FAILED(hr)) goto LExit;
259
260 hr = pInitMethod->Invoke_3(vNull, saArgs, &vResult);
261
262LExit:
263 SafeArrayDestroy(saArgs);
264 if (SUCCEEDED(hr))
265 {
266 *puiResult = (UINT) vResult.lVal;
267 if ((*puiResult & 0xFFFF) == 0)
268 {
269 // Due to interop limitations, the successful resulting UILevel is returned
270 // as the high-word of the return value instead of via a ref parameter.
271 *pdwInternalUILevel = *puiResult >> 16;
272 *puiResult = 0;
273 }
274 return true;
275 }
276 else
277 {
278 Log(hSession, L"Failed to invoke EmbeddedUI Initialize method. Error code 0x%X", hr);
279 return false;
280 }
281}
diff --git a/src/samples/Dtf/Tools/SfxCA/EntryPoints.def b/src/samples/Dtf/Tools/SfxCA/EntryPoints.def
new file mode 100644
index 00000000..dd28b920
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/EntryPoints.def
@@ -0,0 +1,140 @@
1; Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3
4LIBRARY "SfxCA"
5
6EXPORTS
7
8CustomActionEntryPoint000________________________________________________=CustomActionEntryPoint000
9CustomActionEntryPoint001________________________________________________=CustomActionEntryPoint001
10CustomActionEntryPoint002________________________________________________=CustomActionEntryPoint002
11CustomActionEntryPoint003________________________________________________=CustomActionEntryPoint003
12CustomActionEntryPoint004________________________________________________=CustomActionEntryPoint004
13CustomActionEntryPoint005________________________________________________=CustomActionEntryPoint005
14CustomActionEntryPoint006________________________________________________=CustomActionEntryPoint006
15CustomActionEntryPoint007________________________________________________=CustomActionEntryPoint007
16CustomActionEntryPoint008________________________________________________=CustomActionEntryPoint008
17CustomActionEntryPoint009________________________________________________=CustomActionEntryPoint009
18CustomActionEntryPoint010________________________________________________=CustomActionEntryPoint010
19CustomActionEntryPoint011________________________________________________=CustomActionEntryPoint011
20CustomActionEntryPoint012________________________________________________=CustomActionEntryPoint012
21CustomActionEntryPoint013________________________________________________=CustomActionEntryPoint013
22CustomActionEntryPoint014________________________________________________=CustomActionEntryPoint014
23CustomActionEntryPoint015________________________________________________=CustomActionEntryPoint015
24CustomActionEntryPoint016________________________________________________=CustomActionEntryPoint016
25CustomActionEntryPoint017________________________________________________=CustomActionEntryPoint017
26CustomActionEntryPoint018________________________________________________=CustomActionEntryPoint018
27CustomActionEntryPoint019________________________________________________=CustomActionEntryPoint019
28CustomActionEntryPoint020________________________________________________=CustomActionEntryPoint020
29CustomActionEntryPoint021________________________________________________=CustomActionEntryPoint021
30CustomActionEntryPoint022________________________________________________=CustomActionEntryPoint022
31CustomActionEntryPoint023________________________________________________=CustomActionEntryPoint023
32CustomActionEntryPoint024________________________________________________=CustomActionEntryPoint024
33CustomActionEntryPoint025________________________________________________=CustomActionEntryPoint025
34CustomActionEntryPoint026________________________________________________=CustomActionEntryPoint026
35CustomActionEntryPoint027________________________________________________=CustomActionEntryPoint027
36CustomActionEntryPoint028________________________________________________=CustomActionEntryPoint028
37CustomActionEntryPoint029________________________________________________=CustomActionEntryPoint029
38CustomActionEntryPoint030________________________________________________=CustomActionEntryPoint030
39CustomActionEntryPoint031________________________________________________=CustomActionEntryPoint031
40CustomActionEntryPoint032________________________________________________=CustomActionEntryPoint032
41CustomActionEntryPoint033________________________________________________=CustomActionEntryPoint033
42CustomActionEntryPoint034________________________________________________=CustomActionEntryPoint034
43CustomActionEntryPoint035________________________________________________=CustomActionEntryPoint035
44CustomActionEntryPoint036________________________________________________=CustomActionEntryPoint036
45CustomActionEntryPoint037________________________________________________=CustomActionEntryPoint037
46CustomActionEntryPoint038________________________________________________=CustomActionEntryPoint038
47CustomActionEntryPoint039________________________________________________=CustomActionEntryPoint039
48CustomActionEntryPoint040________________________________________________=CustomActionEntryPoint040
49CustomActionEntryPoint041________________________________________________=CustomActionEntryPoint041
50CustomActionEntryPoint042________________________________________________=CustomActionEntryPoint042
51CustomActionEntryPoint043________________________________________________=CustomActionEntryPoint043
52CustomActionEntryPoint044________________________________________________=CustomActionEntryPoint044
53CustomActionEntryPoint045________________________________________________=CustomActionEntryPoint045
54CustomActionEntryPoint046________________________________________________=CustomActionEntryPoint046
55CustomActionEntryPoint047________________________________________________=CustomActionEntryPoint047
56CustomActionEntryPoint048________________________________________________=CustomActionEntryPoint048
57CustomActionEntryPoint049________________________________________________=CustomActionEntryPoint049
58CustomActionEntryPoint050________________________________________________=CustomActionEntryPoint050
59CustomActionEntryPoint051________________________________________________=CustomActionEntryPoint051
60CustomActionEntryPoint052________________________________________________=CustomActionEntryPoint052
61CustomActionEntryPoint053________________________________________________=CustomActionEntryPoint053
62CustomActionEntryPoint054________________________________________________=CustomActionEntryPoint054
63CustomActionEntryPoint055________________________________________________=CustomActionEntryPoint055
64CustomActionEntryPoint056________________________________________________=CustomActionEntryPoint056
65CustomActionEntryPoint057________________________________________________=CustomActionEntryPoint057
66CustomActionEntryPoint058________________________________________________=CustomActionEntryPoint058
67CustomActionEntryPoint059________________________________________________=CustomActionEntryPoint059
68CustomActionEntryPoint060________________________________________________=CustomActionEntryPoint060
69CustomActionEntryPoint061________________________________________________=CustomActionEntryPoint061
70CustomActionEntryPoint062________________________________________________=CustomActionEntryPoint062
71CustomActionEntryPoint063________________________________________________=CustomActionEntryPoint063
72CustomActionEntryPoint064________________________________________________=CustomActionEntryPoint064
73CustomActionEntryPoint065________________________________________________=CustomActionEntryPoint065
74CustomActionEntryPoint066________________________________________________=CustomActionEntryPoint066
75CustomActionEntryPoint067________________________________________________=CustomActionEntryPoint067
76CustomActionEntryPoint068________________________________________________=CustomActionEntryPoint068
77CustomActionEntryPoint069________________________________________________=CustomActionEntryPoint069
78CustomActionEntryPoint070________________________________________________=CustomActionEntryPoint070
79CustomActionEntryPoint071________________________________________________=CustomActionEntryPoint071
80CustomActionEntryPoint072________________________________________________=CustomActionEntryPoint072
81CustomActionEntryPoint073________________________________________________=CustomActionEntryPoint073
82CustomActionEntryPoint074________________________________________________=CustomActionEntryPoint074
83CustomActionEntryPoint075________________________________________________=CustomActionEntryPoint075
84CustomActionEntryPoint076________________________________________________=CustomActionEntryPoint076
85CustomActionEntryPoint077________________________________________________=CustomActionEntryPoint077
86CustomActionEntryPoint078________________________________________________=CustomActionEntryPoint078
87CustomActionEntryPoint079________________________________________________=CustomActionEntryPoint079
88CustomActionEntryPoint080________________________________________________=CustomActionEntryPoint080
89CustomActionEntryPoint081________________________________________________=CustomActionEntryPoint081
90CustomActionEntryPoint082________________________________________________=CustomActionEntryPoint082
91CustomActionEntryPoint083________________________________________________=CustomActionEntryPoint083
92CustomActionEntryPoint084________________________________________________=CustomActionEntryPoint084
93CustomActionEntryPoint085________________________________________________=CustomActionEntryPoint085
94CustomActionEntryPoint086________________________________________________=CustomActionEntryPoint086
95CustomActionEntryPoint087________________________________________________=CustomActionEntryPoint087
96CustomActionEntryPoint088________________________________________________=CustomActionEntryPoint088
97CustomActionEntryPoint089________________________________________________=CustomActionEntryPoint089
98CustomActionEntryPoint090________________________________________________=CustomActionEntryPoint090
99CustomActionEntryPoint091________________________________________________=CustomActionEntryPoint091
100CustomActionEntryPoint092________________________________________________=CustomActionEntryPoint092
101CustomActionEntryPoint093________________________________________________=CustomActionEntryPoint093
102CustomActionEntryPoint094________________________________________________=CustomActionEntryPoint094
103CustomActionEntryPoint095________________________________________________=CustomActionEntryPoint095
104CustomActionEntryPoint096________________________________________________=CustomActionEntryPoint096
105CustomActionEntryPoint097________________________________________________=CustomActionEntryPoint097
106CustomActionEntryPoint098________________________________________________=CustomActionEntryPoint098
107CustomActionEntryPoint099________________________________________________=CustomActionEntryPoint099
108CustomActionEntryPoint100________________________________________________=CustomActionEntryPoint100
109CustomActionEntryPoint101________________________________________________=CustomActionEntryPoint101
110CustomActionEntryPoint102________________________________________________=CustomActionEntryPoint102
111CustomActionEntryPoint103________________________________________________=CustomActionEntryPoint103
112CustomActionEntryPoint104________________________________________________=CustomActionEntryPoint104
113CustomActionEntryPoint105________________________________________________=CustomActionEntryPoint105
114CustomActionEntryPoint106________________________________________________=CustomActionEntryPoint106
115CustomActionEntryPoint107________________________________________________=CustomActionEntryPoint107
116CustomActionEntryPoint108________________________________________________=CustomActionEntryPoint108
117CustomActionEntryPoint109________________________________________________=CustomActionEntryPoint109
118CustomActionEntryPoint110________________________________________________=CustomActionEntryPoint110
119CustomActionEntryPoint111________________________________________________=CustomActionEntryPoint111
120CustomActionEntryPoint112________________________________________________=CustomActionEntryPoint112
121CustomActionEntryPoint113________________________________________________=CustomActionEntryPoint113
122CustomActionEntryPoint114________________________________________________=CustomActionEntryPoint114
123CustomActionEntryPoint115________________________________________________=CustomActionEntryPoint115
124CustomActionEntryPoint116________________________________________________=CustomActionEntryPoint116
125CustomActionEntryPoint117________________________________________________=CustomActionEntryPoint117
126CustomActionEntryPoint118________________________________________________=CustomActionEntryPoint118
127CustomActionEntryPoint119________________________________________________=CustomActionEntryPoint119
128CustomActionEntryPoint120________________________________________________=CustomActionEntryPoint120
129CustomActionEntryPoint121________________________________________________=CustomActionEntryPoint121
130CustomActionEntryPoint122________________________________________________=CustomActionEntryPoint122
131CustomActionEntryPoint123________________________________________________=CustomActionEntryPoint123
132CustomActionEntryPoint124________________________________________________=CustomActionEntryPoint124
133CustomActionEntryPoint125________________________________________________=CustomActionEntryPoint125
134CustomActionEntryPoint126________________________________________________=CustomActionEntryPoint126
135CustomActionEntryPoint127________________________________________________=CustomActionEntryPoint127
136
137zzzzInvokeManagedCustomActionOutOfProcW=InvokeManagedCustomActionOutOfProc
138zzzInitializeEmbeddedUI=InitializeEmbeddedUI
139zzzEmbeddedUIHandler=EmbeddedUIHandler
140zzzShutdownEmbeddedUI=ShutdownEmbeddedUI
diff --git a/src/samples/Dtf/Tools/SfxCA/EntryPoints.h b/src/samples/Dtf/Tools/SfxCA/EntryPoints.h
new file mode 100644
index 00000000..bd2fa970
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/EntryPoints.h
@@ -0,0 +1,162 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3int InvokeCustomAction(MSIHANDLE hSession,
4 const wchar_t* szWorkingDir, const wchar_t* szEntryPoint);
5
6/// <summary>
7/// Macro for defining and exporting a custom action entrypoint.
8/// </summary>
9/// <param name="name">Name of the entrypoint as exported from
10/// the DLL.</param>
11/// <param name="method">Path to the managed custom action method,
12/// in the form: "AssemblyName!Namespace.Class.Method"</param>
13/// <remarks>
14/// To prevent the exported name from being decorated, add
15/// /EXPORT:name to the linker options for every entrypoint.
16/// </remarks>
17#define CUSTOMACTION_ENTRYPOINT(name,method) extern "C" int __stdcall \
18 name(MSIHANDLE hSession) { return InvokeCustomAction(hSession, NULL, method); }
19
20// TEMPLATE ENTRYPOINTS
21// To be edited by the MakeSfxCA tool.
22
23#define NULLSPACE \
24L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
25L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
26L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
27L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
28
29#define TEMPLATE_CA_ENTRYPOINT(id,sid) CUSTOMACTION_ENTRYPOINT( \
30 CustomActionEntryPoint##id##, \
31 L"CustomActionEntryPoint" sid NULLSPACE)
32
33TEMPLATE_CA_ENTRYPOINT(000,L"000");
34TEMPLATE_CA_ENTRYPOINT(001,L"001");
35TEMPLATE_CA_ENTRYPOINT(002,L"002");
36TEMPLATE_CA_ENTRYPOINT(003,L"003");
37TEMPLATE_CA_ENTRYPOINT(004,L"004");
38TEMPLATE_CA_ENTRYPOINT(005,L"005");
39TEMPLATE_CA_ENTRYPOINT(006,L"006");
40TEMPLATE_CA_ENTRYPOINT(007,L"007");
41TEMPLATE_CA_ENTRYPOINT(008,L"008");
42TEMPLATE_CA_ENTRYPOINT(009,L"009");
43TEMPLATE_CA_ENTRYPOINT(010,L"010");
44TEMPLATE_CA_ENTRYPOINT(011,L"011");
45TEMPLATE_CA_ENTRYPOINT(012,L"012");
46TEMPLATE_CA_ENTRYPOINT(013,L"013");
47TEMPLATE_CA_ENTRYPOINT(014,L"014");
48TEMPLATE_CA_ENTRYPOINT(015,L"015");
49TEMPLATE_CA_ENTRYPOINT(016,L"016");
50TEMPLATE_CA_ENTRYPOINT(017,L"017");
51TEMPLATE_CA_ENTRYPOINT(018,L"018");
52TEMPLATE_CA_ENTRYPOINT(019,L"019");
53TEMPLATE_CA_ENTRYPOINT(020,L"020");
54TEMPLATE_CA_ENTRYPOINT(021,L"021");
55TEMPLATE_CA_ENTRYPOINT(022,L"022");
56TEMPLATE_CA_ENTRYPOINT(023,L"023");
57TEMPLATE_CA_ENTRYPOINT(024,L"024");
58TEMPLATE_CA_ENTRYPOINT(025,L"025");
59TEMPLATE_CA_ENTRYPOINT(026,L"026");
60TEMPLATE_CA_ENTRYPOINT(027,L"027");
61TEMPLATE_CA_ENTRYPOINT(028,L"028");
62TEMPLATE_CA_ENTRYPOINT(029,L"029");
63TEMPLATE_CA_ENTRYPOINT(030,L"030");
64TEMPLATE_CA_ENTRYPOINT(031,L"031");
65TEMPLATE_CA_ENTRYPOINT(032,L"032");
66TEMPLATE_CA_ENTRYPOINT(033,L"033");
67TEMPLATE_CA_ENTRYPOINT(034,L"034");
68TEMPLATE_CA_ENTRYPOINT(035,L"035");
69TEMPLATE_CA_ENTRYPOINT(036,L"036");
70TEMPLATE_CA_ENTRYPOINT(037,L"037");
71TEMPLATE_CA_ENTRYPOINT(038,L"038");
72TEMPLATE_CA_ENTRYPOINT(039,L"039");
73TEMPLATE_CA_ENTRYPOINT(040,L"040");
74TEMPLATE_CA_ENTRYPOINT(041,L"041");
75TEMPLATE_CA_ENTRYPOINT(042,L"042");
76TEMPLATE_CA_ENTRYPOINT(043,L"043");
77TEMPLATE_CA_ENTRYPOINT(044,L"044");
78TEMPLATE_CA_ENTRYPOINT(045,L"045");
79TEMPLATE_CA_ENTRYPOINT(046,L"046");
80TEMPLATE_CA_ENTRYPOINT(047,L"047");
81TEMPLATE_CA_ENTRYPOINT(048,L"048");
82TEMPLATE_CA_ENTRYPOINT(049,L"049");
83TEMPLATE_CA_ENTRYPOINT(050,L"050");
84TEMPLATE_CA_ENTRYPOINT(051,L"051");
85TEMPLATE_CA_ENTRYPOINT(052,L"052");
86TEMPLATE_CA_ENTRYPOINT(053,L"053");
87TEMPLATE_CA_ENTRYPOINT(054,L"054");
88TEMPLATE_CA_ENTRYPOINT(055,L"055");
89TEMPLATE_CA_ENTRYPOINT(056,L"056");
90TEMPLATE_CA_ENTRYPOINT(057,L"057");
91TEMPLATE_CA_ENTRYPOINT(058,L"058");
92TEMPLATE_CA_ENTRYPOINT(059,L"059");
93TEMPLATE_CA_ENTRYPOINT(060,L"060");
94TEMPLATE_CA_ENTRYPOINT(061,L"061");
95TEMPLATE_CA_ENTRYPOINT(062,L"062");
96TEMPLATE_CA_ENTRYPOINT(063,L"063");
97TEMPLATE_CA_ENTRYPOINT(064,L"064");
98TEMPLATE_CA_ENTRYPOINT(065,L"065");
99TEMPLATE_CA_ENTRYPOINT(066,L"066");
100TEMPLATE_CA_ENTRYPOINT(067,L"067");
101TEMPLATE_CA_ENTRYPOINT(068,L"068");
102TEMPLATE_CA_ENTRYPOINT(069,L"069");
103TEMPLATE_CA_ENTRYPOINT(070,L"070");
104TEMPLATE_CA_ENTRYPOINT(071,L"071");
105TEMPLATE_CA_ENTRYPOINT(072,L"072");
106TEMPLATE_CA_ENTRYPOINT(073,L"073");
107TEMPLATE_CA_ENTRYPOINT(074,L"074");
108TEMPLATE_CA_ENTRYPOINT(075,L"075");
109TEMPLATE_CA_ENTRYPOINT(076,L"076");
110TEMPLATE_CA_ENTRYPOINT(077,L"077");
111TEMPLATE_CA_ENTRYPOINT(078,L"078");
112TEMPLATE_CA_ENTRYPOINT(079,L"079");
113TEMPLATE_CA_ENTRYPOINT(080,L"080");
114TEMPLATE_CA_ENTRYPOINT(081,L"081");
115TEMPLATE_CA_ENTRYPOINT(082,L"082");
116TEMPLATE_CA_ENTRYPOINT(083,L"083");
117TEMPLATE_CA_ENTRYPOINT(084,L"084");
118TEMPLATE_CA_ENTRYPOINT(085,L"085");
119TEMPLATE_CA_ENTRYPOINT(086,L"086");
120TEMPLATE_CA_ENTRYPOINT(087,L"087");
121TEMPLATE_CA_ENTRYPOINT(088,L"088");
122TEMPLATE_CA_ENTRYPOINT(089,L"089");
123TEMPLATE_CA_ENTRYPOINT(090,L"090");
124TEMPLATE_CA_ENTRYPOINT(091,L"091");
125TEMPLATE_CA_ENTRYPOINT(092,L"092");
126TEMPLATE_CA_ENTRYPOINT(093,L"093");
127TEMPLATE_CA_ENTRYPOINT(094,L"094");
128TEMPLATE_CA_ENTRYPOINT(095,L"095");
129TEMPLATE_CA_ENTRYPOINT(096,L"096");
130TEMPLATE_CA_ENTRYPOINT(097,L"097");
131TEMPLATE_CA_ENTRYPOINT(098,L"098");
132TEMPLATE_CA_ENTRYPOINT(099,L"099");
133TEMPLATE_CA_ENTRYPOINT(100,L"100");
134TEMPLATE_CA_ENTRYPOINT(101,L"101");
135TEMPLATE_CA_ENTRYPOINT(102,L"102");
136TEMPLATE_CA_ENTRYPOINT(103,L"103");
137TEMPLATE_CA_ENTRYPOINT(104,L"104");
138TEMPLATE_CA_ENTRYPOINT(105,L"105");
139TEMPLATE_CA_ENTRYPOINT(106,L"106");
140TEMPLATE_CA_ENTRYPOINT(107,L"107");
141TEMPLATE_CA_ENTRYPOINT(108,L"108");
142TEMPLATE_CA_ENTRYPOINT(109,L"109");
143TEMPLATE_CA_ENTRYPOINT(110,L"110");
144TEMPLATE_CA_ENTRYPOINT(111,L"111");
145TEMPLATE_CA_ENTRYPOINT(112,L"112");
146TEMPLATE_CA_ENTRYPOINT(113,L"113");
147TEMPLATE_CA_ENTRYPOINT(114,L"114");
148TEMPLATE_CA_ENTRYPOINT(115,L"115");
149TEMPLATE_CA_ENTRYPOINT(116,L"116");
150TEMPLATE_CA_ENTRYPOINT(117,L"117");
151TEMPLATE_CA_ENTRYPOINT(118,L"118");
152TEMPLATE_CA_ENTRYPOINT(119,L"119");
153TEMPLATE_CA_ENTRYPOINT(120,L"120");
154TEMPLATE_CA_ENTRYPOINT(121,L"121");
155TEMPLATE_CA_ENTRYPOINT(122,L"122");
156TEMPLATE_CA_ENTRYPOINT(123,L"123");
157TEMPLATE_CA_ENTRYPOINT(124,L"124");
158TEMPLATE_CA_ENTRYPOINT(125,L"125");
159TEMPLATE_CA_ENTRYPOINT(126,L"126");
160TEMPLATE_CA_ENTRYPOINT(127,L"127");
161
162// Note: Keep in sync with EntryPoints.def
diff --git a/src/samples/Dtf/Tools/SfxCA/Extract.cpp b/src/samples/Dtf/Tools/SfxCA/Extract.cpp
new file mode 100644
index 00000000..171cf52f
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/Extract.cpp
@@ -0,0 +1,282 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5//---------------------------------------------------------------------
6// CABINET EXTRACTION
7//---------------------------------------------------------------------
8
9// Globals make this code unsuited for multhreaded use,
10// but FDI doesn't provide any other way to pass context.
11
12// Handle to the FDI (cab extraction) engine. Need access to this in a callback.
13static HFDI g_hfdi;
14
15// FDI is not unicode-aware, so avoid passing these paths through the callbacks.
16static const wchar_t* g_szExtractDir;
17static const wchar_t* g_szCabFile;
18
19// Offset into the source file where the cabinet really starts.
20// Used to trick FDI into extracting from a concatenated cabinet.
21static int g_lCabOffset;
22
23// Use the secure CRT version of _wsopen if available.
24#ifdef __GOT_SECURE_LIB__
25#define _wsopen__s(hf,file,oflag,shflag,pmode) _wsopen_s(&hf,file,oflag,shflag,pmode)
26#else
27#define _wsopen__s(hf,file,oflag,shflag,pmode) hf = _wsopen(file,oflag,shflag,pmode)
28#endif
29
30/// <summary>
31/// FDI callback to open a cabinet file.
32/// </summary>
33/// <param name="pszFile">Name of the file to be opened. This parameter
34/// is ignored since with our limited use this method is only ever called
35/// to open the main cabinet file.</param>
36/// <param name="oflag">Type of operations allowed.</param>
37/// <param name="pmode">Permission setting.</param>
38/// <returns>Integer file handle, or -1 if the file could not be opened.</returns>
39/// <remarks>
40/// To support reading from a cabinet that is concatenated onto
41/// another file, this function first searches for the offset of the cabinet,
42/// then saves that offset for use in recalculating later seeks.
43/// </remarks>
44static FNOPEN(CabOpen)
45{
46 UNREFERENCED_PARAMETER(pszFile);
47 int hf;
48 _wsopen__s(hf, g_szCabFile, oflag, _SH_DENYWR, pmode);
49 if (hf != -1)
50 {
51 FDICABINETINFO cabInfo;
52 int length = _lseek(hf, 0, SEEK_END);
53 for(int offset = 0; offset < length; offset += 256)
54 {
55 if (_lseek(hf, offset, SEEK_SET) != offset) break;
56 if (FDIIsCabinet(g_hfdi, hf, &cabInfo))
57 {
58 g_lCabOffset = offset;
59 _lseek(hf, offset, SEEK_SET);
60 return hf;
61 }
62 }
63 _close(hf);
64 }
65 return -1;
66}
67
68/// <summary>
69/// FDI callback to seek within a file.
70/// </summary>
71/// <param name="hf">File handle.</param>
72/// <param name="dist">Seek distance</param>
73/// <param name="seektype">Whether to seek relative to the
74/// beginning, current position, or end of the file.</param>
75/// <returns>Resultant position within the cabinet.</returns>
76/// <remarks>
77/// To support reading from a cabinet that is concatenated onto
78/// another file, this function recalculates seeks based on the
79/// offset that was determined when the cabinet was opened.
80/// </remarks>
81static FNSEEK(CabSeek)
82{
83 if (seektype == SEEK_SET) dist += g_lCabOffset;
84 int pos = _lseek((int) hf, dist, seektype);
85 pos -= g_lCabOffset;
86 return pos;
87}
88
89/// <summary>
90/// Ensures a directory and its parent directory path exists.
91/// </summary>
92/// <param name="szDirPath">Directory path, not including file name.</param>
93/// <returns>0 if the directory exists or was successfully created, else nonzero.</returns>
94/// <remarks>
95/// This function modifies characters in szDirPath, but always restores them
96/// regardless of error condition.
97/// </remarks>
98static int EnsureDirectoryExists(__inout_z wchar_t* szDirPath)
99{
100 int ret = 0;
101 if (!::CreateDirectoryW(szDirPath, NULL))
102 {
103 UINT err = ::GetLastError();
104 if (err != ERROR_ALREADY_EXISTS)
105 {
106 // Directory creation failed for some reason other than already existing.
107 // Try to create the parent directory first.
108 wchar_t* szLastSlash = NULL;
109 for (wchar_t* sz = szDirPath; *sz; sz++)
110 {
111 if (*sz == L'\\')
112 {
113 szLastSlash = sz;
114 }
115 }
116 if (szLastSlash)
117 {
118 // Temporarily take one directory off the path and recurse.
119 *szLastSlash = L'\0';
120 ret = EnsureDirectoryExists(szDirPath);
121 *szLastSlash = L'\\';
122
123 // Try to create the directory if all parents are created.
124 if (ret == 0 && !::CreateDirectoryW(szDirPath, NULL))
125 {
126 err = ::GetLastError();
127 if (err != ERROR_ALREADY_EXISTS)
128 {
129 ret = -1;
130 }
131 }
132 }
133 else
134 {
135 ret = -1;
136 }
137 }
138 }
139 return ret;
140}
141
142/// <summary>
143/// Ensures a file's directory and its parent directory path exists.
144/// </summary>
145/// <param name="szDirPath">Path including file name.</param>
146/// <returns>0 if the file's directory exists or was successfully created, else nonzero.</returns>
147/// <remarks>
148/// This function modifies characters in szFilePath, but always restores them
149/// regardless of error condition.
150/// </remarks>
151static int EnsureFileDirectoryExists(__inout_z wchar_t* szFilePath)
152{
153 int ret = 0;
154 wchar_t* szLastSlash = NULL;
155 for (wchar_t* sz = szFilePath; *sz; sz++)
156 {
157 if (*sz == L'\\')
158 {
159 szLastSlash = sz;
160 }
161 }
162 if (szLastSlash)
163 {
164 *szLastSlash = L'\0';
165 ret = EnsureDirectoryExists(szFilePath);
166 *szLastSlash = L'\\';
167 }
168 return ret;
169}
170
171/// <summary>
172/// FDI callback for handling files in the cabinet.
173/// </summary>
174/// <param name="fdint">Type of notification.</param>
175/// <param name="pfdin">Structure containing data about the notification.</param>
176/// <remarks>
177/// Refer to fdi.h for more comments on this notification callback.
178/// </remarks>
179static FNFDINOTIFY(CabNotification)
180{
181 // fdintCOPY_FILE:
182 // Called for each file that *starts* in the current cabinet, giving
183 // the client the opportunity to request that the file be copied or
184 // skipped.
185 // Entry:
186 // pfdin->psz1 = file name in cabinet
187 // pfdin->cb = uncompressed size of file
188 // pfdin->date = file date
189 // pfdin->time = file time
190 // pfdin->attribs = file attributes
191 // pfdin->iFolder = file's folder index
192 // Exit-Success:
193 // Return non-zero file handle for destination file; FDI writes
194 // data to this file use the PFNWRITE function supplied to FDICreate,
195 // and then calls fdintCLOSE_FILE_INFO to close the file and set
196 // the date, time, and attributes.
197 // Exit-Failure:
198 // Returns 0 => Skip file, do not copy
199 // Returns -1 => Abort FDICopy() call
200 if (fdint == fdintCOPY_FILE)
201 {
202 size_t cchFile = MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1, NULL, 0);
203 size_t cchFilePath = wcslen(g_szExtractDir) + 1 + cchFile;
204 wchar_t* szFilePath = (wchar_t*) _alloca((cchFilePath + 1) * sizeof(wchar_t));
205 if (szFilePath == NULL) return -1;
206 StringCchCopyW(szFilePath, cchFilePath + 1, g_szExtractDir);
207 StringCchCatW(szFilePath, cchFilePath + 1, L"\\");
208 MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1,
209 szFilePath + cchFilePath - cchFile, (int) cchFile + 1);
210 int hf = -1;
211 if (EnsureFileDirectoryExists(szFilePath) == 0)
212 {
213 _wsopen__s(hf, szFilePath,
214 _O_BINARY | _O_CREAT | _O_WRONLY | _O_SEQUENTIAL,
215 _SH_DENYWR, _S_IREAD | _S_IWRITE);
216 }
217 return hf;
218 }
219
220 // fdintCLOSE_FILE_INFO:
221 // Called after all of the data has been written to a target file.
222 // This function must close the file and set the file date, time,
223 // and attributes.
224 // Entry:
225 // pfdin->psz1 = file name in cabinet
226 // pfdin->hf = file handle
227 // pfdin->date = file date
228 // pfdin->time = file time
229 // pfdin->attribs = file attributes
230 // pfdin->iFolder = file's folder index
231 // pfdin->cb = Run After Extract (0 - don't run, 1 Run)
232 // Exit-Success:
233 // Returns TRUE
234 // Exit-Failure:
235 // Returns FALSE, or -1 to abort
236 else if (fdint == fdintCLOSE_FILE_INFO)
237 {
238 _close((int) pfdin->hf);
239 return TRUE;
240 }
241 return 0;
242}
243
244/// <summary>
245/// Extracts all contents of a cabinet file to a directory.
246/// </summary>
247/// <param name="szCabFile">Path to the cabinet file to be extracted.
248/// The cabinet may actually start at some offset within the file,
249/// as long as that offset is a multiple of 256.</param>
250/// <param name="szExtractDir">Directory where files are to be extracted.
251/// This directory must already exist, but should be empty.</param>
252/// <returns>0 if the cabinet was extracted successfully,
253/// or an error code if any error occurred.</returns>
254/// <remarks>
255/// The extraction will not overwrite any files in the destination
256/// directory; extraction will be interrupted and fail if any files
257/// with the same name already exist.
258/// </remarks>
259int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir)
260{
261 ERF erf;
262 // Most of the FDI callbacks can be handled by existing CRT I/O functions.
263 // For our functionality we only need to handle the open and seek callbacks.
264 HFDI hfdi = FDICreate((PFNALLOC) malloc, (PFNFREE) free, CabOpen,
265 (PFNREAD) _read, (PFNWRITE) _write, (PFNCLOSE) _close,
266 CabSeek, cpu80386, &erf);
267 if (hfdi != NULL)
268 {
269 g_hfdi = hfdi;
270 g_szCabFile = szCabFile;
271 g_szExtractDir = szExtractDir;
272 char szEmpty[1] = {0};
273 if (FDICopy(hfdi, szEmpty, szEmpty, 0, CabNotification, NULL, NULL))
274 {
275 FDIDestroy(hfdi);
276 return 0;
277 }
278 FDIDestroy(hfdi);
279 }
280
281 return erf.erfOper;
282}
diff --git a/src/samples/Dtf/Tools/SfxCA/RemoteMsi.cpp b/src/samples/Dtf/Tools/SfxCA/RemoteMsi.cpp
new file mode 100644
index 00000000..ba59fdf7
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/RemoteMsi.cpp
@@ -0,0 +1,629 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4#include "RemoteMsiSession.h"
5
6
7//
8// Ensures that the request buffer is large enough to hold a request,
9// reallocating the buffer if necessary.
10// It will also reduce the buffer size if the previous allocation was very large.
11//
12static __success(return == 0) UINT EnsureBufSize(__deref_out_ecount(*pcchBuf) wchar_t** pszBuf, __deref_inout DWORD* pcchBuf, DWORD cchRequired)
13{
14 // It will also reduce the buffer size if the previous allocation was very large.
15 if (*pcchBuf < cchRequired || (LARGE_BUFFER_THRESHOLD/2 < *pcchBuf && cchRequired < *pcchBuf))
16 {
17 if (*pszBuf != NULL)
18 {
19 SecureZeroMemory(*pszBuf, *pcchBuf);
20 delete[] *pszBuf;
21 }
22
23 *pcchBuf = max(MIN_BUFFER_STRING_SIZE, cchRequired);
24 *pszBuf = new wchar_t[*pcchBuf];
25
26 if (*pszBuf == NULL)
27 {
28 return ERROR_OUTOFMEMORY;
29 }
30 }
31
32 return ERROR_SUCCESS;
33}
34
35typedef int (WINAPI *PMsiFunc_I_I)(int in1, __out int* out1);
36typedef int (WINAPI *PMsiFunc_II_I)(int in1, int in2, __out int* out1);
37typedef int (WINAPI *PMsiFunc_IS_I)(int in1, __in_z wchar_t* in2, __out int* out1);
38typedef int (WINAPI *PMsiFunc_ISI_I)(int in1, __in_z wchar_t* in2, int in3, __out int* out1);
39typedef int (WINAPI *PMsiFunc_ISII_I)(int in1, __in_z wchar_t* in2, int in3, int in4, __out int* out1);
40typedef int (WINAPI *PMsiFunc_IS_II)(int in1, __in_z wchar_t* in2, __out int* out1, __out int* out2);
41typedef MSIDBERROR (WINAPI *PMsiEFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1);
42typedef int (WINAPI *PMsiFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1);
43typedef int (WINAPI *PMsiFunc_II_S)(int in1, int in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1);
44typedef int (WINAPI *PMsiFunc_IS_S)(int in1, __in_z wchar_t* in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1);
45typedef int (WINAPI *PMsiFunc_ISII_SII)(int in1, __in_z wchar_t* in2, int in3, int in4, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1, __out int* out2, __out int* out3);
46
47UINT MsiFunc_I_I(PMsiFunc_I_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
48{
49 int in1 = pReq->fields[0].iValue;
50 int out1;
51 UINT ret = (UINT) func(in1, &out1);
52 if (ret == 0)
53 {
54 pResp->fields[1].vt = VT_I4;
55 pResp->fields[1].iValue = out1;
56 }
57 return ret;
58}
59
60UINT MsiFunc_II_I(PMsiFunc_II_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
61{
62 int in1 = pReq->fields[0].iValue;
63 int in2 = pReq->fields[1].iValue;
64 int out1;
65 UINT ret = (UINT) func(in1, in2, &out1);
66 if (ret == 0)
67 {
68 pResp->fields[1].vt = VT_I4;
69 pResp->fields[1].iValue = out1;
70 }
71 return ret;
72}
73
74UINT MsiFunc_IS_I(PMsiFunc_IS_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
75{
76 int in1 = pReq->fields[0].iValue;
77 wchar_t* in2 = pReq->fields[1].szValue;
78 int out1;
79 UINT ret = (UINT) func(in1, in2, &out1);
80 if (ret == 0)
81 {
82 pResp->fields[1].vt = VT_I4;
83 pResp->fields[1].iValue = out1;
84 }
85 return ret;
86}
87
88UINT MsiFunc_ISI_I(PMsiFunc_ISI_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
89{
90 int in1 = pReq->fields[0].iValue;
91 wchar_t* in2 = pReq->fields[1].szValue;
92 int in3 = pReq->fields[2].iValue;
93 int out1;
94 UINT ret = (UINT) func(in1, in2, in3, &out1);
95 if (ret == 0)
96 {
97 pResp->fields[1].vt = VT_I4;
98 pResp->fields[1].iValue = out1;
99 }
100 return ret;
101}
102
103UINT MsiFunc_ISII_I(PMsiFunc_ISII_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
104{
105 int in1 = pReq->fields[0].iValue;
106 wchar_t* in2 = pReq->fields[1].szValue;
107 int in3 = pReq->fields[2].iValue;
108 int in4 = pReq->fields[3].iValue;
109 int out1;
110 UINT ret = (UINT) func(in1, in2, in3, in4, &out1);
111 if (ret == 0)
112 {
113 pResp->fields[1].vt = VT_I4;
114 pResp->fields[1].iValue = out1;
115 }
116 return ret;
117}
118
119UINT MsiFunc_IS_II(PMsiFunc_IS_II func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
120{
121 int in1 = pReq->fields[0].iValue;
122 wchar_t* in2 = pReq->fields[1].szValue;
123 int out1, out2;
124 UINT ret = (UINT) func(in1, in2, &out1, &out2);
125 if (ret == 0)
126 {
127 pResp->fields[1].vt = VT_I4;
128 pResp->fields[1].iValue = out1;
129 pResp->fields[2].vt = VT_I4;
130 pResp->fields[2].iValue = out2;
131 }
132 return ret;
133}
134
135UINT MsiFunc_I_S(PMsiFunc_I_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
136{
137 int in1 = pReq->fields[0].iValue;
138 szBuf[0] = L'\0';
139 DWORD cchValue = cchBuf;
140 UINT ret = (UINT) func(in1, szBuf, &cchValue);
141 if (ret == ERROR_MORE_DATA)
142 {
143 ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue);
144 if (ret == 0)
145 {
146 ret = (UINT) func(in1, szBuf, &cchValue);
147 }
148 }
149 if (ret == 0)
150 {
151 pResp->fields[1].vt = VT_LPWSTR;
152 pResp->fields[1].szValue = szBuf;
153 }
154 return ret;
155}
156
157MSIDBERROR MsiEFunc_I_S(PMsiEFunc_I_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
158{
159 int in1 = pReq->fields[0].iValue;
160 szBuf[0] = L'\0';
161 DWORD cchValue = cchBuf;
162 MSIDBERROR ret = func(in1, szBuf, &cchValue);
163 if (ret == MSIDBERROR_MOREDATA)
164 {
165 if (0 == EnsureBufSize(&szBuf, &cchBuf, ++cchValue))
166 {
167 ret = func(in1, szBuf, &cchValue);
168 }
169 }
170 if (ret != MSIDBERROR_MOREDATA)
171 {
172 pResp->fields[1].vt = VT_LPWSTR;
173 pResp->fields[1].szValue = szBuf;
174 }
175 return ret;
176}
177
178UINT MsiFunc_II_S(PMsiFunc_II_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
179{
180 int in1 = pReq->fields[0].iValue;
181 int in2 = pReq->fields[1].iValue;
182 szBuf[0] = L'\0';
183 DWORD cchValue = cchBuf;
184 UINT ret = (UINT) func(in1, in2, szBuf, &cchValue);
185 if (ret == ERROR_MORE_DATA)
186 {
187 ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue);
188 if (ret == 0)
189 {
190 ret = (UINT) func(in1, in2, szBuf, &cchValue);
191 }
192 }
193 if (ret == 0)
194 {
195 pResp->fields[1].vt = VT_LPWSTR;
196 pResp->fields[1].szValue = szBuf;
197 }
198 return ret;
199}
200
201UINT MsiFunc_IS_S(PMsiFunc_IS_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
202{
203 int in1 = pReq->fields[0].iValue;
204 wchar_t* in2 = pReq->fields[1].szValue;
205 szBuf[0] = L'\0';
206 DWORD cchValue = cchBuf;
207 UINT ret = (UINT) func(in1, in2, szBuf, &cchValue);
208 if (ret == ERROR_MORE_DATA)
209 {
210 ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue);
211 if (ret == 0)
212 {
213 ret = (UINT) func(in1, in2, szBuf, &cchValue);
214 }
215 }
216 if (ret == 0)
217 {
218 pResp->fields[1].vt = VT_LPWSTR;
219 pResp->fields[1].szValue = szBuf;
220 }
221 return ret;
222}
223
224UINT MsiFunc_ISII_SII(PMsiFunc_ISII_SII func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
225{
226 int in1 = pReq->fields[0].iValue;
227 wchar_t* in2 = pReq->fields[1].szValue;
228 int in3 = pReq->fields[2].iValue;
229 int in4 = pReq->fields[3].iValue;
230 szBuf[0] = L'\0';
231 DWORD cchValue = cchBuf;
232 int out2, out3;
233 UINT ret = (UINT) func(in1, in2, in3, in4, szBuf, &cchValue, &out2, &out3);
234 if (ret == ERROR_MORE_DATA)
235 {
236 ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue);
237 if (ret == 0)
238 {
239 ret = (UINT) func(in1, in2, in3, in4, szBuf, &cchValue, &out2, &out3);
240 }
241 }
242 if (ret == 0)
243 {
244 pResp->fields[1].vt = VT_LPWSTR;
245 pResp->fields[1].szValue = szBuf;
246 pResp->fields[2].vt = VT_I4;
247 pResp->fields[2].iValue = out2;
248 pResp->fields[3].vt = VT_I4;
249 pResp->fields[3].iValue = out3;
250 }
251 return ret;
252}
253
254void RemoteMsiSession::ProcessRequest(RequestId id, const RequestData* pReq, RequestData* pResp)
255{
256 SecureZeroMemory(pResp, sizeof(RequestData));
257
258 UINT ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, 1024);
259
260 if (0 == ret)
261 {
262 switch (id)
263 {
264 case RemoteMsiSession::EndSession:
265 {
266 this->ExitCode = pReq->fields[0].iValue;
267 }
268 break;
269 case RemoteMsiSession::MsiCloseHandle:
270 {
271 MSIHANDLE h = (MSIHANDLE) pReq->fields[0].iValue;
272 ret = ::MsiCloseHandle(h);
273 }
274 break;
275 case RemoteMsiSession::MsiProcessMessage:
276 {
277 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
278 INSTALLMESSAGE eMessageType = (INSTALLMESSAGE) pReq->fields[1].iValue;
279 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[2].iValue;
280 ret = ::MsiProcessMessage(hInstall, eMessageType, hRecord);
281 }
282 break;
283 case RemoteMsiSession::MsiGetProperty:
284 {
285 ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetProperty, pReq, pResp, m_pBufSend, m_cbBufSend);
286 }
287 break;
288 case RemoteMsiSession::MsiSetProperty:
289 {
290 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
291 const wchar_t* szName = pReq->fields[1].szValue;
292 const wchar_t* szValue = pReq->fields[2].szValue;
293 ret = ::MsiSetProperty(hInstall, szName, szValue);
294 }
295 break;
296 case RemoteMsiSession::MsiCreateRecord:
297 {
298 UINT cParams = pReq->fields[0].uiValue;
299 ret = ::MsiCreateRecord(cParams);
300 }
301 break;
302 case RemoteMsiSession::MsiRecordGetFieldCount:
303 {
304 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
305 ret = ::MsiRecordGetFieldCount(hRecord);
306 }
307 break;
308 case RemoteMsiSession::MsiRecordGetInteger:
309 {
310 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
311 UINT iField = pReq->fields[1].uiValue;
312 ret = ::MsiRecordGetInteger(hRecord, iField);
313 }
314 break;
315 case RemoteMsiSession::MsiRecordSetInteger:
316 {
317 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
318 UINT iField = pReq->fields[1].uiValue;
319 int iValue = pReq->fields[2].iValue;
320 ret = ::MsiRecordSetInteger(hRecord, iField, iValue);
321 }
322 break;
323 case RemoteMsiSession::MsiRecordGetString:
324 {
325 ret = MsiFunc_II_S((PMsiFunc_II_S) ::MsiRecordGetString, pReq, pResp, m_pBufSend, m_cbBufSend);
326 }
327 break;
328 case RemoteMsiSession::MsiRecordSetString:
329 {
330 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
331 UINT iField = pReq->fields[1].uiValue;
332 const wchar_t* szValue = pReq->fields[2].szValue;
333 ret = ::MsiRecordSetString(hRecord, iField, szValue);
334 }
335 break;
336 case RemoteMsiSession::MsiRecordClearData:
337 {
338 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
339 ret = ::MsiRecordClearData(hRecord);
340 }
341 break;
342 case RemoteMsiSession::MsiRecordIsNull:
343 {
344 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
345 UINT iField = pReq->fields[1].uiValue;
346 ret = ::MsiRecordIsNull(hRecord, iField);
347 }
348 break;
349 case RemoteMsiSession::MsiFormatRecord:
350 {
351 ret = MsiFunc_II_S((PMsiFunc_II_S) ::MsiFormatRecord, pReq, pResp, m_pBufSend, m_cbBufSend);
352 }
353 break;
354 case RemoteMsiSession::MsiGetActiveDatabase:
355 {
356 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
357 ret = (UINT) ::MsiGetActiveDatabase(hInstall);
358 }
359 break;
360 case RemoteMsiSession::MsiDatabaseOpenView:
361 {
362 ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiDatabaseOpenView, pReq, pResp);
363 }
364 break;
365 case RemoteMsiSession::MsiViewExecute:
366 {
367 MSIHANDLE hView = (MSIHANDLE) pReq->fields[0].iValue;
368 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[1].iValue;
369 ret = ::MsiViewExecute(hView, hRecord);
370 }
371 break;
372 case RemoteMsiSession::MsiViewFetch:
373 {
374 ret = MsiFunc_I_I((PMsiFunc_I_I) ::MsiViewFetch, pReq, pResp);
375 }
376 break;
377 case RemoteMsiSession::MsiViewModify:
378 {
379 MSIHANDLE hView = (MSIHANDLE) pReq->fields[0].iValue;
380 MSIMODIFY eModifyMode = (MSIMODIFY) pReq->fields[1].iValue;
381 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[2].iValue;
382 ret = ::MsiViewModify(hView, eModifyMode, hRecord);
383 }
384 break;
385 case RemoteMsiSession::MsiViewGetError:
386 {
387 ret = MsiEFunc_I_S((PMsiEFunc_I_S) ::MsiViewGetError, pReq, pResp, m_pBufSend, m_cbBufSend);
388 }
389 break;
390 case RemoteMsiSession::MsiViewGetColumnInfo:
391 {
392 ret = MsiFunc_II_I((PMsiFunc_II_I) ::MsiViewGetColumnInfo, pReq, pResp);
393 }
394 break;
395 case RemoteMsiSession::MsiDatabaseGetPrimaryKeys:
396 {
397 ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiDatabaseGetPrimaryKeys, pReq, pResp);
398 }
399 break;
400 case RemoteMsiSession::MsiDatabaseIsTablePersistent:
401 {
402 MSIHANDLE hDb = (MSIHANDLE) pReq->fields[0].iValue;
403 const wchar_t* szTable = pReq->fields[1].szValue;
404 ret = ::MsiDatabaseIsTablePersistent(hDb, szTable);
405 }
406 break;
407 case RemoteMsiSession::MsiDoAction:
408 {
409 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
410 const wchar_t* szAction = pReq->fields[1].szValue;
411 ret = ::MsiDoAction(hInstall, szAction);
412 }
413 break;
414 case RemoteMsiSession::MsiEnumComponentCosts:
415 {
416 ret = MsiFunc_ISII_SII((PMsiFunc_ISII_SII) ::MsiEnumComponentCosts, pReq, pResp, m_pBufSend, m_cbBufSend);
417 }
418 break;
419 case RemoteMsiSession::MsiEvaluateCondition:
420 {
421 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
422 const wchar_t* szCondition = pReq->fields[1].szValue;
423 ret = ::MsiEvaluateCondition(hInstall, szCondition);
424 }
425 break;
426 case RemoteMsiSession::MsiGetComponentState:
427 {
428 ret = MsiFunc_IS_II((PMsiFunc_IS_II) ::MsiGetComponentState, pReq, pResp);
429 }
430 break;
431 case RemoteMsiSession::MsiGetFeatureCost:
432 {
433 ret = MsiFunc_ISII_I((PMsiFunc_ISII_I) ::MsiGetFeatureCost, pReq, pResp);
434 }
435 break;
436 case RemoteMsiSession::MsiGetFeatureState:
437 {
438 ret = MsiFunc_IS_II((PMsiFunc_IS_II) ::MsiGetFeatureState, pReq, pResp);
439 }
440 break;
441 case RemoteMsiSession::MsiGetFeatureValidStates:
442 {
443 ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiGetFeatureValidStates, pReq, pResp);
444 }
445 break;
446 case RemoteMsiSession::MsiGetLanguage:
447 {
448 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
449 ret = ::MsiGetLanguage(hInstall);
450 }
451 break;
452 case RemoteMsiSession::MsiGetLastErrorRecord:
453 {
454 ret = ::MsiGetLastErrorRecord();
455 }
456 break;
457 case RemoteMsiSession::MsiGetMode:
458 {
459 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
460 MSIRUNMODE iRunMode = (MSIRUNMODE) pReq->fields[1].iValue;
461 ret = ::MsiGetMode(hInstall, iRunMode);
462 }
463 break;
464 case RemoteMsiSession::MsiGetSourcePath:
465 {
466 ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetSourcePath, pReq, pResp, m_pBufSend, m_cbBufSend);
467 }
468 break;
469 case RemoteMsiSession::MsiGetSummaryInformation:
470 {
471 ret = MsiFunc_ISI_I((PMsiFunc_ISI_I) ::MsiGetSummaryInformation, pReq, pResp);
472 }
473 break;
474 case RemoteMsiSession::MsiGetTargetPath:
475 {
476 ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetTargetPath, pReq, pResp, m_pBufSend, m_cbBufSend);
477 }
478 break;
479 case RemoteMsiSession::MsiRecordDataSize:
480 {
481 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
482 UINT iField = pReq->fields[1].uiValue;
483 ret = ::MsiRecordDataSize(hRecord, iField);
484 }
485 break;
486 case RemoteMsiSession::MsiRecordReadStream:
487 {
488 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
489 UINT iField = pReq->fields[1].uiValue;
490 DWORD cbRead = (DWORD) pReq->fields[2].uiValue;
491 ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, (cbRead + 1) / 2);
492 if (ret == 0)
493 {
494 ret = ::MsiRecordReadStream(hRecord, iField, (char*) m_pBufSend, &cbRead);
495 if (ret == 0)
496 {
497 pResp->fields[1].vt = VT_STREAM;
498 pResp->fields[1].szValue = m_pBufSend;
499 pResp->fields[2].vt = VT_I4;
500 pResp->fields[2].uiValue = (UINT) cbRead;
501 }
502 }
503 }
504 break;
505 case RemoteMsiSession::MsiRecordSetStream:
506 {
507 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
508 UINT iField = pReq->fields[1].uiValue;
509 const wchar_t* szFilePath = pReq->fields[2].szValue;
510 ret = ::MsiRecordSetStream(hRecord, iField, szFilePath);
511 }
512 break;
513 case RemoteMsiSession::MsiSequence:
514 {
515 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
516 const wchar_t* szTable = pReq->fields[1].szValue;
517 UINT iSequenceMode = pReq->fields[2].uiValue;
518 ret = ::MsiSequence(hRecord, szTable, iSequenceMode);
519 }
520 break;
521 case RemoteMsiSession::MsiSetComponentState:
522 {
523 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
524 const wchar_t* szComponent = pReq->fields[1].szValue;
525 INSTALLSTATE iState = (INSTALLSTATE) pReq->fields[2].iValue;
526 ret = ::MsiSetComponentState(hInstall, szComponent, iState);
527 }
528 break;
529 case RemoteMsiSession::MsiSetFeatureAttributes:
530 {
531 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
532 const wchar_t* szFeature = pReq->fields[1].szValue;
533 DWORD dwAttrs = (DWORD) pReq->fields[2].uiValue;
534 ret = ::MsiSetFeatureAttributes(hInstall, szFeature, dwAttrs);
535 }
536 break;
537 case RemoteMsiSession::MsiSetFeatureState:
538 {
539 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
540 const wchar_t* szFeature = pReq->fields[1].szValue;
541 INSTALLSTATE iState = (INSTALLSTATE) pReq->fields[2].iValue;
542 ret = ::MsiSetFeatureState(hInstall, szFeature, iState);
543 }
544 break;
545 case RemoteMsiSession::MsiSetInstallLevel:
546 {
547 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
548 int iInstallLevel = pReq->fields[1].iValue;
549 ret = ::MsiSetInstallLevel(hInstall, iInstallLevel);
550 }
551 break;
552 case RemoteMsiSession::MsiSetMode:
553 {
554 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
555 MSIRUNMODE iRunMode = (MSIRUNMODE) pReq->fields[1].uiValue;
556 BOOL fState = (BOOL) pReq->fields[2].iValue;
557 ret = ::MsiSetMode(hInstall, iRunMode, fState);
558 }
559 break;
560 case RemoteMsiSession::MsiSetTargetPath:
561 {
562 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
563 const wchar_t* szFolder = pReq->fields[1].szValue;
564 const wchar_t* szFolderPath = pReq->fields[2].szValue;
565 ret = ::MsiSetTargetPath(hInstall, szFolder, szFolderPath);
566 }
567 break;
568 case RemoteMsiSession::MsiSummaryInfoGetProperty:
569 {
570 MSIHANDLE hSummaryInfo = (MSIHANDLE) pReq->fields[0].iValue;
571 UINT uiProperty = pReq->fields[1].uiValue;
572 UINT uiDataType;
573 int iValue;
574 FILETIME ftValue;
575 m_pBufSend[0] = L'\0';
576 DWORD cchValue = m_cbBufSend;
577 ret = ::MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, &uiDataType, &iValue, &ftValue, m_pBufSend, &cchValue);
578 if (ret == ERROR_MORE_DATA)
579 {
580 ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, ++cchValue);
581 if (ret == 0)
582 {
583 ret = ::MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, &uiDataType, &iValue, &ftValue, m_pBufSend, &cchValue);
584 }
585 }
586 if (ret == 0)
587 {
588 pResp->fields[1].vt = VT_UI4;
589 pResp->fields[1].uiValue = uiDataType;
590
591 switch (uiDataType)
592 {
593 case VT_I2:
594 case VT_I4:
595 pResp->fields[2].vt = VT_I4;
596 pResp->fields[2].iValue = iValue;
597 break;
598 case VT_FILETIME:
599 pResp->fields[2].vt = VT_UI4;
600 pResp->fields[2].iValue = ftValue.dwHighDateTime;
601 pResp->fields[3].vt = VT_UI4;
602 pResp->fields[3].iValue = ftValue.dwLowDateTime;
603 break;
604 case VT_LPSTR:
605 pResp->fields[2].vt = VT_LPWSTR;
606 pResp->fields[2].szValue = m_pBufSend;
607 break;
608 }
609 }
610 }
611 break;
612 case RemoteMsiSession::MsiVerifyDiskSpace:
613 {
614 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
615 ret = ::MsiVerifyDiskSpace(hInstall);
616 }
617 break;
618
619 default:
620 {
621 ret = ERROR_INVALID_FUNCTION;
622 }
623 break;
624 }
625 }
626
627 pResp->fields[0].vt = VT_UI4;
628 pResp->fields[0].uiValue = ret;
629}
diff --git a/src/samples/Dtf/Tools/SfxCA/RemoteMsiSession.h b/src/samples/Dtf/Tools/SfxCA/RemoteMsiSession.h
new file mode 100644
index 00000000..90c7c01f
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/RemoteMsiSession.h
@@ -0,0 +1,898 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#define LARGE_BUFFER_THRESHOLD 65536 // bytes
4#define MIN_BUFFER_STRING_SIZE 1024 // wchar_ts
5
6///////////////////////////////////////////////////////////////////////////////////////
7// RemoteMsiSession //
8//////////////////////
9//
10// Allows accessing MSI APIs from another process using named pipes.
11//
12class RemoteMsiSession
13{
14public:
15
16 // This enumeration MUST stay in sync with the
17 // managed equivalent in RemotableNativeMethods.cs!
18 enum RequestId
19 {
20 EndSession = 0,
21 MsiCloseHandle,
22 MsiCreateRecord,
23 MsiDatabaseGetPrimaryKeys,
24 MsiDatabaseIsTablePersistent,
25 MsiDatabaseOpenView,
26 MsiDoAction,
27 MsiEnumComponentCosts,
28 MsiEvaluateCondition,
29 MsiFormatRecord,
30 MsiGetActiveDatabase,
31 MsiGetComponentState,
32 MsiGetFeatureCost,
33 MsiGetFeatureState,
34 MsiGetFeatureValidStates,
35 MsiGetLanguage,
36 MsiGetLastErrorRecord,
37 MsiGetMode,
38 MsiGetProperty,
39 MsiGetSourcePath,
40 MsiGetSummaryInformation,
41 MsiGetTargetPath,
42 MsiProcessMessage,
43 MsiRecordClearData,
44 MsiRecordDataSize,
45 MsiRecordGetFieldCount,
46 MsiRecordGetInteger,
47 MsiRecordGetString,
48 MsiRecordIsNull,
49 MsiRecordReadStream,
50 MsiRecordSetInteger,
51 MsiRecordSetStream,
52 MsiRecordSetString,
53 MsiSequence,
54 MsiSetComponentState,
55 MsiSetFeatureAttributes,
56 MsiSetFeatureState,
57 MsiSetInstallLevel,
58 MsiSetMode,
59 MsiSetProperty,
60 MsiSetTargetPath,
61 MsiSummaryInfoGetProperty,
62 MsiVerifyDiskSpace,
63 MsiViewExecute,
64 MsiViewFetch,
65 MsiViewGetError,
66 MsiViewGetColumnInfo,
67 MsiViewModify,
68 };
69
70 static const int MAX_REQUEST_FIELDS = 4;
71
72 // Used to pass data back and forth for remote API calls,
73 // including in & out params & return values.
74 // Only strings and ints are supported.
75 struct RequestData
76 {
77 struct
78 {
79 VARENUM vt;
80 union {
81 int iValue;
82 UINT uiValue;
83 DWORD cchValue;
84 LPWSTR szValue;
85 BYTE* sValue;
86 DWORD cbValue;
87 };
88 } fields[MAX_REQUEST_FIELDS];
89 };
90
91public:
92
93 // This value is set from the single data parameter in the EndSession request.
94 // It saves the exit code of the out-of-proc custom action.
95 int ExitCode;
96
97 /////////////////////////////////////////////////////////////////////////////////////
98 // RemoteMsiSession constructor
99 //
100 // Creates a new remote session instance, for use either by the server
101 // or client process.
102 //
103 // szName - Identifies the session instance being remoted. The server and
104 // the client must use the same name. The name should be unique
105 // enough to avoid conflicting with other instances on the system.
106 //
107 // fServer - True if the calling process is the server process, false if the
108 // calling process is the client process.
109 //
110 RemoteMsiSession(const wchar_t* szName, bool fServer=true)
111 : m_fServer(fServer),
112 m_szName(szName != NULL && szName[0] != L'\0' ? szName : L"RemoteMsiSession"),
113 m_szPipeName(NULL),
114 m_hPipe(NULL),
115 m_fConnecting(false),
116 m_fConnected(false),
117 m_hReceiveThread(NULL),
118 m_hReceiveStopEvent(NULL),
119 m_pBufReceive(NULL),
120 m_cbBufReceive(0),
121 m_pBufSend(NULL),
122 m_cbBufSend(0),
123 ExitCode(ERROR_INSTALL_FAILURE)
124 {
125 SecureZeroMemory(&m_overlapped, sizeof(OVERLAPPED));
126 m_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
127 }
128
129 /////////////////////////////////////////////////////////////////////////////////////
130 // RemoteMsiSession destructor
131 //
132 // Closes any open handles and frees any allocated memory.
133 //
134 ~RemoteMsiSession()
135 {
136 WaitExitCode();
137 if (m_hPipe != NULL)
138 {
139 CloseHandle(m_hPipe);
140 m_hPipe = NULL;
141 }
142 if (m_overlapped.hEvent != NULL)
143 {
144 CloseHandle(m_overlapped.hEvent);
145 m_overlapped.hEvent = NULL;
146 }
147 if (m_szPipeName != NULL)
148 {
149 delete[] m_szPipeName;
150 m_szPipeName = NULL;
151 }
152 if (m_pBufReceive != NULL)
153 {
154 SecureZeroMemory(m_pBufReceive, m_cbBufReceive);
155 delete[] m_pBufReceive;
156 m_pBufReceive = NULL;
157 }
158 if (m_pBufSend != NULL)
159 {
160 SecureZeroMemory(m_pBufSend, m_cbBufSend);
161 delete[] m_pBufSend;
162 m_pBufSend = NULL;
163 }
164 m_fConnecting = false;
165 m_fConnected = false;
166 }
167
168 /////////////////////////////////////////////////////////////////////////////////////
169 // RemoteMsiSession::WaitExitCode()
170 //
171 // Waits for the server processing thread to complete.
172 //
173 void WaitExitCode()
174 {
175 if (m_hReceiveThread != NULL)
176 {
177 SetEvent(m_hReceiveStopEvent);
178 WaitForSingleObject(m_hReceiveThread, INFINITE);
179 CloseHandle(m_hReceiveThread);
180 m_hReceiveThread = NULL;
181 }
182 }
183
184 /////////////////////////////////////////////////////////////////////////////////////
185 // RemoteMsiSession::Connect()
186 //
187 // Connects the inter-process communication channel.
188 // (Currently implemented as a named pipe.)
189 //
190 // This method must be called first by the server process, then by the client
191 // process. The method does not block; the server will asynchronously wait
192 // for the client process to make the connection.
193 //
194 // Returns: 0 on success, Win32 error code on failure.
195 //
196 virtual DWORD Connect()
197 {
198 const wchar_t* szPipePrefix = L"\\\\.\\pipe\\";
199 size_t cchPipeNameBuf = wcslen(szPipePrefix) + wcslen(m_szName) + 1;
200 m_szPipeName = new wchar_t[cchPipeNameBuf];
201
202 if (m_szPipeName == NULL)
203 {
204 return ERROR_OUTOFMEMORY;
205 }
206 else
207 {
208 wcscpy_s(m_szPipeName, cchPipeNameBuf, szPipePrefix);
209 wcscat_s(m_szPipeName, cchPipeNameBuf, m_szName);
210
211 if (m_fServer)
212 {
213 return this->ConnectPipeServer();
214 }
215 else
216 {
217 return this->ConnectPipeClient();
218 }
219 }
220 }
221
222 /////////////////////////////////////////////////////////////////////////////////////
223 // RemoteMsiSession::IsConnected()
224 //
225 // Checks if the server process and client process are currently connected.
226 //
227 virtual bool IsConnected() const
228 {
229 return m_fConnected;
230 }
231
232 /////////////////////////////////////////////////////////////////////////////////////
233 // RemoteMsiSession::ProcessRequests()
234 //
235 // For use by the service process. Watches for requests in the input buffer and calls
236 // the callback for each one.
237 //
238 // This method does not block; it spawns a separate thread to do the work.
239 //
240 // Returns: 0 on success, Win32 error code on failure.
241 //
242 virtual DWORD ProcessRequests()
243 {
244 return this->StartProcessingReqests();
245 }
246
247 /////////////////////////////////////////////////////////////////////////////////////
248 // RemoteMsiSession::SendRequest()
249 //
250 // For use by the client process. Sends a request to the server and
251 // synchronously waits on a response, up to the timeout value.
252 //
253 // id - ID code of the MSI API call being requested.
254 //
255 // pRequest - Pointer to a data structure containing request parameters.
256 //
257 // ppResponse - [OUT] Pointer to a location that receives the response parameters.
258 //
259 // Returns: 0 on success, Win32 error code on failure.
260 // Returns WAIT_TIMEOUT if no response was received in time.
261 //
262 virtual DWORD SendRequest(RequestId id, const RequestData* pRequest, RequestData** ppResponse)
263 {
264 if (m_fServer)
265 {
266 return ERROR_INVALID_OPERATION;
267 }
268
269 if (!m_fConnected)
270 {
271 *ppResponse = NULL;
272 return 0;
273 }
274
275 DWORD dwRet = this->SendRequest(id, pRequest);
276 if (dwRet != 0)
277 {
278 return dwRet;
279 }
280
281 if (id != EndSession)
282 {
283 static RequestData response;
284 if (ppResponse != NULL)
285 {
286 *ppResponse = &response;
287 }
288
289 return this->ReceiveResponse(id, &response);
290 }
291 else
292 {
293 CloseHandle(m_hPipe);
294 m_hPipe = NULL;
295 m_fConnected = false;
296 return 0;
297 }
298 }
299
300private:
301
302 //
303 // Do not allow assignment.
304 //
305 RemoteMsiSession& operator=(const RemoteMsiSession&);
306
307 //
308 // Called only by the server process.
309 // Create a new thread to handle receiving requests.
310 //
311 DWORD StartProcessingReqests()
312 {
313 if (!m_fServer || m_hReceiveStopEvent != NULL)
314 {
315 return ERROR_INVALID_OPERATION;
316 }
317
318 DWORD dwRet = 0;
319
320 m_hReceiveStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
321
322 if (m_hReceiveStopEvent == NULL)
323 {
324 dwRet = GetLastError();
325 }
326 else
327 {
328 if (m_hReceiveThread != NULL)
329 {
330 CloseHandle(m_hReceiveThread);
331 }
332
333 m_hReceiveThread = CreateThread(NULL, 0,
334 RemoteMsiSession::ProcessRequestsThreadStatic, this, 0, NULL);
335
336 if (m_hReceiveThread == NULL)
337 {
338 dwRet = GetLastError();
339 CloseHandle(m_hReceiveStopEvent);
340 m_hReceiveStopEvent = NULL;
341 }
342 }
343
344 return dwRet;
345 }
346
347 //
348 // Called only by the watcher process.
349 // First verify the connection is complete. Then continually read and parse messages,
350 // invoke the callback, and send the replies.
351 //
352 static DWORD WINAPI ProcessRequestsThreadStatic(void* pv)
353 {
354 return reinterpret_cast<RemoteMsiSession*>(pv)->ProcessRequestsThread();
355 }
356
357 DWORD ProcessRequestsThread()
358 {
359 DWORD dwRet;
360
361 dwRet = CompleteConnection();
362 if (dwRet != 0)
363 {
364 if (dwRet == ERROR_OPERATION_ABORTED) dwRet = 0;
365 }
366
367 while (m_fConnected)
368 {
369 RequestId id;
370 RequestData req;
371 dwRet = ReceiveRequest(&id, &req);
372 if (dwRet != 0)
373 {
374 if (dwRet == ERROR_OPERATION_ABORTED ||
375 dwRet == ERROR_BROKEN_PIPE || dwRet == ERROR_NO_DATA)
376 {
377 dwRet = 0;
378 }
379 }
380 else
381 {
382 RequestData resp;
383 ProcessRequest(id, &req, &resp);
384
385 if (id == EndSession)
386 {
387 break;
388 }
389
390 dwRet = SendResponse(id, &resp);
391 if (dwRet != 0 && dwRet != ERROR_BROKEN_PIPE && dwRet != ERROR_NO_DATA)
392 {
393 dwRet = 0;
394 }
395 }
396 }
397
398 CloseHandle(m_hReceiveStopEvent);
399 m_hReceiveStopEvent = NULL;
400 return dwRet;
401 }
402
403 //
404 // Called only by the server process's receive thread.
405 // Read one request into a RequestData object.
406 //
407 DWORD ReceiveRequest(RequestId* pId, RequestData* pReq)
408 {
409 DWORD dwRet = this->ReadPipe((BYTE*) pId, sizeof(RequestId));
410
411 if (dwRet == 0)
412 {
413 dwRet = this->ReadRequestData(pReq);
414 }
415
416 return dwRet;
417 }
418
419 //
420 // Called by the server process's receive thread or the client's request call
421 // to read the response. Read data from the pipe, allowing interruption by the
422 // stop event if on the server.
423 //
424 DWORD ReadPipe(__out_bcount(cbRead) BYTE* pBuf, DWORD cbRead)
425 {
426 DWORD dwRet = 0;
427 DWORD dwTotalBytesRead = 0;
428
429 while (dwRet == 0 && dwTotalBytesRead < cbRead)
430 {
431 DWORD dwBytesReadThisTime;
432 ResetEvent(m_overlapped.hEvent);
433 if (!ReadFile(m_hPipe, pBuf + dwTotalBytesRead, cbRead - dwTotalBytesRead, &dwBytesReadThisTime, &m_overlapped))
434 {
435 dwRet = GetLastError();
436 if (dwRet == ERROR_IO_PENDING)
437 {
438 if (m_fServer)
439 {
440 HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent };
441 dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE);
442 }
443 else
444 {
445 dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE);
446 }
447
448 if (dwRet == WAIT_OBJECT_0)
449 {
450 if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesReadThisTime, FALSE))
451 {
452 dwRet = GetLastError();
453 }
454 }
455 else if (dwRet == WAIT_FAILED)
456 {
457 dwRet = GetLastError();
458 }
459 else
460 {
461 dwRet = ERROR_OPERATION_ABORTED;
462 }
463 }
464 }
465
466 dwTotalBytesRead += dwBytesReadThisTime;
467 }
468
469 if (dwRet != 0)
470 {
471 if (m_fServer)
472 {
473 CancelIo(m_hPipe);
474 DisconnectNamedPipe(m_hPipe);
475 }
476 else
477 {
478 CloseHandle(m_hPipe);
479 m_hPipe = NULL;
480 }
481 m_fConnected = false;
482 }
483
484 return dwRet;
485 }
486
487 //
488 // Called only by the server process.
489 // Given a request, invoke the MSI API and return the response.
490 // This is implemented in RemoteMsi.cpp.
491 //
492 void ProcessRequest(RequestId id, const RequestData* pReq, RequestData* pResp);
493
494 //
495 // Called only by the client process.
496 // Send request data over the pipe.
497 //
498 DWORD SendRequest(RequestId id, const RequestData* pRequest)
499 {
500 DWORD dwRet = WriteRequestData(id, pRequest);
501
502 if (dwRet != 0)
503 {
504 m_fConnected = false;
505 CloseHandle(m_hPipe);
506 m_hPipe = NULL;
507 }
508
509 return dwRet;
510 }
511
512 //
513 // Called only by the server process.
514 // Just send a response over the pipe.
515 //
516 DWORD SendResponse(RequestId id, const RequestData* pResp)
517 {
518 DWORD dwRet = WriteRequestData(id, pResp);
519
520 if (dwRet != 0)
521 {
522 DisconnectNamedPipe(m_hPipe);
523 m_fConnected = false;
524 }
525
526 return dwRet;
527 }
528
529 //
530 // Called either by the client or server process.
531 // Writes data to the pipe for a request or response.
532 //
533 DWORD WriteRequestData(RequestId id, const RequestData* pReq)
534 {
535 DWORD dwRet = 0;
536
537 RequestData req = *pReq; // Make a copy because the const data can't be changed.
538
539 dwRet = this->WritePipe((const BYTE *)&id, sizeof(RequestId));
540 if (dwRet != 0)
541 {
542 return dwRet;
543 }
544
545 BYTE* sValues[MAX_REQUEST_FIELDS] = {0};
546 for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
547 {
548 if (req.fields[i].vt == VT_LPWSTR)
549 {
550 sValues[i] = (BYTE*) req.fields[i].szValue;
551 req.fields[i].cchValue = (DWORD) wcslen(req.fields[i].szValue);
552 }
553 else if (req.fields[i].vt == VT_STREAM)
554 {
555 sValues[i] = req.fields[i].sValue;
556 req.fields[i].cbValue = (DWORD) req.fields[i + 1].uiValue;
557 }
558 }
559
560 dwRet = this->WritePipe((const BYTE *)&req, sizeof(RequestData));
561 if (dwRet != 0)
562 {
563 return dwRet;
564 }
565
566 for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
567 {
568 if (sValues[i] != NULL)
569 {
570 DWORD cbValue;
571 if (req.fields[i].vt == VT_LPWSTR)
572 {
573 cbValue = (req.fields[i].cchValue + 1) * sizeof(WCHAR);
574 }
575 else
576 {
577 cbValue = req.fields[i].cbValue;
578 }
579
580 dwRet = this->WritePipe(const_cast<BYTE*> (sValues[i]), cbValue);
581 if (dwRet != 0)
582 {
583 break;
584 }
585 }
586 }
587
588 return dwRet;
589 }
590
591 //
592 // Called when writing a request or response. Writes data to
593 // the pipe, allowing interruption by the stop event if on the server.
594 //
595 DWORD WritePipe(const BYTE* pBuf, DWORD cbWrite)
596 {
597 DWORD dwRet = 0;
598 DWORD dwTotalBytesWritten = 0;
599
600 while (dwRet == 0 && dwTotalBytesWritten < cbWrite)
601 {
602 DWORD dwBytesWrittenThisTime;
603 ResetEvent(m_overlapped.hEvent);
604 if (!WriteFile(m_hPipe, pBuf + dwTotalBytesWritten, cbWrite - dwTotalBytesWritten, &dwBytesWrittenThisTime, &m_overlapped))
605 {
606 dwRet = GetLastError();
607 if (dwRet == ERROR_IO_PENDING)
608 {
609 if (m_fServer)
610 {
611 HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent };
612 dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE);
613 }
614 else
615 {
616 dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE);
617 }
618
619 if (dwRet == WAIT_OBJECT_0)
620 {
621 if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesWrittenThisTime, FALSE))
622 {
623 dwRet = GetLastError();
624 }
625 }
626 else if (dwRet == WAIT_FAILED)
627 {
628 dwRet = GetLastError();
629 }
630 else
631 {
632 dwRet = ERROR_OPERATION_ABORTED;
633 }
634 }
635 }
636
637 dwTotalBytesWritten += dwBytesWrittenThisTime;
638 }
639
640 return dwRet;
641 }
642
643 //
644 // Called either by the client or server process.
645 // Reads data from the pipe for a request or response.
646 //
647 DWORD ReadRequestData(RequestData* pReq)
648 {
649 DWORD dwRet = ReadPipe((BYTE*) pReq, sizeof(RequestData));
650
651 if (dwRet == 0)
652 {
653 DWORD cbData = 0;
654 for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
655 {
656 if (pReq->fields[i].vt == VT_LPWSTR)
657 {
658 cbData += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR);
659 }
660 else if (pReq->fields[i].vt == VT_STREAM)
661 {
662 cbData += pReq->fields[i].cbValue;
663 }
664 }
665
666 if (cbData > 0)
667 {
668 if (!CheckRequestDataBuf(cbData))
669 {
670 return ERROR_OUTOFMEMORY;
671 }
672
673 dwRet = this->ReadPipe((BYTE*) m_pBufReceive, cbData);
674 if (dwRet == 0)
675 {
676 DWORD dwOffset = 0;
677 for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
678 {
679 if (pReq->fields[i].vt == VT_LPWSTR)
680 {
681 LPWSTR szTemp = (LPWSTR) (m_pBufReceive + dwOffset);
682 dwOffset += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR);
683 pReq->fields[i].szValue = szTemp;
684 }
685 else if (pReq->fields[i].vt == VT_STREAM)
686 {
687 BYTE* sTemp = m_pBufReceive + dwOffset;
688 dwOffset += pReq->fields[i].cbValue;
689 pReq->fields[i].sValue = sTemp;
690 }
691 }
692 }
693 }
694 }
695
696 return dwRet;
697 }
698
699 //
700 // Called only by the client process.
701 // Wait for a response on the pipe. If no response is received before the timeout,
702 // then give up and close the connection.
703 //
704 DWORD ReceiveResponse(RequestId id, RequestData* pResp)
705 {
706 RequestId responseId;
707 DWORD dwRet = ReadPipe((BYTE*) &responseId, sizeof(RequestId));
708 if (dwRet == 0 && responseId != id)
709 {
710 dwRet = ERROR_OPERATION_ABORTED;
711 }
712
713 if (dwRet == 0)
714 {
715 dwRet = this->ReadRequestData(pResp);
716 }
717
718 return dwRet;
719 }
720
721 //
722 // Called only by the server process's receive thread.
723 // Try to complete and verify an asynchronous connection operation.
724 //
725 DWORD CompleteConnection()
726 {
727 DWORD dwRet = 0;
728 if (m_fConnecting)
729 {
730 HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent };
731 DWORD dwWaitRes = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE);
732
733 if (dwWaitRes == WAIT_OBJECT_0)
734 {
735 m_fConnecting = false;
736
737 DWORD dwUnused;
738 if (GetOverlappedResult(m_hPipe, &m_overlapped, &dwUnused, FALSE))
739 {
740 m_fConnected = true;
741 }
742 else
743 {
744 dwRet = GetLastError();
745 }
746 }
747 else if (dwWaitRes == WAIT_FAILED)
748 {
749 CancelIo(m_hPipe);
750 dwRet = GetLastError();
751 }
752 else
753 {
754 CancelIo(m_hPipe);
755 dwRet = ERROR_OPERATION_ABORTED;
756 }
757 }
758 return dwRet;
759 }
760
761 //
762 // Called only by the server process.
763 // Creates a named pipe instance and begins asynchronously waiting
764 // for a connection from the client process.
765 //
766 DWORD ConnectPipeServer()
767 {
768 DWORD dwRet = 0;
769 const int BUFSIZE = 1024; // Suggested pipe I/O buffer sizes
770 m_hPipe = CreateNamedPipe(
771 m_szPipeName,
772 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE,
773 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,
774 1, BUFSIZE, BUFSIZE, 0, NULL);
775 if (m_hPipe == INVALID_HANDLE_VALUE)
776 {
777 m_hPipe = NULL;
778 dwRet = GetLastError();
779 }
780 else if (ConnectNamedPipe(m_hPipe, &m_overlapped))
781 {
782 m_fConnected = true;
783 }
784 else
785 {
786 dwRet = GetLastError();
787
788 if (dwRet == ERROR_PIPE_BUSY)
789 {
790 // All pipe instances are busy, so wait for a maximum of 20 seconds
791 dwRet = 0;
792 if (WaitNamedPipe(m_szPipeName, 20000))
793 {
794 m_fConnected = true;
795 }
796 else
797 {
798 dwRet = GetLastError();
799 }
800 }
801
802 if (dwRet == ERROR_IO_PENDING)
803 {
804 dwRet = 0;
805 m_fConnecting = true;
806 }
807 }
808 return dwRet;
809 }
810
811 //
812 // Called only by the client process.
813 // Attemps to open a connection to an existing named pipe instance
814 // which should have already been created by the server process.
815 //
816 DWORD ConnectPipeClient()
817 {
818 DWORD dwRet = 0;
819 m_hPipe = CreateFile(
820 m_szPipeName, GENERIC_READ | GENERIC_WRITE,
821 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
822 if (m_hPipe != INVALID_HANDLE_VALUE)
823 {
824 m_fConnected = true;
825 }
826 else
827 {
828 m_hPipe = NULL;
829 dwRet = GetLastError();
830 }
831 return dwRet;
832 }
833
834 //
835 // Ensures that the request buffer is large enough to hold a request,
836 // reallocating the buffer if necessary.
837 // It will also reduce the buffer size if the previous allocation was very large.
838 //
839 BOOL CheckRequestDataBuf(DWORD cbBuf)
840 {
841 if (m_cbBufReceive < cbBuf || (LARGE_BUFFER_THRESHOLD < m_cbBufReceive && cbBuf < m_cbBufReceive))
842 {
843 if (m_pBufReceive != NULL)
844 {
845 SecureZeroMemory(m_pBufReceive, m_cbBufReceive);
846 delete[] m_pBufReceive;
847 }
848 m_cbBufReceive = max(MIN_BUFFER_STRING_SIZE*2, cbBuf);
849 m_pBufReceive = new BYTE[m_cbBufReceive];
850 if (m_pBufReceive == NULL)
851 {
852 m_cbBufReceive = 0;
853 }
854 }
855 return m_pBufReceive != NULL;
856 }
857
858private:
859
860 // Name of this instance.
861 const wchar_t* m_szName;
862
863 // "\\.\pipe\name"
864 wchar_t* m_szPipeName;
865
866 // Handle to the pipe instance.
867 HANDLE m_hPipe;
868
869 // Handle to the thread that receives requests.
870 HANDLE m_hReceiveThread;
871
872 // Handle to the event used to signal the receive thread to exit.
873 HANDLE m_hReceiveStopEvent;
874
875 // All pipe I/O is done in overlapped mode to avoid unintentional blocking.
876 OVERLAPPED m_overlapped;
877
878 // Dynamically-resized buffer for receiving requests.
879 BYTE* m_pBufReceive;
880
881 // Current size of the receive request buffer.
882 DWORD m_cbBufReceive;
883
884 // Dynamically-resized buffer for sending requests.
885 wchar_t* m_pBufSend;
886
887 // Current size of the send request buffer.
888 DWORD m_cbBufSend;
889
890 // True if this is the server process, false if this is the client process.
891 const bool m_fServer;
892
893 // True if an asynchronous connection operation is currently in progress.
894 bool m_fConnecting;
895
896 // True if the pipe is currently connected.
897 bool m_fConnected;
898};
diff --git a/src/samples/Dtf/Tools/SfxCA/SfxCA.cpp b/src/samples/Dtf/Tools/SfxCA/SfxCA.cpp
new file mode 100644
index 00000000..06319f1e
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/SfxCA.cpp
@@ -0,0 +1,363 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4#include "EntryPoints.h"
5#include "SfxUtil.h"
6
7#define MANAGED_CAs_OUT_OF_PROC 1
8
9HMODULE g_hModule;
10bool g_fRunningOutOfProc = false;
11
12RemoteMsiSession* g_pRemote = NULL;
13
14// Prototypes for local functions.
15// See the function definitions for comments.
16
17bool InvokeManagedCustomAction(MSIHANDLE hSession,
18 _AppDomain* pAppDomain, const wchar_t* szEntryPoint, int* piResult);
19
20/// <summary>
21/// Entry-point for the CA DLL when re-launched as a separate process;
22/// connects the comm channel for remote MSI APIs, then invokes the
23/// managed custom action entry-point.
24/// </summary>
25/// <remarks>
26/// Do not change the parameters or calling-convention: RUNDLL32
27/// requires this exact signature.
28/// </remarks>
29extern "C"
30void __stdcall InvokeManagedCustomActionOutOfProc(
31 __in HWND hwnd, __in HINSTANCE hinst, __in_z wchar_t* szCmdLine, int nCmdShow)
32{
33 UNREFERENCED_PARAMETER(hwnd);
34 UNREFERENCED_PARAMETER(hinst);
35 UNREFERENCED_PARAMETER(nCmdShow);
36
37 g_fRunningOutOfProc = true;
38
39 const wchar_t* szSessionName = szCmdLine;
40 MSIHANDLE hSession;
41 const wchar_t* szEntryPoint;
42
43 int i;
44 for (i = 0; szCmdLine[i] && szCmdLine[i] != L' '; i++);
45 if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0';
46 hSession = _wtoi(szCmdLine + i);
47
48 for (; szCmdLine[i] && szCmdLine[i] != L' '; i++);
49 if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0';
50 szEntryPoint = szCmdLine + i;
51
52 g_pRemote = new RemoteMsiSession(szSessionName, false);
53 g_pRemote->Connect();
54
55 int ret = InvokeCustomAction(hSession, NULL, szEntryPoint);
56
57 RemoteMsiSession::RequestData requestData;
58 SecureZeroMemory(&requestData, sizeof(RemoteMsiSession::RequestData));
59 requestData.fields[0].vt = VT_I4;
60 requestData.fields[0].iValue = ret;
61 g_pRemote->SendRequest(RemoteMsiSession::EndSession, &requestData, NULL);
62 delete g_pRemote;
63}
64
65/// <summary>
66/// Re-launch this CA DLL as a separate process, and setup a comm channel
67/// for remote MSI API calls back to this process.
68/// </summary>
69int InvokeOutOfProcManagedCustomAction(MSIHANDLE hSession, const wchar_t* szEntryPoint)
70{
71 wchar_t szSessionName[100] = {0};
72 swprintf_s(szSessionName, 100, L"SfxCA_%d", ::GetTickCount());
73
74 RemoteMsiSession remote(szSessionName, true);
75
76 DWORD ret = remote.Connect();
77 if (ret != 0)
78 {
79 Log(hSession, L"Failed to create communication pipe for new CA process. Error code: %d", ret);
80 return ERROR_INSTALL_FAILURE;
81 }
82
83 ret = remote.ProcessRequests();
84 if (ret != 0)
85 {
86 Log(hSession, L"Failed to open communication pipe for new CA process. Error code: %d", ret);
87 return ERROR_INSTALL_FAILURE;
88 }
89
90 wchar_t szModule[MAX_PATH] = {0};
91 GetModuleFileName(g_hModule, szModule, MAX_PATH);
92
93 const wchar_t* rundll32 = L"rundll32.exe";
94 wchar_t szRunDll32Path[MAX_PATH] = {0};
95 GetSystemDirectory(szRunDll32Path, MAX_PATH);
96 wcscat_s(szRunDll32Path, MAX_PATH, L"\\");
97 wcscat_s(szRunDll32Path, MAX_PATH, rundll32);
98
99 const wchar_t* entry = L"zzzzInvokeManagedCustomActionOutOfProc";
100 wchar_t szCommandLine[1024] = {0};
101 swprintf_s(szCommandLine, 1024, L"%s \"%s\",%s %s %d %s",
102 rundll32, szModule, entry, szSessionName, hSession, szEntryPoint);
103
104 STARTUPINFO si;
105 SecureZeroMemory(&si, sizeof(STARTUPINFO));
106 si.cb = sizeof(STARTUPINFO);
107
108 PROCESS_INFORMATION pi;
109 SecureZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
110
111 if (!CreateProcess(szRunDll32Path, szCommandLine, NULL, NULL, FALSE,
112 0, NULL, NULL, &si, &pi))
113 {
114 DWORD err = GetLastError();
115 Log(hSession, L"Failed to create new CA process via RUNDLL32. Error code: %d", err);
116 return ERROR_INSTALL_FAILURE;
117 }
118
119 DWORD dwWait = WaitForSingleObject(pi.hProcess, INFINITE);
120 if (dwWait != WAIT_OBJECT_0)
121 {
122 DWORD err = GetLastError();
123 Log(hSession, L"Failed to wait for CA process. Error code: %d", err);
124 return ERROR_INSTALL_FAILURE;
125 }
126
127 DWORD dwExitCode;
128 BOOL bRet = GetExitCodeProcess(pi.hProcess, &dwExitCode);
129 if (!bRet)
130 {
131 DWORD err = GetLastError();
132 Log(hSession, L"Failed to get exit code of CA process. Error code: %d", err);
133 return ERROR_INSTALL_FAILURE;
134 }
135 else if (dwExitCode != 0)
136 {
137 Log(hSession, L"RUNDLL32 returned error code: %d", dwExitCode);
138 return ERROR_INSTALL_FAILURE;
139 }
140
141 CloseHandle(pi.hThread);
142 CloseHandle(pi.hProcess);
143
144 remote.WaitExitCode();
145 return remote.ExitCode;
146}
147
148/// <summary>
149/// Entrypoint for the managed CA proxy (RemotableNativeMethods) to
150/// call MSI APIs remotely.
151/// </summary>
152void __stdcall MsiRemoteInvoke(RemoteMsiSession::RequestId id, RemoteMsiSession::RequestData* pRequest, RemoteMsiSession::RequestData** ppResponse)
153{
154 if (g_fRunningOutOfProc)
155 {
156 g_pRemote->SendRequest(id, pRequest, ppResponse);
157 }
158 else
159 {
160 *ppResponse = NULL;
161 }
162}
163
164/// <summary>
165/// Invokes a managed custom action from native code by
166/// extracting the package to a temporary working directory
167/// then hosting the CLR and locating and calling the entrypoint.
168/// </summary>
169/// <param name="hSession">Handle to the installation session.
170/// Passed to custom action entrypoints by the installer engine.</param>
171/// <param name="szWorkingDir">Directory containing the CA binaries
172/// and the CustomAction.config file defining the entrypoints.
173/// This may be NULL, in which case the current module must have
174/// a concatenated cabinet containing those files, which will be
175/// extracted to a temporary directory.</param>
176/// <param name="szEntryPoint">Name of the CA entrypoint to be invoked.
177/// This must be either an explicit &quot;AssemblyName!Namespace.Class.Method&quot;
178/// string, or a simple name that maps to a full entrypoint definition
179/// in CustomAction.config.</param>
180/// <returns>The value returned by the managed custom action method,
181/// or ERROR_INSTALL_FAILURE if the CA could not be invoked.</returns>
182int InvokeCustomAction(MSIHANDLE hSession,
183 const wchar_t* szWorkingDir, const wchar_t* szEntryPoint)
184{
185#ifdef MANAGED_CAs_OUT_OF_PROC
186 if (!g_fRunningOutOfProc && szWorkingDir == NULL)
187 {
188 return InvokeOutOfProcManagedCustomAction(hSession, szEntryPoint);
189 }
190#endif
191
192 wchar_t szTempDir[MAX_PATH];
193 bool fDeleteTemp = false;
194 if (szWorkingDir == NULL)
195 {
196 if (!ExtractToTempDirectory(hSession, g_hModule, szTempDir, MAX_PATH))
197 {
198 return ERROR_INSTALL_FAILURE;
199 }
200 szWorkingDir = szTempDir;
201 fDeleteTemp = true;
202 }
203
204 wchar_t szConfigFilePath[MAX_PATH + 20];
205 StringCchCopy(szConfigFilePath, MAX_PATH + 20, szWorkingDir);
206 StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\CustomAction.config");
207
208 const wchar_t* szConfigFile = szConfigFilePath;
209 if (!::PathFileExists(szConfigFilePath))
210 {
211 szConfigFile = NULL;
212 }
213
214 wchar_t szWIAssembly[MAX_PATH + 50];
215 StringCchCopy(szWIAssembly, MAX_PATH + 50, szWorkingDir);
216 StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll");
217
218 int iResult = ERROR_INSTALL_FAILURE;
219 ICorRuntimeHost* pHost;
220 if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &pHost))
221 {
222 _AppDomain* pAppDomain;
223 if (CreateAppDomain(hSession, pHost, L"CustomAction", szWorkingDir,
224 szConfigFile, &pAppDomain))
225 {
226 if (!InvokeManagedCustomAction(hSession, pAppDomain, szEntryPoint, &iResult))
227 {
228 iResult = ERROR_INSTALL_FAILURE;
229 }
230 HRESULT hr = pHost->UnloadDomain(pAppDomain);
231 if (FAILED(hr))
232 {
233 Log(hSession, L"Failed to unload app domain. Error code 0x%X", hr);
234 }
235 pAppDomain->Release();
236 }
237
238 pHost->Stop();
239 pHost->Release();
240 }
241
242 if (fDeleteTemp)
243 {
244 DeleteDirectory(szTempDir);
245 }
246 return iResult;
247}
248
249/// <summary>
250/// Called by the system when the DLL is loaded.
251/// Saves the module handle for later use.
252/// </summary>
253BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, void* pReserved)
254{
255 UNREFERENCED_PARAMETER(pReserved);
256
257 switch (dwReason)
258 {
259 case DLL_PROCESS_ATTACH:
260 g_hModule = hModule;
261 break;
262 case DLL_THREAD_ATTACH:
263 case DLL_THREAD_DETACH:
264 case DLL_PROCESS_DETACH:
265 break;
266 }
267 return TRUE;
268}
269
270/// <summary>
271/// Loads and invokes the managed portion of the proxy.
272/// </summary>
273/// <param name="hSession">Handle to the installer session,
274/// used for logging errors and to be passed on to the custom action.</param>
275/// <param name="pAppDomain">AppDomain which has its application
276/// base set to the CA working directory.</param>
277/// <param name="szEntryPoint">Name of the CA entrypoint to be invoked.
278/// This must be either an explicit &quot;AssemblyName!Namespace.Class.Method&quot;
279/// string, or a simple name that maps to a full entrypoint definition
280/// in CustomAction.config.</param>
281/// <param name="piResult">Return value of the invoked custom
282/// action method.</param>
283/// <returns>True if the managed proxy was invoked successfully,
284/// false if there was some error. Note the custom action itself may
285/// return an error via piResult while this method still returns true
286/// since the invocation was successful.</returns>
287bool InvokeManagedCustomAction(MSIHANDLE hSession, _AppDomain* pAppDomain,
288 const wchar_t* szEntryPoint, int* piResult)
289{
290 VARIANT vResult;
291 ::VariantInit(&vResult);
292
293 const bool f64bit = (sizeof(void*) == sizeof(LONGLONG));
294 const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller";
295 const wchar_t* szMsiCAProxyClass = L"WixToolset.Dtf.WindowsInstaller.CustomActionProxy";
296 const wchar_t* szMsiCAInvokeMethod = (f64bit ? L"InvokeCustomAction64" : L"InvokeCustomAction32");
297
298 _MethodInfo* pCAInvokeMethod;
299 if (!GetMethod(hSession, pAppDomain, szMsiAssemblyName,
300 szMsiCAProxyClass, szMsiCAInvokeMethod, &pCAInvokeMethod))
301 {
302 return false;
303 }
304
305 HRESULT hr;
306 VARIANT vNull;
307 vNull.vt = VT_EMPTY;
308 SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3);
309 VARIANT vSessionHandle;
310 vSessionHandle.vt = VT_I4;
311 vSessionHandle.intVal = hSession;
312 LONG index = 0;
313 hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle);
314 if (FAILED(hr)) goto LExit;
315 VARIANT vEntryPoint;
316 vEntryPoint.vt = VT_BSTR;
317 vEntryPoint.bstrVal = SysAllocString(szEntryPoint);
318 if (vEntryPoint.bstrVal == NULL)
319 {
320 hr = E_OUTOFMEMORY;
321 goto LExit;
322 }
323 index = 1;
324 hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint);
325 if (FAILED(hr)) goto LExit;
326 VARIANT vRemotingFunctionPtr;
327#pragma warning(push)
328#pragma warning(disable:4127) // conditional expression is constant
329 if (f64bit)
330#pragma warning(pop)
331 {
332 vRemotingFunctionPtr.vt = VT_I8;
333 vRemotingFunctionPtr.llVal = (LONGLONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL);
334 }
335 else
336 {
337 vRemotingFunctionPtr.vt = VT_I4;
338#pragma warning(push)
339#pragma warning(disable:4302) // truncation
340#pragma warning(disable:4311) // pointer truncation
341 vRemotingFunctionPtr.lVal = (LONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL);
342#pragma warning(pop)
343 }
344 index = 2;
345 hr = SafeArrayPutElement(saArgs, &index, &vRemotingFunctionPtr);
346 if (FAILED(hr)) goto LExit;
347
348 hr = pCAInvokeMethod->Invoke_3(vNull, saArgs, &vResult);
349
350LExit:
351 SafeArrayDestroy(saArgs);
352 pCAInvokeMethod->Release();
353
354 if (FAILED(hr))
355 {
356 Log(hSession, L"Failed to invoke custom action method. Error code 0x%X", hr);
357 return false;
358 }
359
360 *piResult = vResult.intVal;
361 return true;
362}
363
diff --git a/src/samples/Dtf/Tools/SfxCA/SfxCA.rc b/src/samples/Dtf/Tools/SfxCA/SfxCA.rc
new file mode 100644
index 00000000..4d78194b
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/SfxCA.rc
@@ -0,0 +1,10 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#define VER_DLL
4#define VER_LANG_NEUTRAL
5#define VER_ORIGINAL_FILENAME "SfxCA.dll"
6#define VER_INTERNAL_NAME "SfxCA"
7#define VER_FILE_DESCRIPTION "DTF Self-Extracting Custom Action"
8
9// Additional resources here
10
diff --git a/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj b/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj
new file mode 100644
index 00000000..aeaaa776
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj
@@ -0,0 +1,68 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
4 <ItemGroup Label="ProjectConfigurations">
5 <ProjectConfiguration Include="Debug|Win32">
6 <Configuration>Debug</Configuration>
7 <Platform>Win32</Platform>
8 </ProjectConfiguration>
9 <ProjectConfiguration Include="Release|Win32">
10 <Configuration>Release</Configuration>
11 <Platform>Win32</Platform>
12 </ProjectConfiguration>
13 <ProjectConfiguration Include="Debug|x64">
14 <Configuration>Debug</Configuration>
15 <Platform>x64</Platform>
16 </ProjectConfiguration>
17 <ProjectConfiguration Include="Release|x64">
18 <Configuration>Release</Configuration>
19 <Platform>x64</Platform>
20 </ProjectConfiguration>
21 </ItemGroup>
22
23 <PropertyGroup Label="Globals">
24 <ProjectGuid>{55D5BA28-D427-4F53-80C2-FE9EF23C1553}</ProjectGuid>
25 <ConfigurationType>DynamicLibrary</ConfigurationType>
26 <TargetName>SfxCA</TargetName>
27 <PlatformToolset>v142</PlatformToolset>
28 <CharacterSet>Unicode</CharacterSet>
29 <ProjectModuleDefinitionFile>EntryPoints.def</ProjectModuleDefinitionFile>
30 </PropertyGroup>
31 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
32 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
33 <PropertyGroup>
34 <ProjectAdditionalLinkLibraries>msi.lib;cabinet.lib;shlwapi.lib</ProjectAdditionalLinkLibraries>
35 </PropertyGroup>
36 <ItemGroup>
37 <ClCompile Include="ClrHost.cpp" />
38 <ClCompile Include="Extract.cpp" />
39 <ClCompile Include="precomp.cpp">
40 <PrecompiledHeader>Create</PrecompiledHeader>
41 </ClCompile>
42 <ClCompile Include="RemoteMsi.cpp" />
43 <ClCompile Include="SfxCA.cpp" />
44 <ClCompile Include="SfxUtil.cpp" />
45 <ClCompile Include="EmbeddedUI.cpp" />
46 </ItemGroup>
47 <ItemGroup>
48 <ClInclude Include="precomp.h" />
49 <ClInclude Include="EntryPoints.h" />
50 <ClInclude Include="RemoteMsiSession.h" />
51 <ClInclude Include="SfxUtil.h" />
52 </ItemGroup>
53 <ItemGroup>
54 <None Include="EntryPoints.def" />
55 <None Include="packages.config" />
56 </ItemGroup>
57 <ItemGroup>
58 <ResourceCompile Include="SfxCA.rc" />
59 </ItemGroup>
60 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
61 <Import Project="..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets" Condition="Exists('..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets')" />
62 <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
63 <PropertyGroup>
64 <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
65 </PropertyGroup>
66 <Error Condition="!Exists('..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets'))" />
67 </Target>
68</Project> \ No newline at end of file
diff --git a/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj.filters b/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj.filters
new file mode 100644
index 00000000..a5ebf693
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj.filters
@@ -0,0 +1,62 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 <ItemGroup>
4 <ClCompile Include="ClrHost.cpp">
5 <Filter>Source Files</Filter>
6 </ClCompile>
7 <ClCompile Include="EmbeddedUI.cpp">
8 <Filter>Source Files</Filter>
9 </ClCompile>
10 <ClCompile Include="Extract.cpp">
11 <Filter>Source Files</Filter>
12 </ClCompile>
13 <ClCompile Include="RemoteMsi.cpp">
14 <Filter>Source Files</Filter>
15 </ClCompile>
16 <ClCompile Include="SfxCA.cpp">
17 <Filter>Source Files</Filter>
18 </ClCompile>
19 <ClCompile Include="SfxUtil.cpp">
20 <Filter>Source Files</Filter>
21 </ClCompile>
22 <ClCompile Include="precomp.cpp">
23 <Filter>Source Files</Filter>
24 </ClCompile>
25 </ItemGroup>
26 <ItemGroup>
27 <ClInclude Include="EntryPoints.h">
28 <Filter>Header Files</Filter>
29 </ClInclude>
30 <ClInclude Include="precomp.h">
31 <Filter>Header Files</Filter>
32 </ClInclude>
33 <ClInclude Include="RemoteMsiSession.h">
34 <Filter>Header Files</Filter>
35 </ClInclude>
36 <ClInclude Include="SfxUtil.h">
37 <Filter>Header Files</Filter>
38 </ClInclude>
39 </ItemGroup>
40 <ItemGroup>
41 <Filter Include="Resource Files">
42 <UniqueIdentifier>{81c92f68-18c2-4cd4-a588-5c3616860dd9}</UniqueIdentifier>
43 </Filter>
44 <Filter Include="Header Files">
45 <UniqueIdentifier>{6cdc30ee-e14d-4679-b92e-3e080535e53b}</UniqueIdentifier>
46 </Filter>
47 <Filter Include="Source Files">
48 <UniqueIdentifier>{1666a44e-4f2e-4f13-980e-d0c3dfa7cb6d}</UniqueIdentifier>
49 </Filter>
50 </ItemGroup>
51 <ItemGroup>
52 <ResourceCompile Include="SfxCA.rc">
53 <Filter>Resource Files</Filter>
54 </ResourceCompile>
55 </ItemGroup>
56 <ItemGroup>
57 <None Include="EntryPoints.def">
58 <Filter>Resource Files</Filter>
59 </None>
60 <None Include="packages.config" />
61 </ItemGroup>
62</Project> \ No newline at end of file
diff --git a/src/samples/Dtf/Tools/SfxCA/SfxUtil.cpp b/src/samples/Dtf/Tools/SfxCA/SfxUtil.cpp
new file mode 100644
index 00000000..1bf2c5b2
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/SfxUtil.cpp
@@ -0,0 +1,209 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4#include "SfxUtil.h"
5
6/// <summary>
7/// Writes a formatted message to the MSI log.
8/// Does out-of-proc MSI calls if necessary.
9/// </summary>
10void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...)
11{
12 const int LOG_BUFSIZE = 4096;
13 wchar_t szBuf[LOG_BUFSIZE];
14 va_list args;
15 va_start(args, szMessage);
16 StringCchVPrintf(szBuf, LOG_BUFSIZE, szMessage, args);
17
18 if (!g_fRunningOutOfProc || NULL == g_pRemote)
19 {
20 MSIHANDLE hRec = MsiCreateRecord(1);
21 MsiRecordSetString(hRec, 0, L"SFXCA: [1]");
22 MsiRecordSetString(hRec, 1, szBuf);
23 MsiProcessMessage(hSession, INSTALLMESSAGE_INFO, hRec);
24 MsiCloseHandle(hRec);
25 }
26 else
27 {
28 // Logging is the only remote-MSI operation done from unmanaged code.
29 // It's not very convenient here because part of the infrastructure
30 // for remote MSI APIs is on the managed side.
31
32 RemoteMsiSession::RequestData req;
33 RemoteMsiSession::RequestData* pResp = NULL;
34 SecureZeroMemory(&req, sizeof(RemoteMsiSession::RequestData));
35
36 req.fields[0].vt = VT_UI4;
37 req.fields[0].uiValue = 1;
38 g_pRemote->SendRequest(RemoteMsiSession::MsiCreateRecord, &req, &pResp);
39 MSIHANDLE hRec = (MSIHANDLE) pResp->fields[0].iValue;
40
41 req.fields[0].vt = VT_I4;
42 req.fields[0].iValue = (int) hRec;
43 req.fields[1].vt = VT_UI4;
44 req.fields[1].uiValue = 0;
45 req.fields[2].vt = VT_LPWSTR;
46 req.fields[2].szValue = L"SFXCA: [1]";
47 g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp);
48
49 req.fields[0].vt = VT_I4;
50 req.fields[0].iValue = (int) hRec;
51 req.fields[1].vt = VT_UI4;
52 req.fields[1].uiValue = 1;
53 req.fields[2].vt = VT_LPWSTR;
54 req.fields[2].szValue = szBuf;
55 g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp);
56
57 req.fields[0].vt = VT_I4;
58 req.fields[0].iValue = (int) hSession;
59 req.fields[1].vt = VT_I4;
60 req.fields[1].iValue = (int) INSTALLMESSAGE_INFO;
61 req.fields[2].vt = VT_I4;
62 req.fields[2].iValue = (int) hRec;
63 g_pRemote->SendRequest(RemoteMsiSession::MsiProcessMessage, &req, &pResp);
64
65 req.fields[0].vt = VT_I4;
66 req.fields[0].iValue = (int) hRec;
67 req.fields[1].vt = VT_EMPTY;
68 req.fields[2].vt = VT_EMPTY;
69 g_pRemote->SendRequest(RemoteMsiSession::MsiCloseHandle, &req, &pResp);
70 }
71}
72
73/// <summary>
74/// Deletes a directory, including all files and subdirectories.
75/// </summary>
76/// <param name="szDir">Path to the directory to delete,
77/// not including a trailing backslash.</param>
78/// <returns>True if the directory was successfully deleted, or false
79/// if the deletion failed (most likely because some files were locked).
80/// </returns>
81bool DeleteDirectory(const wchar_t* szDir)
82{
83 size_t cchDir = wcslen(szDir);
84 size_t cchPathBuf = cchDir + 3 + MAX_PATH;
85 wchar_t* szPath = (wchar_t*) _alloca(cchPathBuf * sizeof(wchar_t));
86 if (szPath == NULL) return false;
87 StringCchCopy(szPath, cchPathBuf, szDir);
88 StringCchCat(szPath, cchPathBuf, L"\\*");
89 WIN32_FIND_DATA fd;
90 HANDLE hSearch = FindFirstFile(szPath, &fd);
91 while (hSearch != INVALID_HANDLE_VALUE)
92 {
93 StringCchCopy(szPath + cchDir + 1, cchPathBuf - (cchDir + 1), fd.cFileName);
94 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
95 {
96 if (wcscmp(fd.cFileName, L".") != 0 && wcscmp(fd.cFileName, L"..") != 0)
97 {
98 DeleteDirectory(szPath);
99 }
100 }
101 else
102 {
103 DeleteFile(szPath);
104 }
105 if (!FindNextFile(hSearch, &fd))
106 {
107 FindClose(hSearch);
108 hSearch = INVALID_HANDLE_VALUE;
109 }
110 }
111 return RemoveDirectory(szDir) != 0;
112}
113
114bool DirectoryExists(const wchar_t* szDir)
115{
116 if (szDir != NULL)
117 {
118 DWORD dwAttrs = GetFileAttributes(szDir);
119 if (dwAttrs != -1 && (dwAttrs & FILE_ATTRIBUTE_DIRECTORY) != 0)
120 {
121 return true;
122 }
123 }
124 return false;
125}
126
127/// <summary>
128/// Extracts a cabinet that is concatenated to a module
129/// to a new temporary directory.
130/// </summary>
131/// <param name="hSession">Handle to the installer session,
132/// used just for logging.</param>
133/// <param name="hModule">Module that has the concatenated cabinet.</param>
134/// <param name="szTempDir">Buffer for returning the path of the
135/// created temp directory.</param>
136/// <param name="cchTempDirBuf">Size in characters of the buffer.
137/// <returns>True if the files were extracted, or false if the
138/// buffer was too small or the directory could not be created
139/// or the extraction failed for some other reason.</returns>
140__success(return != false)
141bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule,
142 __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf)
143{
144 wchar_t szModule[MAX_PATH];
145 DWORD cchCopied = GetModuleFileName(hModule, szModule, MAX_PATH - 1);
146 if (cchCopied == 0)
147 {
148 Log(hSession, L"Failed to get module path. Error code %d.", GetLastError());
149 return false;
150 }
151 else if (cchCopied == MAX_PATH - 1)
152 {
153 Log(hSession, L"Failed to get module path -- path is too long.");
154 return false;
155 }
156
157 if (szTempDir == NULL || cchTempDirBuf < wcslen(szModule) + 1)
158 {
159 Log(hSession, L"Temp directory buffer is NULL or too small.");
160 return false;
161 }
162 StringCchCopy(szTempDir, cchTempDirBuf, szModule);
163 StringCchCat(szTempDir, cchTempDirBuf, L"-");
164
165 DWORD cchTempDir = (DWORD) wcslen(szTempDir);
166 for (int i = 0; DirectoryExists(szTempDir); i++)
167 {
168 swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i);
169 }
170
171 if (!CreateDirectory(szTempDir, NULL))
172 {
173 cchCopied = GetTempPath(cchTempDirBuf, szTempDir);
174 if (cchCopied == 0 || cchCopied >= cchTempDirBuf)
175 {
176 Log(hSession, L"Failed to get temp directory. Error code %d", GetLastError());
177 return false;
178 }
179
180 wchar_t* szModuleName = wcsrchr(szModule, L'\\');
181 if (szModuleName == NULL) szModuleName = szModule;
182 else szModuleName = szModuleName + 1;
183 StringCchCat(szTempDir, cchTempDirBuf, szModuleName);
184 StringCchCat(szTempDir, cchTempDirBuf, L"-");
185
186 cchTempDir = (DWORD) wcslen(szTempDir);
187 for (int i = 0; DirectoryExists(szTempDir); i++)
188 {
189 swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i);
190 }
191
192 if (!CreateDirectory(szTempDir, NULL))
193 {
194 Log(hSession, L"Failed to create temp directory. Error code %d", GetLastError());
195 return false;
196 }
197 }
198
199 Log(hSession, L"Extracting custom action to temporary directory: %s\\", szTempDir);
200 int err = ExtractCabinet(szModule, szTempDir);
201 if (err != 0)
202 {
203 Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err);
204 DeleteDirectory(szTempDir);
205 return false;
206 }
207 return true;
208}
209
diff --git a/src/samples/Dtf/Tools/SfxCA/SfxUtil.h b/src/samples/Dtf/Tools/SfxCA/SfxUtil.h
new file mode 100644
index 00000000..af12d8dd
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/SfxUtil.h
@@ -0,0 +1,31 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "RemoteMsiSession.h"
4
5void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...);
6
7int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir);
8
9bool DeleteDirectory(const wchar_t* szDir);
10
11__success(return != false)
12bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule,
13 __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf);
14
15bool LoadCLR(MSIHANDLE hSession, const wchar_t* szVersion, const wchar_t* szConfigFile,
16 const wchar_t* szPrimaryAssembly, ICorRuntimeHost** ppHost);
17
18bool CreateAppDomain(MSIHANDLE hSession, ICorRuntimeHost* pHost,
19 const wchar_t* szName, const wchar_t* szAppBase,
20 const wchar_t* szConfigFile, _AppDomain** ppAppDomain);
21
22bool GetMethod(MSIHANDLE hSession, _AppDomain* pAppDomain,
23 const wchar_t* szAssembly, const wchar_t* szClass,
24 const wchar_t* szMethod, _MethodInfo** ppCAMethod);
25
26extern HMODULE g_hModule;
27extern bool g_fRunningOutOfProc;
28
29extern RemoteMsiSession* g_pRemote;
30
31
diff --git a/src/samples/Dtf/Tools/SfxCA/packages.config b/src/samples/Dtf/Tools/SfxCA/packages.config
new file mode 100644
index 00000000..1ffaa8df
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/packages.config
@@ -0,0 +1,4 @@
1<?xml version="1.0" encoding="utf-8"?>
2<packages>
3 <package id="Nerdbank.GitVersioning" version="3.3.37" targetFramework="native" developmentDependency="true" />
4</packages> \ No newline at end of file
diff --git a/src/samples/Dtf/Tools/SfxCA/precomp.cpp b/src/samples/Dtf/Tools/SfxCA/precomp.cpp
new file mode 100644
index 00000000..ce82c1d7
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/precomp.cpp
@@ -0,0 +1,3 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h" \ No newline at end of file
diff --git a/src/samples/Dtf/Tools/SfxCA/precomp.h b/src/samples/Dtf/Tools/SfxCA/precomp.h
new file mode 100644
index 00000000..48d4f011
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/precomp.h
@@ -0,0 +1,18 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#include <windows.h>
6#include <msiquery.h>
7#include <strsafe.h>
8#include <mscoree.h>
9#include <io.h>
10#include <fcntl.h>
11#include <share.h>
12#include <shlwapi.h>
13#include <sys/stat.h>
14#include <malloc.h>
15#include <fdi.h>
16#include <msiquery.h>
17#import <mscorlib.tlb> raw_interfaces_only rename("ReportEvent", "CorReportEvent")
18using namespace mscorlib;
diff --git a/src/samples/Dtf/Tools/Tools.proj b/src/samples/Dtf/Tools/Tools.proj
new file mode 100644
index 00000000..751247dc
--- /dev/null
+++ b/src/samples/Dtf/Tools/Tools.proj
@@ -0,0 +1,15 @@
1<?xml version='1.0' encoding='utf-8'?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
6 <ItemGroup>
7 <ProjectReference Include="MakeSfxCA\MakeSfxCA.csproj" />
8 <ProjectReference Include="SfxCA\SfxCA.vcxproj" />
9 <ProjectReference Include="SfxCA\SfxCA.vcxproj">
10 <Properties>Platform=x64</Properties>
11 </ProjectReference>
12 </ItemGroup>
13
14 <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\Traversal.targets" />
15</Project>
diff --git a/src/samples/Dtf/WiFile/WiFile.cs b/src/samples/Dtf/WiFile/WiFile.cs
new file mode 100644
index 00000000..1e5c80df
--- /dev/null
+++ b/src/samples/Dtf/WiFile/WiFile.cs
@@ -0,0 +1,147 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.IO;
5using System.Reflection;
6using System.Collections;
7using System.Collections.Generic;
8using System.Diagnostics.CodeAnalysis;
9using System.Globalization;
10using System.Text.RegularExpressions;
11using WixToolset.Dtf.WindowsInstaller;
12using WixToolset.Dtf.WindowsInstaller.Package;
13
14[assembly: AssemblyDescription("Windows Installer package file extraction and update tool")]
15
16
17/// <summary>
18/// Shows sample use of the InstallPackage class.
19/// </summary>
20public class WiFile
21{
22 public static void Usage(TextWriter w)
23 {
24 w.WriteLine("Usage: WiFile.exe package.msi /l [filename,filename2,...]");
25 w.WriteLine("Usage: WiFile.exe package.msi /x [filename,filename2,...]");
26 w.WriteLine("Usage: WiFile.exe package.msi /u [filename,filename2,...]");
27 w.WriteLine();
28 w.WriteLine("Lists (/l), extracts (/x) or updates (/u) files in an MSI or MSM.");
29 w.WriteLine("Files are extracted using their source path relative to the package.");
30 w.WriteLine("Specified filenames do not include paths.");
31 w.WriteLine("Filenames may be a pattern such as *.exe or file?.dll");
32 }
33
34 [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
35 public static int Main(string[] args)
36 {
37 if(!(args.Length == 2 || args.Length == 3))
38 {
39 Usage(Console.Out);
40 return -1;
41 }
42
43 string msiFile = args[0];
44
45 string option = args[1].ToLowerInvariant();
46 if(option.StartsWith("-", StringComparison.Ordinal)) option = "/" + option.Substring(1);
47
48 string[] fileNames = null;
49 if(args.Length == 3)
50 {
51 fileNames = args[2].Split(',');
52 }
53
54 try
55 {
56 switch(option)
57 {
58 case "/l":
59 using(InstallPackage pkg = new InstallPackage(msiFile, DatabaseOpenMode.ReadOnly))
60 {
61 pkg.Message += new InstallPackageMessageHandler(Console.WriteLine);
62 IEnumerable<string> fileKeys = (fileNames != null ? FindFileKeys(pkg, fileNames) : pkg.Files.Keys);
63
64 foreach(string fileKey in fileKeys)
65 {
66 Console.WriteLine(pkg.Files[fileKey]);
67 }
68 }
69 break;
70
71 case "/x":
72 using(InstallPackage pkg = new InstallPackage(msiFile, DatabaseOpenMode.ReadOnly))
73 {
74 pkg.Message += new InstallPackageMessageHandler(Console.WriteLine);
75 ICollection<string> fileKeys = FindFileKeys(pkg, fileNames);
76
77 pkg.ExtractFiles(fileKeys);
78 }
79 break;
80
81 case "/u":
82 using(InstallPackage pkg = new InstallPackage(msiFile, DatabaseOpenMode.Transact))
83 {
84 pkg.Message += new InstallPackageMessageHandler(Console.WriteLine);
85 ICollection<string> fileKeys = FindFileKeys(pkg, fileNames);
86
87 pkg.UpdateFiles(fileKeys);
88 pkg.Commit();
89 }
90 break;
91
92 default:
93 Usage(Console.Out);
94 return -1;
95 }
96 }
97 catch(InstallerException iex)
98 {
99 Console.WriteLine("Error: " + iex.Message);
100 return iex.ErrorCode != 0 ? iex.ErrorCode : 1;
101 }
102 catch(FileNotFoundException fnfex)
103 {
104 Console.WriteLine(fnfex.Message);
105 return 2;
106 }
107 catch(Exception ex)
108 {
109 Console.WriteLine("Error: " + ex.Message);
110 return 1;
111 }
112 return 0;
113 }
114
115 static ICollection<string> FindFileKeys(InstallPackage pkg, ICollection<string> fileNames)
116 {
117 List<string> fileKeys = null;
118 if(fileNames != null)
119 {
120 fileKeys = new List<string>();
121 foreach(string fileName in fileNames)
122 {
123 string[] foundFileKeys = null;
124 if(fileName.IndexOfAny(new char[] { '*', '?' }) >= 0)
125 {
126 foundFileKeys = pkg.FindFiles(FilePatternToRegex(fileName));
127 }
128 else
129 {
130 foundFileKeys = pkg.FindFiles(fileName);
131 }
132 fileKeys.AddRange(foundFileKeys);
133 }
134 if(fileKeys.Count == 0)
135 {
136 throw new FileNotFoundException("Files not found in package.");
137 }
138 }
139 return fileKeys;
140 }
141
142 static Regex FilePatternToRegex(string pattern)
143 {
144 return new Regex("^" + Regex.Escape(pattern).Replace("\\*", ".*").Replace("\\?", ".") + "$",
145 RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
146 }
147}
diff --git a/src/samples/Dtf/WiFile/WiFile.csproj b/src/samples/Dtf/WiFile/WiFile.csproj
new file mode 100644
index 00000000..b5a95481
--- /dev/null
+++ b/src/samples/Dtf/WiFile/WiFile.csproj
@@ -0,0 +1,27 @@
1
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
6 <PropertyGroup>
7 <ProjectGuid>{AE562F7F-EE33-41D6-A962-DA488FEFBD08}</ProjectGuid>
8 <OutputType>Exe</OutputType>
9 <RootNamespace>WixToolset.Dtf.Samples.WiFile</RootNamespace>
10 <AssemblyName>WiFile</AssemblyName>
11 <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
12 </PropertyGroup>
13
14 <ItemGroup>
15 <Compile Include="WiFile.cs" />
16 </ItemGroup>
17
18 <ItemGroup>
19 <Reference Include="System" />
20 <Reference Include="System.Data" />
21 <Reference Include="System.Xml" />
22 <ProjectReference Include="..\..\Libraries\WindowsInstaller.Package\WindowsInstaller.Package.csproj" />
23 <ProjectReference Include="..\..\Libraries\WindowsInstaller\WindowsInstaller.csproj" />
24 </ItemGroup>
25
26 <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" />
27</Project>
diff --git a/src/samples/Dtf/XPack/AssemblyInfo.cs b/src/samples/Dtf/XPack/AssemblyInfo.cs
new file mode 100644
index 00000000..6dfb9437
--- /dev/null
+++ b/src/samples/Dtf/XPack/AssemblyInfo.cs
@@ -0,0 +1,5 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System.Reflection;
4
5[assembly: AssemblyDescription("Simple command-line CAB/ZIP packing and unpacking tool.")]
diff --git a/src/samples/Dtf/XPack/XPack.cs b/src/samples/Dtf/XPack/XPack.cs
new file mode 100644
index 00000000..36543a73
--- /dev/null
+++ b/src/samples/Dtf/XPack/XPack.cs
@@ -0,0 +1,80 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Samples.XPack
4{
5 using System;
6 using System.IO;
7 using System.Collections.Generic;
8 using System.Text;
9 using WixToolset.Dtf.Compression;
10
11 public class XPack
12 {
13 public static void Usage(TextWriter writer)
14 {
15 writer.WriteLine("Usage: XPack /P <archive.cab> <directory>");
16 writer.WriteLine("Usage: XPack /P <archive.zip> <directory>");
17 writer.WriteLine();
18 writer.WriteLine("Packs all files in a directory tree into an archive,");
19 writer.WriteLine("using either the cab or zip format. Any existing archive");
20 writer.WriteLine("with the same name will be overwritten.");
21 writer.WriteLine();
22 writer.WriteLine("Usage: XPack /U <archive.cab> <directory>");
23 writer.WriteLine("Usage: XPack /U <archive.zip> <directory>");
24 writer.WriteLine();
25 writer.WriteLine("Unpacks all files from a cab or zip archive to the");
26 writer.WriteLine("specified directory. Any existing files with the same");
27 writer.WriteLine("names will be overwritten.");
28 }
29
30 public static void Main(string[] args)
31 {
32 try
33 {
34 if (args.Length == 3 && args[0].ToUpperInvariant() == "/P")
35 {
36 ArchiveInfo a = GetArchive(args[1]);
37 a.Pack(args[2], true, CompressionLevel.Max, ProgressHandler);
38 }
39 else if (args.Length == 3 && args[0].ToUpperInvariant() == "/U")
40 {
41 ArchiveInfo a = GetArchive(args[1]);
42 a.Unpack(args[2], ProgressHandler);
43 }
44 else
45 {
46 Usage(Console.Out);
47 }
48 }
49 catch (Exception ex)
50 {
51 Console.WriteLine(ex);
52 }
53 }
54
55 private static void ProgressHandler(object source, ArchiveProgressEventArgs e)
56 {
57 if (e.ProgressType == ArchiveProgressType.StartFile)
58 {
59 Console.WriteLine(e.CurrentFileName);
60 }
61 }
62
63 private static ArchiveInfo GetArchive(string name)
64 {
65 string extension = Path.GetExtension(name).ToUpperInvariant();
66 if (extension == ".CAB")
67 {
68 return new WixToolset.Dtf.Compression.Cab.CabInfo(name);
69 }
70 else if (extension == ".ZIP")
71 {
72 return new WixToolset.Dtf.Compression.Zip.ZipInfo(name);
73 }
74 else
75 {
76 throw new ArgumentException("Unknown archive file extension: " + extension);
77 }
78 }
79 }
80}
diff --git a/src/samples/Dtf/XPack/XPack.csproj b/src/samples/Dtf/XPack/XPack.csproj
new file mode 100644
index 00000000..778c2d94
--- /dev/null
+++ b/src/samples/Dtf/XPack/XPack.csproj
@@ -0,0 +1,27 @@
1
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
6 <PropertyGroup>
7 <ProjectGuid>{03E55D95-DABE-4571-9CDA-92A44F92A465}</ProjectGuid>
8 <OutputType>Exe</OutputType>
9 <RootNamespace>WixToolset.Dtf.Samples.XPack</RootNamespace>
10 <AssemblyName>XPack</AssemblyName>
11 <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
12 </PropertyGroup>
13
14 <ItemGroup>
15 <Compile Include="AssemblyInfo.cs" />
16 <Compile Include="XPack.cs" />
17 </ItemGroup>
18
19 <ItemGroup>
20 <Reference Include="System" />
21 <ProjectReference Include="..\..\Libraries\Compression.Cab\Compression.Cab.csproj" />
22 <ProjectReference Include="..\..\Libraries\Compression.Zip\Compression.Zip.csproj" />
23 <ProjectReference Include="..\..\Libraries\Compression\Compression.csproj" />
24 </ItemGroup>
25
26 <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" />
27</Project>
diff --git a/src/signing.json b/src/signing.json
new file mode 100644
index 00000000..fe1c8c9b
--- /dev/null
+++ b/src/signing.json
@@ -0,0 +1,13 @@
1{
2 "SignClient": {
3 "AzureAd": {
4 "AADInstance": "https://login.microsoftonline.com/",
5 "ClientId": "c248d68a-ba6f-4aa9-8a68-71fe872063f8",
6 "TenantId": "16076fdc-fcc1-4a15-b1ca-32c9a255900e"
7 },
8 "Service": {
9 "Url": "https://codesign.dotnetfoundation.org/",
10 "ResourceId": "https://SignService/3c30251f-36f3-490b-a955-520addb85001"
11 }
12 }
13}
diff --git a/src/version.json b/src/version.json
index 5f857771..2d200f90 100644
--- a/src/version.json
+++ b/src/version.json
@@ -1,5 +1,5 @@
1{ 1{
2 "version": "4.0", 2 "version": "4.0.0-preview.0-build.{height}",
3 "publicReleaseRefSpec": [ 3 "publicReleaseRefSpec": [
4 "^refs/heads/master$" 4 "^refs/heads/master$"
5 ], 5 ],
diff --git a/src/wix.snk b/src/wix.snk
new file mode 100644
index 00000000..3908a66a
--- /dev/null
+++ b/src/wix.snk
Binary files differ