├── .editorconfig ├── .gitignore ├── .gitmodules ├── CodeContracts.ruleset ├── CustomDictionary.xml ├── Directory.Build.props ├── Key.snk ├── LICENSE ├── README.md ├── ReleaseNotes.md ├── Screenshot.png ├── Wax.Model ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── Mapping │ ├── DirectoryMapping.cs │ ├── FeatureMapping.cs │ ├── FileMapping.cs │ ├── MappingState.cs │ └── UnmappedFile.cs ├── Properties │ └── AssemblyInfo.cs ├── Tools │ ├── AssemblyHelper.cs │ ├── SerializerExtensions.cs │ └── XmlExtensions.cs ├── VisualStudio │ ├── BuildFileGroups.cs │ ├── DteExtensions.cs │ ├── Project.cs │ ├── ProjectOutput.cs │ ├── ProjectOutputGroup.cs │ ├── ProjectReference.cs │ └── Solution.cs ├── Wax.Model.csproj └── Wix │ ├── ProjectConfiguration.cs │ ├── WixComponentGroupNode.cs │ ├── WixComponentNode.cs │ ├── WixDefine.cs │ ├── WixDirectoryNode.cs │ ├── WixFeatureNode.cs │ ├── WixFileNode.cs │ ├── WixNames.cs │ ├── WixNode.cs │ ├── WixProject.cs │ └── WixSourceFile.cs ├── Wax.sln ├── Wax.sln.DotSettings ├── Wax ├── 200x200.png ├── 32x32.png ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── GroupBox.cs ├── Guids.cs ├── MainView.xaml ├── MainView.xaml.cs ├── MainViewModel.cs ├── PkgCmdID.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.de.resx │ ├── Resources.fr.resx │ ├── Resources.resx │ ├── Resources.zh-CN.resx │ └── launchSettings.json ├── Resources │ ├── Icon32.pdn │ ├── Package.ico │ ├── VSColorScheme.xaml │ ├── btn_donate_SM.gif │ ├── icon.png │ ├── images.png │ ├── like.png │ ├── ok.png │ ├── refresh.png │ └── warning.png ├── ShellView.xaml ├── ShellView.xaml.cs ├── Themes │ └── Generic.xaml ├── ToolWindow.cs ├── VSPackage.de.resx ├── VSPackage.fr.resx ├── VSPackage.resx ├── VSPackage.zh-CN.resx ├── VsPackage.cs ├── Wax.csproj ├── Wax.vsct └── source.extension.vsixmanifest └── azure-pipelines.yml /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # VSTHRD010: Invoke single-threaded types on Main thread 4 | dotnet_diagnostic.VSTHRD010.severity = suggestion 5 | 6 | # IDE0008: Use explicit type 7 | csharp_style_var_elsewhere = true 8 | 9 | # IDE0008: Use explicit type 10 | csharp_style_var_for_built_in_types = true 11 | 12 | # IDE0008: Use explicit type 13 | csharp_style_var_when_type_is_apparent = true 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | # Uncomment if you have tasks that create the project's static files in wwwroot 27 | #wwwroot/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # DNX 43 | project.lock.json 44 | artifacts/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opendb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | *.sap 88 | 89 | # TFS 2012 Local Workspace 90 | $tf/ 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | *.DotSettings.user 99 | 100 | # JustCode is a .NET coding add-in 101 | .JustCode 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | _NCrunch_* 111 | .*crunch*.local.xml 112 | nCrunchTemp_* 113 | 114 | # MightyMoose 115 | *.mm.* 116 | AutoTest.Net/ 117 | 118 | # Web workbench (sass) 119 | .sass-cache/ 120 | 121 | # Installshield output folder 122 | [Ee]xpress/ 123 | 124 | # DocProject is a documentation generator add-in 125 | DocProject/buildhelp/ 126 | DocProject/Help/*.HxT 127 | DocProject/Help/*.HxC 128 | DocProject/Help/*.hhc 129 | DocProject/Help/*.hhk 130 | DocProject/Help/*.hhp 131 | DocProject/Help/Html2 132 | DocProject/Help/html 133 | 134 | # Click-Once directory 135 | publish/ 136 | 137 | # Publish Web Output 138 | *.[Pp]ublish.xml 139 | *.azurePubxml 140 | # TODO: Comment the next line if you want to checkin your web deploy settings 141 | # but database connection strings (with potential passwords) will be unencrypted 142 | *.pubxml 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | # NuGet v3's project.json files produces more ignoreable files 154 | *.nuget.props 155 | *.nuget.targets 156 | 157 | # Microsoft Azure Build Output 158 | csx/ 159 | *.build.csdef 160 | 161 | # Microsoft Azure Emulator 162 | ecf/ 163 | rcf/ 164 | 165 | # Microsoft Azure ApplicationInsights config file 166 | ApplicationInsights.config 167 | 168 | # Windows Store app package directory 169 | AppPackages/ 170 | BundleArtifacts/ 171 | 172 | # Visual Studio cache files 173 | # files ending in .cache can be ignored 174 | *.[Cc]ache 175 | # but keep track of directories ending in .cache 176 | !*.[Cc]ache/ 177 | 178 | # Others 179 | ClientBin/ 180 | ~$* 181 | *~ 182 | *.dbmdl 183 | *.dbproj.schemaview 184 | *.pfx 185 | *.publishsettings 186 | node_modules/ 187 | orleans.codegen.cs 188 | 189 | # RIA/Silverlight projects 190 | Generated_Code/ 191 | 192 | # Backup & report files from converting an old project file 193 | # to a newer Visual Studio version. Backup files are not needed, 194 | # because we have git ;-) 195 | _UpgradeReport_Files/ 196 | Backup*/ 197 | UpgradeLog*.XML 198 | UpgradeLog*.htm 199 | 200 | # SQL Server files 201 | *.mdf 202 | *.ldf 203 | 204 | # Business Intelligence projects 205 | *.rdl.data 206 | *.bim.layout 207 | *.bim_*.settings 208 | 209 | # Microsoft Fakes 210 | FakesAssemblies/ 211 | 212 | # GhostDoc plugin setting file 213 | *.GhostDoc.xml 214 | 215 | # Node.js Tools for Visual Studio 216 | .ntvs_analysis.dat 217 | 218 | # Visual Studio 6 build log 219 | *.plg 220 | 221 | # Visual Studio 6 workspace options file 222 | *.opt 223 | 224 | # Visual Studio LightSwitch build output 225 | **/*.HTMLClient/GeneratedArtifacts 226 | **/*.DesktopClient/GeneratedArtifacts 227 | **/*.DesktopClient/ModelManifest.xml 228 | **/*.Server/GeneratedArtifacts 229 | **/*.Server/ModelManifest.xml 230 | _Pvt_Extensions 231 | 232 | # Paket dependency manager 233 | .paket/paket.exe 234 | 235 | # FAKE - F# Make 236 | .fake/ 237 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "BAML"] 2 | path = BAML 3 | url = https://github.com/tom-englert/BAML.git 4 | branch = master 5 | -------------------------------------------------------------------------------- /CodeContracts.ruleset: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /CustomDictionary.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | tomenglertde 9 | VSIX 10 | visualstudiogallery 11 | workitem 12 | wix 13 | dte 14 | dlls 15 | Unmap 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | tomenglertde 30 | vsix 31 | HKLM 32 | HKCU 33 | HKCR 34 | HKMU 35 | DWORD 36 | 37 | 38 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.7.0.0 5 | tom-englert.de 6 | Tom's Toolbox 7 | Copyright © tom-englert.de 2017-2021 8 | true 9 | 9.0 10 | true 11 | embedded 12 | true 13 | enable 14 | true 15 | ..\Key.snk 16 | true 17 | win 18 | 19 | -------------------------------------------------------------------------------- /Key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-englert/Wax/57dd8d47972869363733e66712cf813d3efec7d8/Key.snk -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2020 Tom Englert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wax [![Build Status](https://dev.azure.com/tom-englert/Open%20Source/_apis/build/status/Wax?branchName=master)](https://dev.azure.com/tom-englert/Open%20Source/_build/latest?definitionId=47&branchName=master) 2 | An interactive editor for [WiX Toolset](http://wixtoolset.org/) setup projects. 3 | 4 | _Wax keeps your candle burning_ 5 | 6 | While it's an easy task to create an empty setup project with the [WiX Toolset](http://wixtoolset.org/), populating the list of deployable 7 | files and even more keeping the list up to date can be a very fumbling task. 8 | This tool is a Visual Studio Extension that helps you to create, verify and maintain the list of the deployed files in an interactive GUI. 9 | 10 | Just select the projects that you want to install in the list box on the left side. 11 | Two data grids on the right will show you the target directories needed and files to be installed, and how they are mapped to the file nodes in your WiX setup project. 12 | 13 | Files that have no corresponding item in the WiX setup project are show in red. You can add them to the setup project by clicking the '+' button in the rightmost column. 14 | Files that already exist in the setup project are shown in yellow. You can link them together by clicking the '?' button in the rightmost column. 15 | Files that already exist in the setup project but have multiple matches are shown in orange. You can link items together by selecting the proper file from the combo box. 16 | 17 | The file grid supports multiple selection, so you can apply all commands to many files in one step. 18 | 19 | ![Wax main screen](http://tom-englert.github.io/Wax/Screenshot.png) 20 | 21 | ## Installation 22 | 23 | --- 24 | ### Make sure you have installed the [WiX Toolset](http://wixtoolset.org/) before installing Wax! 25 | --- 26 | 27 | This tool is a Visual Studio Extension. Use the Visual Studio Extension Manager, install from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=TomEnglert.Wax), or download the binaries and double click the Wax.vsix in the windows explorer. 28 | 29 | The Wax editor is a tool to maintain WiX projects, so you should have installed the [WiX Toolset](http://wixtoolset.org/). 30 | 31 | ## Usage 32 | 33 | ### Preparing the solution 34 | 35 | We'll assume here that you already have a solution with some projects that you want to deploy. 36 | The Wax editor is collecting the output of the selected project(s) to install and all dependencies to populate the files list. 37 | In order to get the full benefit from this tool, all files that need to be installed should be part of the projects. 38 | Files that are not generated by the build, e.g. read-me files or documentation, should be added to the project with the `Build Action` set to `Content` and `Copy to Output Directory` to `Copy always` or `Copy if newer`. 39 | If you follow this pattern, you won't have to manually fumble around with additional files in the WiX project. 40 | 41 | ### Create a new WiX Setup project 42 | 43 | If you don't already have a WiX Setup project in your solution, create a new one now: 44 | - In Visual Studio Click `File`, then click `New`, then click `Project`. 45 | - Choose the Windows Installer XML node in the Project types tree, then select Setup Project 46 | - Name your project and press OK. 47 | 48 | ### Open the editor 49 | 50 | The WiX Setup Editor menu entry is located in the Visual Studios "Tools" menu. 51 | 52 | ![Wax main screen](http://tom-englert.github.io/Wax/Screenshot.png) 53 | 54 | When parsing the projects, all files of the `Built`, `ContentFiles` and `LocalizedResourceDlls` build groups are collected. If you want to deploy the symbol files with your project, check the `Deploy symbols` toggle button in the tool bar. 55 | 56 | There are five sections you have to edit in sequential order. If a green check mark appears in the top right corner, this section is complete. If there is a red exclamation mark, the section needs editing. 57 | 58 | If you have made conceptual changes to your solution while the editor is open, click the refresh button. 59 | 60 | ### (1) Select the WiX project to edit. 61 | 62 | As the first step select the WiX project you want to edit. If there is only one setup project in the solution, it will be already selected. 63 | 64 | ### (2) Map the root directory. 65 | 66 | The editor needs to know the root directory definition in the wix file. Simply select it from the items in the combo box. 67 | If you have created a new WiX Setup project, there is only one and it is named ```INSTALLFOLDER```. 68 | 69 | ### (3) Select the project(s) to install. 70 | 71 | Select the project(s) that you want to install. This is maybe just the one .exe project in your solution. 72 | Since dependencies are detected automatically and don't need to be selected explicitly, they are not shown in the list; also test projects are hidden by default. 73 | However if you think some project is missing here, check the "Show all projects" check box to see every project of the solution. 74 | 75 | Every project you select will be automatically added to the WiX projects references, so the build order will be correct and you can use project reference variables in the WiX project. 76 | 77 | ### (4) Create the directory mappings. 78 | 79 | If you projects need to deploy files into subdirectories of the ```INSTALLFOLDER```, you need to define or map them here. 80 | If you are starting with an empty project, just click on the "+" in the rightmost column of each directory to create the WiX definitions. 81 | If there are already directories defined in the WiX project, a combo box will appear where you can select the directory that maps to the projects output folder. 82 | 83 | ### (5) Create the file mappings. 84 | 85 | The file mappings list shows all output from the selected projects. 86 | If you are starting with an empty WiX project, all files will be shown in red and the state is "Unmapped". 87 | You can select all files and click on the "+" button in the rightmost column to create all files entries. 88 | 89 | If you are editing an existing WiX project that you have created manually or e.g. with the "Harvest" tool, files might also appear as "Unique" or "Ambiguous". 90 | "Unique" means that there is only one file with that name in the whole solution, so it probably matches the file to be installed. Click on the "?" button in the rightmost column to confirm the match. 91 | "Ambiguous" means that there are several files with the same name. Use the combo box to select the matching file. 92 | 93 | ### Post processing 94 | 95 | #### Save all files 96 | Wax adds a new file to your setup project, named ```.wax```. This file stores all configurations you have made in the above steps. Make sure to save this file with your project, since it contains all information you need to maintain the project later. Also make sure the WiX files (.wxs, .wxi) are saved. 97 | 98 | #### Check your feature tree 99 | Wax will add a ComponentGroupRef node for all component groups it creates to the first feature it finds in your project. If you have just one feature defined, this will be fine; if you have more than one feature in your setup project, copy or move the entries as desired. 100 | 101 | ``` 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | ``` 110 | 111 | Now you should be able to build the setup project. 112 | 113 | Powered by   ReSharper

114 |

Support this Project: Donate

