我应该如何在msbuild脚本中引用sn.exe?

时间:2012-10-08 12:35:07

标签: c# .net msbuild code-signing assembly-signing

我需要在构建完成后重新签署我的程序集(我已经完成了其他一些事情),所以我开始添加一个名为<Exec>的{​​{1}}任务。这必须适用于其他开发人员/环境,所以我希望我可以从该文件夹中复制C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\sn.exesn.exe并将其存储在我们的代码存储库中,这样我就可以从中调用它的通用版本一个已知的位置。

sn.exe.config在sdk目录之外孤立地崩溃,所以我想知道如何引用它而不知道它将在哪个路径下。不同的人有不同的环境(x86 vs x64,不同的安装目录,不同的版本),所以我希望能够轻松地引用该工具的最新版本(或者任何版本)。看起来像一个简单的工具,也许还有另一种方法用另一个工具/命令/ msbuild任务签署一个程序集?任何帮助将不胜感激。

6 个答案:

答案 0 :(得分:7)

原来有一个名为“GetFrameworkSdkPath”的任务将获得Windows SDK位置。从那里,我必须测试以查看sn.exe是否直接存在于bin文件夹中,或者是否存在于bin\NETFX 4.0 Tools\中。到目前为止看似可靠。

<PropertyGroup>
  <SNExePath>NotSet</SNExePath>
</PropertyGroup>

<!-- Sometimes theres nothing in the WindowsSdkPath dir and there's stuff in a deeper folder called 'NETFX 4.0 Tools'. -->
<Target Name="GetSNPath" BeforeTargets="AfterBuild">
  <GetFrameworkSdkPath>
    <Output TaskParameter="Path" PropertyName="WindowsSdkPath" />
  </GetFrameworkSdkPath>
  <PropertyGroup>
    <SNExePath>$(WindowsSdkPath)bin\sn.exe</SNExePath>
  </PropertyGroup>
  <PropertyGroup>
    <SNExePath Condition="!Exists($(SNExePath))">$(WindowsSdkPath)bin\NETFX 4.0 Tools\sn.exe</SNExePath>
  </PropertyGroup>
</Target>  

<Target Name="AfterBuild">
  <Exec Command="$(SNExePath) -R $(TargetPath) $(SignatureFile)" />
</Target>

答案 1 :(得分:7)

要在msbuild脚本中以适合大多数人的方式正确引用snsqlmetal(我之后)之类的工具,您必须考虑到不同方面的问题。运营环境和框架实施。有两个主要案例:Microsoft Windows和Microsoft的框架实现,其次是其他所有(我的意思是Mono / unix)。最后列出了支持我能想到的情况的正确方法的一个例子。

微软

找到sn或其他类似工具在Windows中的位置的正确方法是从GetFrameworkSdkPath task开始,为already mentioned

但是,正如问题所示,不能直接确定sn或其他工具所在的FrameworkSdkPath中的确切位置。引用的答案表明,FrameworkSdkPath下唯一可能存在的工具是binbin/NETFX 4.0 Tools。但是,其他值也是可能的(Visual Studio 2013 Preview使用bin/NETFX 4.5.1 Tools)。因此,搜索sn的唯一正确方法是使用glob表达式或递归搜索它。我无法弄清楚如何使用MSBuild进行glob扩展,并且内置的MSBuild任务似乎不支持在FrameworkSdkPath下搜索特定的实用程序。但是,cmd的WHERE具有此功能,可用于进行搜索。结果类似于以下msbuild代码:

<Target Name="GetSNPath" BeforeTargets="AfterBuild">
  <GetFrameworkSdkPath>
    <Output TaskParameter="Path" PropertyName="WindowsSdkPath" />
  </GetFrameworkSdkPath>
  <Exec Command="WHERE /r &quot;$(WindowsSdkPath.TrimEnd('\\'))&quot; sn &gt; sn-path.txt"  />
  <ReadLinesFromFile File="sn-path.txt">
    <Output TaskParameter="Lines" PropertyName="SNPath"/>
  </ReadLinesFromFile>
  <Delete Files="sn-path.txt" />
  <PropertyGroup>
    <SNPath>$([System.Text.RegularExpressions.Regex]::Replace('$(SNPath)', ';.*', ''))</SNPath>
  </PropertyGroup>
</Target>

(请参阅Property Functions了解我可以在此使用String.TrimEnd的原因。WHERE不喜欢尾随斜杠。编辑:我添加了使用属性函数来访问Regex.Replace()删除SNPath属性中除第一个找到的路径之外的所有路径。我朋友的一台机器的WHERE调用会为某些命令输出多个结果,并打破了对<Exec/>喜欢的工具的任何尝试。更改确保只找到一个结果并且<Exec/>实际上成功。)

现在,您可以使用sn调用<Exec Command="&quot;$(SNPath)&quot;" />

便携式

