diff options
Diffstat (limited to 'src/samples')
97 files changed, 12813 insertions, 0 deletions
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 | |||
| 3 | using System; | ||
| 4 | using System.IO; | ||
| 5 | using System.Collections; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Dtf.Compression.Cab; | ||
| 8 | |||
| 9 | namespace 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 | |||
| 3 | using System; | ||
| 4 | using System.IO; | ||
| 5 | using System.Text; | ||
| 6 | |||
| 7 | namespace 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 | |||
| 3 | using System; | ||
| 4 | using System.IO; | ||
| 5 | using System.Collections; | ||
| 6 | |||
| 7 | namespace 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 | |||
| 3 | using System; | ||
| 4 | using System.IO; | ||
| 5 | |||
| 6 | namespace 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 | |||
| 3 | using System; | ||
| 4 | using System.IO; | ||
| 5 | using System.Collections; | ||
| 6 | |||
| 7 | namespace 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 | |||
| 3 | using System; | ||
| 4 | using System.IO; | ||
| 5 | using System.Collections; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Dtf.WindowsInstaller; | ||
| 8 | using WixToolset.Dtf.WindowsInstaller.Package; | ||
| 9 | |||
| 10 | namespace 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 | |||
| 3 | using System; | ||
| 4 | using System.IO; | ||
| 5 | using WixToolset.Dtf.WindowsInstaller; | ||
| 6 | using WixToolset.Dtf.WindowsInstaller.Package; | ||
| 7 | |||
| 8 | namespace 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 | |||
| 3 | using System; | ||
| 4 | using System.IO; | ||
| 5 | using System.Diagnostics; | ||
| 6 | |||
| 7 | namespace 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 | |||
| 3 | using System; | ||
| 4 | using System.IO; | ||
| 5 | using WixToolset.Dtf.WindowsInstaller; | ||
| 6 | |||
| 7 | namespace 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> > | ||
| 16 | <a href="managedcas.htm">Managed CAs</a> > | ||
| 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> > | ||
| 16 | <a href="samples.htm">Samples</a> > | ||
| 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 <directory> <package.cab> | ||
| 26 | Usage: XPack /P <archive.cab> <directory> | ||
| 27 | Usage: XPack /P <archive.zip> <directory> | ||
| 28 | |||
| 29 | Packs all files in a directory tree into an archive, | ||
| 30 | using either the cab or zip format. Any existing archive | ||
| 31 | with the same name will be overwritten. | ||
| 32 | |||
| 33 | |||
| 34 | Usage: XPack /U <archive.cab> <directory> | ||
| 35 | Usage: XPack /U <archive.zip> <directory> | ||
| 36 | |||
| 37 | Unpacks all files from a cab or zip archive to the | ||
| 38 | specified directory. Any existing files with the same | ||
| 39 | names 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> > | ||
| 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. 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. 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. 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. 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. 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. 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> > | ||
| 16 | <a href="managedcas.htm">Managed CAs</a> > | ||
| 17 | <a href="writingcas.htm">Writing CAs</a> > | ||
| 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 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 30 | <configuration> | ||
| 31 | <startup> | ||
| 32 | <supportedRuntime version="v2.0.50727"/> | ||
| 33 | </startup> | ||
| 34 | </configuration></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> > | ||
| 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. 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. 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. 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. 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. Create a record with one field containing the new binary value.</p> | ||
| 69 | <p>4. 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. <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_WindowsInstaller_Database_Commit.htm">Commit</a> the Database.</p> | ||
| 73 | <p>6. <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> > | ||
| 16 | <a href="managedcas.htm">Managed CAs</a> > | ||
| 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> > | ||
| 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> > | ||
| 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 /> <br /> <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 "released" 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("") to session.Message(User,""), | ||
| 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 & 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 & 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 "error 0"</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 | |||
| 350 | Documentation!<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 | & Session classes</li> | ||
| 378 | <li>Added extensive XML doc-comments to Installer, Database, View, | ||
| 379 | & 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> | ||
| 414 | Initial posting to http://toolbox | ||
| 415 | |||
| 416 | <p> </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> > | ||
| 16 | <a href="managedcas.htm">Managed CAs</a> > | ||
| 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> </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> > | ||
| 16 | <a href="samples.htm">Samples</a> > | ||
| 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 | "In Subtree" 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 "Find" again will continue the same search from that | ||
| 54 | point (unless you uncheck the "Continue" 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> > | ||
| 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> > | ||
| 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. 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. 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. 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. 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. 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> > | ||
| 16 | <a href="samples.htm">Samples</a> > | ||
| 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] | ||
| 28 | Example: DDiff d:\dir1 d:\dir2 | ||
| 29 | Example: DDiff setup1.msi setup2.msi | ||
| 30 | Example: DDiff patch1.msp patch2.msp -patchtarget target.msi | ||
| 31 | Example: DDiff package1.cab package2.cab | ||
| 32 | |||
| 33 | Options: | ||
| 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> > | ||
| 16 | <a href="managedcas.htm">Managed CAs</a> > | ||
| 17 | <a href="writingcas.htm">Writing CAs</a> > | ||
| 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. 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> > | ||
| 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> > | ||
| 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> > | ||
| 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> <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"> 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 & | ||
| 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> </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> > | ||
| 16 | <a href="samples.htm">Samples</a> > | ||
| 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,...] | ||
| 26 | Usage: WiFile.exe package.msi /x [filename,filename2,...] | ||
| 27 | Usage: WiFile.exe package.msi /u [filename,filename2,...] | ||
| 28 | |||
| 29 | Lists (/l), extracts (/x) or updates (/u) files in an MSI or MSM. | ||
| 30 | Files are extracted using their source path relative to the package. | ||
| 31 | Specified filenames do not include paths. | ||
| 32 | Filenames 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> | ||
| 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> > | ||
| 16 | <a href="managedcas.htm">Managed CAs</a> > | ||
| 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 | "name" 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] | ||
| 2 | Auto Index=Yes | ||
| 3 | Compatibility=1.1 or later | ||
| 4 | Compiled file=DTF.chm | ||
| 5 | Contents file=DTF.hhc | ||
| 6 | Default Window=DTF | ||
| 7 | Default topic=Content\about.htm | ||
| 8 | Display compile progress=Yes | ||
| 9 | Error log file=DTFhelp.log | ||
| 10 | Full-text search=Yes | ||
| 11 | Index file=DTF.hhk | ||
| 12 | Language=0x409 English (United States) | ||
| 13 | Title=Deployment Tools Foundation | ||
| 14 | |||
| 15 | [WINDOWS] | ||
| 16 | DTF="Deployment Tools Foundation","DTF.hhc","DTF.hhk","Content\about.htm","Content\about.htm",,,,,0x22520,,0x384e,[143,72,937,601],,,,,,,0 | ||
| 17 | |||
| 18 | |||
| 19 | [FILES] | ||
| 20 | styles\presentation.css | ||
| 21 | Content\about.htm | ||
| 22 | Content\whatsnew.htm | ||
| 23 | Content\dependencies.htm | ||
| 24 | Content\using.htm | ||
| 25 | DTF.hhc | ||
| 26 | DTF.hhk | ||
| 27 | Content\samples.htm | ||
| 28 | Content\support.htm | ||
| 29 | Content\history.htm | ||
| 30 | Content\inventory.htm | ||
| 31 | Content\wifile.htm | ||
| 32 | Content\cabpack.htm | ||
| 33 | Content\powerdiff.htm | ||
| 34 | Content\cabs.htm | ||
| 35 | Content\databases.htm | ||
| 36 | Content\packages.htm | ||
| 37 | Content\managedcas.htm | ||
| 38 | Content\samplecas.htm | ||
| 39 | Content\writingcas.htm | ||
| 40 | Content\buildingcas.htm | ||
| 41 | Content\debuggingcas.htm | ||
| 42 | Content\caproxy.htm | ||
| 43 | Content\caconfig.htm | ||
| 44 | Content\installutil.htm | ||
| 45 | |||
| 46 | [MERGE FILES] | ||
| 47 | DTFAPI.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 | |||
| 4 | body { | ||
| 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 | |||
| 13 | table { | ||
| 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 | |||
| 20 | div#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 | |||
| 30 | span.productTitle { | ||
| 31 | font-size: 80%; | ||
| 32 | } | ||
| 33 | |||
| 34 | span.topicTitle { | ||
| 35 | font-size: 140%; | ||
| 36 | font-weight: bold; | ||
| 37 | color: #003399; | ||
| 38 | } | ||
| 39 | |||
| 40 | span#chickenFeet { | ||
| 41 | float: left; | ||
| 42 | } | ||
| 43 | |||
| 44 | span#languageFilter { | ||
| 45 | float: right; | ||
| 46 | } | ||
| 47 | |||
| 48 | /* scrolling (content) region style */ | ||
| 49 | |||
| 50 | div#main { | ||
| 51 | margin: 0; | ||
| 52 | padding: 1em; | ||
| 53 | width: 100%; | ||
| 54 | clear: both; | ||
| 55 | } | ||
| 56 | |||
| 57 | /* sections */ | ||
| 58 | |||
| 59 | div#header { | ||
| 60 | font-size: 70%; | ||
| 61 | color: #666666; | ||
| 62 | margin-bottom: 0.5em; | ||
| 63 | } | ||
| 64 | |||
| 65 | div.section { | ||
| 66 | margin-bottom: 1em; | ||
| 67 | } | ||
| 68 | |||
| 69 | div.sectionTitle { | ||
| 70 | display: inline; | ||
| 71 | font-size: 120%; | ||
| 72 | font-weight: bold; | ||
| 73 | color: #003399; | ||
| 74 | } | ||
| 75 | |||
| 76 | div.sectionContent { | ||
| 77 | margin-top: 0.2em; | ||
| 78 | } | ||
| 79 | |||
| 80 | span.subsectionTitle { | ||
| 81 | font-weight: bold; | ||
| 82 | } | ||
| 83 | |||
| 84 | div#footer { | ||
| 85 | margin-top: 1em; | ||
| 86 | border-top: thin solid #003399; | ||
| 87 | padding-top: 0.5em; | ||
| 88 | } | ||
| 89 | |||
| 90 | /* authored content (block) */ | ||
| 91 | |||
| 92 | p { | ||
| 93 | margin-top: 0; | ||
| 94 | margin-bottom: 1em; | ||
| 95 | } | ||
| 96 | |||
| 97 | dl { | ||
| 98 | margin-top: 0; | ||
| 99 | margin-bottom: 1em; | ||
| 100 | } | ||
| 101 | |||
| 102 | div.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 | |||
| 114 | pre { | ||
| 115 | margin: 0; | ||
| 116 | padding: 0; | ||
| 117 | } | ||
| 118 | |||
| 119 | table.authoredTable { | ||
| 120 | table-layout: fixed; | ||
| 121 | width: 100%; | ||
| 122 | margin-bottom: 1em; | ||
| 123 | } | ||
| 124 | |||
| 125 | table.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 | |||
| 136 | table.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 | |||
| 145 | div.alert { | ||
| 146 | border: 1px solid #C8CDDE; | ||
| 147 | background: #F7F7FF; | ||
| 148 | } | ||
| 149 | |||
| 150 | div.media { | ||
| 151 | text-align: center; | ||
| 152 | margin-bottom: 1em; | ||
| 153 | } | ||
| 154 | |||
| 155 | |||
| 156 | /* authored content (inline) */ | ||
| 157 | |||
| 158 | span.keyword { | ||
| 159 | font-weight: bold; | ||
| 160 | } | ||
| 161 | |||
| 162 | span.code { | ||
| 163 | font-family: "Andale Mono", "Courier New", Courier, monospace; | ||
| 164 | } | ||
| 165 | |||
| 166 | /* auto-generated controls */ | ||
| 167 | |||
| 168 | div.langTabs { | ||
| 169 | width: 100%; | ||
| 170 | } | ||
| 171 | |||
| 172 | div.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 | |||
| 185 | div.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 | |||
| 198 | table.members { | ||
| 199 | table-layout: fixed; | ||
| 200 | width: 100%; | ||
| 201 | } | ||
| 202 | |||
| 203 | table.members th.iconColumn { | ||
| 204 | width: 60px; | ||
| 205 | } | ||
| 206 | |||
| 207 | table.members th.nameColumn { | ||
| 208 | width: 33%; | ||
| 209 | } | ||
| 210 | |||
| 211 | table.members th.descriptionColumn { | ||
| 212 | width: 66%; | ||
| 213 | } | ||
| 214 | |||
| 215 | table.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 | |||
| 226 | table.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 | |||
| 236 | table.exceptions { | ||
| 237 | table-layout: fixed; | ||
| 238 | width: 100%; | ||
| 239 | } | ||
| 240 | |||
| 241 | |||
| 242 | table.exceptions th.exceptionNameColumn { | ||
| 243 | width: 33%; | ||
| 244 | } | ||
| 245 | |||
| 246 | table.exceptions th.exceptionConditionColumn { | ||
| 247 | width: 66%; | ||
| 248 | } | ||
| 249 | |||
| 250 | table.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 | |||
| 261 | table.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 | |||
| 270 | table.permissions { | ||
| 271 | table-layout: fixed; | ||
| 272 | width: 100%; | ||
| 273 | } | ||
| 274 | |||
| 275 | |||
| 276 | table.permissions th.permissionNameColumn { | ||
| 277 | width: 33%; | ||
| 278 | } | ||
| 279 | |||
| 280 | table.permissions th.permissionConditionColumn { | ||
| 281 | width: 66%; | ||
| 282 | } | ||
| 283 | |||
| 284 | table.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 | |||
| 295 | table.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 | |||
| 304 | span.obsolete { | ||
| 305 | color: red; | ||
| 306 | } | ||
| 307 | |||
| 308 | span.cs { | ||
| 309 | display: inline; | ||
| 310 | } | ||
| 311 | |||
| 312 | span.vb { | ||
| 313 | display: none; | ||
| 314 | } | ||
| 315 | |||
| 316 | span.cpp { | ||
| 317 | display: none; | ||
| 318 | } | ||
| 319 | /* syntax styling */ | ||
| 320 | |||
| 321 | div.code span.identifier { | ||
| 322 | font-size: 120%; | ||
| 323 | font-weight: bold; | ||
| 324 | } | ||
| 325 | |||
| 326 | div.code span.keyword { | ||
| 327 | color: green; | ||
| 328 | } | ||
| 329 | |||
| 330 | div.code span.parameter { | ||
| 331 | font-style: italic; | ||
| 332 | color: purple; | ||
| 333 | } | ||
| 334 | |||
| 335 | div.code span.literal { | ||
| 336 | color: purple; | ||
| 337 | } | ||
| 338 | |||
| 339 | div.code span.comment { | ||
| 340 | color: red; | ||
| 341 | } | ||
| 342 | |||
| 343 | span.foreignPhrase { | ||
| 344 | font-style: italic; | ||
| 345 | } | ||
| 346 | |||
| 347 | span.placeholder { | ||
| 348 | font-style: italic; | ||
| 349 | } | ||
| 350 | |||
| 351 | a { | ||
| 352 | color: blue; | ||
| 353 | font-weight: bold; | ||
| 354 | text-decoration: none; | ||
| 355 | } | ||
| 356 | |||
| 357 | MSHelp\:link { | ||
| 358 | color: blue; | ||
| 359 | font-weight: bold; | ||
| 360 | hoverColor: #3366ff; | ||
| 361 | } | ||
| 362 | |||
| 363 | span.nolink { | ||
| 364 | font-weight: bold; | ||
| 365 | } | ||
| 366 | |||
| 367 | table.filter { | ||
| 368 | table-layout: fixed; | ||
| 369 | } | ||
| 370 | |||
| 371 | tr.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 | |||
| 382 | tr.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 | |||
| 392 | td.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>&lt%3bscript src=&quot%3bhelplink.js&quot%3b&gt%3b&lt%3b/script&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 | |||
| 3 | FixHelpLinks(); | ||
| 4 | |||
| 5 | function 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 | |||
| 150 | function 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. | ||
| 167 | function 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 | |||
| 3 | using 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 | |||
| 3 | namespace 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 | |||
| 3 | namespace 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 | |||
| 3 | namespace 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 | |||
| 3 | using System; | ||
| 4 | using System.IO; | ||
| 5 | using System.Data; | ||
| 6 | using System.Collections; | ||
| 7 | using System.Collections.Generic; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.Windows.Forms; | ||
| 10 | using WixToolset.Dtf.WindowsInstaller; | ||
| 11 | |||
| 12 | namespace 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 | |||
| 3 | using System; | ||
| 4 | using System.Data; | ||
| 5 | |||
| 6 | namespace 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 | |||
| 3 | using System; | ||
| 4 | using System.IO; | ||
| 5 | using System.Drawing; | ||
| 6 | using System.Collections; | ||
| 7 | using System.ComponentModel; | ||
| 8 | using System.Diagnostics.CodeAnalysis; | ||
| 9 | using System.Windows.Forms; | ||
| 10 | using System.Globalization; | ||
| 11 | using System.Reflection; | ||
| 12 | using System.Resources; | ||
| 13 | using System.Threading; | ||
| 14 | using System.Security.Permissions; | ||
| 15 | using 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 | |||
| 25 | namespace 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 | |||
| 3 | using System; | ||
| 4 | using System.IO; | ||
| 5 | using System.Data; | ||
| 6 | using System.Text; | ||
| 7 | using System.Collections; | ||
| 8 | using System.Collections.Generic; | ||
| 9 | using System.Globalization; | ||
| 10 | using System.Windows.Forms; | ||
| 11 | using Microsoft.Win32; | ||
| 12 | using WixToolset.Dtf.WindowsInstaller; | ||
| 13 | using View = WixToolset.Dtf.WindowsInstaller.View; | ||
| 14 | |||
| 15 | namespace 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 | |||
| 3 | using System; | ||
| 4 | using System.Collections; | ||
| 5 | using WixToolset.Dtf.WindowsInstaller; | ||
| 6 | |||
| 7 | |||
| 8 | namespace 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 | |||
| 3 | using System; | ||
| 4 | using System.IO; | ||
| 5 | using System.Security; | ||
| 6 | using System.Data; | ||
| 7 | using System.Collections; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.Windows.Forms; | ||
| 10 | using WixToolset.Dtf.WindowsInstaller; | ||
| 11 | |||
| 12 | namespace 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 | |||
| 3 | using System; | ||
| 4 | using System.Data; | ||
| 5 | using System.Globalization; | ||
| 6 | using System.Collections; | ||
| 7 | using System.Windows.Forms; | ||
| 8 | using WixToolset.Dtf.WindowsInstaller; | ||
| 9 | |||
| 10 | namespace 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 | |||
| 3 | using 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 | |||
| 3 | namespace 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 | |||
| 3 | namespace 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 | |||
| 5 | void 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> | ||
| 32 | bool 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> | ||
| 125 | bool 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> | ||
| 213 | bool 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. | ||
| 7 | static const wchar_t* g_szWorkingDir; | ||
| 8 | static ICorRuntimeHost* g_pClrHost; | ||
| 9 | static _AppDomain* g_pAppDomain; | ||
| 10 | static _MethodInfo* g_pProcessMessageMethod; | ||
| 11 | static _MethodInfo* g_pShutdownMethod; | ||
| 12 | |||
| 13 | // Reserve extra space for strings to be replaced at build time. | ||
| 14 | #define NULLSPACE \ | ||
| 15 | L"\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" \ | ||
| 16 | L"\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" \ | ||
| 17 | L"\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" \ | ||
| 18 | L"\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 | |||
| 23 | bool 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> | ||
| 37 | extern "C" | ||
| 38 | UINT __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> | ||
| 145 | extern "C" | ||
| 146 | INT __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 | |||
| 176 | LExit: | ||
| 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> | ||
| 192 | extern "C" | ||
| 193 | DWORD __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> | ||
| 227 | bool 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 | |||
| 262 | LExit: | ||
| 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 | |||
| 4 | LIBRARY "SfxCA" | ||
| 5 | |||
| 6 | EXPORTS | ||
| 7 | |||
| 8 | CustomActionEntryPoint000________________________________________________=CustomActionEntryPoint000 | ||
| 9 | CustomActionEntryPoint001________________________________________________=CustomActionEntryPoint001 | ||
| 10 | CustomActionEntryPoint002________________________________________________=CustomActionEntryPoint002 | ||
| 11 | CustomActionEntryPoint003________________________________________________=CustomActionEntryPoint003 | ||
| 12 | CustomActionEntryPoint004________________________________________________=CustomActionEntryPoint004 | ||
| 13 | CustomActionEntryPoint005________________________________________________=CustomActionEntryPoint005 | ||
| 14 | CustomActionEntryPoint006________________________________________________=CustomActionEntryPoint006 | ||
| 15 | CustomActionEntryPoint007________________________________________________=CustomActionEntryPoint007 | ||
| 16 | CustomActionEntryPoint008________________________________________________=CustomActionEntryPoint008 | ||
| 17 | CustomActionEntryPoint009________________________________________________=CustomActionEntryPoint009 | ||
| 18 | CustomActionEntryPoint010________________________________________________=CustomActionEntryPoint010 | ||
| 19 | CustomActionEntryPoint011________________________________________________=CustomActionEntryPoint011 | ||
| 20 | CustomActionEntryPoint012________________________________________________=CustomActionEntryPoint012 | ||
| 21 | CustomActionEntryPoint013________________________________________________=CustomActionEntryPoint013 | ||
| 22 | CustomActionEntryPoint014________________________________________________=CustomActionEntryPoint014 | ||
| 23 | CustomActionEntryPoint015________________________________________________=CustomActionEntryPoint015 | ||
| 24 | CustomActionEntryPoint016________________________________________________=CustomActionEntryPoint016 | ||
| 25 | CustomActionEntryPoint017________________________________________________=CustomActionEntryPoint017 | ||
| 26 | CustomActionEntryPoint018________________________________________________=CustomActionEntryPoint018 | ||
| 27 | CustomActionEntryPoint019________________________________________________=CustomActionEntryPoint019 | ||
| 28 | CustomActionEntryPoint020________________________________________________=CustomActionEntryPoint020 | ||
| 29 | CustomActionEntryPoint021________________________________________________=CustomActionEntryPoint021 | ||
| 30 | CustomActionEntryPoint022________________________________________________=CustomActionEntryPoint022 | ||
| 31 | CustomActionEntryPoint023________________________________________________=CustomActionEntryPoint023 | ||
| 32 | CustomActionEntryPoint024________________________________________________=CustomActionEntryPoint024 | ||
| 33 | CustomActionEntryPoint025________________________________________________=CustomActionEntryPoint025 | ||
| 34 | CustomActionEntryPoint026________________________________________________=CustomActionEntryPoint026 | ||
| 35 | CustomActionEntryPoint027________________________________________________=CustomActionEntryPoint027 | ||
| 36 | CustomActionEntryPoint028________________________________________________=CustomActionEntryPoint028 | ||
| 37 | CustomActionEntryPoint029________________________________________________=CustomActionEntryPoint029 | ||
| 38 | CustomActionEntryPoint030________________________________________________=CustomActionEntryPoint030 | ||
| 39 | CustomActionEntryPoint031________________________________________________=CustomActionEntryPoint031 | ||
| 40 | CustomActionEntryPoint032________________________________________________=CustomActionEntryPoint032 | ||
| 41 | CustomActionEntryPoint033________________________________________________=CustomActionEntryPoint033 | ||
| 42 | CustomActionEntryPoint034________________________________________________=CustomActionEntryPoint034 | ||
| 43 | CustomActionEntryPoint035________________________________________________=CustomActionEntryPoint035 | ||
| 44 | CustomActionEntryPoint036________________________________________________=CustomActionEntryPoint036 | ||
| 45 | CustomActionEntryPoint037________________________________________________=CustomActionEntryPoint037 | ||
| 46 | CustomActionEntryPoint038________________________________________________=CustomActionEntryPoint038 | ||
| 47 | CustomActionEntryPoint039________________________________________________=CustomActionEntryPoint039 | ||
| 48 | CustomActionEntryPoint040________________________________________________=CustomActionEntryPoint040 | ||
| 49 | CustomActionEntryPoint041________________________________________________=CustomActionEntryPoint041 | ||
| 50 | CustomActionEntryPoint042________________________________________________=CustomActionEntryPoint042 | ||
| 51 | CustomActionEntryPoint043________________________________________________=CustomActionEntryPoint043 | ||
| 52 | CustomActionEntryPoint044________________________________________________=CustomActionEntryPoint044 | ||
| 53 | CustomActionEntryPoint045________________________________________________=CustomActionEntryPoint045 | ||
| 54 | CustomActionEntryPoint046________________________________________________=CustomActionEntryPoint046 | ||
| 55 | CustomActionEntryPoint047________________________________________________=CustomActionEntryPoint047 | ||
| 56 | CustomActionEntryPoint048________________________________________________=CustomActionEntryPoint048 | ||
| 57 | CustomActionEntryPoint049________________________________________________=CustomActionEntryPoint049 | ||
| 58 | CustomActionEntryPoint050________________________________________________=CustomActionEntryPoint050 | ||
| 59 | CustomActionEntryPoint051________________________________________________=CustomActionEntryPoint051 | ||
| 60 | CustomActionEntryPoint052________________________________________________=CustomActionEntryPoint052 | ||
| 61 | CustomActionEntryPoint053________________________________________________=CustomActionEntryPoint053 | ||
| 62 | CustomActionEntryPoint054________________________________________________=CustomActionEntryPoint054 | ||
| 63 | CustomActionEntryPoint055________________________________________________=CustomActionEntryPoint055 | ||
| 64 | CustomActionEntryPoint056________________________________________________=CustomActionEntryPoint056 | ||
| 65 | CustomActionEntryPoint057________________________________________________=CustomActionEntryPoint057 | ||
| 66 | CustomActionEntryPoint058________________________________________________=CustomActionEntryPoint058 | ||
| 67 | CustomActionEntryPoint059________________________________________________=CustomActionEntryPoint059 | ||
| 68 | CustomActionEntryPoint060________________________________________________=CustomActionEntryPoint060 | ||
| 69 | CustomActionEntryPoint061________________________________________________=CustomActionEntryPoint061 | ||
| 70 | CustomActionEntryPoint062________________________________________________=CustomActionEntryPoint062 | ||
| 71 | CustomActionEntryPoint063________________________________________________=CustomActionEntryPoint063 | ||
| 72 | CustomActionEntryPoint064________________________________________________=CustomActionEntryPoint064 | ||
| 73 | CustomActionEntryPoint065________________________________________________=CustomActionEntryPoint065 | ||
| 74 | CustomActionEntryPoint066________________________________________________=CustomActionEntryPoint066 | ||
| 75 | CustomActionEntryPoint067________________________________________________=CustomActionEntryPoint067 | ||
| 76 | CustomActionEntryPoint068________________________________________________=CustomActionEntryPoint068 | ||
| 77 | CustomActionEntryPoint069________________________________________________=CustomActionEntryPoint069 | ||
| 78 | CustomActionEntryPoint070________________________________________________=CustomActionEntryPoint070 | ||
| 79 | CustomActionEntryPoint071________________________________________________=CustomActionEntryPoint071 | ||
| 80 | CustomActionEntryPoint072________________________________________________=CustomActionEntryPoint072 | ||
| 81 | CustomActionEntryPoint073________________________________________________=CustomActionEntryPoint073 | ||
| 82 | CustomActionEntryPoint074________________________________________________=CustomActionEntryPoint074 | ||
| 83 | CustomActionEntryPoint075________________________________________________=CustomActionEntryPoint075 | ||
| 84 | CustomActionEntryPoint076________________________________________________=CustomActionEntryPoint076 | ||
| 85 | CustomActionEntryPoint077________________________________________________=CustomActionEntryPoint077 | ||
| 86 | CustomActionEntryPoint078________________________________________________=CustomActionEntryPoint078 | ||
| 87 | CustomActionEntryPoint079________________________________________________=CustomActionEntryPoint079 | ||
| 88 | CustomActionEntryPoint080________________________________________________=CustomActionEntryPoint080 | ||
| 89 | CustomActionEntryPoint081________________________________________________=CustomActionEntryPoint081 | ||
| 90 | CustomActionEntryPoint082________________________________________________=CustomActionEntryPoint082 | ||
| 91 | CustomActionEntryPoint083________________________________________________=CustomActionEntryPoint083 | ||
| 92 | CustomActionEntryPoint084________________________________________________=CustomActionEntryPoint084 | ||
| 93 | CustomActionEntryPoint085________________________________________________=CustomActionEntryPoint085 | ||
| 94 | CustomActionEntryPoint086________________________________________________=CustomActionEntryPoint086 | ||
| 95 | CustomActionEntryPoint087________________________________________________=CustomActionEntryPoint087 | ||
| 96 | CustomActionEntryPoint088________________________________________________=CustomActionEntryPoint088 | ||
| 97 | CustomActionEntryPoint089________________________________________________=CustomActionEntryPoint089 | ||
| 98 | CustomActionEntryPoint090________________________________________________=CustomActionEntryPoint090 | ||
| 99 | CustomActionEntryPoint091________________________________________________=CustomActionEntryPoint091 | ||
| 100 | CustomActionEntryPoint092________________________________________________=CustomActionEntryPoint092 | ||
| 101 | CustomActionEntryPoint093________________________________________________=CustomActionEntryPoint093 | ||
| 102 | CustomActionEntryPoint094________________________________________________=CustomActionEntryPoint094 | ||
| 103 | CustomActionEntryPoint095________________________________________________=CustomActionEntryPoint095 | ||
| 104 | CustomActionEntryPoint096________________________________________________=CustomActionEntryPoint096 | ||
| 105 | CustomActionEntryPoint097________________________________________________=CustomActionEntryPoint097 | ||
| 106 | CustomActionEntryPoint098________________________________________________=CustomActionEntryPoint098 | ||
| 107 | CustomActionEntryPoint099________________________________________________=CustomActionEntryPoint099 | ||
| 108 | CustomActionEntryPoint100________________________________________________=CustomActionEntryPoint100 | ||
| 109 | CustomActionEntryPoint101________________________________________________=CustomActionEntryPoint101 | ||
| 110 | CustomActionEntryPoint102________________________________________________=CustomActionEntryPoint102 | ||
| 111 | CustomActionEntryPoint103________________________________________________=CustomActionEntryPoint103 | ||
| 112 | CustomActionEntryPoint104________________________________________________=CustomActionEntryPoint104 | ||
| 113 | CustomActionEntryPoint105________________________________________________=CustomActionEntryPoint105 | ||
| 114 | CustomActionEntryPoint106________________________________________________=CustomActionEntryPoint106 | ||
| 115 | CustomActionEntryPoint107________________________________________________=CustomActionEntryPoint107 | ||
| 116 | CustomActionEntryPoint108________________________________________________=CustomActionEntryPoint108 | ||
| 117 | CustomActionEntryPoint109________________________________________________=CustomActionEntryPoint109 | ||
| 118 | CustomActionEntryPoint110________________________________________________=CustomActionEntryPoint110 | ||
| 119 | CustomActionEntryPoint111________________________________________________=CustomActionEntryPoint111 | ||
| 120 | CustomActionEntryPoint112________________________________________________=CustomActionEntryPoint112 | ||
| 121 | CustomActionEntryPoint113________________________________________________=CustomActionEntryPoint113 | ||
| 122 | CustomActionEntryPoint114________________________________________________=CustomActionEntryPoint114 | ||
| 123 | CustomActionEntryPoint115________________________________________________=CustomActionEntryPoint115 | ||
| 124 | CustomActionEntryPoint116________________________________________________=CustomActionEntryPoint116 | ||
| 125 | CustomActionEntryPoint117________________________________________________=CustomActionEntryPoint117 | ||
| 126 | CustomActionEntryPoint118________________________________________________=CustomActionEntryPoint118 | ||
| 127 | CustomActionEntryPoint119________________________________________________=CustomActionEntryPoint119 | ||
| 128 | CustomActionEntryPoint120________________________________________________=CustomActionEntryPoint120 | ||
| 129 | CustomActionEntryPoint121________________________________________________=CustomActionEntryPoint121 | ||
| 130 | CustomActionEntryPoint122________________________________________________=CustomActionEntryPoint122 | ||
| 131 | CustomActionEntryPoint123________________________________________________=CustomActionEntryPoint123 | ||
| 132 | CustomActionEntryPoint124________________________________________________=CustomActionEntryPoint124 | ||
| 133 | CustomActionEntryPoint125________________________________________________=CustomActionEntryPoint125 | ||
| 134 | CustomActionEntryPoint126________________________________________________=CustomActionEntryPoint126 | ||
| 135 | CustomActionEntryPoint127________________________________________________=CustomActionEntryPoint127 | ||
| 136 | |||
| 137 | zzzzInvokeManagedCustomActionOutOfProcW=InvokeManagedCustomActionOutOfProc | ||
| 138 | zzzInitializeEmbeddedUI=InitializeEmbeddedUI | ||
| 139 | zzzEmbeddedUIHandler=EmbeddedUIHandler | ||
| 140 | zzzShutdownEmbeddedUI=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 | |||
| 3 | int 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 \ | ||
| 24 | L"\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" \ | ||
| 25 | L"\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" \ | ||
| 26 | L"\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" \ | ||
| 27 | L"\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 | |||
| 33 | TEMPLATE_CA_ENTRYPOINT(000,L"000"); | ||
| 34 | TEMPLATE_CA_ENTRYPOINT(001,L"001"); | ||
| 35 | TEMPLATE_CA_ENTRYPOINT(002,L"002"); | ||
| 36 | TEMPLATE_CA_ENTRYPOINT(003,L"003"); | ||
| 37 | TEMPLATE_CA_ENTRYPOINT(004,L"004"); | ||
| 38 | TEMPLATE_CA_ENTRYPOINT(005,L"005"); | ||
| 39 | TEMPLATE_CA_ENTRYPOINT(006,L"006"); | ||
| 40 | TEMPLATE_CA_ENTRYPOINT(007,L"007"); | ||
| 41 | TEMPLATE_CA_ENTRYPOINT(008,L"008"); | ||
| 42 | TEMPLATE_CA_ENTRYPOINT(009,L"009"); | ||
| 43 | TEMPLATE_CA_ENTRYPOINT(010,L"010"); | ||
| 44 | TEMPLATE_CA_ENTRYPOINT(011,L"011"); | ||
| 45 | TEMPLATE_CA_ENTRYPOINT(012,L"012"); | ||
| 46 | TEMPLATE_CA_ENTRYPOINT(013,L"013"); | ||
| 47 | TEMPLATE_CA_ENTRYPOINT(014,L"014"); | ||
| 48 | TEMPLATE_CA_ENTRYPOINT(015,L"015"); | ||
| 49 | TEMPLATE_CA_ENTRYPOINT(016,L"016"); | ||
| 50 | TEMPLATE_CA_ENTRYPOINT(017,L"017"); | ||
| 51 | TEMPLATE_CA_ENTRYPOINT(018,L"018"); | ||
| 52 | TEMPLATE_CA_ENTRYPOINT(019,L"019"); | ||
| 53 | TEMPLATE_CA_ENTRYPOINT(020,L"020"); | ||
| 54 | TEMPLATE_CA_ENTRYPOINT(021,L"021"); | ||
| 55 | TEMPLATE_CA_ENTRYPOINT(022,L"022"); | ||
| 56 | TEMPLATE_CA_ENTRYPOINT(023,L"023"); | ||
| 57 | TEMPLATE_CA_ENTRYPOINT(024,L"024"); | ||
| 58 | TEMPLATE_CA_ENTRYPOINT(025,L"025"); | ||
| 59 | TEMPLATE_CA_ENTRYPOINT(026,L"026"); | ||
| 60 | TEMPLATE_CA_ENTRYPOINT(027,L"027"); | ||
| 61 | TEMPLATE_CA_ENTRYPOINT(028,L"028"); | ||
| 62 | TEMPLATE_CA_ENTRYPOINT(029,L"029"); | ||
| 63 | TEMPLATE_CA_ENTRYPOINT(030,L"030"); | ||
| 64 | TEMPLATE_CA_ENTRYPOINT(031,L"031"); | ||
| 65 | TEMPLATE_CA_ENTRYPOINT(032,L"032"); | ||
| 66 | TEMPLATE_CA_ENTRYPOINT(033,L"033"); | ||
| 67 | TEMPLATE_CA_ENTRYPOINT(034,L"034"); | ||
| 68 | TEMPLATE_CA_ENTRYPOINT(035,L"035"); | ||
| 69 | TEMPLATE_CA_ENTRYPOINT(036,L"036"); | ||
| 70 | TEMPLATE_CA_ENTRYPOINT(037,L"037"); | ||
| 71 | TEMPLATE_CA_ENTRYPOINT(038,L"038"); | ||
| 72 | TEMPLATE_CA_ENTRYPOINT(039,L"039"); | ||
| 73 | TEMPLATE_CA_ENTRYPOINT(040,L"040"); | ||
| 74 | TEMPLATE_CA_ENTRYPOINT(041,L"041"); | ||
| 75 | TEMPLATE_CA_ENTRYPOINT(042,L"042"); | ||
| 76 | TEMPLATE_CA_ENTRYPOINT(043,L"043"); | ||
| 77 | TEMPLATE_CA_ENTRYPOINT(044,L"044"); | ||
| 78 | TEMPLATE_CA_ENTRYPOINT(045,L"045"); | ||
| 79 | TEMPLATE_CA_ENTRYPOINT(046,L"046"); | ||
| 80 | TEMPLATE_CA_ENTRYPOINT(047,L"047"); | ||
| 81 | TEMPLATE_CA_ENTRYPOINT(048,L"048"); | ||
| 82 | TEMPLATE_CA_ENTRYPOINT(049,L"049"); | ||
| 83 | TEMPLATE_CA_ENTRYPOINT(050,L"050"); | ||
| 84 | TEMPLATE_CA_ENTRYPOINT(051,L"051"); | ||
| 85 | TEMPLATE_CA_ENTRYPOINT(052,L"052"); | ||
| 86 | TEMPLATE_CA_ENTRYPOINT(053,L"053"); | ||
| 87 | TEMPLATE_CA_ENTRYPOINT(054,L"054"); | ||
| 88 | TEMPLATE_CA_ENTRYPOINT(055,L"055"); | ||
| 89 | TEMPLATE_CA_ENTRYPOINT(056,L"056"); | ||
| 90 | TEMPLATE_CA_ENTRYPOINT(057,L"057"); | ||
| 91 | TEMPLATE_CA_ENTRYPOINT(058,L"058"); | ||
| 92 | TEMPLATE_CA_ENTRYPOINT(059,L"059"); | ||
| 93 | TEMPLATE_CA_ENTRYPOINT(060,L"060"); | ||
| 94 | TEMPLATE_CA_ENTRYPOINT(061,L"061"); | ||
| 95 | TEMPLATE_CA_ENTRYPOINT(062,L"062"); | ||
| 96 | TEMPLATE_CA_ENTRYPOINT(063,L"063"); | ||
| 97 | TEMPLATE_CA_ENTRYPOINT(064,L"064"); | ||
| 98 | TEMPLATE_CA_ENTRYPOINT(065,L"065"); | ||
| 99 | TEMPLATE_CA_ENTRYPOINT(066,L"066"); | ||
| 100 | TEMPLATE_CA_ENTRYPOINT(067,L"067"); | ||
| 101 | TEMPLATE_CA_ENTRYPOINT(068,L"068"); | ||
| 102 | TEMPLATE_CA_ENTRYPOINT(069,L"069"); | ||
| 103 | TEMPLATE_CA_ENTRYPOINT(070,L"070"); | ||
| 104 | TEMPLATE_CA_ENTRYPOINT(071,L"071"); | ||
| 105 | TEMPLATE_CA_ENTRYPOINT(072,L"072"); | ||
| 106 | TEMPLATE_CA_ENTRYPOINT(073,L"073"); | ||
| 107 | TEMPLATE_CA_ENTRYPOINT(074,L"074"); | ||
| 108 | TEMPLATE_CA_ENTRYPOINT(075,L"075"); | ||
| 109 | TEMPLATE_CA_ENTRYPOINT(076,L"076"); | ||
| 110 | TEMPLATE_CA_ENTRYPOINT(077,L"077"); | ||
| 111 | TEMPLATE_CA_ENTRYPOINT(078,L"078"); | ||
| 112 | TEMPLATE_CA_ENTRYPOINT(079,L"079"); | ||
| 113 | TEMPLATE_CA_ENTRYPOINT(080,L"080"); | ||
| 114 | TEMPLATE_CA_ENTRYPOINT(081,L"081"); | ||
| 115 | TEMPLATE_CA_ENTRYPOINT(082,L"082"); | ||
| 116 | TEMPLATE_CA_ENTRYPOINT(083,L"083"); | ||
| 117 | TEMPLATE_CA_ENTRYPOINT(084,L"084"); | ||
| 118 | TEMPLATE_CA_ENTRYPOINT(085,L"085"); | ||
| 119 | TEMPLATE_CA_ENTRYPOINT(086,L"086"); | ||
| 120 | TEMPLATE_CA_ENTRYPOINT(087,L"087"); | ||
| 121 | TEMPLATE_CA_ENTRYPOINT(088,L"088"); | ||
| 122 | TEMPLATE_CA_ENTRYPOINT(089,L"089"); | ||
| 123 | TEMPLATE_CA_ENTRYPOINT(090,L"090"); | ||
| 124 | TEMPLATE_CA_ENTRYPOINT(091,L"091"); | ||
| 125 | TEMPLATE_CA_ENTRYPOINT(092,L"092"); | ||
| 126 | TEMPLATE_CA_ENTRYPOINT(093,L"093"); | ||
| 127 | TEMPLATE_CA_ENTRYPOINT(094,L"094"); | ||
| 128 | TEMPLATE_CA_ENTRYPOINT(095,L"095"); | ||
| 129 | TEMPLATE_CA_ENTRYPOINT(096,L"096"); | ||
| 130 | TEMPLATE_CA_ENTRYPOINT(097,L"097"); | ||
| 131 | TEMPLATE_CA_ENTRYPOINT(098,L"098"); | ||
| 132 | TEMPLATE_CA_ENTRYPOINT(099,L"099"); | ||
| 133 | TEMPLATE_CA_ENTRYPOINT(100,L"100"); | ||
| 134 | TEMPLATE_CA_ENTRYPOINT(101,L"101"); | ||
| 135 | TEMPLATE_CA_ENTRYPOINT(102,L"102"); | ||
| 136 | TEMPLATE_CA_ENTRYPOINT(103,L"103"); | ||
| 137 | TEMPLATE_CA_ENTRYPOINT(104,L"104"); | ||
| 138 | TEMPLATE_CA_ENTRYPOINT(105,L"105"); | ||
| 139 | TEMPLATE_CA_ENTRYPOINT(106,L"106"); | ||
| 140 | TEMPLATE_CA_ENTRYPOINT(107,L"107"); | ||
| 141 | TEMPLATE_CA_ENTRYPOINT(108,L"108"); | ||
| 142 | TEMPLATE_CA_ENTRYPOINT(109,L"109"); | ||
| 143 | TEMPLATE_CA_ENTRYPOINT(110,L"110"); | ||
| 144 | TEMPLATE_CA_ENTRYPOINT(111,L"111"); | ||
| 145 | TEMPLATE_CA_ENTRYPOINT(112,L"112"); | ||
| 146 | TEMPLATE_CA_ENTRYPOINT(113,L"113"); | ||
| 147 | TEMPLATE_CA_ENTRYPOINT(114,L"114"); | ||
| 148 | TEMPLATE_CA_ENTRYPOINT(115,L"115"); | ||
| 149 | TEMPLATE_CA_ENTRYPOINT(116,L"116"); | ||
| 150 | TEMPLATE_CA_ENTRYPOINT(117,L"117"); | ||
| 151 | TEMPLATE_CA_ENTRYPOINT(118,L"118"); | ||
| 152 | TEMPLATE_CA_ENTRYPOINT(119,L"119"); | ||
| 153 | TEMPLATE_CA_ENTRYPOINT(120,L"120"); | ||
| 154 | TEMPLATE_CA_ENTRYPOINT(121,L"121"); | ||
| 155 | TEMPLATE_CA_ENTRYPOINT(122,L"122"); | ||
| 156 | TEMPLATE_CA_ENTRYPOINT(123,L"123"); | ||
| 157 | TEMPLATE_CA_ENTRYPOINT(124,L"124"); | ||
| 158 | TEMPLATE_CA_ENTRYPOINT(125,L"125"); | ||
| 159 | TEMPLATE_CA_ENTRYPOINT(126,L"126"); | ||
| 160 | TEMPLATE_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. | ||
| 13 | static HFDI g_hfdi; | ||
| 14 | |||
| 15 | // FDI is not unicode-aware, so avoid passing these paths through the callbacks. | ||
| 16 | static const wchar_t* g_szExtractDir; | ||
| 17 | static 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. | ||
| 21 | static 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> | ||
| 44 | static 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> | ||
| 81 | static 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> | ||
| 98 | static 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> | ||
| 151 | static 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> | ||
| 179 | static 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> | ||
| 259 | int 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 | // | ||
| 12 | static __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 | |||
| 35 | typedef int (WINAPI *PMsiFunc_I_I)(int in1, __out int* out1); | ||
| 36 | typedef int (WINAPI *PMsiFunc_II_I)(int in1, int in2, __out int* out1); | ||
| 37 | typedef int (WINAPI *PMsiFunc_IS_I)(int in1, __in_z wchar_t* in2, __out int* out1); | ||
| 38 | typedef int (WINAPI *PMsiFunc_ISI_I)(int in1, __in_z wchar_t* in2, int in3, __out int* out1); | ||
| 39 | typedef int (WINAPI *PMsiFunc_ISII_I)(int in1, __in_z wchar_t* in2, int in3, int in4, __out int* out1); | ||
| 40 | typedef int (WINAPI *PMsiFunc_IS_II)(int in1, __in_z wchar_t* in2, __out int* out1, __out int* out2); | ||
| 41 | typedef MSIDBERROR (WINAPI *PMsiEFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); | ||
| 42 | typedef int (WINAPI *PMsiFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); | ||
| 43 | typedef int (WINAPI *PMsiFunc_II_S)(int in1, int in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); | ||
| 44 | typedef int (WINAPI *PMsiFunc_IS_S)(int in1, __in_z wchar_t* in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); | ||
| 45 | typedef 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 | |||
| 47 | UINT 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 | |||
| 60 | UINT 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 | |||
| 74 | UINT 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 | |||
| 88 | UINT 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 | |||
| 103 | UINT 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 | |||
| 119 | UINT 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 | |||
| 135 | UINT 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 | |||
| 157 | MSIDBERROR 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 | |||
| 178 | UINT 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 | |||
| 201 | UINT 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 | |||
| 224 | UINT 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 | |||
| 254 | void 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 | // | ||
| 12 | class RemoteMsiSession | ||
| 13 | { | ||
| 14 | public: | ||
| 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 | |||
| 91 | public: | ||
| 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 | |||
| 300 | private: | ||
| 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 | |||
| 858 | private: | ||
| 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 | |||
| 9 | HMODULE g_hModule; | ||
| 10 | bool g_fRunningOutOfProc = false; | ||
| 11 | |||
| 12 | RemoteMsiSession* g_pRemote = NULL; | ||
| 13 | |||
| 14 | // Prototypes for local functions. | ||
| 15 | // See the function definitions for comments. | ||
| 16 | |||
| 17 | bool 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> | ||
| 29 | extern "C" | ||
| 30 | void __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> | ||
| 69 | int 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> | ||
| 152 | void __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 "AssemblyName!Namespace.Class.Method" | ||
| 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> | ||
| 182 | int 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> | ||
| 253 | BOOL 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 "AssemblyName!Namespace.Class.Method" | ||
| 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> | ||
| 287 | bool 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 | |||
| 350 | LExit: | ||
| 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> | ||
| 10 | void 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> | ||
| 81 | bool 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 | |||
| 114 | bool 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) | ||
| 141 | bool 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 | |||
| 5 | void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...); | ||
| 6 | |||
| 7 | int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir); | ||
| 8 | |||
| 9 | bool DeleteDirectory(const wchar_t* szDir); | ||
| 10 | |||
| 11 | __success(return != false) | ||
| 12 | bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule, | ||
| 13 | __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf); | ||
| 14 | |||
| 15 | bool LoadCLR(MSIHANDLE hSession, const wchar_t* szVersion, const wchar_t* szConfigFile, | ||
| 16 | const wchar_t* szPrimaryAssembly, ICorRuntimeHost** ppHost); | ||
| 17 | |||
| 18 | bool CreateAppDomain(MSIHANDLE hSession, ICorRuntimeHost* pHost, | ||
| 19 | const wchar_t* szName, const wchar_t* szAppBase, | ||
| 20 | const wchar_t* szConfigFile, _AppDomain** ppAppDomain); | ||
| 21 | |||
| 22 | bool GetMethod(MSIHANDLE hSession, _AppDomain* pAppDomain, | ||
| 23 | const wchar_t* szAssembly, const wchar_t* szClass, | ||
| 24 | const wchar_t* szMethod, _MethodInfo** ppCAMethod); | ||
| 25 | |||
| 26 | extern HMODULE g_hModule; | ||
| 27 | extern bool g_fRunningOutOfProc; | ||
| 28 | |||
| 29 | extern 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") | ||
| 18 | using 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 | |||
| 3 | using System; | ||
| 4 | using System.IO; | ||
| 5 | using System.Reflection; | ||
| 6 | using System.Collections; | ||
| 7 | using System.Collections.Generic; | ||
| 8 | using System.Diagnostics.CodeAnalysis; | ||
| 9 | using System.Globalization; | ||
| 10 | using System.Text.RegularExpressions; | ||
| 11 | using WixToolset.Dtf.WindowsInstaller; | ||
| 12 | using 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> | ||
| 20 | public 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 | |||
| 3 | using 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 | |||
| 3 | namespace 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> | ||
