Я пытаюсь отправить сценарий PowerShell в виде строки через SSH в PowerShell 5.1. Его цель — изменить содержимое файла конфигурации на удаленном компьютере. Мне удалось исправить проблемы, связанные с экранированием последовательностей специальных символов, благодаря этому ответу в моем предыдущем вопросе. Однако я предполагал, что ssh $Username@$ComputerIP $stringScript
будет выполняться аналогично cmd.exe /c $stringScript
, но, судя по моему тестированию, это не так, они обрабатываются по-другому на уровне, который я не понимаю, поскольку код с ssh выдает ошибку.
Вот мой предполагаемый код:
$forceOnlyKeysSSH = '`nMatch all`n`tPasswordAuthentication no'
$rmtPSAuthOnlyKeys = "powershell Add-Content -Force -Verbose -Path c:\ProgramData\ssh\sshd_config " +
"-Value \`"$forceOnlyKeysSSH\`""
ssh -o ConnectTimeout=10 $Username@$ComputerIP $rmtPSAuthOnlyKeys
В идеале он должен изменить файл sshd_config
с помощью
Match all
PasswordAuthentication no
Но я получаю эту ошибку, которая, по моему мнению, заключается в разделении аргументов по пробелам.
Add-Content : A positional parameter cannot be found that accepts argument 'all
PasswordAuthentication'.
At line:1 char:1
+ Add-Content -Force -Verbose -Path c:\ProgramData\ssh\sshd_config -Val ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Add-Content], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.AddContentCommand
Я ошибочно избегаю строки? Почему эта ошибка не появляется при вызове cmd.exe /c $rmtPSAuthOnlyKeys
?
Если вы вызываете ssh.exe
из Windows PowerShell или PowerShell 7 v7.2.x или ниже, вам, к сожалению, необходимо компенсировать ошибку, которая влияет на аргументы со встроенными символами "
.
Так получилось, что ошибка устраняется при прямом вызове cmd.exe /c
из-за собственных особенностей синтаксического анализа cmd.exe
.
Однако это затрагивает все остальные внешние программы, включая ssh.exe
.
Таким образом, при конфигурации SSH-сервера по умолчанию в Windows, даже если команда, переданная клиенту ssh.exe
, в конечном итоге передается cmd /c
, ошибку необходимо устранить.
См. обходной путь ниже.
Эта ошибка исправлена в PowerShell (Core) 7 версии 7.3 и выше, поэтому при вызове оттуда не требуется никаких дополнительных усилий.
См. этот ответ для получения дополнительной информации.
Обходной путь:
В частности, в Windows PowerShell и PowerShell 7.2 необходимо вручную \
-экранировать "
символы. (двойные кавычки), встроенные в аргументы.
Вот решение для разных выпусков, которое применяет ручное экранирование только при необходимости:
$forceOnlyKeysSSH = '`nMatch all`n`tPasswordAuthentication no'
$rmtPSAuthOnlyKeys = "powershell -NoProfile Add-Content -Force -Verbose -Path c:\ProgramData\ssh\sshd_config " +
"-Value \`"$forceOnlyKeysSSH\`""
if ($PSVersionTable.PSVersion -lt '7.3') { # Workaround needed
# Manually add another round of \-escaping to the embedded, escaped \" chars.
$rmtPSAuthOnlyKeys = $rmtPSAuthOnlyKeys.Replace('\"', '\\\"')
}
ssh -o ConnectTimeout=10 $Username@$ComputerIP $rmtPSAuthOnlyKeys
Примечание:
Как написано, значение, хранящееся в $forceOnlyKeysSSH
, подлежит нормализации пробелов, так что, например,
foo bar
будет передано как foo bar
; то есть серии из двух или более пробелов сворачиваются в одно пространство каждый.
Если это вызывает беспокойство, потребуется дополнительная работа, как подробно описано в ответе на ваш предыдущий вопрос - обратите внимание, что необходимость в дополнительном руководстве \
-экранирование в равной степени применима к ситуации; чтобы изложить более громоздкое решение без нормализации пробелов:
$forceOnlyKeysSSH = '`nMatch all`n`tPasswordAuthentication no'
# Note the (escaped) "..." enclosure around the (implied) -Command argument
# and the (escaped) "^""..."^"" enclosure around $forceOnlyKeysSSH
$rmtPSAuthOnlyKeys = "powershell -NoProfile `"Add-Content -Force -Verbose -Path c:\ProgramData\ssh\sshd_config " +
"-Value `"^`"`"$forceOnlyKeysSSH`"^`"`"`""
if ($PSVersionTable.PSVersion -lt '7.3') { # Workaround needed
# Manually add another round of \-escaping to the embedded " chars.
$rmtPSAuthOnlyKeys = $rmtPSAuthOnlyKeys.Replace('"', '\"')
}
ssh -o ConnectTimeout=10 $Username@$ComputerIP $rmtPSAuthOnlyKeys
@JackOA, решение, позволяющее избежать нормализации пробелов, находится во втором фрагменте кода этого ответа внизу. Я удивлен, что это не работает, но я проверю (необходимо настроить SSH-сервер в Windows).
@JackOA, я настроил SSH-сервер в Windows, и оба фрагмента кода в этом ответе работают для меня так, как рекламируется при вызове из PowerShell. Вы уверены, что использовали код именно так, как показано?
Неважно, я неправильно понял, что это условие выполняется для версий новее 7.3. Это действительно успешно работает. Однако я не уверен, что полностью понимаю добавление ^. Чтобы прояснить ответ, поскольку экранирование довольно сложное, не могли бы вы описать, как будет выглядеть строка в $rmtPSAuthOnlyKeys
после каждого прохода анализа с помощью PowerShell и командной строки Windows?
Возьмите принятый ответ от @mklement0 как лучший подход.
Когда я попробовал свои собственные различные форматы экранирования, я обнаружил, что экранирование пробелов с помощью `
в $forceOnlyKeysSSH
также приводит к желаемому результату без каких-либо ошибок. Я не совсем уверен, как это работает и масштабируемо ли, но требует меньше изменений в исходном коде.
$forceOnlyKeysSSH = '`nMatch` all`n`tPasswordAuthentication` no'
Даже с дополнительным экранированием $forceOnlyKeysSSH и в начале команды предлагаемые изменения приводят к тому же сообщению об ошибке, как показано в вопросе
Add-Content : A positional parameter cannot be found that accepts argument...
. Кроме того, цель состоит в том, чтобы строка внутри $forceOnlyKeysSSH сохраняла свой формат, так как же можно избежать нормализации *пробелов? В остальном решение для кросс-редакций выглядит очень полезным.