This is my second pass on support for users and groups on Windows.
I've moved tests to rspec, and refactored a little bit here and there.
Nothing has changed since my first pass in essentials.

The code is to be found at http://github.com/finalprefix/puppet/tree/win.

All comments and help are welcome.

- Joel.

diff --git a/lib/puppet/external/event-loop/event-loop.rb
b/lib/puppet/external/event-loop/event-loop.rb
index 17a520e..bb7b94c 100644
--- a/lib/puppet/external/event-loop/event-loop.rb
+++ b/lib/puppet/external/event-loop/event-loop.rb
@@ -74,9 +74,11 @@ class EventLoop

     @notify_src, @notify_snk = IO.pipe

-    # prevent file descriptor leaks
-    @notify_src.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
-    @notify_snk.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
+    if !Puppet.features.windows?
+        # prevent file descriptor leaks
+        @notify_src.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
+        @notify_snk.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
+    end

     @notify_src.will_block = false
     @notify_snk.will_block = false
@@ -244,6 +246,9 @@ class IO

   def will_block= (wants_blocking)
     require "fcntl"
+
+    return if Puppet.features.windows?
+
     flags = fcntl(Fcntl::F_GETFL, 0)
     if wants_blocking
       flags &= ~Fcntl::O_NONBLOCK
