OldSquirrelForWindows icon indicating copy to clipboard operation
OldSquirrelForWindows copied to clipboard

Challenge: detect whether a project is .NET4/.NET45/.NET451

Open shiftkey opened this issue 11 years ago • 20 comments

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

shiftkey avatar Sep 25 '13 05:09 shiftkey

Isn't this just grepping for TargetFrameworkVersion?

anaisbetts avatar Sep 25 '13 17:09 anaisbetts

@paulcbetts yes

shiftkey avatar Sep 25 '13 22:09 shiftkey

@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 avatar Sep 26 '13 07:09 peters

@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 avatar Sep 26 '13 07:09 shiftkey

@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 :)

peters avatar Sep 26 '13 08:09 peters

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.

peters avatar Sep 26 '13 08:09 peters

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?

anaisbetts avatar Sep 26 '13 18:09 anaisbetts

@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 avatar Sep 26 '13 18:09 schulz3000

@schulz3000 Aha, that's a much smoother approach ;)

peters avatar Sep 27 '13 06:09 peters

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?

anaisbetts avatar Sep 27 '13 18:09 anaisbetts

@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 ;)

peters avatar Sep 28 '13 01:09 peters

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 avatar Sep 28 '13 23:09 akoeplinger

@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 avatar Sep 28 '13 23:09 shiftkey

@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:\> 

peters avatar Oct 15 '13 12:10 peters

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

peters avatar Oct 15 '13 12:10 peters

@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

image

peters avatar Oct 17 '13 14:10 peters

@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 avatar Oct 22 '13 08:10 peters

@peters I've been busy with Other Things, this Shimmer backlog is something I'll be triaging tomorrow...

shiftkey avatar Oct 22 '13 11:10 shiftkey

:+1:

peters avatar Oct 22 '13 15:10 peters

@shiftkey Guess you can close this ticket now :)

peters avatar Mar 26 '14 16:03 peters