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

5
我需要在构建完成后重新签署程序集(并对其进行了其他操作),因此我首先添加了一个<Exec>任务,调用C:\ Program Files(x86)\ Microsoft SDKs \ Windows \ v8.0A \ bin \ NETFX 4.0 Tools \ sn.exe 。这必须适用于其他开发人员/环境,因此我希望我只需从该文件夹复制sn.exesn.exe.config并将其存储在我们的代码库中,以便我始终可以从已知位置调用通用版本。

sn.exe在sdk目录之外的隔离环境中崩溃,因此我想知道如何引用它而不知道它将位于哪个路径下。不同的人有不同的环境(x86与x64,不同的安装目录,不同的版本),因此我希望能够轻松引用工具的最新版本(或者任何版本)。似乎是一个足够简单的工具,也许有另一种使用其他工具/命令/msbuild任务签署程序集的方法?非常感谢您的帮助。


sn.exe有一个关联的配置文件。我注意到如果你不向exe提供任何参数,它也会崩溃。 - OnResolve
我已经尝试复制 sn.exe.config 文件,但似乎也不起作用。甚至不能运行 sn.exe -h 命令获取帮助信息,否则就会崩溃。 - Ocelot20
你可以尝试下载Dependency Walker,看看是否有任何缺失的依赖项。 - default
6个回答

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>

7

为了在msbuild脚本中正确引用像snsqlmetal这样的工具(我所追求的),以便为大多数人提供有效的工作方式,您必须考虑操作环境和框架实现的不同方面。有两个主要情况:Microsoft Windows和Microsoft的框架实现,以及其他所有内容(我的意思是Mono / unix)。支持我能想到的各种情况的正确方法示例列在结尾处。

Microsoft

在Windows中找到sn或其他类似工具的正确方法是从GetFrameworkSdkPath任务开始,如已经提到的那样。

然而,正如问题所述,无法直接确定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>

(参见属性函数,了解为什么我可以在这里使用String.TrimEndWHERE不喜欢尾随斜杠。编辑:我添加了使用属性函数访问Regex.Replace()以删除SNPath属性中除第一个找到的路径之外的所有内容。我的朋友的机器上的WHERE调用将为某些命令输出多个结果,并破坏任何尝试执行<Exec/>的工具。此更改确保仅找到一个结果,并且<Exec/>实际成功。)

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

便携式

毫不意外,在Windows以外的任何操作系统上,解决sn的路径要简单得多。在Mac OSX和任何Linux发行版上,我可以在PATH中找到sn。在这种情况下,使用GetFrameworkSdkPath并没有帮助;事实上,对于我在使用xbuild时测试的旧版本mono-2.10而言,它似乎返回了一个无法找到sn的路径:

  • 在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.0,而sn/usr/bin/sn(它是一个shell脚本,用mono调用/usr/lib64/mono/4.0/sn.exe)。
因此,我们需要尝试执行sn。任何将其sn实现放置在非标准位置的Unix用户都知道要适当更新PATH,因此构建脚本无需搜索它。此外,在Unix中不存在WHERE。因此,在Unix情况下,我们想要用某些输出仅在Unix上输出sn的东西来替换第一个<Exec/>调用,并在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 所需的字符串,具有便携性。这种解决方案同时支持 Microsoft 及其 msbuild 和使用 xbuild 的其他平台。它还克服了将 bin\NETFX 4.0 Tools 硬编码到 .csproj 文件中以同时支持当前和未来版本的 Microsoft 工具的问题。

绝对比我的原始回答更详尽。+1。 - Ocelot20

2

我目前使用的方法是使用属性函数在框架SDK路径下执行对SN.exe的搜索,如下所示:

<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。


1

就我所知,在类似情况下,$(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>

1

您可以在每台开发机器上创建一个环境变量,该变量引用可由MSBuild作为属性引用的可执行文件。

因此,请通过系统属性的高级选项卡创建环境变量。我通常只创建系统环境变量,而不是针对当前用户的环境变量。您需要重新启动Visual Studio才能使其生效。

然后,在MSBuild中引用它:

<Exec Command="$(SnExe)">

其中SnExe是您定义的环境变量。


这不是很便携,并且需要机器级别的更改才能允许构建运行。目标是允许一种与机器无关的方式从.NET sdk中查找工具。 - Sebazzz

0

.NET Framework 工具(例如 sn.exe)的路径实际上是使用 GetFrameworkSDKPath(如上所述)和(几乎)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>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接