sys-admin 1.5.6 → 1.6.0

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