ruby-wmi 0.2.2 → 0.4.0

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