Everything that's been written here is correct, of course, but the original question has not been answered, yet. Theoretically, you could write such a tool yourself by iterating through the code and see if you find a reference to a method that first existed in this SP1. Here's a crude example that uses Cecil to parse the assembly:
namespace SampleNamespace
{
using System;
using Mono.Cecil;
using Mono.Cecil.Cil;
internal static class Program
{
public static void Main(string[] args)
{
foreach (string arg in args)
{
Console.WriteLine("{0}: {1}", arg, NeedsDotNet35SP1(arg));
Console.ReadLine();
}
}
private static bool NeedsDotNet35SP1(string fileName)
{
return NeedsDotNet35SP1(ModuleDefinition.ReadModule(fileName));
}
private static bool NeedsDotNet35SP1(ModuleDefinition module)
{
if (TargetRuntime.Net_2_0 != module.Runtime)
{
// I don't care about .NET 1.0, 1.1. or 4.0 for this example.
return false;
}
foreach (TypeDefinition type in module.Types)
{
if (NeedsDotNet35SP1(type))
{
return true;
}
}
return false;
}
private static bool NeedsDotNet35SP1(TypeDefinition type)
{
foreach (MethodDefinition method in type.Methods)
{
if (NeedsDotNet35SP1(method))
{
return true;
}
}
return false;
}
private static bool NeedsDotNet35SP1(MethodDefinition method)
{
return NeedsDotNet35SP1(method.Body);
}
private static bool NeedsDotNet35SP1(MethodBody body)
{
foreach (Instruction instruction in body.Instructions)
{
if (NeedsDotNet35SP1(instruction))
{
return true;
}
}
return false;
}
private static bool NeedsDotNet35SP1(Instruction instruction)
{
if (OperandType.InlineMethod != instruction.OpCode.OperandType)
{
return false;
}
return NeedsDotNet35SP1((MethodReference)instruction.Operand);
}
private static bool NeedsDotNet35SP1(MethodReference method)
{
return method.FullName.Equals(
"System.Boolean System.Threading.WaitHandle::WaitOne(System.Int32)",
StringComparison.OrdinalIgnoreCase);
}
}
}
Obviously, this example only takes that one method into account, but it should be possible to expand on it, if you really need to. :)