毫不奇怪,在除Windows之外的任何操作系统上,解析sn的路径要简单得多。在Mac OSX和Linux的任何发行版中,我在PATH中找到sn。在这种情况下使用GetFrameworkSdkPath无济于事;事实上,这似乎返回了一条无法找到sn的路径,至少对于我在使用xbuild时测试的旧版本的mono-2.10:

  • 在Mac OSX FrameworkSdkPath/Library/Frameworks/Mono.framework/Versions/2.10.5/lib/mono/2.0/usr/bin/sn/Library/Frameworks/Mono.framework/Commands/sn的符号链接。
  • 在某个Linux安装上,FrameworkSdkPath/usr/lib64/mono/2.0sn/usr/bin/sn(这是一个使用/usr/lib64/mono/4.0/sn.exe调用mono的shell脚本)。

因此,我们需要做的就是尝试执行sn。任何将其sn实现放置在非标准位置的unix用户都已知道适当更新PATH,因此构建脚本无需搜索它。此外,unix中不存在WHERE。因此,在unix的情况下,我们希望将第一个<Exec/>调用替换为仅在unix上输出sn的内容,并在Windows上运行时仍然执行完整搜索。为了区分类似unix和Windows的环境,我们使用了一个技巧,它利用了unix shell对true命令和cmd标签语法的快捷方式。作为一个简短示例,以下脚本将在unix shellout中输出I’m unix!,在Windows shellout上输出I’m Windows :-/

:; echo 'I’m unix!'; exit $?
echo I’m Windows :-/

利用这一点,我们得到的GetSNPath任务看起来像:

<Target Name="GetSNPath" BeforeTargets="AfterBuild">
  <GetFrameworkSdkPath>
    <Output TaskParameter="Path" PropertyName="WindowsSdkPath" />
  </GetFrameworkSdkPath>
  <Exec Command=":; echo sn &gt; sn-path.txt; exit $?
WHERE /r &quot;$(WindowsSdkPath.TrimEnd('\\'))&quot; sn &gt; sn-path.txt"  />
  <ReadLinesFromFile File="sn-path.txt">
    <Output TaskParameter="Lines" PropertyName="SNPath"/>
  </ReadLinesFromFile>
  <Delete Files="sn-path.txt" />
  <PropertyGroup>
    <SNPath>$([System.Text.RegularExpressions.Regex]::Replace('$(SNPath)', ';.*', ''))</SNPath>
  </PropertyGroup>
</Target>

结果是一种可移植的方法,用于查找调用sn所需的字符串。最后一个解决方案允许您使用xbuild支持Microsoft及其msbuild和其他所有平台。它还克服了将bin\NETFX 4.0 Tools硬编码到.csproj文件中以同时支持Microsoft工具的未来和当前版本。

答案 2 :(得分:2)

我现在使用的方法涉及使用属性函数在框架SDK路径下搜索SN.exe - ala:

<GetFrameworkSDKPath>
  <Output TaskParameter="Path" PropertyName="DotNetFrameworkDir"/>
</GetFrameworkSDKPath>

<PropertyGroup>
  <SNPath>$([System.IO.Directory]::GetFiles("$(DotNetFrameworkDir)", "sn.exe", SearchOption.AllDirectories)[0])</SNPath>
</PropertyGroup>

到目前为止,我只是在Visual Studio 2013上亲自测试过,但文档暗示它应该回到Visual Studio 2010。

答案 3 :(得分:1)

您可以在每个引用可执行文件的开发计算机上创建环境变量,MSBuild允许您将其作为属性引用。

因此,通过系统属性的高级选项卡创建环境变量。我通常只创建一个系统环境变量,而不是一个作用于当前用户的变量。您必须重新启动Visual Studio才能将其拾起。

然后,在MSBuild中引用它:

<Exec Command="$(SnExe)">

SnExe是您定义的环境变量。

答案 4 :(得分:0)

在类似的情况下,$(SDK40ToolsPath)变量对我有用。这使得无需知道安装了哪些特定版本的工具,例如:

  <PropertyGroup>
    <XsdExePath>$(SDK40ToolsPath)xsd.exe</XsdExePath>
  </PropertyGroup>
  <Target Name="BeforeBuild">
    <ItemGroup>
      <xsd Include="Objects.xsd" />
    </ItemGroup>
    <Exec Command="&quot;$(XsdExePath)&quot; @(xsd) /c /namespace:Blah.Objects" />
  </Target>

答案 5 :(得分:0)

像 sn.exe 这样的 .NET Framwork 工具的路径实际上是使用 GetFramworkSDKPath(如上所述)和(几乎)TargetFrameworkVersion(如项目文件中指定的)的组合。您基本上需要从 TargetFrameworkVersion 中删除初始的“v”并将这些部分连接在一起以形成工具的路径,例如

<Target Name="GetSNPath" BeforeTargets="AfterBuild">
  <GetFrameworkSdkPath>
    <Output TaskParameter="Path" PropertyName="WindowsSdkPath" />
  </GetFrameworkSdkPath>
  <PropertyGroup>
  
   <FrameworkVersion>$([System.Text.RegularExpressions.Regex]::Replace('$(TargetFrameworkVersilon)', 'v', ''))</FrameworkVersion>
    <NETFXVersion>NETFX $(FrameworkVersion) Tools</NETFXVersion>
    <SNPath>$(WindowsSdkPath)bin\$(NETFXVersion)\sn.exe</SNPath>
  </PropertyGroup>
</Target>