Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Deploy OpenSSH to Windows Server

Posted on December 17, 2020December 17, 2020

Yesterday I shared some PowerShell ideas for remotely setting up the server component of ssh on a remote Windows 10 system. This would allow you to establish an ssh session to the desktop or even use PowerShell remoting over ssh. Next, we need to look at doing the same thing for Windows Servers.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!

Windows Server 2019

If you are using Windows Server 2019, you can use the same code I used with Windows 10, at least based on my experiences. Windows Server 2019 includes the OpenSSH.Server capability that you can install with Add-WindowsCapability. I was able to use the same deployment script I used with Windows 10 to install PowerShell 7 and sshd. The only change I made was to the Write-Progress activity.

$prog = @{
    Activity = "Deploy SSH Server to Windows 10/Windows Server 2019"
    Status = $Computername.toUpper()
    CurrentOperation = ""
    PercentComplete = 0
}

Otherwise, everything ran the same and in minutes PowerShell 7 and ssh remoting were ready to go.

Windows Server 2019 PowerShell remoting over ssh

Windows Server 2016

Installing for Windows Server 2016 is a bit different. According to the Microsoft documentation that I could find, you need to install the latest bits from GitHub. You can do that with Invoke-Webrequest. Although before you do anything, you will most likely need to configure network security.

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

If you don't do this on Windows Server 2016, you'll get errors trying to connect to GitHub and the PowerShell Gallery. Once the zip file is downloaded, you can extract it and then run the setup script. Here's the server-focused PowerShell script.

#requires -version 5.1

<#
    setup SSHD on Windows Server 2016
    https://github.com/PowerShell/Win32-OpenSSH/wiki/Install-Win32-OpenSSH

    This script is designed to be run remotely using Invoke-Command

    Sample usage:
    Verbose and Whatif
    invoke-command -FilePath .\Setup-SSHServer2.ps1 -ComputerName srv1 -cred $admin -ArgumentList @("Continue",$True)

    Verbose
    invoke-command -FilePath .\Setup-SSHServer2.ps1 -ComputerName srv1 -cred $admin -ArgumentList @("Continue")
#>

[cmdletbinding(SupportsShouldProcess)]
Param([string]$VerboseOption = "SilentlyContinue", [bool]$WhatIfOption = $False)

$VerbosePreference = $VerboseOption
$WhatIfPreference = $WhatIfOption

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$url = 'https://github.com/PowerShell/Win32-OpenSSH/releases/latest/'
$request = [System.Net.WebRequest]::Create($url)
$request.AllowAutoRedirect = $false
$response = $request.GetResponse()
$dl = $([String]$response.GetResponseHeader("Location")).Replace('tag', 'download') + '/OpenSSH-Win64.zip'

$zip = "C:\OpenSSH64.zip"
Write-Verbose "download SSH from $dl to $zip"
if ($pscmdlet.ShouldProcess($dl,"Downloading OpenSSH-Win64 Package")) {
    Try {
        Invoke-WebRequest -Uri $dl -OutFile $zip -DisableKeepAlive -ErrorAction Stop
    }
    Catch {
        Throw $_
    }
    if (Test-Path $zip) {
        Write-Verbose "Extract zip file"
        Expand-Archive $zip -DestinationPath "c:\Program Files" -force
    }
    else {
        Return "zip file failed to download"
    }
}

Write-Verbose "Install SSH"
if ($pscmdlet.ShouldProcess("install-sshd.ps1", "Execute")) {
    Push-Location
    Set-Location -Path 'C:\Program Files\OpenSSH-Win64\'
    . .\install-sshd
    Pop-Location
}

Write-Verbose "Set sshd to auto start"
Try {
    if ($pscmdlet.ShouldProcess("sshd", "Configure service")) {
        Set-Service -Name sshd -StartupType Automatic -ErrorAction stop
        Write-Verbose "Start the sshd service"
        Start-Service -Name sshd -ErrorAction Stop
        Get-Service sshd | Select-Object -Property * | Out-String | Write-Verbose
    }
}
Catch {
    Write-Warning "There was a problem configuring the sshd service."
}

Write-Verbose "Add the firewall rule"
if ($pscmdlet.ShouldProcess("sshd", "Add firewall rule")) {
    Try {
        $rule = New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 -ErrorAction Stop
        Write-Verbose ($rule | Out-String)
    }
    Catch {
        Throw $_
    }
}

if (-Not $WhatIfOption) {
    #only display the summary if not using -WhatIf
    $msg = @"

SSH setup complete. Edit $ENV:ProgramData\ssh\sshd_config for additional configurations options
or to enable remoting under PowerShell 7.

You will need to restart the sshd service for changes to take effect.

"@
    Write-Host $msg -ForegroundColor green
}

Write-Verbose "Ending SSHD setup process."

