网站搜索

将 OpenSSH 服务器部署到 Windows 10


PowerShell 7 提供了许多令人信服的理由来采用它。其中最好的一项是支持 SSH 作为 PowerShell 远程协议。不幸的是,这不是以 Windows 为中心的 IT 专业人员通常会想到的话题。我知道这对我来说绝对是正确的,也是我一直在努力解决的缺陷。从好的方面来说,Windows 10 附带了已安装且可以使用的 OpenSSH 客户端。但是 OpenSSH 服务器呢?在 Windows 平台上,如果要在 PowerShell 中使用 SSH 远程处理,则需要启用并配置此元素。当然,我想使用 PowerShell 远程完成此操作。首先,我想向您展示如何在 Windows 10 上设置 OpenSSH Server 组件。

手动设置

在 Windows 10 中安装 OpenSSH Server 组件的 PowerShell 命令非常简单。首先,我需要能力。我正在以交互方式运行这些命令。

Get-WindowsCapability -online -name openssh.server* | Add-WindowsCapability -online

我使用带有通配符的 Get-WindowsCapability,以防版本号在某个时刻发生变化。我可以看到这有效。

这将设置未运行并配置为手动启动的 sshd 服务。我想将服务配置为启动并自动启动。

Set-Service -Name sshd -StartupType Automatic
Start-Service -Name sshd

我了解到关于 ssh 的一件事,特别是如果您想将它与 PowerShell 远程处理一起使用,那就是验证 ssh 在 PowerShell 之外是否有效。

ssh localhost

系统可能会提示您接受密钥和密码。如果你的设置和我的一样,你最终会看到 CMD 提示符。但也许您在通过 ssh 连接时需要不同的 shell。请注意,我还没有谈论通过 ssh 进行 PowerShell 远程处理。您可以修改计算机上的注册表。

$regPath = "HKLM:\SOFTWARE\OpenSSH\"
$regProperty = "DefaultShell"
$regValue = (Get-Command powershell.exe).source
Set-ItemProperty -Path $regPath -Name $regProperty -Value $regValue

更改应该立即生效,并且不需要重新启动 sshd 服务。使用 ssh 连接到本地主机现在可以提供 Windows PowerShell 会话。组装一个简单的 PowerShell 脚本来交互运行这些命令并不需要太多时间。

远程安装

但我想远程执行此操作。我有一些 Windows 10 虚拟机,我想通过 ssh 连接到它们。我需要使用传统的 PowerShell 远程处理。是的,您可以使用 psexec.exe 等第三方工具或 Desired State Configuration 等工具,但我想坚持使用您可能已经拥有的工具和技能。

当我从事这项工作时,我有一个简单的 PowerShell 脚本,它运行我刚才向您展示的步骤。我认为我需要做的就是使用 Invoke-Command 运行脚本。没有。事实证明,Add-WindowsCapability 命令在远程会话中不起作用。这显然是按需安装功能的一个已知限制。但我不会让这阻止我。我最终的解决方法是创建一个临时计划任务,将 OpenSSH.Server 添加为系统。

$Time = New-ScheduledTaskTrigger -At 23:59 -Once
$User = New-ScheduledTaskPrincipal System
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument '-noprofile -command "& { Get-WindowsCapability -online -name openssh.server* | Add-WindowsCapability -online}"'
$tmpTask = Register-ScheduledTask -taskpath \ -TaskName "AddTask" -Trigger $Time -Principal $user -Action $Action
Start-ScheduledTask -TaskName AddTask

#give the task a chance to finish
Start-Sleep -Seconds 20

$tmpTask | Unregister-ScheduledTask -confirm:$false

触发时间无关紧要,因为我将手动启动任务,然后将其删除。只要我不在接近午夜的时间运行,就应该没问题。

我还希望我的脚本支持 -WhatIf 并提供详细输出,即使代码是远程运行的。这是我想出的脚本。

#requires -version 5.1

