diff options
Diffstat (limited to 'src/tools')
103 files changed, 9940 insertions, 0 deletions
diff --git a/src/tools/Directory.Build.props b/src/tools/Directory.Build.props new file mode 100644 index 00000000..3fdc6553 --- /dev/null +++ b/src/tools/Directory.Build.props | |||
@@ -0,0 +1,10 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
3 | |||
4 | <Project> | ||
5 | <PropertyGroup> | ||
6 | <SegmentName>tools</SegmentName> | ||
7 | </PropertyGroup> | ||
8 | |||
9 | <Import Project="..\Directory.Build.props" /> | ||
10 | </Project> | ||
diff --git a/src/tools/Dtf/DDiff/CabDiffEngine.cs b/src/tools/Dtf/DDiff/CabDiffEngine.cs new file mode 100644 index 00000000..b3bafaa3 --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/DDiff/DDiff.cs b/src/tools/Dtf/DDiff/DDiff.cs new file mode 100644 index 00000000..4e9121b6 --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/DDiff/DDiff.csproj b/src/tools/Dtf/DDiff/DDiff.csproj new file mode 100644 index 00000000..bf4d6521 --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/DDiff/DirectoryDiffEngine.cs b/src/tools/Dtf/DDiff/DirectoryDiffEngine.cs new file mode 100644 index 00000000..a5216f22 --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/DDiff/FileDiffEngine.cs b/src/tools/Dtf/DDiff/FileDiffEngine.cs new file mode 100644 index 00000000..9eb197ea --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/DDiff/IDiffEngine.cs b/src/tools/Dtf/DDiff/IDiffEngine.cs new file mode 100644 index 00000000..918e42e6 --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/DDiff/MsiDiffEngine.cs b/src/tools/Dtf/DDiff/MsiDiffEngine.cs new file mode 100644 index 00000000..4e3b2c7b --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/DDiff/MspDiffEngine.cs b/src/tools/Dtf/DDiff/MspDiffEngine.cs new file mode 100644 index 00000000..a32ab24a --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/DDiff/TextFileDiffEngine.cs b/src/tools/Dtf/DDiff/TextFileDiffEngine.cs new file mode 100644 index 00000000..beb5ea84 --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/DDiff/VersionedFileDiffEngine.cs b/src/tools/Dtf/DDiff/VersionedFileDiffEngine.cs new file mode 100644 index 00000000..9a3e399f --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/Documents/Guide/Content/about.htm b/src/tools/Dtf/Documents/Guide/Content/about.htm new file mode 100644 index 00000000..393b5a81 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/buildingcas.htm b/src/tools/Dtf/Documents/Guide/Content/buildingcas.htm new file mode 100644 index 00000000..e88ad552 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/cabpack.htm b/src/tools/Dtf/Documents/Guide/Content/cabpack.htm new file mode 100644 index 00000000..2d9f725e --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/cabs.htm b/src/tools/Dtf/Documents/Guide/Content/cabs.htm new file mode 100644 index 00000000..e88d1e15 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/cabwrapper.htm b/src/tools/Dtf/Documents/Guide/Content/cabwrapper.htm new file mode 100644 index 00000000..fd88437c --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/caconfig.htm b/src/tools/Dtf/Documents/Guide/Content/caconfig.htm new file mode 100644 index 00000000..a6c97d2b --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/caproxy.htm b/src/tools/Dtf/Documents/Guide/Content/caproxy.htm new file mode 100644 index 00000000..2ee962d5 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/databases.htm b/src/tools/Dtf/Documents/Guide/Content/databases.htm new file mode 100644 index 00000000..4fe1fba9 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/debuggingcas.htm b/src/tools/Dtf/Documents/Guide/Content/debuggingcas.htm new file mode 100644 index 00000000..ca1be161 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/dependencies.htm b/src/tools/Dtf/Documents/Guide/Content/dependencies.htm new file mode 100644 index 00000000..cfec5880 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/filepatchwrapper.htm b/src/tools/Dtf/Documents/Guide/Content/filepatchwrapper.htm new file mode 100644 index 00000000..6bab69b5 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/history.htm b/src/tools/Dtf/Documents/Guide/Content/history.htm new file mode 100644 index 00000000..704ce875 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/installutil.htm b/src/tools/Dtf/Documents/Guide/Content/installutil.htm new file mode 100644 index 00000000..e235a7b6 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/inventory.htm b/src/tools/Dtf/Documents/Guide/Content/inventory.htm new file mode 100644 index 00000000..40a6ef74 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/managedcas.htm b/src/tools/Dtf/Documents/Guide/Content/managedcas.htm new file mode 100644 index 00000000..9cce0432 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/msihelper.htm b/src/tools/Dtf/Documents/Guide/Content/msihelper.htm new file mode 100644 index 00000000..c1493117 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/msiwrapper.htm b/src/tools/Dtf/Documents/Guide/Content/msiwrapper.htm new file mode 100644 index 00000000..70190ac4 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/packages.htm b/src/tools/Dtf/Documents/Guide/Content/packages.htm new file mode 100644 index 00000000..aa521685 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/powerdiff.htm b/src/tools/Dtf/Documents/Guide/Content/powerdiff.htm new file mode 100644 index 00000000..f420b47e --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/samplecas.htm b/src/tools/Dtf/Documents/Guide/Content/samplecas.htm new file mode 100644 index 00000000..4dfed6f0 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/samples.htm b/src/tools/Dtf/Documents/Guide/Content/samples.htm new file mode 100644 index 00000000..3bcd379a --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/support.htm b/src/tools/Dtf/Documents/Guide/Content/support.htm new file mode 100644 index 00000000..89acbadf --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/using.htm b/src/tools/Dtf/Documents/Guide/Content/using.htm new file mode 100644 index 00000000..6fe960e8 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/whatsnew.htm b/src/tools/Dtf/Documents/Guide/Content/whatsnew.htm new file mode 100644 index 00000000..3efe67bd --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/wifile.htm b/src/tools/Dtf/Documents/Guide/Content/wifile.htm new file mode 100644 index 00000000..20998b73 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/Content/writingcas.htm b/src/tools/Dtf/Documents/Guide/Content/writingcas.htm new file mode 100644 index 00000000..6beccf5f --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/DTF.hhc b/src/tools/Dtf/Documents/Guide/DTF.hhc new file mode 100644 index 00000000..bf43e447 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/DTF.hhk b/src/tools/Dtf/Documents/Guide/DTF.hhk new file mode 100644 index 00000000..bc6e49b3 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/DTF.hhp b/src/tools/Dtf/Documents/Guide/DTF.hhp new file mode 100644 index 00000000..e9b8ad90 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/dtfguide.helpproj b/src/tools/Dtf/Documents/Guide/dtfguide.helpproj new file mode 100644 index 00000000..4df2765d --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Guide/styles/presentation.css b/src/tools/Dtf/Documents/Guide/styles/presentation.css new file mode 100644 index 00000000..b71c8582 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Reference/Compression.htm b/src/tools/Dtf/Documents/Reference/Compression.htm new file mode 100644 index 00000000..7782bea1 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Reference/Compression1.png b/src/tools/Dtf/Documents/Reference/Compression1.png new file mode 100644 index 00000000..5b2e177f --- /dev/null +++ b/src/tools/Dtf/Documents/Reference/Compression1.png | |||
Binary files differ | |||
diff --git a/src/tools/Dtf/Documents/Reference/Compression2.png b/src/tools/Dtf/Documents/Reference/Compression2.png new file mode 100644 index 00000000..394a5f18 --- /dev/null +++ b/src/tools/Dtf/Documents/Reference/Compression2.png | |||
Binary files differ | |||
diff --git a/src/tools/Dtf/Documents/Reference/WindowsInstaller.htm b/src/tools/Dtf/Documents/Reference/WindowsInstaller.htm new file mode 100644 index 00000000..28990ce4 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Reference/WindowsInstaller1.png b/src/tools/Dtf/Documents/Reference/WindowsInstaller1.png new file mode 100644 index 00000000..cc769cc7 --- /dev/null +++ b/src/tools/Dtf/Documents/Reference/WindowsInstaller1.png | |||
Binary files differ | |||
diff --git a/src/tools/Dtf/Documents/Reference/WindowsInstaller2.png b/src/tools/Dtf/Documents/Reference/WindowsInstaller2.png new file mode 100644 index 00000000..0c11e501 --- /dev/null +++ b/src/tools/Dtf/Documents/Reference/WindowsInstaller2.png | |||
Binary files differ | |||
diff --git a/src/tools/Dtf/Documents/Reference/WindowsInstaller3.png b/src/tools/Dtf/Documents/Reference/WindowsInstaller3.png new file mode 100644 index 00000000..68acd7d8 --- /dev/null +++ b/src/tools/Dtf/Documents/Reference/WindowsInstaller3.png | |||
Binary files differ | |||
diff --git a/src/tools/Dtf/Documents/Reference/dtfref.shfbproj b/src/tools/Dtf/Documents/Reference/dtfref.shfbproj new file mode 100644 index 00000000..e45d2a07 --- /dev/null +++ b/src/tools/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/tools/Dtf/Documents/Reference/helplink.js b/src/tools/Dtf/Documents/Reference/helplink.js new file mode 100644 index 00000000..a4989824 --- /dev/null +++ b/src/tools/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/tools/Dtf/Inventory/Columns.resx b/src/tools/Dtf/Inventory/Columns.resx new file mode 100644 index 00000000..cfeb11e3 --- /dev/null +++ b/src/tools/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/tools/Dtf/Inventory/Features.cs b/src/tools/Dtf/Inventory/Features.cs new file mode 100644 index 00000000..9d2747ba --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/Inventory/IInventoryDataProvider.cs b/src/tools/Dtf/Inventory/IInventoryDataProvider.cs new file mode 100644 index 00000000..affcb358 --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/Inventory/Inventory.cs b/src/tools/Dtf/Inventory/Inventory.cs new file mode 100644 index 00000000..07735086 --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/Inventory/Inventory.csproj b/src/tools/Dtf/Inventory/Inventory.csproj new file mode 100644 index 00000000..57bae907 --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/Inventory/Inventory.ico b/src/tools/Dtf/Inventory/Inventory.ico new file mode 100644 index 00000000..d5757f7a --- /dev/null +++ b/src/tools/Dtf/Inventory/Inventory.ico | |||
Binary files differ | |||
diff --git a/src/tools/Dtf/Inventory/Inventory.resx b/src/tools/Dtf/Inventory/Inventory.resx new file mode 100644 index 00000000..9aeb4d2c --- /dev/null +++ b/src/tools/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/tools/Dtf/Inventory/components.cs b/src/tools/Dtf/Inventory/components.cs new file mode 100644 index 00000000..b516af46 --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/Inventory/msiutils.cs b/src/tools/Dtf/Inventory/msiutils.cs new file mode 100644 index 00000000..189d28a9 --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/Inventory/patches.cs b/src/tools/Dtf/Inventory/patches.cs new file mode 100644 index 00000000..ca96a97d --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/Inventory/products.cs b/src/tools/Dtf/Inventory/products.cs new file mode 100644 index 00000000..635e5439 --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/Inventory/xp.manifest b/src/tools/Dtf/Inventory/xp.manifest new file mode 100644 index 00000000..34d61fea --- /dev/null +++ b/src/tools/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/tools/Dtf/WiFile/WiFile.cs b/src/tools/Dtf/WiFile/WiFile.cs new file mode 100644 index 00000000..1e5c80df --- /dev/null +++ b/src/tools/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/tools/Dtf/WiFile/WiFile.csproj b/src/tools/Dtf/WiFile/WiFile.csproj new file mode 100644 index 00000000..c8d39235 --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/XPack/AssemblyInfo.cs b/src/tools/Dtf/XPack/AssemblyInfo.cs new file mode 100644 index 00000000..6dfb9437 --- /dev/null +++ b/src/tools/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/tools/Dtf/XPack/XPack.cs b/src/tools/Dtf/XPack/XPack.cs new file mode 100644 index 00000000..cc52bb7d --- /dev/null +++ b/src/tools/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.Tools.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/tools/Dtf/XPack/XPack.csproj b/src/tools/Dtf/XPack/XPack.csproj new file mode 100644 index 00000000..3e76de2e --- /dev/null +++ b/src/tools/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.Tools.XPack</RootNamespace> | ||
10 | <AssemblyName>XPack</AssemblyName> | ||
11 | <TargetFrameworkVersion>v2.0</TargetFrameworkVersion> | ||
12 | </PropertyGroup> | ||
13 | |||
14 | <ItemGroup> | ||
15 | <Compile Include="AssemblyInfo.cs" /> | ||
16 | <Compile Include="XPack.cs" /> | ||
17 | </ItemGroup> | ||
18 | |||
19 | <ItemGroup> | ||
20 | <Reference Include="System" /> | ||
21 | <ProjectReference Include="..\..\Libraries\Compression.Cab\Compression.Cab.csproj" /> | ||
22 | <ProjectReference Include="..\..\Libraries\Compression.Zip\Compression.Zip.csproj" /> | ||
23 | <ProjectReference Include="..\..\Libraries\Compression\Compression.csproj" /> | ||
24 | </ItemGroup> | ||
25 | |||
26 | <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" /> | ||
27 | </Project> | ||
diff --git a/src/tools/ThmViewerPackage/Package.wxs b/src/tools/ThmViewerPackage/Package.wxs new file mode 100644 index 00000000..dae6a9d0 --- /dev/null +++ b/src/tools/ThmViewerPackage/Package.wxs | |||
@@ -0,0 +1,30 @@ | |||
1 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
3 | <Package Name="WiX Toolset Theme Viewer" Manufacturer="WiX Toolset" Language="1033" Version="!(bind.fileVersion.ThmViewerFile)" UpgradeCode="59c4b122-5167-445b-8fc4-09dcd4eced89"> | ||
4 | <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." /> | ||
5 | <MediaTemplate EmbedCab="yes" /> | ||
6 | |||
7 | <Feature Id="Main"> | ||
8 | <ComponentGroupRef Id="Components" /> | ||
9 | </Feature> | ||
10 | </Package> | ||
11 | |||
12 | <Fragment> | ||
13 | <ComponentGroup Id="Components" Directory="INSTALLFOLDER" Subdirectory="bin"> | ||
14 | <Component> | ||
15 | <File Id="ThmViewerFile" Source="thmviewer.exe" /> | ||
16 | <Shortcut Name="!(bind.property.ProductName)" Directory="ShortcutFolder" Advertise="yes" /> | ||
17 | </Component> | ||
18 | </ComponentGroup> | ||
19 | </Fragment> | ||
20 | |||
21 | <Fragment> | ||
22 | <StandardDirectory Id="ProgramFilesFolder"> | ||
23 | <Directory Id="INSTALLFOLDER" Name="WiX Toolset v4.0" /> | ||
24 | </StandardDirectory> | ||
25 | <StandardDirectory Id="ProgramMenuFolder"> | ||
26 | <Directory Id="ShortcutFolder" Name="WiX Toolset" /> | ||
27 | </StandardDirectory> | ||
28 | </Fragment> | ||
29 | |||
30 | </Wix> | ||
diff --git a/src/tools/ThmViewerPackage/ThmViewerPackage.wixproj b/src/tools/ThmViewerPackage/ThmViewerPackage.wixproj new file mode 100644 index 00000000..9b2a2b02 --- /dev/null +++ b/src/tools/ThmViewerPackage/ThmViewerPackage.wixproj | |||
@@ -0,0 +1,10 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <Project Sdk="WixToolset.Sdk"> | ||
3 | <PropertyGroup> | ||
4 | <OutputPath>$(PackageOutputPath)</OutputPath> | ||
5 | </PropertyGroup> | ||
6 | |||
7 | <ItemGroup> | ||
8 | <ProjectReference Include="..\thmviewer\thmviewer.vcxproj" /> | ||
9 | </ItemGroup> | ||
10 | </Project> | ||
diff --git a/src/tools/WixToolset.Templates/WixToolset.Templates.csproj b/src/tools/WixToolset.Templates/WixToolset.Templates.csproj new file mode 100644 index 00000000..00b6c6d3 --- /dev/null +++ b/src/tools/WixToolset.Templates/WixToolset.Templates.csproj | |||
@@ -0,0 +1,20 @@ | |||
1 | <Project Sdk="Microsoft.NET.Sdk"> | ||
2 | <PropertyGroup> | ||
3 | <PackageType>Template</PackageType> | ||
4 | <Title>WixToolset Templates</Title> | ||
5 | <Description>Project and item template for the WiX Toolset.</Description> | ||
6 | |||
7 | <TargetFramework>netstandard2.0</TargetFramework> | ||
8 | <IncludeBuildOutput>false</IncludeBuildOutput> | ||
9 | <NoWarn>$(NoWarn);NU5128</NoWarn> | ||
10 | |||
11 | <GeneratePackageOnBuild>true</GeneratePackageOnBuild> | ||
12 | <IncludeContentInPack>true</IncludeContentInPack> | ||
13 | <ContentTargetFolders>content</ContentTargetFolders> | ||
14 | </PropertyGroup> | ||
15 | |||
16 | <ItemGroup> | ||
17 | <Compile Remove="**" /> | ||
18 | <Content Include="templates\**" /> | ||
19 | </ItemGroup> | ||
20 | </Project> | ||
diff --git a/src/tools/WixToolset.Templates/templates/FragmentItem/.template.config/template.json b/src/tools/WixToolset.Templates/templates/FragmentItem/.template.config/template.json new file mode 100644 index 00000000..2bd67b11 --- /dev/null +++ b/src/tools/WixToolset.Templates/templates/FragmentItem/.template.config/template.json | |||
@@ -0,0 +1,14 @@ | |||
1 | { | ||
2 | "$schema": "http://json.schemastore.org/template", | ||
3 | "identity": "WixToolset.Templates.FragmentItem", | ||
4 | "name": "WiX Toolset Fragment", | ||
5 | "shortName": "fragment", | ||
6 | "author": "WiX Toolset Team", | ||
7 | "classifications": ["WixToolset", "Code"], | ||
8 | "tags": { | ||
9 | "language": "WiX", | ||
10 | "type": "item" | ||
11 | }, | ||
12 | "sourceName": "$safeprojectname$", | ||
13 | "defaultName": "Fragment" | ||
14 | } | ||
diff --git a/src/tools/WixToolset.Templates/templates/FragmentItem/Fragment.wxs b/src/tools/WixToolset.Templates/templates/FragmentItem/Fragment.wxs new file mode 100644 index 00000000..12e1bfc3 --- /dev/null +++ b/src/tools/WixToolset.Templates/templates/FragmentItem/Fragment.wxs | |||
@@ -0,0 +1,4 @@ | |||
1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
2 | <Fragment> | ||
3 | </Fragment> | ||
4 | </Wix> | ||
diff --git a/src/tools/WixToolset.Templates/templates/LibraryProject/$safeprojectname$.wixproj b/src/tools/WixToolset.Templates/templates/LibraryProject/$safeprojectname$.wixproj new file mode 100644 index 00000000..c3536151 --- /dev/null +++ b/src/tools/WixToolset.Templates/templates/LibraryProject/$safeprojectname$.wixproj | |||
@@ -0,0 +1,5 @@ | |||
1 | <Project Sdk="WixToolset.Sdk/4.0.0"> | ||
2 | <PropertyGroup> | ||
3 | <OutputType>Library</OutputType> | ||
4 | </PropertyGroup> | ||
5 | </Project> | ||
diff --git a/src/tools/WixToolset.Templates/templates/LibraryProject/.template.config/template.json b/src/tools/WixToolset.Templates/templates/LibraryProject/.template.config/template.json new file mode 100644 index 00000000..ddfa82e9 --- /dev/null +++ b/src/tools/WixToolset.Templates/templates/LibraryProject/.template.config/template.json | |||
@@ -0,0 +1,14 @@ | |||
1 | { | ||
2 | "$schema": "http://json.schemastore.org/template", | ||
3 | "identity": "WixToolset.Templates.LibraryProject", | ||
4 | "name": "WiX Toolset Library", | ||
5 | "shortName": "wixlib", | ||
6 | "author": "WiX Toolset Team", | ||
7 | "classifications": ["WixToolset", "Library"], | ||
8 | "tags": { | ||
9 | "language": "WiX", | ||
10 | "type": "project" | ||
11 | }, | ||
12 | "sourceName": "$safeprojectname$", | ||
13 | "defaultName": "Library" | ||
14 | } | ||
diff --git a/src/tools/WixToolset.Templates/templates/LibraryProject/Fragment.wxs b/src/tools/WixToolset.Templates/templates/LibraryProject/Fragment.wxs new file mode 100644 index 00000000..12e1bfc3 --- /dev/null +++ b/src/tools/WixToolset.Templates/templates/LibraryProject/Fragment.wxs | |||
@@ -0,0 +1,4 @@ | |||
1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
2 | <Fragment> | ||
3 | </Fragment> | ||
4 | </Wix> | ||
diff --git a/src/tools/WixToolset.Templates/templates/ModuleProject/$safeprojectname$.wixproj b/src/tools/WixToolset.Templates/templates/ModuleProject/$safeprojectname$.wixproj new file mode 100644 index 00000000..cc813aab --- /dev/null +++ b/src/tools/WixToolset.Templates/templates/ModuleProject/$safeprojectname$.wixproj | |||
@@ -0,0 +1,5 @@ | |||
1 | <Project Sdk="WixToolset.Sdk/4.0.0"> | ||
2 | <PropertyGroup> | ||
3 | <OutputType>Module</OutputType> | ||
4 | </PropertyGroup> | ||
5 | </Project> | ||
diff --git a/src/tools/WixToolset.Templates/templates/ModuleProject/.template.config/template.json b/src/tools/WixToolset.Templates/templates/ModuleProject/.template.config/template.json new file mode 100644 index 00000000..2be72f83 --- /dev/null +++ b/src/tools/WixToolset.Templates/templates/ModuleProject/.template.config/template.json | |||
@@ -0,0 +1,26 @@ | |||
1 | { | ||
2 | "$schema": "http://json.schemastore.org/template", | ||
3 | "identity": "WixToolset.Templates.ModuleProject", | ||
4 | "name": "WiX Toolset Module", | ||
5 | "shortName": "msm", | ||
6 | "author": "WiX Toolset Team", | ||
7 | "classifications": ["WixToolset", "Module"], | ||
8 | "tags": { | ||
9 | "language": "WiX", | ||
10 | "type": "project" | ||
11 | }, | ||
12 | "symbols": { | ||
13 | "company": { | ||
14 | "type": "parameter", | ||
15 | "defaultValue": "Your Company Name", | ||
16 | "replaces": "$company$" | ||
17 | }, | ||
18 | "version": { | ||
19 | "type": "parameter", | ||
20 | "defaultValue": "0.0.1", | ||
21 | "replaces": "$version$" | ||
22 | } | ||
23 | }, | ||
24 | "sourceName": "$safeprojectname$", | ||
25 | "defaultName": "Module" | ||
26 | } | ||
diff --git a/src/tools/WixToolset.Templates/templates/ModuleProject/Module.wxs b/src/tools/WixToolset.Templates/templates/ModuleProject/Module.wxs new file mode 100644 index 00000000..592c844d --- /dev/null +++ b/src/tools/WixToolset.Templates/templates/ModuleProject/Module.wxs | |||
@@ -0,0 +1,8 @@ | |||
1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
2 | <Module Id="$safeprojectname$" Guid="PUT-GUID-HERE" Manufacturer="$company$" Language="0" Version="$version$"> | ||
3 | <Component Guid='PUT-GUID-HERE' Directory='TARGETDIR'> | ||
4 | <!-- TODO: Install something more useful than this source code file itself --> | ||
5 | <File Source="Module.wxs" /> | ||
6 | </Component> | ||
7 | </Module> | ||
8 | </Wix> | ||
diff --git a/src/tools/WixToolset.Templates/templates/PackageProject/$safeprojectname$.wixproj b/src/tools/WixToolset.Templates/templates/PackageProject/$safeprojectname$.wixproj new file mode 100644 index 00000000..17640703 --- /dev/null +++ b/src/tools/WixToolset.Templates/templates/PackageProject/$safeprojectname$.wixproj | |||
@@ -0,0 +1,2 @@ | |||
1 | <Project Sdk="WixToolset.Sdk/4.0.0"> | ||
2 | </Project> | ||
diff --git a/src/tools/WixToolset.Templates/templates/PackageProject/.template.config/template.json b/src/tools/WixToolset.Templates/templates/PackageProject/.template.config/template.json new file mode 100644 index 00000000..d12f3109 --- /dev/null +++ b/src/tools/WixToolset.Templates/templates/PackageProject/.template.config/template.json | |||
@@ -0,0 +1,26 @@ | |||
1 | { | ||
2 | "$schema": "http://json.schemastore.org/template", | ||
3 | "identity": "WixToolset.Templates.PackageProject", | ||
4 | "name": "WiX Toolset Package", | ||
5 | "shortName": "msi", | ||
6 | "author": "WiX Toolset Team", | ||
7 | "classifications": ["WixToolset", "Package"], | ||
8 | "tags": { | ||
9 | "language": "WiX", | ||
10 | "type": "project" | ||
11 | }, | ||
12 | "symbols": { | ||
13 | "company": { | ||
14 | "type": "parameter", | ||
15 | "defaultValue": "Your Company Name", | ||
16 | "replaces": "$company$" | ||
17 | }, | ||
18 | "version": { | ||
19 | "type": "parameter", | ||
20 | "defaultValue": "0.0.1", | ||
21 | "replaces": "$version$" | ||
22 | } | ||
23 | }, | ||
24 | "sourceName": "$safeprojectname$", | ||
25 | "defaultName": "Package" | ||
26 | } | ||
diff --git a/src/tools/WixToolset.Templates/templates/PackageProject/Package.wxs b/src/tools/WixToolset.Templates/templates/PackageProject/Package.wxs new file mode 100644 index 00000000..ac69e549 --- /dev/null +++ b/src/tools/WixToolset.Templates/templates/PackageProject/Package.wxs | |||
@@ -0,0 +1,24 @@ | |||
1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
2 | <Package Name="$safeprojectname$" Manufacturer="$company$" Version="$version$" UpgradeCode="PUT-GUID-HERE"> | ||
3 | <MajorUpgrade DowngradeError="A newer version of [ProductName] is already installed" /> | ||
4 | |||
5 | <Feature Id="Main"> | ||
6 | <ComponentGroupRef Id="Components" /> | ||
7 | </Feature> | ||
8 | </Package> | ||
9 | |||
10 | <Fragment> | ||
11 | <ComponentGroup Id="Components" Directory="INSTALLFOLDER"> | ||
12 | <Component> | ||
13 | <!-- TODO: Install something more useful than this source code file itself --> | ||
14 | <File Source="Package.wxs" /> | ||
15 | </Component> | ||
16 | </ComponentGroup> | ||
17 | </Fragment> | ||
18 | |||
19 | <Fragment> | ||
20 | <StandardDirectory Id="ProgramFiles6432Folder"> | ||
21 | <Directory Id="INSTALLFOLDER" Name="!(Property.Manufacturer) !(Property.ProductName)" /> | ||
22 | </StandardDirectory> | ||
23 | </Fragment> | ||
24 | </Wix> | ||
diff --git a/src/tools/burn/ManagedBundleRunner/BundleErrorEventArgs.cs b/src/tools/burn/ManagedBundleRunner/BundleErrorEventArgs.cs new file mode 100644 index 00000000..964b976e --- /dev/null +++ b/src/tools/burn/ManagedBundleRunner/BundleErrorEventArgs.cs | |||
@@ -0,0 +1,33 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Tools | ||
4 | { | ||
5 | using System; | ||
6 | |||
7 | /// <summary> | ||
8 | /// Arguments provided when bundle encounters an error. | ||
9 | /// </summary> | ||
10 | [Serializable] | ||
11 | public class BundleErrorEventArgs : EventArgs | ||
12 | { | ||
13 | /// <summary> | ||
14 | /// Gets the error code. | ||
15 | /// </summary> | ||
16 | public int Code { get; set; } | ||
17 | |||
18 | /// <summary> | ||
19 | /// Gets the error message. | ||
20 | /// </summary> | ||
21 | public string Message { get; set; } | ||
22 | |||
23 | /// <summary> | ||
24 | /// Gets the recommended display flags for an error dialog. | ||
25 | /// </summary> | ||
26 | public int UIHint { get; set; } | ||
27 | |||
28 | /// <summary> | ||
29 | /// Gets or sets the <see cref="Result"/> of the operation. This is passed back to the bundle. | ||
30 | /// </summary> | ||
31 | public BundleResult Result { get; set; } | ||
32 | } | ||
33 | } | ||
diff --git a/src/tools/burn/ManagedBundleRunner/BundleProgressEventArgs.cs b/src/tools/burn/ManagedBundleRunner/BundleProgressEventArgs.cs new file mode 100644 index 00000000..2add7cca --- /dev/null +++ b/src/tools/burn/ManagedBundleRunner/BundleProgressEventArgs.cs | |||
@@ -0,0 +1,23 @@ | |||
1 | // Copyright (c) .NET Foundation 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.Tools | ||
4 | { | ||
5 | using System; | ||
6 | |||
7 | /// <summary> | ||
8 | /// Arguments provided when bundle progress is updated. | ||
9 | /// </summary> | ||
10 | [Serializable] | ||
11 | public class BundleProgressEventArgs : EventArgs | ||
12 | { | ||
13 | /// <summary> | ||
14 | /// Gets the percentage from 0 to 100 completed for a bundle. | ||
15 | /// </summary> | ||
16 | public int Progress { get; set; } | ||
17 | |||
18 | /// <summary> | ||
19 | /// Gets or sets the <see cref="Result"/> of the operation. This is passed back to the bundle. | ||
20 | /// </summary> | ||
21 | public BundleResult Result { get; set; } | ||
22 | } | ||
23 | } | ||
diff --git a/src/tools/burn/ManagedBundleRunner/BundleResult.cs b/src/tools/burn/ManagedBundleRunner/BundleResult.cs new file mode 100644 index 00000000..5df0d699 --- /dev/null +++ b/src/tools/burn/ManagedBundleRunner/BundleResult.cs | |||
@@ -0,0 +1,24 @@ | |||
1 | // Copyright (c) .NET Foundation 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.Tools | ||
4 | { | ||
5 | /// <summary> | ||
6 | /// Result codes. | ||
7 | /// </summary> | ||
8 | public enum BundleResult | ||
9 | { | ||
10 | Error = -1, | ||
11 | None, | ||
12 | Ok, | ||
13 | Cancel, | ||
14 | Abort, | ||
15 | Retry, | ||
16 | Ignore, | ||
17 | Yes, | ||
18 | No, | ||
19 | Close, | ||
20 | Help, | ||
21 | TryAgain, | ||
22 | Continue, | ||
23 | } | ||
24 | } | ||
diff --git a/src/tools/burn/ManagedBundleRunner/BundleRunner.cs b/src/tools/burn/ManagedBundleRunner/BundleRunner.cs new file mode 100644 index 00000000..98d92c66 --- /dev/null +++ b/src/tools/burn/ManagedBundleRunner/BundleRunner.cs | |||
@@ -0,0 +1,212 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Tools | ||
4 | { | ||
5 | using System; | ||
6 | using System.Diagnostics; | ||
7 | using System.IO.Pipes; | ||
8 | using System.Text; | ||
9 | using System.Threading; | ||
10 | |||
11 | /// <summary> | ||
12 | /// Runs a bundle with provided command-line. | ||
13 | /// </summary> | ||
14 | public class BundleRunner | ||
15 | { | ||
16 | /// <summary> | ||
17 | /// Creates a runner for the provided bundle. | ||
18 | /// </summary> | ||
19 | /// <param name="bundle">Path to the bundle to run.</param> | ||
20 | public BundleRunner(string bundle) | ||
21 | { | ||
22 | this.Path = bundle; | ||
23 | } | ||
24 | |||
25 | /// <summary> | ||
26 | /// Fired when the bundle encounters an error. | ||
27 | /// </summary> | ||
28 | public event EventHandler<BundleErrorEventArgs> Error; | ||
29 | |||
30 | /// <summary> | ||
31 | /// Fired when the bundle progress is udpated. | ||
32 | /// </summary> | ||
33 | public event EventHandler<BundleProgressEventArgs> Progress; | ||
34 | |||
35 | /// <summary> | ||
36 | /// Gets the path to the bundle to run. | ||
37 | /// </summary> | ||
38 | public string Path { get; private set; } | ||
39 | |||
40 | /// <summary> | ||
41 | /// Runs the bundle with the provided command-line. | ||
42 | /// </summary> | ||
43 | /// <param name="commandLine">Optional command-line to pass to the bundle.</param> | ||
44 | /// <returns>Exit code from the bundle.</returns> | ||
45 | public int Run(string commandLine = null) | ||
46 | { | ||
47 | WaitHandle[] waits = new WaitHandle[] { new ManualResetEvent(false), new ManualResetEvent(false) }; | ||
48 | int returnCode = 0; | ||
49 | int pid = Process.GetCurrentProcess().Id; | ||
50 | string pipeName = String.Concat("bpe_", pid); | ||
51 | string pipeSecret = Guid.NewGuid().ToString("N"); | ||
52 | |||
53 | using (NamedPipeServerStream pipe = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1)) | ||
54 | { | ||
55 | using (Process bundleProcess = new Process()) | ||
56 | { | ||
57 | bundleProcess.StartInfo.FileName = this.Path; | ||
58 | bundleProcess.StartInfo.Arguments = String.Format("{0} -burn.embedded {1} {2} {3}", commandLine ?? String.Empty, pipeName, pipeSecret, pid); | ||
59 | bundleProcess.StartInfo.UseShellExecute = false; | ||
60 | bundleProcess.StartInfo.CreateNoWindow = true; | ||
61 | bundleProcess.Start(); | ||
62 | |||
63 | Connect(pipe, pipeSecret, pid, bundleProcess.Id); | ||
64 | |||
65 | PumpMessages(pipe); | ||
66 | |||
67 | bundleProcess.WaitForExit(); | ||
68 | returnCode = bundleProcess.ExitCode; | ||
69 | } | ||
70 | } | ||
71 | |||
72 | return returnCode; | ||
73 | } | ||
74 | |||
75 | /// <summary> | ||
76 | /// Called when bundle encounters an error. | ||
77 | /// </summary> | ||
78 | /// <param name="e">Additional arguments for this event.</param> | ||
79 | protected virtual void OnError(BundleErrorEventArgs e) | ||
80 | { | ||
81 | EventHandler<BundleErrorEventArgs> handler = this.Error; | ||
82 | if (handler != null) | ||
83 | { | ||
84 | handler(this, e); | ||
85 | } | ||
86 | } | ||
87 | |||
88 | /// <summary> | ||
89 | /// Called when bundle progress is updated. | ||
90 | /// </summary> | ||
91 | /// <param name="e">Additional arguments for this event.</param> | ||
92 | protected virtual void OnProgress(BundleProgressEventArgs e) | ||
93 | { | ||
94 | EventHandler<BundleProgressEventArgs> handler = this.Progress; | ||
95 | if (handler != null) | ||
96 | { | ||
97 | handler(this, e); | ||
98 | } | ||
99 | } | ||
100 | |||
101 | private void Connect(NamedPipeServerStream pipe, string pipeSecret, int pid, int childPid) | ||
102 | { | ||
103 | pipe.WaitForConnection(); | ||
104 | |||
105 | WriteSecretToPipe(pipe, pipeSecret); | ||
106 | |||
107 | WriteNumberToPipe(pipe, (uint)pid); | ||
108 | |||
109 | uint ack = ReadNumberFromPipe(pipe); | ||
110 | // This is not true when bundle is run under a debugger | ||
111 | //if (ack != childPid) | ||
112 | //{ | ||
113 | // throw new ApplicationException("Incorrect child process."); | ||
114 | //} | ||
115 | } | ||
116 | |||
117 | private void PumpMessages(NamedPipeServerStream pipe) | ||
118 | { | ||
119 | uint messageId; | ||
120 | while (TryReadNumberFromPipe(pipe, out messageId)) | ||
121 | { | ||
122 | uint messageSize = ReadNumberFromPipe(pipe); | ||
123 | |||
124 | BundleResult result = BundleResult.None; | ||
125 | switch (messageId) | ||
126 | { | ||
127 | case 1: //error | ||
128 | result = ProcessErrorMessage(pipe); | ||
129 | break; | ||
130 | |||
131 | case 2: // progress | ||
132 | result = ProcessProgressMessage(pipe); | ||
133 | break; | ||
134 | |||
135 | default: // unknown message, do nothing. | ||
136 | break; | ||
137 | } | ||
138 | |||
139 | CompleteMessage(pipe, result); | ||
140 | } | ||
141 | } | ||
142 | |||
143 | private BundleResult ProcessErrorMessage(NamedPipeServerStream pipe) | ||
144 | { | ||
145 | BundleErrorEventArgs e = new BundleErrorEventArgs(); | ||
146 | e.Code = (int)ReadNumberFromPipe(pipe); | ||
147 | e.Message = ReadStringFromPipe(pipe); | ||
148 | e.UIHint = (int)ReadNumberFromPipe(pipe); | ||
149 | |||
150 | this.OnError(e); | ||
151 | |||
152 | return e.Result; | ||
153 | } | ||
154 | |||
155 | private BundleResult ProcessProgressMessage(NamedPipeServerStream pipe) | ||
156 | { | ||
157 | ReadNumberFromPipe(pipe); // eat the first progress number because it is always zero. | ||
158 | |||
159 | BundleProgressEventArgs e = new BundleProgressEventArgs(); | ||
160 | e.Progress = (int)ReadNumberFromPipe(pipe); | ||
161 | |||
162 | this.OnProgress(e); | ||
163 | |||
164 | return e.Result; | ||
165 | } | ||
166 | |||
167 | private void CompleteMessage(NamedPipeServerStream pipe, BundleResult result) | ||
168 | { | ||
169 | uint complete = 0xF0000002; | ||
170 | WriteNumberToPipe(pipe, complete); | ||
171 | WriteNumberToPipe(pipe, 4); // size of message data | ||
172 | WriteNumberToPipe(pipe, (uint)result); | ||
173 | } | ||
174 | |||
175 | private uint ReadNumberFromPipe(NamedPipeServerStream pipe) | ||
176 | { | ||
177 | byte[] buffer = new byte[4]; | ||
178 | pipe.Read(buffer, 0, buffer.Length); | ||
179 | return BitConverter.ToUInt32(buffer, 0); | ||
180 | } | ||
181 | |||
182 | private string ReadStringFromPipe(NamedPipeServerStream pipe) | ||
183 | { | ||
184 | uint length = ReadNumberFromPipe(pipe); | ||
185 | |||
186 | byte[] buffer = new byte[length * 2]; | ||
187 | pipe.Read(buffer, 0, buffer.Length); | ||
188 | |||
189 | return Encoding.Unicode.GetString(buffer); | ||
190 | } | ||
191 | |||
192 | private bool TryReadNumberFromPipe(NamedPipeServerStream pipe, out uint value) | ||
193 | { | ||
194 | value = ReadNumberFromPipe(pipe); // reading will not block and return zero if pipe is not connected. | ||
195 | return pipe.IsConnected; | ||
196 | } | ||
197 | |||
198 | private void WriteNumberToPipe(NamedPipeServerStream pipe, uint value) | ||
199 | { | ||
200 | byte[] buffer = BitConverter.GetBytes(value); | ||
201 | pipe.Write(buffer, 0, buffer.Length); | ||
202 | } | ||
203 | |||
204 | private void WriteSecretToPipe(NamedPipeServerStream pipe, string secret) | ||
205 | { | ||
206 | byte[] buffer = Encoding.Unicode.GetBytes(secret); | ||
207 | |||
208 | WriteNumberToPipe(pipe, (uint)buffer.Length); | ||
209 | pipe.Write(buffer, 0, buffer.Length); | ||
210 | } | ||
211 | } | ||
212 | } | ||
diff --git a/src/tools/burn/runbundle/AssemblyInfo.cs b/src/tools/burn/runbundle/AssemblyInfo.cs new file mode 100644 index 00000000..3a66d5e3 --- /dev/null +++ b/src/tools/burn/runbundle/AssemblyInfo.cs | |||
@@ -0,0 +1,12 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | using System; | ||
4 | using System.Reflection; | ||
5 | using System.Runtime.CompilerServices; | ||
6 | using System.Runtime.InteropServices; | ||
7 | |||
8 | [assembly: AssemblyTitle("Executable to demonstrate Bundle Runner Sample")] | ||
9 | [assembly: AssemblyDescription("")] | ||
10 | [assembly: AssemblyCulture("")] | ||
11 | [assembly: CLSCompliant(true)] | ||
12 | [assembly: ComVisible(false)] | ||
diff --git a/src/tools/burn/runbundle/Program.cs b/src/tools/burn/runbundle/Program.cs new file mode 100644 index 00000000..fd36c2ca --- /dev/null +++ b/src/tools/burn/runbundle/Program.cs | |||
@@ -0,0 +1,47 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Tools | ||
4 | { | ||
5 | using System; | ||
6 | using System.Linq; | ||
7 | using WixToolset.Tools; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Example executable that installs then immediately uninstalls a bundle showing progress. | ||
11 | /// </summary> | ||
12 | class Program | ||
13 | { | ||
14 | static int Main(string[] args) | ||
15 | { | ||
16 | if (args.Length == 0) | ||
17 | { | ||
18 | Console.WriteLine("Must provide the path to the bundle to install then uninstall."); | ||
19 | return -1; | ||
20 | } | ||
21 | |||
22 | BundleRunner runner = new BundleRunner(args[0]); | ||
23 | runner.Error += Program.OnError; | ||
24 | runner.Progress += Program.OnProgress; | ||
25 | |||
26 | Console.WriteLine("Installing: {0}", runner.Path); | ||
27 | int exitCode = runner.Run(String.Join(" ", args.Skip(1).ToArray())); | ||
28 | if (0 == exitCode) | ||
29 | { | ||
30 | Console.WriteLine("\r\nUninstalling: {0}", runner.Path); | ||
31 | exitCode = runner.Run("-uninstall"); | ||
32 | } | ||
33 | |||
34 | return exitCode; | ||
35 | } | ||
36 | |||
37 | static void OnError(object sender, BundleErrorEventArgs e) | ||
38 | { | ||
39 | Console.WriteLine("error: {0}, uiHint: {1}, message: {2}", e.Code, e.UIHint, e.Message); | ||
40 | } | ||
41 | |||
42 | static void OnProgress(object sender, BundleProgressEventArgs e) | ||
43 | { | ||
44 | Console.WriteLine("progresss: {0}%", e.Progress); | ||
45 | } | ||
46 | } | ||
47 | } | ||
diff --git a/src/tools/thmviewer/Resources/LoremIpsum.rtf b/src/tools/thmviewer/Resources/LoremIpsum.rtf new file mode 100644 index 00000000..1ab0e65b --- /dev/null +++ b/src/tools/thmviewer/Resources/LoremIpsum.rtf | |||
Binary files differ | |||
diff --git a/src/tools/thmviewer/Resources/thm.xml b/src/tools/thmviewer/Resources/thm.xml new file mode 100644 index 00000000..6394f0f1 --- /dev/null +++ b/src/tools/thmviewer/Resources/thm.xml | |||
@@ -0,0 +1,11 @@ | |||
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 | <Theme> | ||
6 | <Font Id="0" Height="-48" Weight="700" Foreground="$windowtext">Consolas</Font> | ||
7 | <Font Id="1" Height="-12" Weight="700" Foreground="$windowtext" Background="$window">Consolas</Font> | ||
8 | <Window Width="220" Height="200" FontId="0" Caption="Theme Viewer"> | ||
9 | <TreeView Name="Tree" X="0" Y="0" Width="0" Height="0" FontId="1" Visible="yes" FullRowSelect="yes" HasButtons="yes" AlwaysShowSelect="yes" HasLines="yes" LinesAtRoot="yes"/> | ||
10 | </Window> | ||
11 | </Theme> | ||
diff --git a/src/tools/thmviewer/display.cpp b/src/tools/thmviewer/display.cpp new file mode 100644 index 00000000..444c6cfb --- /dev/null +++ b/src/tools/thmviewer/display.cpp | |||
@@ -0,0 +1,339 @@ | |||
1 | // Copyright (c) .NET Foundation 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 | static const LPCWSTR THMVWR_WINDOW_CLASS_DISPLAY = L"ThmViewerDisplay"; | ||
6 | |||
7 | struct DISPLAY_THREAD_CONTEXT | ||
8 | { | ||
9 | HWND hWnd; | ||
10 | HINSTANCE hInstance; | ||
11 | |||
12 | HANDLE hInit; | ||
13 | }; | ||
14 | |||
15 | static DWORD WINAPI DisplayThreadProc( | ||
16 | __in LPVOID pvContext | ||
17 | ); | ||
18 | static LRESULT CALLBACK DisplayWndProc( | ||
19 | __in HWND hWnd, | ||
20 | __in UINT uMsg, | ||
21 | __in WPARAM wParam, | ||
22 | __in LPARAM lParam | ||
23 | ); | ||
24 | static BOOL DisplayOnThmLoadedControl( | ||
25 | __in THEME* pTheme, | ||
26 | __in const THEME_LOADEDCONTROL_ARGS* args, | ||
27 | __in THEME_LOADEDCONTROL_RESULTS* results | ||
28 | ); | ||
29 | |||
30 | |||
31 | extern "C" HRESULT DisplayStart( | ||
32 | __in HINSTANCE hInstance, | ||
33 | __in HWND hWnd, | ||
34 | __out HANDLE *phThread, | ||
35 | __out DWORD* pdwThreadId | ||
36 | ) | ||
37 | { | ||
38 | HRESULT hr = S_OK; | ||
39 | HANDLE rgHandles[2] = { }; | ||
40 | DISPLAY_THREAD_CONTEXT context = { }; | ||
41 | |||
42 | rgHandles[0] = ::CreateEventW(NULL, TRUE, FALSE, NULL); | ||
43 | ExitOnNullWithLastError(rgHandles[0], hr, "Failed to create load init event."); | ||
44 | |||
45 | context.hWnd = hWnd; | ||
46 | context.hInstance = hInstance; | ||
47 | context.hInit = rgHandles[0]; | ||
48 | |||
49 | rgHandles[1] = ::CreateThread(NULL, 0, DisplayThreadProc, reinterpret_cast<LPVOID>(&context), 0, pdwThreadId); | ||
50 | ExitOnNullWithLastError(rgHandles[1], hr, "Failed to create display thread."); | ||
51 | |||
52 | ::WaitForMultipleObjects(countof(rgHandles), rgHandles, FALSE, INFINITE); | ||
53 | |||
54 | *phThread = rgHandles[1]; | ||
55 | rgHandles[1] = NULL; | ||
56 | |||
57 | LExit: | ||
58 | ReleaseHandle(rgHandles[1]); | ||
59 | ReleaseHandle(rgHandles[0]); | ||
60 | return hr; | ||
61 | } | ||
62 | |||
63 | static DWORD WINAPI DisplayThreadProc( | ||
64 | __in LPVOID pvContext | ||
65 | ) | ||
66 | { | ||
67 | HRESULT hr = S_OK; | ||
68 | |||
69 | DISPLAY_THREAD_CONTEXT* pContext = static_cast<DISPLAY_THREAD_CONTEXT*>(pvContext); | ||
70 | HINSTANCE hInstance = pContext->hInstance; | ||
71 | HWND hwndParent = pContext->hWnd; | ||
72 | |||
73 | // We can signal the initialization event as soon as we have copied the context | ||
74 | // values into local variables. | ||
75 | ::SetEvent(pContext->hInit); | ||
76 | |||
77 | BOOL fComInitialized = FALSE; | ||
78 | |||
79 | HANDLE_THEME* pCurrentHandle = NULL; | ||
80 | ATOM atomWc = 0; | ||
81 | WNDCLASSW wc = { }; | ||
82 | HWND hWnd = NULL; | ||
83 | RECT rc = { }; | ||
84 | int x = CW_USEDEFAULT; | ||
85 | int y = CW_USEDEFAULT; | ||
86 | |||
87 | BOOL fRedoMsg = FALSE; | ||
88 | BOOL fRet = FALSE; | ||
89 | MSG msg = { }; | ||
90 | |||
91 | BOOL fCreateIfNecessary = FALSE; | ||
92 | |||
93 | hr = ::CoInitialize(NULL); | ||
94 | ExitOnFailure(hr, "Failed to initialize COM on display thread."); | ||
95 | fComInitialized = TRUE; | ||
96 | |||
97 | // As long as the parent window is alive and kicking, keep this thread going (with or without a theme to display ). | ||
98 | while (::IsWindow(hwndParent)) | ||
99 | { | ||
100 | if (pCurrentHandle && fCreateIfNecessary) | ||
101 | { | ||
102 | THEME* pTheme = pCurrentHandle->pTheme; | ||
103 | |||
104 | if (CW_USEDEFAULT == x && CW_USEDEFAULT == y && ::GetWindowRect(hwndParent, &rc)) | ||
105 | { | ||
106 | x = rc.left; | ||
107 | y = rc.bottom + 20; | ||
108 | } | ||
109 | |||
110 | hr = ThemeCreateParentWindow(pTheme, 0, wc.lpszClassName, pTheme->sczCaption, pTheme->dwStyle, x, y, hwndParent, hInstance, pCurrentHandle, THEME_WINDOW_INITIAL_POSITION_DEFAULT, &hWnd); | ||
111 | ExitOnFailure(hr, "Failed to create display window."); | ||
112 | |||
113 | fCreateIfNecessary = FALSE; | ||
114 | } | ||
115 | |||
116 | // message pump | ||
117 | while (fRedoMsg || 0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0))) | ||
118 | { | ||
119 | if (fRedoMsg) | ||
120 | { | ||
121 | fRedoMsg = FALSE; | ||
122 | } | ||
123 | |||
124 | if (-1 == fRet) | ||
125 | { | ||
126 | hr = E_UNEXPECTED; | ||
127 | ExitOnFailure(hr, "Unexpected return value from display message pump."); | ||
128 | } | ||
129 | else if (NULL == msg.hwnd) // Thread message. | ||
130 | { | ||
131 | if (WM_THMVWR_NEW_THEME == msg.message) | ||
132 | { | ||
133 | // If there is already a handle, release it. | ||
134 | if (pCurrentHandle) | ||
135 | { | ||
136 | DecrementHandleTheme(pCurrentHandle); | ||
137 | pCurrentHandle = NULL; | ||
138 | } | ||
139 | |||
140 | // If the window was created, remember its window location before we destroy | ||
141 | // it so so we can open the new window in the same place. | ||
142 | if (::IsWindow(hWnd)) | ||
143 | { | ||
144 | ::GetWindowRect(hWnd, &rc); | ||
145 | x = rc.left; | ||
146 | y = rc.top; | ||
147 | |||
148 | ::DestroyWindow(hWnd); | ||
149 | } | ||
150 | |||
151 | // If the display window class was registered, unregister it so we can | ||
152 | // reuse the same window class name for the new theme. | ||
153 | if (atomWc) | ||
154 | { | ||
155 | if (!::UnregisterClassW(reinterpret_cast<LPCWSTR>(atomWc), hInstance)) | ||
156 | { | ||
157 | DWORD er = ::GetLastError(); | ||
158 | er = er; | ||
159 | } | ||
160 | |||
161 | atomWc = 0; | ||
162 | } | ||
163 | |||
164 | // If we were provided a new theme handle, create a new window class to | ||
165 | // support it. | ||
166 | pCurrentHandle = reinterpret_cast<HANDLE_THEME*>(msg.lParam); | ||
167 | if (pCurrentHandle) | ||
168 | { | ||
169 | ThemeInitializeWindowClass(pCurrentHandle->pTheme, &wc, DisplayWndProc, hInstance, THMVWR_WINDOW_CLASS_DISPLAY); | ||
170 | |||
171 | atomWc = ::RegisterClassW(&wc); | ||
172 | if (!atomWc) | ||
173 | { | ||
174 | ExitWithLastError(hr, "Failed to register display window class."); | ||
175 | } | ||
176 | } | ||
177 | } | ||
178 | else if (WM_THMVWR_SHOWPAGE == msg.message) | ||
179 | { | ||
180 | if (pCurrentHandle && ::IsWindow(hWnd) && pCurrentHandle->pTheme->hwndParent == hWnd) | ||
181 | { | ||
182 | DWORD dwPageId = static_cast<DWORD>(msg.lParam); | ||
183 | int nCmdShow = static_cast<int>(msg.wParam); | ||
184 | |||
185 | // First show/hide the controls not associated with a page. | ||
186 | for (DWORD i = 0; i < pCurrentHandle->pTheme->cControls; ++i) | ||
187 | { | ||
188 | THEME_CONTROL* pControl = pCurrentHandle->pTheme->rgControls + i; | ||
189 | if (!pControl->wPageId) | ||
190 | { | ||
191 | ThemeShowControl(pControl, nCmdShow); | ||
192 | } | ||
193 | } | ||
194 | |||
195 | // If a page id was provided also, show/hide those controls | ||
196 | if (dwPageId) | ||
197 | { | ||
198 | // Ignore error since we aren't using variables and it can only fail when using variables. | ||
199 | ThemeShowPage(pCurrentHandle->pTheme, dwPageId, nCmdShow); | ||
200 | } | ||
201 | } | ||
202 | else // display window isn't visible or it doesn't match the current handle. | ||
203 | { | ||
204 | // Keep the current message around to try again after we break out of this loop | ||
205 | // and create the window. | ||
206 | fRedoMsg = TRUE; | ||
207 | fCreateIfNecessary = TRUE; | ||
208 | break; | ||
209 | } | ||
210 | } | ||
211 | } | ||
212 | else if (!ThemeHandleKeyboardMessage(pCurrentHandle->pTheme, hwndParent, &msg)) // Window message. | ||
213 | { | ||
214 | ::TranslateMessage(&msg); | ||
215 | ::DispatchMessageW(&msg); | ||
216 | } | ||
217 | } | ||
218 | } | ||
219 | |||
220 | LExit: | ||
221 | if (::IsWindow(hWnd)) | ||
222 | { | ||
223 | ::DestroyWindow(hWnd); | ||
224 | } | ||
225 | |||
226 | if (atomWc) | ||
227 | { | ||
228 | if (!::UnregisterClassW(THMVWR_WINDOW_CLASS_DISPLAY, hInstance)) | ||
229 | { | ||
230 | DWORD er = ::GetLastError(); | ||
231 | er = er; | ||
232 | } | ||
233 | } | ||
234 | |||
235 | DecrementHandleTheme(pCurrentHandle); | ||
236 | |||
237 | if (fComInitialized) | ||
238 | { | ||
239 | ::CoUninitialize(); | ||
240 | } | ||
241 | |||
242 | return hr; | ||
243 | } | ||
244 | |||
245 | static LRESULT CALLBACK DisplayWndProc( | ||
246 | __in HWND hWnd, | ||
247 | __in UINT uMsg, | ||
248 | __in WPARAM wParam, | ||
249 | __in LPARAM lParam | ||
250 | ) | ||
251 | { | ||
252 | static DWORD dwProgress = 0; | ||
253 | HANDLE_THEME* pHandleTheme = reinterpret_cast<HANDLE_THEME*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA)); | ||
254 | |||
255 | switch (uMsg) | ||
256 | { | ||
257 | case WM_NCCREATE: | ||
258 | { | ||
259 | LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam); | ||
260 | pHandleTheme = reinterpret_cast<HANDLE_THEME*>(lpcs->lpCreateParams); | ||
261 | IncrementHandleTheme(pHandleTheme); | ||
262 | ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pHandleTheme)); | ||
263 | } | ||
264 | break; | ||
265 | |||
266 | case WM_TIMER: | ||
267 | if (!lParam && SUCCEEDED(ThemeSetProgressControl(reinterpret_cast<THEME_CONTROL*>(wParam), dwProgress))) | ||
268 | { | ||
269 | dwProgress += rand() % 10 + 1; | ||
270 | if (dwProgress > 100) | ||
271 | { | ||
272 | dwProgress = 0; | ||
273 | } | ||
274 | |||
275 | return 0; | ||
276 | } | ||
277 | break; | ||
278 | |||
279 | case WM_COMMAND: | ||
280 | { | ||
281 | WCHAR wzText[1024]; | ||
282 | ::StringCchPrintfW(wzText, countof(wzText), L"Command %u\r\n", LOWORD(wParam)); | ||
283 | OutputDebugStringW(wzText); | ||
284 | //::MessageBoxW(hWnd, wzText, L"Command fired", MB_OK); | ||
285 | } | ||
286 | break; | ||
287 | |||
288 | case WM_SYSCOMMAND: | ||
289 | { | ||
290 | WCHAR wzText[1024]; | ||
291 | ::StringCchPrintfW(wzText, countof(wzText), L"SysCommand %u\r\n", LOWORD(wParam)); | ||
292 | OutputDebugStringW(wzText); | ||
293 | //::MessageBoxW(hWnd, wzText, L"Command fired", MB_OK); | ||
294 | } | ||
295 | break; | ||
296 | |||
297 | case WM_NCDESTROY: | ||
298 | DecrementHandleTheme(pHandleTheme); | ||
299 | ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0); | ||
300 | ::PostQuitMessage(0); | ||
301 | break; | ||
302 | |||
303 | case WM_THMUTIL_LOADED_CONTROL: | ||
304 | if (pHandleTheme) | ||
305 | { | ||
306 | return DisplayOnThmLoadedControl(pHandleTheme->pTheme, reinterpret_cast<THEME_LOADEDCONTROL_ARGS*>(wParam), reinterpret_cast<THEME_LOADEDCONTROL_RESULTS*>(lParam)); | ||
307 | } | ||
308 | } | ||
309 | |||
310 | return ThemeDefWindowProc(pHandleTheme ? pHandleTheme->pTheme : NULL, hWnd, uMsg, wParam, lParam); | ||
311 | } | ||
312 | |||
313 | static BOOL DisplayOnThmLoadedControl( | ||
314 | __in THEME* pTheme, | ||
315 | __in const THEME_LOADEDCONTROL_ARGS* args, | ||
316 | __in THEME_LOADEDCONTROL_RESULTS* results | ||
317 | ) | ||
318 | { | ||
319 | HRESULT hr = S_OK; | ||
320 | const THEME_CONTROL* pControl = args->pThemeControl; | ||
321 | |||
322 | // Pre-populate some control types with data. | ||
323 | if (THEME_CONTROL_TYPE_RICHEDIT == pControl->type) | ||
324 | { | ||
325 | hr = WnduLoadRichEditFromResource(pControl->hWnd, MAKEINTRESOURCEA(THMVWR_RES_RICHEDIT_FILE), ::GetModuleHandleW(NULL)); | ||
326 | ExitOnFailure(hr, "Failed to load richedit text."); | ||
327 | } | ||
328 | else if (THEME_CONTROL_TYPE_PROGRESSBAR == pControl->type) | ||
329 | { | ||
330 | UINT_PTR timerId = reinterpret_cast<UINT_PTR>(pControl); | ||
331 | UINT_PTR id = ::SetTimer(pTheme->hwndParent, timerId, 500, NULL); | ||
332 | id = id; // prevents warning in "ship" build. | ||
333 | Assert(id == timerId); | ||
334 | } | ||
335 | |||
336 | LExit: | ||
337 | results->hr = hr; | ||
338 | return TRUE; | ||
339 | } | ||
diff --git a/src/tools/thmviewer/load.cpp b/src/tools/thmviewer/load.cpp new file mode 100644 index 00000000..0267402a --- /dev/null +++ b/src/tools/thmviewer/load.cpp | |||
@@ -0,0 +1,221 @@ | |||
1 | // Copyright (c) .NET Foundation 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 | struct LOAD_THREAD_CONTEXT | ||
6 | { | ||
7 | HWND hWnd; | ||
8 | LPCWSTR wzThemePath; | ||
9 | LPCWSTR wzWxlPath; | ||
10 | |||
11 | HANDLE hInit; | ||
12 | }; | ||
13 | |||
14 | static DWORD WINAPI LoadThreadProc( | ||
15 | __in LPVOID pvContext | ||
16 | ); | ||
17 | |||
18 | |||
19 | extern "C" HRESULT LoadStart( | ||
20 | __in_z LPCWSTR wzThemePath, | ||
21 | __in_z_opt LPCWSTR wzWxlPath, | ||
22 | __in HWND hWnd, | ||
23 | __out HANDLE* phThread | ||
24 | ) | ||
25 | { | ||
26 | HRESULT hr = S_OK; | ||
27 | HANDLE rgHandles[2] = { }; | ||
28 | LOAD_THREAD_CONTEXT context = { }; | ||
29 | |||
30 | rgHandles[0] = ::CreateEventW(NULL, TRUE, FALSE, NULL); | ||
31 | ExitOnNullWithLastError(rgHandles[0], hr, "Failed to create load init event."); | ||
32 | |||
33 | context.hWnd = hWnd; | ||
34 | context.wzThemePath = wzThemePath; | ||
35 | context.wzWxlPath = wzWxlPath; | ||
36 | context.hInit = rgHandles[0]; | ||
37 | |||
38 | rgHandles[1] = ::CreateThread(NULL, 0, LoadThreadProc, &context, 0, NULL); | ||
39 | ExitOnNullWithLastError(rgHandles[1], hr, "Failed to create load thread."); | ||
40 | |||
41 | ::WaitForMultipleObjects(countof(rgHandles), rgHandles, FALSE, INFINITE); | ||
42 | |||
43 | *phThread = rgHandles[1]; | ||
44 | rgHandles[1] = NULL; | ||
45 | |||
46 | LExit: | ||
47 | ReleaseHandle(rgHandles[1]); | ||
48 | ReleaseHandle(rgHandles[0]); | ||
49 | return hr; | ||
50 | } | ||
51 | |||
52 | |||
53 | static DWORD WINAPI LoadThreadProc( | ||
54 | __in LPVOID pvContext | ||
55 | ) | ||
56 | { | ||
57 | HRESULT hr = S_OK; | ||
58 | WIX_LOCALIZATION* pWixLoc = NULL; | ||
59 | LPWSTR sczThemePath = NULL; | ||
60 | LPWSTR sczWxlPath = NULL; | ||
61 | BOOL fComInitialized = FALSE; | ||
62 | HANDLE hDirectory = INVALID_HANDLE_VALUE; | ||
63 | LPWSTR sczDirectory = NULL; | ||
64 | LPWSTR wzFileName = NULL; | ||
65 | |||
66 | THEME* pTheme = NULL; | ||
67 | HANDLE_THEME* pHandle = NULL; | ||
68 | |||
69 | LOAD_THREAD_CONTEXT* pContext = static_cast<LOAD_THREAD_CONTEXT*>(pvContext); | ||
70 | HWND hWnd = pContext->hWnd; | ||
71 | |||
72 | hr = StrAllocString(&sczThemePath, pContext->wzThemePath, 0); | ||
73 | ExitOnFailure(hr, "Failed to copy path to initial theme file."); | ||
74 | |||
75 | if (pContext->wzWxlPath) | ||
76 | { | ||
77 | hr = StrAllocString(&sczWxlPath, pContext->wzWxlPath, 0); | ||
78 | ExitOnFailure(hr, "Failed to copy .wxl path to initial file."); | ||
79 | } | ||
80 | |||
81 | // We can signal the initialization event as soon as we have copied the context | ||
82 | // values into local variables. | ||
83 | ::SetEvent(pContext->hInit); | ||
84 | |||
85 | hr = ::CoInitialize(NULL); | ||
86 | ExitOnFailure(hr, "Failed to initialize COM on load thread."); | ||
87 | fComInitialized = TRUE; | ||
88 | |||
89 | // Open a handle to the directory so we can put a notification on it. | ||
90 | hr = PathGetDirectory(sczThemePath, &sczDirectory); | ||
91 | ExitOnFailure(hr, "Failed to get path directory."); | ||
92 | |||
93 | wzFileName = PathFile(sczThemePath); | ||
94 | |||
95 | hDirectory = ::CreateFileW(sczDirectory, FILE_LIST_DIRECTORY, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); | ||
96 | if (INVALID_HANDLE_VALUE == hDirectory) | ||
97 | { | ||
98 | ExitWithLastError(hr, "Failed to open directory: %ls", sczDirectory); | ||
99 | } | ||
100 | |||
101 | BOOL fUpdated = FALSE; | ||
102 | do | ||
103 | { | ||
104 | // Get the last modified time on the file we're loading for verification that the | ||
105 | // file actually gets changed down below. | ||
106 | FILETIME ftModified = { }; | ||
107 | FileGetTime(sczThemePath, NULL, NULL, &ftModified); | ||
108 | |||
109 | ::SendMessageW(hWnd, WM_THMVWR_THEME_LOAD_BEGIN, 0, 0); | ||
110 | |||
111 | // Try to load the theme file. | ||
112 | hr = ThemeLoadFromFile(sczThemePath, &pTheme); | ||
113 | if (FAILED(hr)) | ||
114 | { | ||
115 | ::SendMessageW(hWnd, WM_THMVWR_THEME_LOAD_ERROR, 0, hr); | ||
116 | } | ||
117 | else | ||
118 | { | ||
119 | if (sczWxlPath) | ||
120 | { | ||
121 | hr = LocLoadFromFile(sczWxlPath, &pWixLoc); | ||
122 | ExitOnFailure(hr, "Failed to load loc file from path: %ls", sczWxlPath); | ||
123 | |||
124 | hr = ThemeLocalize(pTheme, pWixLoc); | ||
125 | ExitOnFailure(hr, "Failed to localize theme: %ls", sczWxlPath); | ||
126 | } | ||
127 | |||
128 | hr = AllocHandleTheme(pTheme, &pHandle); | ||
129 | ExitOnFailure(hr, "Failed to allocate handle to theme"); | ||
130 | |||
131 | ::SendMessageW(hWnd, WM_THMVWR_NEW_THEME, 0, reinterpret_cast<LPARAM>(pHandle)); | ||
132 | pHandle = NULL; | ||
133 | } | ||
134 | |||
135 | fUpdated = FALSE; | ||
136 | do | ||
137 | { | ||
138 | DWORD rgbNotifications[1024]; | ||
139 | DWORD cbRead = 0; | ||
140 | if (!::ReadDirectoryChangesW(hDirectory, rgbNotifications, sizeof(rgbNotifications), FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, &cbRead, NULL, NULL)) | ||
141 | { | ||
142 | ExitWithLastError(hr, "Failed while watching directory: %ls", sczDirectory); | ||
143 | } | ||
144 | |||
145 | // Wait for half a second to let all the file handles get closed to minimize access | ||
146 | // denied errors. | ||
147 | ::Sleep(500); | ||
148 | |||
149 | FILE_NOTIFY_INFORMATION* pNotification = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(rgbNotifications); | ||
150 | while (pNotification) | ||
151 | { | ||
152 | // If our file was updated, check to see if the modified time really changed. The notifications | ||
153 | // are often trigger happy thinking the file changed two or three times in a row. Maybe it's AV | ||
154 | // software creating the problems but actually checking the modified date works well. | ||
155 | if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pNotification->FileName, pNotification->FileNameLength / sizeof(WCHAR), wzFileName, -1)) | ||
156 | { | ||
157 | FILETIME ft = { }; | ||
158 | FileGetTime(sczThemePath, NULL, NULL, &ft); | ||
159 | |||
160 | fUpdated = (ftModified.dwHighDateTime < ft.dwHighDateTime) || (ftModified.dwHighDateTime == ft.dwHighDateTime && ftModified.dwLowDateTime < ft.dwLowDateTime); | ||
161 | break; | ||
162 | } | ||
163 | |||
164 | pNotification = pNotification->NextEntryOffset ? reinterpret_cast<FILE_NOTIFY_INFORMATION*>(reinterpret_cast<BYTE*>(pNotification) + pNotification->NextEntryOffset) : NULL; | ||
165 | } | ||
166 | } while (!fUpdated); | ||
167 | } while(fUpdated); | ||
168 | |||
169 | LExit: | ||
170 | if (fComInitialized) | ||
171 | { | ||
172 | ::CoUninitialize(); | ||
173 | } | ||
174 | |||
175 | LocFree(pWixLoc); | ||
176 | ReleaseFileHandle(hDirectory); | ||
177 | ReleaseStr(sczDirectory); | ||
178 | ReleaseStr(sczThemePath); | ||
179 | ReleaseStr(sczWxlPath); | ||
180 | return hr; | ||
181 | } | ||
182 | |||
183 | extern "C" HRESULT AllocHandleTheme( | ||
184 | __in THEME* pTheme, | ||
185 | __out HANDLE_THEME** ppHandle | ||
186 | ) | ||
187 | { | ||
188 | HRESULT hr = S_OK; | ||
189 | HANDLE_THEME* pHandle = NULL; | ||
190 | |||
191 | pHandle = static_cast<HANDLE_THEME*>(MemAlloc(sizeof(HANDLE_THEME), TRUE)); | ||
192 | ExitOnNull(pHandle, hr, E_OUTOFMEMORY, "Failed to allocate theme handle."); | ||
193 | |||
194 | pHandle->cReferences = 1; | ||
195 | pHandle->pTheme = pTheme; | ||
196 | |||
197 | *ppHandle = pHandle; | ||
198 | pHandle = NULL; | ||
199 | |||
200 | LExit: | ||
201 | ReleaseMem(pHandle); | ||
202 | return hr; | ||
203 | } | ||
204 | |||
205 | extern "C" void IncrementHandleTheme( | ||
206 | __in HANDLE_THEME* pHandle | ||
207 | ) | ||
208 | { | ||
209 | ::InterlockedIncrement(reinterpret_cast<LONG*>(&pHandle->cReferences)); | ||
210 | } | ||
211 | |||
212 | extern "C" void DecrementHandleTheme( | ||
213 | __in HANDLE_THEME* pHandle | ||
214 | ) | ||
215 | { | ||
216 | if (pHandle && 0 == ::InterlockedDecrement(reinterpret_cast<LONG*>(&pHandle->cReferences))) | ||
217 | { | ||
218 | ThemeFree(pHandle->pTheme); | ||
219 | MemFree(pHandle); | ||
220 | } | ||
221 | } | ||
diff --git a/src/tools/thmviewer/precomp.cpp b/src/tools/thmviewer/precomp.cpp new file mode 100644 index 00000000..37664a1c --- /dev/null +++ b/src/tools/thmviewer/precomp.cpp | |||
@@ -0,0 +1,3 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | #include "precomp.h" | ||
diff --git a/src/tools/thmviewer/precomp.h b/src/tools/thmviewer/precomp.h new file mode 100644 index 00000000..762a0623 --- /dev/null +++ b/src/tools/thmviewer/precomp.h | |||
@@ -0,0 +1,72 @@ | |||
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 <objbase.h> | ||
8 | #include <shlobj.h> | ||
9 | #include <shlwapi.h> | ||
10 | #include <stdlib.h> | ||
11 | #include <strsafe.h> | ||
12 | |||
13 | #pragma warning(push) | ||
14 | #pragma warning(disable:4458) | ||
15 | #include <gdiplus.h> | ||
16 | #pragma warning(pop) | ||
17 | |||
18 | #include "dutil.h" | ||
19 | #include "apputil.h" | ||
20 | #include "memutil.h" | ||
21 | #include "dictutil.h" | ||
22 | #include "dirutil.h" | ||
23 | #include "fileutil.h" | ||
24 | #include "locutil.h" | ||
25 | #include "logutil.h" | ||
26 | #include "pathutil.h" | ||
27 | #include "resrutil.h" | ||
28 | #include "shelutil.h" | ||
29 | #include "strutil.h" | ||
30 | #include "thmutil.h" | ||
31 | #include "wndutil.h" | ||
32 | |||
33 | #include "resource.h" | ||
34 | |||
35 | struct HANDLE_THEME | ||
36 | { | ||
37 | DWORD cReferences; | ||
38 | THEME* pTheme; | ||
39 | }; | ||
40 | |||
41 | enum WM_THMVWR | ||
42 | { | ||
43 | WM_THMVWR_SHOWPAGE = WM_APP, | ||
44 | WM_THMVWR_PARSE_FILE, | ||
45 | WM_THMVWR_NEW_THEME, | ||
46 | WM_THMVWR_THEME_LOAD_ERROR, | ||
47 | WM_THMVWR_THEME_LOAD_BEGIN, | ||
48 | }; | ||
49 | |||
50 | extern "C" HRESULT DisplayStart( | ||
51 | __in HINSTANCE hInstance, | ||
52 | __in HWND hWnd, | ||
53 | __out HANDLE *phThread, | ||
54 | __out DWORD* pdwThreadId | ||
55 | ); | ||
56 | extern "C" HRESULT LoadStart( | ||
57 | __in_z LPCWSTR wzThemePath, | ||
58 | __in_z LPCWSTR wzWxlPath, | ||
59 | __in HWND hWnd, | ||
60 | __out HANDLE* phThread | ||
61 | ); | ||
62 | |||
63 | extern "C" HRESULT AllocHandleTheme( | ||
64 | __in THEME* pTheme, | ||
65 | __out HANDLE_THEME** ppHandle | ||
66 | ); | ||
67 | extern "C" void IncrementHandleTheme( | ||
68 | __in HANDLE_THEME* pHandle | ||
69 | ); | ||
70 | extern "C" void DecrementHandleTheme( | ||
71 | __in HANDLE_THEME* pHandle | ||
72 | ); | ||
diff --git a/src/tools/thmviewer/resource.h b/src/tools/thmviewer/resource.h new file mode 100644 index 00000000..4acc32cc --- /dev/null +++ b/src/tools/thmviewer/resource.h | |||
@@ -0,0 +1,16 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | #define IDC_STATIC -1 | ||
4 | #define THMVWR_RES_THEME_FILE 1 | ||
5 | #define THMVWR_RES_RICHEDIT_FILE 2 | ||
6 | |||
7 | // Next default values for new objects | ||
8 | // | ||
9 | #ifdef APSTUDIO_INVOKED | ||
10 | #ifndef APSTUDIO_READONLY_SYMBOLS | ||
11 | #define _APS_NEXT_RESOURCE_VALUE 102 | ||
12 | #define _APS_NEXT_COMMAND_VALUE 40001 | ||
13 | #define _APS_NEXT_CONTROL_VALUE 1003 | ||
14 | #define _APS_NEXT_SYMED_VALUE 101 | ||
15 | #endif | ||
16 | #endif | ||
diff --git a/src/tools/thmviewer/thmviewer.cpp b/src/tools/thmviewer/thmviewer.cpp new file mode 100644 index 00000000..38f3c4dc --- /dev/null +++ b/src/tools/thmviewer/thmviewer.cpp | |||
@@ -0,0 +1,564 @@ | |||
1 | // Copyright (c) .NET Foundation 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 | static const LPCWSTR THMVWR_WINDOW_CLASS_MAIN = L"ThmViewerMain"; | ||
6 | |||
7 | static THEME* vpTheme = NULL; | ||
8 | static DWORD vdwDisplayThreadId = 0; | ||
9 | static LPWSTR vsczThemeLoadErrors = NULL; | ||
10 | |||
11 | enum THMVWR_CONTROL | ||
12 | { | ||
13 | // Non-paged controls | ||
14 | THMVWR_CONTROL_TREE = THEME_FIRST_ASSIGN_CONTROL_ID, | ||
15 | }; | ||
16 | |||
17 | // Internal functions | ||
18 | |||
19 | static HRESULT ProcessCommandLine( | ||
20 | __in_z_opt LPCWSTR wzCommandLine, | ||
21 | __out_z LPWSTR* psczThemeFile, | ||
22 | __out_z LPWSTR* psczWxlFile | ||
23 | ); | ||
24 | static HRESULT CreateTheme( | ||
25 | __in HINSTANCE hInstance, | ||
26 | __out THEME** ppTheme | ||
27 | ); | ||
28 | static HRESULT CreateMainWindowClass( | ||
29 | __in HINSTANCE hInstance, | ||
30 | __in THEME* pTheme, | ||
31 | __out ATOM* pAtom | ||
32 | ); | ||
33 | static LRESULT CALLBACK MainWndProc( | ||
34 | __in HWND hWnd, | ||
35 | __in UINT uMsg, | ||
36 | __in WPARAM wParam, | ||
37 | __in LPARAM lParam | ||
38 | ); | ||
39 | static void OnThemeLoadBegin( | ||
40 | __in_z_opt LPWSTR sczThemeLoadErrors | ||
41 | ); | ||
42 | static void OnThemeLoadError( | ||
43 | __in THEME* pTheme, | ||
44 | __in HRESULT hrFailure | ||
45 | ); | ||
46 | static void OnNewTheme( | ||
47 | __in THEME* pTheme, | ||
48 | __in HWND hWnd, | ||
49 | __in HANDLE_THEME* pHandle | ||
50 | ); | ||
51 | static BOOL OnThemeLoadingControl( | ||
52 | __in const THEME_LOADINGCONTROL_ARGS* pArgs, | ||
53 | __in THEME_LOADINGCONTROL_RESULTS* pResults | ||
54 | ); | ||
55 | static BOOL OnThemeControlWmNotify( | ||
56 | __in const THEME_CONTROLWMNOTIFY_ARGS* pArgs, | ||
57 | __in THEME_CONTROLWMNOTIFY_RESULTS* pResults | ||
58 | ); | ||
59 | static void CALLBACK ThmviewerTraceError( | ||
60 | __in_z LPCSTR szFile, | ||
61 | __in int iLine, | ||
62 | __in REPORT_LEVEL rl, | ||
63 | __in UINT source, | ||
64 | __in HRESULT hrError, | ||
65 | __in_z __format_string LPCSTR szFormat, | ||
66 | __in va_list args | ||
67 | ); | ||
68 | |||
69 | |||
70 | int WINAPI wWinMain( | ||
71 | __in HINSTANCE hInstance, | ||
72 | __in_opt HINSTANCE /* hPrevInstance */, | ||
73 | __in_z LPWSTR lpCmdLine, | ||
74 | __in int /*nCmdShow*/ | ||
75 | ) | ||
76 | { | ||
77 | ::HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0); | ||
78 | |||
79 | HRESULT hr = S_OK; | ||
80 | BOOL fComInitialized = FALSE; | ||
81 | LPWSTR sczThemeFile = NULL; | ||
82 | LPWSTR sczWxlFile = NULL; | ||
83 | ATOM atom = 0; | ||
84 | HWND hWnd = NULL; | ||
85 | |||
86 | HANDLE hDisplayThread = NULL; | ||
87 | HANDLE hLoadThread = NULL; | ||
88 | |||
89 | BOOL fRet = FALSE; | ||
90 | MSG msg = { }; | ||
91 | |||
92 | hr = ::CoInitialize(NULL); | ||
93 | ExitOnFailure(hr, "Failed to initialize COM."); | ||
94 | fComInitialized = TRUE; | ||
95 | |||
96 | DutilInitialize(&ThmviewerTraceError); | ||
97 | |||
98 | hr = ProcessCommandLine(lpCmdLine, &sczThemeFile, &sczWxlFile); | ||
99 | ExitOnFailure(hr, "Failed to process command line."); | ||
100 | |||
101 | hr = CreateTheme(hInstance, &vpTheme); | ||
102 | ExitOnFailure(hr, "Failed to create theme."); | ||
103 | |||
104 | hr = CreateMainWindowClass(hInstance, vpTheme, &atom); | ||
105 | ExitOnFailure(hr, "Failed to create main window."); | ||
106 | |||
107 | hr = ThemeCreateParentWindow(vpTheme, 0, reinterpret_cast<LPCWSTR>(atom), vpTheme->sczCaption, vpTheme->dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, hInstance, NULL, THEME_WINDOW_INITIAL_POSITION_DEFAULT, &hWnd); | ||
108 | ExitOnFailure(hr, "Failed to create window."); | ||
109 | |||
110 | if (!sczThemeFile) | ||
111 | { | ||
112 | // Prompt for a path to the theme file. | ||
113 | OPENFILENAMEW ofn = { }; | ||
114 | WCHAR wzFile[MAX_PATH] = { }; | ||
115 | |||
116 | ofn.lStructSize = sizeof(ofn); | ||
117 | ofn.hwndOwner = hWnd; | ||
118 | ofn.lpstrFile = wzFile; | ||
119 | ofn.nMaxFile = countof(wzFile); | ||
120 | ofn.lpstrFilter = L"Theme Files (*.thm)\0*.thm\0XML Files (*.xml)\0*.xml\0All Files (*.*)\0*.*\0"; | ||
121 | ofn.nFilterIndex = 1; | ||
122 | ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; | ||
123 | ofn.lpstrTitle = vpTheme->sczCaption; | ||
124 | |||
125 | if (::GetOpenFileNameW(&ofn)) | ||
126 | { | ||
127 | hr = StrAllocString(&sczThemeFile, wzFile, 0); | ||
128 | ExitOnFailure(hr, "Failed to copy opened file to theme file."); | ||
129 | } | ||
130 | else | ||
131 | { | ||
132 | ::MessageBoxW(hWnd, L"Must specify a path to theme file.", vpTheme->sczCaption, MB_OK | MB_ICONERROR); | ||
133 | ExitFunction1(hr = E_INVALIDARG); | ||
134 | } | ||
135 | } | ||
136 | |||
137 | hr = DisplayStart(hInstance, hWnd, &hDisplayThread, &vdwDisplayThreadId); | ||
138 | ExitOnFailure(hr, "Failed to start display."); | ||
139 | |||
140 | hr = LoadStart(sczThemeFile, sczWxlFile, hWnd, &hLoadThread); | ||
141 | ExitOnFailure(hr, "Failed to start load."); | ||
142 | |||
143 | // message pump | ||
144 | while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0))) | ||
145 | { | ||
146 | if (-1 == fRet) | ||
147 | { | ||
148 | hr = E_UNEXPECTED; | ||
149 | ExitOnFailure(hr, "Unexpected return value from message pump."); | ||
150 | } | ||
151 | else if (!ThemeHandleKeyboardMessage(vpTheme, msg.hwnd, &msg)) | ||
152 | { | ||
153 | ::TranslateMessage(&msg); | ||
154 | ::DispatchMessageW(&msg); | ||
155 | } | ||
156 | } | ||
157 | |||
158 | LExit: | ||
159 | if (::IsWindow(hWnd)) | ||
160 | { | ||
161 | ::DestroyWindow(hWnd); | ||
162 | } | ||
163 | |||
164 | if (hDisplayThread) | ||
165 | { | ||
166 | ::PostThreadMessageW(vdwDisplayThreadId, WM_QUIT, 0, 0); | ||
167 | ::WaitForSingleObject(hDisplayThread, 10000); | ||
168 | ::CloseHandle(hDisplayThread); | ||
169 | } | ||
170 | |||
171 | // TODO: come up with a good way to kill the load thread, probably need to switch | ||
172 | // the ReadDirectoryW() to overlapped mode. | ||
173 | ReleaseHandle(hLoadThread); | ||
174 | |||
175 | if (atom && !::UnregisterClassW(reinterpret_cast<LPCWSTR>(atom), hInstance)) | ||
176 | { | ||
177 | DWORD er = ::GetLastError(); | ||
178 | er = er; | ||
179 | } | ||
180 | |||
181 | ThemeFree(vpTheme); | ||
182 | ThemeUninitialize(); | ||
183 | DutilUninitialize(); | ||
184 | |||
185 | // uninitialize COM | ||
186 | if (fComInitialized) | ||
187 | { | ||
188 | ::CoUninitialize(); | ||
189 | } | ||
190 | |||
191 | ReleaseNullStr(vsczThemeLoadErrors); | ||
192 | ReleaseStr(sczThemeFile); | ||
193 | ReleaseStr(sczWxlFile); | ||
194 | return hr; | ||
195 | } | ||
196 | |||
197 | static void CALLBACK ThmviewerTraceError( | ||
198 | __in_z LPCSTR /*szFile*/, | ||
199 | __in int /*iLine*/, | ||
200 | __in REPORT_LEVEL /*rl*/, | ||
201 | __in UINT source, | ||
202 | __in HRESULT hrError, | ||
203 | __in_z __format_string LPCSTR szFormat, | ||
204 | __in va_list args | ||
205 | ) | ||
206 | { | ||
207 | HRESULT hr = S_OK; | ||
208 | LPSTR sczFormattedAnsi = NULL; | ||
209 | LPWSTR sczMessage = NULL; | ||
210 | |||
211 | if (DUTIL_SOURCE_THMUTIL != source) | ||
212 | { | ||
213 | ExitFunction(); | ||
214 | } | ||
215 | |||
216 | hr = StrAnsiAllocFormattedArgs(&sczFormattedAnsi, szFormat, args); | ||
217 | ExitOnFailure(hr, "Failed to format error log string."); | ||
218 | |||
219 | hr = StrAllocFormatted(&sczMessage, L"Error 0x%08x: %S\r\n", hrError, sczFormattedAnsi); | ||
220 | ExitOnFailure(hr, "Failed to prepend error number to error log string."); | ||
221 | |||
222 | hr = StrAllocConcat(&vsczThemeLoadErrors, sczMessage, 0); | ||
223 | ExitOnFailure(hr, "Failed to append theme load error."); | ||
224 | |||
225 | LExit: | ||
226 | ReleaseStr(sczFormattedAnsi); | ||
227 | ReleaseStr(sczMessage); | ||
228 | } | ||
229 | |||
230 | |||
231 | // | ||
232 | // ProcessCommandLine - process the provided command line arguments. | ||
233 | // | ||
234 | static HRESULT ProcessCommandLine( | ||
235 | __in_z_opt LPCWSTR wzCommandLine, | ||
236 | __out_z LPWSTR* psczThemeFile, | ||
237 | __out_z LPWSTR* psczWxlFile | ||
238 | ) | ||
239 | { | ||
240 | HRESULT hr = S_OK; | ||
241 | int argc = 0; | ||
242 | LPWSTR* argv = NULL; | ||
243 | |||
244 | if (wzCommandLine && *wzCommandLine) | ||
245 | { | ||
246 | hr = AppParseCommandLine(wzCommandLine, &argc, &argv); | ||
247 | ExitOnFailure(hr, "Failed to parse command line."); | ||
248 | |||
249 | for (int i = 0; i < argc; ++i) | ||
250 | { | ||
251 | if (argv[i][0] == L'-' || argv[i][0] == L'/') | ||
252 | { | ||
253 | if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"lang", -1)) | ||
254 | { | ||
255 | if (i + 1 >= argc) | ||
256 | { | ||
257 | ExitOnRootFailure(hr = E_INVALIDARG, "Must specify a language."); | ||
258 | } | ||
259 | |||
260 | ++i; | ||
261 | } | ||
262 | } | ||
263 | else | ||
264 | { | ||
265 | LPCWSTR wzExtension = PathExtension(argv[i]); | ||
266 | if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, wzExtension, -1, L".wxl", -1)) | ||
267 | { | ||
268 | hr = StrAllocString(psczWxlFile, argv[i], 0); | ||
269 | } | ||
270 | else | ||
271 | { | ||
272 | hr = StrAllocString(psczThemeFile, argv[i], 0); | ||
273 | } | ||
274 | ExitOnFailure(hr, "Failed to copy path to file."); | ||
275 | } | ||
276 | } | ||
277 | } | ||
278 | |||
279 | LExit: | ||
280 | if (argv) | ||
281 | { | ||
282 | AppFreeCommandLineArgs(argv); | ||
283 | } | ||
284 | |||
285 | return hr; | ||
286 | } | ||
287 | |||
288 | static HRESULT CreateTheme( | ||
289 | __in HINSTANCE hInstance, | ||
290 | __out THEME** ppTheme | ||
291 | ) | ||
292 | { | ||
293 | HRESULT hr = S_OK; | ||
294 | |||
295 | hr = ThemeInitialize(hInstance); | ||
296 | ExitOnFailure(hr, "Failed to initialize theme manager."); | ||
297 | |||
298 | hr = ThemeLoadFromResource(hInstance, MAKEINTRESOURCEA(THMVWR_RES_THEME_FILE), ppTheme); | ||
299 | ExitOnFailure(hr, "Failed to load theme from thmviewer.thm."); | ||
300 | |||
301 | LExit: | ||
302 | return hr; | ||
303 | } | ||
304 | |||
305 | static HRESULT CreateMainWindowClass( | ||
306 | __in HINSTANCE hInstance, | ||
307 | __in THEME* pTheme, | ||
308 | __out ATOM* pAtom | ||
309 | ) | ||
310 | { | ||
311 | HRESULT hr = S_OK; | ||
312 | ATOM atom = 0; | ||
313 | WNDCLASSW wc = { }; | ||
314 | |||
315 | ThemeInitializeWindowClass(pTheme, &wc, MainWndProc, hInstance, THMVWR_WINDOW_CLASS_MAIN); | ||
316 | |||
317 | atom = ::RegisterClassW(&wc); | ||
318 | if (!atom) | ||
319 | { | ||
320 | ExitWithLastError(hr, "Failed to register main windowclass ."); | ||
321 | } | ||
322 | |||
323 | *pAtom = atom; | ||
324 | |||
325 | LExit: | ||
326 | return hr; | ||
327 | } | ||
328 | |||
329 | static LRESULT CALLBACK MainWndProc( | ||
330 | __in HWND hWnd, | ||
331 | __in UINT uMsg, | ||
332 | __in WPARAM wParam, | ||
333 | __in LPARAM lParam | ||
334 | ) | ||
335 | { | ||
336 | HANDLE_THEME* pHandleTheme = reinterpret_cast<HANDLE_THEME*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA)); | ||
337 | |||
338 | switch (uMsg) | ||
339 | { | ||
340 | case WM_NCCREATE: | ||
341 | { | ||
342 | //LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam); | ||
343 | //pBA = reinterpret_cast<CWixStandardBootstrapperApplication*>(lpcs->lpCreateParams); | ||
344 | //::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pBA)); | ||
345 | } | ||
346 | break; | ||
347 | |||
348 | case WM_NCDESTROY: | ||
349 | DecrementHandleTheme(pHandleTheme); | ||
350 | ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0); | ||
351 | ::PostQuitMessage(0); | ||
352 | break; | ||
353 | |||
354 | case WM_THMVWR_THEME_LOAD_BEGIN: | ||
355 | OnThemeLoadBegin(vsczThemeLoadErrors); | ||
356 | return 0; | ||
357 | |||
358 | case WM_THMVWR_THEME_LOAD_ERROR: | ||
359 | OnThemeLoadError(vpTheme, lParam); | ||
360 | return 0; | ||
361 | |||
362 | case WM_THMVWR_NEW_THEME: | ||
363 | OnNewTheme(vpTheme, hWnd, reinterpret_cast<HANDLE_THEME*>(lParam)); | ||
364 | return 0; | ||
365 | |||
366 | case WM_THMUTIL_LOADING_CONTROL: | ||
367 | return OnThemeLoadingControl(reinterpret_cast<THEME_LOADINGCONTROL_ARGS*>(wParam), reinterpret_cast<THEME_LOADINGCONTROL_RESULTS*>(lParam)); | ||
368 | |||
369 | case WM_THMUTIL_CONTROL_WM_NOTIFY: | ||
370 | return OnThemeControlWmNotify(reinterpret_cast<THEME_CONTROLWMNOTIFY_ARGS*>(wParam), reinterpret_cast<THEME_CONTROLWMNOTIFY_RESULTS*>(lParam)); | ||
371 | } | ||
372 | |||
373 | return ThemeDefWindowProc(vpTheme, hWnd, uMsg, wParam, lParam); | ||
374 | } | ||
375 | |||
376 | static void OnThemeLoadBegin( | ||
377 | __in_z_opt LPWSTR sczThemeLoadErrors | ||
378 | ) | ||
379 | { | ||
380 | ReleaseNullStr(sczThemeLoadErrors); | ||
381 | } | ||
382 | |||
383 | static void OnThemeLoadError( | ||
384 | __in THEME* pTheme, | ||
385 | __in HRESULT hrFailure | ||
386 | ) | ||
387 | { | ||
388 | HRESULT hr = S_OK; | ||
389 | LPWSTR sczMessage = NULL; | ||
390 | LPWSTR* psczErrors = NULL; | ||
391 | UINT cErrors = 0; | ||
392 | TVINSERTSTRUCTW tvi = { }; | ||
393 | const THEME_CONTROL* pTreeControl = NULL; | ||
394 | |||
395 | if (!ThemeControlExistsById(pTheme, THMVWR_CONTROL_TREE, &pTreeControl)) | ||
396 | { | ||
397 | ExitWithRootFailure(hr, E_INVALIDSTATE, "THMVWR_CONTROL_TREE control doesn't exist."); | ||
398 | } | ||
399 | |||
400 | // Add the application node. | ||
401 | tvi.hParent = NULL; | ||
402 | tvi.hInsertAfter = TVI_ROOT; | ||
403 | tvi.item.mask = TVIF_TEXT | TVIF_PARAM; | ||
404 | tvi.item.lParam = 0; | ||
405 | tvi.item.pszText = L"Failed to load theme."; | ||
406 | tvi.hParent = reinterpret_cast<HTREEITEM>(::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi))); | ||
407 | |||
408 | if (!vsczThemeLoadErrors) | ||
409 | { | ||
410 | hr = StrAllocFormatted(&sczMessage, L"Error 0x%08x.", hrFailure); | ||
411 | ExitOnFailure(hr, "Failed to format error message."); | ||
412 | |||
413 | tvi.item.pszText = sczMessage; | ||
414 | ::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi)); | ||
415 | |||
416 | hr = StrAllocFromError(&sczMessage, hrFailure, NULL); | ||
417 | ExitOnFailure(hr, "Failed to format error message text."); | ||
418 | |||
419 | tvi.item.pszText = sczMessage; | ||
420 | ::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi)); | ||
421 | } | ||
422 | else | ||
423 | { | ||
424 | hr = StrSplitAllocArray(&psczErrors, &cErrors, vsczThemeLoadErrors, L"\r\n"); | ||
425 | ExitOnFailure(hr, "Failed to split theme load errors."); | ||
426 | |||
427 | for (DWORD i = 0; i < cErrors; ++i) | ||
428 | { | ||
429 | tvi.item.pszText = psczErrors[i]; | ||
430 | ::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi)); | ||
431 | } | ||
432 | } | ||
433 | |||
434 | ::SendMessage(pTreeControl->hWnd, TVM_EXPAND, TVE_EXPAND, reinterpret_cast<LPARAM>(tvi.hParent)); | ||
435 | |||
436 | LExit: | ||
437 | ReleaseStr(sczMessage); | ||
438 | ReleaseMem(psczErrors); | ||
439 | } | ||
440 | |||
441 | |||
442 | static void OnNewTheme( | ||
443 | __in THEME* pTheme, | ||
444 | __in HWND hWnd, | ||
445 | __in HANDLE_THEME* pHandle | ||
446 | ) | ||
447 | { | ||
448 | const THEME_CONTROL* pTreeControl = NULL; | ||
449 | HANDLE_THEME* pOldHandle = reinterpret_cast<HANDLE_THEME*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA)); | ||
450 | THEME* pNewTheme = pHandle->pTheme; | ||
451 | |||
452 | WCHAR wzSelectedPage[MAX_PATH] = { }; | ||
453 | HTREEITEM htiSelected = NULL; | ||
454 | TVINSERTSTRUCTW tvi = { }; | ||
455 | TVITEMW item = { }; | ||
456 | |||
457 | if (pOldHandle) | ||
458 | { | ||
459 | DecrementHandleTheme(pOldHandle); | ||
460 | pOldHandle = NULL; | ||
461 | } | ||
462 | |||
463 | // Pass the new theme handle to the display thread so it can get the display window prepared | ||
464 | // to show the new theme. | ||
465 | IncrementHandleTheme(pHandle); | ||
466 | ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_NEW_THEME, 0, reinterpret_cast<LPARAM>(pHandle)); | ||
467 | |||
468 | ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pHandle)); | ||
469 | |||
470 | if (!ThemeControlExistsById(pTheme, THMVWR_CONTROL_TREE, &pTreeControl)) | ||
471 | { | ||
472 | TraceError(E_INVALIDSTATE, "Tree control doesn't exist."); | ||
473 | return; | ||
474 | } | ||
475 | |||
476 | // Remember the currently selected item by name so we can try to automatically select it later. | ||
477 | // Otherwise, the user would see their window destroyed after every save of their theme file and | ||
478 | // have to click to get the window back. | ||
479 | item.mask = TVIF_TEXT; | ||
480 | item.pszText = wzSelectedPage; | ||
481 | item.cchTextMax = countof(wzSelectedPage); | ||
482 | item.hItem = reinterpret_cast<HTREEITEM>(::SendMessage(pTreeControl->hWnd, TVM_GETNEXTITEM, TVGN_CARET, NULL)); | ||
483 | ::SendMessage(pTreeControl->hWnd, TVM_GETITEM, 0, reinterpret_cast<LPARAM>(&item)); | ||
484 | |||
485 | // Remove the previous items in the tree. | ||
486 | ::SendMessage(pTreeControl->hWnd, TVM_DELETEITEM, 0, reinterpret_cast<LPARAM>(TVI_ROOT)); | ||
487 | |||
488 | // Add the application node. | ||
489 | tvi.hParent = NULL; | ||
490 | tvi.hInsertAfter = TVI_ROOT; | ||
491 | tvi.item.mask = TVIF_TEXT | TVIF_PARAM; | ||
492 | tvi.item.lParam = 0; | ||
493 | tvi.item.pszText = pHandle && pHandle->pTheme && pHandle->pTheme->sczCaption ? pHandle->pTheme->sczCaption : L"Window"; | ||
494 | |||
495 | // Add the pages. | ||
496 | tvi.hParent = reinterpret_cast<HTREEITEM>(::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi))); | ||
497 | tvi.hInsertAfter = TVI_SORT; | ||
498 | for (DWORD i = 0; i < pNewTheme->cPages; ++i) | ||
499 | { | ||
500 | THEME_PAGE* pPage = pNewTheme->rgPages + i; | ||
501 | if (pPage->sczName && *pPage->sczName) | ||
502 | { | ||
503 | tvi.item.pszText = pPage->sczName; | ||
504 | tvi.item.lParam = i + 1; //prgdwPageIds[i]; - TODO: do the right thing here by calling ThemeGetPageIds(), should not assume we know how the page ids will be calculated. | ||
505 | |||
506 | HTREEITEM hti = reinterpret_cast<HTREEITEM>(::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi))); | ||
507 | if (*wzSelectedPage && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pPage->sczName, -1, wzSelectedPage, -1)) | ||
508 | { | ||
509 | htiSelected = hti; | ||
510 | } | ||
511 | } | ||
512 | } | ||
513 | |||
514 | if (*wzSelectedPage && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, L"Application", -1, wzSelectedPage, -1)) | ||
515 | { | ||
516 | htiSelected = tvi.hParent; | ||
517 | } | ||
518 | |||
519 | ::SendMessage(pTreeControl->hWnd, TVM_EXPAND, TVE_EXPAND, reinterpret_cast<LPARAM>(tvi.hParent)); | ||
520 | if (htiSelected) | ||
521 | { | ||
522 | ::SendMessage(pTreeControl->hWnd, TVM_SELECTITEM, TVGN_CARET, reinterpret_cast<LPARAM>(htiSelected)); | ||
523 | } | ||
524 | } | ||
525 | |||
526 | static BOOL OnThemeLoadingControl( | ||
527 | __in const THEME_LOADINGCONTROL_ARGS* pArgs, | ||
528 | __in THEME_LOADINGCONTROL_RESULTS* pResults | ||
529 | ) | ||
530 | { | ||
531 | if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pArgs->pThemeControl->sczName, -1, L"Tree", -1)) | ||
532 | { | ||
533 | pResults->wId = THMVWR_CONTROL_TREE; | ||
534 | } | ||
535 | |||
536 | pResults->hr = S_OK; | ||
537 | return TRUE; | ||
538 | } | ||
539 | |||
540 | static BOOL OnThemeControlWmNotify( | ||
541 | __in const THEME_CONTROLWMNOTIFY_ARGS* pArgs, | ||
542 | __in THEME_CONTROLWMNOTIFY_RESULTS* /*pResults*/ | ||
543 | ) | ||
544 | { | ||
545 | BOOL fProcessed = FALSE; | ||
546 | |||
547 | switch (pArgs->lParam->code) | ||
548 | { | ||
549 | case TVN_SELCHANGEDW: | ||
550 | switch (pArgs->pThemeControl->wId) | ||
551 | { | ||
552 | case THMVWR_CONTROL_TREE: | ||
553 | NMTREEVIEWW* ptv = reinterpret_cast<NMTREEVIEWW*>(pArgs->lParam); | ||
554 | ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_SHOWPAGE, SW_HIDE, ptv->itemOld.lParam); | ||
555 | ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_SHOWPAGE, SW_SHOW, ptv->itemNew.lParam); | ||
556 | |||
557 | fProcessed = TRUE; | ||
558 | break; | ||
559 | } | ||
560 | break; | ||
561 | } | ||
562 | |||
563 | return fProcessed; | ||
564 | } | ||
diff --git a/src/tools/thmviewer/thmviewer.manifest b/src/tools/thmviewer/thmviewer.manifest new file mode 100644 index 00000000..4663b61c --- /dev/null +++ b/src/tools/thmviewer/thmviewer.manifest | |||
@@ -0,0 +1,19 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
3 | |||
4 | |||
5 | <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> | ||
6 | <assemblyIdentity name="thmviewer.exe" version="1.0.0.0" processorArchitecture="x86" type="win32"/> | ||
7 | <description>WiX Toolset Theme Viewer</description> | ||
8 | <dependency><dependentAssembly><assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="X86" publicKeyToken="6595b64144ccf1df" language="*" /></dependentAssembly></dependency> | ||
9 | <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/><supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/></application></compatibility> | ||
10 | <application xmlns="urn:schemas-microsoft-com:asm.v3"> | ||
11 | <windowsSettings xmlns="urn:schemas-microsoft-com:asm.v3"> | ||
12 | <!-- pre-Win10 1607 --> | ||
13 | <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> | ||
14 | <!-- Win10 picks the first one it recognizes --> | ||
15 | <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor, System</dpiAwareness> | ||
16 | </windowsSettings> | ||
17 | </application> | ||
18 | <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"><security><requestedPrivileges><requestedExecutionLevel level="asInvoker" uiAccess="false"/></requestedPrivileges></security></trustInfo> | ||
19 | </assembly> | ||
diff --git a/src/tools/thmviewer/thmviewer.rc b/src/tools/thmviewer/thmviewer.rc new file mode 100644 index 00000000..dc6d7242 --- /dev/null +++ b/src/tools/thmviewer/thmviewer.rc | |||
@@ -0,0 +1,12 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | #include <winver.h> | ||
4 | #include <windows.h> | ||
5 | #include "resource.h" | ||
6 | |||
7 | //#define MANIFEST_RESOURCE_ID 1 | ||
8 | //MANIFEST_RESOURCE_ID RT_MANIFEST "thmviewer.manifest" | ||
9 | |||
10 | LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL | ||
11 | THMVWR_RES_THEME_FILE RCDATA "Resources\\thm.xml" | ||
12 | THMVWR_RES_RICHEDIT_FILE RCDATA "Resources\\LoremIpsum.rtf" | ||
diff --git a/src/tools/thmviewer/thmviewer.v3.ncrunchproject b/src/tools/thmviewer/thmviewer.v3.ncrunchproject new file mode 100644 index 00000000..3cffd6ce --- /dev/null +++ b/src/tools/thmviewer/thmviewer.v3.ncrunchproject | |||
@@ -0,0 +1,7 @@ | |||
1 | <ProjectConfiguration> | ||
2 | <Settings> | ||
3 | <AdditionalFilesToIncludeForProject> | ||
4 | <Value>thmviewer.manifest</Value> | ||
5 | </AdditionalFilesToIncludeForProject> | ||
6 | </Settings> | ||
7 | </ProjectConfiguration> \ No newline at end of file | ||
diff --git a/src/tools/thmviewer/thmviewer.vcxproj b/src/tools/thmviewer/thmviewer.vcxproj new file mode 100644 index 00000000..3fd87878 --- /dev/null +++ b/src/tools/thmviewer/thmviewer.vcxproj | |||
@@ -0,0 +1,69 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
3 | |||
4 | <Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
5 | <ItemGroup Label="ProjectConfigurations"> | ||
6 | <ProjectConfiguration Include="Debug|Win32"> | ||
7 | <Configuration>Debug</Configuration> | ||
8 | <Platform>Win32</Platform> | ||
9 | </ProjectConfiguration> | ||
10 | <ProjectConfiguration Include="Release|Win32"> | ||
11 | <Configuration>Release</Configuration> | ||
12 | <Platform>Win32</Platform> | ||
13 | </ProjectConfiguration> | ||
14 | </ItemGroup> | ||
15 | |||
16 | <PropertyGroup Label="Globals"> | ||
17 | <ProjectGuid>{95228C13-97F5-484A-B4A2-ECF4618B0881}</ProjectGuid> | ||
18 | <Keyword>Win32Proj</Keyword> | ||
19 | <ConfigurationType>Application</ConfigurationType> | ||
20 | <CharacterSet>Unicode</CharacterSet> | ||
21 | <Description>WiX Toolset Theme Viewer</Description> | ||
22 | </PropertyGroup> | ||
23 | |||
24 | <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> | ||
25 | <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> | ||
26 | |||
27 | <ImportGroup Label="ExtensionSettings"> | ||
28 | </ImportGroup> | ||
29 | |||
30 | <ImportGroup Label="Shared"> | ||
31 | </ImportGroup> | ||
32 | |||
33 | <PropertyGroup> | ||
34 | <ProjectAdditionalLinkLibraries>comctl32.lib;gdiplus.lib;msimg32.lib;shlwapi.lib</ProjectAdditionalLinkLibraries> | ||
35 | </PropertyGroup> | ||
36 | |||
37 | <ItemGroup> | ||
38 | <ClCompile Include="display.cpp" /> | ||
39 | <ClCompile Include="load.cpp" /> | ||
40 | <ClCompile Include="precomp.cpp"> | ||
41 | <PrecompiledHeader>Create</PrecompiledHeader> | ||
42 | </ClCompile> | ||
43 | <ClCompile Include="thmviewer.cpp" /> | ||
44 | </ItemGroup> | ||
45 | <ItemGroup> | ||
46 | <ClInclude Include="precomp.h" /> | ||
47 | <ClInclude Include="resource.h" /> | ||
48 | </ItemGroup> | ||
49 | <ItemGroup> | ||
50 | <None Include="packages.config" /> | ||
51 | <None Include="Resources\LoremIpsum.rtf" /> | ||
52 | <None Include="Resources\thm.xml" /> | ||
53 | </ItemGroup> | ||
54 | <ItemGroup> | ||
55 | <ResourceCompile Include="thmviewer.rc" /> | ||
56 | </ItemGroup> | ||
57 | <ItemGroup> | ||
58 | <Manifest Include="thmviewer.manifest" /> | ||
59 | </ItemGroup> | ||
60 | |||
61 | <ItemGroup> | ||
62 | <PackageReference Include="WixToolset.DUtil" /> | ||
63 | |||
64 | <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" /> | ||
65 | <PackageReference Include="GitInfo" PrivateAssets="All" /> | ||
66 | </ItemGroup> | ||
67 | |||
68 | <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> | ||
69 | </Project> | ||
diff --git a/src/tools/thmviewer/thmviewer.vcxproj.filters b/src/tools/thmviewer/thmviewer.vcxproj.filters new file mode 100644 index 00000000..488d5510 --- /dev/null +++ b/src/tools/thmviewer/thmviewer.vcxproj.filters | |||
@@ -0,0 +1,56 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
3 | <ItemGroup> | ||
4 | <Filter Include="Source Files"> | ||
5 | <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> | ||
6 | <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> | ||
7 | </Filter> | ||
8 | <Filter Include="Header Files"> | ||
9 | <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> | ||
10 | <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions> | ||
11 | </Filter> | ||
12 | <Filter Include="Resource Files"> | ||
13 | <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> | ||
14 | <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav</Extensions> | ||
15 | </Filter> | ||
16 | </ItemGroup> | ||
17 | <ItemGroup> | ||
18 | <ClCompile Include="thmviewer.cpp"> | ||
19 | <Filter>Source Files</Filter> | ||
20 | </ClCompile> | ||
21 | <ClCompile Include="display.cpp"> | ||
22 | <Filter>Source Files</Filter> | ||
23 | </ClCompile> | ||
24 | <ClCompile Include="load.cpp"> | ||
25 | <Filter>Source Files</Filter> | ||
26 | </ClCompile> | ||
27 | <ClCompile Include="precomp.cpp"> | ||
28 | <Filter>Source Files</Filter> | ||
29 | </ClCompile> | ||
30 | </ItemGroup> | ||
31 | <ItemGroup> | ||
32 | <None Include="Resources\thm.xml"> | ||
33 | <Filter>Resource Files</Filter> | ||
34 | </None> | ||
35 | <None Include="Resources\LoremIpsum.rtf"> | ||
36 | <Filter>Resource Files</Filter> | ||
37 | </None> | ||
38 | <None Include="packages.config" /> | ||
39 | </ItemGroup> | ||
40 | <ItemGroup> | ||
41 | <ClInclude Include="precomp.h"> | ||
42 | <Filter>Header Files</Filter> | ||
43 | </ClInclude> | ||
44 | <ClInclude Include="resource.h"> | ||
45 | <Filter>Header Files</Filter> | ||
46 | </ClInclude> | ||
47 | </ItemGroup> | ||
48 | <ItemGroup> | ||
49 | <ResourceCompile Include="thmviewer.rc"> | ||
50 | <Filter>Resource Files</Filter> | ||
51 | </ResourceCompile> | ||
52 | </ItemGroup> | ||
53 | <ItemGroup> | ||
54 | <Manifest Include="thmviewer.manifest" /> | ||
55 | </ItemGroup> | ||
56 | </Project> \ No newline at end of file | ||
diff --git a/src/tools/tools.cmd b/src/tools/tools.cmd new file mode 100644 index 00000000..4764a8fe --- /dev/null +++ b/src/tools/tools.cmd | |||
@@ -0,0 +1,18 @@ | |||
1 | @setlocal | ||
2 | @pushd %~dp0 | ||
3 | |||
4 | @set _C=Debug | ||
5 | :parse_args | ||
6 | @if /i "%1"=="release" set _C=Release | ||
7 | @if not "%1"=="" shift & goto parse_args | ||
8 | |||
9 | @echo Building tools %_C% | ||
10 | |||
11 | :: tools | ||
12 | |||
13 | nuget restore || exit /b | ||
14 | |||
15 | msbuild -t:Build -p:Configuration=%_C% -nologo -m -warnaserror -bl:..\..\build\logs\tools_build.binlog || exit /b | ||
16 | |||
17 | @popd | ||
18 | @endlocal | ||
diff --git a/src/tools/tools.sln b/src/tools/tools.sln new file mode 100644 index 00000000..4da1e8fd --- /dev/null +++ b/src/tools/tools.sln | |||
@@ -0,0 +1,40 @@ | |||
1 | |||
2 | Microsoft Visual Studio Solution File, Format Version 12.00 | ||
3 | # Visual Studio Version 16 | ||
4 | VisualStudioVersion = 16.0.30711.63 | ||
5 | MinimumVisualStudioVersion = 15.0.26124.0 | ||
6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "thmviewer", "thmviewer\thmviewer.vcxproj", "{95228C13-97F5-484A-B4A2-ECF4618B0881}" | ||
7 | EndProject | ||
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Templates", "WixToolset.Templates\WixToolset.Templates.csproj", "{D1385232-CA10-4092-BAB5-4E5499FE144C}" | ||
9 | EndProject | ||
10 | Global | ||
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
12 | Debug|Any CPU = Debug|Any CPU | ||
13 | Debug|x86 = Debug|x86 | ||
14 | Release|Any CPU = Release|Any CPU | ||
15 | Release|x86 = Release|x86 | ||
16 | EndGlobalSection | ||
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
18 | {95228C13-97F5-484A-B4A2-ECF4618B0881}.Debug|Any CPU.ActiveCfg = Debug|Win32 | ||
19 | {95228C13-97F5-484A-B4A2-ECF4618B0881}.Debug|Any CPU.Build.0 = Debug|Win32 | ||
20 | {95228C13-97F5-484A-B4A2-ECF4618B0881}.Debug|x86.ActiveCfg = Debug|Win32 | ||
21 | {95228C13-97F5-484A-B4A2-ECF4618B0881}.Debug|x86.Build.0 = Debug|Win32 | ||
22 | {95228C13-97F5-484A-B4A2-ECF4618B0881}.Release|Any CPU.ActiveCfg = Release|Win32 | ||
23 | {95228C13-97F5-484A-B4A2-ECF4618B0881}.Release|x86.ActiveCfg = Release|Win32 | ||
24 | {95228C13-97F5-484A-B4A2-ECF4618B0881}.Release|x86.Build.0 = Release|Win32 | ||
25 | {D1385232-CA10-4092-BAB5-4E5499FE144C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
26 | {D1385232-CA10-4092-BAB5-4E5499FE144C}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
27 | {D1385232-CA10-4092-BAB5-4E5499FE144C}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
28 | {D1385232-CA10-4092-BAB5-4E5499FE144C}.Debug|x86.Build.0 = Debug|Any CPU | ||
29 | {D1385232-CA10-4092-BAB5-4E5499FE144C}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
30 | {D1385232-CA10-4092-BAB5-4E5499FE144C}.Release|Any CPU.Build.0 = Release|Any CPU | ||
31 | {D1385232-CA10-4092-BAB5-4E5499FE144C}.Release|x86.ActiveCfg = Release|Any CPU | ||
32 | {D1385232-CA10-4092-BAB5-4E5499FE144C}.Release|x86.Build.0 = Release|Any CPU | ||
33 | EndGlobalSection | ||
34 | GlobalSection(SolutionProperties) = preSolution | ||
35 | HideSolutionNode = FALSE | ||
36 | EndGlobalSection | ||
37 | GlobalSection(ExtensibilityGlobals) = postSolution | ||
38 | SolutionGuid = {537F1116-39FE-4AED-A9A2-35030E5750D5} | ||
39 | EndGlobalSection | ||
40 | EndGlobal | ||