sys-admin 1.3.1-mswin32 → 1.4.0-mswin32

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/CHANGES CHANGED
@@ -1,3 +1,19 @@
1
+ == 1.4.0 - 20-Jan-2007
2
+ * Added the following methods: add_local_user, config_local_user,
3
+ delete_local_user, add_global_group, config_global_group, and
4
+ delete_global_group. MS Windows only at the moment.
5
+ * Added corresponding tests.
6
+ * Added much more inline documentation.
7
+ * Major refactoring of the get_lastlog_info helper function in admin.h. This
8
+ fixed a major bug in some flavors of Linux where the Admin.users method
9
+ could go into an infinite loop. It also fixed some minor bugs where console
10
+ and host values were sometimes filled with junk characters.
11
+ * Added the User#change attribute, and a check for the pw_change struct member
12
+ in the extconf.rb file.
13
+ * The User#expire attribute is now handled as a Time object instead of an
14
+ integer.
15
+ * Renamed tc_win32.rb to tc_windows.rb
16
+
1
17
  == 1.3.1 - 29-Jun-2005
2
18
  * Fixed a bug where the inability to read the lastlog file caused an error.
3
19
  From now on that error is ignored, and the lastlog attributes of the User
data/MANIFEST CHANGED
@@ -1,17 +1,18 @@
1
- extconf.rb
2
- install.rb
3
- sys-admin.gemspec
4
- CHANGES
5
- MANIFEST
6
- README
1
+ * install.rb
2
+ * sys-admin.gemspec
3
+ * CHANGES
4
+ * MANIFEST
5
+ * README
7
6
 
8
- examples/groups.rb
9
- examples/users.rb
7
+ * examples/groups.rb
8
+ * examples/users.rb
10
9
 
11
- lib/sys/unix.c
12
- lib/sys/unix.h
13
- lib/sys/win32.rb
10
+ * ext/admin.c
11
+ * ext/admin.h
12
+ * ext/extconf.rb
14
13
 
15
- test/tc_admin.rb
16
- test/tc_unix.rb
17
- test/tc_win32.rb
14
+ * lib/sys/win32.rb
15
+
16
+ * test/tc_admin.rb
17
+ * test/tc_unix.rb
18
+ * test/tc_windows.rb
data/README CHANGED
@@ -3,18 +3,18 @@
3
3
  Etc module.
4
4
 
5
5
  == Installation
6
- === Win32
7
- ruby test\tc_admin.rb # optional
8
- ruby install.rb
6
+ === Windows
7
+ ruby tc_admin.rb # optional (in the 'test' directory)
8
+ ruby install.rb
9
9
 
10
10
  === Unix
11
- ruby extconf.rb
12
- nmake
13
- ruby test\tc_admin.rb # optional
14
- nmake site-install
11
+ ruby extconf.rb (in the 'ext' directory)
12
+ make
13
+ ruby tc_admin.rb # optional (in the 'test' directory)
14
+ make install
15
15
 
16
16
  == Synopsis
17
- require "sys/admin"
17
+ require 'sys/admin'
18
18
  include Sys
19
19
 
20
20
  # Yields a User object for each user
@@ -47,7 +47,7 @@ Admin.get_user(name, host=localhost)
47
47
  Admin.get_user(uid, host=localhost, local=true)
48
48
  Returns a User object based on +name+ or +uid+.
49
49
 
50
- Win32 only: you may specify a host from which information is retrieved.
50
+ Windows only: you may specify a host from which information is retrieved.
51
51
  The default is the local machine. You may also specify whether to
52
52
  retrieve a local or global account. The default is local.
53
53
 
@@ -55,7 +55,7 @@ Admin.get_group(name, host=localhost, local=true)
55
55
  Admin.get_group(gid, host=localhost, local=true)
56
56
  Returns a Group object based on +name+ or +uid+.
57
57
 
58
- Win32 only: you may specify a host from which information is retrieved.
58
+ Windows only: you may specify a host from which information is retrieved.
59
59
  The default is the local machine. You can retrieve either a global or
60
60
  local group, depending on the value of the +local+ argument.
61
61
 
@@ -64,7 +64,7 @@ Admin.groups(host=localhost, local=true){ |group| ... }
64
64
  In block form, yields a Group object for each user on the system. In
65
65
  non-block form, returns an Array of Group objects.
66
66
 
67
- Win32 only: you may specify a host from which information is retrieved.
67
+ Windows only: you may specify a host from which information is retrieved.
68
68
  The default is the local machine. You can retrieve either a global or
69
69
  local group, depending on the value of the +local+ argument.
70
70
 
@@ -73,13 +73,13 @@ Admin.users(host=localhost, local=true){ |user| ... }
73
73
  In block form, yields a User object for each user on the system. In
74
74
  non-block form, returns an Array of User objects.
75
75
 
76
- Win32 only: you may specify a host from which information is retrieved.
76
+ Windows only: you may specify a host from which information is retrieved.
77
77
  The default is the local machine. You can retrieve either a global or
78
78
  local group, depending on the value of the +local+ argument.
79
79
 
80
80
  == User class
81
- === User (Win32)
82
- The User class has the following attributes on Win32 systems:
81
+ === User (Windows)
82
+ The User class has the following attributes on MS Windows systems:
83
83
 
84
84
  * account_type
85
85
  * caption
@@ -99,7 +99,7 @@ Admin.users(host=localhost, local=true){ |user| ... }
99
99
  * password_required?
100
100
 
101
101
  === User (Unix)
102
- The User class has the following attributes on Unix systems:
102
+ The User class has the following attributes on Unix systems:
103
103
 
104
104
  * name
105
105
  * passwd
@@ -112,11 +112,12 @@ Admin.users(host=localhost, local=true){ |user| ... }
112
112
  * age
113
113
  * class
114
114
  * comment
115
+ * change
115
116
  * expire
116
117
 
117
118
  == Group Classes
118
- === Group (Win32)
119
- The Group class has the following attributes on Win32 systems:
119
+ === Group (Windows)
120
+ The Group class has the following attributes on MS Windows systems:
120
121
 
121
122
  * caption
122
123
  * description
@@ -129,46 +130,61 @@ Admin.users(host=localhost, local=true){ |user| ... }
129
130
  * local?
