Browse Source

Bug 1640: Support for .NET Core/Standard + Supporting .NET Framework 4.0 and newer only

https://winscp.net/tracker/1640

Source commit: 817bf4ffa77497183362850fdcef2691c066e1ca
Martin Prikryl 7 years ago
parent
commit
d49ae8072b

+ 6 - 1
build.bat

@@ -19,5 +19,10 @@ set BDS_BUILD_PROPERTIES=RELEASE_TYPE=%RELEASE_TYPE%;CONFIG=%BUILD_CONFIG%;INTER
 
 if "%WITH_DOTNET%"=="0" goto SKIP_DOTNET
 cd ..\dotnet
-"%MSBUILD%" WinSCPnet.csproj /t:Build /p:Configuration=%BUILD_CONFIG%;Platform=AnyCPU;INTERM_PATH=.;FINAL_PATH=.
+set DOTNET_BUILD_PROPERTIES=INTERM_PATH=.;FINAL_PATH=.
+dotnet restore WinSCPnet.csproj -p:%DOTNET_BUILD_PROPERTIES%
+mkdir obj
+move win32\Debug\WinSCPnet.csproj.nuget.g.targets obj\
+rmdir /s /q win32
+dotnet build WinSCPnet.csproj -c %BUILD_CONFIG% -p:%DOTNET_BUILD_PROPERTIES%
 :SKIP_DOTNET

+ 2 - 1
deployment/WinSCPnet.nuspec

@@ -22,7 +22,8 @@ The NuGet package includes the assembly itself and a required WinSCP executable.
     <tags>winscp sftp ftp ftps webdav s3 scp transfer</tags>
   </metadata>
   <files>
-    <file src="$DotNetBuildConfigDir$\WinSCPnet.dll" target="lib\net"/>
+    <file src="$DotNetBuildConfigDir$\net40\WinSCPnet.dll" target="lib\net"/>
+    <file src="$DotNetBuildConfigDir$\netstandard2.0\WinSCPnet.dll" target="lib\netstandard"/>
     <file src="$BuildConfigDir$\WinSCP.exe" target="tools"/>
     <file src="WinSCP.targets" target="build"/>
   </files>

+ 8 - 0
dotnet/Session.cs

