Hi,

This patch has basic support for users and groups on Windows.

* It for version 0.24.7.
* It does not include support for active directory.
* It does not work with the 0.24.8 beta.

The code is up at http://github.com/finalprefix/puppet/tree/0.24.7-win.

I'd love any feedback you might have!

Thanks,
Joel.

diff --git a/lib/puppet.rb b/lib/puppet.rb
index 4b0091c..77dbdfe 100644
--- a/lib/puppet.rb
+++ b/lib/puppet.rb
@@ -190,53 +190,57 @@ module Puppet
     # Trap a couple of the main signals.  This should probably be
handled
     # in a way that anyone else can register callbacks for traps,
but, eh.
     def self.settraps
-        [:INT, :TERM].each do |signal|
-            trap(signal) do
-                Puppet.notice "Caught #{signal}; shutting down"
-                Puppet.debug "Signal caught here:"
-                caller.each { |l| Puppet.debug l }
-                Puppet.shutdown
-            end
-        end
-
-        # Handle restarting.
-        trap(:HUP) do
-            if client = @services.find { |s| s.is_a?
Puppet::Network::Client.master } and client.running?
-                client.restart
-            else
-                Puppet.restart
-            end
-        end
-
-        # Provide a hook for running clients where appropriate
-        trap(:USR1) do
-            done = 0
-            Puppet.notice "Caught USR1; triggering client run"
-            @services.find_all { |s| s.is_a?
Puppet::Network::Client }.each do |client|
-                if client.respond_to? :running?
-                    if client.running?
-                        Puppet.info "Ignoring running %s" %
client.class
-                    else
-                        done += 1
-                        begin
-                            client.runnow
-                        rescue => detail
-                            Puppet.err "Could not run client: %s" %
detail
-                        end
-                    end
-                else
-                    Puppet.info "Ignoring %s; cannot test whether it
is running" %
-                        client.class
-                end
-            end
-
-            unless done > 0
-                Puppet.notice "No clients were run"
-            end
-        end
-
-        trap(:USR2) do
-            Puppet::Util::Log.reopen
+       begin
+               [:INT, :TERM].each do |signal|
+                   trap(signal) do
+                       Puppet.notice "Caught #{signal}; shutting down"
+                       Puppet.debug "Signal caught here:"
+                       caller.each { |l| Puppet.debug l }
+                       Puppet.shutdown
+                   end
+               end
+
+               # Handle restarting.
+               trap(:HUP) do
+                   if client = @services.find { |s| s.is_a?
Puppet::Network::Client.master } and client.running?
+                       client.restart
+                   else
+                       Puppet.restart
+                   end
+               end
+
+               # Provide a hook for running clients where appropriate
+               trap(:USR1) do
+                   done = 0
+                   Puppet.notice "Caught USR1; triggering client run"
+                   @services.find_all { |s| s.is_a? Puppet::Network::Client 
}.each
do |client|
+                       if client.respond_to? :running?
+                           if client.running?
+                               Puppet.info "Ignoring running %s" % client.class
+                           else
+                               done += 1
+                               begin
+                                   client.runnow
+                               rescue => detail
+                                   Puppet.err "Could not run client: %s" % 
detail
+                               end
+                           end
+                       else
+                           Puppet.info "Ignoring %s; cannot test whether it is 
running" %
+                               client.class
+                       end
+                   end
+
+                   unless done > 0
+                       Puppet.notice "No clients were run"
+                   end
+               end
+
+               trap(:USR2) do
+                   Puppet::Util::Log.reopen
+               end
+        rescue ArgumentError => e
+            raise if e.to_s.index('SIGHUP') == nil
         end
     end