130
131
 
131
132
  === Group (Unix)
132
- The Group class has the following attributes on Unix systems:
133
+ The Group class has the following attributes on Unix systems:
133
134
 
134
- * name
135
- * gid
136
- * members
137
- * passwd
135
+ * name
136
+ * gid
137
+ * members
138
+ * passwd
138
139
 
139
140
  == Error Classes
140
141
  AdminError < StandardError
141
142
  Raised if anything goes wrong with any of the above methods.
142
143
 
143
144
  == Developer's Notes
144
- === Win32
145
- The Win32 version now uses a win32ole + WMI approach to getting
146
- information. This means that the WMI service must be running on the
147
- target machine in order to work (which it is, by default).
145
+ === MS Windows
146
+ The Windows version now uses a win32ole + WMI approach to getting
147
+ information. This means that the WMI service must be running on the
148
+ target machine in order to work (which it is, by default).
148
149
 
149
- Note that, by default, local user and group information is retrieved
150
- instead of global. You probably do NOT want to iterate over global users
151
- or groups because there can be quite a few on your domain.
150
+ Note that, by default, local user and group information is retrieved
151
+ instead of global. You probably do NOT want to iterate over global users
152
+ or groups because there can be quite a few on your domain.
153
+
154
+ === UNIX
155
+ The underlying implementation is similar to core Ruby's Etc implementation.
156
+ But, in addition to the different interface, I use the re-entrant version
157
+ of the appropriate functions when available.
152
158
 
153
159
  == Future Plans
154
- The following methods will be added for both platforms:
155
-
156
- * Admin.add_user
157
- * Admin.config_user
158
- * Admin.delete_user
160
+ Add the following methods for UNIX:
161
+
162
+ * Admin.add_local_user
163
+ * Admin.config_local_user
164
+ * Admin.delete_local_user
165
+ * Admin.add_global_user
166
+ * Admin.config_global_user
167
+ * Admin.delete_global_user
168
+
169
+ * Admin.add_local_group
170
+ * Admin.config_local_group
171
+ * Admin.delete_local_group
172
+ * Admin.add_global_group
173
+ * Admin.config_global_group
174
+ * Admin.delete_global_group
159
175
 
160
176
  == Known Bugs
161
- None that I'm aware of. If you find any, please log them on the project
177
+ None that I'm aware of. If you find any, please log them on the project
162
178
  page at http://www.rubyforge.org/projects/sysutils.
163
179
 
164
180
  == License
165
181
  Ruby's
166
182
 
167
183
  == Copyright
168
- (C) 2005, Daniel J. Berger
184
+ (C) 2005-2007, Daniel J. Berger
169
185
  All Rights Reserved
170
186
 
171
187
  == Author
172
188
  Daniel J. Berger