diff --git a/lib/puppet/feature/windows.rb b/lib/puppet/feature/windows.rb
new file mode 100644
index 0000000..d5e4f38
--- /dev/null
+++ b/lib/puppet/feature/windows.rb
@@ -0,0 +1,16 @@
+require 'puppet/util/feature'
+
+Puppet.features.add(:windows) do
+    result = false
+
+    if Facter.operatingsystem == 'windows'
+        begin
+            require 'win32ole'
+            require 'Win32API'
+            result = true
+        rescue
+        end
+    end
+
+    result
+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..398b076
--- /dev/null
+++ b/lib/puppet/provider/group/groupadd_win.rb
@@ -0,0 +1,29 @@
+Puppet::Type.type(:group).provide :groupadd_win do
+    desc "Group management for windows"
+
+    confine :true => Puppet.features.windows?
+    require 'puppet/util/windows_system'
+
+    has_features :manages_members
+
+    def members
+        Puppet::Util::Windows::Group.new(@resource[:name]).members
+    end
+
+    def members=(members)
+        Puppet::Util::Windows::Group.new(@resource[:name]).set_members(members)
+    end
+
+    def create
+        group = Puppet::Util::Windows::Group.create(@resource[:name])
+        group.set_members(@resource[:members])
+    end
+
+    def exists?
+        Puppet::Util::Windows::Group.exists?(@resource[:name])
+    end
+
+    def delete
+        Puppet::Util::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..e839104
--- /dev/null
+++ b/lib/puppet/provider/user/useradd_win.rb
@@ -0,0 +1,40 @@
+require 'puppet/provider'
+
+Puppet::Type.type(:user).provide :useradd_win do
+    desc "User management for windows"
+
+    confine :true => Puppet.features.windows?
+    require 'puppet/util/windows_system'
+
+    has_features :manages_passwords
+
+    def password
+        name, password = @resource[:name], @resource[:password]
+        Puppet::Util::Windows::User.new(name).password_is?(password)
?password :"" rescue :absent
+    end
+
+    def password=(pwd)
+        Puppet::Util::Windows::User.new(@resource[:name]).password =
@resource[:password]
+    end
+
+    def groups
+        Puppet::Util::Windows::User.new(@resource[:name]).groups.join(',')
rescue :absent
+    end
+
+    def groups=(groups)
+        Puppet::Util::Windows::User.new(@resource[:name]).set_groups(groups)
+    end
+
+    def create
+        user = Puppet::Util::Windows::User.create(@resource[:name],
@resource[:password])
+        user.set_groups(@resource[:groups], @resource[:membership] == :minimum)
+    end
+
+    def exists?
+        return Puppet::Util::Windows::User.exists?(@resource[:name])
+    end
+
+    def delete
+        Puppet::Util::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..1ea42a0
--- /dev/null
+++ b/lib/puppet/util/windows_system.rb
@@ -0,0 +1,215 @@
+if Puppet.features.windows?
+    require 'win32ole'
+    require 'Win32API'
+end
+
+module Puppet::Util::ADSI
+    def self.connectable?(uri)
+        begin
+            adsi_obj = WIN32OLE.connect(uri)
+            return adsi_obj != nil;
+        rescue
+        end
+
+        return false
+    end
+end
+
+module Puppet::Util::Windows
+    include Puppet::Util::ADSI
+
+    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_to_add = names.find_all {|name|
!current_groups.include?(name) }
+            add_to_groups(names_to_add)
+
+            names_to_remove = current_groups.find_all {|name|
!names.include?(name) }
+            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
Puppet::Util::ADSI::connectable?(User.resource_uri(username))
+        end
+
+        def User.create(username, password)
+            newuser = new(username, Computer.create("user", username))
+            newuser.password = password
+            yield newuser if block_given?
+            return newuser
+        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)
+            newgroup = new(name, Computer.create("group", name))
+            yield newgroup if block_given?
+            return newgroup
+        end
+
+        def Group.exists?(name)
+            return Puppet::Util::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/spec/integration/provider/group/groupadd_win.rb
b/spec/integration/provider/group/groupadd_win.rb
new file mode 100644
index 0000000..51d1df7
--- /dev/null
+++ b/spec/integration/provider/group/groupadd_win.rb
@@ -0,0 +1,48 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+describe "Provider for windows groups" do
+    confine :true => Puppet.features.windows?
+
+    require File.dirname(__FILE__) + '/../windowstest'
+    require File.dirname(__FILE__) +
'/../../../../lib/puppet/provider/group/groupadd_win.rb'
+
+    include WindowsTest
+
+    def group_provider(resource_configuration)
+        provider = Puppet::Type::Group::ProviderGroupadd_win.new
+        provider.resource = resource_configuration
+        return provider
+    end
+
+    after(:each) do
+        clear
+    end
+
+    it 'should create a group with configured members' do
+        groupname = "randomgroup"
+        register_group groupname
+
+        expected_members = ["test1", "test2"]
+        mkusers(expected_members)
+
+        provider = group_provider :name => groupname, :members =>
['test1', 'test2']
+        provider.create
+
+        should_have_no_missing_member(group(groupname), expected_members)
+    end
+
+    it 'should set a groups members' do
+        groupname = "randomgroup"
+        expected_members = ["test1", "test2"]
+
+        testgroup = mkgroup(groupname)
+        mkusers(expected_members)
+
+        provider = group_provider :name => groupname, :members =>
['test1', 'test2']
+        provider.members = ['test1', 'test2']
+
+        should_have_no_missing_member(testgroup, expected_members)
+    end
+end
diff --git a/spec/integration/provider/user/useradd_win.rb
b/spec/integration/provider/user/useradd_win.rb
new file mode 100644
index 0000000..6b0eac1
--- /dev/null
+++ b/spec/integration/provider/user/useradd_win.rb
@@ -0,0 +1,69 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+describe "Provider for windows users" do
+    confine :true => Puppet.features.windows?
+
+    require File.dirname(__FILE__) + '/../windowstest'
+    require File.dirname(__FILE__) +
'/../../../../lib/puppet/provider/user/useradd_win.rb'
+
+    include WindowsTest
+
+    def user_provider(resource_configuration)
+        provider = Puppet::Type::User::ProviderUseradd_win.new
+        provider.resource = resource_configuration
+        return provider
+    end
+
+    after(:each) do
+        clear
+    end
+
+    it 'should add a user with the given password and group membership' do
+        expected_groups = ["randomgroup1", "randomgroup2"]
+        username = "testuser"
+        password = "1234"
+
+        mkgroups(expected_groups)
+        register_user username
+
+        provider = user_provider :name => username, :password =>
password, :groups => expected_groups.join(",")
+        provider.create
+
+        testuser = user(username)
+        testuser.password_is?(password).should be_true
+
+        groups = testuser.groups
+
+        expected_groups.each {|expected_group|
groups.include?(expected_group).should be_true }
+        expected_groups.length.should be_eql(groups.length)
+    end
+
+    it 'should set the group membership of an existing user' do
+        expected_groups = ["randomgroup1", "randomgroup2"]
+        username = "testuser"
+
+        mkgroups expected_groups
+        mkuser username
+
+        provider = user_provider :name => username
+        provider.groups = expected_groups.join(",")
+
+        groups = provider.groups.split(',').collect {|group| group.strip }
+        groups.length.should be_eql(expected_groups.length)
+        groups.each {|group| expected_groups.include?(group).should be_true }
+    end
+
+    it 'should set a users password' do
+        username = "testuser"
+        password = "11112222"
+
+        testuser = mkuser username, password
+
+        provider = user_provider :name => username, :password => password
+        provider.password = password
+
+        testuser.password_is?(password).should be_true
+    end
+end
diff --git a/spec/integration/provider/windowstest.rb
b/spec/integration/provider/windowstest.rb
new file mode 100644
index 0000000..e667531
--- /dev/null
+++ b/spec/integration/provider/windowstest.rb
@@ -0,0 +1,109 @@
+require File.dirname(__FILE__) + '/../../../lib/puppet/util/windows_system.rb'
+
+module WindowsTest
+    include Puppet::Util::Windows
+
+    class List
+        def initialize
+            @list = []
+        end
+
+        def clear
+            destroy
+            @list = []
+        end
+
+        def register(item)
+            @list << item
+        end
+    end
+
+    class Groups < List
+        include Puppet::Util::Windows
+
+        def destroy
+            @list.each {|group|
+                begin
+                    Group.delete(group)
+                rescue
+                    puts "Group #{group} not found"
+                end
+            }
+        end
+    end
+
+    class Users < List
+        include Puppet::Util::Windows
+
+        def destroy
+            @list.each {|user|
+                begin
+                    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")
+        User.create(name, password) { register_user name }
+    end
+
+    def mkgroup(name)
+        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)
+        Group.new(name)
+    end
+
+    def user(name)
+        User.new(name)
+    end
+
+    def assert_no_missing_member(group, expected_members)
+        members = group.members
+        expected_members.each {|member|
assert(members.include?(member), "#{member} should be a member") }
+    end
+
+    def should_have_no_missing_member(testgroup, expected_members)
+        members = testgroup.members
+        expected_members.each {|member|
members.include?(member).should be_true }
+    end
+
+    def teardown
+        clear
+    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

--~--~---------~--~----~------------~-------~--~----~
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