ruby-wmi 0.2.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +4 -0
  3. data/{README.txt → README.md} +21 -24
  4. data/Thorfile +86 -0
  5. data/lib/ruby-wmi.rb +85 -8
  6. data/lib/ruby-wmi/base.rb +355 -119
  7. data/lib/ruby-wmi/core_ext/time_ext.rb +8 -3
  8. data/lib/ruby-wmi/errors.rb +21 -0
  9. data/lib/ruby-wmi/{constants.rb → privilege.rb} +2 -2
  10. data/lib/ruby-wmi/version.rb +3 -0
  11. data/ruby-wmi.gemspec +21 -0
  12. data/spec/time_ext_spec.rb +19 -0
  13. data/spec/wql_spec.rb +80 -0
  14. data/web/public/css/active4d.css +114 -0
  15. data/web/public/css/all_hallows_eve.css +72 -0
  16. data/web/public/css/amy.css +147 -0
  17. data/web/public/css/blackboard.css +88 -0
  18. data/web/public/css/brilliance_black.css +605 -0
  19. data/web/public/css/brilliance_dull.css +599 -0
  20. data/web/public/css/cobalt.css +149 -0
  21. data/web/public/css/dawn.css +121 -0
  22. data/web/public/css/eiffel.css +121 -0
  23. data/web/public/css/espresso_libre.css +109 -0
  24. data/web/public/css/idle.css +62 -0
  25. data/web/public/css/iplastic.css +80 -0
  26. data/web/public/css/lazy.css +73 -0
  27. data/web/public/css/mac_classic.css +123 -0
  28. data/web/public/css/magicwb_amiga.css +104 -0
  29. data/web/public/css/pastels_on_dark.css +188 -0
  30. data/web/public/css/rspec.css +118 -0
  31. data/web/public/css/slush_poppies.css +85 -0
  32. data/web/public/css/spacecadet.css +51 -0
  33. data/web/public/css/sunburst.css +180 -0
  34. data/web/public/css/toader.css +285 -0
  35. data/web/public/css/twilight.css +137 -0
  36. data/web/public/css/zenburnesque.css +91 -0
  37. data/web/public/images/bg.gif +0 -0
  38. data/web/public/images/bullet.gif +0 -0
  39. data/web/public/images/footer_pic.gif +0 -0
  40. data/web/public/images/green_vr.gif +0 -0
  41. data/web/public/images/hr.gif +0 -0
  42. data/web/public/images/main.gif +0 -0
  43. data/web/public/images/nav_over.gif +0 -0
  44. data/web/public/images/nav_over_left.gif +0 -0
  45. data/web/public/images/nav_over_right.gif +0 -0
  46. data/web/public/images/nav_under.gif +0 -0
  47. data/web/public/images/your-face.gif +0 -0
  48. data/web/public/images/your-picture.gif +0 -0
  49. data/web/templates/index.html.erb +71 -0
  50. data/web/templates/template-1109.zip +0 -0
  51. metadata +109 -64
  52. data/History.txt +0 -25
  53. data/Manifest.txt +0 -14
  54. data/Rakefile +0 -62
  55. data/test/test_ruby-wmi.rb +0 -2
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ /pkg
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # A sample Gemfile
2
+ source "http://rubygems.org"
3
+
4
+ gemspec
@@ -1,26 +1,26 @@
1
- ruby-wmi
2
- by Gordon Thiesfeld
3
- http://ruby-wmi.rubyforge.org/
4
- gthiesfeld@gmail.com
1
+ # Description
5
2
 
6
- == DESCRIPTION:
3
+ ruby-wmi is an ActiveRecord style interface for Microsoft's Windows
4
+ Management Instrumentation provider.
7
5
 
8
- ruby-wmi is an ActiveRecord style interface for Microsoft's Windows
9
- Management Instrumentation provider.
6
+ Many of the methods in WMI::Base are borrowed directly, or with some
7
+ modification from ActiveRecord.
8
+ http://api.rubyonrails.org/classes/ActiveRecord/Base.html
10
9
 
11
- Many of the methods in WMI::Base are borrowed directly, or with some
12
- modification from ActiveRecord.
13
- http://api.rubyonrails.org/classes/ActiveRecord/Base.html
10
+ The major tool in this library is the #find method. For more
11
+ information, see WMI::Base.
14
12
 
15
- The major tool in this library is the #find method. For more
16
- information, see WMI::Base.
13
+ There is also a WMI.sublasses method included for reflection.
17
14
 
18
- There is also a WMI.sublasses method included for reflection.
15
+ # Requirements
19
16
 
20
- == SYNOPSIS:
17
+ * Windows 2000 or newer
18
+ * Ruby 1.8 / 1.9
21
19
 
22
- # The following code sample kills all processes of a given name
23
- # (in this case, Notepad), except the oldest.
20
+ # Synopsis
21
+
22
+ The following code sample kills all processes of a given name
23
+ (in this case, Notepad), except the oldest.
24
24
 
25
25
  require 'ruby-wmi'
26
26
 
@@ -31,21 +31,18 @@ ruby-wmi
31
31
  morituri.shift
32
32
  morituri.each{|p| p.terminate }
33
33
 
34
- == REQUIREMENTS:
34
+ # Installation
35
35
 
36
- Windows 2000 or newer
37
- Ruby 1.8
36
+ $ gem install ruby-wmi
38
37
 
39
- == INSTALL:
38
+ # License and Authors
40
39
 
41
- gem install ruby-wmi
40
+ Maintainer:: Jamie Winsor (<jamie@vialstudios.com>)
42
41
 
43
- == LICENSE:
42
+ Original Author:: Gordon Thiesfeld (<gthiesfeld@gmail.com>)
44
43
 
45
44
  (The MIT License)
46
45
 
47
- Copyright (c) 2007 Gordon Thiesfeld
48
-
49
46
  Permission is hereby granted, free of charge, to any person obtaining
50
47
  a copy of this software and associated documentation files (the
51
48
  'Software'), to deal in the Software without restriction, including
@@ -0,0 +1,86 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require 'ruby-wmi/version'
3
+
4
+ class Default < Thor
5
+ class_option :verbose,
6
+ :type => :boolean,
7
+ :aliases => "-v",
8
+ :default => false
9
+
10
+ desc "build", "Build the ruby-wmi gem"
11
+ def build
12
+ invoke :clean
13
+
14
+ sh("gem build -V '#{source_root.join("ruby-wmi.gemspec")}'", source_root)
15
+ FileUtils.mv(Dir.glob("*.gem"), "pkg/")
16
+ rescue => e
17
+ say e, :red
18
+ exit 1
19
+ end
20
+
21
+ desc "clean", "Clean the project"
22
+ def clean
23
+ FileUtils.mkdir_p(pkg_path)
24
+ Dir[pkg_path.join("*")].each { |f| FileUtils.rm(f, :force => true) }
25
+ end
26
+
27
+ desc "release", "Create a tag from the gem version, build, and push the ruby-wmi gem to rubygems"
28
+ def release
29
+ unless clean?
30
+ say "There are files that need to be committed first.", :red
31
+ exit 1
32
+ end
33
+
34
+ invoke :clean
35
+ invoke :build
36
+
37
+ tag_version {
38
+ sh("gem push #{pkg_path.join("ruby-wmi-#{RubyWMI::VERSION}.gem")}")
39
+ }
40
+ end
41
+
42
+ private
43
+
44
+ def clean?
45
+ sh_with_excode("git diff --exit-code")[1] == 0
46
+ end
47
+
48
+ def pkg_path
49
+ source_root.join("pkg")
50
+ end
51
+
52
+ def sh(cmd, dir = source_root, &block)
53
+ out, code = sh_with_excode(cmd, dir, &block)
54
+ code == 0 ? out : raise(out.empty? ? "Running `#{cmd}` failed. Run this command directly for more detailed output." : out)
55
+ end
56
+
57
+ def sh_with_excode(cmd, dir = source_root, &block)
58
+ cmd << " 2>&1"
59
+ outbuf = ''
60
+
61
+ Dir.chdir(dir) {
62
+ outbuf = `#{cmd}`
63
+ if $? == 0
64
+ block.call(outbuf) if block
65
+ end
66
+ }
67
+
68
+ [ outbuf, $? ]
69
+ end
70
+
71
+ def source_root
72
+ Pathname.new File.dirname(File.expand_path(__FILE__))
73
+ end
74
+
75
+ def tag_version
76
+ sh "git tag -a -m \"Version #{RubyWMI::VERSION}\" #{RubyWMI::VERSION}"
77
+ say "Tagged: #{RubyWMI::VERSION}", :green
78
+ yield if block_given?
79
+ sh "git push --tags"
80
+ rescue => e
81
+ say "Untagging: #{RubyWMI::VERSION} due to error", :red
82
+ sh_with_excode "git tag -d #{RubyWMI::VERSION}"
83
+ say e, :red
84
+ exit 1
85
+ end
86
+ end
@@ -1,10 +1,87 @@
1
- class RubyWMI
2
- VERSION = '0.2.2'
3
- end
1
+ require 'ruby-wmi/version'
2
+ require 'ruby-wmi/core_ext'
3
+ require 'ruby-wmi/errors'
4
+ require 'win32ole'
4
5
 
5
- $:.unshift(File.dirname(__FILE__)) unless
6
- $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
6
+ WIN32OLE.codepage = WIN32OLE::CP_UTF8
7
7
 
8
- require 'ruby-wmi/core_ext'
9
- require 'ruby-wmi/constants'
10
- require 'ruby-wmi/base'
8
+ module RubyWMI
9
+ autoload :Privilege, 'ruby-wmi/privilege'
10
+ autoload :Base, 'ruby-wmi/base'
11
+
12
+ # Returns an array containing all the WMI subclasses
13
+ # on a sytem. Defaults to localhost
14
+ #
15
+ # WMI.subclasses
16
+ # => ["Win32_PrivilegesStatus", "Win32_TSNetworkAdapterSettingError", ...]
17
+ #
18
+ # For a more human readable version of subclasses when using options:
19
+ #
20
+ # WMI.subclasses_of(:host => 'some_computer')
21
+ # => ["Win32_PrivilegesStatus", "Win32_TSNetworkAdapterSettingError", ...]
22
+ #
23
+ # WMI.subclasses_of(
24
+ # :host => :some_computer,
25
+ # :namespace => "root\\Microsoft\\SqlServer\\ComputerManagement"
26
+ # )
27
+ def subclasses(options ={})
28
+ options.merge!(:method => :SubclassesOf)
29
+ instances_of(options).
30
+ map{ |subclass| subclass.Path_.Class }
31
+ end
32
+ alias :subclasses_of :subclasses
33
+
34
+ # Returns an array containing all the WMI providers
35
+ # on a sytem. Defaults to localhost
36
+ #
37
+ # WMI.providers
38
+ #
39
+ # For a more human readable version of providers when using options:
40
+ #
41
+ # WMI.providers_of(:host => :some_computer)
42
+ def providers(options ={})
43
+ options.merge!(:method => :InstancesOf, :instance => "__Win32Provider")
44
+ instances_of(options).
45
+ map{ |provider| provider.name }.compact
46
+ end
47
+ alias :providers_of :providers
48
+
49
+ # Returns an array containing all the WMI namespaces
50
+ # on a sytem. Defaults to localhost
51
+ #
52
+ # WMI.namespaces
53
+ #
54
+ # For a more human readable version of namespaces when using options:
55
+ #
56
+ # WMI.namespaces_of(:host => :some_computer)
57
+ #
58
+ # WMI.namespaces_of(:namespace => "root\\Microsoft")
59
+ def namespaces(options ={})
60
+ options.merge!(:method => :InstancesOf, :instance => "__NAMESPACE")
61
+ options[:namespace] ||= 'root'
62
+ instances_of(options).
63
+ map{ |namespace|
64
+ namespace = "#{options[:namespace]}\\#{namespace.name}"
65
+ ns = namespaces(options.merge(:namespace => namespace)) rescue nil
66
+ [namespace, ns]
67
+ }.flatten
68
+ end
69
+ alias :namespaces_of :namespaces
70
+
71
+ def instances_of(options)
72
+ Base.set_connection(options)
73
+ conn = Base.send(:connection)
74
+ items = namespaces = conn.send(options[:method], options[:instance])
75
+ Base.send(:clear_connection_options)
76
+ items
77
+ end
78
+
79
+ extend self
80
+
81
+ def const_missing(name)
82
+ self.const_set(name, Class.new(self::Base))
83
+ end
84
+ end
85
+
86
+ # Alias for {RubyWMI}
87
+ WMI = RubyWMI
@@ -1,66 +1,38 @@
1
- require 'win32ole'
2
-
3
- module WMI
4
-
5
- # Generic WMI exception class.
6
- class WMIError < StandardError
7
- end
8
-
9
- # Invalid Class exception class.
10
- class InvalidClass < WMIError
11
- end
12
-
13
- # Invalid Query exception class.
14
- class InvalidQuery < WMIError
15
- end
16
-
17
- # Returns an array conating all the WMI subclasses
18
- # on a sytem. Defaults to localhost
19
- #
20
- # WMI.subclasses
21
- # => ["Win32_PrivilegesStatus", "Win32_TSNetworkAdapterSettingError", ...]
22
- #
23
- # For a more human readable version of subclasses when using options:
24
- #
25
- # WMI.subclasses_of(:host => some_computer)
26
- # => ["Win32_PrivilegesStatus", "Win32_TSNetworkAdapterSettingError", ...]
27
- def subclasses(options ={})
28
- Base.set_connection(options)
29
- b = Base.send(:connection)
30
- b.SubclassesOf.map { |subclass| class_name = subclass.Path_.Class }
31
- end
32
-
33
-
34
- alias :subclasses_of :subclasses
35
-
36
- extend self
37
-
38
- class Base
1
+ module RubyWMI
39
2
  # Many of the methods in Base are borrowed directly, or with some modification from ActiveRecord
40
3
  # http://api.rubyonrails.org/classes/ActiveRecord/Base.html
41
-
42
- class << self
43
-
4
+ class Base
5
+ class << self
44
6
  # #find_by_wql currently only works when called through #find
45
7
  # it may stay like that too. I haven't decided.
46
8
  def find_by_wql(query)
9
+ # TODO: add logging, ie:
10
+ logger.debug query if logger
47
11
  d = connection.ExecQuery(query)
48
12
  begin
49
13
  d.count # needed to check for errors. Weird, but it works.
50
14
  rescue => error
51
15
  case error.to_s
52
- when /Invalid class/i : raise InvalidClass
53
- when /Invalid query/i : raise InvalidQuery
16
+ when /Invalid class/i
17
+ raise InvalidClass
18
+ when /Invalid query/i
19
+ raise InvalidQuery
20
+ when /Invalid namespace/i
21
+ raise InvalidNameSpace
54
22
  end
55
23
  end
56
- d.to_a
24
+ clear_connection_options
25
+ d.map{|wmi_object| new(wmi_object) }
57
26
  end
58
27
 
59
- # WMI::Win32ComputerSystem.find(:all)
28
+ # WMI::Win32_ComputerSystem.find(:all)
60
29
  # returns an array of Win32ComputerSystem objects
61
30
  #
62
- # WMI::Win32ComputerSystem.find(:first)
63
- # returns a Win32ComputerSystem object
31
+ # WMI::Win32_ComputerSystem.find(:first)
32
+ # returns the first Win32_ComputerSystem object
33
+ #
34
+ # WMI::Win32_ComputerSystem.find(:last)
35
+ # returns the last Win32_ComputerSystem object
64
36
  #
65
37
  # options:
66
38
  #
@@ -68,113 +40,377 @@ module WMI
68
40
  #
69
41
  # Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement.
70
42
  # The array form is to be used when the condition input is tainted and requires sanitization. The string form can
71
- # be used for statements that don't involve tainted data. The hash form works much like the array form, except
72
- # only equality and range is possible. Examples:
43
+ # be used for statements that don't involve tainted data. The hash form works much like the array form.
44
+ #
45
+ # Hash examples:
46
+ #
47
+ # WMI::Win32_ComputerSystem.find(:all, :conditions => {:drive_type => 3} )
48
+ # WMI::Win32_NTLogEvent.find(:all, :conditions => {:time_written => time_range})
49
+ #
50
+ # Array examples:
73
51
  #
74
- # Win32ComputerSystem.find(:all, :conditions => {:drivetype => 3} ) # Hash
75
- # Win32ComputerSystem.find(:all, :conditions => [:drivetype, 3] ) # Array
76
- # Win32ComputerSystem.find(:all, :conditions => 'drivetype = 3' ) # String
52
+ # WMI::Win32_ComputerSystem.find(:all, :conditions => ['DriveType = ?', 3] )
53
+ # WMI::Win32_Service.all(:conditions => ["StartMode = ? AND Started = ?", 'manual', true])
54
+ # WMI::Win32_Process.all(:conditions => ["Name LIKE %s", 'G%'])
55
+ #
56
+ # String example:
57
+ #
58
+ # WMI::Win32_ComputerSystem.find(:all, :conditions => 'DriveType = 3' )
59
+ #
60
+ # :select
61
+ # By default, this is "*" as in "SELECT * FROM", but can be changed.
62
+ # Takes a string or symbol with a single column (e.g. "id"), or an array with
63
+ # multiple columns, (e.g. [:start_mode, :start_name, :status] )
64
+ # Column names are converted from underscore to camelcase, so :start_mode
65
+ # becomes StartMode
77
66
  #
78
67
  # :host - computername, defaults to localhost
79
- # :class - swebm class , defaults to 'root\\cimv2'
68
+ # :namespace - swebm namespace , defaults to 'root\\cimv2'
80
69
  # :privileges - see WMI::Privilege for a list of privileges
81
70
  # :user - username (domain\\username)
82
71
  # :password - password
83
72
  def find(arg=:all, options={})
84
73
  set_connection options
85
74
  case arg
86
- when :all; find_all(options)
87
- when :first; find_first(options)
75
+ when :all
76
+ find_all(options)
77
+ when :first
78
+ find_first(options)
79
+ when :last
80
+ find_last(options)
81
+ when String
82
+ options.merge!(:conditions => { :name => arg })
83
+ find_first(options)
88
84
  end
89
85
  end
90
86
 
91
- def find_first(options={})
92
- find_all(options).first
87
+ # an alias for find(:last)
88
+ def last(options={})
89
+ find(:last, options)
90
+ end
91
+
92
+ # an alias for find(:first)
93
+ def first(options={})
94
+ find(:first, options)
95
+ end
96
+
97
+ # an alias for find(:all)
98
+ def all(options={})
99
+ find(:all, options)
93
100
  end
94
101
 
95
- def find_all(options={})
96
- find_by_wql(construct_finder_sql(options))
102
+ def count(options={})
103
+ find(:all, options).size
97
104
  end
105
+
106
+ def method_missing(method_id, *arguments)
107
+ if match = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(method_id.to_s)
108
+ case match[1]
109
+ when 'all_by'
110
+ conditions = match[2].split('_and_').zip(arguments)
111
+ conditions = Hash[*conditions.flatten]
112
+ all(:conditions => conditions)
113
+ when 'by'
114
+ first(:conditions => {match[2] => arguments.first})
115
+ end
116
+ else
117
+ super
118
+ end
119
+ end
120
+
98
121
 
99
- def set_connection(options)
100
- @host = options[:host]
101
- @klass = options[:class] || 'root\\cimv2'
102
- @user,@password = options[:user], options[:password]
122
+ def set_connection(options={})
123
+ @host = options[:host].to_s || connection_options[:host].to_s
124
+ @user = options[:user] || connection_options[:user]
125
+ @password = options[:password] || connection_options[:password]
126
+ @namespace = options[:namespace] || self.namespace
103
127
  @privileges = options[:privileges]
104
128
  end
129
+
130
+ def clear_connection_options
131
+ @host = nil
132
+ @user = nil
133
+ @password = nil
134
+ @namespace = nil
135
+ @privileges = nil
136
+ connection_options.clear
137
+ end
138
+
139
+ def set_wmi_class_name(name)
140
+ @subclass_name = name
141
+ end
142
+
143
+ def set_wmi_namespace(namespace)
144
+ @namespace_ = namespace
145
+ end
105
146
 
106
- private
147
+ def namespace
148
+ @namespace_ ||= 'root\\cimv2'
149
+ end
107
150
 
108
151
  def subclass_name
109
- self.name.split('::').last
152
+ @subclass_name ||= self.name.split('::').last
110
153
  end
111
-
112
- def connection
113
- @c ||= WIN32OLE.new("WbemScripting.SWbemLocator")
114
- @privileges.each { |priv| @c.security_.privileges.add(priv, true) } if @privileges
115
- @c.ConnectServer(@host,@klass,@user,@password)
154
+
155
+ def host(hostname)
156
+ connection_options[:host] = hostname.to_s
157
+ self
158
+ end
159
+
160
+ def connection_options
161
+ @connection_options ||= {}
162
+ end
163
+
164
+ def columns
165
+ @columns ||= first(:from => 'meta_class', :conditions => "__this ISA '#{subclass_name}'").attribute_names
166
+ end
167
+
168
+ def writable?(key)
169
+ obj_def = connection.get(subclass_name)
170
+ key = camelize(key)
171
+ key_prop = obj_def.properties_(key)
172
+ key_prop.qualifiers_.each do |q|
173
+ return true if q.name == 'write'
174
+ end
175
+ false
176
+ end
177
+
178
+ def logger
179
+ @@logger ||= nil
180
+ end
181
+
182
+ def logger=(logger)
183
+ @@logger = logger
116
184
  end
117
185
 
118
- def construct_finder_sql(options)
119
- #~ scope = scope(:find)
120
- sql = "SELECT #{options[:select] || '*'} "
121
- sql << "FROM #{options[:from] || subclass_name} "
186
+ private
122
187
 
123
- #~ add_joins!(sql, options, scope)
124
- add_conditions!(sql, options[:conditions], nil)
188
+ def connection
189
+ @c ||= WIN32OLE.new("WbemScripting.SWbemLocator")
190
+ @privileges.each { |priv| @c.security_.privileges.add(priv, true) } if @privileges
191
+ log_connection if logger
192
+ @c.ConnectServer(@host,@namespace,@user,@password)
193
+ end
194
+
195
+ # logs SWbemLocator.ConnectServer parameters
196
+ # default parameters aren't logged.
197
+ def log_connection
198
+ msg = ""
199
+ msg << "Host: #{@host.inspect}, " unless @host.empty?
200
+ msg << "Namespace: #{@namespace.inspect}, " unless @namespace == "root\\cimv2" && @host.empty?
201
+ msg << "User: #{@user.inspect}, " if @user
202
+ msg << "Password: #{@password.gsub(/./,'#')}, " if @password
203
+ msg << "Privileges: #{@privileges.inspect}" if @privileges
204
+ logger.debug msg unless msg.empty?
205
+ end
125
206
 
126
- sql << " GROUP BY #{options[:group]} " if options[:group]
207
+ def find_first(options={})
208
+ find_all(options).first
209
+ end
210
+
211
+ def find_last(options={})
212
+ find_all(options).last
213
+ end
127
214
 
128
- #~ add_order!(sql, options[:order], scope)
129
- #~ add_limit!(sql, options, scope)
130
- #~ add_lock!(sql, options, scope)
215
+ def find_all(options={})
216
+ find_by_wql(construct_finder_sql(options))
217
+ end
131
218
 
132
- sql
133
- end
219
+ def construct_finder_sql(options)
220
+ [
221
+ select(options[:select]),
222
+ from(options[:from]),
223
+ conditions(options[:conditions], nil),
224
+ group(options[:group])
225
+ ].compact.join(' ')
226
+ end
227
+
228
+ def select(*selectors)
229
+ selectors = selectors.compact.empty? ? '*' : selectors.flatten.map{|i| camelize(i) }.join(', ')
230
+ "SELECT #{selectors}"
231
+ end
232
+
233
+ def from(wmi_class)
234
+ "FROM #{wmi_class || subclass_name}"
235
+ end
134
236
 
135
- def add_conditions!(sql, conditions, scope = :auto)
136
- #~ scope = scope(:find) if :auto == scope
137
- segments = []
138
- segments << sanitize_sql(conditions) unless conditions.nil?
139
- #~ segments << conditions unless conditions.nil?
140
- #~ segments << type_condition unless descends_from_active_record?
141
- segments.compact!
142
- sql << "WHERE #{segments.join(") AND (")} " unless segments.empty?
143
- sql.gsub!("\\","\\\\\\")
144
- end
237
+ def conditions(conditions, scope = :auto)
238
+ segments = []
239
+ segments << sanitize_sql(conditions) unless conditions.nil?
240
+ segments.compact!
241
+ "WHERE #{segments.join(") AND (")}".gsub(/\\/, '\&\&') unless segments.empty?
242
+ end
243
+
244
+ def group(items)
245
+ "GROUP BY #{items}" if items
246
+ end
145
247
 
146
- # Accepts an array, hash, or string of sql conditions and sanitizes
147
- # them into a valid SQL fragment.
148
- # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
149
- # { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
150
- # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
151
- def sanitize_sql(condition)
152
- case condition
153
- when Array; sanitize_sql_array(condition)
154
- when Hash; sanitize_sql_hash(condition)
155
- else condition
248
+ # Accepts an array, hash, or string of sql conditions and sanitizes
249
+ # them into a valid SQL fragment.
250
+ # ["name='%s' and device_id='%s'", "foo'bar", 4] returns "name='foo''bar' AND DeviceId='4'"
251
+ # { :name => "foo'bar", :device_id => 4 } returns "name='foo''bar' AND DeviceId='4'"
252
+ # "name='foo''bar' AND DeviceId='4'" returns "name='foo''bar' AND DeviceId='4'"
253
+ def sanitize_sql(condition)
254
+ case condition
255
+ when Array
256
+ sanitize_sql_array(condition)
257
+ when Hash
258
+ sanitize_sql_hash(condition)
259
+ else condition
260
+ end
156
261
  end
157
- end
158
262
 
159
- # Sanitizes a hash of attribute/value pairs into SQL conditions.
160
- # { :name => "foo'bar", :group_id => 4 }
161
- # # => "name='foo''bar' and group_id= 4"
162
- # { :status => nil, :group_id => [1,2,3] }
163
- # # => "status IS NULL and group_id IN (1,2,3)"
164
- def sanitize_sql_hash(attrs)
165
- conditions = attrs.map do |attr, value|
166
- #~ "#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
167
- "#{attr} = '#{value}'"
168
- end.join(' AND ')
263
+ # Sanitizes a hash of attribute/value pairs into SQL conditions.
264
+ # { :name => "foo'bar", :device_id => 4 }
265
+ # # => "name='foo''bar' AND DeviceId= 4"
266
+ # { :status => nil, :device_id => [1,2,3] }
267
+ # # => "status IS NULL AND DeviceId = '1' OR DeviceId = '2' OR DeviceId = '3'"
268
+ def sanitize_sql_hash(attrs)
269
+ conditions = attrs.map do |attr, value|
270
+ attribute_condition(camelize(attr), value)
271
+ end.join(' AND ')
272
+ replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
273
+ end
274
+
275
+ def sanitize_sql_array(ary)
276
+ statement, *values = ary
277
+ if values.first.is_a?(Hash) and statement =~ /:\w+/
278
+ replace_named_bind_variables(statement, values.first)
279
+ elsif statement.include?('?')
280
+ replace_bind_variables(statement, values)
281
+ else
282
+ statement % values.collect { |value| "'#{value}'" }
283
+ end
284
+ end
285
+
286
+ def replace_bind_variables(statement, values) #:nodoc:
287
+ raise WMIError.new("Mismatched arity #{statement}:#{values.inspect}") unless statement.count('?') == values.size
288
+ bound = values.dup
289
+ statement.gsub('?') { quote(bound.shift) }
290
+ end
291
+
292
+ def attribute_condition(column_name, argument)
293
+ case argument
294
+ when nil then "#{column_name} IS ?"
295
+ when Array then argument.map{|a| "#{column_name} = ? "}.join(" OR ")
296
+ when Range then if argument.exclude_end?
297
+ "#{column_name} >= ? AND #{column_name} < ?"
298
+ else
299
+ "#{column_name} >= ? AND #{column_name} <= ?"
300
+ end
301
+ else
302
+ "#{column_name} = ?"
303
+ end
304
+ end
305
+
306
+ def expand_range_bind_variables(bind_vars)
307
+ expanded = []
308
+
309
+ bind_vars.each do |var|
310
+ next if var.is_a?(Hash)
169
311
 
170
- #~ replace_bind_variables(conditions, attrs.values)
171
- end
312
+ if var.is_a?(Range)
313
+ expanded << var.first
314
+ expanded << var.last
315
+ elsif var.is_a?(Array)
316
+ expanded = var
317
+ else
318
+ expanded << var
319
+ end
320
+ end
321
+
322
+ expanded
323
+ end
324
+
325
+ def quote(item)
326
+ case item
327
+ when NilClass
328
+ "NULL"
329
+ when Time
330
+ "'#{item.to_swbem_date_time}'"
331
+ else
332
+ "'#{item}'"
333
+ end
334
+ end
335
+
336
+ def camelize(string)
337
+ string.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }
338
+ end
339
+
340
+ def underscore(string)
341
+ string.to_s.gsub(/::/, '/').gsub('_', '___').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
342
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
343
+ end
172
344
  end