173
- djberg96@yahoo.com
174
- IRC nickname: imperator/mok/rubyhacker1
189
+ djberg96 at nospam at gmail dot com
190
+ IRC nickname: imperator/mok/rubyhacker1 (freenode)
@@ -0,0 +1,39 @@
1
+ ###########################################################################
2
+ # groups.rb
3
+ #
4
+ # Sample script to demonstrate some of the various group methods. Alter
5
+ # as you see fit.
6
+ ###########################################################################
7
+ base = File.basename(Dir.pwd)
8
+
9
+ if base == "examples" || base =~ /sys-admin.*/
10
+ require "ftools"
11
+ Dir.chdir("..") if base == "examples"
12
+ Dir.mkdir("sys") unless File.exists?("sys")
13
+ if RUBY_PLATFORM.match("mswin")
14
+ File.copy("lib/sys/admin.rb", "sys/admin.rb")
15
+ else
16
+ File.copy("admin.so","sys") if File.exists?("admin.so")
17
+ end
18
+ $LOAD_PATH.unshift(Dir.pwd)
19
+ end
20
+
21
+ require "pp"
22
+ require "sys/admin"
23
+ include Sys
24
+
25
+ if PLATFORM.match("mswin")
26
+ pp Admin.get_group("guests")
27
+ pp Admin.get_group(513)
28
+ else
29
+ pp Admin.get_group("adm")
30
+ pp Admin.get_group(7)
31
+ end
32
+
33
+ Admin.groups{ |g|
34
+ pp g
35
+ puts
36
+ }
37
+
38
+ # This should raise an error
39
+ Admin.get_group("fofofofof")
@@ -0,0 +1,53 @@
1
+ ###########################################################################
2
+ # users.rb
3
+ #
4
+ # Sample script to demonstrate some of the various user methods. Alter
5
+ # as you see fit.
6
+ ###########################################################################
7
+ base = File.basename(Dir.pwd)
8
+
9
+ if base == "examples" || base =~ /sys-admin.*/
10
+ require "ftools"
11
+ Dir.chdir("..") if base == "examples"
12
+ Dir.mkdir("sys") unless File.exists?("sys")
13
+ if RUBY_PLATFORM.match("mswin")
14
+ File.copy("lib/sys/admin.rb", "sys/admin.rb")
15
+ else
16
+ File.copy("admin.so","sys") if File.exists?("admin.so")
17
+ end
18
+ $LOAD_PATH.unshift(Dir.pwd)
19
+ end
20
+
21
+ require "pp"
22
+ require "sys/admin"
23
+ include Sys
24
+
25
+ user = User.new do |u|
26
+ u.name = "Foo"
27
+ u.description = "Test account"
28
+ u.password = "changeme"
29
+ #u.lockout = false
30
+ u.disabled = true
31
+ #u.password_required = true
32
+ end
33
+
34
+ Admin.delete_user(u.name) rescue nil
35
+ Admin.add_user(user)
36
+
37
+ #pp Admin.get_user("Foo")
38
+
39
+ #Admin.delete_user("Foo")
40
+
41
+ =begin
42
+ user = Admin.get_login
43
+
44
+ puts "User: #{user}"
45
+
46
+ Admin.users{ |u|
47
+ pp u
48
+ puts
49
+ }
50
+
51
+ pp Admin.get_user(user)
52
+ pp Admin.get_user(501)
53
+ =end
@@ -0,0 +1,22 @@
1
+ ##############################################
2
+ # install.rb
3
+ #
4
+ # Install script for Win32 systems only.
5
+ ##############################################
6
+ unless File::ALT_SEPARATOR
7
+ STDERR.puts "Unix systems should use the extconf.rb file for installation."
8
+ STDERR.puts "Exiting. The sys-admin package was NOT installed."
9
+ exit
10
+ end
11
+
12
+ require "rbconfig"
13
+ require "ftools"
14
+ include Config
15
+
16
+ sitedir = CONFIG['sitelibdir']
17
+
18
+ unless File.exist?("#{sitedir}/sys")
19
+ Dir.mkdir("#{sitedir}/sys")
20
+ end
21
+
22
+ File.copy("lib/sys/win32.rb", "#{sitedir}/sys/admin.rb", true)
@@ -1,403 +1,712 @@
1
- require "win32ole"
2
- require "Win32API"
3
- require "socket"
4
-
5
- module Sys
6
- class AdminError < StandardError; end
7
-
8
- class Group
9
- attr_accessor :caption, :description, :domain, :install_date
10
- attr_accessor :name, :sid, :status, :gid
11
- attr_writer :local
12
- def initialize
13
- yield self if block_given?
14
- end
15
-
16
- def local?
17
- @local
18
- end
19
-
20
- def sid_type
21
- @sid_type
22
- end
23
-
24
- def sid_type=(stype)
25
- case stype
26
- when 1
27
- @sid_type = "user"
28
- when 2
29
- @sid_type = "group"
30
- when 3
31
- @sid_type = "domain"
32
- when 4
33
- @sid_type = "alias"
34
- when 5
35
- @sid_type = "well_known_group"
36
- when 6
37
- @sid_type = "deleted_account"
38
- when 7
39
- @sid_type = "invalid"
40
- when 8
41
- @sid_type = "unknown"
42
- when 9
43
- @sid_type = "computer"
44
- else
45
- @sid_type = "unknown"
46
- end
47
- @sid_type
48
- end
49
- end
50
-
51
- class User
52
-
53
- attr_accessor :caption, :description, :domain, :password
54
- attr_accessor :full_name, :install_date, :name, :sid, :status
55
- attr_writer :disabled, :local, :lockout, :password_changeable
56
- attr_writer :password_expires, :password_required
57
- attr_reader :account_type
58
-
59
- def initialize
60
- yield self if block_given?
61
- end
62
-
63
- def account_type=(type)
64
- case type
65
- when 256
66
- @account_type = "duplicate"
67
- when 512
68
- @account_type = "normal"
69
- when 2048
70
- @account_type = "interdomain_trust"
71
- when 4096
72
- @account_type = "workstation_trust"
73
- when 8192
74
- @account_type = "server_trust"
75
- else
76
- @account_type = "unknown"
77
- end
78
- end
79
-
80
- def sid_type
81
- @sid_type
82
- end
83
-
84
- def sid_type=(stype)
85
- case stype
86
- when 1
87
- @sid_type = "user"
88
- when 2
89
- @sid_type = "group"
90
- when 3
91
- @sid_type = "domain"
92
- when 4
93
- @sid_type = "alias"
94
- when 5
95
- @sid_type = "well_known_group"
96
- when 6
97
- @sid_type = "deleted_account"
98
- when 7
99
- @sid_type = "invalid"
100
- when 8
101
- @sid_type = "unknown"
102
- when 9
103
- @sid_type = "computer"
104
- else
105
- @sid_type = "unknown"
106
- end
107
- end
108
-
109
- def disabled?
110
- @disabled
111
- end
112
-
113
- def local?
114
- @local
115
- end
116
-
117
- def lockout?
118
- @lockout
119
- end
120
-
121
- def password_changeable?
122
- @password_changeable
123
- end
124
-
125
- def password_expires?
126
- @password_expires
127
- end
128
-
129
- def password_required?
130
- @password_required
131
- end
132
- end
133
-
134
- class Admin
135
- VERSION = "1.3.1"
136
-
137
- # Deletes +userid+ from the given +host+, or the local host if no host
138
- # is specified.
139
- #
140
- def self.delete_user(userid=nil, host=Socket.gethostname)
141
- begin
142
- adsi = WIN32OLE.connect("WinNT://#{host},Computer")
143
- rescue WIN32OLERuntimeError => err
144
- raise AdminError, err
145
- end
146
-
147
- begin
148
- adsi.delete("user", userid)
149
- rescue WIN32OLERuntimeError => err
150
- raise AdminError, err
151
- end
152
- end
153
-
154
- # Returns the user name (only) of the current login.
155
- #
156
- def self.get_login
157
- getlogin = Win32API.new("advapi32","GetUserName",['P','P'],'L')
158
- buffer = "\0" * 256;
159
- nsize = [256].pack("L")
160
- getlogin.call(buffer,nsize)
161
- len = nsize.unpack("L")[0]
162
- username = buffer[0 ... len].chop
163
- username
164
- end
165
-
166
- # Returns a User object based on either +name+ or +uid+.
167
- #
168
- # call-seq:
169
- # get_user(name, host=localhost)
170
- # get_user(uid, host=localhost, local=true)
171
- #
172
- # You may specify a +host+ from which information is retrieved. The
173
- # default is the local machine. You may also specify whether to
174
- # retrieve a local or global account. The default is local.
175
- #
176
- def self.get_user(uid, host=Socket.gethostname, local=true)
177
- host = Socket.gethostname if host.nil?
178
- cs = "winmgmts:{impersonationLevel=impersonate}!"
179
- cs << "//#{host}/root/cimv2"
180
-
181
- begin
182
- wmi = WIN32OLE.connect(cs)
183
- rescue WIN32OLERuntimeError => e
184
- raise AdminError, e
185
- end
186
-
187
- query = "select * from win32_useraccount"
188
- query << " where localaccount = true" if local
189
-
190
- if uid.kind_of?(Fixnum)
191
- if local
192
- query << " and sid like '%-#{uid}'"
193
- else
194
- query << " where sid like '%-#{uid}'"
195
- end
196
- else
197
- if local
198
- query << " and name = '#{uid}'"
199
- else
200
- query << " where name = '#{uid}'"
201
- end
202
- end
203
-
204
- wmi.execquery(query).each{ |user|
205
- # Because our 'like' query isn't fulproof, let's parse
206
- # the SID again to make sure
207
- if uid.kind_of?(Fixnum)
208
- if user.sid.split("-").last.to_i != uid
209
- next
210
- end
211
- end
212
- usr = User.new do |u|
213
- u.account_type = user.accounttype
214
- u.caption = user.caption
215
- u.description = user.description
216
- u.disabled = user.disabled
217
- u.domain = user.domain
218
- u.full_name = user.fullname
219
- u.install_date = user.installdate
220
- u.local = user.localaccount
221
- u.lockout = user.lockout
222
- u.name = user.name
223
- u.password_changeable = user.passwordchangeable
224
- u.password_expires = user.passwordexpires
225
- u.password_required = user.passwordrequired
226
- u.sid = user.sid
227
- u.sid_type = user.sidtype
228
- u.status = user.status
229
- end
230
- return usr
231
- }
232
- end
233
-
234
- # In block form, yields a User object for each user on the system. In
235
- # non-block form, returns an Array of User objects.
236
- #
237
- # call-seq:
238
- # users(host=localhost, local=true)
239
- # users(host=localhost, local=true){ |user| ... }
240
- #
241
- # You may specify a host from which information is retrieved. The
242
- # default is the local machine. You can retrieve either a global or
243
- # group, depending on the value of the +local+ argument.
244
- #
245
- def self.users(host=Socket.gethostname, local=true)
246
- host = Socket.gethostname if host.nil?
247
- cs = "winmgmts:{impersonationLevel=impersonate}!"
248
- cs << "//#{host}/root/cimv2"
249
-
250
- begin
251
- wmi = WIN32OLE.connect(cs)
252
- rescue WIN32OLERuntimeError => e
253
- raise AdminError, e
254
- end
255
-
256
- query = "select * from win32_useraccount"
257
- query << " where localaccount = true" if local
258
- array = []
259
-
260
- wmi.execquery(query).each{ |user|
261
- usr = User.new do |u|
262
- u.account_type = user.accounttype
263
- u.caption = user.caption
264
- u.description = user.description
265
- u.disabled = user.disabled
266
- u.domain = user.domain
267
- u.full_name = user.fullname
268
- u.install_date = user.installdate
269
- u.local = user.localaccount
270
- u.lockout = user.lockout
271
- u.name = user.name
272
- u.password_changeable = user.passwordchangeable
273
- u.password_expires = user.passwordexpires
274
- u.password_required = user.passwordrequired
275
- u.sid = user.sid
276
- u.sid_type = user.sidtype
277
- u.status = user.status
278
- end
279
-
280
- if block_given?
281
- yield usr
282
- else
283
- array.push(usr)
284
- end
285
- }
286
- return array unless block_given?
287
- end
288
-
289
- # Returns a Group object based on either +name+ or +uid+.
290
- #
291
- # call-seq:
292
- # get_group(name, host=localhost, local=true)
293
- # get_group(gid, host=localhost, local=true)
294
- #
295
- # You may specify a host from which information is retrieved.
296
- # The default is the local machine. You can retrieve either a global or
297
- # local group, depending on the value of the +local+ argument.
298
- #
299
- def self.get_group(grp, host=Socket.gethostname, local=true)
300
- host = Socket.gethostname if host.nil?
301
- cs = "winmgmts:{impersonationLevel=impersonate}!"
302
- cs << "//#{host}/root/cimv2"
303
- gid = nil
304
-
305
- begin
306
- wmi = WIN32OLE.connect(cs)
307
- rescue WIN32OLERuntimeError => e
308
- raise AdminError, e
309
- end
310
-
311
- query = "select * from win32_group"
312
- query << " where localaccount = true" if local
313
-
314
- if grp.kind_of?(Fixnum)
315
- if local
316
- query << " and sid like '%-#{grp}'"
317
- else
318
- query << " where sid like '%-#{grp}'"
319
- end
320
- else
321
- if local
322
- query << " and name = '#{grp}'"
323
- else
324
- query << " where name = '#{grp}'"
325
- end
326
- end
327
-
328
- wmi.execquery(query).each{ |group|
329
- gid = group.sid.split("-").last.to_i
330
-
331
- # Because our 'like' query isn't fulproof, let's parse
332
- # the SID again to make sure
333
- if grp.kind_of?(Fixnum)
334
- next if grp != gid
335
- end
336
-
337
- grp = Group.new do |g|
338
- g.caption = group.caption
339
- g.description = group.description
340
- g.domain = group.domain
341
- g.gid = gid
342
- g.install_date = group.installdate
343
- g.local = group.localaccount
344
- g.name = group.name
345
- g.sid = group.sid
346
- g.sid_type = group.sidtype
347
- g.status = group.status
348
- end
349
- return grp
350
- }
351
- # If we're here, it means it wasn't found.
352
- raise AdminError, "no group found for '#{grp}'"
353
- end
354
-
355
- # In block form, yields a Group object for each user on the system. In
356
- # non-block form, returns an Array of Group objects.
357
- #
358
- # call-seq:
359
- # groups(host=localhost, local=true)
360
- # groups(host=localhost, local=true){ |group| ... }
361
- #
362
- # You may specify a host from which information is retrieved.
363
- # The default is the local machine. You can retrieve either a global or
364
- # local group, depending on the value of the +local+ argument.
365
- #
366
- def self.groups(host=Socket.gethostname, local=true)
367
- host = Socket.gethostname if host.nil?
368
- cs = "winmgmts:{impersonationLevel=impersonate}!"
369
- cs << "//#{host}/root/cimv2"
370
-
371
- begin
372
- wmi = WIN32OLE.connect(cs)
373
- rescue WIN32OLERuntimeError => e
374
- raise AdminError, e
375
- end
376
-
377
- query = "select * from win32_group"
378
- query << " where localaccount = true" if local
379
-
380
- array = []
381
- wmi.execquery(query).each{ |group|
382
- grp = Group.new do |g|
383
- g.caption = group.caption
384
- g.description = group.description
385
- g.domain = group.domain
386
- g.gid = group.sid.split("-").last.to_i
387
- g.install_date = group.installdate
388
- g.local = group.localaccount
389
- g.name = group.name
390
- g.sid = group.sid
391
- g.sid_type = group.sidtype
392
- g.status = group.status
393
- end
394
- if block_given?
395
- yield grp
396
- else
397
- array.push(grp)
398
- end
399
- }
400
- return array unless block_given?
401
- end
402
- end
403
- end
1
+ require "win32ole"
2
+ require "Win32API"
3
+ require "socket"
4
+
5
+ module Sys
6
+ # This is the error raised in the majority of cases if anything goes wrong
7
+ # with any of the Sys::Admin methods.
8
+ #
9
+ class AdminError < StandardError; end
10
+
11
+ class Group
12
+ # Short description of the object.
13
+ attr_accessor :caption
14
+
15
+ # Description of the group.
16
+ attr_accessor :description
17
+
18
+ # Name of the Windows domain to which the group account belongs.
19
+ attr_accessor :domain
20
+
21
+ # Date the group was added.
22
+ attr_accessor :install_date
23
+
24
+ # Name of the Windows group account on the Group#domain specified.
25
+ attr_accessor :name
26
+
27
+ # Security identifier for this group.
28
+ attr_accessor :sid
29
+
30
+ # Current status for the group, such as "ok", "error", etc.
31
+ attr_accessor :status
32
+
33
+ # The group ID.
34
+ attr_accessor :gid
35
+
36
+ # Sets whether or not the group is local (as opposed to global).
37
+ attr_writer :local
38
+
39
+ # Creates and returns a new Group object. This class encapsulates
40
+ # the information for a group account, whether it be global or local.
41
+ #
42
+ # Yields +self+ if a block is given.
43
+ #
44
+ def initialize
45
+ yield self if block_given?
46
+ end
47
+
48
+ # Returns whether or not the group is a local group.
49
+ #
50
+ def local?
51
+ @local
52
+ end
53
+
54
+ # Returns the type of SID (Security Identifier) as a stringified value.
55
+ #
56
+ def sid_type
57
+ @sid_type
58
+ end
59
+
60
+ # Sets the SID (Security Identifier) type to +stype+, which can be
61
+ # one of the following constant values:
62
+ #
63
+ # * Admin::SidTypeUser
64
+ # * Admin::SidTypeGroup
65
+ # * Admin::SidTypeDomain
66
+ # * Admin::SidTypeAlias
67
+ # * Admin::SidTypeWellKnownGroup
68
+ # * Admin::SidTypeDeletedAccount
69
+ # * Admin::SidTypeInvalid
70
+ # * Admin::SidTypeUnknown
71
+ # * Admin::SidTypeComputer
72
+ #
73
+ def sid_type=(stype)
74
+ if stype.kind_of?(String)
75
+ @sid_type = stype.downcase
76
+ else
77
+ case stype
78
+ when Admin::SidTypeUser
79
+ @sid_type = "user"
80
+ when Admin::SidTypeGroup
81
+ @sid_type = "group"
82
+ when Admin::SidTypeDomain
83
+ @sid_type = "domain"
84
+ when Admin::SidTypeAlias
85
+ @sid_type = "alias"
86
+ when Admin::SidTypeWellKnownGroup
87
+ @sid_type = "well_known_group"
88
+ when Admin::SidTypeDeletedAccount
89
+ @sid_type = "deleted_account"
90
+ when Admin::SidTypeInvalid
91
+ @sid_type = "invalid"
92
+ when Admin::SidTypeUnknown
93
+ @sid_type = "unknown"
94
+ when Admin::SidTypeComputer
95
+ @sid_type = "computer"
96
+ else
97
+ @sid_type = "unknown"
98
+ end
99
+ end
100
+ @sid_type
101
+ end
102
+ end
103
+
104
+ class User
105
+ # An account for users whose primary account is in another domain.
106
+ TEMP_DUPLICATE = 0x0100
107
+
108
+ # Default account type that represents a typical user.
109
+ NORMAL = 0x0200
110
+
111
+ # A permit to trust account for a domain that trusts other domains.
112
+ INTERDOMAIN_TRUST = 0x0800
113
+
114
+ # An account for a Windows NT/2000 workstation or server that is a
115
+ # member of this domain.
116
+ WORKSTATION_TRUST = 0x1000
117
+
118
+ # A computer account for a backup domain controller that is a member
119
+ # of this domain.
120
+ SERVER_TRUST = 0x2000
121
+
122
+ # Domain and username of the account.
123
+ attr_accessor :caption
124
+
125
+ # Description of the account.
126
+ attr_accessor :description
127
+
128
+ # Name of the Windows domain to which a user account belongs.
129
+ attr_accessor :domain
130
+
131
+ # The user's password.
132
+ attr_accessor :password
133
+
134
+ # Full name of a local user.
135
+ attr_accessor :full_name
136
+
137
+ # Date the user account was created.
138
+ attr_accessor :install_date
139
+
140
+ # Name of the Windows user account on the domain that the User#domain
141
+ # property specifies.
142
+ attr_accessor :name
143
+
144
+ # The user's security identifier.
145
+ attr_accessor :sid
146
+
147
+ # Current status for the group, such as "ok", "error", etc.
148
+ attr_accessor :status
149
+
150
+ # Used to set whether or not the account is disabled.
151
+ attr_writer :disabled
152
+
153
+ # Sets whether or not the account is defined on the local computer.
154
+ attr_writer :local
155
+
156
+ # Sets whether or not the account is locked out of the OS.
157
+ attr_writer :lockout
158
+
159
+ # Sets whether or not the password for the account can be changed.
160
+ attr_writer :password_changeable
161
+
162
+ # Sets whether or not the password for the account expires.
163
+ attr_writer :password_expires
164
+
165
+ # Sets whether or not a password is required for the account.
166
+ attr_writer :password_required
167
+
168
+ # Returns the account type as a human readable string.
169
+ attr_reader :account_type
170
+
171
+ # Creates an returns a new User object. A User object encapsulates a
172
+ # user account on the operating system.
173
+ #
174
+ # Yields +self+ if a block is provided.
175
+ #
176
+ def initialize
177
+ yield self if block_given?
178
+ end
179
+
180
+ # Sets the account type for the account. Possible values are:
181
+ #
182
+ # * User::TEMP_DUPLICATE
183
+ # * User::NORMAL
184
+ # * User::INTERDOMAIN_TRUST
185
+ # * User::WORKSTATION_TRUST
186
+ # * User::SERVER_TRUST
187
+ #
188
+ def account_type=(type)
189
+ case type
190
+ when TEMP_DUPLICATE
191
+ @account_type = "duplicate"
192
+ when NORMAL
193
+ @account_type = "normal"
194
+ when INTERDOMAIN_TRUST
195
+ @account_type = "interdomain_trust"
196
+ when WORKSTATION_TRUST
197
+ @account_type = "workstation_trust"
198
+ when SERVER_TRUST
199
+ @account_type = "server_trust"
200
+ else
201
+ @account_type = "unknown"
202
+ end
203
+ end
204
+
205
+ # Returns the SID type as a human readable string.
206
+ #
207
+ def sid_type
208
+ @sid_type
209
+ end
210
+
211
+ # Sets the SID (Security Identifier) type to +stype+, which can be
212
+ # one of the following constant values:
213
+ #
214
+ # * Admin::SidTypeUser
215
+ # * Admin::SidTypeGroup
216
+ # * Admin::SidTypeDomain
217
+ # * Admin::SidTypeAlias
218
+ # * Admin::SidTypeWellKnownGroup
219
+ # * Admin::SidTypeDeletedAccount
220
+ # * Admin::SidTypeInvalid
221
+ # * Admin::SidTypeUnknown
222
+ # * Admin::SidTypeComputer
223
+ #
224
+ def sid_type=(stype)
225
+ case stype
226
+ when Admin::SidTypeUser
227
+ @sid_type = "user"
228
+ when Admin::SidTypeGroup
229
+ @sid_type = "group"
230
+ when Admin::SidTypeDomain
231
+ @sid_type = "domain"
232
+ when Admin::SidTypeAlias
233
+ @sid_type = "alias"
234
+ when Admin::SidTypeWellKnownGroup
235
+ @sid_type = "well_known_group"
236
+ when Admin::SidTypeDeletedAccount
237
+ @sid_type = "deleted_account"
238
+ when Admin::SidTypeInvalid
239
+ @sid_type = "invalid"
240
+ when Admin::SidTypeUnknown
241
+ @sid_type = "unknown"
242
+ when Admin::SidTypeComputer
243
+ @sid_type = "computer"
244
+ else
245
+ @sid_type = "unknown"
246
+ end
247
+ end
248
+
249
+ # Returns whether or not the account is disabled.
250
+ #
251
+ def disabled?
252
+ @disabled
253
+ end
254
+
255
+ # Returns whether or not the account is local.
256
+ #
257
+ def local?
258
+ @local
259
+ end
260
+
261
+ # Returns whether or not the account is locked out.
262
+ #
263
+ def lockout?
264
+ @lockout
265
+ end
266
+
267
+ # Returns whether or not the password for the account is changeable.
268
+ #
269
+ def password_changeable?
270
+ @password_changeable
271
+ end
272
+
273
+ # Returns whether or not the password for the account is changeable.
274
+ #
275
+ def password_expires?
276
+ @password_expires
277
+ end
278
+
279
+ # Returns whether or not the a password is required for the account.
280
+ #
281
+ def password_required?
282
+ @password_required
283
+ end
284
+ end
285
+
286
+ class Admin
287
+ VERSION = '1.4.0'
288
+
289
+ SidTypeUser = 1
290
+ SidTypeGroup = 2
291
+ SidTypeDomain = 3
292
+ SidTypeAlias = 4
293
+ SidTypeWellKnownGroup = 5
294
+ SidTypeDeletedAccount = 6
295
+ SidTypeInvalid = 7
296
+ SidTypeUnknown = 8
297
+ SidTypeComputer = 9
298
+
299
+ # Used by the get_login method
300
+ GetUserName = Win32API.new('advapi32', 'GetUserName', 'PP', 'L') # :nodoc:
301
+
302
+ # Configures the global +user+ on +domain+ using options.
303
+ #
304
+ # See http://tinyurl.com/3hjv9 for a list of valid options.
305
+ #
306
+ def self.config_global_user(user, options, domain)
307
+ begin
308
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{user},user")
309
+ options.each{ |option, value|
310
+ adsi.put(option.to_s, value)
311
+ }
312
+ adsi.setinfo
313
+ rescue WIN32OLERuntimeError => err
314
+ raise AdminError, err
315
+ end
316
+ end
317
+
318
+ # Configures the local +user+ on +host+ using +options+. If no host
319
+ # is specified, the default is localhost.
320
+ #
321
+ # See http://tinyurl.com/3hjv9 for a list of valid options.
322
+ #
323
+ def self.config_local_user(user, options, host=Socket.gethostname)
324
+ begin
325
+ adsi = WIN32OLE.connect("WinNT://#{host}/#{user},user")
326
+ options.each{ |option, value|
327
+ adsi.put(option.to_s, value)
328
+ }
329
+ adsi.setinfo
330
+ rescue WIN32OLERuntimeError => err
331
+ raise AdminError, err
332
+ end
333
+ end
334
+
335
+ # Adds the global +user+ on +domain+
336
+ #
337
+ def self.add_global_user(user, domain)
338
+ begin
339
+ adsi = WIN32OLE.connect("WinNT://#{host},Computer")
340
+ user = adsi.create("user", user)
341
+ user.setinfo
342
+ rescue WIN32OLERuntimeError => err
343
+ raise AdminError, err
344
+ end
345
+ end
346
+
347
+ # Adds the local +user+ on +host+, or the localhost if none is specified.
348
+ #
349
+ def self.add_local_user(user, host=Socket.gethostname)
350
+ begin
351
+ adsi = WIN32OLE.connect("WinNT://#{host},Computer")
352
+ user = adsi.create("user", user)
353
+ user.setinfo
354
+ rescue WIN32OLERuntimeError => err
355
+ raise AdminError, err
356
+ end
357
+ end
358
+
359
+ # Deletes the local +user+ from +host+, or localhost if no host specified.
360
+ #
361
+ def self.delete_local_user(user, host=Socket.gethostname)
362
+ begin
363
+ adsi = WIN32OLE.connect("WinNT://#{host},Computer")
364
+ adsi.delete("user", user)
365
+ rescue WIN32OLERuntimeError => err
366
+ raise AdminError, err
367
+ end
368
+ end
369
+
370
+ # Deletes the global +user+ from +domain+.
371
+ #
372
+ def self.delete_global_user(user, domain)
373
+ begin
374
+ adsi = WIN32OLE.connect("WinNT://#{domain}")
375
+ adsi.delete("user", user)
376
+ rescue WIN32OLERuntimeError => err
377
+ raise AdminError, err
378
+ end
379
+ end
380
+
381
+ # Configures the global +group+ on +domain+ using +options+.
382
+ #
383
+ # See http://tinyurl.com/cjkzl for a list of valid options.
384
+ #
385
+ def self.config_global_group(group, options, domain)
386
+ begin
387
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{group},group")
388
+ options.each{ |option, value|
389
+ adsi.put(option.to_s, value)
390
+ }
391
+ adsi.setinfo
392
+ rescue WIN32OLERuntimeError => err
393
+ raise AdminError, err
394
+ end
395
+ end
396
+
397
+ # Configures the local +group+ on +host+ using +options+. If no host
398
+ # is specified, the default is localhost.
399
+ #
400
+ # See http://tinyurl.com/cjkzl for a list of valid options.
401
+ #
402
+ def self.config_local_group(group, options, host=Socket.gethostname)
403
+ begin
404
+ adsi = WIN32OLE.connect("WinNT://#{host}/#{group},group")
405
+ options.each{ |option, value|
406
+ adsi.put(option.to_s, value)
407
+ }
408
+ adsi.setinfo
409
+ rescue WIN32OLERuntimeError => err
410
+ raise AdminError, err
411
+ end
412
+ end
413
+
414
+ # Add global +group+ to +domain+.
415
+ #
416
+ def self.add_global_group(group, domain)
417
+ begin
418
+ adsi = WIN32OLE.connect("WinNT://#{domain},Computer")
419
+ obj = adsi.create("group", group)
420
+ obj.setinfo
421
+ rescue WIN32OLERuntimeError => err
422
+ raise AdminError, err
423
+ end
424
+ end
425
+
426
+ # Add local +group+ to +host+, or the localhost if no host is specified.
427
+ #
428
+ def self.add_local_group(group, host=Socket.gethostname)
429
+ begin
430
+ adsi = WIN32OLE.connect("WinNT://#{host},Computer")
431
+ obj = adsi.create("group", group)
432
+ obj.setinfo
433
+ rescue WIN32OLERuntimeError => err
434
+ raise AdminError, err
435
+ end
436
+ end
437
+
438
+ # Delete the global +group+ from +domain+.
439
+ #
440
+ def self.delete_global_group(groupid, domain)
441
+ begin
442
+ adsi = WIN32OLE.connect("WinNT://#{domain},Computer")
443
+ obj = adsi.delete("group", groupid)
444
+ rescue WIN32OLERuntimeError => err
445
+ raise AdminError, err
446
+ end
447
+ end
448
+
449
+ # Delete the local +group+ from +host+, or localhost if no host specified.
450
+ #
451
+ def self.delete_local_group(groupid, host=Socket.gethostname)
452
+ begin
453
+ adsi = WIN32OLE.connect("WinNT://#{host},Computer")
454
+ obj = adsi.delete("group", groupid)
455
+ rescue WIN32OLERuntimeError => err
456
+ raise AdminError, err
457
+ end
458
+ end
459
+
460
+ # Returns the user name (only) of the current login.
461
+ #
462
+ def self.get_login
463
+ buffer = 0.chr * 256
464
+ nsize = [buffer.size].pack("L")
465
+
466
+ if GetUserName.call(buffer, nsize) == 0
467
+ raise AdminError, 'GetUserName() call failed in get_login'
468
+ end
469
+
470
+ length = nsize.unpack("L")[0]
471
+ username = buffer[0 ... length].chop
472
+ username
473
+ end
474
+
475
+ # Returns a User object based on either +name+ or +uid+.
476
+ #
477
+ # call-seq:
478
+ # get_user(name, host=localhost)
479
+ # get_user(uid, host=localhost, local=true)
480
+ #
481
+ # You may specify a +host+ from which information is retrieved. The
482
+ # default is the local machine. You may also specify whether to
483
+ # retrieve a local or global account. The default is local.
484
+ #
485
+ def self.get_user(uid, host=Socket.gethostname, local=true)
486
+ host = Socket.gethostname if host.nil?
487
+ cs = "winmgmts:{impersonationLevel=impersonate}!"
488
+ cs << "//#{host}/root/cimv2"
489
+
490
+ begin
491
+ wmi = WIN32OLE.connect(cs)
492
+ rescue WIN32OLERuntimeError => e
493
+ raise AdminError, e
494
+ end
495
+
496
+ query = "select * from win32_useraccount"
497
+ query << " where localaccount = true" if local
498
+
499
+ if uid.kind_of?(Fixnum)
500
+ if local
501
+ query << " and sid like '%-#{uid}'"
502
+ else
503
+ query << " where sid like '%-#{uid}'"
504
+ end
505
+ else
506
+ if local
507
+ query << " and name = '#{uid}'"
508
+ else
509
+ query << " where name = '#{uid}'"
510
+ end
511
+ end
512
+
513
+ wmi.execquery(query).each{ |user|
514
+ # Because our 'like' query isn't fulproof, let's parse
515
+ # the SID again to make sure
516
+ if uid.kind_of?(Fixnum)
517
+ if user.sid.split("-").last.to_i != uid
518
+ next
519
+ end
520
+ end
521
+ usr = User.new do |u|
522
+ u.account_type = user.accounttype
523
+ u.caption = user.caption
524
+ u.description = user.description
525
+ u.disabled = user.disabled
526
+ u.domain = user.domain
527
+ u.full_name = user.fullname
528
+ u.install_date = user.installdate
529
+ u.local = user.localaccount
530
+ u.lockout = user.lockout
531
+ u.name = user.name
532
+ u.password_changeable = user.passwordchangeable
533
+ u.password_expires = user.passwordexpires
534
+ u.password_required = user.passwordrequired
535
+ u.sid = user.sid
536
+ u.sid_type = user.sidtype
537
+ u.status = user.status
538
+ end
539
+ return usr
540
+ }
541
+ end
542
+
543
+ # In block form, yields a User object for each user on the system. In
544
+ # non-block form, returns an Array of User objects.
545
+ #
546
+ # call-seq:
547
+ # users(host=localhost, local=true)
548
+ # users(host=localhost, local=true){ |user| ... }
549
+ #
550
+ # You may specify a host from which information is retrieved. The
551
+ # default is the local machine. You can retrieve either a global or
552
+ # local group, depending on the value of the +local+ argument.
553
+ #
554
+ def self.users(host=Socket.gethostname, local=true)
555
+ host = Socket.gethostname if host.nil?
556
+ cs = "winmgmts:{impersonationLevel=impersonate}!"
557
+ cs << "//#{host}/root/cimv2"
558
+
559
+ begin
560
+ wmi = WIN32OLE.connect(cs)
561
+ rescue WIN32OLERuntimeError => e
562
+ raise AdminError, e
563
+ end
564
+
565
+ query = "select * from win32_useraccount"
566
+ query << " where localaccount = true" if local
567
+ array = []
568
+
569
+ wmi.execquery(query).each{ |user|
570
+ usr = User.new do |u|
571
+ u.account_type = user.accounttype
572
+ u.caption = user.caption
573
+ u.description = user.description
574
+ u.disabled = user.disabled
575
+ u.domain = user.domain
576
+ u.full_name = user.fullname
577
+ u.install_date = user.installdate
578
+ u.local = user.localaccount
579
+ u.lockout = user.lockout
580
+ u.name = user.name
581
+ u.password_changeable = user.passwordchangeable
582
+ u.password_expires = user.passwordexpires
583
+ u.password_required = user.passwordrequired
584
+ u.sid = user.sid
585
+ u.sid_type = user.sidtype
586
+ u.status = user.status
587
+ end
588
+
589
+ if block_given?
590
+ yield usr
591
+ else
592
+ array.push(usr)
593
+ end
594
+ }
595
+ return array unless block_given?
596
+ end
597
+
598
+ # Returns a Group object based on either +name+ or +uid+.
599
+ #
600
+ # call-seq:
601
+ # get_group(name, host=localhost, local=true)
602
+ # get_group(gid, host=localhost, local=true)
603
+ #
604
+ # You may specify a host from which information is retrieved.
605
+ # The default is the local machine. You can retrieve either a global or
606
+ # local group, depending on the value of the +local+ argument.
607
+ #
608
+ def self.get_group(grp, host=Socket.gethostname, local=true)
609
+ host = Socket.gethostname if host.nil?
610
+ cs = "winmgmts:{impersonationLevel=impersonate}!"
611
+ cs << "//#{host}/root/cimv2"
612
+ gid = nil
613
+
614
+ begin
615
+ wmi = WIN32OLE.connect(cs)
616
+ rescue WIN32OLERuntimeError => e
617
+ raise AdminError, e
618
+ end
619
+
620
+ query = "select * from win32_group"
621
+ query << " where localaccount = true" if local
622
+
623
+ if grp.kind_of?(Fixnum)
624
+ if local
625
+ query << " and sid like '%-#{grp}'"
626
+ else
627
+ query << " where sid like '%-#{grp}'"
628
+ end
629
+ else
630
+ if local
631
+ query << " and name = '#{grp}'"
632
+ else
633
+ query << " where name = '#{grp}'"
634
+ end
635
+ end
636
+
637
+ wmi.execquery(query).each{ |group|
638
+ gid = group.sid.split("-").last.to_i
639
+
640
+ # Because our 'like' query isn't fulproof, let's parse
641
+ # the SID again to make sure
642
+ if grp.kind_of?(Fixnum)
643
+ next if grp != gid
644
+ end
645
+
646
+ grp = Group.new do |g|
647
+ g.caption = group.caption
648
+ g.description = group.description
649
+ g.domain = group.domain
650
+ g.gid = gid
651
+ g.install_date = group.installdate
652
+ g.local = group.localaccount
653
+ g.name = group.name
654
+ g.sid = group.sid
655
+ g.sid_type = group.sidtype
656
+ g.status = group.status
657
+ end
658
+ return grp
659
+ }
660
+ # If we're here, it means it wasn't found.
661
+ raise AdminError, "no group found for '#{grp}'"
662
+ end
663
+
664
+ # In block form, yields a Group object for each user on the system. In
665
+ # non-block form, returns an Array of Group objects.
666
+ #
667
+ # call-seq:
668
+ # groups(host=localhost, local=true)
669
+ # groups(host=localhost, local=true){ |group| ... }
670
+ #
671
+ # You may specify a host from which information is retrieved.
672
+ # The default is the local machine. You can retrieve either a global or
673
+ # local group, depending on the value of the +local+ argument.
674
+ #
675
+ def self.groups(host=Socket.gethostname, local=true)
676
+ host = Socket.gethostname if host.nil?
677
+ cs = "winmgmts:{impersonationLevel=impersonate}!"
678
+ cs << "//#{host}/root/cimv2"
679
+
680
+ begin
681
+ wmi = WIN32OLE.connect(cs)
682
+ rescue WIN32OLERuntimeError => e
683
+ raise AdminError, e
684
+ end
685
+
686
+ query = "select * from win32_group"
687
+ query << " where localaccount = true" if local
688
+
689
+ array = []
690
+ wmi.execquery(query).each{ |group|
691
+ grp = Group.new do |g|
692
+ g.caption = group.caption
693
+ g.description = group.description
694
+ g.domain = group.domain
695
+ g.gid = group.sid.split("-").last.to_i
696
+ g.install_date = group.installdate
697
+ g.local = group.localaccount
698
+ g.name = group.name
699
+ g.sid = group.sid
700
+ g.sid_type = group.sidtype
701
+ g.status = group.status
702
+ end
703
+ if block_given?
704
+ yield grp
705
+ else
706
+ array.push(grp)
707
+ end
708
+ }
709
+ return array unless block_given?
710
+ end
711
+ end
712
+ end