<#
This is used to setup SSHD on a Windows 10 Client through PowerShell remoting

Sample usage
Verbose and Whatif
invoke-command -FilePath .\Setup-SSHServer.ps1 -ComputerName win10 -cred $artd -ArgumentList @("Continue",$True)

Verbose
invoke-command -FilePath .\Setup-SSHServer.ps1 -ComputerName win10 -cred $artd -ArgumentList @("Continue")
#>

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

$VerbosePreference = $VerboseOption
$WhatIfPreference = $WhatIfOption

Write-Verbose "Starting SSHD setup process"
Try {
    Write-Verbose "Testing for sshd service"
    $svc = Get-Service -Name sshd -ErrorAction Stop
    if ($svc.status -eq 'Running') {
        Write-Verbose "sshd service appears to be installed and running"
    }
    else {
        Write-Warning "sshd service found but it isn't running"
    }
    #take no further action
    Return "OpenSSH Server installation appears to be complete."
}
Catch {
    Write-Verbose "sshd service not found. Continuing with installation"
}

Write-Verbose "Testing OpenSSH.Server Windows capability"
if (-Not $svc) {
    Try {
        $cap = Get-WindowsCapability -Name OpenSSH.Server* -Online -ErrorAction Stop
        $cap | Out-String | Write-Verbose
    }
    Catch {
        Throw $_
    }
}

if ($cap.State -eq 'NotPresent' -And $cap.name -match "OpenSSH") {
    #install capability
    Try {
        Write-Verbose "Adding $($cap.name)"
        if ($pscmdlet.ShouldProcess($cap.Name)) {
            #installing with a Scheduledtask as System to get around the limitation
            #with Add-WindowsCapability in a PowerShell remoting session
            $Time = New-ScheduledTaskTrigger -At 23:59 -Once
            $User = New-ScheduledTaskPrincipal System
            $action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument '-noprofile -command "& { Get-WindowsCapability -online -name openssh.server* | Add-WindowsCapability -online}"'
            $tmpTask = Register-ScheduledTask -taskpath \ -TaskName "AddTask" -Trigger $Time -Principal $user -Action $Action
            Start-ScheduledTask -TaskName AddTask

            #give the task a chance to finish
            Start-Sleep -Seconds 20

            $tmpTask | Unregister-ScheduledTask -confirm:$false
        } #Whatif
    }
    Catch {
        Throw $_
    }
}

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."
}

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

该代码与交互式运行的代码有点不同。当您运行 Invoke-Command 并指定文件时,PowerShell 会将文件的内容视为脚本块并在远程运行空间中运行它。这就是为什么这些参数不是典型的函数参数。我将 Verbose 和 WhatIf 首选项从原始命令传递到脚本。

Invoke-Command -FilePath .\Setup-SSHServer.ps1 -ComputerName win10 -cred $artd -ArgumentList @("Continue", $True)

这为我提供了来自远程 Windows 10 计算机的 Verbose 和 Whatif 输出。使用默认值运行脚本效果很好。如果需要,您可以在注册表中添加行来设置默认 shell。但最终我想通过 ssh 配置 PowerShell 远程处理,所以我不需要它们。

PowerShell 7 SSH 远程处理

最后一步是将远程处理配置为使用 ssh,这发生在远程计算机上。您需要编辑 $env:ProgramData\ssh 中的 sshd_config 文件。这是我的副本,已根据必要的 PowerShell 要求进行了编辑。

# This is the sshd server system-wide configuration file.  See
# sshd_config(5) for more information.

# The strategy used for options in the default sshd_config shipped with
# OpenSSH is to specify options with their default value where
# possible, but leave them commented.  Uncommented options override the
# default value.

#Port 22
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::

#HostKey __PROGRAMDATA__/ssh/ssh_host_rsa_key
#HostKey __PROGRAMDATA__/ssh/ssh_host_dsa_key
#HostKey __PROGRAMDATA__/ssh/ssh_host_ecdsa_key
#HostKey __PROGRAMDATA__/ssh/ssh_host_ed25519_key

