diff --git a/lib/puppet/provider/gpo/lgpo.rb b/lib/puppet/provider/gpo/lgpo.rb index c111d84..99c3290 100644 --- a/lib/puppet/provider/gpo/lgpo.rb +++ b/lib/puppet/provider/gpo/lgpo.rb @@ -8,6 +8,10 @@ def exists? @property_hash[:ensure] == :present end + def deleted? + @property_hash[:ensure] == :deleted + end + def create set_value(resource[:value]) @property_hash[:ensure] = :present @@ -51,7 +55,7 @@ def self.instances ['machine', 'user'].map do |scope| pol_file = "C:\\Windows\\System32\\GroupPolicy\\#{scope.capitalize}\\Registry.pol" next [] unless File.file?(pol_file) - + resources = Hash.new gpos = lgpo('/parse', '/q', "/#{scope[0]}", pol_file) @@ -59,6 +63,11 @@ def self.instances split_g = g.split("\n") path = paths.get_by_key(scope, split_g[1].downcase, split_g[2].downcase) + if path.nil? + warn "Unkown path for gpo resource: '#{split_g[1]}/#{split_g[2]}'" + next + end + admx_file = path['admx_file'].downcase policy_id = path['policy_id'].downcase setting_valuename = path['setting_valuename'].downcase @@ -92,10 +101,31 @@ def self.instances :value => value, } end - + end resources.map{|k, v| new(v)} - end.flatten + end.flatten + end + + def out_line(scope, key, value_name, value) + "#{scope}\n#{key}\n#{value_name}\n#{value}" + end + + # Convert lgpo_import.txt to lgpo_import.pol with lgpo.exe + def convert_to_pol(file) + pol_file = file.sub(/\.txt$/,'.pol') + lgpo_args = ['/r', file, '/w', pol_file] + lgpo(*lgpo_args) + File.delete(file) + pol_file + end + + # import lgpo_import.pol with lgpo.exe + def import_pol(file, scope, guid) + lgpo_args = ["/#{scope[0]}", file] + lgpo_args << '/e' << guid if guid + lgpo(*lgpo_args) + File.delete(file) end def set_value(val) @@ -110,32 +140,56 @@ def set_value(val) raise Puppet::Error, "Wrong path: '#{path}'" end - out_scope = scope == 'machine' ? 'computer' : scope - - delete_value = setting_valuetype == '[HASHTABLE]' ? 'DELETEALLVALUES' : 'DELETE' + out_scope = (scope == 'machine' ? 'computer' : scope).capitalize - real_val = val == 'DELETE' ? delete_value : "#{path['setting_valuetype'].gsub('REG_', '')}:#{val}" - setting_valuename = real_val == 'DELETEALLVALUES' ? '*' : path['setting_valuename'] + out = Array.new + if setting_valuetype == '[HASHTABLE]' + if val == 'DELETE' + out << out_line(out_scope, path['setting_key'], '*', 'DELETEALLVALUES') + else + val.each do |k, v| + out << out_line(out_scope, path['setting_key'], k, "SZ:#{v}") + end + end + else + val = "#{path['setting_valuetype'].gsub('REG_', '')}:#{val}" unless val == 'DELETE' + out << out_line(out_scope, path['setting_key'], path['setting_valuename'], val) + end - out = "#{out_scope}\n#{path['setting_key']}\n#{setting_valuename}\n#{real_val}" + # If it is a hash table we need to delete the current values in the pol file so that they can be updated + remove_key(path['setting_key'], scope, path['policy_cse']) if setting_valuetype == '[HASHTABLE]' out_file_path = File.join(Puppet[:vardir], 'lgpo_import.txt') - out_polfile_path = File.join(Puppet[:vardir], 'lgpo_import.pol') File.open(out_file_path, 'w') do |out_file| - out_file.write(out) + out_file.write(out.join("\n\n")) end + + out_polfile_path = convert_to_pol(out_file_path) + import_pol(out_polfile_path, scope, path['policy_cse']) + end - # Convert lgpo_import.txt to lgpo_import.pol with lgpo.exe - lgpo_args = ['/r', out_file_path, '/w', out_polfile_path] - lgpo(*lgpo_args) - File.delete(out_file_path) + def remove_key(key, scope, cse_guid) + # This function is removing content from the pol file so that hash values can be updated without carying over the old part + Puppet.debug "remove #{key} in scope #{scope}" + + system_pol_file = "C:\\Windows\\System32\\GroupPolicy\\#{scope.capitalize}\\Registry.pol" + return unless File.file?(system_pol_file) + + out_file = File.join(Puppet[:vardir], 'lgpo_import.txt') + gpos = lgpo('/parse', '/q', "/#{scope[0]}", system_pol_file) + # Parse file and remove key + new_gpos = gpos.split("\n\n").reject { |l| l.start_with? ';' } + .reject{ |l| l.split("\n")[1] == key } + File.write(out_file, new_gpos.join("\n\n")) + + # convert txt file to pol file + pol_file = convert_to_pol(out_file) - # import lgpo_import.pol with lgpo.exe - lgpo_args = ["/#{scope[0]}", out_polfile_path] - if guid = path['policy_cse'] - lgpo_args << '/e' << guid - end - lgpo(*lgpo_args) - File.delete(out_polfile_path) + # delete existing polfile + File.delete(system_pol_file) + + # apply pol file to + import_pol(pol_file, scope, cse_guid) end end + diff --git a/lib/puppet/type/gpo.rb b/lib/puppet/type/gpo.rb index 253674f..bd99802 100644 --- a/lib/puppet/type/gpo.rb +++ b/lib/puppet/type/gpo.rb @@ -12,6 +12,11 @@ newvalue(:deleted) do provider.delete end + + def insync?(is) + return true if should == :deleted and provider.deleted? + super + end end newparam(:name, :namevar => true) do diff --git a/spec/unit/puppet/provider/gpo/lgpo_spec.rb b/spec/unit/puppet/provider/gpo/lgpo_spec.rb index b708070..ae6896a 100644 --- a/spec/unit/puppet/provider/gpo/lgpo_spec.rb +++ b/spec/unit/puppet/provider/gpo/lgpo_spec.rb @@ -61,6 +61,45 @@ def stub_create(scope, content, cse) expect(File).to receive(:delete).once.with(out_polfile).and_return(nil) end + def stub_hash_delete(scope, content, cse) + # This is the initial write to the gpo_import_file which writes the deleteallvalues statement for a hashed instance + lgpo_import_file = StringIO.new + expect(File).to receive(:open).once.with(out_file, 'w').and_yield(lgpo_import_file) + expect(lgpo_import_file).to receive(:write).with(content) + + # pol file should get read one time initiated by the lgpo.exe call + pol_file = "C:\\Windows\\System32\\GroupPolicy\\#{scope.capitalize}\\Registry.pol" + allow(File).to receive(:file?) # Catch all calls + expect(File).to receive(:file?).once.with(pol_file).and_return(true) + + # the stub for the pol lgpo call needs to be added here in order to simulate output + if true + provider.class.expects(:lgpo).once.with('/parse', '/q', "/#{scope[0]}", pol_file) + .returns(File.read(File.join( + File.dirname(__FILE__), + "../../../../fixtures/unit/puppet/provider/gpo/lgpo/#{scope}/full.out"))) + else + provider.class.expects(:lgpo).never + end + + # This is the subsequent writes to the lgpo file with the filtered content from the parsing + expect(File).to receive(:open).once.with(out_file, 'a').and_yield(lgpo_import_file) + expect(lgpo_import_file).to receive(:write).at_least(:once) + + args = ["/r", out_file] + args << '/w' << out_polfile + provider.class.expects(:lgpo).once.with(*args).returns(nil) + expect(File).to receive(:delete).once.with(out_file).and_return(nil) + + # Polfile needs to be deleted so a fresh import can be done from the filtered lgpo file + expect(File).to receive(:delete).once.with(pol_file).and_return(nil) + + args = ["/#{scope[0]}", out_polfile] + args << '/e' << cse unless cse.nil? + provider.class.expects(:lgpo).once.with(*args).returns(nil) + expect(File).to receive(:delete).once.with(out_polfile).and_return(nil) + end + context 'when listing instances' do context 'when the gpo file exists' do it 'should list instances' do @@ -175,7 +214,7 @@ def stub_create(scope, content, cse) context 'when there is no cse' do it 'should create a resource without /e' do - stub_create('machine', "computer\nSoftware\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU\nAllowMUUpdateService\nDWORD:1", nil) + stub_create('machine', "Computer\nSoftware\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU\nAllowMUUpdateService\nDWORD:1", nil) provider.create end @@ -191,8 +230,25 @@ def stub_create(scope, content, cse) end it 'should create a resource with /e' do - stub_create('machine', "computer\nSoftware\\Policies\\Microsoft Services\\AdmPwd\nAdmPwdEnabled\nDWORD:1", '{D76B9641-3288-4f75-942D-087DE603E3EA}') + stub_create('machine', "Computer\nSoftware\\Policies\\Microsoft Services\\AdmPwd\nAdmPwdEnabled\nDWORD:1", '{D76B9641-3288-4f75-942D-087DE603E3EA}') + + provider.create + end + end + + context 'when resource contain a hash value' do + let(:params) do + { + :title => 'windowsdefender::exclusions_processes::exclusions_processeslist', + :value => {'c:\windows\process0.exe' => '0', 'c:\windows\process1.exe' => '0',}, + :provider => 'lgpo', + } + end + it 'should create two entries in LGPO import file' do + stub_create('machine', "Computer\nSoftware\\Policies\\Microsoft\\Windows Defender\\Exclusions\\Processes\nc:\\windows\\process0.exe\nSZ:0\n\nComputer\nSoftware\\Policies\\Microsoft\\Windows Defender\\Exclusions\\Processes\nc:\\windows\\process1.exe\nSZ:0", nil) + pol_file = "C:\\Windows\\System32\\GroupPolicy\\Machine\\Registry.pol" + expect(File).to receive(:delete).once.with(pol_file).and_return(nil) provider.create end end @@ -205,38 +261,43 @@ def stub_create(scope, content, cse) context 'when there is no cse' do it 'should create a resource without /e' do - stub_create('machine', "computer\nSoftware\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU\nAllowMUUpdateService\nDELETE", nil) + stub_create('machine', "Computer\nSoftware\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU\nAllowMUUpdateService\nDELETE", nil) provider.delete end end - context 'when we need to delete a HASHTABLE instance' do + context 'when there is a cse' do let(:params) do { - :title => 'machine::windowsdefender::exclusions_processes::exclusions_processeslist', - :ensure => :deleted, + :title => 'admpwd::pol_admpwd_enabled::admpwdenabled', + :value => '1', :provider => 'lgpo', } end - it 'should create a resource without /e' do - stub_create('machine', "computer\nSoftware\\Policies\\Microsoft\\Windows Defender\\Exclusions\\Processes\n*\nDELETEALLVALUES", nil) + + it 'should create a resource with /e' do + stub_create('machine', "Computer\nSoftware\\Policies\\Microsoft Services\\AdmPwd\nAdmPwdEnabled\nDELETE", '{D76B9641-3288-4f75-942D-087DE603E3EA}') provider.delete end end - - context 'when there is a cse' do + end + context 'when deleting a hash resource' do + before :each do + expect(Puppet).to receive(:[]).exactly(3).times.with(:vardir).and_return('C:\ProgramData\PuppetLabs\Puppet\var') + end + + context 'when we need to delete a HASHTABLE instance' do let(:params) do { - :title => 'admpwd::pol_admpwd_enabled::admpwdenabled', - :value => '1', + :title => 'machine::windowsdefender::exclusions_processes::exclusions_processeslist', + :ensure => :deleted, :provider => 'lgpo', } end - - it 'should create a resource with /e' do - stub_create('machine', "computer\nSoftware\\Policies\\Microsoft Services\\AdmPwd\nAdmPwdEnabled\nDELETE", '{D76B9641-3288-4f75-942D-087DE603E3EA}') + it 'should create a resource without /e' do + stub_hash_delete('machine', "Computer\nSoftware\\Policies\\Microsoft\\Windows Defender\\Exclusions\\Processes\n*\nDELETEALLVALUES", nil) provider.delete end