@@ -65,8 +65,10 @@ namespace WinSCP
     public sealed class Session : IDisposable, IReflect
     {
         public string ExecutablePath { get { return _executablePath; } set { CheckNotOpened(); _executablePath = value; } }
+#if !NETSTANDARD
         public string ExecutableProcessUserName { get { return _executableProcessUserName; } set { CheckNotOpened(); _executableProcessUserName = value; } }
         public SecureString ExecutableProcessPassword { get { return _executableProcessPassword; } set { CheckNotOpened(); _executableProcessPassword = value; } }
+#endif
         public string AdditionalExecutableArguments { get { return _additionalExecutableArguments; } set { CheckNotOpened(); _additionalExecutableArguments = value; } }
         [Obsolete("Use AddRawConfiguration")]
         public bool DefaultConfiguration { get { return _defaultConfiguration; } set { CheckNotOpened(); _defaultConfiguration = value; } }
@@ -297,7 +299,9 @@ namespace WinSCP
 
                         if (_process.HasExited && !File.Exists(XmlLogPath))
                         {
+#if !NETSTANDARD
                             Logger.WriteCounters();
+#endif
                             Logger.WriteProcesses();
                             _process.WriteStatus();
                             string exitCode = string.Format(CultureInfo.CurrentCulture, "{0}", _process.ExitCode);
@@ -1201,6 +1205,7 @@ namespace WinSCP
             RawConfiguration.Add(setting, value);
         }
 
+#if !NETSTANDARD
         [ComRegisterFunction]
         private static void ComRegister(Type t)
         {
@@ -1221,6 +1226,7 @@ namespace WinSCP
             string subKey = GetTypeLibKey(t);
             Registry.ClassesRoot.DeleteSubKey(subKey, false);
         }
+#endif
 
         private void ReadFile(RemoteFileInfo fileInfo, CustomLogReader fileReader)
         {
@@ -2262,8 +2268,10 @@ namespace WinSCP
         private int _progressHandling;
         private bool _guardProcessWithJob;
         private string _homePath;
+#if !NETSTANDARD
         private string _executableProcessUserName;
         private SecureString _executableProcessPassword;
+#endif
         private StringCollection _error;
         private bool _ignoreFailed;
         private TimeSpan _sessionTimeout;

+ 7 - 110
dotnet/WinSCPnet.csproj

@@ -1,117 +1,14 @@
 <?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProductVersion>8.0.30703</ProductVersion>
-    <SchemaVersion>2.0</SchemaVersion>
-    <ProjectGuid>{DF44CF5D-6809-4557-A343-FEC46F14B3FF}</ProjectGuid>
-    <OutputType>Library</OutputType>
-    <AppDesignerFolder>Properties</AppDesignerFolder>
-    <RootNamespace>WinSCP</RootNamespace>
-    <AssemblyName>WinSCPnet</AssemblyName>
-    <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
-    <FileAlignment>512</FileAlignment>
+    <TargetFrameworks>net40;netstandard2.0</TargetFrameworks>
+    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <OutputPath>$(FINAL_PATH)\win32\$(Configuration)</OutputPath>
     <IntermediateOutputPath>$(INTERM_PATH)\win32\$(Configuration)</IntermediateOutputPath>
     <BaseIntermediateOutputPath>$(INTERM_PATH)\win32\$(Configuration)</BaseIntermediateOutputPath>
-    <TargetFrameworkProfile />
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <CodeAnalysisRuleSet>WinSCPnet.ruleset</CodeAnalysisRuleSet>
-    <RunCodeAnalysis>true</RunCodeAnalysis>
-    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <DebugType>pdbonly</DebugType>
-    <Optimize>true</Optimize>
-    <DefineConstants>TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <CodeAnalysisRuleSet>WinSCPnet.ruleset</CodeAnalysisRuleSet>
-    <RunCodeAnalysis>true</RunCodeAnalysis>
-    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
-  <PropertyGroup />
-  <PropertyGroup>
-    <SignAssembly>false</SignAssembly>
-  </PropertyGroup>
-  <PropertyGroup>
-    <AssemblyOriginatorKeyFile>
-    </AssemblyOriginatorKeyFile>
-  </PropertyGroup>
-  <PropertyGroup>
-    <DelaySign>false</DelaySign>
+  <PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
+    <DefineConstants>NETSTANDARD</DefineConstants>
   </PropertyGroup>
-  <ItemGroup>
-    <Reference Include="System" />
-    <Reference Include="System.Xml" />
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="ChmodEventArgs.cs" />
-    <Compile Include="CommandExecutionResult.cs" />
-    <Compile Include="Internal\Callstack.cs" />
-    <Compile Include="Internal\CallstackAndLock.cs" />
-    <Compile Include="Internal\ConsoleCommStruct.cs" />
-    <Compile Include="Internal\ExeSessionProcess.cs" />
-    <Compile Include="Internal\GenericSecurity.cs" />
-    <Compile Include="Internal\Job.cs" />
-    <Compile Include="Internal\Lock.cs" />
-    <Compile Include="Internal\Logger.cs" />
-    <Compile Include="Internal\PatientFileStream.cs" />
-    <Compile Include="Internal\ProgressHandler.cs" />
-    <Compile Include="Internal\SessionElementLogReader.cs" />
-    <Compile Include="Internal\Tools.cs" />
-    <Compile Include="Internal\UnsafeNativeMethods.cs" />
-    <Compile Include="OutputDataReceivedEventArgs.cs" />
-    <Compile Include="FileTransferProgressEventArgs.cs" />
-    <Compile Include="QueryReceivedEventArgs.cs" />
-    <Compile Include="RemoteDirectoryInfo.cs" />
-    <Compile Include="FailedEventArgs.cs" />
-    <Compile Include="FileOperationEventArgs.cs" />
-    <Compile Include="GlobalSuppressions.cs" />
-    <Compile Include="FilePermissions.cs" />
-    <Compile Include="Internal\Constants.cs" />
-    <Compile Include="Internal\ReadOnlyInteropCollectionHelper.cs" />
-    <Compile Include="InteropCollections\RemoteFileInfoCollection.cs" />
-    <Compile Include="InteropCollections\RemovalEventArgsCollection.cs" />
-    <Compile Include="InteropCollections\SessionRemoteExceptionCollection.cs" />
-    <Compile Include="InteropCollections\StringCollection.cs" />
-    <Compile Include="InteropCollections\TransferEventArgsCollection.cs" />
-    <Compile Include="RemotePath.cs" />
-    <Compile Include="RemovalEventArgs.cs" />
-    <Compile Include="RemovalOperationResult.cs" />
-    <Compile Include="SessionEvents.cs" />
-    <Compile Include="SynchronizationResult.cs" />
-    <Compile Include="TransferEventArgs.cs" />
-    <Compile Include="TransferOperationResult.cs" />
-    <Compile Include="TransferOptions.cs" />
-    <Compile Include="Internal\CustomLogReader.cs" />
-    <Compile Include="Internal\ElementLogReader.cs" />
-    <Compile Include="Internal\OperationResultGuard.cs" />
-    <Compile Include="OperationEventArgs.cs" />
-    <Compile Include="OperationResultBase.cs" />
-    <Compile Include="RemoteFileInfo.cs" />
-    <Compile Include="Session.cs" />
-    <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="SessionOptions.cs" />
-    <Compile Include="SessionException.cs" />
-    <Compile Include="SessionLocalException.cs" />
-    <Compile Include="SessionRemoteException.cs" />
-    <Compile Include="Internal\SessionLogReader.cs" />
-    <Compile Include="TouchEventArgs.cs" />
-    <Compile Include="TransferResumeSupport.cs" />
-  </ItemGroup>
-  <ItemGroup>
-    <None Include="App.config">
-      <SubType>Designer</SubType>
-    </None>
-  </ItemGroup>
-  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
-</Project>
+</Project>

+ 20 - 2
dotnet/internal/ExeSessionProcess.cs

@@ -152,6 +152,7 @@ namespace WinSCP
                 // The /console is redundant for CreateForConsole
                 _process.StartInfo.Arguments += string.Format(CultureInfo.InvariantCulture, " /console /consoleinstance={0}", _instanceName);
 
+#if !NETSTANDARD
                 // When running under IIS in "impersonated" mode, the process starts, but does not do anything.
                 // Supposedly it "displayes" some invisible error message when starting and hangs.
                 // Running it "as the user" helps, eventhough it already runs as the user.
@@ -192,6 +193,7 @@ namespace WinSCP
                         throw _logger.WriteException(new SessionLocalException(_session, "Error granting access to desktop", e));
                     }
                 }
