sys-admin 1.5.3-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
data/lib/sys/admin.rb ADDED
@@ -0,0 +1,980 @@
1
+ require 'win32ole'
2
+ require 'win32/security'
3
+ require 'win32/registry'
4
+ require 'socket'
5
+
6
+ module Sys
7
+ class Group
8
+ # Short description of the object.
9
+ attr_accessor :caption
10
+
11
+ # Description of the group.
12
+ attr_accessor :description
13
+
14
+ # Name of the Windows domain to which the group account belongs.
15
+ attr_accessor :domain
16
+
17
+ # Date the group was added.
18
+ attr_accessor :install_date
19
+
20
+ # Name of the Windows group account on the Group#domain specified.
21
+ attr_accessor :name
22
+
23
+ # Security identifier for this group.
24
+ attr_accessor :sid
25
+
26
+ # Current status for the group, such as "ok", "error", etc.
27
+ attr_accessor :status
28
+
29
+ # The group ID.
30
+ attr_accessor :gid
31
+
32
+ # Sets whether or not the group is local (as opposed to global).
33
+ attr_writer :local
34
+
35
+ # An array of members for that group. May contain SID's.
36
+ attr_accessor :members
37
+
38
+ # Creates and returns a new Group object. This class encapsulates
39
+ # the information for a group account, whether it be global or local.
40
+ #
41
+ # Yields +self+ if a block is given.
42
+ #
43
+ def initialize
44
+ yield self if block_given?
45
+ end
46
+
47
+ # Returns whether or not the group is a local group.
48
+ #
49
+ def local?
50
+ @local
51
+ end
52
+
53
+ # Returns the type of SID (Security Identifier) as a stringified value.
54
+ #
55
+ def sid_type
56
+ @sid_type
57
+ end
58
+
59
+ # Sets the SID (Security Identifier) type to +stype+, which can be
60
+ # one of the following constant values:
61
+ #
62
+ # * Admin::SidTypeUser
63
+ # * Admin::SidTypeGroup
64
+ # * Admin::SidTypeDomain
65
+ # * Admin::SidTypeAlias
66
+ # * Admin::SidTypeWellKnownGroup
67
+ # * Admin::SidTypeDeletedAccount
68
+ # * Admin::SidTypeInvalid
69
+ # * Admin::SidTypeUnknown
70
+ # * Admin::SidTypeComputer
71
+ #
72
+ def sid_type=(stype)
73
+ if stype.kind_of?(String)
74
+ @sid_type = stype.downcase
75
+ else
76
+ case stype
77
+ when Admin::SidTypeUser
78
+ @sid_type = "user"
79
+ when Admin::SidTypeGroup
80
+ @sid_type = "group"
81
+ when Admin::SidTypeDomain
82
+ @sid_type = "domain"
83
+ when Admin::SidTypeAlias
84
+ @sid_type = "alias"
85
+ when Admin::SidTypeWellKnownGroup
86
+ @sid_type = "well_known_group"
87
+ when Admin::SidTypeDeletedAccount
88
+ @sid_type = "deleted_account"
89
+ when Admin::SidTypeInvalid
90
+ @sid_type = "invalid"
91
+ when Admin::SidTypeUnknown
92
+ @sid_type = "unknown"
93
+ when Admin::SidTypeComputer
94
+ @sid_type = "computer"
95
+ else
96
+ @sid_type = "unknown"
97
+ end
98
+ end
99
+ @sid_type
100
+ end
101
+ end
102
+
103
+ class User
104
+ # An account for users whose primary account is in another domain.
105
+ TEMP_DUPLICATE = 0x0100
106
+
107
+ # Default account type that represents a typical user.
108
+ NORMAL = 0x0200
109
+
110
+ # A permit to trust account for a domain that trusts other domains.
111
+ INTERDOMAIN_TRUST = 0x0800
112
+
113
+ # An account for a Windows NT/2000 workstation or server that is a
114
+ # member of this domain.
115
+ WORKSTATION_TRUST = 0x1000
116
+
117
+ # A computer account for a backup domain controller that is a member
118
+ # of this domain.
119
+ SERVER_TRUST = 0x2000
120
+
121
+ # Domain and username of the account.
122
+ attr_accessor :caption
123
+
124
+ # Description of the account.
125
+ attr_accessor :description
126
+
127
+ # Name of the Windows domain to which a user account belongs.
128
+ attr_accessor :domain
129
+
130
+ # The user's password.
131
+ attr_accessor :password
132
+
133
+ # Full name of a local user.
134
+ attr_accessor :full_name
135
+
136
+ # An array of groups to which the user belongs.
137
+ attr_accessor :groups
138
+
139
+ # Date the user account was created.
140
+ attr_accessor :install_date
141
+
142
+ # Name of the Windows user account on the domain that the User#domain
143
+ # property specifies.
144
+ attr_accessor :name
145
+
146
+ # The user's security identifier.
147
+ attr_accessor :sid
148
+
149
+ # Current status for the user, such as "ok", "error", etc.
150
+ attr_accessor :status
151
+
152
+ # The user's id (RID).
153
+ attr_accessor :uid
154
+
155
+ # The user's home directory
156
+ attr_accessor :dir
157
+
158
+ # Used to set whether or not the account is disabled.
159
+ attr_writer :disabled
160
+
161
+ # Sets whether or not the account is defined on the local computer.
162
+ attr_writer :local
163
+
164
+ # Sets whether or not the account is locked out of the OS.
165
+ attr_writer :lockout
166
+
167
+ # Sets whether or not the password for the account can be changed.
168
+ attr_writer :password_changeable
169
+
170
+ # Sets whether or not the password for the account expires.
171
+ attr_writer :password_expires
172
+
173
+ # Sets whether or not a password is required for the account.
174
+ attr_writer :password_required
175
+
176
+ # Returns the account type as a human readable string.
177
+ attr_reader :account_type
178
+
179
+ # Creates an returns a new User object. A User object encapsulates a
180
+ # user account on the operating system.
181
+ #
182
+ # Yields +self+ if a block is provided.
183
+ #
184
+ def initialize
185
+ yield self if block_given?
186
+ end
187
+
188
+ # Sets the account type for the account. Possible values are:
189
+ #
190
+ # * User::TEMP_DUPLICATE
191
+ # * User::NORMAL
192
+ # * User::INTERDOMAIN_TRUST
193
+ # * User::WORKSTATION_TRUST
194
+ # * User::SERVER_TRUST
195
+ #
196
+ def account_type=(type)
197
+ case type
198
+ when TEMP_DUPLICATE
199
+ @account_type = 'duplicate'
200
+ when NORMAL
201
+ @account_type = 'normal'
202
+ when INTERDOMAIN_TRUST
203
+ @account_type = 'interdomain_trust'
204
+ when WORKSTATION_TRUST
205
+ @account_type = 'workstation_trust'
206
+ when SERVER_TRUST
207
+ @account_type = 'server_trust'
208
+ else
209
+ @account_type = 'unknown'
210
+ end
211
+ end
212
+
213
+ # Returns the SID type as a human readable string.
214
+ #
215
+ def sid_type
216
+ @sid_type
217
+ end
218
+
219
+ # Sets the SID (Security Identifier) type to +stype+, which can be
220
+ # one of the following constant values:
221
+ #
222
+ # * Admin::SidTypeUser
223
+ # * Admin::SidTypeGroup
224
+ # * Admin::SidTypeDomain
225
+ # * Admin::SidTypeAlias
226
+ # * Admin::SidTypeWellKnownGroup
227
+ # * Admin::SidTypeDeletedAccount
228
+ # * Admin::SidTypeInvalid
229
+ # * Admin::SidTypeUnknown
230
+ # * Admin::SidTypeComputer
231
+ #
232
+ def sid_type=(stype)
233
+ case stype
234
+ when Admin::SidTypeUser
235
+ @sid_type = 'user'
236
+ when Admin::SidTypeGroup
237
+ @sid_type = 'group'
238
+ when Admin::SidTypeDomain
239
+ @sid_type = 'domain'
240
+ when Admin::SidTypeAlias
241
+ @sid_type = 'alias'
242
+ when Admin::SidTypeWellKnownGroup
243
+ @sid_type = 'well_known_group'
244
+ when Admin::SidTypeDeletedAccount
245
+ @sid_type = 'deleted_account'
246
+ when Admin::SidTypeInvalid
247
+ @sid_type = 'invalid'
248
+ when Admin::SidTypeUnknown
249
+ @sid_type = 'unknown'
250
+ when Admin::SidTypeComputer
251
+ @sid_type = 'computer'
252
+ else
253
+ @sid_type = 'unknown'
254
+ end
255
+ end
256
+
257
+ # Returns whether or not the account is disabled.
258
+ #
259
+ def disabled?
260
+ @disabled
261
+ end
262
+
263
+ # Returns whether or not the account is local.
264
+ #
265
+ def local?
266
+ @local
267
+ end
268
+
269
+ # Returns whether or not the account is locked out.
270
+ #
271
+ def lockout?
272
+ @lockout
273
+ end
274
+
275
+ # Returns whether or not the password for the account is changeable.
276
+ #
277
+ def password_changeable?
278
+ @password_changeable
279
+ end
280
+
281
+ # Returns whether or not the password for the account is changeable.
282
+ #
283
+ def password_expires?
284
+ @password_expires
285
+ end
286
+
287
+ # Returns whether or not the a password is required for the account.
288
+ #
289
+ def password_required?
290
+ @password_required
291
+ end
292
+ end
293
+
294
+ class Admin
295
+ # The version of the sys-admin library
296
+ VERSION = '1.5.3'
297
+
298
+ # This is the error raised in the majority of cases if anything goes wrong
299
+ # with any of the Sys::Admin methods.
300
+ #
301
+ class Error < StandardError; end
302
+
303
+ SidTypeUser = 1
304
+ SidTypeGroup = 2
305
+ SidTypeDomain = 3
306
+ SidTypeAlias = 4
307
+ SidTypeWellKnownGroup = 5
308
+ SidTypeDeletedAccount = 6
309
+ SidTypeInvalid = 7
310
+ SidTypeUnknown = 8
311
+ SidTypeComputer = 9
312
+
313
+ private
314
+
315
+ HKEY = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\"
316
+
317
+ # Retrieves the user's home directory. For local accounts query the
318
+ # registry. For domain accounts use ADSI and use the HomeDirectory.
319
+ #
320
+ def self.get_home_dir(user, local = false, domain = nil)
321
+ if local
322
+ sec = Win32::Security::SID.open(user)
323
+ key = HKEY + sec.to_s
324
+ dir = nil
325
+
326
+ begin
327
+ Win32::Registry::HKEY_LOCAL_MACHINE.open(key) do |reg|
328
+ dir = reg['ProfileImagePath']
329
+ end
330
+ rescue Win32::Registry::Error
331
+ # Not every local user has a home directory
332
+ end
333
+ else
334
+ domain ||= Socket.gethostname
335
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{user},user")
336
+ dir = adsi.get('HomeDirectory')
337
+ end
338
+
339
+ dir
340
+ end
341
+
342
+ # A private method that lower cases all keys, and converts them
343
+ # all to symbols.
344
+ #
345
+ def self.munge_options(opts)
346
+ rhash = {}
347
+
348
+ opts.each{ |k, v|
349
+ k = k.to_s.downcase.to_sym
350
+ rhash[k] = v
351
+ }
352
+
353
+ rhash
354
+ end
355
+
356
+ # An internal, private method for getting a list of groups for
357
+ # a particular user.
358
+ #
359
+ def self.get_groups(domain, user)
360
+ array = []
361
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{user}")
362
+ adsi.groups.each{ |g| array << g.name }
363
+ array
364
+ end
365
+
366
+ # An internal, private method for getting a list of members for
367
+ # any particular group.
368
+ #
369
+ def self.get_members(domain, group)
370
+ array = []
371
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{group}")
372
+ adsi.members.each{ |g| array << g.name }
373
+ array
374
+ end
375
+
376
+ # Used by the get_login method
377
+ GetUserName = Win32::API.new('GetUserName', 'PP', 'L', 'advapi32') # :nodoc:
378
+
379
+ public
380
+
381
+ # Creates the given +user+. If no domain option is specified,
382
+ # then it defaults to your local host, i.e. a local account is
383
+ # created.
384
+ #
385
+ # Any options provided are treated as IADsUser interface methods
386
+ # and are called before SetInfo is finally called.
387
+ #
388
+ # Examples:
389
+ #
390
+ # # Create a local user with no options
391
+ # Sys::Admin.add_user(:name => 'asmith')
392
+ #
393
+ # # Create a local user with options
394
+ # Sys::Admin.add_user(
395
+ # :name => 'asmith',
396
+ # :description => 'Really cool guy',
397
+ # :password => 'abc123'
398
+ # )
399
+ #
400
+ # # Create a user on a specific domain
401
+ # Sys::Admin.add_user(
402
+ # :name => 'asmith',
403
+ # :domain => 'XX',
404
+ # :fullname => 'Al Smith'
405
+ # )
406
+ #--
407
+ # Most options are passed to the 'put' method. However, we handle the
408
+ # password specially since it's a separate method, and some environments
409
+ # require that it be set up front.
410
+ #
411
+ def self.add_user(options = {})
412
+ options = munge_options(options)
413
+
414
+ name = options.delete(:name) or raise ArgumentError, 'No user given'
415
+ domain = options[:domain]
416
+
417
+ if domain.nil?
418
+ domain = Socket.gethostname
419
+ moniker = "WinNT://#{domain},Computer"
420
+ else
421
+ moniker = "WinNT://#{domain}"
422
+ end
423
+
424
+ begin
425
+ adsi = WIN32OLE.connect(moniker)
426
+ user = adsi.create('user', name)
427
+ options.each{ |option, value|
428
+ if option.to_s == 'password'
429
+ user.setpassword(value)
430
+ else
431
+ user.put(option.to_s, value)
432
+ end
433
+ }
434
+ user.setinfo
435
+ rescue WIN32OLERuntimeError => err
436
+ raise Error, err
437
+ end
438
+ end
439
+
440
+ # Configures the +user+ using +options+. If no domain option is
441
+ # specified then your local host is used, i.e. you are configuring
442
+ # a local user account.
443
+ #
444
+ # See http://tinyurl.com/3hjv9 for a list of valid options.
445
+ #
446
+ # In the case of a password change, pass a two element array as the
447
+ # old value and new value.
448
+ #
449
+ # Examples:
450
+ #
451
+ # # Configure a local user
452
+ # Sys::Admin.configure_user(
453
+ # :name => 'djberge',
454
+ # :description => 'Awesome'
455
+ # )
456
+ #
457
+ # # Change the password
458
+ # Sys::Admin.configure_user(
459
+ # :name => 'asmith',
460
+ # :password => [old_pass, new_pass]
461
+ # )
462
+ #
463
+ # # Configure a user on a specific domain
464
+ # Sys::Admin.configure_user(
465
+ # :name => 'jsmrz',
466
+ # :domain => 'XX',
467
+ # :firstname => 'Jo'
468
+ # )
469
+ #
470
+ def self.configure_user(options = {})
471
+ options = munge_options(options)
472
+
473
+ name = options.delete(:name) or raise ArgumentError, 'No name given'
474
+ domain = options[:domain] || Socket.gethostname
475
+
476
+ begin
477
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{name},user")
478
+ options.each{ |option, value|
479
+ if option.to_s == 'password'
480
+ adsi.changepassword(value[0], value[1])
481
+ else
482
+ adsi.put(option.to_s, value)
483
+ end
484
+ }
485
+ adsi.setinfo
486
+ rescue WIN32OLERuntimeError => err
487
+ raise Error, err
488
+ end
489
+ end
490
+
491
+ # Deletes the given +user+ on +domain+. If no domain is specified,
492
+ # then it defaults to your local host, i.e. a local account is
493
+ # deleted.
494
+ #
495
+ def self.delete_user(user, domain = nil)
496
+ if domain.nil?
497
+ domain = Socket.gethostname
498
+ moniker = "WinNT://#{domain},Computer"
499
+ else
500
+ moniker = "WinNT://#{domain}"
501
+ end
502
+
503
+ begin
504
+ adsi = WIN32OLE.connect(moniker)
505
+ adsi.delete('user', user)
506
+ rescue WIN32OLERuntimeError => err
507
+ raise Error, err
508
+ end
509
+ end
510
+
511
+ # Create a new +group+ using +options+. If no domain option is specified
512
+ # then a local group is created instead.
513
+ #
514
+ # Examples:
515
+ #
516
+ # # Create a local group with no options
517
+ # Sys::Admin.add_group(:name => 'Dudes')
518
+ #
519
+ # # Create a local group with options
520
+ # Sys::Admin.add_group(:name => 'Dudes', :description => 'Boys')
521
+ #
522
+ # # Create a group on a specific domain
523
+ # Sys::Admin.add_group(
524
+ # :name => 'Ladies',
525
+ # :domain => 'XYZ',
526
+ # :description => 'Girls'
527
+ # )
528
+ #
529
+ def self.add_group(options = {})
530
+ options = munge_options(options)
531
+
532
+ group = options.delete(:name) or raise ArgumentError, 'No name given'
533
+ domain = options[:domain]
534
+
535
+ if domain.nil?
536
+ domain = Socket.gethostname
537
+ moniker = "WinNT://#{domain},Computer"
538
+ else
539
+ moniker = "WinNT://#{domain}"
540
+ end
541
+
542
+ begin
543
+ adsi = WIN32OLE.connect(moniker)
544
+ group = adsi.create('group', group)
545
+ group.setinfo
546
+ configure_group(options) unless options.empty?
547
+ rescue WIN32OLERuntimeError => err
548
+ raise Error, err
549
+ end
550
+ end
551
+
552
+ # Configures the +group+ using +options+. If no domain option is
553
+ # specified then your local host is used, i.e. you are configuring
554
+ # a local group.
555
+ #
556
+ # See http://tinyurl.com/cjkzl for a list of valid options.
557
+ #
558
+ # Examples:
559
+ #
560
+ # # Configure a local group.
561
+ # Sys::Admin.configure_group(:name => 'Abba', :description => 'Swedish')
562
+ #
563
+ # # Configure a group on a specific domain.
564
+ # Sys::Admin.configure_group(
565
+ # :name => 'Web Team',
566
+ # :domain => 'Foo',
567
+ # :description => 'Web programming cowboys'
568
+ # )
569
+ #
570
+ def self.configure_group(options = {})
571
+ options = munge_options(options)
572
+
573
+ group = options.delete(:name) or raise ArgumentError, 'No name given'
574
+ domain = options[:domain] || Socket.gethostname
575
+
576
+ begin
577
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{group},group")
578
+ options.each{ |option, value| adsi.put(option.to_s, value) }
579
+ adsi.setinfo
580
+ rescue WIN32OLERuntimeError => err
581
+ raise Error, err
582
+ end
583
+ end
584
+
585
+ # Delete the +group+ from +domain+. If no domain is specified, then
586
+ # you are deleting a local group.
587
+ #
588
+ def self.delete_group(group, domain = nil)
589
+ if domain.nil?
590
+ domain = Socket.gethostname
591
+ moniker = "WinNT://#{domain},Computer"
592
+ else
593
+ moniker = "WinNT://#{domain}"
594
+ end
595
+
596
+ begin
597
+ adsi = WIN32OLE.connect(moniker)
598
+ obj = adsi.delete('group', group)
599
+ rescue WIN32OLERuntimeError => err
600
+ raise Error, err
601
+ end
602
+ end
603
+
604
+ # Returns the user name (only) of the current login.
605
+ #
606
+ def self.get_login
607
+ buffer = 0.chr * 256
608
+ nsize = [buffer.size].pack("L")
609
+
610
+ if GetUserName.call(buffer, nsize) == 0
611
+ raise Error, 'GetUserName() call failed in get_login'
612
+ end
613
+
614
+ length = nsize.unpack('L')[0]
615
+ username = buffer[0 ... length].chop
616
+ username
617
+ end
618
+
619
+ # Returns a User object based on either +name+ or +uid+.
620
+ #
621
+ # call-seq:
622
+ # Sys::Admin.get_user(name, options = {})
623
+ # Sys::Admin.get_user(uid, options = {})
624
+ #
625
+ # Looks for +usr+ information based on the options you specify, where
626
+ # the +usr+ argument can be either a user name or a RID.
627
+ #
628
+ # If a 'host' option is specified, information is retrieved from that
629
+ # host. Otherwise, the local host is used.
630
+ #
631
+ # All other options are converted to WQL statements against the
632
+ # Win32_UserAccount WMI object. See http://tinyurl.com/by9nvn for a
633
+ # list of possible options.
634
+ #
635
+ # Examples:
636
+ #
637
+ # # Get a user by name
638
+ # Admin.get_user('djberge')
639
+ #
640
+ # # Get a user by uid
641
+ # Admin.get_user(100)
642
+ #
643
+ # # Get a user on a specific domain
644
+ # Admin.get_user('djberge', :domain => 'TEST')
645
+ #--
646
+ # The reason for keeping the +usr+ variable as a separate argument
647
+ # instead of rolling it into the options hash was to keep a unified
648
+ # API between the Windows and UNIX versions.
649
+ #
650
+ def self.get_user(usr, options = {})
651
+ options = munge_options(options)
652
+
653
+ host = options.delete(:host) || Socket.gethostname
654
+ cs = "winmgmts:{impersonationLevel=impersonate}!"
655
+ cs << "//#{host}/root/cimv2"
656
+
657
+ begin
658
+ wmi = WIN32OLE.connect(cs)
659
+ rescue WIN32OLERuntimeError => err
660
+ raise Error, err
661
+ end
662
+
663
+ query = "select * from win32_useraccount"
664
+
665
+ i = 0
666
+
667
+ options.each{ |opt, val|
668
+ if i == 0
669
+ query << " where #{opt} = '#{val}'"
670
+ i += 1
671
+ else
672
+ query << " and #{opt} = '#{val}'"
673
+ end
674
+ }
675
+
676
+ if usr.kind_of?(Fixnum)
677
+ query << " and sid like '%-#{usr}'"
678
+ else
679
+ query << " and name = '#{usr}'"
680
+ end
681
+
682
+ domain = options[:domain] || host
683
+
684
+ wmi.execquery(query).each{ |user|
685
+ uid = user.sid.split('-').last.to_i
686
+
687
+ # Because our 'like' query isn't fulproof, let's parse
688
+ # the SID again to make sure
689
+ if usr.kind_of?(Fixnum)
690
+ next if usr != uid
691
+ end
692
+
693
+ user_object = User.new do |u|
694
+ u.account_type = user.accounttype
695
+ u.caption = user.caption
696
+ u.description = user.description
697
+ u.disabled = user.disabled
698
+ u.domain = user.domain
699
+ u.full_name = user.fullname
700
+ u.install_date = user.installdate
701
+ u.local = user.localaccount
702
+ u.lockout = user.lockout
703
+ u.name = user.name
704
+ u.password_changeable = user.passwordchangeable
705
+ u.password_expires = user.passwordexpires
706
+ u.password_required = user.passwordrequired
707
+ u.sid = user.sid
708
+ u.sid_type = user.sidtype
709
+ u.status = user.status
710
+ u.uid = uid
711
+ u.groups = get_groups(domain, user.name)
712
+ u.dir = get_home_dir(user.name, options[:localaccount], domain)
713
+ end
714
+
715
+ return user_object
716
+ }
717
+
718
+ # If we're here, it means it wasn't found.
719
+ raise Error, "no user found for '#{usr}'"
720
+ end
721
+
722
+ # In block form, yields a User object for each user on the system. In
723
+ # non-block form, returns an Array of User objects.
724
+ #
725
+ # call-seq:
726
+ # Sys::Admin.users(options = {})
727
+ # Sys::Admin.users(options = {}){ |user| ... }
728
+ #
729
+ # You may specify a host from which information is retrieved. The
730
+ # default is the local host.
731
+ #
732
+ # All other arguments are passed as WQL query parameters against
733
+ # the Win32_UserAccont WMI object.
734
+ #
735
+ # Examples:
736
+ #
737
+ # # Get all local account users
738
+ # Sys::Admin.users(:localaccount => true)
739
+ #
740
+ # # Get all user accounts on a specific domain
741
+ # Sys::Admin.users(:domain => 'FOO')
742
+ #
743
+ # # Get a single user from a domain
744
+ # Sys::Admin.users(:name => 'djberge', :domain => 'FOO')
745
+ #
746
+ def self.users(options = {})
747
+ options = munge_options(options)
748
+
749
+ host = options.delete(:host) || Socket.gethostname
750
+ cs = "winmgmts:{impersonationLevel=impersonate}!"
751
+ cs << "//#{host}/root/cimv2"
752
+
753
+ begin
754
+ wmi = WIN32OLE.connect(cs)
755
+ rescue WIN32OLERuntimeError => e
756
+ raise Error, e
757
+ end
758
+
759
+ query = "select * from win32_useraccount"
760
+
761
+ i = 0
762
+
763
+ options.each{ |opt, val|
764
+ if i == 0
765
+ query << " where #{opt} = '#{val}'"
766
+ i += 1
767
+ else
768
+ query << " and #{opt} = '#{val}'"
769
+ end
770
+ }
771
+
772
+ array = []
773
+ domain = options[:domain] || host
774
+
775
+ wmi.execquery(query).each{ |user|
776
+ uid = user.sid.split('-').last.to_i
777
+
778
+ usr = User.new do |u|
779
+ u.account_type = user.accounttype
780
+ u.caption = user.caption
781
+ u.description = user.description
782
+ u.disabled = user.disabled
783
+ u.domain = user.domain
784
+ u.full_name = user.fullname
785
+ u.install_date = user.installdate
786
+ u.local = user.localaccount
787
+ u.lockout = user.lockout
788
+ u.name = user.name
789
+ u.password_changeable = user.passwordchangeable
790
+ u.password_expires = user.passwordexpires
791
+ u.password_required = user.passwordrequired
792
+ u.sid = user.sid
793
+ u.sid_type = user.sidtype
794
+ u.status = user.status
795
+ u.groups = get_groups(domain, user.name)
796
+ u.uid = uid
797
+ u.dir = get_home_dir(user.name, options[:localaccount], host)
798
+ end
799
+
800
+ if block_given?
801
+ yield usr
802
+ else
803
+ array.push(usr)
804
+ end
805
+ }
806
+
807
+ return array unless block_given?
808
+ end
809
+
810
+ # Returns a Group object based on either +name+ or +gid+.
811
+ #
812
+ # call-seq:
813
+ # Sys::Admin.get_group(name, options = {})
814
+ # Sys::Admin.get_group(gid, options = {})
815
+ #
816
+ # If a numeric value is sent as the first parameter, it is treated
817
+ # as a RID and is checked against the SID for a match.
818
+ #
819
+ # You may specify a host as an option from which information is
820
+ # retrieved. The default is the local host.
821
+ #
822
+ # All other options are passed as WQL parameters to the Win32_Group
823
+ # WMI object. See http://tinyurl.com/bngc8s for a list of possible
824
+ # options.
825
+ #
826
+ # Examples:
827
+ #
828
+ # # Find a group by name
829
+ # Sys::Admin.get_group('Web Team')
830
+ #
831
+ # # Find a group by id
832
+ # Sys::Admin.get_group(31667)
833
+ #
834
+ # # Find a group on a specific domain
835
+ # Sys::Admin.get_group('Web Team', :domain => 'FOO')
836
+ #
837
+ def self.get_group(grp, options = {})
838
+ options = munge_options(options)
839
+
840
+ host = options.delete(:host) || Socket.gethostname
841
+ cs = "winmgmts:{impersonationLevel=impersonate}!"
842
+ cs << "//#{host}/root/cimv2"
843
+
844
+ begin
845
+ wmi = WIN32OLE.connect(cs)
846
+ rescue WIN32OLERuntimeError => err
847
+ raise Error, err
848
+ end
849
+
850
+ query = "select * from win32_group"
851
+
852
+ i = 0
853
+
854
+ options.each{ |opt, val|
855
+ if i == 0
856
+ query << " where #{opt} = '#{val}'"
857
+ i += 1
858
+ else
859
+ query << " and #{opt} = '#{val}'"
860
+ end
861
+ }
862
+
863
+ if grp.kind_of?(Fixnum)
864
+ query << " and sid like '%-#{grp}'"
865
+ else
866
+ query << " and name = '#{grp}'"
867
+ end
868
+
869
+ domain = options[:domain] || host
870
+
871
+ wmi.execquery(query).each{ |group|
872
+ gid = group.sid.split("-").last.to_i
873
+
874
+ # Because our 'like' query isn't fulproof, let's parse
875
+ # the SID again to make sure
876
+ if grp.kind_of?(Fixnum)
877
+ next if grp != gid
878
+ end
879
+
880
+ group_object = Group.new do |g|
881
+ g.caption = group.caption
882
+ g.description = group.description
883
+ g.domain = group.domain
884
+ g.gid = gid
885
+ g.install_date = group.installdate
886
+ g.local = group.localaccount
887
+ g.name = group.name
888
+ g.sid = group.sid
889
+ g.sid_type = group.sidtype
890
+ g.status = group.status
891
+ g.members = get_members(domain, group.name)
892
+ end
893
+
894
+ return group_object
895
+ }
896
+
897
+ # If we're here, it means it wasn't found.
898
+ raise Error, "no group found for '#{grp}'"
899
+ end
900
+
901
+ # In block form, yields a Group object for each user on the system. In
902
+ # non-block form, returns an Array of Group objects.
903
+ #
904
+ # call-seq:
905
+ # groups(options = {})
906
+ # groups(options = {}){ |group| ... }
907
+ #
908
+ # You may specify a host option from which information is retrieved.
909
+ # The default is the local host.
910
+ #
911
+ # All other options are passed as WQL parameters to the Win32_Group
912
+ # WMI object. See http://tinyurl.com/bngc8s for a list of possible
913
+ # options.
914
+ #
915
+ # Examples:
916
+ #
917
+ # # Get local group information
918
+ # Sys::Admin.groups(:localaccount => true)
919
+ #
920
+ # # Get all groups on a specific domain
921
+ # Sys::Admin.groups(:domain => 'FOO')
922
+ #
923
+ # # Get a specific group on a domain
924
+ # Sys::Admin.groups(:name => 'Some Group', :domain => 'FOO')
925
+ #
926
+ def self.groups(options = {})
927
+ options = munge_options(options)
928
+
929
+ host = options.delete(:host) || Socket.gethostname
930
+ cs = "winmgmts:{impersonationLevel=impersonate}!"
931
+ cs << "//#{host}/root/cimv2"
932
+
933
+ begin
934
+ wmi = WIN32OLE.connect(cs)
935
+ rescue WIN32OLERuntimeError => err
936
+ raise Error, err
937
+ end
938
+
939
+ query = "select * from win32_group"
940
+
941
+ i = 0
942
+
943
+ options.each{ |opt, val|
944
+ if i == 0
945
+ query << " where #{opt} = '#{val}'"
946
+ i += 1
947
+ else
948
+ query << " and #{opt} = '#{val}'"
949
+ end
950
+ }
951
+
952
+ array = []
953
+ domain = options[:domain] || host
954
+
955
+ wmi.execquery(query).each{ |group|
956
+ grp = Group.new do |g|
957
+ g.caption = group.caption
958
+ g.description = group.description
959
+ g.domain = group.domain
960
+ g.gid = group.sid.split("-").last.to_i
961
+ g.install_date = group.installdate
962
+ g.local = group.localaccount
963
+ g.name = group.name
964
+ g.sid = group.sid
965
+ g.sid_type = group.sidtype
966
+ g.status = group.status
967
+ g.members = get_members(domain, group.name)
968
+ end
969
+
970
+ if block_given?
971
+ yield grp
972
+ else
973
+ array.push(grp)
974
+ end
975
+ }
976
+
977
+ return array unless block_given?
978
+ end
979
+ end
980
+ end