├── PMCCommand
├── App.config
├── packages.config
├── GlobalSuppressions.cs
├── GuidsAndIds.cs
├── IOleMessageFilter.cs
├── Properties
│ └── AssemblyInfo.cs
├── CmdLineOptions.cs
├── MessageFilter.cs
├── PMCCommand.csproj
└── Program.cs
├── LICENSE
├── PMCCommand.sln
├── README.md
└── .gitignore
/PMCCommand/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/PMCCommand/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/PMCCommand/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | // This file is used by Code Analysis to maintain SuppressMessage
2 | // attributes that are applied to this project.
3 | // Project-level suppressions either have no target or are given
4 | // a specific target and scoped to a namespace, type, member, etc.
5 |
6 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1652:Enable XML documentation output", Justification = "No XML copyright needed", Scope = "namespace", Target = "~N:PMCCommand")]
7 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File must have header", Justification = "Not necessary", Scope = "namespace", Target = "~N:PMCCommand")]
8 |
--------------------------------------------------------------------------------
/PMCCommand/GuidsAndIds.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Benjamin Trent. All rights reserved. See LICENSE file in project root
2 |
3 | namespace PMCCommand
4 | {
5 | ///
6 | /// The collection of command GUIDs and IDs
7 | /// These were gathered through https://msdn.microsoft.com/en-us/library/cc826040.aspx && https://github.com/mono/nuget/tree/master/src/VsConsole/Console
8 | ///
9 | public static class GuidsAndIds
10 | {
11 | public const string GuidStdCommandSet2K = "{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}";
12 | public const string GuidNuGetConsoleCmdSet = "{1E8A55F6-C18D-407F-91C8-94B02AE1CED6}";
13 | public const int CmdidOutputPaneCombo = 1627;
14 | public const int CmdidNuGetSources = 1024;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/PMCCommand/IOleMessageFilter.cs:
--------------------------------------------------------------------------------
1 | namespace PMCCommand
2 | {
3 | using System;
4 | using System.Runtime.InteropServices;
5 |
6 | ///
7 | /// Copied wholesale from: https://msdn.microsoft.com/en-us/library/ms228772.aspx
8 | ///
9 | [ComImport]
10 | [Guid("00000016-0000-0000-C000-000000000046")]
11 | [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
12 | internal interface IOleMessageFilter
13 | {
14 | [PreserveSig]
15 | int HandleInComingCall(
16 | int dwCallType,
17 | IntPtr hTaskCaller,
18 | int dwTickCount,
19 | IntPtr lpInterfaceInfo);
20 |
21 | [PreserveSig]
22 | int RetryRejectedCall(
23 | IntPtr hTaskCallee,
24 | int dwTickCount,
25 | int dwRejectType);
26 |
27 | [PreserveSig]
28 | int MessagePending(
29 | IntPtr hTaskCallee,
30 | int dwTickCount,
31 | int dwPendingType);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Benjamin Trent
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 |
--------------------------------------------------------------------------------
/PMCCommand/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Benjamin Trent. All rights reserved. See LICENSE file in project root
2 |
3 | using System.Reflection;
4 | using System.Runtime.InteropServices;
5 |
6 | // General Information about an assembly is controlled through the following
7 | // set of attributes. Change these attribute values to modify the information
8 | // associated with an assembly.
9 | [assembly: AssemblyTitle("PMCCommand")]
10 | [assembly: AssemblyDescription("")]
11 | [assembly: AssemblyConfiguration("")]
12 | [assembly: AssemblyProduct("PMCCommand")]
13 | [assembly: AssemblyCopyright("Copyright © Benjamin Trent 2017")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("aa26f6f0-8ddd-4191-bc04-ff749425216e")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.1.0")]
36 | [assembly: AssemblyFileVersion("1.0.1.0")]
37 |
--------------------------------------------------------------------------------
/PMCCommand/CmdLineOptions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Benjamin Trent. All rights reserved. See LICENSE file in project root
2 |
3 | namespace PMCCommand
4 | {
5 | using CommandLine;
6 | using CommandLine.Text;
7 |
8 | public class CmdLineOptions
9 | {
10 | [Option('n', "nugetcommand", Required = true, HelpText = "The NuGet package management console command to execute.")]
11 | public string NuGetCommand { get; set; }
12 |
13 | [Option('p', "project", Required = true, HelpText = "The full path of the .csproj or .sln file in which to run the command.")]
14 | public string ProjectPath { get; set; }
15 |
16 | [Option('v', "vsversion", Required = false, HelpText = "The VisualStudio version for DTE interaction.", DefaultValue = "15.0")]
17 | public string VisualStudioVersion { get; set; }
18 |
19 | [Option('d', "debug", Required = false, HelpText = "Print debuging output to the console.", DefaultValue = false)]
20 | public bool Debug { get; set; }
21 |
22 | [HelpOption]
23 | public string GetUsage()
24 | {
25 | var help = new HelpText
26 | {
27 | Heading = new HeadingInfo("PMCCommand", "1.0.0"),
28 | Copyright = new CopyrightInfo("Benjamin Trent", 2017),
29 | AdditionalNewLineAfterOption = true,
30 | AddDashesToOption = true
31 | };
32 | help.AddOptions(this);
33 | help.AddPostOptionsLine("Example: PMCCommand --nugetcommand \"Update-Package Newtonsoft.Json\" --project \"C:\\Foo\\Bar\\foobar.csproj\"");
34 | return help;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/PMCCommand.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.25420.1
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PMCCommand", "PMCCommand\PMCCommand.csproj", "{AA26F6F0-8DDD-4191-BC04-FF749425216E}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Debug|x64 = Debug|x64
12 | Debug|x86 = Debug|x86
13 | Release|Any CPU = Release|Any CPU
14 | Release|x64 = Release|x64
15 | Release|x86 = Release|x86
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {AA26F6F0-8DDD-4191-BC04-FF749425216E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {AA26F6F0-8DDD-4191-BC04-FF749425216E}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {AA26F6F0-8DDD-4191-BC04-FF749425216E}.Debug|x64.ActiveCfg = Debug|x64
21 | {AA26F6F0-8DDD-4191-BC04-FF749425216E}.Debug|x64.Build.0 = Debug|x64
22 | {AA26F6F0-8DDD-4191-BC04-FF749425216E}.Debug|x86.ActiveCfg = Debug|x86
23 | {AA26F6F0-8DDD-4191-BC04-FF749425216E}.Debug|x86.Build.0 = Debug|x86
24 | {AA26F6F0-8DDD-4191-BC04-FF749425216E}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {AA26F6F0-8DDD-4191-BC04-FF749425216E}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {AA26F6F0-8DDD-4191-BC04-FF749425216E}.Release|x64.ActiveCfg = Release|x64
27 | {AA26F6F0-8DDD-4191-BC04-FF749425216E}.Release|x64.Build.0 = Release|x64
28 | {AA26F6F0-8DDD-4191-BC04-FF749425216E}.Release|x86.ActiveCfg = Release|x86
29 | {AA26F6F0-8DDD-4191-BC04-FF749425216E}.Release|x86.Build.0 = Release|x86
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | EndGlobal
35 |
--------------------------------------------------------------------------------
/PMCCommand/MessageFilter.cs:
--------------------------------------------------------------------------------
1 | namespace PMCCommand
2 | {
3 | using System;
4 | using System.Runtime.InteropServices;
5 |
6 | ///
7 | /// Copied wholesale from: https://msdn.microsoft.com/en-us/library/ms228772.aspx
8 | ///
9 | public class MessageFilter : IOleMessageFilter
10 | {
11 | private static bool Registered { get; set; }
12 |
13 | // Class containing the IOleMessageFilter
14 | // thread error-handling functions.
15 |
16 | // Start the filter.
17 | public static void Register()
18 | {
19 | if (!Registered)
20 | {
21 | Console.WriteLine("Registering message filter");
22 | IOleMessageFilter newFilter = new MessageFilter();
23 | IOleMessageFilter oldFilter = null;
24 | CoRegisterMessageFilter(newFilter, out oldFilter);
25 | Registered = true;
26 | }
27 | }
28 |
29 | // Done with the filter, close it.
30 | public static void Revoke()
31 | {
32 | IOleMessageFilter oldFilter = null;
33 | CoRegisterMessageFilter(null, out oldFilter);
34 | Registered = false;
35 | }
36 |
37 | // IOleMessageFilter functions.
38 | // Handle incoming thread requests.
39 | int IOleMessageFilter.HandleInComingCall(
40 | int dwCallType,
41 | System.IntPtr hTaskCaller,
42 | int dwTickCount,
43 | System.IntPtr lpInterfaceInfo)
44 | {
45 | // Return the flag SERVERCALL_ISHANDLED.
46 | return 0;
47 | }
48 |
49 | // Thread call was rejected, so try again.
50 | int IOleMessageFilter.RetryRejectedCall(
51 | System.IntPtr hTaskCallee,
52 | int dwTickCount,
53 | int dwRejectType)
54 | {
55 | Console.WriteLine("Got rejected call: " + dwRejectType);
56 |
57 | // flag = SERVERCALL_RETRYLATER.
58 | if (dwRejectType == 2)
59 | {
60 | // We don't want to retry IMMEDIATELY, instead, sleep for 500 ms
61 | return 500;
62 | }
63 |
64 | // Too busy; cancel call.
65 | return -1;
66 | }
67 |
68 | int IOleMessageFilter.MessagePending(
69 | System.IntPtr hTaskCallee,
70 | int dwTickCount,
71 | int dwPendingType)
72 | {
73 | // Return the flag PENDINGMSG_WAITDEFPROCESS.
74 | return 2;
75 | }
76 |
77 | // Implement the IOleMessageFilter interface.
78 | [DllImport("Ole32.dll")]
79 | private static extern int CoRegisterMessageFilter(
80 | IOleMessageFilter newFilter,
81 | out IOleMessageFilter oldFilter);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PMCCommand
2 | Allows commands to be sent to the VisualStudio package management console from the command line.
3 |
4 | ## Setup
5 |
6 | For this to work, you need to make sure that you have VisualStudio installed on the system. The version assumed is VS2015, but it should work for any VisualStudio version.
7 |
8 | ## Example Usage
9 |
10 | Make sure that the full project path is used. The command interface for `DTE` does not like relative paths.
11 |
12 | ```
13 | PMCCommand 1.0.0
14 |
15 | -n, --nugetcommand Required. The NuGet package management console command
16 | to execute.
17 |
18 | -p, --project Required. The full path of the .csproj or .sln file in
19 | which to run the command.
20 |
21 | -v, --vsversion (Default: 15.0) The VisualStudio version for DTE
22 | interaction.
23 |
24 | -d, --debug (Default: False) Print debuging output to the console.
25 |
26 | --help Display this help screen.
27 |
28 |
29 | Example:
30 | PMCCommand.exe --nugetcommand "Update-Package Newtonsoft.Json" --project "C:\Foo\Bar\foobar.csproj"
31 | ```
32 | ### Example error output
33 | Errors experienced in the nuget command line interface are printed out after running.
34 |
35 | ```
36 | Update-Package : 'blah' was not installed in any project. Update failed.
37 | At line:1 char:1
38 | + Update-Package blah; $error > C:\Windows\TEMP\tmpF776.tmp ; "False" > C:\Windows
39 | ...
40 | + ~~~~~~~~~~~~~~~~~~~
41 | + CategoryInfo : NotSpecified: (:) [Update-Package], Exception
42 | + FullyQualifiedErrorId : NuGetCmdletUnhandledException,NuGet.PackageManagement
43 | .PowerShellCmdlets.UpdatePackageCommand
44 |
45 | ```
46 | The additional commands after the `Update-Package blah` command are to ensure that errors are captured, and that execution of the main `STAThread` does not continue until AFTER the nuget command is completed.
47 |
48 | ## Running with Jenkins or someother build server
49 |
50 | Make sure that the user that will be doing the action has the appropriate rights. [This StackOverflow Answer](https://stackoverflow.com/questions/1491123/system-unauthorizedaccessexception-retrieving-the-com-class-factory-for-word-in/2560877#2560877) addresses the specific problem. Instead of applying the changes to `Microsoft Word Document` set it for `Microsoft Visual Studio `.
51 |
52 | If you continue to have issues, it MAY be due to the executable being compiled for `Any CPU`. Compile for `x86` and try again.
53 |
54 | ## Additional Resources if you are interested
55 |
56 | - [Get DTE Reference](https://msdn.microsoft.com/en-us/library/68shb4dw.aspx)
57 | - [NuGet Package Management Console Command GUIDs and IDs](https://github.com/mono/nuget/tree/master/src/VsConsole/Console)
58 | - [Visual Studio Commands](https://msdn.microsoft.com/en-us/library/cc826040.aspx)
59 | - [Helpful MSDN List](https://msdn.microsoft.com/en-us/library/microsoft.visualstudio.vsconstants.aspx), just look for GUIDs in there.
60 | - Just recursively grep for the GUID/ID in that directory to see the origin of a given GUID or Command ID (`C:\Program Files (x86)\Microsoft Visual Studio 14.0\VSSDK\VisualStudioIntegration\Common\Inc` for my machine)
61 | - [Nuget Issue that spurned this work](https://github.com/NuGet/Home/issues/1512)
62 | - [Using Messagefilter for DTE COM interactions](https://msdn.microsoft.com/en-us/library/ms228772.aspx)
63 | - [Additional Example of OleMessageFilter](http://dl2.plm.automation.siemens.com/solidedge/api/sesdk_web/OleMessageFilterUsage.html)
64 | - [COM IDs in a Nutshell](https://www.codeproject.com/Articles/1265/COM-IDs-Registry-keys-in-a-nutshell)
65 |
--------------------------------------------------------------------------------
/.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 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # MSTest test Results
33 | [Tt]est[Rr]esult*/
34 | [Bb]uild[Ll]og.*
35 |
36 | # NUNIT
37 | *.VisualState.xml
38 | TestResult.xml
39 |
40 | # Build Results of an ATL Project
41 | [Dd]ebugPS/
42 | [Rr]eleasePS/
43 | dlldata.c
44 |
45 | # .NET Core
46 | project.lock.json
47 | project.fragment.lock.json
48 | artifacts/
49 | **/Properties/launchSettings.json
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # Visual Studio code coverage results
117 | *.coverage
118 | *.coveragexml
119 |
120 | # NCrunch
121 | _NCrunch_*
122 | .*crunch*.local.xml
123 | nCrunchTemp_*
124 |
125 | # MightyMoose
126 | *.mm.*
127 | AutoTest.Net/
128 |
129 | # Web workbench (sass)
130 | .sass-cache/
131 |
132 | # Installshield output folder
133 | [Ee]xpress/
134 |
135 | # DocProject is a documentation generator add-in
136 | DocProject/buildhelp/
137 | DocProject/Help/*.HxT
138 | DocProject/Help/*.HxC
139 | DocProject/Help/*.hhc
140 | DocProject/Help/*.hhk
141 | DocProject/Help/*.hhp
142 | DocProject/Help/Html2
143 | DocProject/Help/html
144 |
145 | # Click-Once directory
146 | publish/
147 |
148 | # Publish Web Output
149 | *.[Pp]ublish.xml
150 | *.azurePubxml
151 | # TODO: Comment the next line if you want to checkin your web deploy settings
152 | # but database connection strings (with potential passwords) will be unencrypted
153 | *.pubxml
154 | *.publishproj
155 |
156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
157 | # checkin your Azure Web App publish settings, but sensitive information contained
158 | # in these scripts will be unencrypted
159 | PublishScripts/
160 |
161 | # NuGet Packages
162 | *.nupkg
163 | # The packages folder can be ignored because of Package Restore
164 | **/packages/*
165 | # except build/, which is used as an MSBuild target.
166 | !**/packages/build/
167 | # Uncomment if necessary however generally it will be regenerated when needed
168 | #!**/packages/repositories.config
169 | # NuGet v3's project.json files produces more ignorable files
170 | *.nuget.props
171 | *.nuget.targets
172 |
173 | # Microsoft Azure Build Output
174 | csx/
175 | *.build.csdef
176 |
177 | # Microsoft Azure Emulator
178 | ecf/
179 | rcf/
180 |
181 | # Windows Store app package directories and files
182 | AppPackages/
183 | BundleArtifacts/
184 | Package.StoreAssociation.xml
185 | _pkginfo.txt
186 |
187 | # Visual Studio cache files
188 | # files ending in .cache can be ignored
189 | *.[Cc]ache
190 | # but keep track of directories ending in .cache
191 | !*.[Cc]ache/
192 |
193 | # Others
194 | ClientBin/
195 | ~$*
196 | *~
197 | *.dbmdl
198 | *.dbproj.schemaview
199 | *.jfm
200 | *.pfx
201 | *.publishsettings
202 | orleans.codegen.cs
203 |
204 | # Since there are multiple workflows, uncomment next line to ignore bower_components
205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
206 | #bower_components/
207 |
208 | # RIA/Silverlight projects
209 | Generated_Code/
210 |
211 | # Backup & report files from converting an old project file
212 | # to a newer Visual Studio version. Backup files are not needed,
213 | # because we have git ;-)
214 | _UpgradeReport_Files/
215 | Backup*/
216 | UpgradeLog*.XML
217 | UpgradeLog*.htm
218 |
219 | # SQL Server files
220 | *.mdf
221 | *.ldf
222 | *.ndf
223 |
224 | # Business Intelligence projects
225 | *.rdl.data
226 | *.bim.layout
227 | *.bim_*.settings
228 |
229 | # Microsoft Fakes
230 | FakesAssemblies/
231 |
232 | # GhostDoc plugin setting file
233 | *.GhostDoc.xml
234 |
235 | # Node.js Tools for Visual Studio
236 | .ntvs_analysis.dat
237 | node_modules/
238 |
239 | # Typescript v1 declaration files
240 | typings/
241 |
242 | # Visual Studio 6 build log
243 | *.plg
244 |
245 | # Visual Studio 6 workspace options file
246 | *.opt
247 |
248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
249 | *.vbw
250 |
251 | # Visual Studio LightSwitch build output
252 | **/*.HTMLClient/GeneratedArtifacts
253 | **/*.DesktopClient/GeneratedArtifacts
254 | **/*.DesktopClient/ModelManifest.xml
255 | **/*.Server/GeneratedArtifacts
256 | **/*.Server/ModelManifest.xml
257 | _Pvt_Extensions
258 |
259 | # Paket dependency manager
260 | .paket/paket.exe
261 | paket-files/
262 |
263 | # FAKE - F# Make
264 | .fake/
265 |
266 | # JetBrains Rider
267 | .idea/
268 | *.sln.iml
269 |
270 | # CodeRush
271 | .cr/
272 |
273 | # Python Tools for Visual Studio (PTVS)
274 | __pycache__/
275 | *.pyc
276 |
277 | # Cake - Uncomment if you are using it
278 | # tools/**
279 | # !tools/packages.config
280 |
281 | # Telerik's JustMock configuration file
282 | *.jmconfig
283 |
284 | # BizTalk build output
285 | *.btp.cs
286 | *.btm.cs
287 | *.odx.cs
288 | *.xsd.cs
289 |
--------------------------------------------------------------------------------
/PMCCommand/PMCCommand.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {AA26F6F0-8DDD-4191-BC04-FF749425216E}
8 | Exe
9 | Properties
10 | PMCCommand
11 | PMCCommand
12 | v4.5.2
13 | 512
14 | true
15 | publish\
16 | true
17 | Disk
18 | false
19 | Foreground
20 | 7
21 | Days
22 | false
23 | false
24 | true
25 | 0
26 | 1.0.0.%2a
27 | false
28 | false
29 | true
30 |
31 |
32 | AnyCPU
33 | true
34 | full
35 | false
36 | bin\Debug\
37 | DEBUG;TRACE
38 | prompt
39 | 4
40 |
41 |
42 | AnyCPU
43 | pdbonly
44 | true
45 | bin\Release\
46 | TRACE
47 | prompt
48 | 4
49 |
50 |
51 | true
52 | bin\x86\Debug\
53 | DEBUG;TRACE
54 | full
55 | x86
56 | prompt
57 | MinimumRecommendedRules.ruleset
58 | true
59 |
60 |
61 | bin\x86\Release\
62 | TRACE
63 | true
64 | pdbonly
65 | x86
66 | prompt
67 | MinimumRecommendedRules.ruleset
68 | true
69 |
70 |
71 | true
72 | bin\x64\Debug\
73 | DEBUG;TRACE
74 | full
75 | x64
76 | prompt
77 | MinimumRecommendedRules.ruleset
78 | true
79 |
80 |
81 | bin\x64\Release\
82 | TRACE
83 | true
84 | pdbonly
85 | x64
86 | prompt
87 | MinimumRecommendedRules.ruleset
88 | true
89 |
90 |
91 |
92 | ..\packages\CommandLineParser.1.9.71\lib\net45\CommandLine.dll
93 | True
94 |
95 |
96 | True
97 | ..\packages\EnvDTE.8.0.2\lib\net10\EnvDTE.dll
98 | True
99 |
100 |
101 | True
102 | ..\packages\EnvDTE80.8.0.2\lib\net10\EnvDTE80.dll
103 | True
104 |
105 |
106 | True
107 | ..\packages\stdole.7.0.3302\lib\net10\stdole.dll
108 | True
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | False
139 | Microsoft .NET Framework 4.5.2 %28x86 and x64%29
140 | true
141 |
142 |
143 | False
144 | .NET Framework 3.5 SP1
145 | false
146 |
147 |
148 |
149 |
156 |
--------------------------------------------------------------------------------
/PMCCommand/Program.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Benjamin Trent. All rights reserved. See LICENSE file in project root
2 |
3 | namespace PMCCommand
4 | {
5 | using System;
6 | using System.Globalization;
7 | using System.IO;
8 | using EnvDTE;
9 | using EnvDTE80;
10 |
11 | ///
12 | /// This is the least hacky way I could find for running commands in the PMC from the command line
13 | /// This program will open an instance of VisualStudio and execute the passed in commands into the PMC directly
14 | /// From all that I could find, there was nothing that allowed this without passing through VisualStudio, which is depressing
15 | ///
16 | public class Program
17 | {
18 | private const string CmdNameForPMC = "View.PackageManagerConsole";
19 | private static bool retry = true;
20 | private static ExecutionState state = ExecutionState.NOT_STARTED;
21 | private static object stateMutex = new object();
22 |
23 | private enum ExecutionState
24 | {
25 | NOT_STARTED,
26 | VS_OPENED,
27 | PROJECT_OPENED,
28 | NUGET_OPENED
29 | }
30 |
31 | private static string VSVersion { get; set; }
32 |
33 | private static string ProjectPath { get; set; }
34 |
35 | private static string NuGetCmd { get; set; }
36 |
37 | private static bool Debug { get; set; }
38 |
39 | private static DTE DTE { get; set; }
40 |
41 | private static string LockFile { get; set; }
42 |
43 | private static string NuGetOutputFile { get; set; }
44 |
45 | private static void CleanUp()
46 | {
47 | if (DTE != null)
48 | {
49 | if (DTE.Solution != null)
50 | {
51 | DTE.Solution.Close(true);
52 | }
53 |
54 | DTE.Quit();
55 | }
56 | }
57 |
58 | ///
59 | /// STAThread is necessary for the MessageFilter
60 | ///
61 | /// Command line args
62 | [STAThread]
63 | private static void Main(string[] args)
64 | {
65 | var options = new CmdLineOptions();
66 | if (CommandLine.Parser.Default.ParseArgumentsStrict(args, options))
67 | {
68 | ProjectPath = options.ProjectPath;
69 | NuGetCmd = options.NuGetCommand;
70 | Debug = options.Debug;
71 |
72 | if (string.IsNullOrWhiteSpace(NuGetCmd))
73 | {
74 | throw new Exception("nugetcommand parameter cannot be empty.");
75 | }
76 |
77 | if (string.IsNullOrWhiteSpace(ProjectPath))
78 | {
79 | throw new Exception("project parameter cannot be empty.");
80 | }
81 |
82 | VSVersion = "15.0";
83 |
84 | if (!string.IsNullOrWhiteSpace(options.VisualStudioVersion))
85 | {
86 | VSVersion = options.VisualStudioVersion;
87 | }
88 |
89 | if (!File.Exists(ProjectPath))
90 | {
91 | throw new FileNotFoundException(string.Format("{0} was not found.", ProjectPath));
92 | }
93 |
94 | Execute();
95 | }
96 | }
97 |
98 | ///
99 | /// When your external, multi-threaded application calls into Visual Studio, it goes through a COM interface.
100 | /// COM sometimes has problems dealing properly with threads, especially with respect to timing.
101 | /// As a result, occasionally the incoming thread from the external application cannot be handled by Visual Studio at the very moment it arrives, resulting in the previously mentioned errors.
102 | /// This does not occur, however, if you are calling from an application that is running inside Visual Studio (in-proc), such as a macro or an add-in.
103 | /// For a more detailed explanation about the reasons behind this, see https://msdn.microsoft.com/en-us/library/8sesy69e.aspx.
104 | /// To avoid these errors, implement an IOleMessageFilter handler function in your application.When you do this,
105 | /// if your external application thread calls into Visual Studio and is rejected (that is, it returns SERVERCALL_RETRYLATER from the IOleMessageFilter.HandleIncomingCall method),
106 | /// then your application can handle it and either retry or cancel the call.
107 | /// To do this, initiate the new thread from your Visual Studio application in a single-threaded apartment(STA) and surround your automation code with the IOleMessageFilter handler.
108 | ///
109 | private static void Execute()
110 | {
111 | // This is the only way I could find for making sure that the PMC command completed
112 | LockFile = Path.GetTempFileName();
113 | using (StreamWriter sw = new StreamWriter(LockFile))
114 | {
115 | sw.WriteLine(true);
116 | }
117 |
118 | NuGetOutputFile = Path.GetTempFileName();
119 | DTE = GetDTE2();
120 | MessageFilter.Register();
121 |
122 | Console.CancelKeyPress += (object sender, ConsoleCancelEventArgs e) =>
123 | {
124 | DTE.Quit();
125 |
126 | // turn off the IOleMessageFilter
127 | MessageFilter.Revoke();
128 | };
129 |
130 | SetDelegatesForDTE();
131 | DTE.MainWindow.Activate();
132 |
133 | SpinWait(ExecutionState.VS_OPENED);
134 | DTE.ExecuteCommand("File.OpenProject", ProjectPath);
135 |
136 | SpinWait(ExecutionState.PROJECT_OPENED);
137 | DTE.ExecuteCommand(CmdNameForPMC);
138 |
139 | SpinWait(ExecutionState.NUGET_OPENED);
140 | var cmd = string.Format("{0}; $error > {1} ; \"False\" > {2}", NuGetCmd, NuGetOutputFile, LockFile);
141 | DTE.ExecuteCommand(CmdNameForPMC, cmd);
142 |
143 | var stillRunning = true;
144 | while (stillRunning)
145 | {
146 | System.Threading.Thread.Sleep(500);
147 | using (StreamReader sr = new StreamReader(LockFile))
148 | {
149 | stillRunning = Convert.ToBoolean(sr.ReadToEnd());
150 | }
151 | }
152 |
153 | Console.WriteLine("Completed");
154 | Console.WriteLine(File.ReadAllText(NuGetOutputFile));
155 | CleanUp();
156 |
157 | // turn off the IOleMessageFilter
158 | MessageFilter.Revoke();
159 | }
160 |
161 | ///
162 | /// We need to insure that particular parts of the main STAThread do not continue until feed back of other actions is received
163 | /// Since we could potentially get a failure from the MessageFilter and a retry.
164 | /// So, using a Monitor would not work as potentially the `Pulse` would occur before the `Wait`
165 | /// Hence, the use of a naive state machine
166 | ///
167 | /// The state desired necessary to continue.
168 | private static void SpinWait(ExecutionState expectedState)
169 | {
170 | while (state < expectedState)
171 | {
172 | System.Threading.Thread.Sleep(1000);
173 | }
174 | }
175 |
176 | ///
177 | /// Failures can occure when accessing the newly created DTE2 instance.
178 | /// This is due to COM Interop errors in Windows.
179 | /// The MessageFilter should catch failures, but if a particular one leaks through, restart this initial setting
180 | ///
181 | private static void SetDelegatesForDTE()
182 | {
183 | try
184 | {
185 | DTE.Events.SolutionEvents.Opened += SolutionEvents_Opened;
186 | DTE.Events.CommandEvents.AfterExecute += CommandEvents_AfterExecute;
187 | DTE.Events.CommandEvents.BeforeExecute += CommandEvents_BeforeExecute;
188 | }
189 | catch (System.Runtime.InteropServices.COMException ex)
190 | {
191 | Console.WriteLine("Exception encountered: " + ex.Message);
192 | if (retry)
193 | {
194 | retry = false;
195 | System.Threading.Thread.Sleep(500);
196 | SetDelegatesForDTE();
197 | }
198 | }
199 | }
200 |
201 | private static void CommandEvents_BeforeExecute(string guid, int id, object customIn, object customOut, ref bool cancelDefault)
202 | {
203 | PrintDebugLog(string.Format("Command Sent: GUID: {0}; ID: {1}; CustomIn: {2}; CustomOut: {3}", guid, id, customIn, customOut));
204 | }
205 |
206 | private static void CommandEvents_AfterExecute(string guid, int id, object customIn, object customOut)
207 | {
208 | PrintDebugLog(string.Format("Command Executed: GUID: {0}; ID: {1}; CustomIn: {2}; CustomOut: {3}", guid, id, customIn, customOut));
209 |
210 | // This means that PMC has loaded and loaded its sources
211 | if (guid == GuidsAndIds.GuidNuGetConsoleCmdSet && id == GuidsAndIds.CmdidNuGetSources)
212 | {
213 | TransitionState(ExecutionState.PROJECT_OPENED, ExecutionState.NUGET_OPENED);
214 | }
215 | else
216 | {
217 | TransitionState(ExecutionState.NOT_STARTED, ExecutionState.VS_OPENED);
218 | }
219 | }
220 |
221 | private static void SolutionEvents_Opened()
222 | {
223 | TransitionState(ExecutionState.VS_OPENED, ExecutionState.PROJECT_OPENED);
224 | }
225 |
226 | private static void TransitionState(ExecutionState expectedOldState, ExecutionState newState)
227 | {
228 | lock (stateMutex)
229 | {
230 | if (state == expectedOldState)
231 | {
232 | state = newState;
233 | PrintDebugLog(string.Format("Transitioned from {0} to {1}", expectedOldState, newState));
234 | }
235 | }
236 | }
237 |
238 | private static string GetValue(string key, string defaultValue)
239 | {
240 | var result = Environment.GetEnvironmentVariable(key);
241 | if (string.IsNullOrEmpty(result))
242 | {
243 | result = defaultValue;
244 | }
245 |
246 | return result;
247 | }
248 |
249 | private static DTE GetDTE2()
250 | {
251 | // Get the ProgID for DTE 14.0.
252 | Type t = Type.GetTypeFromProgID(
253 | "VisualStudio.DTE." + VSVersion, true);
254 |
255 | // Create a new instance of the IDE.
256 | object obj = Activator.CreateInstance(t, true);
257 |
258 | // Cast the instance to DTE2 and assign to variable dte.
259 | DTE2 dte2 = (DTE2)obj;
260 |
261 | // We want to make sure that the devenv is killed when we quit();
262 | dte2.UserControl = false;
263 | return dte2.DTE;
264 | }
265 |
266 | private static void PrintDebugLog(string message)
267 | {
268 | if (Debug)
269 | {
270 | Console.WriteLine("{0}: {1}", DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.ffff", CultureInfo.InvariantCulture), message);
271 | }
272 | }
273 | }
274 | }
275 |
--------------------------------------------------------------------------------