+#endif
 
                 _logger.WriteLine("Starting \"{0}\" {1}", _process.StartInfo.FileName, _process.StartInfo.Arguments);
 
@@ -226,6 +228,7 @@ namespace WinSCP
             }
         }
 
+#if !NETSTANDARD
         private void GrantAccess(IntPtr handle, int accessMask)
         {
             using (SafeHandle safeHandle = new NoopSafeHandle(handle))
@@ -238,6 +241,7 @@ namespace WinSCP
                 security.Persist(safeHandle, AccessControlSections.Access);
             }
         }
+#endif
 
         private void ProcessExited(object sender, EventArgs e)
         {
@@ -580,6 +584,7 @@ namespace WinSCP
             {
                 IntPtr securityAttributesPtr = IntPtr.Zero;
 
+#if !NETSTANDARD
                 // We use the EventWaitHandleSecurity only to generate the descriptor binary form
                 // that does not differ for object types, so we abuse the existing "event handle" implementation,
                 // not to have to create the file mapping SecurityAttributes via P/Invoke.
@@ -605,6 +610,7 @@ namespace WinSCP
                     securityAttributesPtr = Marshal.AllocHGlobal(length);
                     Marshal.StructureToPtr(securityAttributes, securityAttributesPtr, false);
                 }
+#endif
 
                 return
                     UnsafeNativeMethods.CreateFileMapping(
@@ -622,16 +628,23 @@ namespace WinSCP
         {
             _logger.WriteLine("Creating event {0}", name);
 
+            string securityDesc;
+#if !NETSTANDARD
             EventWaitHandleSecurity security = CreateSecurity(EventWaitHandleRights.FullControl);
 
             ev = new EventWaitHandle(false, EventResetMode.AutoReset, name, out bool createdNew, security);
+            securityDesc = (security != null ? security.GetSecurityDescriptorSddlForm(AccessControlSections.All) : "none");
+#else
+            ev = new EventWaitHandle(false, EventResetMode.AutoReset, name, out bool createdNew);
+            securityDesc = "not impl";
+#endif
             _logger.WriteLine(
                 "Created event {0} with handle {1} with security {2}, new {3}",
-                name, ev.SafeWaitHandle.DangerousGetHandle(),
-                (security != null ? security.GetSecurityDescriptorSddlForm(AccessControlSections.All) : "none"), createdNew);
+                name, ev.SafeWaitHandle.DangerousGetHandle(), securityDesc, createdNew);
             return createdNew;
         }
 
+#if !NETSTANDARD
         private EventWaitHandleSecurity CreateSecurity(EventWaitHandleRights eventRights)
         {
             EventWaitHandleSecurity security = null;
@@ -658,6 +671,7 @@ namespace WinSCP
 
             return security;
         }
+#endif
 
         private EventWaitHandle CreateEvent(string name)
         {
@@ -828,8 +842,10 @@ namespace WinSCP
                 else
                 {
                     if (!TryFindExecutableInPath(GetAssemblyPath(), out executablePath) &&
+#if !NETSTANDARD
                         !TryFindExecutableInPath(GetInstallationPath(RegistryHive.CurrentUser, Registry.CurrentUser), out executablePath) &&
                         !TryFindExecutableInPath(GetInstallationPath(RegistryHive.LocalMachine, Registry.LocalMachine), out executablePath) &&
+#endif
                         !TryFindExecutableInPath(GetDefaultInstallationPath(), out executablePath))
                     {
                         throw _logger.WriteException(
@@ -858,6 +874,7 @@ namespace WinSCP
             return Path.Combine(programFiles, "WinSCP");
         }
 
+#if !NETSTANDARD
         private static string GetInstallationPath(RegistryHive hive, RegistryKey rootKey)
         {
             OperatingSystem OS = Environment.OSVersion;
@@ -893,6 +910,7 @@ namespace WinSCP
 
             return result;
         }
+#endif
 
         private bool TryFindExecutableInPath(string path, out string result)
         {

+ 3 - 1
dotnet/internal/GenericSecurity.cs

@@ -1,4 +1,5 @@
-using System;
+#if !NETSTANDARD
+using System;
 using System.Runtime.InteropServices;
 using System.Security.AccessControl;
 using System.Security.Principal;
@@ -69,3 +70,4 @@ namespace WinSCP
         #endregion
     }
 }
+#endif

+ 23 - 0
dotnet/internal/Logger.cs

@@ -51,6 +51,7 @@ namespace WinSCP
             return path;
         }
 
+#if !NETSTANDARD
         private void CreateCounters()
         {
             try
@@ -85,6 +86,7 @@ namespace WinSCP
             counter.NextValue();
             _performanceCounters.Add(counter);
         }
+#endif
 
         public void WriteLine(string line)
         {
@@ -157,19 +159,24 @@ namespace WinSCP
             {
                 if (Logging)
                 {
+#if !NETSTANDARD
                     WriteCounters();
+#endif
                     WriteProcesses();
                     _writter.Dispose();
                     _writter = null;
                 }
 
+#if !NETSTANDARD
                 foreach (PerformanceCounter counter in _performanceCounters)
                 {
                     counter.Dispose();
                 }
+#endif
             }
         }
 
+#if !NETSTANDARD
         public void WriteCounters()
         {
             if (Logging && (LogLevel >= 1))
@@ -191,6 +198,7 @@ namespace WinSCP
                 }
             }
         }
