sys-admin 1.7.0 → 1.7.1

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.
@@ -1,997 +1,997 @@
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 primary group ID.
158
- attr_accessor :gid
159
-
160
- # The user's home directory
161
- attr_accessor :dir
162
-
163
- # Used to set whether or not the account is disabled.
164
- attr_writer :disabled
165
-
166
- # Sets whether or not the account is defined on the local computer.
167
- attr_writer :local
168
-
169
- # Sets whether or not the account is locked out of the OS.
170
- attr_writer :lockout
171
-
172
- # Sets whether or not the password for the account can be changed.
173
- attr_writer :password_changeable
174
-
175
- # Sets whether or not the password for the account expires.
176
- attr_writer :password_expires
177
-
178
- # Sets whether or not a password is required for the account.
179
- attr_writer :password_required
180
-
181
- # Returns the account type as a human readable string.
182
- attr_reader :account_type
183
-
184
- # Creates an returns a new User object. A User object encapsulates a
185
- # user account on the operating system.
186
- #
187
- # Yields +self+ if a block is provided.
188
- #
189
- def initialize
190
- yield self if block_given?
191
- end
192
-
193
- # Sets the account type for the account. Possible values are:
194
- #
195
- # * User::TEMP_DUPLICATE
196
- # * User::NORMAL
197
- # * User::INTERDOMAIN_TRUST
198
- # * User::WORKSTATION_TRUST
199
- # * User::SERVER_TRUST
200
- #
201
- def account_type=(type)
202
- case type
203
- when TEMP_DUPLICATE
204
- @account_type = 'duplicate'
205
- when NORMAL
206
- @account_type = 'normal'
207
- when INTERDOMAIN_TRUST
208
- @account_type = 'interdomain_trust'
209
- when WORKSTATION_TRUST
210
- @account_type = 'workstation_trust'
211
- when SERVER_TRUST
212
- @account_type = 'server_trust'
213
- else
214
- @account_type = 'unknown'
215
- end
216
- end
217
-
218
- # Returns the SID type as a human readable string.
219
- #
220
- def sid_type
221
- @sid_type
222
- end
223
-
224
- # Sets the SID (Security Identifier) type to +stype+, which can be
225
- # one of the following constant values:
226
- #
227
- # * Admin::SidTypeUser
228
- # * Admin::SidTypeGroup
229
- # * Admin::SidTypeDomain
230
- # * Admin::SidTypeAlias
231
- # * Admin::SidTypeWellKnownGroup
232
- # * Admin::SidTypeDeletedAccount
233
- # * Admin::SidTypeInvalid
234
- # * Admin::SidTypeUnknown
235
- # * Admin::SidTypeComputer
236
- #
237
- def sid_type=(stype)
238
- case stype
239
- when Admin::SidTypeUser
240
- @sid_type = 'user'
241
- when Admin::SidTypeGroup
242
- @sid_type = 'group'
243
- when Admin::SidTypeDomain
244
- @sid_type = 'domain'
245
- when Admin::SidTypeAlias
246
- @sid_type = 'alias'
247
- when Admin::SidTypeWellKnownGroup
248
- @sid_type = 'well_known_group'
249
- when Admin::SidTypeDeletedAccount
250
- @sid_type = 'deleted_account'
251
- when Admin::SidTypeInvalid
252
- @sid_type = 'invalid'
253
- when Admin::SidTypeUnknown
254
- @sid_type = 'unknown'
255
- when Admin::SidTypeComputer
256
- @sid_type = 'computer'
257
- else
258
- @sid_type = 'unknown'
259
- end
260
- end
261
-
262
- # Returns whether or not the account is disabled.
263
- #
264
- def disabled?
265
- @disabled
266
- end
267
-
268
- # Returns whether or not the account is local.
269
- #
270
- def local?
271
- @local
272
- end
273
-
274
- # Returns whether or not the account is locked out.
275
- #
276
- def lockout?
277
- @lockout
278
- end
279
-
280
- # Returns whether or not the password for the account is changeable.
281
- #
282
- def password_changeable?
283
- @password_changeable
284
- end
285
-
286
- # Returns whether or not the password for the account is changeable.
287
- #
288
- def password_expires?
289
- @password_expires
290
- end
291
-
292
- # Returns whether or not the a password is required for the account.
293
- #
294
- def password_required?
295
- @password_required
296
- end
297
- end
298
-
299
- class Admin
300
- extend FFI::Library
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. The first member is a list of group names,
362
- # the second member is the primary group ID.
363
- #
364
- def self.get_groups(domain, user)
365
- array = []
366
- adsi = WIN32OLE.connect("WinNT://#{domain}/#{user},user")
367
- adsi.groups.each{ |g| array << g.name }
368
- [array, adsi.PrimaryGroupId]
369
- end
370
-
371
- # An internal, private method for getting a list of members for
372
- # any particular group.
373
- #
374
- def self.get_members(domain, group)
375
- array = []
376
- adsi = WIN32OLE.connect("WinNT://#{domain}/#{group}")
377
- adsi.members.each{ |g| array << g.name }
378
- array
379
- end
380
-
381
- # Used by the get_login method
382
- ffi_lib :advapi32
383
- attach_function :GetUserNameW, [:pointer, :pointer], :bool
384
- private_class_method :GetUserNameW
385
-
386
- public
387
-
388
- # Creates the given +user+. If no domain option is specified,
389
- # then it defaults to your local host, i.e. a local account is
390
- # created.
391
- #
392
- # Any options provided are treated as IADsUser interface methods
393
- # and are called before SetInfo is finally called.
394
- #
395
- # Examples:
396
- #
397
- # # Create a local user with no options
398
- # Sys::Admin.add_user(:name => 'asmith')
399
- #
400
- # # Create a local user with options
401
- # Sys::Admin.add_user(
402
- # :name => 'asmith',
403
- # :description => 'Really cool guy',
404
- # :password => 'abc123'
405
- # )
406
- #
407
- # # Create a user on a specific domain
408
- # Sys::Admin.add_user(
409
- # :name => 'asmith',
410
- # :domain => 'XX',
411
- # :fullname => 'Al Smith'
412
- # )
413
- #--
414
- # Most options are passed to the 'put' method. However, we handle the
415
- # password specially since it's a separate method, and some environments
416
- # require that it be set up front.
417
- #
418
- def self.add_user(options = {})
419
- options = munge_options(options)
420
-
421
- name = options.delete(:name) or raise ArgumentError, 'No user given'
422
- domain = options[:domain]
423
-
424
- if domain.nil?
425
- domain = Socket.gethostname
426
- moniker = "WinNT://#{domain},Computer"
427
- else
428
- moniker = "WinNT://#{domain}"
429
- end
430
-
431
- begin
432
- adsi = WIN32OLE.connect(moniker)
433
- user = adsi.create('user', name)
434
-
435
- options.each{ |option, value|
436
- if option.to_s == 'password'
437
- user.setpassword(value)
438
- else
439
- user.put(option.to_s, value)
440
- end
441
- }
442
-
443
- user.setinfo
444
- rescue WIN32OLERuntimeError => err
445
- raise Error, err
446
- end
447
- end
448
-
449
- # Configures the +user+ using +options+. If no domain option is
450
- # specified then your local host is used, i.e. you are configuring
451
- # a local user account.
452
- #
453
- # See http://tinyurl.com/3hjv9 for a list of valid options.
454
- #
455
- # In the case of a password change, pass a two element array as the
456
- # old value and new value.
457
- #
458
- # Examples:
459
- #
460
- # # Configure a local user
461
- # Sys::Admin.configure_user(
462
- # :name => 'djberge',
463
- # :description => 'Awesome'
464
- # )
465
- #
466
- # # Change the password
467
- # Sys::Admin.configure_user(
468
- # :name => 'asmith',
469
- # :password => 'new_password'
470
- # )
471
- #
472
- # # Configure a user on a specific domain
473
- # Sys::Admin.configure_user(
474
- # :name => 'jsmrz',
475
- # :domain => 'XX',
476
- # :firstname => 'Jo'
477
- # )
478
- #
479
- def self.configure_user(options = {})
480
- options = munge_options(options)
481
-
482
- name = options.delete(:name) or raise ArgumentError, 'No name given'
483
- domain = options[:domain] || Socket.gethostname
484
-
485
- begin
486
- adsi = WIN32OLE.connect("WinNT://#{domain}/#{name},user")
487
-
488
- options.each{ |option, value|
489
- if option.to_s == 'password'
490
- adsi.setpassword(value)
491
- else
492
- adsi.put(option.to_s, value)
493
- end
494
- }
495
-
496
- adsi.setinfo
497
- rescue WIN32OLERuntimeError => err
498
- raise Error, err
499
- end
500
- end
501
-
502
- # Deletes the given +user+ on +domain+. If no domain is specified,
503
- # then it defaults to your local host, i.e. a local account is
504
- # deleted.
505
- #
506
- def self.delete_user(user, domain = nil)
507
- if domain.nil?
508
- domain = Socket.gethostname
509
- moniker = "WinNT://#{domain},Computer"
510
- else
511
- moniker = "WinNT://#{domain}"
512
- end
513
-
514
- begin
515
- adsi = WIN32OLE.connect(moniker)
516
- adsi.delete('user', user)
517
- rescue WIN32OLERuntimeError => err
518
- raise Error, err
519
- end
520
- end
521
-
522
- # Create a new +group+ using +options+. If no domain option is specified
523
- # then a local group is created instead.
524
- #
525
- # Examples:
526
- #
527
- # # Create a local group with no options
528
- # Sys::Admin.add_group(:name => 'Dudes')
529
- #
530
- # # Create a local group with options
531
- # Sys::Admin.add_group(:name => 'Dudes', :description => 'Boys')
532
- #
533
- # # Create a group on a specific domain
534
- # Sys::Admin.add_group(
535
- # :name => 'Ladies',
536
- # :domain => 'XYZ',
537
- # :description => 'Girls'
538
- # )
539
- #
540
- def self.add_group(options = {})
541
- options = munge_options(options)
542
-
543
- group = options.delete(:name) or raise ArgumentError, 'No name given'
544
- domain = options[:domain]
545
-
546
- if domain.nil?
547
- domain = Socket.gethostname
548
- moniker = "WinNT://#{domain},Computer"
549
- else
550
- moniker = "WinNT://#{domain}"
551
- end
552
-
553
- begin
554
- adsi = WIN32OLE.connect(moniker)
555
- group = adsi.create('group', group)
556
- group.setinfo
557
- configure_group(options) unless options.empty?
558
- rescue WIN32OLERuntimeError => err
559
- raise Error, err
560
- end
561
- end
562
-
563
- # Adds +user+ to +group+ on the specified +domain+, or the localhost
564
- # if no domain is specified.
565
- #
566
- def self.add_group_member(user, group, domain=nil)
567
- domain ||= Socket.gethostname
568
- adsi = WIN32OLE.connect("WinNT://#{domain}/#{group},group")
569
- adsi.Add("WinNT://#{domain}/#{user}")
570
- rescue WIN32OLERuntimeError => err
571
- raise Error, err
572
- end
573
-
574
- # Removes +user+ from +group+ on the specified +domain+, or the localhost
575
- # if no domain is specified.
576
- #
577
- def self.remove_group_member(user, group, domain=nil)
578
- domain ||= Socket.gethostname
579
- adsi = WIN32OLE.connect("WinNT://#{domain}/#{group},group")
580
- adsi.Remove("WinNT://#{domain}/#{user}")
581
- rescue WIN32OLERuntimeError => err
582
- raise Error, err
583
- end
584
-
585
- # Configures the +group+ using +options+. If no domain option is
586
- # specified then your local host is used, i.e. you are configuring
587
- # a local group.
588
- #
589
- # See http://tinyurl.com/cjkzl for a list of valid options.
590
- #
591
- # Examples:
592
- #
593
- # # Configure a local group.
594
- # Sys::Admin.configure_group(:name => 'Abba', :description => 'Swedish')
595
- #
596
- # # Configure a group on a specific domain.
597
- # Sys::Admin.configure_group(
598
- # :name => 'Web Team',
599
- # :domain => 'Foo',
600
- # :description => 'Web programming cowboys'
601
- # )
602
- #
603
- def self.configure_group(options = {})
604
- options = munge_options(options)
605
-
606
- group = options.delete(:name) or raise ArgumentError, 'No name given'
607
- domain = options[:domain] || Socket.gethostname
608
-
609
- begin
610
- adsi = WIN32OLE.connect("WinNT://#{domain}/#{group},group")
611
- options.each{ |option, value| adsi.put(option.to_s, value) }
612
- adsi.setinfo
613
- rescue WIN32OLERuntimeError => err
614
- raise Error, err
615
- end
616
- end
617
-
618
- # Delete the +group+ from +domain+. If no domain is specified, then
619
- # you are deleting a local group.
620
- #
621
- def self.delete_group(group, domain = nil)
622
- if domain.nil?
623
- domain = Socket.gethostname
624
- moniker = "WinNT://#{domain},Computer"
625
- else
626
- moniker = "WinNT://#{domain}"
627
- end
628
-
629
- begin
630
- adsi = WIN32OLE.connect(moniker)
631
- adsi.delete('group', group)
632
- rescue WIN32OLERuntimeError => err
633
- raise Error, err
634
- end
635
- end
636
-
637
- # Returns the user name (only) of the current login.
638
- #
639
- def self.get_login
640
- buffer = FFI::MemoryPointer.new(:char, 256)
641
- nsize = FFI::MemoryPointer.new(:ulong)
642
- nsize.write_ulong(buffer.size)
643
-
644
- unless GetUserNameW(buffer, nsize)
645
- raise Error, 'GetUserName() call failed in get_login'
646
- end
647
-
648
- buffer.read_string(nsize.read_ulong * 2).tr(0.chr, '')
649
- end
650
-
651
- # Returns a User object based on either +name+ or +uid+.
652
- #
653
- # call-seq:
654
- # Sys::Admin.get_user(name, options = {})
655
- # Sys::Admin.get_user(uid, options = {})
656
- #
657
- # Looks for +usr+ information based on the options you specify, where
658
- # the +usr+ argument can be either a user name or a RID.
659
- #
660
- # If a 'host' option is specified, information is retrieved from that
661
- # host. Otherwise, the local host is used.
662
- #
663
- # All other options are converted to WQL statements against the
664
- # Win32_UserAccount WMI object. See http://tinyurl.com/by9nvn for a
665
- # list of possible options.
666
- #
667
- # Examples:
668
- #
669
- # # Get a user by name
670
- # Admin.get_user('djberge')
671
- #
672
- # # Get a user by uid
673
- # Admin.get_user(100)
674
- #
675
- # # Get a user on a specific domain
676
- # Admin.get_user('djberge', :domain => 'TEST')
677
- #--
678
- # The reason for keeping the +usr+ variable as a separate argument
679
- # instead of rolling it into the options hash was to keep a unified
680
- # API between the Windows and UNIX versions.
681
- #
682
- def self.get_user(usr, options = {})
683
- options = munge_options(options)
684
-
685
- host = options.delete(:host) || Socket.gethostname
686
- cs = "winmgmts:{impersonationLevel=impersonate}!"
687
- cs << "//#{host}/root/cimv2"
688
-
689
- begin
690
- wmi = WIN32OLE.connect(cs)
691
- rescue WIN32OLERuntimeError => err
692
- raise Error, err
693
- end
694
-
695
- query = "select * from win32_useraccount"
696
-
697
- i = 0
698
-
699
- options.each{ |opt, val|
700
- if i == 0
701
- query << " where #{opt} = '#{val}'"
702
- i += 1
703
- else
704
- query << " and #{opt} = '#{val}'"
705
- end
706
- }
707
-
708
- if usr.kind_of?(Fixnum)
709
- query << " and sid like '%-#{usr}'"
710
- else
711
- query << " and name = '#{usr}'"
712
- end
713
-
714
- domain = options[:domain] || host
715
-
716
- wmi.execquery(query).each{ |user|
717
- uid = user.sid.split('-').last.to_i
718
-
719
- # Because our 'like' query isn't fulproof, let's parse
720
- # the SID again to make sure
721
- if usr.kind_of?(Fixnum)
722
- next if usr != uid
723
- end
724
-
725
- groups, primary_group = *get_groups(domain, user.name)
726
-
727
- user_object = User.new do |u|
728
- u.account_type = user.accounttype
729
- u.caption = user.caption
730
- u.description = user.description
731
- u.disabled = user.disabled
732
- u.domain = user.domain
733
- u.full_name = user.fullname
734
- u.install_date = user.installdate
735
- u.local = user.localaccount
736
- u.lockout = user.lockout
737
- u.name = user.name
738
- u.password_changeable = user.passwordchangeable
739
- u.password_expires = user.passwordexpires
740
- u.password_required = user.passwordrequired
741
- u.sid = user.sid
742
- u.sid_type = user.sidtype
743
- u.status = user.status
744
- u.uid = uid
745
- u.gid = primary_group
746
- u.groups = groups
747
- u.dir = get_home_dir(user.name, options[:localaccount], domain)
748
- end
749
-
750
- return user_object
751
- }
752
-
753
- # If we're here, it means it wasn't found.
754
- raise Error, "no user found for '#{usr}'"
755
- end
756
-
757
- # Returns an array of User objects for each user on the system.
758
- #
759
- # You may specify a host from which information is retrieved. The
760
- # default is the local host.
761
- #
762
- # All other arguments are passed as WQL query parameters against
763
- # the Win32_UserAccont WMI object.
764
- #
765
- # Examples:
766
- #
767
- # # Get all local account users
768
- # Sys::Admin.users(:localaccount => true)
769
- #
770
- # # Get all user accounts on a specific domain
771
- # Sys::Admin.users(:domain => 'FOO')
772
- #
773
- # # Get a single user from a domain
774
- # Sys::Admin.users(:name => 'djberge', :domain => 'FOO')
775
- #
776
- def self.users(options = {})
777
- options = munge_options(options)
778
-
779
- host = options.delete(:host) || Socket.gethostname
780
- cs = "winmgmts:{impersonationLevel=impersonate}!"
781
- cs << "//#{host}/root/cimv2"
782
-
783
- begin
784
- wmi = WIN32OLE.connect(cs)
785
- rescue WIN32OLERuntimeError => e
786
- raise Error, e
787
- end
788
-
789
- query = "select * from win32_useraccount"
790
-
791
- i = 0
792
-
793
- options.each{ |opt, val|
794
- if i == 0
795
- query << " where #{opt} = '#{val}'"
796
- i += 1
797
- else
798
- query << " and #{opt} = '#{val}'"
799
- end
800
- }
801
-
802
- array = []
803
- domain = options[:domain] || host
804
-
805
- wmi.execquery(query).each{ |user|
806
- uid = user.sid.split('-').last.to_i
807
-
808
- usr = User.new do |u|
809
- u.account_type = user.accounttype
810
- u.caption = user.caption
811
- u.description = user.description
812
- u.disabled = user.disabled
813
- u.domain = user.domain
814
- u.full_name = user.fullname
815
- u.install_date = user.installdate
816
- u.local = user.localaccount
817
- u.lockout = user.lockout
818
- u.name = user.name
819
- u.password_changeable = user.passwordchangeable
820
- u.password_expires = user.passwordexpires
821
- u.password_required = user.passwordrequired
822
- u.sid = user.sid
823
- u.sid_type = user.sidtype
824
- u.status = user.status
825
- u.groups = get_groups(domain, user.name)
826
- u.uid = uid
827
- u.dir = get_home_dir(user.name, options[:localaccount], host)
828
- end
829
-
830
- array.push(usr)
831
- }
832
-
833
- array
834
- end
835
-
836
- # Returns a Group object based on either +name+ or +gid+.
837
- #
838
- # call-seq:
839
- # Sys::Admin.get_group(name, options = {})
840
- # Sys::Admin.get_group(gid, options = {})
841
- #
842
- # If a numeric value is sent as the first parameter, it is treated
843
- # as a RID and is checked against the SID for a match.
844
- #
845
- # You may specify a host as an option from which information is
846
- # retrieved. The default is the local host.
847
- #
848
- # All other options are passed as WQL parameters to the Win32_Group
849
- # WMI object. See http://tinyurl.com/bngc8s for a list of possible
850
- # options.
851
- #
852
- # Examples:
853
- #
854
- # # Find a group by name
855
- # Sys::Admin.get_group('Web Team')
856
- #
857
- # # Find a group by id
858
- # Sys::Admin.get_group(31667)
859
- #
860
- # # Find a group on a specific domain
861
- # Sys::Admin.get_group('Web Team', :domain => 'FOO')
862
- #
863
- def self.get_group(grp, options = {})
864
- options = munge_options(options)
865
-
866
- host = options.delete(:host) || Socket.gethostname
867
- cs = "winmgmts:{impersonationLevel=impersonate}!"
868
- cs << "//#{host}/root/cimv2"
869
-
870
- begin
871
- wmi = WIN32OLE.connect(cs)
872
- rescue WIN32OLERuntimeError => err
873
- raise Error, err
874
- end
875
-
876
- query = "select * from win32_group"
877
-
878
- i = 0
879
-
880
- options.each{ |opt, val|
881
- if i == 0
882
- query << " where #{opt} = '#{val}'"
883
- i += 1
884
- else
885
- query << " and #{opt} = '#{val}'"
886
- end
887
- }
888
-
889
- if grp.kind_of?(Fixnum)
890
- query << " and sid like '%-#{grp}'"
891
- else
892
- query << " and name = '#{grp}'"
893
- end
894
-
895
- domain = options[:domain] || host
896
-
897
- wmi.execquery(query).each{ |group|
898
- gid = group.sid.split("-").last.to_i
899
-
900
- # Because our 'like' query isn't fulproof, let's parse
901
- # the SID again to make sure
902
- if grp.kind_of?(Fixnum)
903
- next if grp != gid
904
- end
905
-
906
- group_object = Group.new do |g|
907
- g.caption = group.caption
908
- g.description = group.description
909
- g.domain = group.domain
910
- g.gid = gid
911
- g.install_date = group.installdate
912
- g.local = group.localaccount
913
- g.name = group.name
914
- g.sid = group.sid
915
- g.sid_type = group.sidtype
916
- g.status = group.status
917
- g.members = get_members(domain, group.name)
918
- end
919
-
920
- return group_object
921
- }
922
-
923
- # If we're here, it means it wasn't found.
924
- raise Error, "no group found for '#{grp}'"
925
- end
926
-
927
- # Returns an array of Group objects for each user on the system.
928
- #
929
- # You may specify a host option from which information is retrieved.
930
- # The default is the local host.
931
- #
932
- # All other options are passed as WQL parameters to the Win32_Group
933
- # WMI object. See http://tinyurl.com/bngc8s for a list of possible
934
- # options.
935
- #
936
- # Examples:
937
- #
938
- # # Get local group information
939
- # Sys::Admin.groups(:localaccount => true)
940
- #
941
- # # Get all groups on a specific domain
942
- # Sys::Admin.groups(:domain => 'FOO')
943
- #
944
- # # Get a specific group on a domain
945
- # Sys::Admin.groups(:name => 'Some Group', :domain => 'FOO')
946
- #
947
- def self.groups(options = {})
948
- options = munge_options(options)
949
-
950
- host = options.delete(:host) || Socket.gethostname
951
- cs = "winmgmts:{impersonationLevel=impersonate}!"
952
- cs << "//#{host}/root/cimv2"
953
-
954
- begin
955
- wmi = WIN32OLE.connect(cs)
956
- rescue WIN32OLERuntimeError => err
957
- raise Error, err
958
- end
959
-
960
- query = "select * from win32_group"
961
-
962
- i = 0
963
-
964
- options.each{ |opt, val|
965
- if i == 0
966
- query << " where #{opt} = '#{val}'"
967
- i += 1
968
- else
969
- query << " and #{opt} = '#{val}'"
970
- end
971
- }
972
-
973
- array = []
974
- domain = options[:domain] || host
975
-
976
- wmi.execquery(query).each{ |group|
977
- grp = Group.new do |g|
978
- g.caption = group.caption
979
- g.description = group.description
980
- g.domain = group.domain
981
- g.gid = group.sid.split("-").last.to_i
982
- g.install_date = group.installdate
983
- g.local = group.localaccount
984
- g.name = group.name
985
- g.sid = group.sid
986
- g.sid_type = group.sidtype
987
- g.status = group.status
988
- g.members = get_members(domain, group.name)
989
- end
990
-
991
- array.push(grp)
992
- }
993
-
994
- array
995
- end
996
- end
997
- end
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 primary group ID.
158
+ attr_accessor :gid
159
+
160
+ # The user's home directory
161
+ attr_accessor :dir
162
+
163
+ # Used to set whether or not the account is disabled.
164
+ attr_writer :disabled
165
+
166
+ # Sets whether or not the account is defined on the local computer.
167
+ attr_writer :local
168
+
169
+ # Sets whether or not the account is locked out of the OS.
170
+ attr_writer :lockout
171
+
172
+ # Sets whether or not the password for the account can be changed.
173
+ attr_writer :password_changeable
174
+
175
+ # Sets whether or not the password for the account expires.
176
+ attr_writer :password_expires
177
+
178
+ # Sets whether or not a password is required for the account.
179
+ attr_writer :password_required
180
+
181
+ # Returns the account type as a human readable string.
182
+ attr_reader :account_type
183
+
184
+ # Creates an returns a new User object. A User object encapsulates a
185
+ # user account on the operating system.
186
+ #
187
+ # Yields +self+ if a block is provided.
188
+ #
189
+ def initialize
190
+ yield self if block_given?
191
+ end
192
+
193
+ # Sets the account type for the account. Possible values are:
194
+ #
195
+ # * User::TEMP_DUPLICATE
196
+ # * User::NORMAL
197
+ # * User::INTERDOMAIN_TRUST
198
+ # * User::WORKSTATION_TRUST
199
+ # * User::SERVER_TRUST
200
+ #
201
+ def account_type=(type)
202
+ case type
203
+ when TEMP_DUPLICATE
204
+ @account_type = 'duplicate'
205
+ when NORMAL
206
+ @account_type = 'normal'
207
+ when INTERDOMAIN_TRUST
208
+ @account_type = 'interdomain_trust'
209
+ when WORKSTATION_TRUST
210
+ @account_type = 'workstation_trust'
211
+ when SERVER_TRUST
212
+ @account_type = 'server_trust'
213
+ else
214
+ @account_type = 'unknown'
215
+ end
216
+ end
217
+
218
+ # Returns the SID type as a human readable string.
219
+ #
220
+ def sid_type
221
+ @sid_type
222
+ end
223
+
224
+ # Sets the SID (Security Identifier) type to +stype+, which can be
225
+ # one of the following constant values:
226
+ #
227
+ # * Admin::SidTypeUser
228
+ # * Admin::SidTypeGroup
229
+ # * Admin::SidTypeDomain
230
+ # * Admin::SidTypeAlias
231
+ # * Admin::SidTypeWellKnownGroup
232
+ # * Admin::SidTypeDeletedAccount
233
+ # * Admin::SidTypeInvalid
234
+ # * Admin::SidTypeUnknown
235
+ # * Admin::SidTypeComputer
236
+ #
237
+ def sid_type=(stype)
238
+ case stype
239
+ when Admin::SidTypeUser
240
+ @sid_type = 'user'
241
+ when Admin::SidTypeGroup
242
+ @sid_type = 'group'
243
+ when Admin::SidTypeDomain
244
+ @sid_type = 'domain'
245
+ when Admin::SidTypeAlias
246
+ @sid_type = 'alias'
247
+ when Admin::SidTypeWellKnownGroup
248
+ @sid_type = 'well_known_group'
249
+ when Admin::SidTypeDeletedAccount
250
+ @sid_type = 'deleted_account'
251
+ when Admin::SidTypeInvalid
252
+ @sid_type = 'invalid'
253
+ when Admin::SidTypeUnknown
254
+ @sid_type = 'unknown'
255
+ when Admin::SidTypeComputer
256
+ @sid_type = 'computer'
257
+ else
258
+ @sid_type = 'unknown'
259
+ end
260
+ end
261
+
262
+ # Returns whether or not the account is disabled.
263
+ #
264
+ def disabled?
265
+ @disabled
266
+ end
267
+
268
+ # Returns whether or not the account is local.
269
+ #
270
+ def local?
271
+ @local
272
+ end
273
+
274
+ # Returns whether or not the account is locked out.
275
+ #
276
+ def lockout?
277
+ @lockout
278
+ end
279
+
280
+ # Returns whether or not the password for the account is changeable.
281
+ #
282
+ def password_changeable?
283
+ @password_changeable
284
+ end
285
+
286
+ # Returns whether or not the password for the account is changeable.
287
+ #
288
+ def password_expires?
289
+ @password_expires
290
+ end
291
+
292
+ # Returns whether or not the a password is required for the account.
293
+ #
294
+ def password_required?
295
+ @password_required
296
+ end
297
+ end
298
+
299
+ class Admin
300
+ extend FFI::Library
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. The first member is a list of group names,
362
+ # the second member is the primary group ID.
363
+ #
364
+ def self.get_groups(domain, user)
365
+ array = []
366
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{user},user")
367
+ adsi.groups.each{ |g| array << g.name }
368
+ [array, adsi.PrimaryGroupId]
369
+ end
370
+
371
+ # An internal, private method for getting a list of members for
372
+ # any particular group.
373
+ #
374
+ def self.get_members(domain, group)
375
+ array = []
376
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{group}")
377
+ adsi.members.each{ |g| array << g.name }
378
+ array
379
+ end
380
+
381
+ # Used by the get_login method
382
+ ffi_lib :advapi32
383
+ attach_function :GetUserNameW, [:pointer, :pointer], :bool
384
+ private_class_method :GetUserNameW
385
+
386
+ public
387
+
388
+ # Creates the given +user+. If no domain option is specified,
389
+ # then it defaults to your local host, i.e. a local account is
390
+ # created.
391
+ #
392
+ # Any options provided are treated as IADsUser interface methods
393
+ # and are called before SetInfo is finally called.
394
+ #
395
+ # Examples:
396
+ #
397
+ # # Create a local user with no options
398
+ # Sys::Admin.add_user(:name => 'asmith')
399
+ #
400
+ # # Create a local user with options
401
+ # Sys::Admin.add_user(
402
+ # :name => 'asmith',
403
+ # :description => 'Really cool guy',
404
+ # :password => 'abc123'
405
+ # )
406
+ #
407
+ # # Create a user on a specific domain
408
+ # Sys::Admin.add_user(
409
+ # :name => 'asmith',
410
+ # :domain => 'XX',
411
+ # :fullname => 'Al Smith'
412
+ # )
413
+ #--
414
+ # Most options are passed to the 'put' method. However, we handle the
415
+ # password specially since it's a separate method, and some environments
416
+ # require that it be set up front.
417
+ #
418
+ def self.add_user(options = {})
419
+ options = munge_options(options)
420
+
421
+ name = options.delete(:name) or raise ArgumentError, 'No user given'
422
+ domain = options[:domain]
423
+
424
+ if domain.nil?
425
+ domain = Socket.gethostname
426
+ moniker = "WinNT://#{domain},Computer"
427
+ else
428
+ moniker = "WinNT://#{domain}"
429
+ end
430
+
431
+ begin
432
+ adsi = WIN32OLE.connect(moniker)
433
+ user = adsi.create('user', name)
434
+
435
+ options.each{ |option, value|
436
+ if option.to_s == 'password'
437
+ user.setpassword(value)
438
+ else
439
+ user.put(option.to_s, value)
440
+ end
441
+ }
442
+
443
+ user.setinfo
444
+ rescue WIN32OLERuntimeError => err
445
+ raise Error, err
446
+ end
447
+ end
448
+
449
+ # Configures the +user+ using +options+. If no domain option is
450
+ # specified then your local host is used, i.e. you are configuring
451
+ # a local user account.
452
+ #
453
+ # See http://tinyurl.com/3hjv9 for a list of valid options.
454
+ #
455
+ # In the case of a password change, pass a two element array as the
456
+ # old value and new value.
457
+ #
458
+ # Examples:
459
+ #
460
+ # # Configure a local user
461
+ # Sys::Admin.configure_user(
462
+ # :name => 'djberge',
463
+ # :description => 'Awesome'
464
+ # )
465
+ #
466
+ # # Change the password
467
+ # Sys::Admin.configure_user(
468
+ # :name => 'asmith',
469
+ # :password => 'new_password'
470
+ # )
471
+ #
472
+ # # Configure a user on a specific domain
473
+ # Sys::Admin.configure_user(
474
+ # :name => 'jsmrz',
475
+ # :domain => 'XX',
476
+ # :firstname => 'Jo'
477
+ # )
478
+ #
479
+ def self.configure_user(options = {})
480
+ options = munge_options(options)
481
+
482
+ name = options.delete(:name) or raise ArgumentError, 'No name given'
483
+ domain = options[:domain] || Socket.gethostname
484
+
485
+ begin
486
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{name},user")
487
+
488
+ options.each{ |option, value|
489
+ if option.to_s == 'password'
490
+ adsi.setpassword(value)
491
+ else
492
+ adsi.put(option.to_s, value)
493
+ end
494
+ }
495
+
496
+ adsi.setinfo
497
+ rescue WIN32OLERuntimeError => err
498
+ raise Error, err
499
+ end
500
+ end
501
+
502
+ # Deletes the given +user+ on +domain+. If no domain is specified,
503
+ # then it defaults to your local host, i.e. a local account is
504
+ # deleted.
505
+ #
506
+ def self.delete_user(user, domain = nil)
507
+ if domain.nil?
508
+ domain = Socket.gethostname
509
+ moniker = "WinNT://#{domain},Computer"
510
+ else
511
+ moniker = "WinNT://#{domain}"
512
+ end
513
+
514
+ begin
515
+ adsi = WIN32OLE.connect(moniker)
516
+ adsi.delete('user', user)
517
+ rescue WIN32OLERuntimeError => err
518
+ raise Error, err
519
+ end
520
+ end
521
+
522
+ # Create a new +group+ using +options+. If no domain option is specified
523
+ # then a local group is created instead.
524
+ #
525
+ # Examples:
526
+ #
527
+ # # Create a local group with no options
528
+ # Sys::Admin.add_group(:name => 'Dudes')
529
+ #
530
+ # # Create a local group with options
531
+ # Sys::Admin.add_group(:name => 'Dudes', :description => 'Boys')
532
+ #
533
+ # # Create a group on a specific domain
534
+ # Sys::Admin.add_group(
535
+ # :name => 'Ladies',
536
+ # :domain => 'XYZ',
537
+ # :description => 'Girls'
538
+ # )
539
+ #
540
+ def self.add_group(options = {})
541
+ options = munge_options(options)
542
+
543
+ group = options.delete(:name) or raise ArgumentError, 'No name given'
544
+ domain = options[:domain]
545
+
546
+ if domain.nil?
547
+ domain = Socket.gethostname
548
+ moniker = "WinNT://#{domain},Computer"
549
+ else
550
+ moniker = "WinNT://#{domain}"
551
+ end
552
+
553
+ begin
554
+ adsi = WIN32OLE.connect(moniker)
555
+ group = adsi.create('group', group)
556
+ group.setinfo
557
+ configure_group(options) unless options.empty?
558
+ rescue WIN32OLERuntimeError => err
559
+ raise Error, err
560
+ end
561
+ end
562
+
563
+ # Adds +user+ to +group+ on the specified +domain+, or the localhost
564
+ # if no domain is specified.
565
+ #
566
+ def self.add_group_member(user, group, domain=nil)
567
+ domain ||= Socket.gethostname
568
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{group},group")
569
+ adsi.Add("WinNT://#{domain}/#{user}")
570
+ rescue WIN32OLERuntimeError => err
571
+ raise Error, err
572
+ end
573
+
574
+ # Removes +user+ from +group+ on the specified +domain+, or the localhost
575
+ # if no domain is specified.
576
+ #
577
+ def self.remove_group_member(user, group, domain=nil)
578
+ domain ||= Socket.gethostname
579
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{group},group")
580
+ adsi.Remove("WinNT://#{domain}/#{user}")
581
+ rescue WIN32OLERuntimeError => err
582
+ raise Error, err
583
+ end
584
+
585
+ # Configures the +group+ using +options+. If no domain option is
586
+ # specified then your local host is used, i.e. you are configuring
587
+ # a local group.
588
+ #
589
+ # See http://tinyurl.com/cjkzl for a list of valid options.
590
+ #
591
+ # Examples:
592
+ #
593
+ # # Configure a local group.
594
+ # Sys::Admin.configure_group(:name => 'Abba', :description => 'Swedish')
595
+ #
596
+ # # Configure a group on a specific domain.
597
+ # Sys::Admin.configure_group(
598
+ # :name => 'Web Team',
599
+ # :domain => 'Foo',
600
+ # :description => 'Web programming cowboys'
601
+ # )
602
+ #
603
+ def self.configure_group(options = {})
604
+ options = munge_options(options)
605
+
606
+ group = options.delete(:name) or raise ArgumentError, 'No name given'
607
+ domain = options[:domain] || Socket.gethostname
608
+
609
+ begin
610
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{group},group")
611
+ options.each{ |option, value| adsi.put(option.to_s, value) }
612
+ adsi.setinfo
613
+ rescue WIN32OLERuntimeError => err
614
+ raise Error, err
615
+ end
616
+ end
617
+
618
+ # Delete the +group+ from +domain+. If no domain is specified, then
619
+ # you are deleting a local group.
620
+ #
621
+ def self.delete_group(group, domain = nil)
622
+ if domain.nil?
623
+ domain = Socket.gethostname
624
+ moniker = "WinNT://#{domain},Computer"
625
+ else
626
+ moniker = "WinNT://#{domain}"
627
+ end
628
+
629
+ begin
630
+ adsi = WIN32OLE.connect(moniker)
631
+ adsi.delete('group', group)
632
+ rescue WIN32OLERuntimeError => err
633
+ raise Error, err
634
+ end
635
+ end
636
+
637
+ # Returns the user name (only) of the current login.
638
+ #
639
+ def self.get_login
640
+ buffer = FFI::MemoryPointer.new(:char, 256)
641
+ nsize = FFI::MemoryPointer.new(:ulong)
642
+ nsize.write_ulong(buffer.size)
643
+
644
+ unless GetUserNameW(buffer, nsize)
645
+ raise Error, 'GetUserName() call failed in get_login'
646
+ end
647
+
648
+ buffer.read_string(nsize.read_ulong * 2).tr(0.chr, '')
649
+ end
650
+
651
+ # Returns a User object based on either +name+ or +uid+.
652
+ #
653
+ # call-seq:
654
+ # Sys::Admin.get_user(name, options = {})
655
+ # Sys::Admin.get_user(uid, options = {})
656
+ #
657
+ # Looks for +usr+ information based on the options you specify, where
658
+ # the +usr+ argument can be either a user name or a RID.
659
+ #
660
+ # If a 'host' option is specified, information is retrieved from that
661
+ # host. Otherwise, the local host is used.
662
+ #
663
+ # All other options are converted to WQL statements against the
664
+ # Win32_UserAccount WMI object. See http://tinyurl.com/by9nvn for a
665
+ # list of possible options.
666
+ #
667
+ # Examples:
668
+ #
669
+ # # Get a user by name
670
+ # Admin.get_user('djberge')
671
+ #
672
+ # # Get a user by uid
673
+ # Admin.get_user(100)
674
+ #
675
+ # # Get a user on a specific domain
676
+ # Admin.get_user('djberge', :domain => 'TEST')
677
+ #--
678
+ # The reason for keeping the +usr+ variable as a separate argument
679
+ # instead of rolling it into the options hash was to keep a unified
680
+ # API between the Windows and UNIX versions.
681
+ #
682
+ def self.get_user(usr, options = {})
683
+ options = munge_options(options)
684
+
685
+ host = options.delete(:host) || Socket.gethostname
686
+ cs = "winmgmts:{impersonationLevel=impersonate}!"
687
+ cs << "//#{host}/root/cimv2"
688
+
689
+ begin
690
+ wmi = WIN32OLE.connect(cs)
691
+ rescue WIN32OLERuntimeError => err
692
+ raise Error, err
693
+ end
694
+
695
+ query = "select * from win32_useraccount"
696
+
697
+ i = 0
698
+
699
+ options.each{ |opt, val|
700
+ if i == 0
701
+ query << " where #{opt} = '#{val}'"
702
+ i += 1
703
+ else
704
+ query << " and #{opt} = '#{val}'"
705
+ end
706
+ }
707
+
708
+ if usr.kind_of?(Fixnum)
709
+ query << " and sid like '%-#{usr}'"
710
+ else
711
+ query << " and name = '#{usr}'"
712
+ end
713
+
714
+ domain = options[:domain] || host
715
+
716
+ wmi.execquery(query).each{ |user|
717
+ uid = user.sid.split('-').last.to_i
718
+
719
+ # Because our 'like' query isn't fulproof, let's parse
720
+ # the SID again to make sure
721
+ if usr.kind_of?(Fixnum)
722
+ next if usr != uid
723
+ end
724
+
725
+ groups, primary_group = *get_groups(domain, user.name)
726
+
727
+ user_object = User.new do |u|
728
+ u.account_type = user.accounttype
729
+ u.caption = user.caption
730
+ u.description = user.description
731
+ u.disabled = user.disabled
732
+ u.domain = user.domain
733
+ u.full_name = user.fullname
734
+ u.install_date = user.installdate
735
+ u.local = user.localaccount
736
+ u.lockout = user.lockout
737
+ u.name = user.name
738
+ u.password_changeable = user.passwordchangeable
739
+ u.password_expires = user.passwordexpires
740
+ u.password_required = user.passwordrequired
741
+ u.sid = user.sid
742
+ u.sid_type = user.sidtype
743
+ u.status = user.status
744
+ u.uid = uid
745
+ u.gid = primary_group
746
+ u.groups = groups
747
+ u.dir = get_home_dir(user.name, options[:localaccount], domain)
748
+ end
749
+
750
+ return user_object
751
+ }
752
+
753
+ # If we're here, it means it wasn't found.
754
+ raise Error, "no user found for '#{usr}'"
755
+ end
756
+
757
+ # Returns an array of User objects for each user on the system.
758
+ #
759
+ # You may specify a host from which information is retrieved. The
760
+ # default is the local host.
761
+ #
762
+ # All other arguments are passed as WQL query parameters against
763
+ # the Win32_UserAccont WMI object.
764
+ #
765
+ # Examples:
766
+ #
767
+ # # Get all local account users
768
+ # Sys::Admin.users(:localaccount => true)
769
+ #
770
+ # # Get all user accounts on a specific domain
771
+ # Sys::Admin.users(:domain => 'FOO')
772
+ #
773
+ # # Get a single user from a domain
774
+ # Sys::Admin.users(:name => 'djberge', :domain => 'FOO')
775
+ #
776
+ def self.users(options = {})
777
+ options = munge_options(options)
778
+
779
+ host = options.delete(:host) || Socket.gethostname
780
+ cs = "winmgmts:{impersonationLevel=impersonate}!"
781
+ cs << "//#{host}/root/cimv2"
782
+
783
+ begin
784
+ wmi = WIN32OLE.connect(cs)
785
+ rescue WIN32OLERuntimeError => e
786
+ raise Error, e
787
+ end
788
+
789
+ query = "select * from win32_useraccount"
790
+
791
+ i = 0
792
+
793
+ options.each{ |opt, val|
794
+ if i == 0
795
+ query << " where #{opt} = '#{val}'"
796
+ i += 1
797
+ else
798
+ query << " and #{opt} = '#{val}'"
799
+ end
800
+ }
801
+
802
+ array = []
803
+ domain = options[:domain] || host
804
+
805
+ wmi.execquery(query).each{ |user|
806
+ uid = user.sid.split('-').last.to_i
807
+
808
+ usr = User.new do |u|
809
+ u.account_type = user.accounttype
810
+ u.caption = user.caption
811
+ u.description = user.description
812
+ u.disabled = user.disabled
813
+ u.domain = user.domain
814
+ u.full_name = user.fullname
815
+ u.install_date = user.installdate
816
+ u.local = user.localaccount
817
+ u.lockout = user.lockout
818
+ u.name = user.name
819
+ u.password_changeable = user.passwordchangeable
820
+ u.password_expires = user.passwordexpires
821
+ u.password_required = user.passwordrequired
822
+ u.sid = user.sid
823
+ u.sid_type = user.sidtype
824
+ u.status = user.status
825
+ u.groups = get_groups(domain, user.name)
826
+ u.uid = uid
827
+ u.dir = get_home_dir(user.name, options[:localaccount], host)
828
+ end
829
+
830
+ array.push(usr)
831
+ }
832
+
833
+ array
834
+ end
835
+
836
+ # Returns a Group object based on either +name+ or +gid+.
837
+ #
838
+ # call-seq:
839
+ # Sys::Admin.get_group(name, options = {})
840
+ # Sys::Admin.get_group(gid, options = {})
841
+ #
842
+ # If a numeric value is sent as the first parameter, it is treated
843
+ # as a RID and is checked against the SID for a match.
844
+ #
845
+ # You may specify a host as an option from which information is
846
+ # retrieved. The default is the local host.
847
+ #
848
+ # All other options are passed as WQL parameters to the Win32_Group
849
+ # WMI object. See http://tinyurl.com/bngc8s for a list of possible
850
+ # options.
851
+ #
852
+ # Examples:
853
+ #
854
+ # # Find a group by name
855
+ # Sys::Admin.get_group('Web Team')
856
+ #
857
+ # # Find a group by id
858
+ # Sys::Admin.get_group(31667)
859
+ #
860
+ # # Find a group on a specific domain
861
+ # Sys::Admin.get_group('Web Team', :domain => 'FOO')
862
+ #
863
+ def self.get_group(grp, options = {})
864
+ options = munge_options(options)
865
+
866
+ host = options.delete(:host) || Socket.gethostname
867
+ cs = "winmgmts:{impersonationLevel=impersonate}!"
868
+ cs << "//#{host}/root/cimv2"
869
+
870
+ begin
871
+ wmi = WIN32OLE.connect(cs)
872
+ rescue WIN32OLERuntimeError => err
873
+ raise Error, err
874
+ end
875
+
876
+ query = "select * from win32_group"
877
+
878
+ i = 0
879
+
880
+ options.each{ |opt, val|
881
+ if i == 0
882
+ query << " where #{opt} = '#{val}'"
883
+ i += 1
884
+ else
885
+ query << " and #{opt} = '#{val}'"
886
+ end
887
+ }
888
+
889
+ if grp.kind_of?(Fixnum)
890
+ query << " and sid like '%-#{grp}'"
891
+ else
892
+ query << " and name = '#{grp}'"
893
+ end
894
+
895
+ domain = options[:domain] || host
896
+
897
+ wmi.execquery(query).each{ |group|
898
+ gid = group.sid.split("-").last.to_i
899
+
900
+ # Because our 'like' query isn't fulproof, let's parse
901
+ # the SID again to make sure
902
+ if grp.kind_of?(Fixnum)
903
+ next if grp != gid
904
+ end
905
+
906
+ group_object = Group.new do |g|
907
+ g.caption = group.caption
908
+ g.description = group.description
909
+ g.domain = group.domain
910
+ g.gid = gid
911
+ g.install_date = group.installdate
912
+ g.local = group.localaccount
913
+ g.name = group.name
914
+ g.sid = group.sid
915
+ g.sid_type = group.sidtype
916
+ g.status = group.status
917
+ g.members = get_members(domain, group.name)
918
+ end
919
+
920
+ return group_object
921
+ }
922
+
923
+ # If we're here, it means it wasn't found.
924
+ raise Error, "no group found for '#{grp}'"
925
+ end
926
+
927
+ # Returns an array of Group objects for each user on the system.
928
+ #
929
+ # You may specify a host option from which information is retrieved.
930
+ # The default is the local host.
931
+ #
932
+ # All other options are passed as WQL parameters to the Win32_Group
933
+ # WMI object. See http://tinyurl.com/bngc8s for a list of possible
934
+ # options.
935
+ #
936
+ # Examples:
937
+ #
938
+ # # Get local group information
939
+ # Sys::Admin.groups(:localaccount => true)
940
+ #
941
+ # # Get all groups on a specific domain
942
+ # Sys::Admin.groups(:domain => 'FOO')
943
+ #
944
+ # # Get a specific group on a domain
945
+ # Sys::Admin.groups(:name => 'Some Group', :domain => 'FOO')
946
+ #
947
+ def self.groups(options = {})
948
+ options = munge_options(options)
949
+
950
+ host = options.delete(:host) || Socket.gethostname
951
+ cs = "winmgmts:{impersonationLevel=impersonate}!"
952
+ cs << "//#{host}/root/cimv2"
953
+
954
+ begin
955
+ wmi = WIN32OLE.connect(cs)
956
+ rescue WIN32OLERuntimeError => err
957
+ raise Error, err
958
+ end
959
+
960
+ query = "select * from win32_group"
961
+
962
+ i = 0
963
+
964
+ options.each{ |opt, val|
965
+ if i == 0
966
+ query << " where #{opt} = '#{val}'"
967
+ i += 1
968
+ else
969
+ query << " and #{opt} = '#{val}'"
970
+ end
971
+ }
972
+
973
+ array = []
974
+ domain = options[:domain] || host
975
+
976
+ wmi.execquery(query).each{ |group|
977
+ grp = Group.new do |g|
978
+ g.caption = group.caption
979
+ g.description = group.description
980
+ g.domain = group.domain
981
+ g.gid = group.sid.split("-").last.to_i
982
+ g.install_date = group.installdate
983
+ g.local = group.localaccount
984
+ g.name = group.name
985
+ g.sid = group.sid
986
+ g.sid_type = group.sidtype
987
+ g.status = group.status
988
+ g.members = get_members(domain, group.name)
989
+ end
990
+
991
+ array.push(grp)
992
+ }
993
+
994
+ array
995
+ end
996
+ end
997
+ end