sys-admin 1.5.6 → 1.6.0

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