adds a ps command to vagrant that drops the user into a remote powershell shell
This commit is contained in:
parent
b420da0678
commit
c60a020096
|
@ -0,0 +1,116 @@
|
||||||
|
require "optparse"
|
||||||
|
|
||||||
|
require_relative "../../communicators/winrm/helper"
|
||||||
|
|
||||||
|
module VagrantPlugins
|
||||||
|
module CommandPS
|
||||||
|
class Command < Vagrant.plugin("2", :command)
|
||||||
|
def self.synopsis
|
||||||
|
"connects to machine via powershell remoting"
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute
|
||||||
|
options = {}
|
||||||
|
|
||||||
|
opts = OptionParser.new do |o|
|
||||||
|
o.banner = "Usage: vagrant ps [-- extra ps args]"
|
||||||
|
|
||||||
|
o.separator ""
|
||||||
|
o.separator "Options:"
|
||||||
|
o.separator ""
|
||||||
|
|
||||||
|
o.on("-c", "--command COMMAND", "Execute a powershell command directly") do |c|
|
||||||
|
options[:command] = c
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Parse out the extra args to send to the ps session, which
|
||||||
|
# is everything after the "--"
|
||||||
|
split_index = @argv.index("--")
|
||||||
|
if split_index
|
||||||
|
options[:extra_args] = @argv.drop(split_index + 1)
|
||||||
|
@argv = @argv.take(split_index)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Parse the options and return if we don't have any target.
|
||||||
|
argv = parse_options(opts)
|
||||||
|
return if !argv
|
||||||
|
|
||||||
|
# Check if the host even supports ps remoting
|
||||||
|
raise Errors::HostUnsupported if !@env.host.capability?(:ps_client)
|
||||||
|
|
||||||
|
# Execute ps session if we can
|
||||||
|
with_target_vms(argv, single_target: true) do |machine|
|
||||||
|
if !machine.communicate.ready?
|
||||||
|
raise Vagrant::Errors::VMNotCreatedError
|
||||||
|
end
|
||||||
|
|
||||||
|
if machine.config.vm.communicator != :winrm #|| !machine.provider.capability?(:winrm_info)
|
||||||
|
raise VagrantPlugins::CommunicatorWinRM::Errors::WinRMNotReady
|
||||||
|
end
|
||||||
|
|
||||||
|
if !options[:command].nil?
|
||||||
|
out_code = machine.communicate.execute options[:command]
|
||||||
|
if out_code == 0
|
||||||
|
machine.ui.detail("Command: #{options[:command]} executed succesfully with output code #{out_code}.")
|
||||||
|
end
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
ps_info = VagrantPlugins::CommunicatorWinRM::Helper.winrm_info(machine)
|
||||||
|
ps_info[:username] = machine.config.winrm.username
|
||||||
|
ps_info[:password] = machine.config.winrm.password
|
||||||
|
# Extra arguments if we have any
|
||||||
|
ps_info[:extra_args] = options[:extra_args]
|
||||||
|
|
||||||
|
result = ready_ps_remoting_for machine, ps_info
|
||||||
|
|
||||||
|
machine.ui.detail(
|
||||||
|
"Creating powershell session to #{ps_info[:host]}:#{ps_info[:port]}")
|
||||||
|
machine.ui.detail("Username: #{ps_info[:username]}")
|
||||||
|
|
||||||
|
begin
|
||||||
|
@env.host.capability(:ps_client, ps_info)
|
||||||
|
ensure
|
||||||
|
if !result["PreviousTrustedHosts"].nil?
|
||||||
|
reset_ps_remoting_for machine, ps_info
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def ready_ps_remoting_for(machine, ps_info)
|
||||||
|
machine.ui.output(I18n.t("vagrant_ps.detecting"))
|
||||||
|
script_path = File.expand_path("../scripts/enable_psremoting.ps1", __FILE__)
|
||||||
|
args = []
|
||||||
|
args << "-hostname" << ps_info[:host]
|
||||||
|
args << "-port" << ps_info[:port].to_s
|
||||||
|
args << "-username" << ps_info[:username]
|
||||||
|
args << "-password" << ps_info[:password]
|
||||||
|
result = Vagrant::Util::PowerShell.execute(script_path, *args)
|
||||||
|
if result.exit_code != 0
|
||||||
|
raise Errors::PowershellError,
|
||||||
|
script: script_path,
|
||||||
|
stderr: result.stderr
|
||||||
|
end
|
||||||
|
|
||||||
|
result_output = JSON.parse(result.stdout)
|
||||||
|
raise Errors::PSRemotingUndetected if !result_output["Success"]
|
||||||
|
result_output
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset_ps_remoting_for(machine, ps_info)
|
||||||
|
machine.ui.output(I18n.t("vagrant_ps.reseting"))
|
||||||
|
script_path = File.expand_path("../scripts/reset_trustedhosts.ps1", __FILE__)
|
||||||
|
args = []
|
||||||
|
args << "-hostname" << ps_info[:host]
|
||||||
|
result = Vagrant::Util::PowerShell.execute(script_path, *args)
|
||||||
|
if result.exit_code != 0
|
||||||
|
raise Errors::PowershellError,
|
||||||
|
script: script_path,
|
||||||
|
stderr: result.stderr
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,18 @@
|
||||||
|
module VagrantPlugins
|
||||||
|
module CommandPS
|
||||||
|
module Errors
|
||||||
|
# A convenient superclass for all our errors.
|
||||||
|
class PSError < Vagrant::Errors::VagrantError
|
||||||
|
error_namespace("vagrant_ps.errors")
|
||||||
|
end
|
||||||
|
|
||||||
|
class HostUnsupported < PSError
|
||||||
|
error_key(:host_unsupported)
|
||||||
|
end
|
||||||
|
|
||||||
|
class PSRemotingUndetected < PSError
|
||||||
|
error_key(:ps_remoting_undetected)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,31 @@
|
||||||
|
require "vagrant"
|
||||||
|
|
||||||
|
module VagrantPlugins
|
||||||
|
module CommandPS
|
||||||
|
autoload :Errors, File.expand_path("../errors", __FILE__)
|
||||||
|
|
||||||
|
class Plugin < Vagrant.plugin("2")
|
||||||
|
name "ps command"
|
||||||
|
description <<-DESC
|
||||||
|
The ps command opens a remote powershell session to the
|
||||||
|
machine if it supports powershell remoting.
|
||||||
|
DESC
|
||||||
|
|
||||||
|
command("ps") do
|
||||||
|
require File.expand_path("../command", __FILE__)
|
||||||
|
init!
|
||||||
|
Command
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def self.init!
|
||||||
|
return if defined?(@_init)
|
||||||
|
I18n.load_path << File.expand_path(
|
||||||
|
"templates/locales/command_ps.yml", Vagrant.source_root)
|
||||||
|
I18n.reload!
|
||||||
|
@_init = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,60 @@
|
||||||
|
Param(
|
||||||
|
[string]$hostname,
|
||||||
|
[string]$port,
|
||||||
|
[string]$username,
|
||||||
|
[string]$password
|
||||||
|
)
|
||||||
|
# If we are in this script, we know basic winrm is working
|
||||||
|
# If the user is not using a domain acount and chances are
|
||||||
|
# they are not, PS Remoting will not work if the guest is not
|
||||||
|
# listed in the trusted hosts.
|
||||||
|
|
||||||
|
$encrypted_password = ConvertTo-SecureString $password -asplaintext -force
|
||||||
|
$creds = New-Object System.Management.Automation.PSCredential (
|
||||||
|
"$hostname\\$username", $encrypted_password)
|
||||||
|
|
||||||
|
$result = @{
|
||||||
|
Success = $false
|
||||||
|
PreviousTrustedHosts = $null
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
invoke-command -computername $hostname `
|
||||||
|
-Credential $creds `
|
||||||
|
-Port $port `
|
||||||
|
-ScriptBlock {} `
|
||||||
|
-ErrorAction Stop
|
||||||
|
$result.Success = $true
|
||||||
|
} catch{}
|
||||||
|
|
||||||
|
if(!$result.Success) {
|
||||||
|
$newHosts = @()
|
||||||
|
$result.PreviousTrustedHosts=(
|
||||||
|
Get-Item "wsman:\localhost\client\trustedhosts").Value
|
||||||
|
$hostArray=$result.PreviousTrustedHosts.Split(",").Trim()
|
||||||
|
if($hostArray -contains "*") {
|
||||||
|
$result.PreviousTrustedHosts = $null
|
||||||
|
}
|
||||||
|
elseif(!($hostArray -contains $hostname)) {
|
||||||
|
$strNewHosts = $hostname
|
||||||
|
if($result.PreviousTrustedHosts.Length -gt 0){
|
||||||
|
$strNewHosts = $result.PreviousTrustedHosts + "," + $strNewHosts
|
||||||
|
}
|
||||||
|
Set-Item -Path "wsman:\localhost\client\trustedhosts" `
|
||||||
|
-Value $strNewHosts -Force
|
||||||
|
|
||||||
|
try {
|
||||||
|
invoke-command -computername $hostname `
|
||||||
|
-Credential $creds `
|
||||||
|
-Port $port `
|
||||||
|
-ScriptBlock {} `
|
||||||
|
-ErrorAction Stop
|
||||||
|
$result.Success = $true
|
||||||
|
} catch{
|
||||||
|
Set-Item -Path "wsman:\localhost\client\trustedhosts" `
|
||||||
|
-Value $result.PreviousTrustedHosts -Force
|
||||||
|
$result.PreviousTrustedHosts = $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output $(ConvertTo-Json $result)
|
|
@ -0,0 +1,12 @@
|
||||||
|
Param(
|
||||||
|
[string]$hostname
|
||||||
|
)
|
||||||
|
|
||||||
|
$trustedHosts = (
|
||||||
|
Get-Item "wsman:\localhost\client\trustedhosts").Value.Replace(
|
||||||
|
$hostname, '')
|
||||||
|
$trustedHosts = $trustedHosts.Replace(",,","")
|
||||||
|
if($trustedHosts.EndsWith(",")){
|
||||||
|
$trustedHosts = $trustedHosts.Substring(0,$trustedHosts.length-1)
|
||||||
|
}
|
||||||
|
Set-Item "wsman:\localhost\client\trustedhosts" -Value $trustedHosts -Force
|
|
@ -0,0 +1,50 @@
|
||||||
|
require "pathname"
|
||||||
|
require "tmpdir"
|
||||||
|
|
||||||
|
require "vagrant/util/subprocess"
|
||||||
|
|
||||||
|
module VagrantPlugins
|
||||||
|
module HostWindows
|
||||||
|
module Cap
|
||||||
|
class PS
|
||||||
|
def self.ps_client(env, ps_info)
|
||||||
|
logger = Log4r::Logger.new("vagrant::hosts::windows")
|
||||||
|
|
||||||
|
command = <<-EOS
|
||||||
|
$plain_password = "#{ps_info[:password]}"
|
||||||
|
$username = "#{ps_info[:username]}"
|
||||||
|
$port = "#{ps_info[:port]}"
|
||||||
|
$hostname = "#{ps_info[:host]}"
|
||||||
|
$password = ConvertTo-SecureString $plain_password -asplaintext -force
|
||||||
|
$creds = New-Object System.Management.Automation.PSCredential ("$hostname\\$username", $password)
|
||||||
|
function prompt { kill $PID }
|
||||||
|
Enter-PSSession -ComputerName $hostname -Credential $creds -Port $port
|
||||||
|
EOS
|
||||||
|
|
||||||
|
logger.debug("Starting remote powershell with command:\n#{command}")
|
||||||
|
command = command.chars.to_a.join("\x00").chomp
|
||||||
|
command << "\x00" unless command[-1].eql? "\x00"
|
||||||
|
if(defined?(command.encode))
|
||||||
|
command = command.encode('ASCII-8BIT')
|
||||||
|
command = Base64.strict_encode64(command)
|
||||||
|
else
|
||||||
|
command = Base64.encode64(command).chomp
|
||||||
|
end
|
||||||
|
|
||||||
|
args = ["-NoProfile"]
|
||||||
|
args << "-ExecutionPolicy"
|
||||||
|
args << "Bypass"
|
||||||
|
args << "-NoExit"
|
||||||
|
args << "-EncodedCommand"
|
||||||
|
args << command
|
||||||
|
if ps_info[:extra_args]
|
||||||
|
args << ps_info[:extra_args]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Launch it
|
||||||
|
Vagrant::Util::Subprocess.execute("powershell", *args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -20,6 +20,11 @@ module VagrantPlugins
|
||||||
require_relative "cap/rdp"
|
require_relative "cap/rdp"
|
||||||
Cap::RDP
|
Cap::RDP
|
||||||
end
|
end
|
||||||
|
|
||||||
|
host_capability("windows", "ps_client") do
|
||||||
|
require_relative "cap/ps"
|
||||||
|
Cap::PS
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
en:
|
||||||
|
vagrant_ps:
|
||||||
|
detecting: |-
|
||||||
|
Detecting if a remote powershell connection can be made with the guest...
|
||||||
|
reseting: |-
|
||||||
|
Reseting WinRM TrustedHosts to their original value.
|
||||||
|
|
||||||
|
errors:
|
||||||
|
host_unsupported: |-
|
||||||
|
Your host does not support powershell. A remote powershell connection
|
||||||
|
can only be made from a windows host.
|
||||||
|
|
||||||
|
ps_remoting_undetected: |-
|
||||||
|
Unable to establish a remote powershell connection with the guest.
|
||||||
|
Check if the firewall rules on the guest allow connections to the
|
||||||
|
windows remote management service.
|
Loading…
Reference in New Issue