115 | -------------------------------------------------------------------------------- /ReleaseNotes.md: -------------------------------------------------------------------------------- 1 | 1.7: 2 | - Fix #76: Prerequisites outdated 3 | - Fix color scheme 4 | 5 | 1.6: 6 | - Detect new SDK style test projects: all projects referencing MSTest.TestFramework. 7 | - Drop support of VS2017. 8 | - Support of VS2022. 9 | - Improve resolving of binary source dir by favoring top level project output. 10 | - Improve display of implicit selected items. 11 | - UX improvements 12 | 13 | 1.5: 14 | - Support dark mode 15 | - Allow filtering of project items 16 | - Fix #69: Implicit selected projects are not detected in netstandard format projects 17 | 18 | 1.4: 19 | - Performance improvements. 20 | 21 | 1.3: 22 | - Support VS2019 23 | 24 | 1.2: 25 | - Ignore VS references for assemblies, only rely on the real references of the target assemblies. 26 | - Detect late bound XAML references 27 | 28 | 1.1: 29 | - use new version numbering scheme compliant with build agent auto-numbering 30 | 31 | 1.0.27.0: 32 | - Fix second tier reference lookup. 33 | - Show feature mappings. 34 | - Make localization satellites optional. 35 | - Fix #48: EnvDTE.Project.Dte may throw on imcomplete Project implementations. 36 | - Fix #44: outdated document link 37 | 38 | 1.0.26.0: 39 | - Fix #6: null reference exception on project with invalid project references 40 | 41 | 1.0.25.0: 42 | - Fix #6: Another flavor of this issue occured. 43 | - Fix #41: support WiX v4 templates. 44 | 45 | 1.0.24.0: 46 | - Fix #42: EnvDTE.ProjectItem throws null ref exception when item has been removed 47 | 48 | 1.0.23.0: 49 | - Update packages to align with other extensions (to avoid https://connect.microsoft.com/VisualStudio/feedback/details/2993889/) 50 | 51 | 1.0.22.0: 52 | - Fix undefined behavior when not all projects have been built yet and project output is not available. 53 | 54 | 1.0.21.0: 55 | - Added french translation 56 | - Make second tier sattelite dlls optional. 57 | 58 | 1.0.20.0: 59 | - #13, #21: Corectly handle output folder for non-default output locations, e.g. WebApi projects. 60 | - #15, #26, #29: Include second tier references and their sattelite dlls. 61 | - #17, #20: Improve readablity in dark theme. 62 | - #19: Prefix names of directories if they match well known property names to avoid conflicts. 63 | - #23: Generated ComponentGroupRef nodes sometimes do not appear. 64 | - Added Chinese and German translation. 65 | 66 | 1.0.19.0: 67 | - Fix #11: Handle deep directory structures 68 | - Fix #12: Allow DirectoryNodes without name to support merge modules with the "MergeRedirectFolder" 69 | - Format XML when adding new nodes. 70 | 71 | - 1.0.18.0: 72 | - Fix #10: VSIX installer problem with .net framework version 73 | 74 | 1.0.17.0: 75 | - Fix #9: VS does not always reliably detect the installation of Wix 76 | 77 | 1.0.16.0: 78 | - Support VS15 RC 79 | 80 | 1.0.15.0: 81 | - Fix #6: Possible null ref exception when a project has no valid references 82 | 83 | 1.0.14.0 84 | - Fix #3: No vertical scrollbar in directory pane. 85 | 86 | 1.0.13.0 87 | - Fix http://waxsetupeditor.codeplex.com/workitem/4692 88 | 89 | 1.0.12.0 90 | - Support VS15 91 | 92 | 1.0.11.0 93 | - WI4692: Somtimes VS throws exceptions when enumerating project references. 94 | - Exclude project references that are not marked as copy local 95 | 96 | 1.0.10.0 97 | - WI4656: Visual Studio crashes on GetSubProjects 98 | - WI4662: Path comparison should not be case sensitive 99 | 100 | 1.0.9.0 101 | - WI4655: Format XML 102 | 103 | 1.0.8.0 104 | - Fix missing XML-declaration element in .wxs file. 105 | - New icon, internal fixes. 106 | 107 | 1.0.7.0 108 | - WI4586: Suggestion for better file source determination. => Projects references are added and project reference variables used to locate the files. 109 | - WI4599: Sub folders not copied => Wax now creates the ComponentGroupRef entries. 110 | 111 | 1.0.6.0 112 | - Generate default defines for project output folders. 113 | - Improve UX in project lists. 114 | 115 | 1.0.5.0 116 | - WI1408: Fix missing scrollbar issue. 117 | - Add documentation. 118 | 119 | 1.0.4.0 120 | - Improve UI 121 | - Unmapped items can be removed 122 | 123 | 1.0.3.0 124 | - Id's may not contain characters except letters, digits, '.', '_' 125 | 126 | 1.0.2.0 127 | - Deployment of symbols optional per project. 128 | - Show unmapped files. 129 | - Sort projects by folder/name. 130 | 131 | 1.0.1.0 132 | - Make it compile and run with VS2010 too. -------------------------------------------------------------------------------- /Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-englert/Wax/57dd8d47972869363733e66712cf813d3efec7d8/Screenshot.png -------------------------------------------------------------------------------- /Wax.Model/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Wax.Model/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Used to control if the On_PropertyName_Changed feature is enabled. 12 | 13 | 14 | 15 | 16 | Used to control if the Dependent properties feature is enabled. 17 | 18 | 19 | 20 | 21 | Used to control if the IsChanged property feature is enabled. 22 | 23 | 24 | 25 | 26 | Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form. 27 | 28 | 29 | 30 | 31 | Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project. 32 | 33 | 34 | 35 | 36 | Used to control if equality checks should use the Equals method resolved from the base class. 37 | 38 | 39 | 40 | 41 | Used to control if equality checks should use the static Equals method resolved from the base class. 42 | 43 | 44 | 45 | 46 | Used to turn off build warnings from this weaver. 47 | 48 | 49 | 50 | 51 | Used to turn off build warnings about mismatched On_PropertyName_Changed methods. 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 62 | 63 | 64 | 65 | 66 | A comma-separated list of error codes that can be safely ignored in assembly verification. 67 | 68 | 69 | 70 | 71 | 'false' to turn off automatic generation of the XML Schema file. 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /Wax.Model/Mapping/DirectoryMapping.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.Mapping 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Windows.Input; 6 | 7 | using PropertyChanged; 8 | 9 | using tomenglertde.Wax.Model.Wix; 10 | 11 | using TomsToolbox.Wpf; 12 | 13 | [AddINotifyPropertyChangedInterface] 14 | public class DirectoryMapping 15 | { 16 | private readonly WixProject _wixProject; 17 | 18 | public DirectoryMapping(string directory, WixProject wixProject, IList unmappedNodes) 19 | { 20 | Directory = directory; 21 | _wixProject = wixProject; 22 | Id = wixProject.GetDirectoryId(directory); 23 | UnmappedNodes = unmappedNodes; 24 | 25 | MappedNode = wixProject.DirectoryNodes.FirstOrDefault(node => node.Id == Id); 26 | } 27 | 28 | public string Directory { get; } 29 | 30 | public string Id { get; } 31 | 32 | public IList UnmappedNodes { get; } 33 | 34 | public ICommand AddDirectoryCommand => new DelegateCommand(CanAddDirectory, AddDirectory); 35 | 36 | public ICommand ClearMappingCommand => new DelegateCommand(CanClearMapping, ClearMapping); 37 | 38 | public WixDirectoryNode? MappedNodeSetter 39 | { 40 | get => null; 41 | set 42 | { 43 | if (value != null) 44 | { 45 | MappedNode = value; 46 | } 47 | } 48 | } 49 | 50 | [OnChangedMethod(nameof(OnMappedNodeChanged))] 51 | public WixDirectoryNode? MappedNode { get; set; } 52 | 53 | private void OnMappedNodeChanged(WixDirectoryNode? oldValue, WixDirectoryNode? newValue) 54 | { 55 | if (oldValue != null) 56 | { 57 | UnmappedNodes.Add(oldValue); 58 | _wixProject.UnmapDirectory(Directory); 59 | } 60 | 61 | if (newValue != null) 62 | { 63 | UnmappedNodes.Remove(newValue); 64 | _wixProject.MapDirectory(Directory, newValue); 65 | } 66 | } 67 | 68 | private void AddDirectory() 69 | { 70 | MappedNode = _wixProject.AddDirectoryNode(Directory); 71 | } 72 | 73 | private bool CanAddDirectory() 74 | { 75 | return (MappedNode == null); 76 | } 77 | 78 | private void ClearMapping() 79 | { 80 | MappedNode = null; 81 | } 82 | 83 | private bool CanClearMapping() 84 | { 85 | return (MappedNode != null) && (!_wixProject.HasDefaultDirectoryId(this)); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /Wax.Model/Mapping/FeatureMapping.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.Mapping 2 | { 3 | using System.Collections.Generic; 4 | 5 | using tomenglertde.Wax.Model.VisualStudio; 6 | using tomenglertde.Wax.Model.Wix; 7 | 8 | public class FeatureMapping 9 | { 10 | public FeatureMapping(WixFeatureNode featureNode, ICollection installedFiles, 11 | ICollection projects, ICollection requiredProjectOutputs, ICollection missingProjectOutputs) 12 | { 13 | FeatureNode = featureNode; 14 | InstalledFiles = installedFiles; 15 | Projects = projects; 16 | RequiredProjectOutputs = requiredProjectOutputs; 17 | MissingProjectOutputs = missingProjectOutputs; 18 | } 19 | 20 | public WixFeatureNode FeatureNode { get; } 21 | 22 | public ICollection InstalledFiles { get; } 23 | 24 | public ICollection Projects { get; } 25 | 26 | public ICollection RequiredProjectOutputs { get; } 27 | 28 | public ICollection MissingProjectOutputs { get; } 29 | 30 | public FeatureMapping? Parent { get; set; } 31 | 32 | public ICollection Children { get; } = new List(); 33 | } 34 | } -------------------------------------------------------------------------------- /Wax.Model/Mapping/FileMapping.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.Mapping 2 | { 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Collections.ObjectModel; 7 | using System.Collections.Specialized; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Windows.Input; 11 | 12 | using PropertyChanged; 13 | 14 | using tomenglertde.Wax.Model.VisualStudio; 15 | using tomenglertde.Wax.Model.Wix; 16 | 17 | using TomsToolbox.ObservableCollections; 18 | using TomsToolbox.Wpf; 19 | 20 | [AddINotifyPropertyChangedInterface] 21 | public class FileMapping 22 | { 23 | private readonly ProjectOutputGroup _projectOutputGroup; 24 | private readonly ObservableCollection _allUnmappedProjectOutputs; 25 | private readonly ObservableFilteredCollection _unmappedProjectOutputs; 26 | private readonly WixProject _wixProject; 27 | private readonly IList _allUnmappedFiles; 28 | private readonly ObservableFilteredCollection _unmappedFiles; 29 | 30 | public FileMapping(ProjectOutputGroup projectOutputGroup, ObservableCollection allUnmappedProjectOutputs, WixProject wixProject, IList allUnmappedFiles) 31 | { 32 | _projectOutputGroup = projectOutputGroup; 33 | _allUnmappedProjectOutputs = allUnmappedProjectOutputs; 34 | _wixProject = wixProject; 35 | _allUnmappedFiles = allUnmappedFiles; 36 | 37 | Id = wixProject.GetFileId(TargetName); 38 | 39 | MappedNode = wixProject.FileNodes.FirstOrDefault(node => node.Id == Id); 40 | 41 | _unmappedProjectOutputs = new ObservableFilteredCollection(_allUnmappedProjectOutputs, item => string.Equals(item?.FileName, DisplayName, StringComparison.OrdinalIgnoreCase)); 42 | _unmappedProjectOutputs.CollectionChanged += UnmappedProjectOutputs_CollectionChanged; 43 | 44 | _unmappedFiles = new ObservableFilteredCollection(allUnmappedFiles, item => string.Equals(item?.Node.Name, DisplayName, StringComparison.OrdinalIgnoreCase)); 45 | _unmappedFiles.CollectionChanged += UnmappedNodes_CollectionChanged; 46 | 47 | UpdateMappingState(); 48 | } 49 | 50 | public string DisplayName => _projectOutputGroup.FileName; 51 | 52 | public string Id { get; } 53 | 54 | public string UniqueName => _projectOutputGroup.TargetName; 55 | 56 | public string Extension => Path.GetExtension(_projectOutputGroup.TargetName); 57 | 58 | public string TargetName => _projectOutputGroup.TargetName; 59 | 60 | public string SourceName => _projectOutputGroup.SourceName; 61 | 62 | public IList UnmappedNodes => _unmappedFiles; 63 | 64 | [DoNotNotify] 65 | public ICommand AddFileCommand => new DelegateCommand(_ => CanAddFile(), AddFile); 66 | 67 | [DoNotNotify] 68 | public ICommand ClearMappingCommand => new DelegateCommand(_ => CanClearMapping(), ClearMapping); 69 | 70 | [DoNotNotify] 71 | public ICommand ResolveFileCommand => new DelegateCommand(_ => CanResolveFile(), ResolveFile); 72 | 73 | public Project Project => 74 | _projectOutputGroup.ProjectOutputs 75 | .Select(output => output.Project) 76 | .SortByRelevance() 77 | .First(); 78 | 79 | public Project TopLevelProject 80 | { 81 | get 82 | { 83 | var project = Project; 84 | while (true) 85 | { 86 | var referencedBy = project.ImplicitSelectedByProjects.SortByRelevance().FirstOrDefault(); 87 | if (referencedBy == null) 88 | return project; 89 | project = referencedBy; 90 | } 91 | } 92 | } 93 | 94 | public WixFileNode? MappedNodeSetter 95 | { 96 | get => null; 97 | set 98 | { 99 | if (value != null) 100 | { 101 | MappedNode = value; 102 | } 103 | } 104 | } 105 | 106 | [OnChangedMethod(nameof(OnMappedNodeChanged))] 107 | public WixFileNode? MappedNode { get; set; } 108 | 109 | private void OnMappedNodeChanged(WixFileNode? oldValue, WixFileNode? newValue) 110 | { 111 | if (oldValue != null) 112 | { 113 | _allUnmappedFiles.Add(new UnmappedFile(oldValue, _allUnmappedFiles)); 114 | _wixProject.UnmapFile(TargetName); 115 | _allUnmappedProjectOutputs.Add(_projectOutputGroup); 116 | } 117 | 118 | if (newValue != null) 119 | { 120 | var unmappedFile = _allUnmappedFiles.FirstOrDefault(file => Equals(file.Node, newValue)); 121 | _allUnmappedFiles.Remove(unmappedFile); 122 | _wixProject.MapFile(TargetName, newValue); 123 | _allUnmappedProjectOutputs.Remove(_projectOutputGroup); 124 | } 125 | 126 | UpdateMappingState(); 127 | } 128 | 129 | public MappingState MappingState { get; set; } 130 | 131 | private void UnmappedNodes_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 132 | { 133 | UpdateMappingState(); 134 | } 135 | 136 | private void UnmappedProjectOutputs_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 137 | { 138 | UpdateMappingState(); 139 | } 140 | 141 | private bool CanAddFile() 142 | { 143 | return (MappedNode == null) && !_unmappedFiles.Any(); 144 | } 145 | 146 | private static void AddFile(IEnumerable selectedItems) 147 | { 148 | selectedItems.Cast().ToList().ForEach(fileMapping => fileMapping.AddFile()); 149 | } 150 | 151 | private void AddFile() 152 | { 153 | if (CanAddFile()) 154 | { 155 | MappedNode = _wixProject.AddFileNode(this); 156 | } 157 | } 158 | 159 | private bool CanClearMapping() 160 | { 161 | return (MappedNode != null) && (!_wixProject.HasDefaultFileId(this)); 162 | } 163 | 164 | private static void ClearMapping(IEnumerable selectedItems) 165 | { 166 | selectedItems.Cast().ToList().ForEach(fileMapping => fileMapping.ClearMapping()); 167 | } 168 | 169 | private void ClearMapping() 170 | { 171 | if (CanClearMapping()) 172 | { 173 | MappedNode = null; 174 | } 175 | } 176 | 177 | private bool CanResolveFile() 178 | { 179 | return (MappedNode == null) && (_unmappedFiles.Count == 1); 180 | } 181 | 182 | private static void ResolveFile(IEnumerable selectedItems) 183 | { 184 | selectedItems.Cast().ToList().ForEach(fileMapping => fileMapping.ResolveFile()); 185 | } 186 | 187 | private void ResolveFile() 188 | { 189 | if (CanResolveFile()) 190 | { 191 | MappedNode = _unmappedFiles[0]; 192 | } 193 | } 194 | 195 | private void UpdateMappingState() 196 | { 197 | if (MappedNode != null) 198 | { 199 | MappingState = MappingState.Resolved; 200 | return; 201 | } 202 | 203 | switch (_unmappedFiles.Count) 204 | { 205 | case 0: 206 | MappingState = MappingState.Unmapped; 207 | return; 208 | 209 | case 1: 210 | if (_unmappedProjectOutputs.Count == 1) 211 | { 212 | MappingState = MappingState.Unique; 213 | return; 214 | } 215 | break; 216 | } 217 | 218 | MappingState = MappingState.Ambiguous; 219 | } 220 | } 221 | } -------------------------------------------------------------------------------- /Wax.Model/Mapping/MappingState.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.Mapping 2 | { 3 | public enum MappingState 4 | { 5 | Unmapped, 6 | Ambiguous, 7 | Unique, 8 | Resolved 9 | } 10 | } -------------------------------------------------------------------------------- /Wax.Model/Mapping/UnmappedFile.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.Mapping 2 | { 3 | using System.Collections.Generic; 4 | using System.Windows.Input; 5 | 6 | using tomenglertde.Wax.Model.Wix; 7 | 8 | using TomsToolbox.Wpf; 9 | 10 | public class UnmappedFile 11 | { 12 | private readonly IList _allUnmappedFiles; 13 | 14 | public UnmappedFile(WixFileNode node, IList allUnmappedFiles) 15 | { 16 | Node = node; 17 | _allUnmappedFiles = allUnmappedFiles; 18 | } 19 | 20 | public ICommand DeleteCommand => new DelegateCommand(Delete); 21 | 22 | public WixFileNode Node { get; } 23 | 24 | public WixFileNode ToWixFileNode() 25 | { 26 | return Node; 27 | } 28 | 29 | public static implicit operator WixFileNode?(UnmappedFile? file) => file?.Node; 30 | 31 | private void Delete() 32 | { 33 | _allUnmappedFiles.Remove(this); 34 | Node.Remove(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Wax.Model/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Markup; 2 | 3 | [assembly: XmlnsDefinition("urn:wax", "tomenglertde.Wax.Model")] 4 | [assembly: XmlnsDefinition("urn:wax", "tomenglertde.Wax.Model.Mapping")] 5 | [assembly: XmlnsDefinition("urn:wax", "tomenglertde.Wax.Model.Tools")] 6 | [assembly: XmlnsDefinition("urn:wax", "tomenglertde.Wax.Model.VisualStudio")] 7 | [assembly: XmlnsDefinition("urn:wax", "tomenglertde.Wax.Model.Wix")] 8 | -------------------------------------------------------------------------------- /Wax.Model/Tools/AssemblyHelper.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.Tools 2 | { 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Resources; 10 | 11 | using Baml; 12 | 13 | using Mono.Cecil; 14 | 15 | using TomsToolbox.Essentials; 16 | 17 | public static class AssemblyHelper 18 | { 19 | private static readonly Dictionary _referenceCache = new(StringComparer.OrdinalIgnoreCase); 20 | private static readonly Dictionary _directoryCache = new(StringComparer.OrdinalIgnoreCase); 21 | 22 | public static IReadOnlyCollection FindReferences(string target, string outputDirectory) 23 | { 24 | var timeStamp = File.GetLastWriteTime(target); 25 | 26 | if (_referenceCache.TryGetValue(target, out var cacheEntry) && (cacheEntry.TimeStamp == timeStamp)) 27 | { 28 | return cacheEntry.References; 29 | } 30 | 31 | var existingAssemblies = FindExistingAssemblies(outputDirectory); 32 | 33 | var references = FindReferences(target, existingAssemblies); 34 | 35 | _referenceCache[target] = new ReferenceCacheEntry(references, timeStamp); 36 | 37 | return references; 38 | } 39 | 40 | private static Dictionary FindExistingAssemblies(string outputDirectory) 41 | { 42 | var folder = new DirectoryInfo(outputDirectory); 43 | var files = folder.GetFiles("*.dll"); 44 | var hash = files.Select(file => file.LastWriteTime.GetHashCode()).Aggregate(0, HashCode.Aggregate); 45 | 46 | Dictionary existingAssemblies; 47 | 48 | if (_directoryCache.TryGetValue(folder.FullName, out var directoryCacheEntry) && (directoryCacheEntry.Hash == hash)) 49 | { 50 | existingAssemblies = directoryCacheEntry.AssemblyNames; 51 | } 52 | else 53 | { 54 | existingAssemblies = files 55 | .Select(file => file.FullName) 56 | .Select(TryGetAssemblyName) 57 | .ExceptNullItems() 58 | .ToDictionary(assemblyName => assemblyName.Name); 59 | 60 | _directoryCache[folder.FullName] = new DirectoryCacheEntry(existingAssemblies, hash); 61 | } 62 | 63 | return existingAssemblies; 64 | } 65 | 66 | private static AssemblyName? TryGetAssemblyName(string assemblyFile) 67 | { 68 | try 69 | { 70 | return AssemblyName.GetAssemblyName(assemblyFile); 71 | } 72 | catch 73 | { 74 | return null; 75 | } 76 | } 77 | 78 | private static IReadOnlyCollection FindReferences(string target, IDictionary existingAssemblies) 79 | { 80 | try 81 | { 82 | var assembly = ModuleDefinition.ReadModule(target); 83 | 84 | var usedAssemblies = FindXamlReferences(existingAssemblies, assembly); 85 | 86 | var referencedAssemblyNames = assembly.AssemblyReferences 87 | .Select(item => item?.Name) 88 | .ExceptNullItems() 89 | .Select(existingAssemblies.GetValueOrDefault) 90 | .ExceptNullItems(); 91 | 92 | usedAssemblies.AddRange(referencedAssemblyNames); 93 | 94 | return usedAssemblies.ToList().AsReadOnly(); 95 | } 96 | catch 97 | { 98 | return Array.Empty(); 99 | } 100 | } 101 | 102 | private static HashSet FindXamlReferences(IDictionary existingAssemblies, ModuleDefinition assembly) 103 | { 104 | var assemblyResources = assembly.Resources?.OfType().FirstOrDefault(res => res.Name?.EndsWith("g.resources", StringComparison.Ordinal) == true); 105 | 106 | var usedAssemblies = new HashSet(); 107 | 108 | if (assemblyResources == null) 109 | return usedAssemblies; 110 | 111 | var resourceStream = assemblyResources.GetResourceStream(); 112 | 113 | if (resourceStream == null) 114 | return usedAssemblies; 115 | 116 | using (var resourceReader = new ResourceReader(resourceStream)) 117 | { 118 | foreach (DictionaryEntry entry in resourceReader) 119 | { 120 | if ((entry.Key as string)?.EndsWith(".baml", StringComparison.Ordinal) != true) 121 | continue; 122 | 123 | if (entry.Value is not Stream bamlStream) 124 | continue; 125 | 126 | var records = Baml.ReadDocument(bamlStream); 127 | 128 | foreach (var name in records.OfType().Select(ai => new AssemblyName(ai.AssemblyFullName))) 129 | { 130 | if (existingAssemblies.TryGetValue(name.Name, out var assemblyName) && ((name.Version == null) || (name.Version <= assemblyName.Version))) 131 | { 132 | usedAssemblies.Add(assemblyName); 133 | } 134 | } 135 | } 136 | } 137 | 138 | return usedAssemblies; 139 | } 140 | 141 | private class ReferenceCacheEntry 142 | { 143 | public ReferenceCacheEntry(IReadOnlyCollection references, DateTime timeStamp) 144 | { 145 | References = references; 146 | TimeStamp = timeStamp; 147 | } 148 | 149 | public IReadOnlyCollection References { get; } 150 | 151 | public DateTime TimeStamp { get; } 152 | } 153 | 154 | private class DirectoryCacheEntry 155 | { 156 | public DirectoryCacheEntry(Dictionary assemblyNames, int hash) 157 | { 158 | AssemblyNames = assemblyNames; 159 | Hash = hash; 160 | } 161 | 162 | public Dictionary AssemblyNames { get; } 163 | 164 | public int Hash { get; } 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /Wax.Model/Tools/SerializerExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.Tools 2 | { 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Text; 6 | using System.Xml; 7 | using System.Xml.Serialization; 8 | 9 | public static class SerializerExtensions 10 | { 11 | public static T Deserialize(this string? data) where T : class, new() 12 | { 13 | if (string.IsNullOrEmpty(data)) 14 | return new T(); 15 | 16 | var serializer = new XmlSerializer(typeof(T)); 17 | 18 | try 19 | { 20 | var xmlReader = new XmlTextReader(new StringReader(data)); 21 | 22 | if (serializer.CanDeserialize(xmlReader)) 23 | return (serializer.Deserialize(xmlReader) as T) ?? new T(); 24 | } 25 | catch 26 | { 27 | // file is corrupt 28 | } 29 | 30 | return new T(); 31 | } 32 | 33 | public static string Serialize(this T value) 34 | { 35 | var result = new StringBuilder(); 36 | 37 | var serializer = new XmlSerializer(typeof(T)); 38 | 39 | using (var stringWriter = new StringWriter(result, CultureInfo.InvariantCulture)) 40 | { 41 | serializer.Serialize(stringWriter, value); 42 | } 43 | 44 | return result.ToString(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Wax.Model/Tools/XmlExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.Tools 2 | { 3 | using System.Linq; 4 | using System.Xml; 5 | using System.Xml.Linq; 6 | 7 | public static class XmlExtensions 8 | { 9 | public static void RemoveSelfAndWhiteSpace(this XElement element) 10 | { 11 | if ((element.PreviousNode is XText previous) && string.IsNullOrWhiteSpace(previous.Value)) 12 | { 13 | previous.Remove(); 14 | } 15 | 16 | element.Remove(); 17 | } 18 | 19 | public static void AddWithFormatting(this XElement parent, XElement item) 20 | { 21 | var firstNode = parent.FirstNode; 22 | var lastNode = parent.LastNode; 23 | 24 | if ((firstNode?.NodeType == XmlNodeType.Text) && (lastNode != null)) 25 | { 26 | var whiteSpace = "\n" + ((firstNode as XText)?.Value?.Split('\n').LastOrDefault() ?? new string(' ', lastNode.GetDefaultIndent())); 27 | 28 | lastNode.AddBeforeSelf(new XText(whiteSpace), item); 29 | } 30 | else 31 | { 32 | var previousNode = parent.PreviousNode; 33 | 34 | var whiteSpace = "\n" + ((previousNode as XText)?.Value?.Split('\n').LastOrDefault() ?? new string(' ', parent.GetDefaultIndent())); 35 | 36 | parent.Add(new XText(whiteSpace + " "), item, new XText(whiteSpace)); 37 | } 38 | } 39 | 40 | private static int GetDefaultIndent(this XObject item) 41 | { 42 | return item.Parent?.GetDefaultIndent() ?? 0 + 2; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Wax.Model/VisualStudio/BuildFileGroups.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable UnusedMember.Global 2 | namespace tomenglertde.Wax.Model.VisualStudio 3 | { 4 | using System; 5 | 6 | [Flags] 7 | public enum BuildFileGroups 8 | { 9 | None = 0, 10 | LocalizedResourceDlls = 1, 11 | XmlSerializer = 2, 12 | ContentFiles = 4, 13 | Built = 8, 14 | SourceFiles = 16, 15 | Symbols = 32, 16 | Documentation = 64 17 | } 18 | } -------------------------------------------------------------------------------- /Wax.Model/VisualStudio/DteExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.VisualStudio 2 | { 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Runtime.InteropServices; 10 | using System.Xml.Linq; 11 | 12 | using Microsoft.VisualStudio.Shell.Flavor; 13 | using Microsoft.VisualStudio.Shell.Interop; 14 | 15 | using IServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider; 16 | 17 | internal static class DteExtensions 18 | { 19 | /// 20 | /// Gets all projects in the solution. 21 | /// 22 | /// The solution. 23 | /// The projects. 24 | public static IReadOnlyCollection GetProjects(this EnvDTE.Solution solution) 25 | { 26 | var items = new List(); 27 | 28 | var projects = solution.Projects; 29 | 30 | if (projects == null) 31 | return items; 32 | 33 | for (var i = 1; i <= projects.Count; i++) 34 | { 35 | try 36 | { 37 | var project = projects.Item(i); 38 | if (project == null) 39 | continue; 40 | 41 | items.Add(project); 42 | 43 | project.ProjectItems.GetSubProjects(items); 44 | } 45 | catch 46 | { 47 | Trace.TraceError("Error loading project #" + i); 48 | } 49 | } 50 | 51 | return items; 52 | } 53 | 54 | private static void GetSubProjects(this IEnumerable? projectItems, ICollection items) 55 | { 56 | if (projectItems == null) 57 | return; 58 | 59 | foreach (var projectItem in projectItems.OfType()) 60 | { 61 | projectItem.GetSubProjects(items); 62 | } 63 | } 64 | 65 | private static void GetSubProjects(this EnvDTE.ProjectItem? projectItem, ICollection items) 66 | { 67 | var subProject = projectItem?.SubProject; 68 | 69 | if (subProject == null) 70 | return; 71 | 72 | items.Add(subProject); 73 | 74 | subProject.ProjectItems.GetSubProjects(items); 75 | } 76 | 77 | public static IEnumerable EnumerateAllProjectItems(this EnvDTE.Project project) 78 | { 79 | if (project.ProjectItems == null) 80 | yield break; 81 | 82 | foreach (var item in project.ProjectItems.OfType()) 83 | { 84 | yield return item; 85 | 86 | foreach (var subItem in EnumerateProjectItems(item)) 87 | { 88 | yield return subItem; 89 | } 90 | } 91 | } 92 | 93 | private static IEnumerable EnumerateProjectItems(EnvDTE.ProjectItem projectItem) 94 | { 95 | if (projectItem.ProjectItems == null) 96 | yield break; 97 | 98 | foreach (var item in projectItem.ProjectItems.OfType()) 99 | { 100 | yield return item; 101 | 102 | foreach (var subItem in EnumerateProjectItems(item)) 103 | { 104 | yield return subItem; 105 | } 106 | } 107 | } 108 | 109 | public static string GetProjectTypeGuids(this EnvDTE.Project proj) 110 | { 111 | try 112 | { 113 | var dte = proj.TryGetDte(); 114 | if (dte == null) 115 | return string.Empty; 116 | 117 | var solution = GetService(dte); 118 | 119 | if (solution == null) 120 | return string.Empty; 121 | 122 | var result = solution.GetProjectOfUniqueName(proj.UniqueName, out IVsHierarchy hierarchy); 123 | 124 | if (result == 0) 125 | { 126 | if (hierarchy is IVsAggregatableProjectCorrected aggregatableProject) 127 | { 128 | result = aggregatableProject.GetAggregateProjectTypeGuids(out string projectTypeGuids); 129 | 130 | if ((result == 0) && (projectTypeGuids != null)) 131 | return projectTypeGuids; 132 | 133 | } 134 | } 135 | } 136 | catch 137 | { 138 | // internal error 139 | } 140 | 141 | return string.Empty; 142 | } 143 | 144 | private static T? GetService(object serviceProvider) where T : class 145 | { 146 | return (T?)GetService((IServiceProvider)serviceProvider, typeof(T).GUID); 147 | } 148 | 149 | private static object? GetService(IServiceProvider serviceProvider, Guid guid) 150 | { 151 | var hr = serviceProvider.QueryService(guid, guid, out var serviceHandle); 152 | 153 | if (hr != 0) 154 | { 155 | Marshal.ThrowExceptionForHR(hr); 156 | } 157 | else if (!serviceHandle.Equals(IntPtr.Zero)) 158 | { 159 | var service = Marshal.GetObjectForIUnknown(serviceHandle); 160 | Marshal.Release(serviceHandle); 161 | return service; 162 | } 163 | 164 | return null; 165 | } 166 | 167 | public static string? TryGetFileName(this EnvDTE.ProjectItem projectItem) 168 | { 169 | try 170 | { 171 | // some items report a file count > 0 but don't return a file name! 172 | if (projectItem.FileCount > 0) 173 | { 174 | return projectItem.FileNames[0]; 175 | } 176 | } 177 | catch (Exception) 178 | { 179 | // s.a. 180 | } 181 | 182 | return null; 183 | } 184 | 185 | public static XDocument GetXmlContent(this EnvDTE.ProjectItem projectItem, LoadOptions loadOptions) 186 | { 187 | return XDocument.Parse(projectItem.GetContent(), loadOptions); 188 | } 189 | 190 | public static string GetContent(this EnvDTE.ProjectItem projectItem) 191 | { 192 | try 193 | { 194 | if (!projectItem.IsOpen) 195 | projectItem.Open(); 196 | 197 | var document = projectItem.Document; 198 | 199 | if (document != null) 200 | { 201 | return GetContent((EnvDTE.TextDocument)document.Object("TextDocument")); 202 | } 203 | 204 | var fileName = projectItem.TryGetFileName(); 205 | 206 | if (string.IsNullOrEmpty(fileName)) 207 | return string.Empty; 208 | 209 | return File.ReadAllText(fileName); 210 | } 211 | catch (Exception) 212 | { 213 | return string.Empty; 214 | } 215 | } 216 | 217 | private static string GetContent(EnvDTE.TextDocument document) 218 | { 219 | return document.StartPoint.CreateEditPoint().GetText(document.EndPoint); 220 | } 221 | 222 | public static void SetContent(this EnvDTE.ProjectItem projectItem, string text) 223 | { 224 | if (!projectItem.IsOpen) 225 | projectItem.Open(EnvDTE.Constants.vsViewKindCode); 226 | 227 | var document = projectItem.Document; 228 | 229 | if (document != null) 230 | { 231 | SetContent(document, text); 232 | } 233 | else 234 | { 235 | var fileName = projectItem.TryGetFileName(); 236 | 237 | if (string.IsNullOrEmpty(fileName)) 238 | return; 239 | 240 | File.WriteAllText(fileName, text); 241 | } 242 | } 243 | 244 | private static void SetContent(EnvDTE.Document document, string? text) 245 | { 246 | var textDocument = (EnvDTE.TextDocument)document.Object("TextDocument"); 247 | 248 | textDocument.StartPoint.CreateEditPoint().ReplaceText(textDocument.EndPoint, text, 0); 249 | } 250 | 251 | public static object? TryGetObject(this EnvDTE.Project? project) 252 | { 253 | try 254 | { 255 | return project?.Object; 256 | } 257 | catch 258 | { 259 | return null; 260 | } 261 | } 262 | 263 | public static EnvDTE80.DTE2? TryGetDte(this EnvDTE.Project? project) 264 | { 265 | try 266 | { 267 | return project?.DTE as EnvDTE80.DTE2; 268 | } 269 | catch 270 | { 271 | return null; 272 | } 273 | } 274 | 275 | public static bool GetCopyLocal(this VSLangProj.Reference? reference) 276 | { 277 | if (reference == null) 278 | return false; 279 | 280 | try 281 | { 282 | return reference.CopyLocal || (reference.ContainingProject != null); 283 | } 284 | catch 285 | { 286 | return false; 287 | } 288 | } 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /Wax.Model/VisualStudio/ProjectOutput.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.VisualStudio 2 | { 3 | using System; 4 | using System.IO; 5 | 6 | public class ProjectOutput 7 | { 8 | public ProjectOutput(Project project, string relativeFileName, BuildFileGroups buildFileGroup, string binaryTargetDirectory) 9 | { 10 | Project = project; 11 | 12 | var prefix = binaryTargetDirectory + @"\"; 13 | 14 | if ((buildFileGroup != BuildFileGroups.ContentFiles) && relativeFileName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) 15 | { 16 | SourceName = relativeFileName.Substring(prefix.Length); 17 | } 18 | else 19 | { 20 | SourceName = relativeFileName; 21 | } 22 | 23 | BuildFileGroup = buildFileGroup; 24 | 25 | TargetName = (BuildFileGroup == BuildFileGroups.ContentFiles) ? SourceName : Path.Combine(binaryTargetDirectory, SourceName); 26 | } 27 | 28 | public ProjectOutput(Project project, string relativeFileName, string binaryTargetDirectory) 29 | { 30 | Project = project; 31 | SourceName = relativeFileName; 32 | TargetName = Path.Combine(binaryTargetDirectory, relativeFileName); 33 | } 34 | 35 | public ProjectOutput(Project project, VSLangProj.Reference reference, string binaryTargetDirectory) 36 | : this(project, Path.GetFileName(reference.Path), binaryTargetDirectory) 37 | { 38 | } 39 | 40 | public string SourceName { get; } 41 | 42 | public string TargetName { get; } 43 | 44 | public Project Project { get; } 45 | 46 | public bool IsReference => BuildFileGroup == BuildFileGroups.None; 47 | 48 | public BuildFileGroups BuildFileGroup { get; } 49 | 50 | public override string ToString() 51 | { 52 | return TargetName; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Wax.Model/VisualStudio/ProjectOutputGroup.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.VisualStudio 2 | { 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | public class ProjectOutputGroup 8 | { 9 | public ProjectOutputGroup(string targetName, IReadOnlyCollection projectOutputs) 10 | { 11 | TargetName = targetName; 12 | ProjectOutputs = projectOutputs; 13 | Projects = new HashSet(projectOutputs.Select(p => p.Project)); 14 | } 15 | 16 | public IReadOnlyCollection ProjectOutputs { get; } 17 | 18 | public ICollection Projects { get; } 19 | 20 | public string TargetName { get; } 21 | 22 | public string FileName => Path.GetFileName(TargetName); 23 | 24 | public string SourceName => ProjectOutputs.First().SourceName; 25 | 26 | public override string ToString() 27 | { 28 | return TargetName; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Wax.Model/VisualStudio/ProjectReference.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.VisualStudio 2 | { 3 | using System; 4 | using System.Linq; 5 | 6 | using Equatable; 7 | 8 | [ImplementsEquatable] 9 | public class ProjectReference 10 | { 11 | private readonly Solution _solution; 12 | private readonly VSLangProj.Reference _reference; 13 | 14 | public ProjectReference(Solution solution, VSLangProj.Reference reference) 15 | { 16 | _solution = solution; 17 | _reference = reference; 18 | } 19 | 20 | public Project? SourceProject => _solution.Projects.SingleOrDefault(p => string.Equals(p.UniqueName, _reference.SourceProject?.UniqueName, StringComparison.OrdinalIgnoreCase)); 21 | 22 | [Equals] 23 | public string? Identity => _reference.Identity; 24 | } 25 | } -------------------------------------------------------------------------------- /Wax.Model/VisualStudio/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.VisualStudio 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | using tomenglertde.Wax.Model.Wix; 8 | 9 | public class Solution 10 | { 11 | private readonly EnvDTE.Solution _solution; 12 | 13 | public Solution(EnvDTE.Solution solution) 14 | { 15 | _solution = solution; 16 | 17 | Projects = _solution.GetProjects() 18 | .Select(project => new Project(this, project)) 19 | .Where(project => project.IsVsProject) 20 | .OrderBy(project => project.Name) 21 | .ToList() 22 | .AsReadOnly(); 23 | 24 | foreach (var project in Projects) 25 | { 26 | if (project == null) 27 | continue; 28 | 29 | foreach (var dependency in project.GetProjectReferences()) 30 | { 31 | dependency.SourceProject?.ReferencedBy.Add(project); 32 | } 33 | } 34 | 35 | // Microsoft.Tools.WindowsInstallerXml.VisualStudio.OAWixProject 36 | WixProjects = _solution.GetProjects() 37 | .Where(project => "{930c7802-8a8c-48f9-8165-68863bccd9dd}".Equals(project.Kind, StringComparison.OrdinalIgnoreCase)) 38 | .Select(project => new WixProject(this, project)) 39 | .Where(project => !project.IsBootstrapper) 40 | .OrderBy(project => project.Name) 41 | .ToList() 42 | .AsReadOnly(); 43 | } 44 | 45 | public string? FullName => _solution.FullName; 46 | 47 | public IEnumerable Projects { get; } 48 | 49 | public IEnumerable WixProjects { get; } 50 | 51 | public IEnumerable EnumerateTopLevelProjects => Projects 52 | .Where(project => project.IsTopLevelProject) 53 | .OrderBy(project => project.Name); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Wax.Model/Wax.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | net472 5 | tomenglertde.Wax.Model 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Tools\Baml.cs 14 | 15 | 16 | 17 | 18 | 1.9.0 19 | all 20 | 21 | 22 | 6.6.0 23 | all 24 | 25 | 26 | 2021.3.0 27 | all 28 | 29 | 30 | 1.10.0 31 | all 32 | 33 | 34 | 35 | 15.0.1 36 | 37 | 38 | 0.11.4 39 | 40 | 41 | all 42 | runtime; build; native; contentfiles; analyzers; buildtransitive 43 | 44 | 45 | 3.4.0 46 | all 47 | 48 | 49 | 50 | 51 | 52 | 2.7.4 53 | 54 | 55 | -------------------------------------------------------------------------------- /Wax.Model/Wix/ProjectConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.Wix 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Xml.Serialization; 7 | 8 | using TomsToolbox.Essentials; 9 | 10 | [Serializable] 11 | [XmlType("Configuration")] 12 | public class ProjectConfiguration 13 | { 14 | private string[]? _deployProjectNames; 15 | 16 | [XmlArray("DeployedProjects")] 17 | public string[] DeployedProjectNames 18 | { 19 | get => _deployProjectNames ?? Array.Empty(); 20 | set => _deployProjectNames = value; 21 | } 22 | 23 | [XmlArray("DirectoryMappings")] 24 | public MappingItem[] DirectoryMappingNames 25 | { 26 | get => DirectoryMappings.Select(item => new MappingItem { Key = item.Key, Value = item.Value }).ToArray(); 27 | set => DirectoryMappings = value.ToDictionary(item => item.Key, item => item.Value, StringComparer.OrdinalIgnoreCase); 28 | } 29 | 30 | [XmlArray("FileMappings")] 31 | public MappingItem[] FileMappingNames 32 | { 33 | get => FileMappings.Select(item => new MappingItem { Key = item.Key, Value = item.Value }).ToArray(); 34 | set => FileMappings = value.ToDictionary(item => item.Key, item => item.Value, StringComparer.OrdinalIgnoreCase); 35 | } 36 | 37 | [XmlIgnore] 38 | public Dictionary DirectoryMappings { get; private set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); 39 | 40 | [XmlIgnore] 41 | public Dictionary FileMappings { get; private set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); 42 | 43 | [XmlElement("DeploySymbols")] 44 | public bool DeploySymbols { get; set; } 45 | 46 | [XmlElement("DeployLocalizations")] 47 | public bool DeployLocalizations { get; set; } = true; 48 | 49 | [XmlElement("DeployExternalLocalizations")] 50 | public bool DeployExternalLocalizations { get; set; } 51 | 52 | [XmlElement("ExcludedProjectItems")] 53 | public string ExcludedProjectItemsValue 54 | { 55 | get => ExcludedProjectItems.IsNullOrEmpty() ? "-" : ExcludedProjectItems; 56 | set => ExcludedProjectItems = value == "-" ? null : value; 57 | } 58 | 59 | [XmlIgnore] 60 | public string? ExcludedProjectItems { get; set; } 61 | } 62 | 63 | [Serializable] 64 | [XmlType("Item")] 65 | public class MappingItem 66 | { 67 | [XmlAttribute("Key")] 68 | public string Key 69 | { 70 | get; 71 | set; 72 | } = string.Empty; 73 | 74 | [XmlAttribute("Value")] 75 | public string Value 76 | { 77 | get; 78 | set; 79 | } = string.Empty; 80 | } 81 | } -------------------------------------------------------------------------------- /Wax.Model/Wix/WixComponentGroupNode.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.Wix 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Xml.Linq; 6 | 7 | using tomenglertde.Wax.Model.Mapping; 8 | 9 | using TomsToolbox.Essentials; 10 | 11 | public class WixComponentGroupNode : WixNode 12 | { 13 | public WixComponentGroupNode(WixSourceFile sourceFile, XElement node) 14 | : base(sourceFile, node) 15 | { 16 | } 17 | 18 | public string? Directory => GetAttribute("Directory"); 19 | 20 | public IEnumerable Components => Node 21 | .Descendants(WixNames.ComponentNode) 22 | .Where(node => node.Parent == Node) 23 | .Select(node => node.GetAttribute("Id")) 24 | .Where(id => !string.IsNullOrEmpty(id)) 25 | .ExceptNullItems(); 26 | 27 | public IEnumerable ComponentRefs => Node 28 | .Descendants(WixNames.ComponentRefNode) 29 | .Where(node => node.Parent == Node) 30 | .Select(node => node.GetAttribute("Id")) 31 | .Where(id => !string.IsNullOrEmpty(id)) 32 | .ExceptNullItems(); 33 | 34 | public IEnumerable ComponentGroupRefs => Node 35 | .Descendants(WixNames.ComponentGroupRefNode) 36 | .Where(node => node.Parent == Node) 37 | .Select(node => node.GetAttribute("Id")) 38 | .Where(id => !string.IsNullOrEmpty(id)) 39 | .ExceptNullItems(); 40 | 41 | public WixFileNode AddFileComponent(string id, string name, FileMapping fileMapping) 42 | { 43 | return SourceFile.AddFileComponent(this, id, name, fileMapping); 44 | } 45 | 46 | public IEnumerable EnumerateComponents(IDictionary componentGroupNodes, IDictionary componentNodes) 47 | { 48 | var byComponentGroupRef = ComponentGroupRefs.Select(componentGroupNodes.GetValueOrDefault) 49 | .ExceptNullItems() 50 | .SelectMany(cg => cg.EnumerateComponents(componentGroupNodes, componentNodes)); 51 | 52 | var byComponentRef = ComponentRefs.Select(componentNodes.GetValueOrDefault) 53 | .ExceptNullItems(); 54 | 55 | var children = Components.Select(componentNodes.GetValueOrDefault) 56 | .ExceptNullItems(); 57 | 58 | 59 | return byComponentGroupRef.Concat(byComponentRef).Concat(children); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /Wax.Model/Wix/WixComponentNode.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.Wix 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Xml.Linq; 6 | 7 | using TomsToolbox.Essentials; 8 | 9 | public class WixComponentNode : WixNode 10 | { 11 | public WixComponentNode(WixSourceFile sourceFile, XElement node) 12 | : base(sourceFile, node) 13 | { 14 | } 15 | 16 | public string? Directory => GetAttribute("Directory"); 17 | 18 | public IEnumerable Files => Node 19 | .Descendants(WixNames.FileNode) 20 | .Where(node => node.Parent == Node) 21 | .Select(node => node.GetAttribute("Id")) 22 | .Where(id => !string.IsNullOrEmpty(id)) 23 | .ExceptNullItems(); 24 | 25 | public IEnumerable EnumerateFiles(IDictionary fileNodes) 26 | { 27 | return Files.Select(fileNodes.GetValueOrDefault!).ExceptNullItems(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Wax.Model/Wix/WixDefine.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.Wix 2 | { 3 | using System.Linq; 4 | using System.Xml.Linq; 5 | 6 | using Equatable; 7 | 8 | [ImplementsEquatable] 9 | public class WixDefine 10 | { 11 | public WixDefine(WixSourceFile sourceFile, XProcessingInstruction node) 12 | { 13 | SourceFile = sourceFile; 14 | Node = node; 15 | } 16 | 17 | public string Name => Node.Data.Split('=').Select(item => item.Trim()).FirstOrDefault() ?? ""; 18 | 19 | [Equals] 20 | public XProcessingInstruction Node { get; } 21 | 22 | public WixSourceFile SourceFile { get; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Wax.Model/Wix/WixDirectoryNode.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.Wix 2 | { 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Xml.Linq; 6 | 7 | using tomenglertde.Wax.Model.Tools; 8 | 9 | using TomsToolbox.Essentials; 10 | 11 | public class WixDirectoryNode : WixNode 12 | { 13 | private WixDirectoryNode? _parent; 14 | 15 | public WixDirectoryNode(WixSourceFile sourceFile, XElement node) 16 | : base(sourceFile, node) 17 | { 18 | } 19 | 20 | public WixDirectoryNode? Parent => _parent ??= ResolveParent(); 21 | 22 | public string Path 23 | { 24 | get 25 | { 26 | var name = Name ?? "."; 27 | return Parent != null ? (Parent.Path + @"\" + name) : name; 28 | } 29 | } 30 | 31 | public WixDirectoryNode AddSubDirectory(string id, string name) 32 | { 33 | var directoryElement = new XElement(WixNames.DirectoryNode, new XAttribute("Id", id), new XAttribute("Name", name)); 34 | 35 | Node.AddWithFormatting(directoryElement); 36 | 37 | SourceFile.Save(); 38 | 39 | return SourceFile.AddDirectoryNode(directoryElement); 40 | } 41 | 42 | private WixDirectoryNode? ResolveParent() 43 | { 44 | var parentElement = Node.Parent; 45 | 46 | if (parentElement == null) 47 | return null; 48 | 49 | return parentElement.Name.LocalName switch 50 | { 51 | "Directory" or "DirectoryRef" => SourceFile.Project.DirectoryNodes.FirstOrDefault(node => node.Id == parentElement.GetAttribute("Id")), 52 | _ => null, 53 | }; 54 | } 55 | 56 | public override string ToString() 57 | { 58 | return string.Format(CultureInfo.CurrentCulture, "{0} ({1})", Id, Path); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /Wax.Model/Wix/WixFeatureNode.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.Wix 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Xml.Linq; 6 | 7 | using tomenglertde.Wax.Model.Tools; 8 | 9 | using TomsToolbox.Essentials; 10 | 11 | public class WixFeatureNode : WixNode 12 | { 13 | public WixFeatureNode(WixSourceFile sourceFile, XElement node) 14 | : base(sourceFile, node) 15 | { 16 | } 17 | 18 | public WixFeatureNode? Parent { get; private set; } 19 | 20 | public IEnumerable ComponentGroupRefs => Node 21 | .Descendants(WixNames.ComponentGroupRefNode) 22 | .Where(node => node.Parent == Node) 23 | .Select(node => node.GetAttribute("Id")) 24 | .Where(id => !string.IsNullOrEmpty(id)) 25 | .ExceptNullItems(); 26 | 27 | public IEnumerable ComponentRefs => Node 28 | .Descendants(WixNames.ComponentRefNode) 29 | .Where(node => node.Parent == Node) 30 | .Select(node => node.GetAttribute("Id")) 31 | .Where(id => !string.IsNullOrEmpty(id)) 32 | .ExceptNullItems(); 33 | 34 | public IEnumerable Components => Node 35 | .Descendants(WixNames.ComponentNode) 36 | .Where(node => node.Parent == Node) 37 | .Select(node => node.GetAttribute("Id")) 38 | .Where(id => !string.IsNullOrEmpty(id)) 39 | .ExceptNullItems(); 40 | 41 | public IEnumerable Features => Node 42 | .Descendants(WixNames.FeatureNode) 43 | .Where(node => node.Parent == Node) 44 | .Select(node => node.GetAttribute("Id")) 45 | .Where(id => !string.IsNullOrEmpty(id)) 46 | .ExceptNullItems(); 47 | 48 | public IEnumerable FeatureRefs => Node 49 | .Descendants(WixNames.FeatureRefNode) 50 | .Where(node => node.Parent == Node) 51 | .Select(node => node.GetAttribute("Id")) 52 | .Where(id => !string.IsNullOrEmpty(id)) 53 | .ExceptNullItems(); 54 | 55 | public void AddComponentGroupRef(string id) 56 | { 57 | Node.AddWithFormatting(new XElement(WixNames.ComponentGroupRefNode, new XAttribute("Id", id))); 58 | } 59 | 60 | public void BuildTree(IDictionary allFeatures) 61 | { 62 | var children = Features.Concat(FeatureRefs) 63 | .Select(allFeatures.GetValueOrDefault!) 64 | .ExceptNullItems(); 65 | 66 | foreach (var child in children) 67 | { 68 | child.Parent = this; 69 | } 70 | } 71 | 72 | public IEnumerable EnumerateInstalledFiles(IDictionary componentGroupNodes, IDictionary componentNodes, IDictionary fileNodes) 73 | { 74 | var byComponentGroupRef = ComponentGroupRefs.Select(componentGroupNodes.GetValueOrDefault!) 75 | .ExceptNullItems() 76 | .SelectMany(cg => cg.EnumerateComponents(componentGroupNodes, componentNodes)); 77 | 78 | var byComponentRef = ComponentRefs.Select(componentNodes.GetValueOrDefault!) 79 | .ExceptNullItems(); 80 | 81 | var children = Components.Select(componentNodes.GetValueOrDefault!) 82 | .ExceptNullItems(); 83 | 84 | var files = byComponentGroupRef.Concat(byComponentRef).Concat(children).SelectMany(c => c?.EnumerateFiles(fileNodes)); 85 | 86 | if (Parent != null) 87 | { 88 | files = Parent.EnumerateInstalledFiles(componentGroupNodes, componentNodes, fileNodes).Concat(files); 89 | } 90 | 91 | return files; 92 | } 93 | 94 | public override string ToString() 95 | { 96 | return Id; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Wax.Model/Wix/WixFileNode.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.Wix 2 | { 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | using System.Linq; 6 | using System.Xml.Linq; 7 | 8 | using tomenglertde.Wax.Model.Tools; 9 | 10 | using TomsToolbox.Essentials; 11 | 12 | public class WixFileNode : WixNode 13 | { 14 | private readonly IList _collection; 15 | private WixComponentGroupNode? _componentGroup; 16 | 17 | public WixFileNode(WixSourceFile sourceFile, XElement node, IList collection) 18 | : base(sourceFile, node) 19 | { 20 | _collection = collection; 21 | } 22 | 23 | public string? Source => GetAttribute("Source"); 24 | 25 | public WixComponentGroupNode? ComponentGroup => _componentGroup ??= ResolveComponentGroup(); 26 | 27 | public void Remove() 28 | { 29 | var parentNode = Node.Parent; 30 | 31 | _collection.Remove(this); 32 | 33 | Node.RemoveSelfAndWhiteSpace(); 34 | 35 | if (parentNode != null && (parentNode.Name == WixNames.ComponentNode) && !parentNode.Descendants().Any()) 36 | { 37 | parentNode.Remove(); 38 | } 39 | 40 | SourceFile.Save(); 41 | } 42 | 43 | public override string ToString() 44 | { 45 | return string.Format(CultureInfo.CurrentCulture, "{0} ({1}, {2})", Id, Name, Source); 46 | } 47 | 48 | private WixComponentGroupNode? ResolveComponentGroup() 49 | { 50 | var componentNode = Node.Parent; 51 | 52 | var componentGroupNode = componentNode?.Parent; 53 | 54 | if (componentGroupNode == null) 55 | return null; 56 | 57 | var componentGroups = SourceFile.Project.ComponentGroupNodes; 58 | 59 | if (componentGroupNode.Name == WixNames.ComponentGroupNode) 60 | { 61 | _componentGroup = componentGroups.FirstOrDefault(group => group.Node == componentGroupNode); 62 | } 63 | else if (componentGroupNode.Name == WixNames.ComponentGroupRefNode) 64 | { 65 | _componentGroup = componentGroups.FirstOrDefault(group => group.Id == componentGroupNode.GetAttribute("Id")); 66 | } 67 | 68 | return null; 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /Wax.Model/Wix/WixNames.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.Wix 2 | { 3 | using System.Xml.Linq; 4 | 5 | public class WixNames 6 | { 7 | private readonly string _namespace; 8 | 9 | public WixNames(string @namespace) 10 | { 11 | _namespace = @namespace; 12 | } 13 | 14 | public XName FileNode => XName.Get("File", _namespace); 15 | 16 | public XName DirectoryNode => XName.Get("Directory", _namespace); 17 | 18 | public XName FragmentNode => XName.Get("Fragment", _namespace); 19 | 20 | public XName DirectoryRefNode => XName.Get("DirectoryRef", _namespace); 21 | 22 | public XName ComponentNode => XName.Get("Component", _namespace); 23 | 24 | public XName ComponentGroupNode => XName.Get("ComponentGroup", _namespace); 25 | 26 | public XName ComponentRefNode => XName.Get("ComponentRef", _namespace); 27 | 28 | public XName ComponentGroupRefNode => XName.Get("ComponentGroupRef", _namespace); 29 | 30 | public XName FeatureNode => XName.Get("Feature", _namespace); 31 | 32 | public XName FeatureRefNode => XName.Get("FeatureRef", _namespace); 33 | 34 | public XName PropertyNode => XName.Get("Property", _namespace); 35 | 36 | public XName RegistrySearch => XName.Get("RegistrySearch", _namespace); 37 | 38 | public XName CustomActionRefNode => XName.Get("CustomActionRef", _namespace); 39 | 40 | public const string Define = "define"; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Wax.Model/Wix/WixNode.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.Wix 2 | { 3 | using System.Xml.Linq; 4 | 5 | using Equatable; 6 | 7 | using JetBrains.Annotations; 8 | 9 | using TomsToolbox.Essentials; 10 | 11 | [ImplementsEquatable] 12 | public class WixNode 13 | { 14 | public WixNode(WixSourceFile sourceFile, XElement node) 15 | { 16 | SourceFile = sourceFile; 17 | Node = node; 18 | } 19 | 20 | [Equals] 21 | [UsedImplicitly] 22 | public string Kind => Node.Name.LocalName; 23 | 24 | [Equals] 25 | public string Id => GetAttribute("Id") ?? string.Empty; 26 | 27 | public string? Name => GetAttribute("Name"); 28 | 29 | internal XElement Node { get; } 30 | 31 | public WixSourceFile SourceFile { get; } 32 | 33 | public WixNames WixNames => SourceFile.WixNames; 34 | 35 | protected string? GetAttribute(string name) 36 | { 37 | return Node.GetAttribute(name); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /Wax.Model/Wix/WixSourceFile.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax.Model.Wix 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | using System.Diagnostics; 7 | using System.Globalization; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Xml; 11 | using System.Xml.Linq; 12 | 13 | using tomenglertde.Wax.Model.Mapping; 14 | using tomenglertde.Wax.Model.Tools; 15 | using tomenglertde.Wax.Model.VisualStudio; 16 | 17 | public class WixSourceFile 18 | { 19 | private readonly EnvDTE.ProjectItem _projectItem; 20 | private readonly XDocument _xmlFile; 21 | private XDocument _rawXmlFile; 22 | private readonly XElement _root; 23 | private readonly List _fileNodes; 24 | private readonly List _directoryNodes; 25 | private readonly List _componentGroupNodes; 26 | private readonly List _componentNodes; 27 | private readonly List _featureNodes; 28 | private readonly List _defines; 29 | 30 | public WixSourceFile(WixProject project, EnvDTE.ProjectItem projectItem) 31 | { 32 | Project = project; 33 | _projectItem = projectItem; 34 | 35 | try 36 | { 37 | _xmlFile = _projectItem.GetXmlContent(LoadOptions.PreserveWhitespace); 38 | _rawXmlFile = _projectItem.GetXmlContent(LoadOptions.None); 39 | } 40 | catch 41 | { 42 | var placeholder = @""; 43 | _xmlFile = XDocument.Parse(placeholder); 44 | _rawXmlFile = XDocument.Parse(placeholder); 45 | } 46 | 47 | var root = _xmlFile.Root; 48 | 49 | _root = root ?? throw new InvalidDataException("Invalid source file: " + projectItem.TryGetFileName()); 50 | 51 | WixNames = new WixNames(root.GetDefaultNamespace().NamespaceName); 52 | 53 | _defines = root.Nodes().OfType() 54 | .Where(p => p.Target.Equals(WixNames.Define, StringComparison.Ordinal)) 55 | .Select(p => new WixDefine(this, p)) 56 | .ToList(); 57 | 58 | _componentGroupNodes = root.Descendants(WixNames.ComponentGroupNode) 59 | .Select(node => new WixComponentGroupNode(this, node)) 60 | .ToList(); 61 | 62 | _componentNodes = root.Descendants(WixNames.ComponentNode) 63 | .Select(node => new WixComponentNode(this, node)) 64 | .ToList(); 65 | 66 | _fileNodes = new List(); 67 | 68 | _fileNodes.AddRange(root 69 | .Descendants(WixNames.FileNode) 70 | .Select(node => new WixFileNode(this, node, _fileNodes))); 71 | 72 | _directoryNodes = root 73 | .Descendants(WixNames.DirectoryNode) 74 | .Select(node => new WixDirectoryNode(this, node)) 75 | .Where(node => node.Id != "TARGETDIR") 76 | .ToList(); 77 | 78 | _featureNodes = root 79 | .Descendants(WixNames.FeatureNode) 80 | .Select(node => new WixFeatureNode(this, node)) 81 | .ToList(); 82 | 83 | var featureNodesLookup = _featureNodes.ToDictionary(item => item.Id); 84 | 85 | foreach (var featureNode in _featureNodes) 86 | { 87 | featureNode.BuildTree(featureNodesLookup); 88 | } 89 | } 90 | 91 | public WixNames WixNames { get; } 92 | 93 | public IEnumerable FileNodes => new ReadOnlyCollection(_fileNodes); 94 | 95 | public IEnumerable DirectoryNodes => new ReadOnlyCollection(_directoryNodes); 96 | 97 | public IEnumerable ComponentGroupNodes => new ReadOnlyCollection(_componentGroupNodes); 98 | 99 | public IEnumerable FeatureNodes => new ReadOnlyCollection(_featureNodes); 100 | 101 | public IEnumerable ComponentNodes => new ReadOnlyCollection(_componentNodes); 102 | 103 | public WixProject Project { get; } 104 | 105 | public bool HasChanges 106 | { 107 | get 108 | { 109 | try 110 | { 111 | var xmlFile = _projectItem.GetXmlContent(LoadOptions.None); 112 | 113 | return xmlFile.ToString(SaveOptions.DisableFormatting) != _rawXmlFile.ToString(SaveOptions.DisableFormatting); 114 | } 115 | catch (XmlException) 116 | { 117 | return true; 118 | } 119 | } 120 | } 121 | 122 | internal WixDirectoryNode AddDirectory(string id, string name, string parentId) 123 | { 124 | var root = _root; 125 | 126 | var fragmentElement = new XElement(WixNames.FragmentNode); 127 | root.AddWithFormatting(fragmentElement); 128 | 129 | var directoryRefElement = new XElement(WixNames.DirectoryRefNode, new XAttribute("Id", parentId)); 130 | fragmentElement.AddWithFormatting(directoryRefElement); 131 | 132 | var directoryElement = new XElement(WixNames.DirectoryNode, new XAttribute("Id", id), new XAttribute("Name", name)); 133 | directoryRefElement.AddWithFormatting(directoryElement); 134 | 135 | Save(); 136 | 137 | return AddDirectoryNode(directoryElement); 138 | } 139 | 140 | public WixDirectoryNode AddDirectoryNode(XElement directoryElement) 141 | { 142 | var directoryNode = new WixDirectoryNode(this, directoryElement); 143 | _directoryNodes.Add(directoryNode); 144 | return directoryNode; 145 | } 146 | 147 | internal WixComponentGroupNode AddComponentGroup(string directoryId) 148 | { 149 | var root = _root; 150 | 151 | var fragmentElement = new XElement(WixNames.FragmentNode); 152 | root.AddWithFormatting(fragmentElement); 153 | 154 | var componentGroupElement = new XElement(WixNames.ComponentGroupNode, new XAttribute("Id", directoryId + "_files"), new XAttribute("Directory", directoryId)); 155 | fragmentElement.AddWithFormatting(componentGroupElement); 156 | 157 | Save(); 158 | 159 | var componentGroup = new WixComponentGroupNode(this, componentGroupElement); 160 | _componentGroupNodes.Add(componentGroup); 161 | return componentGroup; 162 | } 163 | 164 | internal WixFileNode AddFileComponent(WixComponentGroupNode componentGroup, string id, string name, FileMapping fileMapping) 165 | { 166 | Debug.Assert(componentGroup.SourceFile.Equals(this)); 167 | 168 | var project = fileMapping.TopLevelProject; 169 | 170 | var variableName = string.Format(CultureInfo.InvariantCulture, "{0}_TargetDir", project.Name); 171 | 172 | ForceDirectoryVariable(variableName, project); 173 | 174 | var componentElement = new XElement(WixNames.ComponentNode, new XAttribute("Id", id), new XAttribute("Guid", Guid.NewGuid().ToString())); 175 | 176 | componentGroup.Node.AddWithFormatting(componentElement); 177 | 178 | var source = string.Format(CultureInfo.InvariantCulture, "$(var.{0}){1}", variableName, fileMapping.SourceName); 179 | 180 | var fileElement = new XElement(WixNames.FileNode, new XAttribute("Id", id), new XAttribute("Name", name), new XAttribute("Source", source)); 181 | 182 | componentElement.AddWithFormatting(fileElement); 183 | 184 | Save(); 185 | 186 | var fileNode = new WixFileNode(componentGroup.SourceFile, fileElement, _fileNodes); 187 | _fileNodes.Add(fileNode); 188 | return fileNode; 189 | } 190 | 191 | internal void Save() 192 | { 193 | _projectItem.SetContent(_xmlFile.Declaration + _xmlFile.ToString(SaveOptions.DisableFormatting)); 194 | _rawXmlFile = _projectItem.GetXmlContent(LoadOptions.None); 195 | } 196 | 197 | private void ForceDirectoryVariable(string variableName, Project project) 198 | { 199 | if (_defines.Any(d => d.Name.Equals(variableName, StringComparison.Ordinal))) 200 | return; 201 | 202 | var data = string.Format(CultureInfo.InvariantCulture, "{0}=$(var.{1}.TargetDir)", variableName, project.Name); 203 | var processingInstruction = new XProcessingInstruction(WixNames.Define, data); 204 | 205 | var lastNode = _defines.LastOrDefault(); 206 | 207 | if (lastNode != null) 208 | { 209 | lastNode.Node.AddAfterSelf(processingInstruction); 210 | } 211 | else 212 | { 213 | var firstNode = _root.FirstNode; 214 | if (firstNode != null) 215 | { 216 | firstNode.AddBeforeSelf(processingInstruction); 217 | } 218 | else 219 | { 220 | _root.Add(processingInstruction); 221 | } 222 | } 223 | 224 | _defines.Add(new WixDefine(this, processingInstruction)); 225 | } 226 | } 227 | } -------------------------------------------------------------------------------- /Wax.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31402.337 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wax", "Wax\Wax.csproj", "{A9900171-C65D-40ED-8764-74C80E05DB9B}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wax.Model", "Wax.Model\Wax.Model.csproj", "{A3F2A655-5194-4A7A-AA66-E9A536F3E352}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5A77E932-3BEE-4FCB-A6C6-CBEDCE1B927D}" 11 | ProjectSection(SolutionItems) = preProject 12 | .editorconfig = .editorconfig 13 | Directory.Build.props = Directory.Build.props 14 | LICENSE = LICENSE 15 | README.md = README.md 16 | ReleaseNotes.md = ReleaseNotes.md 17 | Screenshot.png = Screenshot.png 18 | EndProjectSection 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {A9900171-C65D-40ED-8764-74C80E05DB9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {A9900171-C65D-40ED-8764-74C80E05DB9B}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {A9900171-C65D-40ED-8764-74C80E05DB9B}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {A9900171-C65D-40ED-8764-74C80E05DB9B}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {A3F2A655-5194-4A7A-AA66-E9A536F3E352}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {A3F2A655-5194-4A7A-AA66-E9A536F3E352}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {A3F2A655-5194-4A7A-AA66-E9A536F3E352}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {A3F2A655-5194-4A7A-AA66-E9A536F3E352}.Release|Any CPU.Build.0 = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(ExtensibilityGlobals) = postSolution 39 | SolutionGuid = {CC847233-AC59-4104-B415-47B4C3F9FCB3} 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /Wax.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | OPTIMISTIC 3 | True 4 | 1 5 | NEVER 6 | True 7 | True 8 | True 9 | True 10 | True 11 | True 12 | True 13 | True 14 | True 15 | True 16 | True -------------------------------------------------------------------------------- /Wax/200x200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-englert/Wax/57dd8d47972869363733e66712cf813d3efec7d8/Wax/200x200.png -------------------------------------------------------------------------------- /Wax/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-englert/Wax/57dd8d47972869363733e66712cf813d3efec7d8/Wax/32x32.png -------------------------------------------------------------------------------- /Wax/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Wax/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Used to control if the On_PropertyName_Changed feature is enabled. 12 | 13 | 14 | 15 | 16 | Used to control if the Dependent properties feature is enabled. 17 | 18 | 19 | 20 | 21 | Used to control if the IsChanged property feature is enabled. 22 | 23 | 24 | 25 | 26 | Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form. 27 | 28 | 29 | 30 | 31 | Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project. 32 | 33 | 34 | 35 | 36 | Used to control if equality checks should use the Equals method resolved from the base class. 37 | 38 | 39 | 40 | 41 | Used to control if equality checks should use the static Equals method resolved from the base class. 42 | 43 | 44 | 45 | 46 | Used to turn off build warnings from this weaver. 47 | 48 | 49 | 50 | 51 | Used to turn off build warnings about mismatched On_PropertyName_Changed methods. 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 61 | 62 | 63 | 64 | 65 | A comma-separated list of error codes that can be safely ignored in assembly verification. 66 | 67 | 68 | 69 | 70 | 'false' to turn off automatic generation of the XML Schema file. 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /Wax/GroupBox.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax 2 | { 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | 7 | using TomsToolbox.Wpf; 8 | 9 | public class GroupBox : HeaderedContentControl 10 | { 11 | static GroupBox() 12 | { 13 | DefaultStyleKeyProperty.OverrideMetadata(typeof(GroupBox), new FrameworkPropertyMetadata(typeof(GroupBox))); 14 | } 15 | 16 | 17 | public bool IsOk 18 | { 19 | get => this.GetValue(IsOkProperty); 20 | set => SetValue(IsOkProperty, value); 21 | } 22 | /// 23 | /// Identifies the IsOk dependency property 24 | /// 25 | public static readonly DependencyProperty IsOkProperty = 26 | DependencyProperty.Register("IsOk", typeof(bool), typeof(GroupBox)); 27 | 28 | 29 | public int Ordinal 30 | { 31 | get => this.GetValue(OrdinalProperty); 32 | set => SetValue(OrdinalProperty, value); 33 | } 34 | /// 35 | /// Identifies the Ordinal dependency property 36 | /// 37 | public static readonly DependencyProperty OrdinalProperty = 38 | DependencyProperty.Register("Ordinal", typeof(int), typeof(GroupBox)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Wax/Guids.cs: -------------------------------------------------------------------------------- 1 | // Guids.cs 2 | // MUST match guids.h 3 | 4 | // ReSharper disable InconsistentNaming 5 | // ReSharper disable IdentifierTypo 6 | // ReSharper disable UnusedMember.Global 7 | namespace tomenglertde.Wax 8 | { 9 | using System; 10 | 11 | internal static class GuidList 12 | { 13 | public const string guidWaxPkgString = "6b9b0621-c739-4ee5-9834-a0ba2a8d3596"; 14 | public const string guidWaxCmdSetString = "b2efcdeb-6b97-430e-82eb-ef8c27280004"; 15 | public const string guidToolWindowPersistanceString = "ba4ab97f-d341-4b14-b8c9-3cba5e401a5f"; 16 | 17 | public static readonly Guid guidWaxCmdSet = new(guidWaxCmdSetString); 18 | } 19 | } -------------------------------------------------------------------------------- /Wax/MainView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax 2 | { 3 | using System; 4 | using System.Linq; 5 | using System.Windows; 6 | using System.Windows.Controls; 7 | using System.Windows.Media; 8 | 9 | /// 10 | /// Interaction logic for MainWindow.xaml 11 | /// 12 | public partial class MainView 13 | { 14 | private readonly EnvDTE80.DTE2 _dte; 15 | 16 | public MainView(EnvDTE80.DTE2 dte) 17 | { 18 | _dte = dte; 19 | 20 | InitializeComponent(); 21 | 22 | Loaded += Self_Loaded; 23 | } 24 | 25 | public bool IsDarkTheme 26 | { 27 | get => (bool)GetValue(IsDarkThemeProperty); 28 | set => SetValue(IsDarkThemeProperty, value); 29 | } 30 | public static readonly DependencyProperty IsDarkThemeProperty = DependencyProperty.Register( 31 | "IsDarkTheme", typeof(bool), typeof(MainView), new PropertyMetadata(default(bool))); 32 | 33 | internal MainViewModel? ViewModel 34 | { 35 | get => DataContext as MainViewModel; 36 | set => DataContext = value; 37 | } 38 | 39 | private void Self_Loaded(object sender, RoutedEventArgs e) 40 | { 41 | var viewModel = ViewModel; 42 | 43 | var solution = _dte.Solution; 44 | 45 | if ((solution == null) || (viewModel == null) || (viewModel.Solution.FullName != solution.FullName) || (viewModel.HasExternalChanges) || !viewModel.Solution.Projects.Any()) 46 | { 47 | Refresh(solution); 48 | } 49 | } 50 | 51 | private void Refresh_Click(object sender, RoutedEventArgs e) 52 | { 53 | Refresh(_dte.Solution); 54 | } 55 | 56 | private void Refresh(EnvDTE.Solution? solution) 57 | { 58 | if (solution == null) 59 | { 60 | ViewModel = null; 61 | return; 62 | } 63 | 64 | try 65 | { 66 | ViewModel = new MainViewModel(solution); 67 | } 68 | catch (Exception ex) 69 | { 70 | MessageBox.Show("Error loading: " + ex); 71 | } 72 | } 73 | 74 | protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) 75 | { 76 | base.OnPropertyChanged(e); 77 | 78 | if (e.Property == DataContextProperty) 79 | { 80 | 81 | if ((e.OldValue is not MainViewModel oldViewModel) || (e.NewValue is not MainViewModel newViewModel)) 82 | return; 83 | 84 | var oldSelectedProject = newViewModel.Solution.WixProjects.FirstOrDefault(p => p.Equals(oldViewModel.SelectedWixProject)); 85 | 86 | if (oldSelectedProject == null) 87 | return; 88 | 89 | newViewModel.SelectedWixProject = oldSelectedProject; 90 | return; 91 | } 92 | 93 | if ((e.Property != ForegroundProperty) && (e.Property != BackgroundProperty)) 94 | return; 95 | 96 | var foreground = ToGray((Foreground as SolidColorBrush)?.Color); 97 | var background = ToGray((Background as SolidColorBrush)?.Color); 98 | 99 | IsDarkTheme = background < foreground; 100 | } 101 | 102 | private static double ToGray(Color? color) 103 | { 104 | return color?.R * 0.21 + color?.G * 0.72 + color?.B * 0.07 ?? 0.0; 105 | } 106 | 107 | private void SetupProjectListBox_Loaded(object sender, RoutedEventArgs e) 108 | { 109 | var listBox = (ListBox)sender; 110 | 111 | if ((listBox.Items.Count == 1) && (listBox.SelectedIndex == -1)) 112 | { 113 | listBox.SelectedIndex = 0; 114 | } 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /Wax/PkgCmdID.cs: -------------------------------------------------------------------------------- 1 | // PkgCmdID.cs 2 | // MUST match PkgCmdID.h 3 | 4 | // ReSharper disable InconsistentNaming 5 | // ReSharper disable IdentifierTypo 6 | // ReSharper disable UnusedMember.Global 7 | namespace tomenglertde.Wax 8 | { 9 | internal static class PkgCmdIDList 10 | { 11 | public const uint cmdidWax = 0x100; 12 | public const uint cmdidWaxToolWindow = 0x101; 13 | 14 | } 15 | } -------------------------------------------------------------------------------- /Wax/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell; 2 | 3 | [assembly: System.Windows.ThemeInfo(System.Windows.ResourceDictionaryLocation.None, System.Windows.ResourceDictionaryLocation.SourceAssembly)] 4 | 5 | [assembly: ProvideCodeBase(CodeBase = "$PackageFolder$\\Microsoft.Xaml.Behaviors.dll")] 6 | [assembly: ProvideCodeBase(CodeBase = "$PackageFolder$\\DataGridExtensions.dll")] 7 | [assembly: ProvideCodeBase(CodeBase = "$PackageFolder$\\TomsToolbox.Essentials.dll")] 8 | [assembly: ProvideCodeBase(CodeBase = "$PackageFolder$\\TomsToolbox.ObservableCollections.dll")] 9 | [assembly: ProvideCodeBase(CodeBase = "$PackageFolder$\\TomsToolbox.Wpf.dll")] 10 | [assembly: ProvideCodeBase(CodeBase = "$PackageFolder$\\TomsToolbox.Wpf.Styles.dll")] -------------------------------------------------------------------------------- /Wax/Properties/Resources.de.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Das Fenster kann nicht erzeugt werden. 122 | 123 | 124 | WiX Setup Editor 125 | 126 | 127 | Debug-Symbole 128 | 129 | 130 | Setup-Projekt: 131 | 132 | 133 | Stammverzeichnis 134 | 135 | 136 | Zu installierende Projekte: 137 | 138 | 139 | Alle Projekte anzeigen 140 | 141 | 142 | Verzeichnis-Zuordnungen 143 | 144 | 145 | Verzeichnis 146 | 147 | 148 | WiX-Definition 149 | 150 | 151 | Eigene Zuordnung entfernen. 152 | 153 | 154 | Neuen Verzeichnisknoten erstellen. 155 | 156 | 157 | Datei 158 | 159 | 160 | Erw. 161 | 162 | 163 | Status 164 | 165 | 166 | Nicht zugeordnete Dateien 167 | 168 | 169 | Id 170 | 171 | 172 | Name 173 | 174 | 175 | Quelle 176 | 177 | 178 | Nicht zugeordnete Datei entfernen. 179 | 180 | 181 | Dateizuordnungen 182 | 183 | 184 | Eigene Datei-Zuordnung entfernen. 185 | 186 | 187 | Neuen Dateiknoten erstellen. 188 | 189 | 190 | Eindeutige Zuordnung bestätigen. 191 | 192 | 193 | Neu laden 194 | 195 | 196 | Unterstütze das Projekt, in dem du die Anwendung bewertest. 197 | 198 | 199 | Dokumentation 200 | 201 | 202 | Das Projekt unterstützen. 203 | 204 | 205 | Ext. Lokalisierungen 206 | 207 | 208 | Debug-Symbole mitinstallieren. 209 | 210 | 211 | Lokalisierungen externer Komponenten installieren. 212 | 213 | 214 | Lokalisierungen 215 | 216 | 217 | Lokalisierungen installieren. 218 | 219 | 220 | Referenziert von: 221 | 222 | 223 | Projekttyp: 224 | 225 | -------------------------------------------------------------------------------- /Wax/Properties/Resources.fr.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Impossible de créer la fenêtre de cet outil. 122 | 123 | 124 | Editeur d'installeur Wix 125 | 126 | 127 | Déployer les symboles 128 | 129 | 130 | Projet d'installation à éditer : 131 | 132 | 133 | Répertoire racine 134 | 135 | 136 | Projets à installer : 137 | 138 | 139 | Afficher tous les projets 140 | 141 | 142 | Mappages de répertoires 143 | 144 | 145 | Répertoire 146 | 147 | 148 | Définition Wix 149 | 150 | 151 | Supprimer le mappage personnalisé. 152 | 153 | 154 | Créer un nouveau noeud de type répertoire. 155 | 156 | 157 | Fichier 158 | 159 | 160 | Ext. 161 | 162 | 163 | Etat 164 | 165 | 166 | Fichiers non mappés 167 | 168 | 169 | Id 170 | 171 | 172 | Nom 173 | 174 | 175 | Source 176 | 177 | 178 | Supprimer les fichiers non mappés. 179 | 180 | 181 | Mappages de fichiers 182 | 183 | 184 | Supprimer le mappage de fichier personnalisé. 185 | 186 | 187 | Créer un nouveau noeud de type fichier. 188 | 189 | 190 | Confirmer le mappage unique de fichier. 191 | 192 | 193 | Rafraîchir 194 | 195 | 196 | Supporter le projet en écrivant un bref avis. 197 | 198 | 199 | Documentation 200 | 201 | 202 | Supporter le projet. 203 | 204 | 205 | Localisations externes 206 | 207 | 208 | Ajouter les symboles dans le projet d'installation. 209 | 210 | 211 | Ajouter aussi les librairies satellites des références externes dans le projet d'installation. 212 | 213 | 214 | Localisations 215 | 216 | 217 | Ajouter aussi les librairies satellites dans le projet d'installation. 218 | 219 | 220 | Référencé par: 221 | 222 | 223 | Type de projet: 224 | 225 | -------------------------------------------------------------------------------- /Wax/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 11 | 12 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | text/microsoft-resx 119 | 120 | 121 | 2.0 122 | 123 | 124 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 125 | 126 | 127 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 128 | 129 | 130 | Can not create tool window. 131 | 132 | 133 | WiX Setup Editor 134 | 135 | 136 | Symbols 137 | 138 | 139 | Setup project to edit: 140 | 141 | 142 | Root directory 143 | 144 | 145 | Projects to install: 146 | 147 | 148 | Show all projects 149 | 150 | 151 | Directory mappings 152 | 153 | 154 | Directory 155 | 156 | 157 | WiX definition 158 | 159 | 160 | Remove the custom mapping. 161 | 162 | 163 | Create a new directory node. 164 | 165 | 166 | File 167 | 168 | 169 | Ext. 170 | @MutedRule(PunctuationTail) 171 | 172 | 173 | State 174 | 175 | 176 | Unmapped Files 177 | 178 | 179 | Id 180 | 181 | 182 | Name 183 | 184 | 185 | Source 186 | 187 | 188 | Remove unmapped file. 189 | 190 | 191 | File mappings 192 | 193 | 194 | Remove custom file mapping. 195 | 196 | 197 | Create a new file node. 198 | 199 | 200 | Confirm unique file mapping. 201 | 202 | 203 | Refresh 204 | 205 | 206 | Support the project by writing a short review. 207 | 208 | 209 | Documentation 210 | 211 | 212 | Support the project. 213 | 214 | 215 | Ext. Localizations 216 | 217 | 218 | Include the debug symbols in the setup project. 219 | 220 | 221 | Include also external/3rd party localizations in the setup project. 222 | 223 | 224 | Localizations 225 | 226 | 227 | Include localizations in the setup project. 228 | 229 | 230 | Referenced by: 231 | 232 | 233 | Project type: 234 | 235 | -------------------------------------------------------------------------------- /Wax/Properties/Resources.zh-CN.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 无法创建工具窗口。 122 | 123 | 124 | Wix安装编辑器 125 | 126 | 127 | 部署调试符号 128 | 129 | 130 | 安装项目编辑: 131 | 132 | 133 | 根目录 134 | 135 | 136 | 项目安装至: 137 | 138 | 139 | 显示所有项目 140 | 141 | 142 | 目录映射 143 | 144 | 145 | 目录 146 | 147 | 148 | Wix定义 149 | 150 | 151 | 移除自定义映射。 152 | 153 | 154 | 创建新目录节点。 155 | 156 | 157 | 文件 158 | 159 | 160 | 扩展名 161 | 162 | 163 | 状态 164 | 165 | 166 | 未映射文件 167 | 168 | 169 | 标识 170 | 171 | 172 | 名称 173 | 174 | 175 | 176 | 177 | 178 | 移除未映射文件。 179 | 180 | 181 | 文件映射 182 | 183 | 184 | 移除自定义文件映射。 185 | 186 | 187 | 创建一个文件节点。 188 | 189 | 190 | 确认唯一文件映射。 191 | 192 | 193 | 刷新 194 | 195 | 196 | 写短评支持此项目。 197 | 198 | 199 | 文档 200 | 201 | 202 | 支持此项目。 203 | 204 | 205 | 外部本地化 206 | 207 | 208 | 在安装项目中包含调试符号。 209 | 210 | 211 | 在安装项目中包含外部或第三方的本地化。 212 | 213 | 214 | 本地化 215 | 216 | 217 | 在安装项目中包含本地化内容。 218 | 219 | 220 | 引用者: 221 | 222 | 223 | 项目类型: 224 | 225 | -------------------------------------------------------------------------------- /Wax/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "VS2022": { 4 | "commandName": "Executable", 5 | "executablePath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Common7\\IDE\\devenv.exe", 6 | "commandLineArgs": "/RootSuffix Exp" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /Wax/Resources/Icon32.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-englert/Wax/57dd8d47972869363733e66712cf813d3efec7d8/Wax/Resources/Icon32.pdn -------------------------------------------------------------------------------- /Wax/Resources/Package.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-englert/Wax/57dd8d47972869363733e66712cf813d3efec7d8/Wax/Resources/Package.ico -------------------------------------------------------------------------------- /Wax/Resources/VSColorScheme.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 8 | 10 | 12 | 14 | 16 | 18 | 19 | 21 | 22 | 24 | 26 | 27 | 29 | 31 | 32 | 34 | 36 | 38 | 39 | 41 | 43 | 44 | 46 | 48 | 50 | 51 | 53 | 55 | 57 | 58 | 60 | 62 | 63 | -------------------------------------------------------------------------------- /Wax/Resources/btn_donate_SM.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-englert/Wax/57dd8d47972869363733e66712cf813d3efec7d8/Wax/Resources/btn_donate_SM.gif -------------------------------------------------------------------------------- /Wax/Resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-englert/Wax/57dd8d47972869363733e66712cf813d3efec7d8/Wax/Resources/icon.png -------------------------------------------------------------------------------- /Wax/Resources/images.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-englert/Wax/57dd8d47972869363733e66712cf813d3efec7d8/Wax/Resources/images.png -------------------------------------------------------------------------------- /Wax/Resources/like.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-englert/Wax/57dd8d47972869363733e66712cf813d3efec7d8/Wax/Resources/like.png -------------------------------------------------------------------------------- /Wax/Resources/ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-englert/Wax/57dd8d47972869363733e66712cf813d3efec7d8/Wax/Resources/ok.png -------------------------------------------------------------------------------- /Wax/Resources/refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-englert/Wax/57dd8d47972869363733e66712cf813d3efec7d8/Wax/Resources/refresh.png -------------------------------------------------------------------------------- /Wax/Resources/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-englert/Wax/57dd8d47972869363733e66712cf813d3efec7d8/Wax/Resources/warning.png -------------------------------------------------------------------------------- /Wax/ShellView.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Wax/ShellView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax 2 | { 3 | /// 4 | /// Interaction logic for ShellView.xaml 5 | /// 6 | public partial class ShellView 7 | { 8 | public ShellView() 9 | { 10 | InitializeComponent(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Wax/Themes/Generic.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 34 | -------------------------------------------------------------------------------- /Wax/ToolWindow.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax 2 | { 3 | using System; 4 | using System.ComponentModel; 5 | using System.Diagnostics; 6 | using System.Runtime.InteropServices; 7 | using System.Windows; 8 | using System.Windows.Controls.Primitives; 9 | 10 | using Microsoft.VisualStudio; 11 | using Microsoft.VisualStudio.Shell; 12 | using Microsoft.VisualStudio.Shell.Interop; 13 | 14 | using tomenglertde.Wax.Model.VisualStudio; 15 | 16 | using TomsToolbox.Wpf; 17 | 18 | /// 19 | /// This class implements the tool window exposed by this package and hosts a user control. 20 | /// 21 | /// In Visual Studio tool windows are composed of a frame (implemented by the shell) and a pane, 22 | /// usually implemented by the package implementer. 23 | /// 24 | /// This class derives from the ToolWindowPane class provided from the MPF in order to use its 25 | /// implementation of the IVsUIElementPane interface. 26 | /// 27 | [Guid("ba4ab97f-d341-4b14-b8c9-3cba5e401a5f")] 28 | public class ToolWindow : ToolWindowPane 29 | { 30 | private EnvDTE80.DTE2? _dte; 31 | 32 | /// 33 | /// Standard constructor for the tool window. 34 | /// 35 | public ToolWindow() 36 | : base(null) 37 | { 38 | // Just to reference something to force load of referenced libraries. 39 | BindingErrorTracer.Start(msg => { Debug.WriteLine(msg); }); 40 | 41 | Caption = Properties.Resources.ToolWindowTitle; 42 | 43 | BitmapResourceID = 301; 44 | BitmapIndex = 1; 45 | } 46 | 47 | protected override void OnCreate() 48 | { 49 | base.OnCreate(); 50 | 51 | EventManager.RegisterClassHandler(typeof(MainView), ButtonBase.ClickEvent, new RoutedEventHandler(Navigate_Click)); 52 | 53 | var t = typeof(Solution); 54 | if (t.GetMembers().Length == 0) 55 | { 56 | // Just to make sure the assembly is loaded, loading it dynamically from XAML may not work! 57 | } 58 | 59 | _dte = (EnvDTE80.DTE2)GetService(typeof(EnvDTE.DTE)); 60 | 61 | Content = new ShellView { Content = new MainView(_dte) }; 62 | } 63 | 64 | private void Navigate_Click(object sender, RoutedEventArgs e) 65 | { 66 | var source = e.OriginalSource as FrameworkElement; 67 | 68 | var button = source?.TryFindAncestorOrSelf(); 69 | if (button == null) 70 | return; 71 | 72 | var url = source.Tag as string; 73 | if (string.IsNullOrEmpty(url) || !url.StartsWith("http", StringComparison.OrdinalIgnoreCase)) 74 | return; 75 | 76 | CreateWebBrowser(url); 77 | } 78 | 79 | 80 | [Localizable(false)] 81 | private void CreateWebBrowser(string url) 82 | { 83 | var webBrowsingService = (IVsWebBrowsingService?)GetService(typeof(SVsWebBrowsingService)); 84 | if (webBrowsingService != null) 85 | { 86 | var hr = webBrowsingService.Navigate(url, (uint)__VSWBNAVIGATEFLAGS.VSNWB_WebURLOnly, out var pFrame); 87 | if (ErrorHandler.Succeeded(hr) && (pFrame != null)) 88 | { 89 | hr = pFrame.Show(); 90 | if (ErrorHandler.Succeeded(hr)) 91 | return; 92 | } 93 | } 94 | 95 | Process.Start(url); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Wax/VSPackage.de.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Wax 122 | 123 | 124 | Wix Setup Editor 125 | 126 | -------------------------------------------------------------------------------- /Wax/VSPackage.fr.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Wax 122 | 123 | 124 | Wix Setup Editor 125 | 126 | -------------------------------------------------------------------------------- /Wax/VSPackage.resx: -------------------------------------------------------------------------------- 1 |  2 | 12 | 13 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | text/microsoft-resx 120 | 121 | 122 | 2.0 123 | 124 | 125 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 126 | 127 | 128 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 129 | 130 | 131 | 132 | Wax 133 | 134 | 135 | WiX Setup Editor 136 | 137 | 138 | 139 | Resources\Package.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 140 | 141 | 142 | 143 | Resources\Images.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 144 | 145 | -------------------------------------------------------------------------------- /Wax/VSPackage.zh-CN.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Wax 122 | 123 | 124 | Wix安装编辑器 125 | 126 | -------------------------------------------------------------------------------- /Wax/VsPackage.cs: -------------------------------------------------------------------------------- 1 | namespace tomenglertde.Wax 2 | { 3 | using System; 4 | using System.ComponentModel.Design; 5 | using System.Diagnostics; 6 | using System.Globalization; 7 | using System.Runtime.InteropServices; 8 | using System.Threading; 9 | 10 | using Microsoft.VisualStudio; 11 | using Microsoft.VisualStudio.Shell; 12 | using Microsoft.VisualStudio.Shell.Interop; 13 | 14 | /// 15 | /// This is the class that implements the package exposed by this assembly. 16 | /// 17 | /// The minimum requirement for a class to be considered a valid package for Visual Studio 18 | /// is to implement the IVsPackage interface and register itself with the shell. 19 | /// This package uses the helper classes defined inside the Managed Package Framework (MPF) 20 | /// to do it: it derives from the Package class that provides the implementation of the 21 | /// IVsPackage interface and uses the registration attributes defined in the framework to 22 | /// register itself and its components with the shell. 23 | /// 24 | // This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is 25 | // a package. 26 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 27 | // This attribute is used to register the informations needed to show the this package 28 | // in the Help/About dialog of Visual Studio. 29 | [InstalledProductRegistration("#110", "#112", "Wax", IconResourceID = 400)] 30 | // This attribute is needed to let the shell know that this package exposes some menus. 31 | [ProvideMenuResource("Menus.ctmenu", 1)] 32 | // This attribute registers a tool window exposed by this package. 33 | [ProvideToolWindow(typeof(ToolWindow))] 34 | [Guid(GuidList.guidWaxPkgString)] 35 | public sealed class VsPackage : AsyncPackage 36 | { 37 | /// 38 | /// Default constructor of the package. 39 | /// Inside this method you can place any initialization code that does not require 40 | /// any Visual Studio service because at this point the package object is created but 41 | /// not sited yet inside Visual Studio environment. The place to do all the other 42 | /// initialization is the Initialize method. 43 | /// 44 | public VsPackage() 45 | { 46 | Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", ToString())); 47 | } 48 | 49 | /// 50 | /// This function is called when the user clicks the menu item that shows the 51 | /// tool window. See the Initialize method to see how the menu item is associated to 52 | /// this function using the OleMenuCommandService service and the MenuCommand class. 53 | /// 54 | private void ShowToolWindow(object? sender, EventArgs e) 55 | { 56 | // Get the instance number 0 of this tool window. This window is single instance so this instance 57 | // is actually the only one. 58 | // The last flag is set to true so that if the tool window does not exists it will be created. 59 | var window = FindToolWindow(typeof(ToolWindow), 0, true); 60 | if (window?.Frame == null) 61 | { 62 | throw new NotSupportedException(Properties.Resources.CanNotCreateWindow); 63 | } 64 | var windowFrame = (IVsWindowFrame)window.Frame; 65 | ErrorHandler.ThrowOnFailure(windowFrame.Show()); 66 | } 67 | 68 | /// 69 | /// Initialization of the package; this method is called right after the package is sited, so this is the place 70 | /// where you can put all the initialization code that rely on services provided by VisualStudio. 71 | /// 72 | protected override async System.Threading.Tasks.Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) 73 | { 74 | Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", ToString())); 75 | 76 | await base.InitializeAsync(cancellationToken, progress).ConfigureAwait(false); 77 | 78 | var menuCommandService = await GetServiceAsync(typeof(IMenuCommandService)).ConfigureAwait(false); 79 | 80 | await JoinableTaskFactory.SwitchToMainThreadAsync(); 81 | 82 | // Add our command handlers for menu (commands must exist in the .vsct file) 83 | if (menuCommandService is OleMenuCommandService mcs) 84 | { 85 | // Create the command for the tool window 86 | var toolWindowCommandID = new CommandID(GuidList.guidWaxCmdSet, (int)PkgCmdIDList.cmdidWaxToolWindow); 87 | var menuToolWin = new MenuCommand(ShowToolWindow, toolWindowCommandID); 88 | mcs.AddCommand(menuToolWin); 89 | } 90 | 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Wax/Wax.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | true 4 | net472 5 | tomenglertde.Wax 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ReleaseNotes.md 14 | PreserveNewest 15 | true 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Always 24 | true 25 | 26 | 27 | Always 28 | true 29 | 30 | 31 | LICENSE 32 | Always 33 | true 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | True 42 | True 43 | Resources.resx 44 | 45 | 46 | PublicResXFileCodeGenerator 47 | Resources.Designer.cs 48 | Designer 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 2.5.13 61 | 62 | 63 | 6.6.0 64 | all 65 | 66 | 67 | 2021.3.0 68 | all 69 | 70 | 71 | 72 | all 73 | runtime; build; native; contentfiles; analyzers; buildtransitive 74 | 75 | 76 | 3.4.0 77 | all 78 | 79 | 80 | 81 | 82 | 83 | 1.7.0 84 | 85 | 86 | 2.7.4 87 | 88 | 89 | 15.0.1 90 | 91 | 92 | all 93 | runtime; build; native; contentfiles; analyzers; buildtransitive 94 | 95 | 96 | all 97 | 98 | 99 | -------------------------------------------------------------------------------- /Wax/Wax.vsct: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 36 | 37 | 38 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | 52 | 59 | 60 | 68 | 69 | 77 | 78 | 79 | 80 | 81 | 82 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /Wax/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Wax 6 | An interactive WiX-Setup editor; easily maintain the files to be installed. 7 | https://github.com/tom-englert/Wax 8 | LICENSE 9 | https://github.com/tom-englert/Wax 10 | ReleaseNotes.md 11 | 32x32.png 12 | 200x200.png 13 | WiX, Install, MSI, Windows Installer, Setup 14 | 15 | 16 | 17 | x86 18 | 19 | 20 | amd64 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | name: $(Build.DefinitionName) $(date:yyyyMMdd)$(rev:.r) 2 | 3 | pool: 4 | vmImage: 'windows-latest' 5 | demands: 6 | - msbuild 7 | - vstest 8 | 9 | variables: 10 | BuildPlatform: 'Any CPU' 11 | BuildConfiguration: 'Release' 12 | 13 | steps: 14 | - task: PowerShell@1 15 | displayName: 'Set build version' 16 | inputs: 17 | scriptType: inlineScript 18 | inlineScript: | 19 | (new-object Net.WebClient).DownloadString("https://raw.github.com/tom-englert/BuildScripts/master/BuildScripts.ps1") | iex 20 | $version = Project-SetVersion "Directory.Build.props" 21 | $version | Build-AppendVersionToBuildNumber 22 | condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) 23 | 24 | - task: VSBuild@1 25 | displayName: 'Build solution' 26 | inputs: 27 | solution: 'wax.sln' 28 | vsVersion: '16.0' 29 | msbuildArgs: '-restore' 30 | platform: '$(BuildPlatform)' 31 | configuration: '$(BuildConfiguration)' 32 | createLogFile: true 33 | 34 | - task: CopyFiles@1 35 | displayName: 'Copy Files to: $(build.artifactstagingdirectory)' 36 | inputs: 37 | SourceFolder: '$(build.sourcesdirectory)' 38 | Contents: '**\bin\$(BuildConfiguration)\**\*.vsix' 39 | TargetFolder: '$(build.artifactstagingdirectory)' 40 | flattenFolders: true 41 | condition: succeededOrFailed() 42 | 43 | - task: PublishBuildArtifacts@1 44 | displayName: 'Publish Artifact: VSIX' 45 | inputs: 46 | PathtoPublish: '$(build.artifactstagingdirectory)\wax.vsix' 47 | ArtifactName: VSIX 48 | 49 | - task: PowerShell@1 50 | displayName: 'Publish to vsix-gallery' 51 | inputs: 52 | scriptType: inlineScript 53 | inlineScript: | 54 | (new-object Net.WebClient).DownloadString("https://raw.github.com/tom-englert/BuildScripts/master/BuildScripts.ps1") | iex 55 | 56 | Vsix-PublishToGallery "$(build.artifactstagingdirectory)\wax.vsix" 57 | condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) 58 | --------------------------------------------------------------------------------