├── .gitattributes ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── build.cake ├── build.ps1 ├── build_azure_pipelines.yml ├── cake.config ├── deployment_scripts ├── deploy.cake ├── deploy.ps1 ├── deploy_common.cake └── deployment_tools │ └── VsixGalleryPublish.ps1 ├── docs └── img │ ├── AddProgressReviewConfiguration.png │ ├── AddProgressReviewResults.png │ ├── AddResultWithSolutionFolders.png │ ├── ClickAddMultipleCommand.png │ ├── ClickLoadFromFolder.png │ ├── SelectFolderForLoadingProjects.png │ └── SelectProjectsToAdd.png ├── source ├── SolutionInfo.cs ├── Vs.AddMultipleProjectsToSolution.Tests │ ├── Gui │ │ └── ViewModels │ │ │ └── MapperTest.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Utilities │ │ └── FileEnumerationHelperTest.cs │ ├── Vs.AddMultipleProjectsToSolution.Tests.csproj │ └── packages.config ├── Vs.AddMultipleProjectsToSolution.sln ├── Vs.AddMultipleProjectsToSolution.sln.DotSettings └── Vs.AddMultipleProjectsToSolution │ ├── Commands │ └── AddMultipleProjectsCommand.cs │ ├── GlobalSuppressions.cs │ ├── Gui │ ├── AddMultipleProjectsConfigurationControl.xaml │ ├── AddMultipleProjectsConfigurationControl.xaml.cs │ ├── AddMultipleProjectsProgressControlControl.xaml │ ├── AddMultipleProjectsProgressControlControl.xaml.cs │ ├── AddMultipleProjectsWindow.cs │ ├── Assets │ │ └── VsControlStyles.xaml │ ├── IWindowService.cs │ ├── LoadProjectsProgressControl.xaml │ ├── LoadProjectsProgressControl.xaml.cs │ ├── LoadProjectsWindow.cs │ ├── LoadProjectsWindowControl.xaml │ ├── LoadProjectsWindowControl.xaml.cs │ ├── Resources │ │ ├── FolderClosed.png │ │ ├── FolderOpened.png │ │ ├── StatusError.png │ │ ├── StatusOK.png │ │ ├── StatusRunning.png │ │ ├── StatusRunningNoColor.png │ │ └── StatusWarning.png │ ├── Utilities │ │ ├── BooleanToFontWeightConverter.cs │ │ ├── IObservableObject.cs │ │ └── ObservableProperty.cs │ ├── ViewModels │ │ ├── AddMultipleProjectsConfigurationViewModel.cs │ │ ├── AddMultipleProjectsProgressViewModel.cs │ │ ├── FsDirectoryViewModel.cs │ │ ├── FsItemBuildSolutionItemHierarchyVisitor.cs │ │ ├── FsItemChangeCreateSolutionFolderVisitor.cs │ │ ├── FsItemCollection.cs │ │ ├── FsProjectDirectoryViewModel.cs │ │ ├── IFsItemViewModel.cs │ │ ├── IFsItemViewModelVisitor.cs │ │ ├── IMapper.cs │ │ ├── ISolutionItemViewModel.cs │ │ ├── LoadProjectsWindowViewModel.cs │ │ ├── Mapper.cs │ │ ├── ProcessingContext.cs │ │ ├── SolutionDirectoryViewModel.cs │ │ ├── SolutionItemCreateStatus.cs │ │ ├── SolutionItemHierarchy.cs │ │ ├── SolutionItemsCount.cs │ │ └── SolutionProjectViewModel.cs │ └── WindowService.cs │ ├── Properties │ ├── Annotations.cs │ └── AssemblyInfo.cs │ ├── Resources │ ├── AddMultipleProjectsCommand.png │ ├── ExtensionIcon.png │ └── ExtensionPreview.png │ ├── Utilities │ ├── Counter.cs │ ├── DirectoryReader.cs │ ├── FileEnumerationHelper.cs │ ├── IDirectoryReader.cs │ ├── IFileEnumerationHelper.cs │ ├── INVsProject.cs │ ├── INVsProjectHierarchy.cs │ ├── INVsSolution.cs │ ├── INVsSolutionFolder.cs │ ├── NVsCommmonHelper.cs │ ├── NVsProject.cs │ ├── NVsSolution.cs │ ├── NVsSolutionFolder.cs │ └── VsConsts │ │ └── ProjectKinds.cs │ ├── Vs.AddMultipleProjectsToSolution.csproj │ ├── VsCommandTable.cs │ ├── VsCommandTable.vsct │ ├── VsPackage.cs │ ├── packages.config │ ├── source.extension.cs │ ├── source.extension.ico │ ├── source.extension.resx │ └── source.extension.vsixmanifest └── test_solution ├── Projects ├── FirstFolder │ ├── Project1 │ │ ├── Project1.csproj │ │ └── Properties │ │ │ └── AssemblyInfo.cs │ └── Project2 │ │ ├── Project2.csproj │ │ ├── Project2Class.cs │ │ └── Properties │ │ └── AssemblyInfo.cs ├── Project4FolderConflict │ ├── Project4FolderConflict.csproj │ ├── Project5FolderConflict │ │ ├── Project5FolderConflict.csproj │ │ └── Properties │ │ │ └── AssemblyInfo.cs │ └── Properties │ │ └── AssemblyInfo.cs ├── Project6DependentOnProject2 │ ├── App.config │ ├── Program.cs │ ├── Project6DependentOnProject2.csproj │ └── Properties │ │ └── AssemblyInfo.cs └── SecondFolder │ └── Project3 │ ├── Project3.csproj │ └── Properties │ └── AssemblyInfo.cs └── TestSolution.sln /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | [Aa][Rr][Mm]/ 24 | [Aa][Rr][Mm]64/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015/2017 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_h.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *_wpftmp.csproj 83 | *.log 84 | *.vspscc 85 | *.vssscc 86 | .builds 87 | *.pidb 88 | *.svclog 89 | *.scc 90 | 91 | # Chutzpah Test files 92 | _Chutzpah* 93 | 94 | # Visual C++ cache files 95 | ipch/ 96 | *.aps 97 | *.ncb 98 | *.opendb 99 | *.opensdf 100 | *.sdf 101 | *.cachefile 102 | *.VC.db 103 | *.VC.VC.opendb 104 | 105 | # Visual Studio profiler 106 | *.psess 107 | *.vsp 108 | *.vspx 109 | *.sap 110 | 111 | # Visual Studio Trace Files 112 | *.e2e 113 | 114 | # TFS 2012 Local Workspace 115 | $tf/ 116 | 117 | # Guidance Automation Toolkit 118 | *.gpState 119 | 120 | # ReSharper is a .NET coding add-in 121 | _ReSharper*/ 122 | *.[Rr]e[Ss]harper 123 | *.DotSettings.user 124 | 125 | # JustCode is a .NET coding add-in 126 | .JustCode 127 | 128 | # TeamCity is a build add-in 129 | _TeamCity* 130 | 131 | # DotCover is a Code Coverage Tool 132 | *.dotCover 133 | 134 | # AxoCover is a Code Coverage Tool 135 | .axoCover/* 136 | !.axoCover/settings.json 137 | 138 | # Visual Studio code coverage results 139 | *.coverage 140 | *.coveragexml 141 | 142 | # NCrunch 143 | _NCrunch_* 144 | .*crunch*.local.xml 145 | nCrunchTemp_* 146 | 147 | # MightyMoose 148 | *.mm.* 149 | AutoTest.Net/ 150 | 151 | # Web workbench (sass) 152 | .sass-cache/ 153 | 154 | # Installshield output folder 155 | [Ee]xpress/ 156 | 157 | # DocProject is a documentation generator add-in 158 | DocProject/buildhelp/ 159 | DocProject/Help/*.HxT 160 | DocProject/Help/*.HxC 161 | DocProject/Help/*.hhc 162 | DocProject/Help/*.hhk 163 | DocProject/Help/*.hhp 164 | DocProject/Help/Html2 165 | DocProject/Help/html 166 | 167 | # Click-Once directory 168 | publish/ 169 | 170 | # Publish Web Output 171 | *.[Pp]ublish.xml 172 | *.azurePubxml 173 | # Note: Comment the next line if you want to checkin your web deploy settings, 174 | # but database connection strings (with potential passwords) will be unencrypted 175 | *.pubxml 176 | *.publishproj 177 | 178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 179 | # checkin your Azure Web App publish settings, but sensitive information contained 180 | # in these scripts will be unencrypted 181 | PublishScripts/ 182 | 183 | # NuGet Packages 184 | *.nupkg 185 | # The packages folder can be ignored because of Package Restore 186 | **/[Pp]ackages/* 187 | # except build/, which is used as an MSBuild target. 188 | !**/[Pp]ackages/build/ 189 | # Uncomment if necessary however generally it will be regenerated when needed 190 | #!**/[Pp]ackages/repositories.config 191 | # NuGet v3's project.json files produces more ignorable files 192 | *.nuget.props 193 | *.nuget.targets 194 | 195 | # Microsoft Azure Build Output 196 | csx/ 197 | *.build.csdef 198 | 199 | # Microsoft Azure Emulator 200 | ecf/ 201 | rcf/ 202 | 203 | # Windows Store app package directories and files 204 | AppPackages/ 205 | BundleArtifacts/ 206 | Package.StoreAssociation.xml 207 | _pkginfo.txt 208 | *.appx 209 | 210 | # Visual Studio cache files 211 | # files ending in .cache can be ignored 212 | *.[Cc]ache 213 | # but keep track of directories ending in .cache 214 | !?*.[Cc]ache/ 215 | 216 | # Others 217 | ClientBin/ 218 | ~$* 219 | *~ 220 | *.dbmdl 221 | *.dbproj.schemaview 222 | *.jfm 223 | *.pfx 224 | *.publishsettings 225 | orleans.codegen.cs 226 | 227 | # Including strong name files can present a security risk 228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 229 | #*.snk 230 | 231 | # Since there are multiple workflows, uncomment next line to ignore bower_components 232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 233 | #bower_components/ 234 | # ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true 235 | **/wwwroot/lib/ 236 | 237 | # RIA/Silverlight projects 238 | Generated_Code/ 239 | 240 | # Backup & report files from converting an old project file 241 | # to a newer Visual Studio version. Backup files are not needed, 242 | # because we have git ;-) 243 | _UpgradeReport_Files/ 244 | Backup*/ 245 | UpgradeLog*.XML 246 | UpgradeLog*.htm 247 | ServiceFabricBackup/ 248 | *.rptproj.bak 249 | 250 | # SQL Server files 251 | *.mdf 252 | *.ldf 253 | *.ndf 254 | 255 | # Business Intelligence projects 256 | *.rdl.data 257 | *.bim.layout 258 | *.bim_*.settings 259 | *.rptproj.rsuser 260 | 261 | # Microsoft Fakes 262 | FakesAssemblies/ 263 | 264 | # GhostDoc plugin setting file 265 | *.GhostDoc.xml 266 | 267 | # Node.js Tools for Visual Studio 268 | .ntvs_analysis.dat 269 | node_modules/ 270 | 271 | # Visual Studio 6 build log 272 | *.plg 273 | 274 | # Visual Studio 6 workspace options file 275 | *.opt 276 | 277 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 278 | *.vbw 279 | 280 | # Visual Studio LightSwitch build output 281 | **/*.HTMLClient/GeneratedArtifacts 282 | **/*.DesktopClient/GeneratedArtifacts 283 | **/*.DesktopClient/ModelManifest.xml 284 | **/*.Server/GeneratedArtifacts 285 | **/*.Server/ModelManifest.xml 286 | _Pvt_Extensions 287 | 288 | # Paket dependency manager 289 | .paket/paket.exe 290 | paket-files/ 291 | 292 | # FAKE - F# Make 293 | .fake/ 294 | 295 | # JetBrains Rider 296 | .idea/ 297 | *.sln.iml 298 | 299 | # CodeRush personal settings 300 | .cr/personal 301 | 302 | # Python Tools for Visual Studio (PTVS) 303 | __pycache__/ 304 | *.pyc 305 | 306 | # Cake - Uncomment if you are using it 307 | # tools/** 308 | # !tools/packages.config 309 | 310 | # Tabs Studio 311 | *.tss 312 | 313 | # Telerik's JustMock configuration file 314 | *.jmconfig 315 | 316 | # BizTalk build output 317 | *.btp.cs 318 | *.btm.cs 319 | *.odx.cs 320 | *.xsd.cs 321 | 322 | # OpenCover UI analysis results 323 | OpenCover/ 324 | 325 | # Azure Stream Analytics local run output 326 | ASALocalRun/ 327 | 328 | # MSBuild Binary and Structured Log 329 | *.binlog 330 | 331 | # NVidia Nsight GPU debugger configuration file 332 | *.nvuser 333 | 334 | # MFractors (Xamarin productivity tool) working folder 335 | .mfractor/ 336 | 337 | # Local History for Visual Studio 338 | .localhistory/ 339 | 340 | # BeatPulse healthcheck temp database 341 | healthchecksdb 342 | 343 | #Ignore Cake tools directory 344 | /tools 345 | /deployment_scripts/tools -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Cake Build Script", 6 | "type": "coreclr", 7 | "request": "launch", 8 | "program": "${workspaceFolder}/tools/Cake.CoreCLR/Cake.dll", 9 | "args":[ 10 | "${workspaceFolder}/build.cake", 11 | "--debug", 12 | "--verbosity=diagnostic" 13 | ], 14 | "cwd": "${workspaceFolder}", 15 | "stopAtEntry": true, 16 | "externalConsole": false 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "cake", 8 | "script": "PublishToVsixGallery", 9 | "problemMatcher": [] 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Road map 2 | 3 | - [ ] Add exclusion list to skip folders like "obj", "bin" from searching. 4 | - [ ] Preserve list of extensions used for searching projects between tool executions. 5 | - [ ] Log performed operations to the Visual Studio output window. 6 | - [ ] Turn on/off creation of solution folders on folder level. 7 | - [ ] Add icons for known project types. 8 | - [ ] Align style of the Tree View displaying projects hierarchy with Visual Studio. 9 | 10 | 15 | 16 | # Change log 17 | 18 | These are the changes to each version that has been released 19 | on the official Visual Studio extension gallery. 20 | 21 | ## 1.2 22 | - [x] Added "vcxproj" to the list of searched projects. 23 | 24 | ## 1.1 25 | - [x] Added support for Visual Studio 2019. 26 | 27 | ## 1.0 28 | 29 | - [x] Add mutliple projects to solution. 30 | - [x] Load projects from given directory (recursive search by file extension). 31 | - [x] Select which projects to add to the solution. 32 | - [x] Define if solution folders hierarchy should be also created. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Looking to contribute something? **Here's how you can help.** 4 | 5 | Please take a moment to review this document in order to make the contribution 6 | process easy and effective for everyone involved. 7 | 8 | Following these guidelines helps to communicate that you respect the time of 9 | the developers managing and developing this open source project. In return, 10 | they should reciprocate that respect in addressing your issue or assessing 11 | patches and features. 12 | 13 | 14 | ## Using the issue tracker 15 | 16 | The issue tracker is the preferred channel for [bug reports](#bug-reports), 17 | [features requests](#feature-requests) and 18 | [submitting pull requests](#pull-requests), but please respect the 19 | following restrictions: 20 | 21 | * Please **do not** use the issue tracker for personal support requests. Stack 22 | Overflow is a better place to get help. 23 | 24 | * Please **do not** derail or troll issues. Keep the discussion on topic and 25 | respect the opinions of others. 26 | 27 | * Please **do not** open issues or pull requests which *belongs to* third party 28 | components. 29 | 30 | 31 | ## Bug reports 32 | 33 | A bug is a _demonstrable problem_ that is caused by the code in the repository. 34 | Good bug reports are extremely helpful, so thanks! 35 | 36 | Guidelines for bug reports: 37 | 38 | 1. **Use the GitHub issue search** — check if the issue has already been 39 | reported. 40 | 41 | 2. **Check if the issue has been fixed** — try to reproduce it using the 42 | latest `master` or development branch in the repository. 43 | 44 | 3. **Isolate the problem** — ideally create an 45 | [SSCCE](http://www.sscce.org/) and a live example. 46 | Uploading the project on cloud storage (OneDrive, DropBox, et el.) 47 | or creating a sample GitHub repository is also helpful. 48 | 49 | 50 | A good bug report shouldn't leave others needing to chase you up for more 51 | information. Please try to be as detailed as possible in your report. What is 52 | your environment? What steps will reproduce the issue? What browser(s) and OS 53 | experience the problem? Do other browsers show the bug differently? What 54 | would you expect to be the outcome? All these details will help people to fix 55 | any potential bugs. 56 | 57 | Example: 58 | 59 | > Short and descriptive example bug report title 60 | > 61 | > A summary of the issue and the Visual Studio, browser, OS environments 62 | > in which it occurs. If suitable, include the steps required to reproduce the bug. 63 | > 64 | > 1. This is the first step 65 | > 2. This is the second step 66 | > 3. Further steps, etc. 67 | > 68 | > `` - a link to the project/file uploaded on cloud storage or other publicly accessible medium. 69 | > 70 | > Any other information you want to share that is relevant to the issue being 71 | > reported. This might include the lines of code that you have identified as 72 | > causing the bug, and potential solutions (and your opinions on their 73 | > merits). 74 | 75 | 76 | ## Feature requests 77 | 78 | Feature requests are welcome. But take a moment to find out whether your idea 79 | fits with the scope and aims of the project. It's up to *you* to make a strong 80 | case to convince the project's developers of the merits of this feature. Please 81 | provide as much detail and context as possible. 82 | 83 | 84 | ## Pull requests 85 | 86 | Good pull requests, patches, improvements and new features are a fantastic 87 | help. They should remain focused in scope and avoid containing unrelated 88 | commits. 89 | 90 | **Please ask first** before embarking on any significant pull request (e.g. 91 | implementing features, refactoring code, porting to a different language), 92 | otherwise you risk spending a lot of time working on something that the 93 | project's developers might not want to merge into the project. 94 | 95 | Please adhere to the [coding guidelines](#code-guidelines) used throughout the 96 | project (indentation, accurate comments, etc.) and any other requirements 97 | (such as test coverage). 98 | 99 | Adhering to the following process is the best way to get your work 100 | included in the project: 101 | 102 | 1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, 103 | and configure the remotes: 104 | 105 | ```bash 106 | # Clone your fork of the repo into the current directory 107 | git clone https://github.com//.git 108 | # Navigate to the newly cloned directory 109 | cd 110 | # Assign the original repo to a remote called "upstream" 111 | git remote add upstream https://github.com/madskristensen/.git 112 | ``` 113 | 114 | 2. If you cloned a while ago, get the latest changes from upstream: 115 | 116 | ```bash 117 | git checkout master 118 | git pull upstream master 119 | ``` 120 | 121 | 3. Create a new topic branch (off the main project development branch) to 122 | contain your feature, change, or fix: 123 | 124 | ```bash 125 | git checkout -b 126 | ``` 127 | 128 | 4. Commit your changes in logical chunks. Please adhere to these [git commit 129 | message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 130 | or your code is unlikely be merged into the main project. Use Git's 131 | [interactive rebase](https://help.github.com/articles/interactive-rebase) 132 | feature to tidy up your commits before making them public. Also, prepend name of the feature 133 | to the commit message. For instance: "SCSS: Fixes compiler results for IFileListener.\nFixes `#123`" 134 | 135 | 5. Locally merge (or rebase) the upstream development branch into your topic branch: 136 | 137 | ```bash 138 | git pull [--rebase] upstream master 139 | ``` 140 | 141 | 6. Push your topic branch up to your fork: 142 | 143 | ```bash 144 | git push origin 145 | ``` 146 | 147 | 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) 148 | with a clear title and description against the `master` branch. 149 | 150 | 151 | ## Code guidelines 152 | 153 | - Always use proper indentation. 154 | - In Visual Studio under `Tools > Options > Text Editor > C# > Advanced`, make sure 155 | `Place 'System' directives first when sorting usings` option is enabled (checked). 156 | - Before committing, organize usings for each updated C# source file. Either you can 157 | right-click editor and select `Organize Usings > Remove and sort` OR use extension 158 | like [BatchFormat](http://visualstudiogallery.msdn.microsoft.com/a7f75c34-82b4-4357-9c66-c18e32b9393e). 159 | - Before committing, run Code Analysis in `Debug` configuration and follow the guidelines 160 | to fix CA issues. Code Analysis commits can be made separately. 161 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Installed product versions 2 | - Visual Studio: [example 2015 Professional] 3 | - This extension: [example 1.1.21] 4 | 5 | ### Description 6 | Replace this text with a short description 7 | 8 | ### Steps to recreate 9 | 1. Replace this 10 | 2. text with 11 | 3. the steps 12 | 4. to recreate 13 | 14 | ### Current behavior 15 | Explain what it's doing and why it's wrong 16 | 17 | ### Expected behavior 18 | Explain what it should be doing after it's fixed. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2019 Maciej Gudanowicz 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Add Multiple Projects To Solution 2 | 3 | [![Build Status](https://dev.azure.com/guudan/GithubBuildAndRelease/_apis/build/status/Build%20Vs.AddMultipleProjectsToSolution?branchName=master)](https://dev.azure.com/guudan/GithubBuildAndRelease/_build/latest?definitionId=3&branchName=master) 4 | 5 | 6 | 7 | Download this extension from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=MaciejGudanowicz.AddMultipleProjectsToSolution). 8 | 11 | 12 | --------------------------------------- 13 | 14 | Visual Studio extension that allows adding multiple existing projects into 15 | the solution. Optionally creates the solution folder structure reflecting 16 | to the physical hierarchy on disk. 17 | 18 | See the [change log](CHANGELOG.md) for changes and road map. 19 | 20 | ## Features 21 | 22 | - Add mutliple projects to solution. 23 | - Load projects from given directory (recursive search by file extension). 24 | - Select which projects to add to the solution. 25 | - Define if solution folders hierarchy should be also created. 26 | 27 | ### Add multiple projects to solution 28 | To add existing projects to the solution: 29 | 1. Right click the Solution node in Solution Explorer select Add -> Multiple Projects 30 | 31 | ![Click Add Multiple Command](docs/img/ClickAddMultipleCommand.png) 32 | 33 | 2. Click Load Projects From Folder 34 | 35 | ![Click Load From Folder](docs/img/ClickLoadFromFolder.png) 36 | 37 | 3. Select directory and click Add 38 | 39 | ![Select Folder For Loading Projects](docs/img/SelectFolderForLoadingProjects.png) 40 | 41 | 4. Found project will be displayed in the folder hierarchy in which they are defined on disk. 42 | - Select projects that you want to add to the solution. 43 | - Define if solution folders should be created. 44 | - Click Add. 45 | 46 | ![Select Projects To Add](docs/img/SelectProjectsToAdd.png) 47 | 48 | 5. Review the structure of projects that will be created and press Start. 49 | 50 | ![Add Progress Review Configuration](docs/img/AddProgressReviewConfiguration.png) 51 | 52 | 6. Check results. 53 | 54 | The state of the operation is indicated by the icon next to the project. 55 | When you will hover with mause over the icon the details will be displayed. 56 | 57 | On the bottom of the window total number of errors and processed projects is displayed. 58 | 59 | ![Add Progress Review Results](docs/img/AddProgressReviewResults.png) 60 | 61 | 7. The result of the operation. 62 | 63 | If you want to add projects without solution folders check configuration in step 4. 64 | 65 | ![Add Result With Solution Folders](docs/img/AddResultWithSolutionFolders.png) 66 | 67 | ## Contribute 68 | Check out the [contribution guidelines](CONTRIBUTING.md) 69 | if you want to contribute to this project. 70 | 71 | For cloning and building this project yourself, make sure 72 | to install the 73 | [Extensibility Tools 2015](https://visualstudiogallery.msdn.microsoft.com/ab39a092-1343-46e2-b0f1-6a3f91155aa6) 74 | extension for Visual Studio which enables some features 75 | used by this project. 76 | 77 | ## License 78 | [MIT License](LICENSE) -------------------------------------------------------------------------------- /build.cake: -------------------------------------------------------------------------------- 1 | #tool "nuget:?package=xunit.runner.console&version=2.4.1" 2 | #load "deployment_scripts/deploy_common.cake" 3 | 4 | var target = Argument("target", "Default"); 5 | 6 | public static class EnvironmentVariableNames 7 | { 8 | public const string BuildNumber = "BUILD_BUILDNUMBER"; 9 | } 10 | 11 | public static class BuildConsts 12 | { 13 | public const string RootDir = "./source"; 14 | public static readonly string SolutionFile = $"{RootDir}/Vs.AddMultipleProjectsToSolution.sln"; 15 | public const string VsPackageProjectName = "Vs.AddMultipleProjectsToSolution"; 16 | public const string VsixManifestFileName = "source.extension.vsixmanifest"; 17 | public static string VsixManifestFilePath = $"{RootDir}/{VsPackageProjectName}/{VsixManifestFileName}"; 18 | public static string VsPackageAssemblyInfoPath = $"{RootDir}/{VsPackageProjectName}/Properties/AssemblyInfo.cs"; 19 | public static string VsixFileName => $"{VsPackageProjectName}.vsix"; 20 | public const string VsTestProjectName = "Vs.AddMultipleProjectsToSolution.Tests"; 21 | public static string VsTestDllFileName = $"{VsTestProjectName}.dll"; 22 | 23 | public const string BuildOutputDir = "bin"; 24 | public const string DeploymentScriptPath = "deployment_scripts/deploy.cake"; 25 | } 26 | 27 | public class BuildInfo 28 | { 29 | public int BuildNumber {get;set;} 30 | public string Configuration {get; set;} 31 | 32 | public string VsixOutputPath => 33 | $"{BuildConsts.RootDir}/{BuildConsts.VsPackageProjectName}/bin/{Configuration}/{BuildConsts.VsixFileName}"; 34 | 35 | public string TestProjectOutputDirPath => 36 | $"{BuildConsts.RootDir}/{BuildConsts.VsTestProjectName}/bin/{Configuration}"; 37 | public string TestProjectDllOutputPath => 38 | $"{TestProjectOutputDirPath}/{BuildConsts.VsTestDllFileName}"; 39 | } 40 | 41 | 42 | public static void ExecuteDeploymentScript(this ICakeContext context) 43 | { 44 | context.Information("Calling deployment script"); 45 | var args = new CakeSettings(); 46 | 47 | var extensionDir = new DirectoryPath($"{BuildConsts.BuildOutputDir}/Extension"); 48 | args.Arguments[DeploymentArgs.DeploymentPackagePath] = context.MakeAbsolute(extensionDir).FullPath; 49 | 50 | ReadArgumentsIntoDictionary(context, args.Arguments, DeploymentArgs.ArgumentList); 51 | 52 | context.CakeExecuteScript(BuildConsts.DeploymentScriptPath, args); 53 | } 54 | 55 | public static void ReadArgumentsIntoDictionary(this ICakeContext context, IDictionary arguments, string[] argumentNames) 56 | { 57 | foreach(var arg in argumentNames) 58 | { 59 | if(context.HasArgument(arg)) 60 | { 61 | var value = context.Argument(arg); 62 | arguments.Add(arg,value); 63 | } 64 | } 65 | } 66 | 67 | Setup(ctx => 68 | { 69 | Information("Running tasks..."); 70 | var buildConfiguration = new BuildInfo(); 71 | buildConfiguration.Configuration = Argument("configuration", "Release"); 72 | 73 | var buildNumberString = EnvironmentVariable(EnvironmentVariableNames.BuildNumber); 74 | Verbose($"Parsing build number \"{buildNumberString}\" from environment variable {EnvironmentVariableNames.BuildNumber}."); 75 | buildConfiguration.BuildNumber = string.IsNullOrWhiteSpace(buildNumberString) ? 0 : int.Parse(buildNumberString); 76 | Information($"Build number: {buildConfiguration.BuildNumber}"); 77 | 78 | return buildConfiguration; 79 | }); 80 | 81 | Teardown(ctx => 82 | { 83 | Information("Finished running tasks."); 84 | }); 85 | 86 | Task("Clean") 87 | .Does(()=>{ 88 | CleanDirectory(BuildConsts.BuildOutputDir); 89 | }); 90 | 91 | Task("UpdatePackageVersion") 92 | .Does(config=>{ 93 | var versionXPath = "/x:PackageManifest/x:Metadata/x:Identity/@Version"; 94 | var vsixNamespace = new KeyValuePair("x", "http://schemas.microsoft.com/developer/vsx-schema/2011"); 95 | 96 | Verbose($"Reading version of the VSIX package from file {BuildConsts.VsixManifestFilePath}."); 97 | var peekSettings = new XmlPeekSettings(); 98 | peekSettings.Namespaces.Add(vsixNamespace); 99 | var currentVersionString = XmlPeek(BuildConsts.VsixManifestFilePath, versionXPath, peekSettings); 100 | var currentVersion = Version.Parse(currentVersionString); 101 | Information($"Original version of the VSIX = {currentVersionString}"); 102 | 103 | var newVersion = new Version(currentVersion.Major, currentVersion.Minor, config.BuildNumber); 104 | Information($"New version of the VSIX = {newVersion}"); 105 | 106 | 107 | Verbose($"Setting version of the VSIX package from file {BuildConsts.VsixManifestFilePath} to {newVersion}."); 108 | var pokeSettings = new XmlPokeSettings(); 109 | pokeSettings.Namespaces.Add(vsixNamespace); 110 | XmlPoke(BuildConsts.VsixManifestFilePath, versionXPath, newVersion.ToString(), pokeSettings); 111 | 112 | Verbose($"Reading assembly info from ${BuildConsts.VsPackageAssemblyInfoPath} file."); 113 | var currentAssemblyInfo = ParseAssemblyInfo(BuildConsts.VsPackageAssemblyInfoPath); 114 | 115 | Verbose($"Setting assembly info in ${BuildConsts.VsPackageAssemblyInfoPath} file."); 116 | var newAssemblyInfo = new AssemblyInfoSettings(); 117 | newAssemblyInfo.Title = currentAssemblyInfo.Title; 118 | newAssemblyInfo.Description = currentAssemblyInfo.Description; 119 | newAssemblyInfo.FileVersion = newVersion.ToString(); 120 | newAssemblyInfo.Version = newVersion.ToString(); 121 | CreateAssemblyInfo(BuildConsts.VsPackageAssemblyInfoPath, newAssemblyInfo); 122 | }); 123 | 124 | Task("RestoreNuget") 125 | .Does(()=>{ 126 | NuGetRestore(BuildConsts.SolutionFile); 127 | }); 128 | 129 | Task("Build") 130 | .IsDependentOn("Clean") 131 | .IsDependentOn("RestoreNuget") 132 | .IsDependentOn("UpdatePackageVersion") 133 | .Does(config=>{ 134 | MSBuild(BuildConsts.SolutionFile, msBuildSettings =>{ 135 | msBuildSettings.SetConfiguration(config.Configuration); 136 | msBuildSettings.ToolVersion = MSBuildToolVersion.VS2017; 137 | msBuildSettings.SetMSBuildPlatform(MSBuildPlatform.x86); 138 | }); 139 | }); 140 | 141 | Task("Test") 142 | .IsDependentOn("Build") 143 | .Does(config=>{ 144 | var testSettings = new XUnit2Settings(); 145 | testSettings.UseX86 = true; 146 | testSettings.ReportName = "TestResults"; 147 | testSettings.XmlReport = true; 148 | testSettings.OutputDirectory = config.TestProjectOutputDirPath; 149 | XUnit2(config.TestProjectDllOutputPath, testSettings); 150 | }); 151 | 152 | Task("PublishTestResults") 153 | .WithCriteria(TFBuild.IsRunningOnVSTS) 154 | .IsDependentOn("Test") 155 | .Does(config=>{ 156 | var publishData = new TFBuildPublishTestResultsData(); 157 | publishData.Configuration = config.Configuration; 158 | publishData.TestResultsFiles.Add(new FilePath($"{config.TestProjectOutputDirPath}/TestResults.xml")); 159 | publishData.TestRunTitle = "Unit Tests"; 160 | publishData.TestRunner = TFTestRunnerType.XUnit; 161 | TFBuild.Commands.PublishTestResults(publishData); 162 | }); 163 | 164 | Task("CopyBuildOutput") 165 | .IsDependentOn("Build") 166 | .Does(config=>{ 167 | var extensionDir = $"{BuildConsts.BuildOutputDir}/Extension"; 168 | CreateDirectory(extensionDir); 169 | CopyFileToDirectory(config.VsixOutputPath, extensionDir); 170 | CopyFiles(new []{"README.md", "CHANGELOG.md", "CHANGELOG.md", "LICENSE" }, extensionDir); 171 | CopyDirectory("docs", $"{extensionDir}/docs"); 172 | }); 173 | 174 | Task("CopyDeploymentScripts") 175 | .Does(config =>{ 176 | CopyDirectory("deployment_scripts", BuildConsts.BuildOutputDir); 177 | }); 178 | 179 | Task("CopyFiles") 180 | .IsDependentOn("CopyBuildOutput") 181 | .IsDependentOn("CopyDeploymentScripts"); 182 | 183 | Task("PublishBuildArtifacts") 184 | .WithCriteria(TFBuild.IsRunningOnVSTS) 185 | .IsDependentOn("CopyFiles") 186 | .Does(config=>{ 187 | TFBuild.Commands.UploadArtifactDirectory(BuildConsts.BuildOutputDir, "BuildResult"); 188 | }); 189 | 190 | Task("PublishToVsixGallery") 191 | //.IsDependentOn("CopyFiles") 192 | .Does(ctx => { 193 | Context.ExecuteDeploymentScript(); 194 | }); 195 | 196 | Task("PublishToGithub") 197 | //.IsDependentOn("CopyFiles") 198 | .Does(ctx =>{ 199 | Context.ExecuteDeploymentScript(); 200 | }); 201 | 202 | Task("PublishToVsMarketplace") 203 | //.IsDependentOn("CopyFiles") 204 | .Does(ctx =>{ 205 | Context.ExecuteDeploymentScript(); 206 | }); 207 | 208 | Task("Default") 209 | .IsDependentOn("Build") 210 | .IsDependentOn("Test") 211 | .IsDependentOn("PublishTestResults") 212 | .IsDependentOn("CopyFiles") 213 | .IsDependentOn("PublishBuildArtifacts") 214 | .Does(() => { 215 | }); 216 | 217 | RunTarget(target); -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | ########################################################################## 2 | # This is the Cake bootstrapper script for PowerShell. 3 | # This file was downloaded from https://github.com/cake-build/resources 4 | # Feel free to change this file to fit your needs. 5 | ########################################################################## 6 | 7 | <# 8 | 9 | .SYNOPSIS 10 | This is a Powershell script to bootstrap a Cake build. 11 | 12 | .DESCRIPTION 13 | This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) 14 | and execute your Cake build script with the parameters you provide. 15 | 16 | .PARAMETER Script 17 | The build script to execute. 18 | .PARAMETER Target 19 | The build script target to run. 20 | .PARAMETER Configuration 21 | The build configuration to use. 22 | .PARAMETER Verbosity 23 | Specifies the amount of information to be displayed. 24 | .PARAMETER ShowDescription 25 | Shows description about tasks. 26 | .PARAMETER DryRun 27 | Performs a dry run. 28 | .PARAMETER SkipToolPackageRestore 29 | Skips restoring of packages. 30 | .PARAMETER ScriptArgs 31 | Remaining arguments are added here. 32 | 33 | .LINK 34 | https://cakebuild.net 35 | 36 | #> 37 | 38 | [CmdletBinding()] 39 | Param( 40 | [string]$Script = "build.cake", 41 | [string]$Target, 42 | [string]$Configuration, 43 | [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] 44 | [string]$Verbosity, 45 | [switch]$ShowDescription, 46 | [Alias("WhatIf", "Noop")] 47 | [switch]$DryRun, 48 | [switch]$SkipToolPackageRestore, 49 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] 50 | [string[]]$ScriptArgs 51 | ) 52 | 53 | # Attempt to set highest encryption available for SecurityProtocol. 54 | # PowerShell will not set this by default (until maybe .NET 4.6.x). This 55 | # will typically produce a message for PowerShell v2 (just an info 56 | # message though) 57 | try { 58 | # Set TLS 1.2 (3072), then TLS 1.1 (768), then TLS 1.0 (192), finally SSL 3.0 (48) 59 | # Use integers because the enumeration values for TLS 1.2 and TLS 1.1 won't 60 | # exist in .NET 4.0, even though they are addressable if .NET 4.5+ is 61 | # installed (.NET 4.5 is an in-place upgrade). 62 | [System.Net.ServicePointManager]::SecurityProtocol = 3072 -bor 768 -bor 192 -bor 48 63 | } catch { 64 | Write-Output 'Unable to set PowerShell to use TLS 1.2 and TLS 1.1 due to old .NET Framework installed. If you see underlying connection closed or trust errors, you may need to upgrade to .NET Framework 4.5+ and PowerShell v3' 65 | } 66 | 67 | [Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null 68 | function MD5HashFile([string] $filePath) 69 | { 70 | if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) 71 | { 72 | return $null 73 | } 74 | 75 | [System.IO.Stream] $file = $null; 76 | [System.Security.Cryptography.MD5] $md5 = $null; 77 | try 78 | { 79 | $md5 = [System.Security.Cryptography.MD5]::Create() 80 | $file = [System.IO.File]::OpenRead($filePath) 81 | return [System.BitConverter]::ToString($md5.ComputeHash($file)) 82 | } 83 | finally 84 | { 85 | if ($file -ne $null) 86 | { 87 | $file.Dispose() 88 | } 89 | } 90 | } 91 | 92 | function GetProxyEnabledWebClient 93 | { 94 | $wc = New-Object System.Net.WebClient 95 | $proxy = [System.Net.WebRequest]::GetSystemWebProxy() 96 | $proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials 97 | $wc.Proxy = $proxy 98 | return $wc 99 | } 100 | 101 | Write-Host "Preparing to run build script..." 102 | 103 | if(!$PSScriptRoot){ 104 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 105 | } 106 | 107 | $TOOLS_DIR = Join-Path $PSScriptRoot "tools" 108 | $ADDINS_DIR = Join-Path $TOOLS_DIR "Addins" 109 | $MODULES_DIR = Join-Path $TOOLS_DIR "Modules" 110 | $NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" 111 | $CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" 112 | $NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" 113 | $PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" 114 | $PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" 115 | $ADDINS_PACKAGES_CONFIG = Join-Path $ADDINS_DIR "packages.config" 116 | $MODULES_PACKAGES_CONFIG = Join-Path $MODULES_DIR "packages.config" 117 | 118 | # Make sure tools folder exists 119 | if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { 120 | Write-Verbose -Message "Creating tools directory..." 121 | New-Item -Path $TOOLS_DIR -Type directory | out-null 122 | } 123 | 124 | # Make sure that packages.config exist. 125 | if (!(Test-Path $PACKAGES_CONFIG)) { 126 | Write-Verbose -Message "Downloading packages.config..." 127 | try { 128 | $wc = GetProxyEnabledWebClient 129 | $wc.DownloadFile("https://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) 130 | } catch { 131 | Throw "Could not download packages.config." 132 | } 133 | } 134 | 135 | # Try find NuGet.exe in path if not exists 136 | if (!(Test-Path $NUGET_EXE)) { 137 | Write-Verbose -Message "Trying to find nuget.exe in PATH..." 138 | $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_ -PathType Container) } 139 | $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 140 | if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { 141 | Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." 142 | $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName 143 | } 144 | } 145 | 146 | # Try download NuGet.exe if not exists 147 | if (!(Test-Path $NUGET_EXE)) { 148 | Write-Verbose -Message "Downloading NuGet.exe..." 149 | try { 150 | $wc = GetProxyEnabledWebClient 151 | $wc.DownloadFile($NUGET_URL, $NUGET_EXE) 152 | } catch { 153 | Throw "Could not download NuGet.exe." 154 | } 155 | } 156 | 157 | # Save nuget.exe path to environment to be available to child processed 158 | $ENV:NUGET_EXE = $NUGET_EXE 159 | 160 | # Restore tools from NuGet? 161 | if(-Not $SkipToolPackageRestore.IsPresent) { 162 | Push-Location 163 | Set-Location $TOOLS_DIR 164 | 165 | # Check for changes in packages.config and remove installed tools if true. 166 | [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) 167 | if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or 168 | ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { 169 | Write-Verbose -Message "Missing or changed package.config hash..." 170 | Get-ChildItem -Exclude packages.config,nuget.exe,Cake.Bakery | 171 | Remove-Item -Recurse 172 | } 173 | 174 | Write-Verbose -Message "Restoring tools from NuGet..." 175 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" 176 | 177 | if ($LASTEXITCODE -ne 0) { 178 | Throw "An error occurred while restoring NuGet tools." 179 | } 180 | else 181 | { 182 | $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" 183 | } 184 | Write-Verbose -Message ($NuGetOutput | out-string) 185 | 186 | Pop-Location 187 | } 188 | 189 | # Restore addins from NuGet 190 | if (Test-Path $ADDINS_PACKAGES_CONFIG) { 191 | Push-Location 192 | Set-Location $ADDINS_DIR 193 | 194 | Write-Verbose -Message "Restoring addins from NuGet..." 195 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$ADDINS_DIR`"" 196 | 197 | if ($LASTEXITCODE -ne 0) { 198 | Throw "An error occurred while restoring NuGet addins." 199 | } 200 | 201 | Write-Verbose -Message ($NuGetOutput | out-string) 202 | 203 | Pop-Location 204 | } 205 | 206 | # Restore modules from NuGet 207 | if (Test-Path $MODULES_PACKAGES_CONFIG) { 208 | Push-Location 209 | Set-Location $MODULES_DIR 210 | 211 | Write-Verbose -Message "Restoring modules from NuGet..." 212 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$MODULES_DIR`"" 213 | 214 | if ($LASTEXITCODE -ne 0) { 215 | Throw "An error occurred while restoring NuGet modules." 216 | } 217 | 218 | Write-Verbose -Message ($NuGetOutput | out-string) 219 | 220 | Pop-Location 221 | } 222 | 223 | # Make sure that Cake has been installed. 224 | if (!(Test-Path $CAKE_EXE)) { 225 | Throw "Could not find Cake.exe at $CAKE_EXE" 226 | } 227 | 228 | 229 | 230 | # Build Cake arguments 231 | $cakeArguments = @("$Script"); 232 | if ($Target) { $cakeArguments += "-target=$Target" } 233 | if ($Configuration) { $cakeArguments += "-configuration=$Configuration" } 234 | if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" } 235 | if ($ShowDescription) { $cakeArguments += "-showdescription" } 236 | if ($DryRun) { $cakeArguments += "-dryrun" } 237 | $cakeArguments += $ScriptArgs 238 | 239 | # Start Cake 240 | Write-Host "Running build script..." 241 | &$CAKE_EXE $cakeArguments 242 | exit $LASTEXITCODE 243 | -------------------------------------------------------------------------------- /build_azure_pipelines.yml: -------------------------------------------------------------------------------- 1 | 2 | # Reference https://aka.ms/yaml 3 | name: $(Rev:r) 4 | trigger: 5 | - master 6 | 7 | pool: 8 | vmImage: windows-2019 9 | 10 | steps: 11 | - task: NuGetToolInstaller@0 12 | displayName: Get NuGet 13 | name: GetNuGet 14 | enabled: true 15 | inputs: 16 | checkLatest: true 17 | - task: Cake@0 18 | displayName: Execute build 19 | name: CakeBuild 20 | enabled: true 21 | condition: succeeded() 22 | inputs: 23 | script: build.cake 24 | target: Default 25 | verbosity: Verbose 26 | useBuildAgentNuGetExe: true 27 | -------------------------------------------------------------------------------- /cake.config: -------------------------------------------------------------------------------- 1 | ; This is the default configuration file for Cake. 2 | ; This file was downloaded from https://github.com/cake-build/resources 3 | 4 | [Nuget] 5 | Source=https://api.nuget.org/v3/index.json 6 | UseInProcessClient=true 7 | LoadDependencies=false 8 | 9 | [Paths] 10 | Tools=./tools 11 | Addins=./tools/Addins 12 | Modules=./tools/Modules 13 | 14 | [Settings] 15 | SkipVerification=false 16 | -------------------------------------------------------------------------------- /deployment_scripts/deploy.cake: -------------------------------------------------------------------------------- 1 | #addin nuget:?package=Cake.Powershell&version=0.4.7 2 | #load "deploy_common.cake" 3 | 4 | var target = Argument(DeploymentArgs.Target, "Default"); 5 | 6 | public static class DeploymentConsts 7 | { 8 | 9 | } 10 | 11 | public class DeploymentInfo 12 | { 13 | public string DeploymentPackagePath {get; set;} 14 | } 15 | 16 | Setup(ctx => 17 | { 18 | Information("Running tasks..."); 19 | var deploymentConfig = new DeploymentInfo(); 20 | deploymentConfig.DeploymentPackagePath = Argument(DeploymentArgs.DeploymentPackagePath, "./Extension"); 21 | 22 | return deploymentConfig; 23 | }); 24 | 25 | Teardown(ctx => 26 | { 27 | Information("Finished running tasks."); 28 | }); 29 | 30 | Task("PublishToVsixGallery") 31 | .Does(config => { 32 | Information("Publishing extension to staging environment Vsix Gallery (http://vsixgallery.com/). "); 33 | Verbose($"Serching for the VSIX file in {config.DeploymentPackagePath}."); 34 | var packageFile = GetFiles($"{config.DeploymentPackagePath}/*.vsix").FirstOrDefault()?.FullPath; 35 | if(string.IsNullOrEmpty(packageFile)){ 36 | throw new Exception("Package file not found."); 37 | } 38 | 39 | Verbose($"Calling powershell script with argument -VsixPath \"{packageFile}\"."); 40 | var settings = new PowershellSettings(); 41 | settings.WorkingDirectory = new DirectoryPath("deployment_tools"); 42 | settings.WithArguments(args => args 43 | .AppendQuoted("VsixPath", packageFile)); 44 | StartPowershellFile("VsixGalleryPublish.ps1", settings); 45 | }); 46 | 47 | Task("PublishToGithub") 48 | .Does(ctx =>{ 49 | 50 | }); 51 | 52 | Task("PublishToVsMarketplace") 53 | .Does(ctx =>{ 54 | Information("Publishing extension to official Visual Studio Marketplace (https://marketplace.visualstudio.com/)."); 55 | }); 56 | 57 | Task("Default") 58 | .Does(()=>{ 59 | Warning("Nothing to do pick different target."); 60 | }); 61 | 62 | RunTarget(target); -------------------------------------------------------------------------------- /deployment_scripts/deploy.ps1: -------------------------------------------------------------------------------- 1 | ########################################################################## 2 | # This is the Cake bootstrapper script for PowerShell. 3 | # This file was downloaded from https://github.com/cake-build/resources 4 | # Feel free to change this file to fit your needs. 5 | ########################################################################## 6 | 7 | <# 8 | 9 | .SYNOPSIS 10 | This is a Powershell script to bootstrap a Cake build. 11 | 12 | .DESCRIPTION 13 | This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) 14 | and execute your Cake build script with the parameters you provide. 15 | 16 | .PARAMETER Script 17 | The build script to execute. 18 | .PARAMETER Target 19 | The build script target to run. 20 | .PARAMETER Configuration 21 | The build configuration to use. 22 | .PARAMETER Verbosity 23 | Specifies the amount of information to be displayed. 24 | .PARAMETER ShowDescription 25 | Shows description about tasks. 26 | .PARAMETER DryRun 27 | Performs a dry run. 28 | .PARAMETER SkipToolPackageRestore 29 | Skips restoring of packages. 30 | .PARAMETER ScriptArgs 31 | Remaining arguments are added here. 32 | 33 | .LINK 34 | https://cakebuild.net 35 | 36 | #> 37 | 38 | [CmdletBinding()] 39 | Param( 40 | [string]$Script = "deploy.cake", 41 | [string]$Target, 42 | [string]$Configuration, 43 | [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] 44 | [string]$Verbosity, 45 | [switch]$ShowDescription, 46 | [Alias("WhatIf", "Noop")] 47 | [switch]$DryRun, 48 | [switch]$SkipToolPackageRestore, 49 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] 50 | [string[]]$ScriptArgs 51 | ) 52 | 53 | # Attempt to set highest encryption available for SecurityProtocol. 54 | # PowerShell will not set this by default (until maybe .NET 4.6.x). This 55 | # will typically produce a message for PowerShell v2 (just an info 56 | # message though) 57 | try { 58 | # Set TLS 1.2 (3072), then TLS 1.1 (768), then TLS 1.0 (192), finally SSL 3.0 (48) 59 | # Use integers because the enumeration values for TLS 1.2 and TLS 1.1 won't 60 | # exist in .NET 4.0, even though they are addressable if .NET 4.5+ is 61 | # installed (.NET 4.5 is an in-place upgrade). 62 | [System.Net.ServicePointManager]::SecurityProtocol = 3072 -bor 768 -bor 192 -bor 48 63 | } catch { 64 | Write-Output 'Unable to set PowerShell to use TLS 1.2 and TLS 1.1 due to old .NET Framework installed. If you see underlying connection closed or trust errors, you may need to upgrade to .NET Framework 4.5+ and PowerShell v3' 65 | } 66 | 67 | [Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null 68 | function MD5HashFile([string] $filePath) 69 | { 70 | if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) 71 | { 72 | return $null 73 | } 74 | 75 | [System.IO.Stream] $file = $null; 76 | [System.Security.Cryptography.MD5] $md5 = $null; 77 | try 78 | { 79 | $md5 = [System.Security.Cryptography.MD5]::Create() 80 | $file = [System.IO.File]::OpenRead($filePath) 81 | return [System.BitConverter]::ToString($md5.ComputeHash($file)) 82 | } 83 | finally 84 | { 85 | if ($file -ne $null) 86 | { 87 | $file.Dispose() 88 | } 89 | } 90 | } 91 | 92 | function GetProxyEnabledWebClient 93 | { 94 | $wc = New-Object System.Net.WebClient 95 | $proxy = [System.Net.WebRequest]::GetSystemWebProxy() 96 | $proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials 97 | $wc.Proxy = $proxy 98 | return $wc 99 | } 100 | 101 | Write-Host "Preparing to run build script..." 102 | 103 | if(!$PSScriptRoot){ 104 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 105 | } 106 | 107 | $TOOLS_DIR = Join-Path $PSScriptRoot "tools" 108 | $ADDINS_DIR = Join-Path $TOOLS_DIR "Addins" 109 | $MODULES_DIR = Join-Path $TOOLS_DIR "Modules" 110 | $NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" 111 | $CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" 112 | $NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" 113 | $PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" 114 | $PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" 115 | $ADDINS_PACKAGES_CONFIG = Join-Path $ADDINS_DIR "packages.config" 116 | $MODULES_PACKAGES_CONFIG = Join-Path $MODULES_DIR "packages.config" 117 | 118 | # Make sure tools folder exists 119 | if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { 120 | Write-Verbose -Message "Creating tools directory..." 121 | New-Item -Path $TOOLS_DIR -Type directory | out-null 122 | } 123 | 124 | # Make sure that packages.config exist. 125 | if (!(Test-Path $PACKAGES_CONFIG)) { 126 | Write-Verbose -Message "Downloading packages.config..." 127 | try { 128 | $wc = GetProxyEnabledWebClient 129 | $wc.DownloadFile("https://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) 130 | } catch { 131 | Throw "Could not download packages.config." 132 | } 133 | } 134 | 135 | # Try find NuGet.exe in path if not exists 136 | if (!(Test-Path $NUGET_EXE)) { 137 | Write-Verbose -Message "Trying to find nuget.exe in PATH..." 138 | $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_ -PathType Container) } 139 | $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 140 | if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { 141 | Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." 142 | $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName 143 | } 144 | } 145 | 146 | # Try download NuGet.exe if not exists 147 | if (!(Test-Path $NUGET_EXE)) { 148 | Write-Verbose -Message "Downloading NuGet.exe..." 149 | try { 150 | $wc = GetProxyEnabledWebClient 151 | $wc.DownloadFile($NUGET_URL, $NUGET_EXE) 152 | } catch { 153 | Throw "Could not download NuGet.exe." 154 | } 155 | } 156 | 157 | # Save nuget.exe path to environment to be available to child processed 158 | $ENV:NUGET_EXE = $NUGET_EXE 159 | 160 | # Restore tools from NuGet? 161 | if(-Not $SkipToolPackageRestore.IsPresent) { 162 | Push-Location 163 | Set-Location $TOOLS_DIR 164 | 165 | # Check for changes in packages.config and remove installed tools if true. 166 | [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) 167 | if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or 168 | ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { 169 | Write-Verbose -Message "Missing or changed package.config hash..." 170 | Get-ChildItem -Exclude packages.config,nuget.exe,Cake.Bakery | 171 | Remove-Item -Recurse 172 | } 173 | 174 | Write-Verbose -Message "Restoring tools from NuGet..." 175 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" 176 | 177 | if ($LASTEXITCODE -ne 0) { 178 | Throw "An error occurred while restoring NuGet tools." 179 | } 180 | else 181 | { 182 | $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" 183 | } 184 | Write-Verbose -Message ($NuGetOutput | out-string) 185 | 186 | Pop-Location 187 | } 188 | 189 | # Restore addins from NuGet 190 | if (Test-Path $ADDINS_PACKAGES_CONFIG) { 191 | Push-Location 192 | Set-Location $ADDINS_DIR 193 | 194 | Write-Verbose -Message "Restoring addins from NuGet..." 195 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$ADDINS_DIR`"" 196 | 197 | if ($LASTEXITCODE -ne 0) { 198 | Throw "An error occurred while restoring NuGet addins." 199 | } 200 | 201 | Write-Verbose -Message ($NuGetOutput | out-string) 202 | 203 | Pop-Location 204 | } 205 | 206 | # Restore modules from NuGet 207 | if (Test-Path $MODULES_PACKAGES_CONFIG) { 208 | Push-Location 209 | Set-Location $MODULES_DIR 210 | 211 | Write-Verbose -Message "Restoring modules from NuGet..." 212 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$MODULES_DIR`"" 213 | 214 | if ($LASTEXITCODE -ne 0) { 215 | Throw "An error occurred while restoring NuGet modules." 216 | } 217 | 218 | Write-Verbose -Message ($NuGetOutput | out-string) 219 | 220 | Pop-Location 221 | } 222 | 223 | # Make sure that Cake has been installed. 224 | if (!(Test-Path $CAKE_EXE)) { 225 | Throw "Could not find Cake.exe at $CAKE_EXE" 226 | } 227 | 228 | 229 | 230 | # Build Cake arguments 231 | $cakeArguments = @("$Script"); 232 | if ($Target) { $cakeArguments += "-target=$Target" } 233 | if ($Configuration) { $cakeArguments += "-configuration=$Configuration" } 234 | if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" } 235 | if ($ShowDescription) { $cakeArguments += "-showdescription" } 236 | if ($DryRun) { $cakeArguments += "-dryrun" } 237 | $cakeArguments += $ScriptArgs 238 | 239 | # Start Cake 240 | Write-Host "Running build script..." 241 | &$CAKE_EXE $cakeArguments 242 | exit $LASTEXITCODE 243 | -------------------------------------------------------------------------------- /deployment_scripts/deploy_common.cake: -------------------------------------------------------------------------------- 1 | public static class DeploymentArgs 2 | { 3 | public const string Target = "target"; 4 | public const string DeploymentPackagePath = "deploymentPackagePath"; 5 | 6 | public const string Verbosity = "verbosity"; 7 | 8 | public static string[] ArgumentList = new string[]{ Target, DeploymentPackagePath, Verbosity }; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /deployment_scripts/deployment_tools/VsixGalleryPublish.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [string]$VsixPath 3 | ) 4 | Write-Host "Running powershell to publish VSIX to Vsix Gallery" 5 | Write-Host "Vsix Path: $VsixPath" 6 | 7 | # Get publish script 8 | (new-object Net.WebClient).DownloadString("https://raw.github.com/madskristensen/ExtensionScripts/master/AppVeyor/vsix.ps1") | iex 9 | # Perform publish 10 | Vsix-PublishToGallery "$VsixPath" -------------------------------------------------------------------------------- /docs/img/AddProgressReviewConfiguration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guudan/Vs-AddMultipleProjectsToSolution/e236b1d430d5483d7f4ea74877eaebe8a6a411e5/docs/img/AddProgressReviewConfiguration.png -------------------------------------------------------------------------------- /docs/img/AddProgressReviewResults.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guudan/Vs-AddMultipleProjectsToSolution/e236b1d430d5483d7f4ea74877eaebe8a6a411e5/docs/img/AddProgressReviewResults.png -------------------------------------------------------------------------------- /docs/img/AddResultWithSolutionFolders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guudan/Vs-AddMultipleProjectsToSolution/e236b1d430d5483d7f4ea74877eaebe8a6a411e5/docs/img/AddResultWithSolutionFolders.png -------------------------------------------------------------------------------- /docs/img/ClickAddMultipleCommand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guudan/Vs-AddMultipleProjectsToSolution/e236b1d430d5483d7f4ea74877eaebe8a6a411e5/docs/img/ClickAddMultipleCommand.png -------------------------------------------------------------------------------- /docs/img/ClickLoadFromFolder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guudan/Vs-AddMultipleProjectsToSolution/e236b1d430d5483d7f4ea74877eaebe8a6a411e5/docs/img/ClickLoadFromFolder.png -------------------------------------------------------------------------------- /docs/img/SelectFolderForLoadingProjects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guudan/Vs-AddMultipleProjectsToSolution/e236b1d430d5483d7f4ea74877eaebe8a6a411e5/docs/img/SelectFolderForLoadingProjects.png -------------------------------------------------------------------------------- /docs/img/SelectProjectsToAdd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guudan/Vs-AddMultipleProjectsToSolution/e236b1d430d5483d7f4ea74877eaebe8a6a411e5/docs/img/SelectProjectsToAdd.png -------------------------------------------------------------------------------- /source/SolutionInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyConfiguration("")] 5 | [assembly: AssemblyCompany("")] 6 | [assembly: AssemblyProduct("Add Multiple Projects To Solution")] 7 | [assembly: AssemblyCopyright("2019 Maciej Gudanowicz")] 8 | [assembly: AssemblyTrademark("")] 9 | [assembly: AssemblyCulture("")] 10 | 11 | [assembly: ComVisible(false)] -------------------------------------------------------------------------------- /source/Vs.AddMultipleProjectsToSolution.Tests/Gui/ViewModels/MapperTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Vs.AddMultipleProjectsToSolution.Gui.ViewModels; 6 | using Xunit; 7 | 8 | namespace Vs.AddMultipleProjectsToSolution.Tests.Gui.ViewModels 9 | { 10 | public class MapperTest 11 | { 12 | [Fact] 13 | public async Task MapFilesToViewModelAsync_ForGivenMultipleProjectPaths_CreatesTreeOfProjectDirectories() 14 | { 15 | var rootDirectory = @"c:\test\input_directory"; 16 | var projectFiles = new 17 | { 18 | FirstProject = @"c:\test\input_directory\Project1.csproj", 19 | SecondProject = @"c:\test\input_directory\ChildDirWith2Projects\Project2\Project2.csproj", 20 | ThirdProject = @"c:\test\input_directory\ChildDirWith2Projects\Project3\Project3.csproj", 21 | FourthProject = @"c:\test\input_directory\Project4\Project4.csproj", 22 | FifthProject = @"c:\test\input_directory\Project4\Project5.csproj", 23 | SixthProject = @"c:\test\input_directory\Project4\Project6\Project6" 24 | }; 25 | 26 | var expectedObjectTree = new List(); 27 | expectedObjectTree.Add(new FsProjectDirectoryViewModel("Project1", projectFiles.FirstProject)); 28 | 29 | var inputDirectory = new FsDirectoryViewModel("input_directory"); 30 | 31 | var childDirWith2Projects = new FsDirectoryViewModel("ChildDirWith2Projects"); 32 | childDirWith2Projects.ChildItems.Add(new FsProjectDirectoryViewModel("Project2", projectFiles.SecondProject)); 33 | childDirWith2Projects.ChildItems.Add(new FsProjectDirectoryViewModel("Project3", projectFiles.ThirdProject)); 34 | inputDirectory.ChildItems.Add(childDirWith2Projects); 35 | 36 | inputDirectory.ChildItems.Add(new FsProjectDirectoryViewModel("Project4", projectFiles.FourthProject)); 37 | inputDirectory.ChildItems.Add(new FsProjectDirectoryViewModel("Project5", projectFiles.FifthProject)); 38 | 39 | var project4Directory = new FsDirectoryViewModel("Project4"); 40 | project4Directory.ChildItems.Add(new FsProjectDirectoryViewModel("Project6", projectFiles.SixthProject)); 41 | inputDirectory.ChildItems.Add(project4Directory); 42 | 43 | expectedObjectTree.Add(inputDirectory); 44 | 45 | var projectList = new[] 46 | { 47 | projectFiles.FirstProject, projectFiles.SecondProject, projectFiles.ThirdProject, 48 | projectFiles.FourthProject, projectFiles.FifthProject, projectFiles.SixthProject 49 | }; 50 | 51 | var mapper = Mapper.Instance; 52 | var resultObjectTree = await mapper.MapFilesToViewModelAsync(rootDirectory, projectList, CancellationToken.None); 53 | 54 | foreach (var expectedTreeItem in expectedObjectTree) 55 | { 56 | Assert.Contains(expectedTreeItem, resultObjectTree); 57 | } 58 | Assert.Equal(expectedObjectTree.Count, resultObjectTree.Length); 59 | } 60 | 61 | [Fact] 62 | public async Task MapFilesToViewModelAsync_FilePathEqualsRootDirectory_FileSkipped() 63 | { 64 | var rootDirectory = @"c:\test\input_directory"; 65 | var mapper = Mapper.Instance; 66 | var resultingObjectTree = 67 | await mapper.MapFilesToViewModelAsync(rootDirectory, new[] {rootDirectory}, CancellationToken.None); 68 | Assert.Empty(resultingObjectTree); 69 | } 70 | 71 | [Fact] 72 | public async Task MapFilesToViewModelAsync_FilePathNull_ThrowsException() 73 | { 74 | var rootDirectory = @"c:\test\input_directory"; 75 | var mapper = Mapper.Instance; 76 | await Assert.ThrowsAnyAsync(() => 77 | mapper.MapFilesToViewModelAsync(rootDirectory, new string[] { null }, CancellationToken.None)); 78 | } 79 | 80 | [Fact] 81 | public async Task MapFilesToViewModelAsync_FilesListNull_ThrowsException() 82 | { 83 | var rootDirectory = @"c:\test\input_directory"; 84 | var mapper = Mapper.Instance; 85 | await Assert.ThrowsAnyAsync(() => 86 | mapper.MapFilesToViewModelAsync(rootDirectory, null, CancellationToken.None)); 87 | } 88 | 89 | [Fact] 90 | public async Task MapFilesToViewModelAsync_FileNotBelowRootFolder_ThrowsException() 91 | { 92 | var rootDirectory = @"c:\test\input_directory"; 93 | var testFilePath = @"c:\test\some_other_directory\Project1.csproj"; 94 | var mapper = Mapper.Instance; 95 | await Assert.ThrowsAnyAsync(()=> 96 | mapper.MapFilesToViewModelAsync(rootDirectory, new[] {testFilePath}, CancellationToken.None)); 97 | } 98 | 99 | [Fact] 100 | public async Task MapFilesToViewModelAsync_RootFolderNull_ThrowsException() 101 | { 102 | var testFilePath = @"c:\test\some_other_directory\Project1.csproj"; 103 | var mapper = Mapper.Instance; 104 | await Assert.ThrowsAnyAsync(() => 105 | mapper.MapFilesToViewModelAsync(null, new[] { testFilePath }, CancellationToken.None)); 106 | } 107 | 108 | [Fact] 109 | public async Task MapFilesToViewModelAsync_RootFolderEmpty_ThrowsException() 110 | { 111 | var testFilePath = @"c:\test\some_other_directory\Project1.csproj"; 112 | var mapper = Mapper.Instance; 113 | await Assert.ThrowsAnyAsync(() => 114 | mapper.MapFilesToViewModelAsync(string.Empty, new[] { testFilePath }, CancellationToken.None)); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /source/Vs.AddMultipleProjectsToSolution.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("Tests For Add Multiple Projects To Solution")] 6 | [assembly: AssemblyDescription("Visual Studio extension that simplifies adding multiple projects to solution.")] 7 | 8 | [assembly: AssemblyVersion("1.0.0.0")] 9 | [assembly: AssemblyFileVersion("1.0.0.0")] 10 | -------------------------------------------------------------------------------- /source/Vs.AddMultipleProjectsToSolution.Tests/Utilities/FileEnumerationHelperTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using NSubstitute; 6 | using Vs.AddMultipleProjectsToSolution.Utilities; 7 | using Xunit; 8 | 9 | namespace Vs.AddMultipleProjectsToSolution.Tests.Utilities 10 | { 11 | public class FileEnumerationHelperTest 12 | { 13 | 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/Vs.AddMultipleProjectsToSolution.Tests/Vs.AddMultipleProjectsToSolution.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Debug 8 | AnyCPU 9 | {A2F5099E-E791-44FE-930C-8DA386F33913} 10 | Library 11 | Properties 12 | Vs.AddMultipleProjectsToSolution.Tests 13 | Vs.AddMultipleProjectsToSolution.Tests 14 | v4.6.1 15 | 512 16 | true 17 | 18 | 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | ..\packages\Castle.Core.4.2.0\lib\net45\Castle.Core.dll 40 | 41 | 42 | ..\packages\NSubstitute.3.1.0\lib\net46\NSubstitute.dll 43 | 44 | 45 | 46 | 47 | 48 | ..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | ..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll 58 | 59 | 60 | ..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll 61 | 62 | 63 | ..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll 64 | 65 | 66 | ..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll 67 | 68 | 69 | 70 | 71 | 72 | 73 | Properties\SolutionInfo.cs 74 | 75 | 76 | 77 | 78 | 79 | {EE6566A1-33E0-463B-B1CA-BA88102E035E} 80 | Vs.AddMultipleProjectsToSolution 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /source/Vs.AddMultipleProjectsToSolution.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /source/Vs.AddMultipleProjectsToSolution.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.271 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vs.AddMultipleProjectsToSolution", "Vs.AddMultipleProjectsToSolution\Vs.AddMultipleProjectsToSolution.csproj", "{EE6566A1-33E0-463B-B1CA-BA88102E035E}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vs.AddMultipleProjectsToSolution.Tests", "Vs.AddMultipleProjectsToSolution.Tests\Vs.AddMultipleProjectsToSolution.Tests.csproj", "{A2F5099E-E791-44FE-930C-8DA386F33913}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{46A9FF98-AA29-423C-AEAB-4520998970FF}" 11 | ProjectSection(SolutionItems) = preProject 12 | ..\CHANGELOG.md = ..\CHANGELOG.md 13 | ..\CONTRIBUTING.md = ..\CONTRIBUTING.md 14 | ..\README.md = ..\README.md 15 | EndProjectSection 16 | EndProject 17 | Global 18 | GlobalSection(CrmCodeGeneration) = preSolution 19 | UseSSL = False 20 | UseIFD = False 21 | UseOnline = False 22 | UseOffice365 = False 23 | ServerName = 24 | ServerPort = 25 | HomeRealm = 26 | Domain = 27 | Organization = DEV-CRM 28 | IncludeEntities = account,contact,lead,opportunity,systemuser 29 | IncludeNonStandard = False 30 | EndGlobalSection 31 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 32 | Debug|Any CPU = Debug|Any CPU 33 | Release|Any CPU = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 36 | {EE6566A1-33E0-463B-B1CA-BA88102E035E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {EE6566A1-33E0-463B-B1CA-BA88102E035E}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {EE6566A1-33E0-463B-B1CA-BA88102E035E}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {EE6566A1-33E0-463B-B1CA-BA88102E035E}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {A2F5099E-E791-44FE-930C-8DA386F33913}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {A2F5099E-E791-44FE-930C-8DA386F33913}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {A2F5099E-E791-44FE-930C-8DA386F33913}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {A2F5099E-E791-44FE-930C-8DA386F33913}.Release|Any CPU.Build.0 = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(ExtensibilityGlobals) = postSolution 49 | SolutionGuid = {39E69A80-C2AA-46FF-8512-D7F35A34834A} 50 | EndGlobalSection 51 | EndGlobal 52 | -------------------------------------------------------------------------------- /source/Vs.AddMultipleProjectsToSolution.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /source/Vs.AddMultipleProjectsToSolution/Commands/AddMultipleProjectsCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using Microsoft.VisualStudio.Shell; 4 | using Vs.AddMultipleProjectsToSolution.Gui; 5 | using Vs.AddMultipleProjectsToSolution.Gui.ViewModels; 6 | using Vs.AddMultipleProjectsToSolution.Utilities; 7 | using Task = System.Threading.Tasks.Task; 8 | 9 | namespace Vs.AddMultipleProjectsToSolution 10 | { 11 | internal sealed class AddMultipleProjectsCommand 12 | { 13 | public static readonly Guid CommandSet = new Guid("52eefd8b-3e82-4d3a-a15f-5ef227b118a9"); 14 | public const int SolutionContextCommandId = 0x0100; 15 | 16 | private readonly AsyncPackage _Package; 17 | 18 | private AddMultipleProjectsCommand(AsyncPackage package, OleMenuCommandService commandService) 19 | { 20 | _Package = package ?? throw new ArgumentNullException(nameof(package)); 21 | commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); 22 | 23 | var solutionContextCommandID = new CommandID(CommandSet, SolutionContextCommandId); 24 | var solutionContextMenuItem = new MenuCommand(Execute, solutionContextCommandID); 25 | commandService.AddCommand(solutionContextMenuItem); 26 | } 27 | 28 | public static AddMultipleProjectsCommand Instance { get; private set; } 29 | 30 | private IAsyncServiceProvider ServiceProvider => _Package; 31 | 32 | public static async Task InitializeAsync(AsyncPackage package) 33 | { 34 | // Switch to the main thread - the call to AddCommand in AddMultipleProjectsCommand's constructor requires 35 | // the UI thread. 36 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 37 | 38 | var commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 39 | Instance = new AddMultipleProjectsCommand(package, commandService); 40 | } 41 | 42 | private void Execute(object sender, EventArgs e) 43 | { 44 | ThreadHelper.ThrowIfNotOnUIThread(); 45 | var solution = NVsSolution.Create(); 46 | var windowService = WindowService.Instance; 47 | var viewModel = new AddMultipleProjectsConfigurationViewModel(windowService, solution); 48 | windowService.OpenAddMultipleProjectsWindow(viewModel); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /source/Vs.AddMultipleProjectsToSolution/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 |  2 | // This file is used by Code Analysis to maintain SuppressMessage 3 | // attributes that are applied to this project. 4 | // Project-level suppressions either have no target or are given 5 | // a specific target and scoped to a namespace, type, member, etc. 6 | 7 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "", Scope = "member", Target = "~M:Vs.AddMultipleProjectsToSolution.Gui.ViewModels.AddMultipleProjectsProgressViewModel.Start(System.Object)")] 8 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "", Scope = "member", Target = "~M:Vs.AddMultipleProjectsToSolution.Gui.ViewModels.LoadProjectsWindowViewModel.LoadProjects(System.Object)")] 9 | 10 | -------------------------------------------------------------------------------- /source/Vs.AddMultipleProjectsToSolution/Gui/AddMultipleProjectsConfigurationControl.xaml: -------------------------------------------------------------------------------- 1 |  15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |