sys-admin 1.3.1-mswin32 → 1.4.0-mswin32

Sign up to get free protection for your applications and to get access to all the features.
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