diff --git a/Fake.sln b/Fake.sln
index ee861128e4d..32830de8673 100644
--- a/Fake.sln
+++ b/Fake.sln
@@ -77,6 +77,8 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fake.DotNet.Xamarin", "src\
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fake.Net.Http", "src\app\Fake.Net.Http\Fake.Net.Http.fsproj", "{D24CEE35-B6C0-4C92-AE18-E80F90B69974}"
EndProject
+Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fake.Net.SSH", "src\app\Fake.Net.SSH\Fake.Net.SSH.fsproj", "{5B2A7546-A441-45C9-8176-2872E2A30477}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{CCAC5CAB-03C8-4C11-ADBE-A0D05F6A4F18}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fake.Core.UnitTests", "src\test\Fake.Core.UnitTests\Fake.Core.UnitTests.fsproj", "{31A5759B-B562-43C0-A845-14EFA4091543}"
@@ -633,6 +635,18 @@ Global
{D24CEE35-B6C0-4C92-AE18-E80F90B69974}.Release|x64.Build.0 = Release|Any CPU
{D24CEE35-B6C0-4C92-AE18-E80F90B69974}.Release|x86.ActiveCfg = Release|Any CPU
{D24CEE35-B6C0-4C92-AE18-E80F90B69974}.Release|x86.Build.0 = Release|Any CPU
+ {5B2A7546-A441-45C9-8176-2872E2A30477}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5B2A7546-A441-45C9-8176-2872E2A30477}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5B2A7546-A441-45C9-8176-2872E2A30477}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5B2A7546-A441-45C9-8176-2872E2A30477}.Debug|x64.Build.0 = Debug|Any CPU
+ {5B2A7546-A441-45C9-8176-2872E2A30477}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5B2A7546-A441-45C9-8176-2872E2A30477}.Debug|x86.Build.0 = Debug|Any CPU
+ {5B2A7546-A441-45C9-8176-2872E2A30477}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5B2A7546-A441-45C9-8176-2872E2A30477}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5B2A7546-A441-45C9-8176-2872E2A30477}.Release|x64.ActiveCfg = Release|Any CPU
+ {5B2A7546-A441-45C9-8176-2872E2A30477}.Release|x64.Build.0 = Release|Any CPU
+ {5B2A7546-A441-45C9-8176-2872E2A30477}.Release|x86.ActiveCfg = Release|Any CPU
+ {5B2A7546-A441-45C9-8176-2872E2A30477}.Release|x86.Build.0 = Release|Any CPU
{31A5759B-B562-43C0-A845-14EFA4091543}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{31A5759B-B562-43C0-A845-14EFA4091543}.Debug|Any CPU.Build.0 = Debug|Any CPU
{31A5759B-B562-43C0-A845-14EFA4091543}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -1261,6 +1275,7 @@ Global
{4BCE4F9C-8FC2-4207-81F1-20CB07D852DC} = {7BFFAE76-DEE9-417A-A79B-6A6644C4553A}
{13C1F95D-2FAD-4890-BF94-0AE7CF9AB2FC} = {7BFFAE76-DEE9-417A-A79B-6A6644C4553A}
{D24CEE35-B6C0-4C92-AE18-E80F90B69974} = {7BFFAE76-DEE9-417A-A79B-6A6644C4553A}
+ {5B2A7546-A441-45C9-8176-2872E2A30477} = {7BFFAE76-DEE9-417A-A79B-6A6644C4553A}
{31A5759B-B562-43C0-A845-14EFA4091543} = {CCAC5CAB-03C8-4C11-ADBE-A0D05F6A4F18}
{D8850C67-0542-427A-ABCB-92174EA42C95} = {7BFFAE76-DEE9-417A-A79B-6A6644C4553A}
{8D72BED1-BC02-4B23-A631-4849BD0FD3E1} = {7BFFAE76-DEE9-417A-A79B-6A6644C4553A}
diff --git a/build.fsx b/build.fsx
index 4ce23bc253e..e5fb51b2756 100644
--- a/build.fsx
+++ b/build.fsx
@@ -381,6 +381,7 @@ let dotnetAssemblyInfos =
"Fake.JavaScript.Yarn", "Running Yarn commands"
"Fake.JavaScript.TypeScript", "Running TypeScript compiler"
"Fake.Net.Http", "HTTP Client"
+ "Fake.Net.SSH", "SSH operations"
"Fake.netcore", "Command line tool"
"Fake.Runtime", "Core runtime features"
"Fake.Sql.DacPac", "Sql Server Data Tools DacPac operations (Obsolete: Use Fake.Sql.SqlPackage instead)"
diff --git a/help/templates/template.cshtml b/help/templates/template.cshtml
index f0d8457e773..a73977c65aa 100644
--- a/help/templates/template.cshtml
+++ b/help/templates/template.cshtml
@@ -201,6 +201,7 @@
Net
diff --git a/src/app/Fake.Net.SSH/AssemblyInfo.fs b/src/app/Fake.Net.SSH/AssemblyInfo.fs
new file mode 100644
index 00000000000..1b35e8b65aa
--- /dev/null
+++ b/src/app/Fake.Net.SSH/AssemblyInfo.fs
@@ -0,0 +1,19 @@
+// Auto-Generated by FAKE; do not edit
+namespace System
+open System.Reflection
+
+[
]
+[]
+[]
+[]
+[]
+[]
+do ()
+
+module internal AssemblyVersionInformation =
+ let [] AssemblyTitle = "FAKE - F# Make SSH operations"
+ let [] AssemblyProduct = "FAKE - F# Make"
+ let [] AssemblyVersion = "5.21.1"
+ let [] AssemblyInformationalVersion = "5.21.1"
+ let [] AssemblyFileVersion = "5.21.1"
+ let [] AssemblyMetadata_BuildDate = "2022-01-31"
diff --git a/src/app/Fake.Net.SSH/Fake.Net.SSH.fsproj b/src/app/Fake.Net.SSH/Fake.Net.SSH.fsproj
new file mode 100644
index 00000000000..2d7e47e2d1b
--- /dev/null
+++ b/src/app/Fake.Net.SSH/Fake.Net.SSH.fsproj
@@ -0,0 +1,23 @@
+
+
+ netstandard2.0;net472
+ Fake.Net.SSH
+ Library
+
+
+ $(DefineConstants)
+
+
+ $(DefineConstants);RELEASE
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/Fake.Net.SSH/SSH.fs b/src/app/Fake.Net.SSH/SSH.fs
new file mode 100644
index 00000000000..c71cf2dd64f
--- /dev/null
+++ b/src/app/Fake.Net.SSH/SSH.fs
@@ -0,0 +1,63 @@
+namespace Fake.Net
+
+open System
+open Fake.Core
+
+[]
+/// Contains a task which allows to perform SSH operations
+module SSH =
+
+ /// The SSH parameter type.
+ type SSHParams =
+ { /// Path of the scp.exe
+ ToolPath : string
+ /// Path of the private key file (optional)
+ PrivateKeyPath : string
+ /// remote User
+ RemoteUser : string
+ RemoteHost : string
+ RemotePort : string
+ }
+
+ /// The SSH default parameters
+ let SSHDefaults : SSHParams =
+ { ToolPath = if Environment.isMono then "ssh" else "ssh.exe"
+ RemoteUser = "fake"
+ RemoteHost = "localhost"
+ RemotePort = "22"
+ PrivateKeyPath = null
+ }
+
+ let private getTarget sshParams =
+ match sshParams.RemotePort with
+ | "22" -> $"%s{sshParams.RemoteUser}@%s{sshParams.RemoteHost}"
+ | _ -> $"%s{sshParams.RemoteUser}@%s{sshParams.RemoteHost}:%s{sshParams.RemotePort}"
+
+ let private getPrivateKey privateKeyPath =
+ if String.IsNullOrEmpty privateKeyPath then "" else $"-i \"%s{privateKeyPath}\""
+
+ let buildArguments sshParams command =
+ let target = sshParams |> getTarget
+ let privateKey = sshParams.PrivateKeyPath |> getPrivateKey
+ $"%s{privateKey} %s{target} %s{Args.toWindowsCommandLine [command]}" |> String.trim
+
+ /// Performs a command via SSH.
+ /// ## Parameters
+ ///
+ /// - `setParams` - Function used to manipulate the default SSHParams value.
+ /// - `command` - The target path. Can be something like user@host:directory/TargetFile or a local path.
+ ///
+ /// ## Sample
+ ///
+ /// SSH (fun p -> { p with ToolPath = "tools/ssh.exe" }) command
+ let SSH setParams command =
+ let (sshParams : SSHParams) = setParams SSHDefaults
+ let target = sshParams |> getTarget
+ let args = buildArguments sshParams command
+
+ Trace.tracefn $"%s{sshParams.ToolPath} %s{args}"
+
+ let result = CreateProcess.fromRawCommandLine sshParams.ToolPath args
+ |> CreateProcess.withTimeout(TimeSpan.MaxValue)
+ |> Proc.run
+ if result.ExitCode <> 0 then failwithf $"Error during SSH. Target: %s{target} Command: %s{command}"
diff --git a/src/app/Fake.Net.SSH/paket.references b/src/app/Fake.Net.SSH/paket.references
new file mode 100644
index 00000000000..edc34b53625
--- /dev/null
+++ b/src/app/Fake.Net.SSH/paket.references
@@ -0,0 +1,4 @@
+group netcore
+
+FSharp.Core
+NETStandard.Library
diff --git a/src/legacy/FakeLib/FakeLib.fsproj b/src/legacy/FakeLib/FakeLib.fsproj
index 7df7ce5abbb..744162c3deb 100644
--- a/src/legacy/FakeLib/FakeLib.fsproj
+++ b/src/legacy/FakeLib/FakeLib.fsproj
@@ -248,6 +248,9 @@
Fake.Net.Http/Http.fs
+
+ Fake.Net.SSH/SSH.fs
+
Fake.Core.Xml/Xml.fs
diff --git a/src/legacy/FakeLib/SSHHelper.fs b/src/legacy/FakeLib/SSHHelper.fs
index 46c43f0ba81..b30f4af9175 100644
--- a/src/legacy/FakeLib/SSHHelper.fs
+++ b/src/legacy/FakeLib/SSHHelper.fs
@@ -1,11 +1,11 @@
[]
-[]
+[]
/// Conatins a task which allows to perform SSH operations
module Fake.SSHHelper
/// The SSH parameter type.
[]
-[]
+[]
type SSHParams =
{ /// Path of the scp.exe
ToolPath : string
@@ -19,7 +19,7 @@ type SSHParams =
/// The SSH default parameters
-[]
+[]
let SSHDefaults : SSHParams =
{ ToolPath = if isMono then "ssh" else "ssh.exe"
RemoteUser = "fake"
@@ -37,7 +37,7 @@ let SSHDefaults : SSHParams =
/// ## Sample
///
/// SSH (fun p -> { p with ToolPath = "tools/ssh.exe" }) command
-[]
+[]
let SSH setParams command =
let (p : SSHParams) = setParams SSHDefaults
let target =
diff --git a/src/test/Fake.Core.UnitTests/Fake.Core.UnitTests.fsproj b/src/test/Fake.Core.UnitTests/Fake.Core.UnitTests.fsproj
index d917134135c..54f4bd598f9 100644
--- a/src/test/Fake.Core.UnitTests/Fake.Core.UnitTests.fsproj
+++ b/src/test/Fake.Core.UnitTests/Fake.Core.UnitTests.fsproj
@@ -15,6 +15,7 @@
+
@@ -76,6 +77,7 @@
+
diff --git a/src/test/Fake.Core.UnitTests/Fake.Net.SSH.fs b/src/test/Fake.Core.UnitTests/Fake.Net.SSH.fs
new file mode 100644
index 00000000000..95015d3db22
--- /dev/null
+++ b/src/test/Fake.Core.UnitTests/Fake.Net.SSH.fs
@@ -0,0 +1,44 @@
+module Fake.Net.SSHTests
+
+open Expecto
+open Fake.Net
+
+[]
+let tests =
+ testList "Fake.Net.SSH.Tests" [
+ testCase "Test all arguments are mapped correctly" <| fun _ ->
+ let args: SSH.SSHParams =
+ { ToolPath = "ssh"
+ RemoteUser = "fake-user"
+ RemoteHost = "localhost"
+ RemotePort = "22"
+ PrivateKeyPath = "private-key-path" }
+ let sshCommand = "pwd"
+ let cmd = SSH.buildArguments args sshCommand
+
+ Expect.equal cmd "-i \"private-key-path\" fake-user@localhost pwd" "expected proper arguments formatting"
+
+ testCase "Test ssh target is mapped correctly when a custom port is used" <| fun _ ->
+ let args: SSH.SSHParams =
+ { ToolPath = "ssh"
+ RemoteUser = "fake-user"
+ RemoteHost = "localhost"
+ RemotePort = "2222"
+ PrivateKeyPath = null }
+ let sshCommand = "pwd"
+ let cmd = SSH.buildArguments args sshCommand
+
+ Expect.equal cmd "fake-user@localhost:2222 pwd" "expected proper arguments formatting"
+
+ testCase "Test PrivateKeyPath is mapped correctly when it's empty" <| fun _ ->
+ let args: SSH.SSHParams =
+ { ToolPath = "ssh"
+ RemoteUser = "fake-user"
+ RemoteHost = "localhost"
+ RemotePort = "22"
+ PrivateKeyPath = null }
+ let sshCommand = "pwd"
+ let cmd = SSH.buildArguments args sshCommand
+
+ Expect.equal cmd "fake-user@localhost pwd" "expected proper arguments formatting"
+ ]