diff options
author | Rob Mensching <rob@firegiant.com> | 2022-07-14 15:19:53 -0700 |
---|---|---|
committer | Rob Mensching <rob@firegiant.com> | 2022-07-14 16:02:24 -0700 |
commit | 229242cf7c328b89b5aa65ed7a04e33c8b93b393 (patch) | |
tree | de0a9547e73e46490b0946d6850228d5b30258b8 /src/tools | |
parent | f46ca6a9dce91607ffc9855270dd6998216e1a8b (diff) | |
download | wix-229242cf7c328b89b5aa65ed7a04e33c8b93b393.tar.gz wix-229242cf7c328b89b5aa65ed7a04e33c8b93b393.tar.bz2 wix-229242cf7c328b89b5aa65ed7a04e33c8b93b393.zip |
Rename "samples" segment to "tools"
This segment is a bit of a "miscellaneous section" in the WiX repo.
As such it has been difficult to name. I originally eschewed the
name "tools" because what is in the "wix" segment was once called
"tools". However, now that wix.exe is firmly established as the
entry point for WiX operations, I've become comfortable with its
segment being named "wix". That meant "tools" was again available
and "tools" better describes the content of this section.
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 | ||