173
- end
174
345
 
175
- private
346
+ def initialize(win32ole_object)
347
+ @win32ole_object = win32ole_object
348
+ end
349
+
350
+ def methods(all_methods=true)
351
+ super(all_methods) + @win32ole_object.methods_.map{|method| underscore(method.name) }
352
+ end
353
+
354
+ def attributes
355
+ if @attributes
356
+ return @attributes
357
+ else
358
+ @attributes = {}
359
+ @win32ole_object.properties_.each{ |prop|
360
+ name = prop.name
361
+ value = @win32ole_object.send(name)
362
+ value = if prop.cimtype == 101 && value
363
+ Time.parse_swbem_date_time(value)
364
+ else
365
+ value
366
+ end
367
+ @attributes[underscore(name)] = value
368
+ }
369
+ return @attributes
370
+ end
371
+ end
372
+
373
+ def attribute_names
374
+ @attribute_names ||= @win32ole_object.properties_.map{ |p| underscore(p.name) }
375
+ end
376
+
377
+ def [](key)
378
+ key = camelize(key.to_s)
379
+ @win32ole_object[key]
380
+ end
381
+
382
+ def []=(key,value)
383
+ key = camelize(key.to_s)
384
+ raise ReadOnlyError unless writable?(key)
385
+ @win32ole_object[key] = value
386
+ @win32ole_object.Put_
387
+ end
388
+
389
+ def method_missing(name,*args)
390
+ name = camelize(name.to_s)
391
+ @win32ole_object.send(name)
392
+ end
393
+
394
+ def camelize(string)
395
+ self.class.send(:camelize, string)
396
+ end
397
+
398
+ def underscore(string)
399
+ self.class.send(:underscore, string)
400
+ end
401
+
402
+ def inspect
403
+ "#<#{self.class}:#{name}>"
404
+ end
176
405
 
177
- def const_missing(name)
178
- self.const_set(name, Class.new(self::Base))
406
+ def name
407
+ @win32ole_object.name
408
+ rescue
409
+ begin
410
+ @win32ole_object.description
411
+ rescue
412
+ object_id
413
+ end
414
+ end
179
415
  end
180
416
  end