As with the Windows 10 version of the script, this is designed to be run remotely using Invoke-Command. And of course, I have a control script.

#requires -version 5.1

#Deployment control script to setup SSH server on a remote Windows Server 2016
#It is assumed PowerShell 7 is, or will be, installed.

[cmdletbinding(SupportsShouldProcess)]
Param(
    [Parameter(Position = 0, Mandatory)]
    [string]$Computername,
    [pscredential]$Credential,
    [switch]$InstallPowerShell
)

#remove parameters from PSBoundparameter that don't apply to New-PSSession
if ($PSBoundParameters.ContainsKey("InstallPowerShell")) {
    [void]($PSBoundParameters.remove("InstallPowerShell"))
}

if ($PSBoundParameters.ContainsKey("WhatIf")) {
    [void]($PSBoundParameters.remove("WhatIf"))
}

#parameters for Write-Progress
$prog = @{
    Activity         = "Deploy SSH Server to Windows Server 2016"
    Status           = $Computername.toUpper()
    CurrentOperation = ""
    PercentComplete  = 0
}
#create the PSSession
Try {
    Write-Verbose "Creating a PSSession to $Computername"
    $prog.CurrentOperation = "Creating a temporary PSSession"
    $prog.PercentComplete = 10
    Write-Progress @prog
    $sess = New-PSSession @PSBoundParameters -ErrorAction Stop
}
Catch {
    Throw $_
}

if ($sess) {
    if ($InstallPowerShell) {
        Write-Verbose "Installing PowerShell 7"
        $prog.currentOperation = "Installing PowerShell 7"
        $prog.percentComplete = 25
        Write-Progress @prog
        #install PowerShell
        if ($pscmdlet.ShouldProcess($Computername.toUpper(), "Install PowerShell 7")) {
            Invoke-Command -ScriptBlock {
                #need to set security protocol to Tls12 for Windows Server 2016 otherwise the installs will fail
                Write-Verbose "Setting network security to Tls12"
                [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
                Write-Verbose "Using Security Protocol $([Net.ServicePointManager]::SecurityProtocol)"
                [void](Install-PackageProvider -Name nuget -force -forcebootstrap -minimumversion '2.8.5.201')
                Install-Module PSReleaseTools -Force -Scope AllUsers -Repository PSGallery
                Install-PowerShell -EnableRemoting -EnableContextMenu -Mode Quiet
            } -Session $sess

            #Installing PowerShell may break the session so rebuild it if necessary
            if ($sess.state -eq 'Broken') {
                Write-Verbose "Rebuilding the temporary PSSession"
                $sess = New-PSSession @PSBoundParameters -ErrorAction Stop
            }
        } #whatif
    } #if install PowerShell 7

    #setup SSH
    $prog.currentOperation = "Installing OpenSSH Server"
    $prog.percentComplete = 50
    Write-Progress @prog
    Invoke-Command -FilePath .\Setup-SSHServer2.ps1 -Session $sess -ArgumentList @($VerbosePreference, $WhatIfPreference)

    #copy the sshd_config file. This assumes you've installed PowerShell 7 on the remote computer
    Write-Verbose "Copying sshd_config to target"
    $prog.currentOperation = "Copying default sshd_config to target"
    $prog.percentcomplete = 60
    Write-Progress @prog
    Copy-Item -Path .\sshd_config_default -Destination $env:ProgramData\ssh\sshd_config -ToSession $sess

    #restart the service
    Write-Verbose "Restarting the sshd service on the target"
    $prog.currentOperation = "Restarting the ssh service target"
    $prog.percentComplete = 75
    Write-Progress @prog
    if ($pscmdlet.ShouldProcess("sshd", "Restart service")) {
        Invoke-Command { Restart-Service -Name sshd } -Session $sess
    }

    Write-Verbose "Removing the temporary PSSession"
    $prog.currentOperation = "Removing temporary PSSession"
    $prog.percentComplete = 90
    Write-Progress @prog
    $sess | Remove-PSSession

    Write-Progress @prog -Completed
    $msg = @"

SSH Server deployment complete for $($Computername.toUpper()).

If PowerShell 7 is installed remotely, you should be able to test
with an expression like:

Enter-PSSession -hostname $Computername -username <user> -sshtransport

"@

    Write-Host $msg -ForegroundColor yellow
} #if $sess
else {
    #this should never get called
    Write-Warning "Failed to create a temporary session to $Computername."
}

The script supports -Verbose and -WhatIf.

The script will copy the sshd_config_default file which you can grab from the previous post. Once in place, remoting with ssh in PowerShell 7 is a breeze.

Next Steps

You should test all of this code in a non-production environment and adjust as necessary. Anytime you play with remoting, you run the risk of leaving yourself vulnerable. Part of this process should include learning more about ssh. All I've done is get it installed and able to work with PowerShell. There is much more to ssh

Comments and questions welcome.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d