Как подписать все сборки приложения

Понадобилось тут по работе подписать все проекты в решении ключиком. Вообще, вариантов тут три:
1.У каждого проекта в свойствах указать ключ вручную на вкладке Signing – Sign the assembly
2.Использовать командную строчку MSBuild а-ля

"C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" .sln /p:Configuration=Release /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=%key_path% /p:OutDir=%outdir%Client"

3.Распарсить каждый файл проекта xxx.csproj и выставить вручную ключи.

Первый вариант подходит. Почти. Потому что если в решении более 10 проектов,  ручная работа такого плана превращается в час-два веселого времяпровождения. В моем случае проектов более сотни, поэтому этот вариант был задвинут в самый дальний ящик стола.

Второй вариант получше, бесспорно. Кроме одного «но» — из студии уже не собрать решение. Ну, как «не собрать». Конечно же можно навесить External Tools на написанный батничек и каждый раз пользоваться этой командой вместо любимого Ctrl+Shift+B, но это же не путь джедая, как все, я надеюсь, понимают. К тому же, возникают некоторые вопросы при автоматизированной сборке решения на TFS. Никому ведь не хочется страдать, если при попытке вчекинить Очередное Важное Исправление GatedCheckin упадет и ваши изменения откатятся.

Третий вариант ближе к истине, поскольку автоматизация – это наше всё, это хлеб и вино. Но кто видел строение sln- и proj-файла, тот в цирке смеяться вряд ли будет. Конечно-конечно, таким профессионалам такая задача по плечу. Но лень.

Поэтому берем гибрид первого и третьего варианта с благословления библиотеки EnvDTE и EnvDTE80. Как уже догадались самые прозорливые из моих читателей, речь дальше пойдет об автоматизации Visual Studio 2010, нашем любимом инструменте профессиональной разработки приложений под Windows.

Итак, что же нам хочется. Нам хочется, чтобы прилетел вдруг волшебник И НАИЗМЕНЯЛ СВОЙСТВА ПРОЕКТОВ САМ. Поначалу мне в голову приходила шальная мысль написать макрос (а что, Alt+F11 и пишем-пишем). Но писать на basic рекурсивный обход проекта мне не улыбнулось, потому, закатав рукава, я взялся за написание кода на C#. Взял за основу вот эту статейку, где как раз показано, как обходить все проекты в решении. Тут стоит уточнить, откуда вообще берется рекурсия и обход дерева. Дело в том, что в файле решения проекты можно класть не только напрямую в корень, как обычно делают школьники и лица, не осознающие прямой выгоды разделения громадного решения на несколько дочерних. В решении можно организовывать папки и подпапки, поэтому задача получения списках всех проектов становится довольно интересной.

Делаем новое решение, добавляем туда ссылки на EnvDTE и EnvDTE80, берем указанную выше статью, вставляем код в SolutionProjects.cs, проверяем – работает. Пишется у нас в консоль список всех проектов в решении, окей. Дальше нам нужно прописать опции SignAssembly и AssemblyOriginatorKeyFile. После некоторого количества крови, слез и пота у нас за минут двадцать появляется КОД:

#region Usings

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;

#endregion

namespace SignAssemblies
{
	internal class Program
	{
		private static void Main(string[] args)
		{
			string keyPath = string.Empty;
			const string configFile = "keyPath.txt";
			if (File.Exists(configFile))
			{
				keyPath = File.ReadAllText(configFile);
			}
			if (string.IsNullOrEmpty(keyPath))
			{
				Console.WriteLine("KeyPath.txt is not found, press <ENTER> to terminate");
				Console.ReadLine();
				return;
			}
			Console.WriteLine("Current key path is {0}", keyPath);
			IList<Project> projects = SolutionProjects.Projects();
			int processedAssembliesCount = 0;
			foreach (Project project in projects)
			{
				Console.WriteLine(project.Name);
				if (TryChangeValue(project.Properties, "SignAssembly", true) &&
				    TryChangeValue(project.Properties, "AssemblyOriginatorKeyFile", keyPath))
				{
					processedAssembliesCount++;
				}
				Console.WriteLine();
			}
			Console.WriteLine("Total: {0} projects", projects.Count);
			Console.WriteLine("Processed: {0} ", processedAssembliesCount);
			Console.ReadLine();
		}

		private static bool TryChangeValue(Properties properties, string propertyName, object propertyValue)
		{
			dynamic property;
			try
			{
				property = properties.Cast<dynamic>().FirstOrDefault(prop => prop.Name == propertyName);
			}
			catch
			{
				//pause a little so vs could check-out project file. I know that's bad
				Thread.Sleep(5000);
				property = properties.Cast<dynamic>().FirstOrDefault(prop => prop.Name == propertyName);
			}
			if (property == null)
			{
				Console.ForegroundColor = ConsoleColor.Red;
				Console.WriteLine("\tProject does not have SignAssembly property");
				Console.ResetColor();
				return false;
			}
			Console.ForegroundColor = ConsoleColor.DarkGreen;
			Console.WriteLine("\tCurrent {1} value is {0},\r\n\t(re)set to True", property.Value, propertyName);
			if (!propertyValue.Equals(property.Value))
			{
				property.Value = propertyValue;
			}
			Console.ForegroundColor = ConsoleColor.DarkYellow;
			Console.WriteLine("\t{0} is now set to {1}", propertyName, propertyValue);
			Console.ResetColor();
			Console.WriteLine();
			return true;
		}
	}
}

«Послекодие» здесь заключается в некотором извинении за слово dynamic. Но для того оно и было добавлено в C#, чтобы пользоваться им при автоматизации OLE-объектов и прочего неправославного. Ну и за неровный почерк простите заодно, волнуюсь. Я же воспитанный .NET программист, прекрасно понимаю, что try-catch, употребленный выше, sucks. For educational purposes only, в общем, и as is.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *