背景
這段時間公司新做了一個支付系統,里面有N個后台服務,每次有更新修改,拷貝打包發布包“不亦樂乎”。。。於是我想要不要自己定制個打包插件。
部分朋友可能會認為,有現成的可以去找一個,干嘛不用持續集成工具啊。
1、公司沒用,也不打算用
2、自己想折騰下,好奇
因此主要是分享這次的學習過程和結果。
涉及知識點
大概構想下,選擇需要打包的項目-重新編譯-拷貝生成文件到指定目錄-OK,剩下就是交給測試去做了。
1、Visual Studio Package 初步使用
2、.Net 執行Dos命令
3、MSBuild簡單運用
4、File.Copy的使用
下載安裝
Visual Studio 2013 SDK包的下載地址:http://www.microsoft.com/en-us/download/details.aspx?id=40758&fa43d42b-25b5-4a42-fe9b-1634f450f5ee=True
完畢后,在新建項目-C#-擴展性,可以見到Visual Studio Package模板選項(見下圖)
第一個vs插件程序
下一步,勾選Menu Command;下一步,修改Command Name;下一步,取消單元測試,Finish。恭喜,您的第一個插件程序就這么誕生了。
編譯后,到生成目錄下,執行.vsix文件,安裝完后,重啟VS,點擊[工具]就會見到您的插件工具。同時你也可以在擴展與更新界面進行對您的插件進行卸載。
項目文件簡介
初建項目,有很多文件,有幾個下面是需要了解一下的。
1、PackingTools.vsct
2、PackingToolsPackage.cs
3、source.extension.vsixmanifest
PackingTools.vsct
vsct是個Xml文件,它用來對插件按鈕進行配置的。
Group是組節點,項目初建,它的Parent為IDM_VS_MENU_TOOLS,對於這個我們可以查看 您的vs安裝目錄\Visual Studio2013\VSSDK\VisualStudioIntegration\Common\Inc\vsshlids.h,打開文件,我們可以看到除IDM_VS_MENU_TOOLS以外還有IDM_VS_MENU_ADDINS、IDM_VS_MENU_HELP等等,我們嘗試改成IDM_VS_MENU_HELP。
Buttons節點下,可以添加多個Button信息,我們嘗試添加一個Button進去,同時GuidSymbol加多一個IDSymbol進去。修改對應新按鈕的id 和 priority。
我們啟動F5,調試看看,插件按鈕位置變了,也是我們第一次修改了按鈕布局。
PackingToolsPackage.cs
插件程序的入口,我們細看代碼,發現在Initialize方法里,就對批量打包這個按鈕進行事件綁定,我們嘗試一下,把MenuItemCallback里的邏輯刪了,展示一個wpf窗體出來。
添加新建項-添加wpf窗體后,需要在項目里新引用一個System.Xaml.dll才能編譯通過。
接着,我們在PackingToolsPackage.cs的MenuItemCallback的方法里寫new MainWindow().Show(),F5運行->點擊批量打包,則會彈出下圖。
主界面就這么出來了,當然,你可以單例一個窗體,不用new。。。
source.extension.vsixmanifest
對Visual Studio 擴展的配置,如:文件模板,項目工程模板,依賴程序集,Visual Studio 工具包logo,VS安裝的版本等等。
讀取已打開的項目信息
這個其實並沒什么特別難的邏輯,只是開始找資料花了一些時間,直接上代碼。

