OldSquirrelForWindows
OldSquirrelForWindows copied to clipboard
Challenge: detect whether a project is .NET4/.NET45/.NET451
So we're going to have to cross the "install .NET 4.5" bridge at some stage, but there's things we can do right now to help make that step easier.
Here's a challenge for someone brave enough:
For a given csproj file, can you detect whether it targets .NET 4 or .NET 4.5 or .NET 4.5.1?
TODO:
- [ ] build a little Powershell function to do this
- [ ] checkin some automated tests using the existing structure
- [ ] on
New-Release
, let's check and report to the user what version they are targeting
The longer-term plans I have are roughly:
- on a new release, detect what framework the application depends on
- generate a setup.exe which includes the frameworks needed
- when the user runs it, if the framework is missing, the installer elevates and installs them
- sunshine and rainbows
Isn't this just grepping for TargetFrameworkVersion
?
@paulcbetts yes
@shiftkey
<PropertyGroup>
<ShimmerFrameworkTarget Condition=" '$(Framework)' == 'NET20' ">NET20</ShimmerFrameworkTarget>
<ShimmerFrameworkTarget Condition=" '$(Framework)' == 'NET35' ">NET35</ShimmerFrameworkTarget>
<ShimmerFrameworkTarget Condition=" '$(Framework)' == 'NET40' ">NET40</ShimmerFrameworkTarget>
<ShimmerFrameworkTarget Condition=" '$(Framework)' == 'NET451' ">NET451</ShimmerFrameworkTarget>
</PropertyGroup>
Unsure about NET451
http://stackoverflow.com/questions/2923210/c-sharp-conditional-compilation-and-framework-targets/2928835#2928835
@peters I want this to be available from Powershell, and I think we can be smart enough to do it without touching their csproj files...
@shiftkey
Install-Package Mono.Cecil
using System;
using System.Diagnostics;
using System.Linq;
using Mono.Cecil;
namespace cecil
{
class Program
{
static void Main(string[] args)
{
var version = GetTargetVersion(@"C:\Users\peters\Documents\GitHub2\opus\src\opus\bin\x86\Release\opus.exe");
Console.WriteLine(version);
Process.GetCurrentProcess().WaitForExit();
}
private static ShimmerTargetRuntime GetTargetVersion(string assemblyPath)
{
var platformAsm = AssemblyDefinition.ReadAssembly(assemblyPath);
foreach (var targetFrameworkVersion in platformAsm.CustomAttributes
.Where(attr => attr.AttributeType.FullName == "System.Runtime.Versioning.TargetFrameworkAttribute")
.Select(attr => attr.Properties.FirstOrDefault().Argument.Value.ToString()))
{
if (targetFrameworkVersion.StartsWith(".NET Framework 4.5.1"))
{
return ShimmerTargetRuntime.Net_4_5_1;
}
if (targetFrameworkVersion.StartsWith(".NET Framework 4.5"))
{
return ShimmerTargetRuntime.Net_4_5;
}
}
return (ShimmerTargetRuntime)platformAsm.MainModule.Runtime;
}
public enum ShimmerTargetRuntime
{
Net_1_0 = TargetRuntime.Net_1_0,
Net_1_1 = TargetRuntime.Net_1_1,
Net_2_0 = TargetRuntime.Net_2_0,
Net_4_0 = TargetRuntime.Net_4_0,
Net_4_5 = TargetRuntime.Net_4_0 << 1,
Net_4_5_1 = TargetRuntime.Net_4_0 << 2
}
}
}
Should do the trick :)
I'm not a powershell guy, but i guess you could call this method somehow ;) After doing some research on stackoverflow, and checking what ILSpy and ILRepack does, this is by far the most reliable way.
This looks great, but what file are you going to examine? What if they have two EXEs? How do you find the paths of said EXEs?
@peters I think you do not need the Mono.Cecil package, you can get the TargetFrameworkAttribute easily via reflection.
var ass = Assembly.LoadFrom("<PathToDllOrExe>");
var target = (TargetFrameworkAttribute)ass.GetCustomAttributes(typeof(TargetFrameworkAttribute), false).FirstOrDefault();
if(target!=null)
Console.WriteLine(target.FrameworkName);
if target is null then the Frameworkversion is smaller then 4.0
Same is also possible in powershell
$ass = [System.Reflection.Assembly]::LoadFrom("<PathToDllOrExe>")
$framework = $ass.GetCustomAttributes([System.Type]::GetType("System.Runtime.Versioning.TargetFrameworkAttribute"), $false)[0].FrameworkName
Maybe it's also to think about to detect if it's dotNet 4.0 Client Profile or Full
@schulz3000 Aha, that's a much smoother approach ;)
The problem with LoadFrom
is that you now have to successfully load the assembly (including resolving all of its dependencies) - aren't there reflection-only versions of Assembly.Load
?
@paulcbetts Oh, well then Assembly.Load
is out. You could parse the MSIL yourself. I could probably rip out the code from Mono.Cecil
, but it seems like hell of alot of work and i really don't see any issues with depending on Mono.Cecil
. If so, do care to enlighten me ;)
I agree with @paulcbetts, loading the target assembly is probably not a good idea.
Here's my approach using MsBuild directly:
Add-Type -AssemblyName 'Microsoft.Build'
[Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.UnloadAllProjects()
$project = New-Object 'Microsoft.Build.Evaluation.Project' MyApp.csproj'
$targetFx = $project.Properties | Where-Object {$_.Name -eq 'TargetFrameworkVersion'} | Select-Object EvaluatedValue -First 1
$targetProfile = $project.Properties | Where-Object {$_.Name -eq 'TargetFrameworkProfile'} | Select-Object EvaluatedValue -First 1
[Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.UnloadAllProjects()
Note: the UnloadAllProjects() calls are to avoid loading stale data when the csproj is modified (loaded projects are cached)
@akoeplinger :sparkles: I think we're getting close.
I'm currently working through the elevation issue here #163 but I'll stub out a method here for us to do that check.
The end goal for this is "hey, you've got a .NET45 project - let's create a .NET45 installer"
@shiftkey @paulcbetts I've created the powershell version of what cecil does. Works with native binaries also.
Gist available here
NB! It's not possible to tell NET45 from NET451 without writing another 500 lines of code. I'll consider it if you need it.
Prerequisites for testing
$ git clone https://github.com/peters/myget.git C:\myget-repository-path\bin
$ cd C:\myget-repository-path\
$ .\myget.ps1 -fakeBuildRunner 1
$ Run gist!
Sample output
PS H:\> D:\Bruker_Data\assemblyinfo.ps1
MSIL - AnyCpu
ProcessorArchitecture : AnyCpu
PEFormat : PE32
Filename : D:\Bruker_Data\anycpu-mixedplatforms\bin\sample.solution.anycpu\1.0.0\AnyCpu\Release\v2.0\sample.solution.anycpu.exe
ModuleKind : Console
ModuleAttributes : ILOnly
MajorRuntimeVersion : 2
ModuleCharacteristics : {DynamicBase, NoSEH, NXCompat, TerminalServerAware}
TargetFramework : NET20
MinorRuntimeVersion : 5
ProcessorArchitecture : AnyCpu
PEFormat : PE32
Filename : D:\Bruker_Data\anycpu-mixedplatforms\bin\sample.solution.anycpu\1.0.0\AnyCpu\Release\v4.0\sample.solution.anycpu.exe
ModuleKind : Console
ModuleAttributes : ILOnly
MajorRuntimeVersion : 2
ModuleCharacteristics : {DynamicBase, NoSEH, NXCompat, TerminalServerAware}
TargetFramework : NET40
MinorRuntimeVersion : 5
ProcessorArchitecture : AnyCpu
PEFormat : PE32
Filename : D:\Bruker_Data\anycpu-mixedplatforms\bin\sample.solution.anycpu\1.0.0\AnyCpu\Release\v4.5\sample.solution.anycpu.exe
ModuleKind : Console
ModuleAttributes : {ILOnly, Required32Bit, Preferred32Bit}
MajorRuntimeVersion : 2
ModuleCharacteristics : {HighEntropyVA, DynamicBase, NoSEH, NXCompat...}
TargetFramework : NET45
MinorRuntimeVersion : 5
ProcessorArchitecture : AnyCpu
PEFormat : PE32
Filename : D:\Bruker_Data\anycpu-mixedplatforms\bin\sample.solution.anycpu\1.0.0\AnyCpu\Release\v4.5.1\sample.solution.anycpu.exe
ModuleKind : Console
ModuleAttributes : {ILOnly, Required32Bit, Preferred32Bit}
MajorRuntimeVersion : 2
ModuleCharacteristics : {HighEntropyVA, DynamicBase, NoSEH, NXCompat...}
TargetFramework : NET45
MinorRuntimeVersion : 5
MSIL - x86
ProcessorArchitecture : AnyCpu
PEFormat : PE32
Filename : D:\Bruker_Data\anycpu-mixedplatforms\bin\sample.solution.mixedplatforms\1.0.0\x86\Release\v2.0\sample.solution.mixedplatf
orms.x86.exe
ModuleKind : Console
ModuleAttributes : {ILOnly, Required32Bit}
MajorRuntimeVersion : 2
ModuleCharacteristics : {DynamicBase, NoSEH, NXCompat, TerminalServerAware}
TargetFramework : NET20
MinorRuntimeVersion : 5
ProcessorArchitecture : AnyCpu
PEFormat : PE32
Filename : D:\Bruker_Data\anycpu-mixedplatforms\bin\sample.solution.mixedplatforms\1.0.0\x86\Release\v4.0\sample.solution.mixedplatf
orms.x86.exe
ModuleKind : Console
ModuleAttributes : {ILOnly, Required32Bit}
MajorRuntimeVersion : 2
ModuleCharacteristics : {DynamicBase, NoSEH, NXCompat, TerminalServerAware}
TargetFramework : NET40
MinorRuntimeVersion : 5
ProcessorArchitecture : AnyCpu
PEFormat : PE32
Filename : D:\Bruker_Data\anycpu-mixedplatforms\bin\sample.solution.mixedplatforms\1.0.0\x86\Release\v4.5\sample.solution.mixedplatf
orms.x86.exe
ModuleKind : Console
ModuleAttributes : {ILOnly, Required32Bit}
MajorRuntimeVersion : 2
ModuleCharacteristics : {HighEntropyVA, DynamicBase, NoSEH, NXCompat...}
TargetFramework : NET45
MinorRuntimeVersion : 5
ProcessorArchitecture : AnyCpu
PEFormat : PE32
Filename : D:\Bruker_Data\anycpu-mixedplatforms\bin\sample.solution.mixedplatforms\1.0.0\x86\Release\v4.5.1\sample.solution.mixedpla
tforms.x86.exe
ModuleKind : Console
ModuleAttributes : {ILOnly, Required32Bit}
MajorRuntimeVersion : 2
ModuleCharacteristics : {HighEntropyVA, DynamicBase, NoSEH, NXCompat...}
TargetFramework : NET45
MinorRuntimeVersion : 5
MSIL - x64
ProcessorArchitecture : x64
PEFormat : PE32Plus
Filename : D:\Bruker_Data\anycpu-mixedplatforms\bin\sample.solution.mixedplatforms\1.0.0\x64\Release\v2.0\sample.solution.mixedplatf
orms.x64.exe
ModuleKind : Console
ModuleAttributes : ILOnly
MajorRuntimeVersion : 2
ModuleCharacteristics : {DynamicBase, NoSEH, NXCompat, TerminalServerAware}
TargetFramework : NET20
MinorRuntimeVersion : 5
ProcessorArchitecture : x64
PEFormat : PE32Plus
Filename : D:\Bruker_Data\anycpu-mixedplatforms\bin\sample.solution.mixedplatforms\1.0.0\x64\Release\v4.0\sample.solution.mixedplatf
orms.x64.exe
ModuleKind : Console
ModuleAttributes : ILOnly
MajorRuntimeVersion : 2
ModuleCharacteristics : {DynamicBase, NoSEH, NXCompat, TerminalServerAware}
TargetFramework : NET40
MinorRuntimeVersion : 5
ProcessorArchitecture : x64
PEFormat : PE32Plus
Filename : D:\Bruker_Data\anycpu-mixedplatforms\bin\sample.solution.mixedplatforms\1.0.0\x64\Release\v4.5\sample.solution.mixedplatf
orms.x64.exe
ModuleKind : Console
ModuleAttributes : ILOnly
MajorRuntimeVersion : 2
ModuleCharacteristics : {HighEntropyVA, DynamicBase, NoSEH, NXCompat...}
TargetFramework : NET45
MinorRuntimeVersion : 5
ProcessorArchitecture : x64
PEFormat : PE32Plus
Filename : D:\Bruker_Data\anycpu-mixedplatforms\bin\sample.solution.mixedplatforms\1.0.0\x64\Release\v4.5.1\sample.solution.mixedpla
tforms.x64.exe
ModuleKind : Console
ModuleAttributes : ILOnly
MajorRuntimeVersion : 2
ModuleCharacteristics : {HighEntropyVA, DynamicBase, NoSEH, NXCompat...}
TargetFramework : NET45
MinorRuntimeVersion : 5
PS H:\>
@shiftkey NET45 is detected by https://gist.github.com/peters/6991125#file-getassemblyinfo-ps1-L496
I discovered via dumpbin that the minor subsystem version was different between 4.0 and 4.5 binaries otherwise it would be impossible to detect. Sadly this is not the case between 4.5 and 4.5.1.
@shiftkey Sorry bud, but powershell is a no go if you want to differentiate NET45 and NET451, so i created a nuget package that uses cecil to extract the correct target framework: https://github.com/peters/assemblyinfo/tree/master
Tests available here.
Nuget package available here
Btw, cecil is merged into assemblyinfo.dll using ILRepack
You can build the project by running .\myget.ps1 -packageVersion 0.1.0
@shiftkey Are you on vacation? ;)
var minimumRuntime = AssemblyInfo.GetTargetFramework(new List<Stream>
{
assembly1,
assembly2,
assembly3,
assembly4
}).MinimumSupportedRuntime();
switch (minimumRuntime) {
case TargetFramework.Net_4_0:
break;
case TargetFramework.Net_4_5:
break;
case TargetFramework.Net_4_5_1:
break;
}
@peters I've been busy with Other Things, this Shimmer backlog is something I'll be triaging tomorrow...
:+1:
@shiftkey Guess you can close this ticket now :)