How should I reference sn.exe in msbuild script?

2019-03-31 22:08发布

I need to re-sign my assembly after the build has finished (and I've done some other things to it), so I started by adding an <Exec> Task that called C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\sn.exe. This has to work for other developers/environments, so I was hoping I could just copy sn.exe and sn.exe.config from that folder and store it in our code repository so I could always call a common version of it from a known location.

sn.exe crashes in isolation outside of the sdk directory, so I'm wondering how I can reference it without knowing what path it will be under. Different people have different environments (x86 vs x64, different install directories, different versions), so I would like to be able to easily reference the latest version of the tool (or perhaps any version). Seems like a simple enough tool, perhaps there is another way to sign an assembly with another tool/command/msbuild task? Any help would be appreciated.

5条回答
一纸荒年 Trace。
2楼-- · 2019-03-31 22:16

The method I'm using at the moment involves using a property function to perform a search for SN.exe underneath the framework SDK path - ala:

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

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

So far I've only tested this personally on Visual Studio 2013, but the documentation implies it ought to work back to Visual Studio 2010.

查看更多
放荡不羁爱自由
3楼-- · 2019-03-31 22:28

You can create an environment variable on each development machine that references the executable, which MSBuild lets you reference as a property.

So, create the environment variable via the advanced tab of system properties. I usually just create a system environment variable as opposed to one scoped to the current user. You will have to restart Visual Studio for it to pick it up.

Then, reference it in MSBuild:

<Exec Command="$(SnExe)">

Where SnExe is the environment variable you defined.

查看更多
可以哭但决不认输i
4楼-- · 2019-03-31 22:32

For what its worth the $(SDK40ToolsPath) variable worked for me in a similar situation. This negates the need to know which specific version of the tools are installed eg:

  <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楼-- · 2019-03-31 22:33

Turns out there's a task called "GetFrameworkSdkPath" that will get the Windows SDK location. From there, I had to test to see if sn.exe existed directly in the bin folder, or if it's in bin\NETFX 4.0 Tools\. Seems reliable so far.

<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>
查看更多
走好不送
6楼-- · 2019-03-31 22:38

To properly reference a tool like sn or sqlmetal (what I am after) in an msbuild script in the way that will work for the most people, you must take into consideration different aspects of the operating environment and framework implementation. There are two main cases: Microsoft Windows and Microsoft’s implementation of the framework followed by everything else (by which I mean Mono/unix). An example of the correct approach which supports the situations I can think of is listed at the end.

Microsoft

The proper way to find where sn or other similar tools live in Windows is to start with the GetFrameworkSdkPath task, as already mentioned.

However, as the question suggests, the exact location within the FrameworkSdkPath that sn or another tool lives cannot be determined directly. The referenced answer suggests that the only possible folders under FrameworkSdkPath for tools to reside in are bin and bin/NETFX 4.0 Tools. However, other values are possible (Visual Studio 2013 Preview uses bin/NETFX 4.5.1 Tools). Thus, the only proper way to search for sn is to use a glob expression or recursively search for it. I have trouble figuring out how to do glob expansion with MSBuild and the built-in MSBuild tasks do not seem to support searching under FrameworkSdkPath for particular utilities. However, cmd’s WHERE has this functionality and can be used to do the search. The result is something like the following msbuild code:

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

(See Property Functions to see why I can use String.TrimEnd here. WHERE doesn’t like trailing slashes. EDIT: I added use of Property Functions to access Regex.Replace() to delete all but the first found path in the SNPath property. One of my friend’s machines’s WHERE invocations would output multiple results for certain commands and broke any attempt to <Exec/> the fond tool. This change ensures that only one result is found and that <Exec/>s actually succeed.)

Now you can invoke sn with <Exec Command="&quot;$(SNPath)&quot;" />.

Portable

Unsurprisingly, resolving the path to sn is much simpler on any operating system other than Windows. On Mac OSX and any distribution of Linux, I find sn in the PATH. Using GetFrameworkSdkPath does not help in such a situation; in fact, this seems to return a path under which sn cannot be found, at least for the old versions of mono-2.10 I tested while using xbuild:

  • On Mac OSX FrameworkSdkPath is /Library/Frameworks/Mono.framework/Versions/2.10.5/lib/mono/2.0 and /usr/bin/sn is a symlink to /Library/Frameworks/Mono.framework/Commands/sn.
  • On a certain Linux install, FrameworkSdkPath is /usr/lib64/mono/2.0 and sn is /usr/bin/sn (which is a shell script invoking /usr/lib64/mono/4.0/sn.exe with mono).

Thus, all we need to do is try to execute sn. Any unix users placing their sn implementations in non-standard places already know to update PATH appropriately, so the build script has no need to ever search for it. Also, WHERE does not exist in unix. Thus, in the unix case, we want to replace the first <Exec/> call with something that will output just sn on unix and still do the full search when run on Windows. To differentiate unix-like and Windows environments, we use a trick which takes advantage of unix shells’ shortcut for the true commmand and cmd’s label syntax. As a short example, the following script will output I’m unix! in a unix shellout and I’m Windows :-/ on a Windows shellout.

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

Taking advantage of this, our resulting GetSNPath Task looks like:

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

The result is a portable method for finding the string required to invoke sn. This last solution lets you support both Microsoft and its msbuild and every other platform using xbuild. It also overcomes hardcoding bin\NETFX 4.0 Tools into .csproj files to support future and current versions of Microsoft tools simultaneously.

查看更多
登录 后发表回答