1 private void MenuItemCallback(object sender, EventArgs e) 2 { 3 IVsSolution pSolution = GetService(typeof(SVsSolution)) as IVsSolution; 4 5 if (pSolution != null) 6 { 7 var projectArray = new string[10]; 8 uint aa; 9 //通過這個方法取到打開解決方案的項目信息 10 pSolution.GetProjectFilesInSolution((uint)__VSGETPROJFILESFLAGS.GPFF_SKIPUNLOADEDPROJECTS, 11 (uint)__VSGETPROJFILESFLAGS.GPFF_SKIPUNLOADEDPROJECTS, projectArray, out aa); 12 13 //轉換信息,讓girdView展示 14 var projectList = 15 projectArray.ToList() 16 .Where(a => a.Contains(".csproj")).Select(a => new ProjectList 17 { 18 Name = Path.GetFileNameWithoutExtension(a), 19 Path = a 20 }).ToList(); 21 22 MainForm.GetInstance(projectList).Show(); 23 } 24 }
對於IVsSolution這個接口的命名空間下,還有各種各樣的類,對Visual Studio Package開發有興趣的同學可以去看看。傳送門
MSBuild的使用
Microsoft Build Engine 是MSBuild的全稱,是一個獨立的存在生成平台,不需要依賴vs,但是vs的生成、發布等等功能都是基於MSBuild去構建的,它能讀取.sln、.csproj、.pubxml等xml文件里的參數進行生成解決和項目。這里就不做過多的介紹和深入,有需要可以點擊這里進行查看文檔
MSBuild.exe在C:\Windows\Microsoft.NET\Framework\v4.0.30319 這個路徑下,值得注意的是Framework 的位數和版本,這個影響選擇MSBuild.exe的路徑。
嘗試一下,打開cmd,輸入-> C:\Windows\Microsoft.NET\Framework\v4.0.30319 "您的項目文件路徑" /t:Rebuild /p:Configuration=Release /p:VisualStudioVersion=12.0
這句話指,用msbuild重新生成Release版本,注意項目路徑是有雙引號的。
然而,我們需要在.net程序里使用這段dos指令,因此我們寫編寫一個dos指令幫助類

1 #region Dos指令幫助類 2 /// <summary> 3 /// Dos指令幫助類 4 /// </summary> 5 public class DosCommanHelper 6 { 7 #region 執行指令 8 /// <summary> 9 /// 執行指令 10 /// </summary> 11 /// <param name="command">指令</param> 12 /// <param name="seconds">最長等待時間(秒)</param> 13 /// <returns></returns> 14 public static string ExeCommand(string command, int seconds = 3) 15 { 16 var output = ""; 17 if (string.IsNullOrWhiteSpace(command)) 18 return output; 19 20 var process = new Process(); //創建進程對象 21 var startInfo = new ProcessStartInfo 22 { 23 FileName = "cmd.exe", //設定需要執行的命令 24 Arguments = "/C " + command, //“/C”表示執行完命令后馬上退出 25 UseShellExecute = false, //不使用系統外殼程序啟動 26 RedirectStandardInput = false, //不重定向輸入 27 RedirectStandardOutput = true, //重定向輸出 28 CreateNoWindow = true //不創建窗口 29 }; 30 31 process.StartInfo = startInfo; 32 try 33 { 34 if (process.Start()) 35 { 36 if (seconds == 0) 37 { 38 process.WaitForExit(); 39 } 40 else 41 { 42 process.WaitForExit(seconds * 1000); 43 } 44 output = process.StandardOutput.ReadToEnd(); 45 } 46 } 47 catch (Exception e) 48 { 49 Console.WriteLine(e); 50 } 51 finally 52 { 53 process.Close(); 54 } 55 return output; 56 } 57 #endregion 58 } 59 #endregion
拷貝生成文件
從上面我們已經讀取到了解決方案對應的項目信息,包括路徑,新建的項目默認生成到.csproj文件目錄下的bin/Release里。
我們利用Path.GetDirectoryName和Path.Combine方法,獲取對應路徑,再自己編寫文件操作幫助類,對應Release里的文件復制到指定位置。

1 #region 文件操作幫助類 2 /// <summary> 3 /// 文件操作幫助類 4 /// </summary> 5 public static class FilesHelper 6 { 7 #region 復制文件 8 /// <summary> 9 /// 復制文件 10 /// </summary> 11 /// <param name="fromPath">原路徑</param> 12 /// <param name="toPath">新路徑</param> 13 /// <returns></returns> 14 public static bool CopyFiles(string fromPath, string toPath) 15 { 16 try 17 { 18 if (!Directory.Exists(fromPath)) 19 throw new Exception("no fromPath"); 20 21 if (Directory.Exists(toPath)) 22 Directory.Delete(toPath, true); 23 24 Directory.CreateDirectory(toPath); 25 26 var fromfiles = Directory.GetFiles(fromPath).ToList(); 27 fromfiles.ForEach(file => 28 { 29 var newFile = Path.Combine(new[] { toPath, Path.GetFileName(file) }); 30 File.Copy(file, newFile, true); 31 }); 32 33 var fromFolders = Directory.GetDirectories(fromPath).ToList(); 34 fromFolders.ForEach(folder => 35 { 36 var destDir = Path.Combine(new[] { toPath, Path.GetFileName(folder) }); 37 CopyFiles(folder, destDir); 38 }); 39 40 return true; 41 } 42 catch 43 { 44 return false; 45 } 46 } 47 #endregion 48 } 49 #endregion
最后我們只需要完善發布按鈕事件,獲取列表選擇項->獲取打包到的指定路徑->遍歷列表項數據->執行MSBuild指令->復制文件到指定路徑->完畢

1 private void Button_Click_1(object sender, RoutedEventArgs e) 2 { 3 var list = MyListView.SelectedItems.Cast<ProjectList>().ToList(); 4 5 var fromPath = PathLabel.Content; 6 7 list.ForEach(item => 8 { 9 DosCommanHelper.ExeCommand(string.Format(@"C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe {0} /t:Rebuild /p:Configuration=Release /p:VisualStudioVersion=12.0", item.Path)); 10 11 var theFileOfDirectoryName = Path.GetDirectoryName(item.Path); 12 var toPath = Path.Combine(theFileOfDirectoryName, @"bin\Release"); 13 FilesHelper.CopyFiles(fromPath.ToString(), Path.Combine(toPath, item.Name)); 14 }); 15 }
完畢
源碼我這里沒有提供,還是希望讀了這篇文章感興趣的小伙伴動手折騰下,Visual Studio Package還可以做模版開發等等,我也沒太多的去深入了解,感興趣的可以去google一下關鍵字Visual Studio Package、vssdk、vsix、插件開發。
以上純屬自己初步折騰的結果,為了寫文章弄出來的簡單demo,還有很多可優化的地方,例如各種驗證判斷,插件按鈕的動態顯示、讀取項目的類型過濾、web項目的發布,文件過濾復制等等。。。。
本篇文章有什么寫錯的或者更好的建議麻煩大家在評論寫給我,我會一一補充修改。如果對大家有幫助,還希望推薦一下,謝謝。