winrm 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/COPYING.txt +674 -0
- data/README +53 -0
- data/Rakefile +69 -0
- data/VERSION +1 -0
- data/lib/extensions/string.rb +37 -0
- data/lib/soap/exceptions/exceptions.rb +28 -0
- data/lib/soap/soap_provider.rb +36 -0
- data/lib/soap/winrm_service.rb +345 -0
- data/lib/winrm.rb +89 -0
- data/preamble +19 -0
- data/test/spec/powershell_tests.spec +24 -0
- data/test/spec/spec.opts +5 -0
- data/test/spec/test.ps1 +14 -0
- data/test/spec/wql_tests.spec +23 -0
- metadata +147 -0
data/README
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--------------------------------------------------------------------------
|
2
|
+
Windows Remote Management (WinRM) for Ruby
|
3
|
+
--------------------------------------------------------------------------
|
4
|
+
This is a SOAP library that uses the functionality in Windows Remote
|
5
|
+
Management(WinRM) to call native object in Windows. This includes, but is
|
6
|
+
not limitted to, running batch scripts, powershell scripts and fetching WMI
|
7
|
+
variables. For more information on WinRM, please visit Microsoft's WinRM
|
8
|
+
site: http://msdn.microsoft.com/en-us/library/aa384426(v=VS.85).aspx
|
9
|
+
|
10
|
+
|
11
|
+
My Info:
|
12
|
+
BLOG: http://distributed-frostbite.blogspot.com/
|
13
|
+
Add me in LinkedIn: http://www.linkedin.com/in/danwanek
|
14
|
+
|
15
|
+
--------------------------------------------------------------------------
|
16
|
+
--------------------------------------------------------------------------
|
17
|
+
TO USE:
|
18
|
+
require 'winrm'
|
19
|
+
# See REQUIRED GEMS below
|
20
|
+
|
21
|
+
gem install -r winrm
|
22
|
+
|
23
|
+
REQUIRED GEMS: (These should automatically install with the winrm gem)
|
24
|
+
# Handsoap
|
25
|
+
gem install -r handsoap
|
26
|
+
# Nokogiri XML Parser
|
27
|
+
gem install -r nokogiri
|
28
|
+
# HttpClient
|
29
|
+
gem install -r httpclient
|
30
|
+
# NTLM Library
|
31
|
+
gem install -r rubyntlm
|
32
|
+
#UUID Library
|
33
|
+
gem install -r uuid
|
34
|
+
|
35
|
+
|
36
|
+
EXAMPLE:
|
37
|
+
require 'winrm'
|
38
|
+
WinRM::WinRM.endpoint = 'http://mywinrmhost:5985/wsman'
|
39
|
+
WinRM::WinRM.set_auth('user','password')
|
40
|
+
winrm = WinRM::WinRM.instance
|
41
|
+
output = winrm.winrm.run_powershell_script 'script.ps1'
|
42
|
+
puts output[:stdout]
|
43
|
+
|
44
|
+
--------------------------------------------------------------------------
|
45
|
+
DISCLAIMER: If you see something that could be done better or would like
|
46
|
+
to help out in the development of this code please feel free to clone the
|
47
|
+
'git' repository and send me patches:
|
48
|
+
git clone git://github.com/zenchild/WinRM.git
|
49
|
+
or add an issue on GitHub:
|
50
|
+
http://github.com/zenchild/WinRM/issues
|
51
|
+
|
52
|
+
Cheers!
|
53
|
+
--------------------------------------------------------------------------
|
data/Rakefile
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/clean'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
CLEAN.include("pkg")
|
7
|
+
CLEAN.include("doc")
|
8
|
+
|
9
|
+
GEMSPEC = Gem::Specification.new do |gem|
|
10
|
+
gem.name = "winrm"
|
11
|
+
gem.version = File.open('VERSION').readline.chomp
|
12
|
+
gem.date = Date.today.to_s
|
13
|
+
gem.platform = Gem::Platform::RUBY
|
14
|
+
gem.rubyforge_project = nil
|
15
|
+
|
16
|
+
gem.author = "Dan Wanek"
|
17
|
+
gem.email = "dan.wanek@gmail.com"
|
18
|
+
gem.homepage = "http://github.com/zenchild/WinRM"
|
19
|
+
|
20
|
+
gem.summary = 'Ruby library for Windows Remote Management'
|
21
|
+
gem.description = <<-EOF
|
22
|
+
Ruby library for Windows Remote Management
|
23
|
+
EOF
|
24
|
+
|
25
|
+
gem.files = `git ls-files`.split(/\n/)
|
26
|
+
gem.require_path = "lib"
|
27
|
+
gem.rdoc_options = %w(-x test/ -x examples/)
|
28
|
+
gem.extra_rdoc_files = %w(README COPYING.txt)
|
29
|
+
|
30
|
+
gem.required_ruby_version = '>= 1.8.7'
|
31
|
+
gem.add_runtime_dependency 'handsoap'
|
32
|
+
gem.add_runtime_dependency 'nokogiri'
|
33
|
+
gem.add_runtime_dependency 'httpclient'
|
34
|
+
gem.add_runtime_dependency 'rubyntlm'
|
35
|
+
gem.add_runtime_dependency 'uuid'
|
36
|
+
end
|
37
|
+
|
38
|
+
Rake::GemPackageTask.new(GEMSPEC) do |pkg|
|
39
|
+
pkg.need_tar = true
|
40
|
+
end
|
41
|
+
|
42
|
+
task :default => [:buildgem]
|
43
|
+
|
44
|
+
desc "Build the gem without a version change"
|
45
|
+
task :buildgem => [:clean, :repackage]
|
46
|
+
|
47
|
+
desc "Build the gem, but increment the version first"
|
48
|
+
task :newrelease => [:versionup, :clean, :repackage]
|
49
|
+
|
50
|
+
|
51
|
+
desc "Increment the version by 1 minor release"
|
52
|
+
task :versionup do
|
53
|
+
ver = up_min_version
|
54
|
+
puts "New version: #{ver}"
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def up_min_version
|
59
|
+
f = File.open('VERSION', 'r+')
|
60
|
+
ver = f.readline.chomp
|
61
|
+
v_arr = ver.split(/\./).map do |v|
|
62
|
+
v.to_i
|
63
|
+
end
|
64
|
+
v_arr[2] += 1
|
65
|
+
ver = v_arr.join('.')
|
66
|
+
f.rewind
|
67
|
+
f.write(ver)
|
68
|
+
ver
|
69
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,37 @@
|
|
1
|
+
#############################################################################
|
2
|
+
# Copyright © 2010 Dan Wanek <dan.wanek@gmail.com>
|
3
|
+
#
|
4
|
+
#
|
5
|
+
# This file is part of WinRM.
|
6
|
+
#
|
7
|
+
# WinRM is free software: you can redistribute it and/or
|
8
|
+
# modify it under the terms of the GNU General Public License as published
|
9
|
+
# by the Free Software Foundation, either version 3 of the License, or (at
|
10
|
+
# your option) any later version.
|
11
|
+
#
|
12
|
+
# WinRM is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
15
|
+
# Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU General Public License along
|
18
|
+
# with WinRM. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
#############################################################################
|
20
|
+
|
21
|
+
# Custom extensions to the class String
|
22
|
+
class String
|
23
|
+
# Change CamelCased strings to ruby_cased strings
|
24
|
+
# It uses the lookahead assertion ?= In this case it basically says match
|
25
|
+
# anything followed by a capital letter, but not the capital letter itself.
|
26
|
+
# @see http://www.pcre.org/pcre.txt The PCRE guide for more details
|
27
|
+
def ruby_case
|
28
|
+
self.split(/(?=[A-Z])/).join('_').downcase
|
29
|
+
end
|
30
|
+
|
31
|
+
# Change a ruby_cased string to CamelCased
|
32
|
+
def camel_case
|
33
|
+
self.split(/_/).map { |i|
|
34
|
+
i.sub(/^./) { |s| s.upcase }
|
35
|
+
}.join
|
36
|
+
end
|
37
|
+
end # String
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#############################################################################
|
2
|
+
# Copyright © 2010 Dan Wanek <dan.wanek@gmail.com>
|
3
|
+
#
|
4
|
+
#
|
5
|
+
# This file is part of WinRM.
|
6
|
+
#
|
7
|
+
# WinRM is free software: you can redistribute it and/or
|
8
|
+
# modify it under the terms of the GNU General Public License as published
|
9
|
+
# by the Free Software Foundation, either version 3 of the License, or (at
|
10
|
+
# your option) any later version.
|
11
|
+
#
|
12
|
+
# WinRM is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
15
|
+
# Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU General Public License along
|
18
|
+
# with WinRM. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
#############################################################################
|
20
|
+
|
21
|
+
module WinRM
|
22
|
+
module SOAP
|
23
|
+
# Generic WinRM SOAP Error
|
24
|
+
class WinRMWebServiceError < StandardError
|
25
|
+
end
|
26
|
+
end # SOAP
|
27
|
+
end # WinRM
|
28
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#############################################################################
|
2
|
+
# Copyright © 2010 Dan Wanek <dan.wanek@gmail.com>
|
3
|
+
#
|
4
|
+
#
|
5
|
+
# This file is part of WinRM.
|
6
|
+
#
|
7
|
+
# WinRM is free software: you can redistribute it and/or
|
8
|
+
# modify it under the terms of the GNU General Public License as published
|
9
|
+
# by the Free Software Foundation, either version 3 of the License, or (at
|
10
|
+
# your option) any later version.
|
11
|
+
#
|
12
|
+
# WinRM is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
15
|
+
# Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU General Public License along
|
18
|
+
# with WinRM. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
#############################################################################
|
20
|
+
require 'handsoap'
|
21
|
+
|
22
|
+
module WinRM
|
23
|
+
module SOAP
|
24
|
+
NS_ADDRESSING ='a' # http://schemas.xmlsoap.org/ws/2004/08/addressing
|
25
|
+
NS_CIMBINDING ='b' # http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd
|
26
|
+
NS_ENUM ='n' # http://schemas.xmlsoap.org/ws/2004/09/enumeration
|
27
|
+
NS_TRANSFER ='x' # http://schemas.xmlsoap.org/ws/2004/09/transfer
|
28
|
+
NS_WSMAN_DMTF ='w' # http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
|
29
|
+
NS_WSMAN_MSFT ='p' # http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd
|
30
|
+
NS_SCHEMA_INST ='xsi' # http://www.w3.org/2001/XMLSchema-instance
|
31
|
+
NS_WIN_SHELL ='rsp' # http://schemas.microsoft.com/wbem/wsman/1/windows/shell
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'soap/exceptions/exceptions'
|
36
|
+
require 'soap/winrm_service'
|
@@ -0,0 +1,345 @@
|
|
1
|
+
#############################################################################
|
2
|
+
# Copyright © 2010 Dan Wanek <dan.wanek@gmail.com>
|
3
|
+
#
|
4
|
+
#
|
5
|
+
# This file is part of WinRM.
|
6
|
+
#
|
7
|
+
# WinRM is free software: you can redistribute it and/or
|
8
|
+
# modify it under the terms of the GNU General Public License as published
|
9
|
+
# by the Free Software Foundation, either version 3 of the License, or (at
|
10
|
+
# your option) any later version.
|
11
|
+
#
|
12
|
+
# WinRM is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
15
|
+
# Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU General Public License along
|
18
|
+
# with WinRM. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
#############################################################################
|
20
|
+
Handsoap.http_driver = :http_client
|
21
|
+
|
22
|
+
module WinRM
|
23
|
+
module SOAP
|
24
|
+
class WinRMWebService < Handsoap::Service
|
25
|
+
include SOAP
|
26
|
+
|
27
|
+
@@raw_soap = false
|
28
|
+
|
29
|
+
def initialize()
|
30
|
+
if $DEBUG
|
31
|
+
@debug = File.new('winrm_debug.out', 'w')
|
32
|
+
@debug.sync = true
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.set_auth(user,pass)
|
38
|
+
@@user = user
|
39
|
+
@@pass = pass
|
40
|
+
end
|
41
|
+
|
42
|
+
# Turn off parsing and just return the soap response
|
43
|
+
def self.raw_soap!
|
44
|
+
@@raw_soap = true
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# ********* Begin Hooks *********
|
49
|
+
|
50
|
+
def on_create_document(doc)
|
51
|
+
doc.alias NS_ADDRESSING, 'http://schemas.xmlsoap.org/ws/2004/08/addressing'
|
52
|
+
doc.alias NS_ENUM, 'http://schemas.xmlsoap.org/ws/2004/09/enumeration'
|
53
|
+
doc.alias NS_WSMAN_DMTF, 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd'
|
54
|
+
doc.alias NS_WSMAN_MSFT, 'http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd'
|
55
|
+
doc.alias NS_SCHEMA_INST,'http://www.w3.org/2001/XMLSchema-instance'
|
56
|
+
doc.alias NS_WIN_SHELL, 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell'
|
57
|
+
doc.alias NS_CIMBINDING, 'http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd'
|
58
|
+
|
59
|
+
header = doc.find('Header')
|
60
|
+
header.add("#{NS_ADDRESSING}:To", WinRMWebService.uri)
|
61
|
+
header.add("#{NS_ADDRESSING}:ReplyTo") {|rto|
|
62
|
+
rto.add("#{NS_ADDRESSING}:Address",'http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous') {|addr|
|
63
|
+
addr.set_attr('mustUnderstand','true')
|
64
|
+
}
|
65
|
+
}
|
66
|
+
header.add("#{NS_WSMAN_DMTF}:MaxEnvelopeSize",'153600') {|mes|
|
67
|
+
mes.set_attr('mustUnderstand','true')
|
68
|
+
}
|
69
|
+
header.add("#{NS_ADDRESSING}:MessageID", "uuid:#{UUID.generate.upcase}")
|
70
|
+
header.add("#{NS_WSMAN_DMTF}:Locale") {|loc|
|
71
|
+
loc.set_attr('xml:lang','en-US')
|
72
|
+
loc.set_attr('mustUnderstand','false')
|
73
|
+
}
|
74
|
+
header.add("#{NS_WSMAN_MSFT}:DataLocale") {|loc|
|
75
|
+
loc.set_attr('xml:lang','en-US')
|
76
|
+
loc.set_attr('mustUnderstand','false')
|
77
|
+
}
|
78
|
+
header.add("#{NS_WSMAN_DMTF}:OperationTimeout",'PT60.000S')
|
79
|
+
end
|
80
|
+
|
81
|
+
# Adds knowledge of namespaces to the response object. These have to be identical to the
|
82
|
+
# URIs returned in the XML response. For example, I had some issues with the 'soap'
|
83
|
+
# namespace because my original URI did not end in a '/'
|
84
|
+
# @example
|
85
|
+
# Won't work: http://schemas.xmlsoap.org/soap/envelope
|
86
|
+
# Works: http://schemas.xmlsoap.org/soap/envelope/
|
87
|
+
def on_response_document(doc)
|
88
|
+
doc.add_namespace NS_ADDRESSING, 'http://schemas.xmlsoap.org/ws/2004/08/addressing'
|
89
|
+
doc.add_namespace NS_ENUM, 'http://schemas.xmlsoap.org/ws/2004/09/enumeration'
|
90
|
+
doc.add_namespace NS_TRANSFER, 'http://schemas.xmlsoap.org/ws/2004/09/transfer'
|
91
|
+
doc.add_namespace NS_WSMAN_DMTF, 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd'
|
92
|
+
doc.add_namespace NS_WSMAN_MSFT, 'http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd'
|
93
|
+
doc.add_namespace NS_WIN_SHELL, 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell'
|
94
|
+
doc.add_namespace NS_CIMBINDING, 'http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd'
|
95
|
+
end
|
96
|
+
|
97
|
+
def on_after_create_http_request(req)
|
98
|
+
req.set_auth @@user, @@pass
|
99
|
+
req.set_header('Content-Type','application/soap+xml;charset=UTF-8')
|
100
|
+
#puts "SOAP DOCUMENT=\n#{req.body}"
|
101
|
+
end
|
102
|
+
|
103
|
+
def on_http_error(resp)
|
104
|
+
puts "HTTP ERROR: #{resp.status}"
|
105
|
+
puts "HEADERS=\n#{resp.headers}"
|
106
|
+
puts "BODY=\n#{resp.body}"
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
# ********** End Hooks **********
|
111
|
+
|
112
|
+
|
113
|
+
# Create a Shell on the destination host
|
114
|
+
# @param [String<optional>] i_stream Which input stream to open. Leave this alone unless you know what you're doing
|
115
|
+
# @param [String<optional>] o_stream Which output stream to open. Leave this alone unless you know what you're doing
|
116
|
+
# @return [String] The ShellId from the SOAP response. This is our open shell instance on the remote machine.
|
117
|
+
def open_shell(i_stream = 'stdin', o_stream = 'stdout stderr')
|
118
|
+
header = {
|
119
|
+
"#{NS_WSMAN_DMTF}:OptionSet" => [
|
120
|
+
{"#{NS_WSMAN_DMTF}:Option" => {:name => 'WINRS_NOPROFILE', :text =>"FALSE"}},
|
121
|
+
{"#{NS_WSMAN_DMTF}:Option" => {:name => 'WINRS_CODEPAGE', :text =>"437"}}
|
122
|
+
]
|
123
|
+
}.merge(resource_uri_cmd).merge(action_create)
|
124
|
+
|
125
|
+
resp = invoke("#{NS_WIN_SHELL}:Shell", {:soap_action => :auto, :http_options => nil, :soap_header => header}) do |shell|
|
126
|
+
shell.add("#{NS_WIN_SHELL}:InputStreams", i_stream)
|
127
|
+
shell.add("#{NS_WIN_SHELL}:OutputStreams",o_stream)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Get the Shell ID from the response
|
131
|
+
(resp/"//*[@Name='ShellId']").to_s
|
132
|
+
end
|
133
|
+
|
134
|
+
# Run a command on a machine with an open shell
|
135
|
+
# @param [String] shell_id The shell id on the remote machine. See #open_shell
|
136
|
+
# @param [String] command The command to run on the remote machine
|
137
|
+
# @return [String] The CommandId from the SOAP response. This is the ID we need to query in order to get output.
|
138
|
+
def run_command(shell_id, command)
|
139
|
+
header = {
|
140
|
+
"#{NS_WSMAN_DMTF}:OptionSet" => {
|
141
|
+
"#{NS_WSMAN_DMTF}:Option" => {:name => 'WINRS_CONSOLEMODE_STDIN', :text =>"TRUE"},
|
142
|
+
}
|
143
|
+
}.merge(resource_uri_cmd).merge(action_command).merge(selector_shell_id(shell_id))
|
144
|
+
|
145
|
+
# Issue the Command
|
146
|
+
resp = invoke("#{NS_WIN_SHELL}:CommandLine", {:soap_action => :auto, :http_options => nil, :soap_header => header}) do |cli|
|
147
|
+
cli.add("#{NS_WIN_SHELL}:Command","\"#{command}\"")
|
148
|
+
end
|
149
|
+
|
150
|
+
(resp/"//#{NS_WIN_SHELL}:CommandId").to_s
|
151
|
+
end
|
152
|
+
|
153
|
+
# Get the Output of the given shell and command
|
154
|
+
# @param [String] shell_id The shell id on the remote machine. See #open_shell
|
155
|
+
# @param [String] command_id The command id on the remote machine. See #run_command
|
156
|
+
# @return [Hash] :stdout and :stderr
|
157
|
+
def get_command_output(shell_id, command_id)
|
158
|
+
header = {}.merge(resource_uri_cmd).merge(action_receive).merge(selector_shell_id(shell_id))
|
159
|
+
|
160
|
+
# Get Command Output
|
161
|
+
resp = invoke("#{NS_WIN_SHELL}:Receive", {:soap_action => :auto, :http_options => nil, :soap_header => header}) do |rec|
|
162
|
+
rec.add("#{NS_WIN_SHELL}:DesiredStream",'stdout stderr') do |ds|
|
163
|
+
ds.set_attr('CommandId', command_id)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
cmd_stdout = ''
|
168
|
+
cmd_stderr = ''
|
169
|
+
(resp/"//*[@Name='stdout']").each do |n|
|
170
|
+
next if n.to_s.nil?
|
171
|
+
cmd_stdout << Base64.decode64(n.to_s)
|
172
|
+
end
|
173
|
+
(resp/"//*[@Name='stderr']").each do |n|
|
174
|
+
next if n.to_s.nil?
|
175
|
+
cmd_stderr << Base64.decode64(n.to_s)
|
176
|
+
end
|
177
|
+
{:stdout => cmd_stdout, :stderr => cmd_stderr}
|
178
|
+
end
|
179
|
+
|
180
|
+
# Clean-up after a command.
|
181
|
+
# @see #run_command
|
182
|
+
# @param [String] shell_id The shell id on the remote machine. See #open_shell
|
183
|
+
# @param [String] command_id The command id on the remote machine. See #run_command
|
184
|
+
# @return [true] This should have more error checking but it just returns true for now.
|
185
|
+
def cleanup_command(shell_id, command_id)
|
186
|
+
header = {}.merge(resource_uri_cmd).merge(action_signal).merge(selector_shell_id(shell_id))
|
187
|
+
# Signal the Command references to terminate (close stdout/stderr)
|
188
|
+
resp = invoke("#{NS_WIN_SHELL}:Signal", {:soap_action => :auto, :http_options => nil, :soap_header => header}) do |sig|
|
189
|
+
sig.set_attr('CommandId', command_id)
|
190
|
+
sig.add("#{NS_WIN_SHELL}:Code",'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate')
|
191
|
+
end
|
192
|
+
true
|
193
|
+
end
|
194
|
+
|
195
|
+
# Close the shell
|
196
|
+
# @param [String] shell_id The shell id on the remote machine. See #open_shell
|
197
|
+
# @return [true] This should have more error checking but it just returns true for now.
|
198
|
+
def close_shell(shell_id)
|
199
|
+
header = {}.merge(resource_uri_cmd).merge(action_delete).merge(selector_shell_id(shell_id))
|
200
|
+
# Delete the Shell reference
|
201
|
+
resp = invoke(:nil_body, {:soap_action => nil, :soap_body => true, :http_options => nil, :soap_header => header})
|
202
|
+
true
|
203
|
+
end
|
204
|
+
|
205
|
+
# Run a Powershell script that resides on the local box.
|
206
|
+
# @param [String] script_file The string representing the path to a Powershell script
|
207
|
+
# @return [Hash] :stdout and :stderr
|
208
|
+
def run_powershell_script(script_file)
|
209
|
+
script = File.read(script_file)
|
210
|
+
script = script.chars.to_a.join("\x00").chomp.encode('ASCII-8BIT')
|
211
|
+
script = Base64.strict_encode64(script)
|
212
|
+
|
213
|
+
shell_id = open_shell
|
214
|
+
command_id = run_command(shell_id, "powershell -encodedCommand #{script}")
|
215
|
+
command_output = get_command_output(shell_id, command_id)
|
216
|
+
cleanup_command(shell_id, command_id)
|
217
|
+
close_shell(shell_id)
|
218
|
+
command_output
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
# Run a WQL Query
|
223
|
+
# @see http://msdn.microsoft.com/en-us/library/aa394606(VS.85).aspx
|
224
|
+
# @param [String] wql The WQL query
|
225
|
+
# @return [Array<Hash>] Returns an array of Hashes that contain the key/value pairs returned from the query.
|
226
|
+
def run_wql(wql)
|
227
|
+
header = {}.merge(resource_uri_wmi).merge(action_enumerate)
|
228
|
+
|
229
|
+
begin
|
230
|
+
resp = invoke("#{NS_ENUM}:Enumerate", {:soap_action => :auto, :http_options => nil, :soap_header => header}) do |enum|
|
231
|
+
enum.add("#{NS_WSMAN_DMTF}:OptimizeEnumeration")
|
232
|
+
enum.add("#{NS_WSMAN_DMTF}:MaxElements",'32000')
|
233
|
+
mattr = nil
|
234
|
+
enum.add("#{NS_WSMAN_DMTF}:Filter", wql) do |filt|
|
235
|
+
filt.set_attr('Dialect','http://schemas.microsoft.com/wbem/wsman/1/WQL')
|
236
|
+
end
|
237
|
+
end
|
238
|
+
rescue Handsoap::Fault => e
|
239
|
+
raise WinRMWebServiceError, e.reason
|
240
|
+
end
|
241
|
+
|
242
|
+
query_response = []
|
243
|
+
(resp/"//#{NS_ENUM}:EnumerateResponse//#{NS_WSMAN_DMTF}:Items/*").each do |i|
|
244
|
+
qitem = {}
|
245
|
+
(i/'*').each do |si|
|
246
|
+
qitem[si.node_name] = si.to_s
|
247
|
+
end
|
248
|
+
query_response << qitem
|
249
|
+
end
|
250
|
+
query_response
|
251
|
+
end
|
252
|
+
|
253
|
+
|
254
|
+
# To create an empty body set :soap_body => true in the invoke options and set the action to :nil_body
|
255
|
+
def iterate_hash_array(element, hash_array)
|
256
|
+
add_hierarchy!(element, hash_array, nil) unless hash_array.key?(:nil_body)
|
257
|
+
end
|
258
|
+
|
259
|
+
|
260
|
+
protected
|
261
|
+
|
262
|
+
# Add a hierarchy of elements from hash data
|
263
|
+
# @example Hash to XML
|
264
|
+
# {:this => {:text =>'that'},'top' => {:id => '32fss', :text => 'TestText', {'middle' => 'bottom'}}}
|
265
|
+
# becomes...
|
266
|
+
# <this>that</this>
|
267
|
+
# <top Id='32fss'>
|
268
|
+
# TestText
|
269
|
+
# <middle>bottom</middle>
|
270
|
+
# </top>
|
271
|
+
def add_hierarchy!(node, e_hash, prefix = NS_ADDRESSING)
|
272
|
+
prefix << ":" unless prefix.nil?
|
273
|
+
e_hash.each_pair do |k,v|
|
274
|
+
name = (k.is_a?(Symbol) && k != :text) ? k.to_s.camel_case : k
|
275
|
+
if v.is_a? Hash
|
276
|
+
node.add("#{prefix}#{name}", v[:text]) do |n|
|
277
|
+
add_hierarchy!(n, v, prefix)
|
278
|
+
end
|
279
|
+
elsif v.is_a? Array
|
280
|
+
node.add("#{prefix}#{name}") do |n|
|
281
|
+
v.each do |i|
|
282
|
+
add_hierarchy!(n, i, prefix)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
else
|
286
|
+
node.set_attr(name, v) unless k == :text
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
|
292
|
+
# Private Methods (Builders and Parsers)
|
293
|
+
private
|
294
|
+
|
295
|
+
def build!(node, opts = {}, &block)
|
296
|
+
#EwsBuilder.new(node, opts, &block)
|
297
|
+
end
|
298
|
+
|
299
|
+
def parse!(response, opts = {})
|
300
|
+
return response if @@raw_soap
|
301
|
+
#EwsParser.new(response).parse(opts)
|
302
|
+
end
|
303
|
+
|
304
|
+
|
305
|
+
# Helper methods for SOAP Headers
|
306
|
+
|
307
|
+
def resource_uri_cmd
|
308
|
+
{"#{NS_WSMAN_DMTF}:ResourceURI" => {'mustUnderstand' => 'true', :text => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd'}}
|
309
|
+
end
|
310
|
+
|
311
|
+
def resource_uri_wmi(namespace = 'root/cimv2/*')
|
312
|
+
{"#{NS_WSMAN_DMTF}:ResourceURI" => {'mustUnderstand' => 'true', :text => "http://schemas.microsoft.com/wbem/wsman/1/wmi/#{namespace}"}}
|
313
|
+
end
|
314
|
+
|
315
|
+
def action_create
|
316
|
+
{"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => 'true', :text => 'http://schemas.xmlsoap.org/ws/2004/09/transfer/Create'}}
|
317
|
+
end
|
318
|
+
|
319
|
+
def action_delete
|
320
|
+
{"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => 'true', :text => 'http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete'}}
|
321
|
+
end
|
322
|
+
|
323
|
+
def action_command
|
324
|
+
{"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => 'true', :text => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command'}}
|
325
|
+
end
|
326
|
+
|
327
|
+
def action_receive
|
328
|
+
{"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => 'true', :text => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive'}}
|
329
|
+
end
|
330
|
+
|
331
|
+
def action_signal
|
332
|
+
{"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => 'true', :text => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal'}}
|
333
|
+
end
|
334
|
+
|
335
|
+
def action_enumerate
|
336
|
+
{"#{NS_ADDRESSING}:Action" => {'mustUnderstand' => 'true', :text => 'http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate'}}
|
337
|
+
end
|
338
|
+
|
339
|
+
def selector_shell_id(shell_id)
|
340
|
+
{"#{NS_WSMAN_DMTF}:SelectorSet" => {"#{NS_WSMAN_DMTF}:Selector" => {:name => 'ShellId', :text => shell_id}}}
|
341
|
+
end
|
342
|
+
|
343
|
+
end # class WinRMWebService
|
344
|
+
end # module SOAP
|
345
|
+
end # WinRM
|
data/lib/winrm.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
#############################################################################
|
2
|
+
# Copyright © 2010 Dan Wanek <dan.wanek@gmail.com>
|
3
|
+
#
|
4
|
+
#
|
5
|
+
# This file is part of WinRM.
|
6
|
+
#
|
7
|
+
# WinRM is free software: you can redistribute it and/or
|
8
|
+
# modify it under the terms of the GNU General Public License as published
|
9
|
+
# by the Free Software Foundation, either version 3 of the License, or (at
|
10
|
+
# your option) any later version.
|
11
|
+
#
|
12
|
+
# WinRM is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
15
|
+
# Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU General Public License along
|
18
|
+
# with WinRM. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
#############################################################################
|
20
|
+
|
21
|
+
# We only what one instance of this class so include Singleton
|
22
|
+
require 'singleton'
|
23
|
+
require 'date'
|
24
|
+
require 'base64'
|
25
|
+
require 'uuid'
|
26
|
+
require 'kconv'
|
27
|
+
|
28
|
+
# Class Extensions
|
29
|
+
require 'extensions/string'
|
30
|
+
|
31
|
+
# Load the backend SOAP infrastructure. Today this is Handsoap.
|
32
|
+
require 'soap/soap_provider'
|
33
|
+
|
34
|
+
|
35
|
+
module WinRM
|
36
|
+
class WinRM
|
37
|
+
include Singleton
|
38
|
+
|
39
|
+
attr_reader :winrm
|
40
|
+
|
41
|
+
# Set the endpoint for WinRM Web Services.
|
42
|
+
# @param [String] endpoint The URL of the endpoint.
|
43
|
+
# https://myserver:5986/wsman
|
44
|
+
# @param [Integer] version The SOAP version to use. This defaults to 1
|
45
|
+
# and you should not need to pass this parameter.
|
46
|
+
def self.endpoint=(endpoint, version = 2)
|
47
|
+
@@endpoint = endpoint
|
48
|
+
SOAP::WinRMWebService.endpoint(:uri => endpoint, :version => version)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Fetch the current endpoint
|
52
|
+
def self.endpoint
|
53
|
+
@@endpoint
|
54
|
+
end
|
55
|
+
|
56
|
+
# Set the SOAP username and password.
|
57
|
+
# @param [String] user The user name
|
58
|
+
# @param [String] pass The password
|
59
|
+
def self.set_auth(user,pass)
|
60
|
+
@@user = user
|
61
|
+
SOAP::WinRMWebService.set_auth(user,pass)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Set the http driver that the SOAP back-end will use.
|
65
|
+
# @param [Symbol] driver The HTTP driver. Available drivers:
|
66
|
+
# :curb, :net_http, :http_client(Default)
|
67
|
+
def self.set_http_driver(driver)
|
68
|
+
Handsoap.http_driver = driver
|
69
|
+
end
|
70
|
+
|
71
|
+
def initialize
|
72
|
+
@winrm = SOAP::WinRMWebService.new
|
73
|
+
end
|
74
|
+
|
75
|
+
# Run a Powershell script
|
76
|
+
# @see WinRM::SOAP::WinRMWebService#run_powershell_script
|
77
|
+
def powershell(script_file)
|
78
|
+
@winrm.run_powershell_script(script_file)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Run a WQL Query
|
82
|
+
# @see WinRM::SOAP::WinRMWebService#run_wql
|
83
|
+
# @see http://msdn.microsoft.com/en-us/library/aa394606(VS.85).aspx
|
84
|
+
def wql(wql)
|
85
|
+
@winrm.run_wql(wql)
|
86
|
+
end
|
87
|
+
|
88
|
+
end # class WinRM
|
89
|
+
end
|
data/preamble
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#############################################################################
|
2
|
+
# Copyright © 2010 Dan Wanek <dan.wanek@gmail.com>
|
3
|
+
#
|
4
|
+
#
|
5
|
+
# This file is part of WinRM.
|
6
|
+
#
|
7
|
+
# WinRM is free software: you can redistribute it and/or
|
8
|
+
# modify it under the terms of the GNU General Public License as published
|
9
|
+
# by the Free Software Foundation, either version 3 of the License, or (at
|
10
|
+
# your option) any later version.
|
11
|
+
#
|
12
|
+
# WinRM is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
15
|
+
# Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU General Public License along
|
18
|
+
# with WinRM. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
#############################################################################
|