+#endif
 
         public void WriteProcesses()
         {
@@ -295,10 +303,12 @@ namespace WinSCP
                         _writter = File.CreateText(_logPath);
                         _writter.AutoFlush = true;
                         WriteEnvironmentInfo();
+#if !NETSTANDARD
                         if (_logLevel >= 1)
                         {
                             CreateCounters();
                         }
+#endif
                     }
                 }
             }
@@ -307,14 +317,25 @@ namespace WinSCP
         private void WriteEnvironmentInfo()
         {
             Assembly assembly = Assembly.GetExecutingAssembly();
+#if NETSTANDARD
+            WriteLine(".NET Standard build");
+#else
+            WriteLine(".NET Framework build");
+#endif
             WriteLine("Executing assembly: {0}", assembly);
             WriteLine("Executing assembly codebase: {0}", (assembly.CodeBase ?? "unknown"));
             WriteLine("Executing assembly location: {0}", (assembly.Location ?? "unknown"));
             Assembly entryAssembly = Assembly.GetEntryAssembly();
             WriteLine("Entry Assembly: {0}", (entryAssembly != null ? entryAssembly.ToString() : "unmanaged"));
             WriteLine("Operating system: {0}", Environment.OSVersion);
+#if NETSTANDARD
+            WriteLine("Operating system information: {0} {1} {2}", RuntimeInformation.OSDescription, RuntimeInformation.OSArchitecture, RuntimeInformation.ProcessArchitecture);
+#endif
             WriteLine("User: {0}@{1}@{2}; Interactive: {3}", Environment.UserName, Environment.UserDomainName, Environment.MachineName, Environment.UserInteractive);
             WriteLine("Runtime: {0}", Environment.Version);
+#if NETSTANDARD
+            WriteLine("Framework description: {0}", RuntimeInformation.FrameworkDescription);
+#endif
             WriteLine("Console encoding: Input: {0} ({1}); Output: {2} ({3})", Console.InputEncoding.EncodingName, Console.InputEncoding.CodePage, Console.OutputEncoding.EncodingName, Console.OutputEncoding.CodePage);
             WriteLine("Working directory: {0}", Environment.CurrentDirectory);
             string path = GetAssemblyFilePath();
@@ -342,7 +363,9 @@ namespace WinSCP
         private readonly Dictionary<int, int> _indents = new Dictionary<int, int>();
         private readonly object _logLock = new object();
         private readonly Lock _lock = new Lock();
+#if !NETSTANDARD
         private List<PerformanceCounter> _performanceCounters = new List<PerformanceCounter>();
+#endif
         private int _logLevel;
     }
 }