diff --git a/lib/puppet/provider/group/groupadd_win.rb b/lib/puppet/
provider/group/groupadd_win.rb
new file mode 100644
index 0000000..c1fa7f4
--- /dev/null
+++ b/lib/puppet/provider/group/groupadd_win.rb
@@ -0,0 +1,30 @@
+require 'puppet/util/windows_system'
+
+Puppet::Type.type(:group).provide :groupadd_win do
+       desc "Group management for windows"
+
+       defaultfor :operatingsystem => :windows
+
+       has_features :manages_members
+
+       def members
+               Windows::Group.new(@resource[:name]).members
+       end
+
+       def members=(members)
+               Windows::Group.new(@resource[:name]).set_members(members)
+       end
+
+       def create
+               group = Windows::Group.create(@resource[:name])
+               group.set_members(@resource[:members])
+       end
+
+       def exists?
+               Windows::Group.exists?(@resource[:name])
+       end
+
+       def delete
+               Windows::Group.delete(@resource[:name])
+       end
+end
diff --git a/lib/puppet/provider/user/useradd_win.rb b/lib/puppet/
provider/user/useradd_win.rb
new file mode 100644
index 0000000..5f9b969
--- /dev/null
+++ b/lib/puppet/provider/user/useradd_win.rb
@@ -0,0 +1,42 @@
+require 'puppet/provider'
+require 'puppet/util/windows_system'
+
+raise "ERROR: A windowsuser resource can only be configured on
Windows. This OS is is #{Facter['kernel'].value}" if Facter
['kernel'].value != 'windows'
+
+Puppet::Type.type(:user).provide :useradd_win do
+       desc "User management for windows"
+
+       defaultfor :operatingsystem => :windows
+
+       has_features :manages_passwords
+
+       def password
+               name, password = @resource[:name], @resource[:password]
+               Windows::User.new(name).password_is?(password) ?password :""
rescue :absent
+       end
+
+       def password=(pwd)
+               Windows::User.new(@resource[:name]).password = 
@resource[:password]
+       end
+
+       def groups
+               Windows::User.new(@resource[:name]).groups.join(',') rescue 
:absent
+       end
+
+       def groups=(groups)
+               Windows::User.new(@resource[:name]).set_groups(groups)
+       end
+
+       def create
+               user = Windows::User.create(@resource[:name], 
@resource[:password])
+               user.set_groups(@resource[:groups], @resource[:membership]
== :minimum)
+       end
+
+       def exists?
+               return Windows::User.exists?(@resource[:name])
+       end
+
+       def delete
+               Windows::User.delete(@resource[:name])
+       end
+end
diff --git a/lib/puppet/util/windows_system.rb b/lib/puppet/util/
windows_system.rb
new file mode 100644
index 0000000..83bc52e
--- /dev/null
+++ b/lib/puppet/util/windows_system.rb
@@ -0,0 +1,205 @@
+if Facter.operatingsystem == "windows"
+       require 'win32ole'
+       require 'Win32API'
+end
+
+module ADSI
+       def ADSI.connectable?(uri)
+               begin
+                       adsi_obj = WIN32OLE.connect(uri)
+                       return adsi_obj != nil;
+               rescue
+               end
+
+               return false
+       end
+end
+
+module Windows
+       class Resource
+               def Resource.uri(resource_name)
+                       "#{Computer.resource_uri}/#{resource_name}"
+               end
+       end
+
+       class User
+               def initialize(username, native_adsi_obj = nil)
+                       @username = username
+                       @user = native_adsi_obj
+               end
+
+               def user
+                       @user = WIN32OLE.connect(User.resource_uri(@username)) 
if @user ==
nil
+                       return @user
+               end
+
+               def password_is?(password)
+                       fLOGON32_LOGON_NETWORK_CLEARTEXT = 8
+                       fLOGON32_PROVIDER_DEFAULT = 0
+
+                       logon_user = Win32API.new("advapi32", "LogonUser", 
['P', 'P', 'P',
'L', 'L', 'P'], 'L')
+                       close_handle = Win32API.new("kernel32", "CloseHandle", 
['P'], 'V')
+
+                       token = ' ' * 4
+                       if logon_user.call(@username, "", password,
fLOGON32_LOGON_NETWORK_CLEARTEXT, fLOGON32_PROVIDER_DEFAULT, token) ==
1
+                               close_handle.call(token.unpack('L')[0])
+                               return true
+                       end
+
+                       return false
+               end
+
+               def add_flag(flag_name, value)
+                       flag = 0
+
+                       begin
+                               flag = user.Get(flag_name)
+                       rescue
+                       end
+
+                       user.Put(flag_name, flag | value)
+                       user.SetInfo
+               end
+
+               def password=(password)
+                       user.SetPassword(password)
+                       user.SetInfo
+
+                       fADS_UF_DONT_EXPIRE_PASSWD = 0x10000
+                       add_flag("UserFlags", fADS_UF_DONT_EXPIRE_PASSWD)
+               end
+
+               def groups
+                       groups = []
+                       user.Groups.each {|group| groups << group.name }
+                       return groups
+               end
+
+               def add_to_groups(group_names)
+                       group_names.each {|name| 
Group.new(name).add_user(@username) } if
group_names.length > 0
+               end
+
+               def remove_from_groups(group_names)
+                       group_names.each {|name| 
Group.new(name).remove_user(@username) }
if group_names.length > 0
+               end
+
+               def set_groups(names, minimal = true)
+                       return if names == nil || names.strip.length == 0
+
+                       names = names.strip.split(',')
+                       current_groups = groups
+
+                       names.find_all {|name| !current_groups.include?(name) 
}.tap {|
names_to_add| add_to_groups(names_to_add) }
+                       current_groups.find_all {|name| !names.include?(name) 
}.tap {|
names_to_remove| remove_from_groups(names_to_remove) } if minimal ==
false
+               end
+
+               def User.resource_uri(username)
+                       return "#{Resource.uri(username)},user"
+               end
+
+               def User.exists?(username)
+                       return ADSI::connectable?(User.resource_uri(username))
+               end
+
+               def User.create(username, password)
+                       User.new(username, Computer.create("user", 
username)).tap {|user|
user.password = password; yield user if block_given? }
+               end
+
+               def User.delete(username)
+                       Computer.delete("user", username)
+               end
+       end
+
+       class Group
+               def initialize(groupname, native_adsi_obj = nil)
+                       @groupname = groupname
+                       @group = native_adsi_obj
+               end
+
+               def resource_uri
+                       Group.resource_uri(@groupname)
+               end
+
+               def Group.resource_uri(name)
+                       "#{Resource.uri(name)},group"
+               end
+
+               def group
+                       @group = WIN32OLE.connect(resource_uri) if @group == nil
+                       return @group
+               end
+
+               def add_user(username)
+                       group.Add(User.resource_uri(username))
+                       group.SetInfo
+               end
+
+               def remove_user(username)
+                       group.Remove(User.resource_uri(username))
+                       group.SetInfo
+               end
+
+               def add_member(name)
+                       group.Add(Resource.uri(name))
+                       group.SetInfo
+               end
+
+               def remove_member(name)
+                       group.Remove(Resource.uri(name))
+                       group.SetInfo
+               end
+
+               def members
+                       list = []
+                       group.Members.each {|member| list << member.Name }
+                       list
+               end
+
+               def set_members(members)
+                       return nil if members == nil || members.length == 0
+
+                       current_members = self.members
+
+                       members.inject([]) {|members_to_add, member|
current_members.include?(member) ? members_to_add : members_to_add <<
member }.each {|member| add_member(member) }
+                       current_members.inject([]) {|members_to_remove, member|
members.include?(member) ? members_to_remove : members_to_remove <<
member }.each {|member| remove_member(member) }
+               end
+
+               def Group.create(name)
+                       Windows::Group.new(name, Computer.create("group", 
name)).tap {|
group| yield group if block_given? }
+               end
+
+               def Group.exists?(name)
+                       return ADSI::connectable?(Group.resource_uri(name))
+               end
+
+               def Group.delete(name)
+                       Computer.delete("group", name)
+               end
+       end
+
+       class Computer
+               def Computer.name
+                       name = " " * 128
+                       size = "128"
+                       
Win32API.new('kernel32','GetComputerName',['P','P'],'I').call
(name,size)
+                       return name.unpack("A*")
+               end
+
+               def Computer.resource_uri
+                       computer_name = Computer.name
+                       return "WinNT://#{computer_name}"
+               end
+
+               def Computer.api
+                       return WIN32OLE.connect(Computer.resource_uri)
+               end
+
+               def Computer.create(resource_type, name)
+                       Computer.api.create(resource_type, name).SetInfo
+               end
+
+               def Computer.delete(resource_type, name)
+                       Computer.api.Delete(resource_type, name)
+               end
+       end
+end
diff --git a/test/ral/providers/group_win.rb b/test/ral/providers/
group_win.rb
new file mode 100644
index 0000000..d341fbd
--- /dev/null
+++ b/test/ral/providers/group_win.rb
@@ -0,0 +1,39 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../lib/puppettest'
+require 'windowstest'
+
+require File.dirname(__FILE__) + '/../../../lib/puppet/provider/group/
groupadd_win.rb'
+
+class TestGroupProvider < Test::Unit::TestCase
+    include WindowsTest
+
+    def group_provider(resource_configuration)
+       Puppet::Type::Group::ProviderGroupadd_win.new.tap {|provider|
provider.resource = resource_configuration }
+    end
+
+    def test_groupGetsCreated
+       groupname = "randomgroup"
+       register_group groupname
+
+       expected_members = ["test1", "test2"]
+       mkusers(expected_members)
+
+       provider = group_provider :name => groupname, :members => ['test1',
'test2']
+
+       assert_nothing_raised { provider.create }
+       assert_no_missing_member(group(groupname), expected_members)
+    end
+
+    def test_groupMembersGetSet
+       groupname = "randomgroup"
+       group = mkgroup(groupname)
+       expected_members = ["test1", "test2"]
+       mkusers(expected_members)
+
+       provider = group_provider :name => groupname, :members => ['test1',
'test2']
+
+       assert_nothing_raised { provider.members = ['test1', 'test2'] }
+       assert_no_missing_member(group, expected_members)
+    end
+end
diff --git a/test/ral/providers/syslog.rb b/test/ral/providers/
syslog.rb
new file mode 100644
index 0000000..396b45c
--- /dev/null
+++ b/test/ral/providers/syslog.rb
@@ -0,0 +1,4 @@
+# The purpose of this file is merely to fake the syslog module on
windows.
+# I placed it here since I was running the tests from here.
+# I'll probably leave the file here until I wrap the windows event
log in a syslogish module for windows.
+# I haven't thought about it much. I'm open to better ideas too.
\ No newline at end of file
diff --git a/test/ral/providers/user/useradd_win.rb b/test/ral/
providers/user/useradd_win.rb
new file mode 100644
index 0000000..11d31e7
--- /dev/null
+++ b/test/ral/providers/user/useradd_win.rb
@@ -0,0 +1,63 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../lib/puppettest'
+require 'windowstest'
+
+require File.dirname(__FILE__) + '/../../../../lib/puppet/provider/
user/useradd_win.rb'
+
+class TestUserProvider < Test::Unit::TestCase
+       include WindowsTest
+
+       def user_provider(resource_configuration)
+               Puppet::Type::User::ProviderUseradd_win.new.tap {|provider|
provider.resource = resource_configuration }
+       end
+
+       def test_userIsCreated
+               expected_groups = ["randomgroup1", "randomgroup2"]
+               mkgroups(expected_groups)
+
+               username = "testuser"
+               register_user username
+
+               password = "1234"
+
+               provider = user_provider :name => username, :password =>
password, :groups => expected_groups.join(",")
+
+               assert_nothing_raised { provider.create }
+
+               user = Windows::User.new(username)
+               assert(user.password_is?(password), "Password of user 
#{username}
should be #{password}")
+               user.groups.tap {|groups|
+                       expected_groups.each {|expected_group| 
assert(groups.include?
(expected_group), "User should be a member of #{expected_group}") }
+                       assert(expected_groups.length == groups.length, "The 
user should
be a member of #{expected_groups.length} groups.")
+               }
+       end
+
+       def test_userGroupsAreSet
+               expected_groups = ["randomgroup1", "randomgroup2"]
+               mkgroups expected_groups
+
+               username = "testuser"
+               mkuser username
+
+               provider = user_provider :name => username
+               provider.groups = expected_groups.join(",")
+
+               provider.groups.split(',').collect {|group| group.strip }.tap {|
groups|
+                       assert(groups.length == expected_groups.length, "The 
user should
be a member of #{expected_groups.length} groups.")
+                       groups.each {|group| 
assert(expected_groups.include?(group), "The
user should be a member of #{group}") }
+               }
+       end
+
+       def test_usersPasswordIsSet
+               username = "testuser"
+               password = "11112222"
+
+               user = mkuser username, password
+
+               provider = user_provider :name => username, :password => 
password
+               provider.password = password
+
+               assert(user.password_is?(password), "User #{username}'s password
should be #{password}.")
+       end
+end
diff --git a/test/ral/providers/windowstest.rb b/test/ral/providers/
windowstest.rb
new file mode 100644
index 0000000..b9ed1e5
--- /dev/null
+++ b/test/ral/providers/windowstest.rb
@@ -0,0 +1,95 @@
+require File.dirname(__FILE__) + '/../../../lib/puppet/util/
windows_system.rb'
+
+module WindowsTest
+    class List
+       def initialize
+               @list = []
+       end
+
+       def clear
+               destroy
+               @list = []
+       end
+
+       def register(item)
+               @list << item
+       end
+    end
+
+    class Groups < List
+       def destroy
+               @list.each {|group|
+                       begin
+                               Windows::Group.delete(group)
+                       rescue
+                               puts "Group #{group} not found"
+                       end
+               }
+       end
+    end
+
+    class Users < List
+       def destroy
+               @list.each {|user|
+                       begin
+                               Windows::User.delete(user)
+                       rescue
+                               puts "User #{user} not found"
+                       end
+               }
+       end
+    end
+
+    def helper_users
+       @users = Users.new if @users == nil
+       @users
+    end
+
+    def helper_groups
+       @groups = Groups.new if @groups == nil
+       @groups
+    end
+
+    def clear
+       helper_groups.clear
+       helper_users.clear
+    end
+
+    def register_group(name)
+       helper_groups.register name
+    end
+
+    def register_user(name)
+       helper_users.register name
+    end
+
+    def mkuser(name, password = "1234567")
+       Windows::User.create(name, password) { register_user name }
+    end
+
+    def mkgroup(name)
+       Windows::Group.create(name) { register_group name }
+    end
+
+    def mkusers(names)
+       names.collect {|name| mkuser name }
+    end
+
+    def mkgroups(names)
+       names.collect {|name| mkgroup name }
+    end
+
+    def group(name)
+       Windows::Group.new(name)
+    end
+
+    def assert_no_missing_member(group, expected_members)
+       group.members.tap {|members|
+               expected_members.each {|member| 
assert(members.include?(member), "#
{member} should be a member") }
+       }
+    end
+
+    def teardown
+      clear
+    end
+end

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Puppet Developers" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/puppet-dev?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to