# Ciphers and keying
#RekeyLimit default none

# Logging
#SyslogFacility AUTH
#LogLevel INFO

# Authentication:

#LoginGraceTime 2m
#PermitRootLogin prohibit-password
#StrictModes yes
#MaxAuthTries 6
#MaxSessions 10

#ENABLED
PubkeyAuthentication yes

# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
# but this is overridden so installations will only check .ssh/authorized_keys
AuthorizedKeysFile	.ssh/authorized_keys

#AuthorizedPrincipalsFile none

# For this to work you will also need host keys in %programData%/ssh/ssh_known_hosts
#HostbasedAuthentication no
# Change to yes if you don't trust ~/.ssh/known_hosts for
# HostbasedAuthentication
#IgnoreUserKnownHosts no
# Don't read the user's ~/.rhosts and ~/.shosts files
#IgnoreRhosts yes

# To disable tunneled clear text passwords, change to no here!
# ENABLED: Set this to YES for PowerShell remoting
PasswordAuthentication yes

#PermitEmptyPasswords no

#AllowAgentForwarding yes
#AllowTcpForwarding yes
#GatewayPorts no
#PermitTTY yes
#PrintMotd yes
#PrintLastLog yes
#TCPKeepAlive yes
#UseLogin no
#PermitUserEnvironment no
#ClientAliveInterval 0
#ClientAliveCountMax 3
#UseDNS no
#PidFile /var/run/sshd.pid
#MaxStartups 10:30:100
#PermitTunnel no
#ChrootDirectory none
#VersionAddendum none

# no default banner path
#Banner none

# override default of no subsystems
Subsystem	sftp	sftp-server.exe
#ADDED
Subsystem     powershell c:\progra~1\powershell\7\pwsh.exe -sshs -NoLogo

# Example of overriding settings on a per-user basis
#Match User anoncvs
#	AllowTcpForwarding no
#	PermitTTY no
#	ForceCommand cvs server

Match Group administrators
       AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys

您可能需要进行其他更改,具体取决于您的组织。假设已安装 OpenSSH.Server 和 PowerShell 7,我可以使用 PowerShell 将此文件复制到远程计算机。

Copy-Item -Path .\sshd_config_default -Destination $env:ProgramData\ssh\sshd_config -ToSession $sess

应重新启动 sshd 服务以使更改生效。

部署

最后一步是将所有这些整合到一个主控制脚本中。我决定添加代码以使用 PSReleaseTools 模块安装 PowerShell 7。

#requires -version 5.1

#Deployment control script to setup SSH server on a remote Windows 10 desktop
#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 10"
    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) {
        $prog.currentOperation = "Installing PowerShell 7"
        $prog.percentComplete = 25
        Write-Progress @prog
        #install PowerShell
        if ($pscmdlet.ShouldProcess($Computername.toUpper(),"Install PowerShell 7")) {

            Invoke-Command -ScriptBlock {
                [void](Install-PackageProvider -Name nuget -force -forcebootstrap)
                Install-Module PSReleaseTools -Force
                Install-PowerShell -EnableRemoting -EnableContextMenu -mode Quiet
            } -session $sess
        } #whatif
    }

    #setup SSH
    $prog.currentOperation = "Installing OpenSSH Server"
    $prog.percentComplete = 50
    Write-Progress @prog
    Invoke-Command -FilePath .\Setup-SSHServer.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

我可能应该在配置 sshd 服务时添加更好的错误处理,但这应该设置所有内容,以便我可以执行以下操作:

下一步

我使用 ssh 和 PowerShell 进行 Windows 之旅的下一部分是配置我的 Windows 服务器。完成后,我可以通过 ssh 使用 PowerShell 远程处理几乎所有内容。明天我会深入探讨这个问题。

相关文章