diff --git a/CHANGELOG.md b/CHANGELOG.md index bebceb2..648fccc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ powershell Cookbook CHANGELOG ============================= This file is used to list changes made in each version of the powershell cookbook. +v3.2.0 (unreleased) +------------------- +- [**Annih**](https://github.com/Annih) + [PR #61](http://github.com/chef-cookbooks/powershell/pull/61) - use the all in one ms_dotnet cookbook +- [**RyanJarv**](https://github.com/RyanJarv) + [PR #60](http://github.com/chef-cookbooks/powershell/pull/60) - Use Ruby to unzip to support 2008 R2 Server Core +- [**Aliasgar16**](https://Aliasgar16) + [PR #57](http://github.com/chef-cookbooks/powershell/pull/57) - Add LCM configuration recipes + v3.1.0 (2015-04-27) ------------------- - [**webframp**](https://github.com/webframp) diff --git a/README.md b/README.md index df3653d..dac520d 100644 --- a/README.md +++ b/README.md @@ -72,9 +72,7 @@ Not every version of Windows supports every version of Powershell. The following PowerShell also requires the appropriate version of the Microsoft .NET Framework to be installed, if the operating system does not ship with that version. The following community cookbooks are used to install the correct version of the .NET Framework: -* ms_dotnet2 -* ms_dotnet4 -* ms_dotnet45 +* ms_dotnet Resource/Provider ----------------- @@ -171,7 +169,8 @@ end ### `powershell_module` -Installs or uninstalls a Powershell module +Installs or uninstalls a Powershell module. You either need to install rubyzip with chef_gem or +include the default recipe before using this resource. #### Actions @@ -188,6 +187,8 @@ Installs or uninstalls a Powershell module #### Examples ```ruby +include_recipe 'powershell::default' + # Install module from local directory path # change the package_name and source powershell_module "PsUrl" do @@ -231,7 +232,7 @@ Usage ### default -The default recipe does nothing. +The default recipe is needs to be included before using the powershell_module resource. ### powershell2 diff --git a/attributes/config_lcm.rb b/attributes/config_lcm.rb new file mode 100644 index 0000000..f6c8a99 --- /dev/null +++ b/attributes/config_lcm.rb @@ -0,0 +1,9 @@ + +## temporary directory related attributes ## +default['lcm']['mof']['temp_dir'] = "#{Chef::Config[:file_cache_path]}\\lcm_mof" + +## lcm configuration related attributes ## +default['lcm']['config']['enable']['config_mode'] = 'ApplyOnly' +default['lcm']['config']['enable']['reboot_node'] = false +default['lcm']['config']['enable']['refresh_mode'] = 'Push' +default['lcm']['config']['disable']['refresh_mode'] = 'Disabled' diff --git a/libraries/powershell_module_provider.rb b/libraries/powershell_module_provider.rb index e69c9c6..b70b09d 100644 --- a/libraries/powershell_module_provider.rb +++ b/libraries/powershell_module_provider.rb @@ -65,13 +65,7 @@ def install_module FileUtils.cp(filename, ps_module_path) end elsif @new_resource.source =~ URI.regexp # Check for valid URL - downloaded_file = download_extract_module - - if ::File.exist?(downloaded_file) - Chef::Log.debug("Powershell Module '#{@powershell_module.package_name}' removing download #{downloaded_file}") - FileUtils.rm_f(downloaded_file) - end - + download_extract_module end end @@ -85,6 +79,39 @@ def uninstall_module end end + def download(download_url, target) + uri = URI(download_url) + + Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http| + f = open(target, 'wb') + http.request_get(uri.path) do |resp| + resp.read_body do |segment| + f.write(segment) + end + end + + f.close + end + end + + def unzip(zip_file, target_directory) + begin + require 'zip' + rescue LoadError + raise( + 'Could not load the rubyzip gem, please make sure this gem is installed with the "chef_gem" resource ' \ + 'or include the powershell::default recipe before using powershell_module.' + ) + end + + Zip::File.open(zip_file) do |zip| + zip.each do |entry| + FileUtils.mkdir_p(::File.join(target_directory, ::File.dirname(entry.name))) + entry.extract(::File.join(target_directory, entry.name)) + end + end + end + def download_extract_module(download_url = nil, target = nil) filename = @new_resource.package_name + '.zip' @@ -97,19 +124,20 @@ def download_extract_module(download_url = nil, target = nil) ps_module_path = sanitize! @new_resource.destination Chef::Log.debug("Powershell Module ps_module_path is #{ps_module_path}") - cmd_str = "powershell.exe Invoke-WebRequest #{download_url} -OutFile #{target}; $shell = new-object -com shell.application;$zip = $shell.NameSpace('#{target.gsub('/', '\\\\')}'); $shell.Namespace('#{ps_module_path}').copyhere($zip.items(), 0x14);write-host $shell" - installed_module = module_exists?(ps_module_path, "*#{@new_resource.package_name}*") - if installed_module Chef::Log.info("Powershell Module #{@new_resource.package_name} already installed.") Chef::Log.info("Remove path at #{ps_module_path}\\#{installed_module} to reinstall.") else - ps_cmd = Mixlib::ShellOut.new(cmd_str) - ps_cmd.run_command + download(download_url, target) + unzip(target, ps_module_path) + remove_download(target) end + end - target + def remove_download(target) + Chef::Log.debug("Powershell Module '#{@powershell_module.package_name}' removing download #{downloaded_file}") + FileUtils.rm_f(downloaded_file) if ::File.exist?(target) end def module_path_name diff --git a/metadata.rb b/metadata.rb index 4fd47d7..e6f352f 100644 --- a/metadata.rb +++ b/metadata.rb @@ -4,21 +4,21 @@ license 'Apache 2.0' description 'Installs/Configures PowerShell on the Windows platform' long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) -version '3.1.0' +version '3.2.0' -recipe 'powershell::default', 'Does nothing; choose the right version of Powershell by selecting the correct recipe' +recipe 'powershell::default', 'Makes sure RubyZip is installed (for powershell_module)' recipe 'powershell::powershell2', 'Installs PowerShell 2.0' recipe 'powershell::powershell3', 'Installs PowerShell 3.0' recipe 'powershell::powershell4', 'Installs PowerShell 4.0' recipe 'powershell::powershell5', 'Installs PowerShell 5.0' recipe 'powershell::winrm', 'Configures WinRM' recipe 'powershell::dsc', 'Desired State Configuration' +recipe 'powershell::enable_lcm', 'Enable the DSC Local Configuration Manager' +recipe 'powershell::disable_lcm', 'Disable the DSC Local Configuration Manager' supports 'windows' depends 'windows', '>= 1.2.8' -depends 'ms_dotnet45' -depends 'ms_dotnet4' -depends 'ms_dotnet2' +depends 'ms_dotnet', '>= 2.6' depends 'chef_handler' source_url 'https://github.com/chef-cookbooks/powershell' if respond_to?(:source_url) diff --git a/recipes/default.rb b/recipes/default.rb index 820ec12..b1dc26a 100644 --- a/recipes/default.rb +++ b/recipes/default.rb @@ -18,3 +18,5 @@ # See the License for the specific language governing permissions and # limitations under the License. # + +chef_gem 'rubyzip' diff --git a/recipes/disable_lcm.rb b/recipes/disable_lcm.rb new file mode 100644 index 0000000..1ab7fb5 --- /dev/null +++ b/recipes/disable_lcm.rb @@ -0,0 +1,63 @@ +# +# Author:: Aliasgar Batterywala () +# Cookbook Name:: powershell +# Recipe:: disable_lcm +# +# Copyright:: Copyright (c) 2015 Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include_recipe 'powershell::powershell5' +include_recipe 'powershell::dsc' + +case node['platform'] +when 'windows' + + directory 'Creating temporary directory to store LCM MOF files' do + path node['lcm']['mof']['temp_dir'] + rights :read, 'Everyone' + action :create + end + + powershell_script 'Disable LCM' do + code <<-EOH + + Configuration DisableLCM + { + Node "localhost" + { + LocalConfigurationManager + { + RefreshMode = "#{node['lcm']['config']['disable']['refresh_mode']}" + } + } + } + + DisableLCM -OutputPath "#{node['lcm']['mof']['temp_dir']}" + + Set-DscLocalConfigurationManager -Path "#{node['lcm']['mof']['temp_dir']}" + EOH + not_if <<-EOH + $LCM = (Get-DscLocalConfigurationManager) + $LCM.RefreshMode -eq "#{node['lcm']['config']['disable']['refresh_mode']}" + EOH + end + + directory 'Deleting temporary directory which stored LCM MOF files' do + path node['lcm']['mof']['temp_dir'] + recursive true + action :delete + end +else + Chef::Log.warn('LCM configuration can only be executed on the Windows platform.') +end diff --git a/recipes/enable_dsc_script.rb b/recipes/enable_dsc_script.rb new file mode 100644 index 0000000..966b8e0 --- /dev/null +++ b/recipes/enable_dsc_script.rb @@ -0,0 +1,20 @@ +# +# Author:: Aliasgar Batterywala () +# Cookbook Name:: powershell +# Recipe:: enable_lcm +# +# Copyright:: Copyright (c) 2015 Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include_recipe 'powershell::enable_lcm' diff --git a/recipes/enable_lcm.rb b/recipes/enable_lcm.rb new file mode 100644 index 0000000..b409360 --- /dev/null +++ b/recipes/enable_lcm.rb @@ -0,0 +1,73 @@ +# +# Author:: Aliasgar Batterywala () +# Cookbook Name:: powershell +# Recipe:: enable_lcm +# +# Copyright:: Copyright (c) 2015 Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include_recipe 'powershell::dsc' + +case node['platform'] +when 'windows' + + directory 'Creating temporary directory to store LCM MOF files' do + path node['lcm']['mof']['temp_dir'] + rights :read, 'Everyone' + action :create + guard_interpreter :powershell_script + not_if <<-EOH + $LCM = (Get-DscLocalConfigurationManager) + $LCM.ConfigurationMode -eq "#{node['lcm']['config']['enable']['config_mode']}" -and + $LCM.RefreshMode -eq "#{node['lcm']['config']['enable']['refresh_mode']}" + EOH + end + + directory 'Deleting temporary directory which stored LCM MOF files' do + path node['lcm']['mof']['temp_dir'] + recursive true + action :nothing + end + + powershell_script 'Configure and Enable LCM' do + code <<-EOH + + Configuration EnableLCM + { + Node "localhost" + { + LocalConfigurationManager + { + ConfigurationMode = "#{node['lcm']['config']['enable']['config_mode']}" + RebootNodeIfNeeded = $#{node['lcm']['config']['enable']['reboot_node']} + RefreshMode = "#{node['lcm']['config']['enable']['refresh_mode']}" + } + } + } + + EnableLCM -OutputPath "#{node['lcm']['mof']['temp_dir']}" + + Set-DscLocalConfigurationManager -Path "#{node['lcm']['mof']['temp_dir']}" + EOH + not_if <<-EOH + $LCM = (Get-DscLocalConfigurationManager) + $LCM.ConfigurationMode -eq "#{node['lcm']['config']['enable']['config_mode']}" -and + $LCM.RefreshMode -eq "#{node['lcm']['config']['enable']['refresh_mode']}" + EOH + notifies :delete, 'directory[Deleting temporary directory which stored LCM MOF files]', :immediately + end + +else + Chef::Log.warn('LCM configuration can only be executed on the Windows platform.') +end diff --git a/recipes/powershell2.rb b/recipes/powershell2.rb index 7c5fef0..6468440 100644 --- a/recipes/powershell2.rb +++ b/recipes/powershell2.rb @@ -23,48 +23,30 @@ case node['platform'] when 'windows' + nt_version = ::Windows::VersionHelper.nt_version(node) - require 'chef/win32/version' - windows_version = Chef::ReservedNames::Win32::Version.new + include_recipe 'ms_dotnet::ms_dotnet2' - if (windows_version.windows_server_2012? || windows_version.windows_8?) && windows_version.core? - # Windows Server 2012 Core does not come with Powershell 2.0 enabled + if nt_version.between?(6.1, 6.2) && ::Windows::VersionHelper.core_version?(node) + feature_suffix = 'V2' if nt_version == 6.2 - windows_feature 'MicrosoftWindowsPowerShellV2' do + windows_feature "MicrosoftWindowsPowerShell#{feature_suffix}" do action :install end - windows_feature 'MicrosoftWindowsPowerShellV2-WOW64' do - action :install - only_if { node['kernel']['machine'] == 'x86_64' } - end - - elsif (windows_version.windows_server_2008_r2? || windows_version.windows_7?) && windows_version.core? - # Windows Server 2008 R2 Core does not come with .NET or Powershell 2.0 enabled - windows_feature 'NetFx2-ServerCore' do - action :install - end - windows_feature 'NetFx2-ServerCore-WOW64' do + windows_feature "MicrosoftWindowsPowerShell#{feature_suffix}-WOW64" do action :install only_if { node['kernel']['machine'] == 'x86_64' } end - windows_feature 'MicrosoftWindowsPowerShell' do - action :install - end - windows_feature 'MicrosoftWindowsPowerShell-WOW64' do - action :install - only_if { node['kernel']['machine'] == 'x86_64' } - end - - elsif windows_version.windows_server_2008? || windows_version.windows_server_2003_r2? || - windows_version.windows_server_2003? || windows_version.windows_xp? - - include_recipe 'ms_dotnet2' - # Reboot if user specifies doesn't specify no_reboot + # WMF 2.0 is required and only compatible with: + # * Windows NT 5.1 & 5.2 (Windows Server 2003 & Windows XP) + # * Windows NT 6.0 server (Windows Server 2008 SP2 not vista) + elsif nt_version.between?(5.1, 5.2) || (nt_version == 6.0 && ::Windows::VersionHelper.server_version?(node)) + # Reboot if user doesn't specify no_reboot include_recipe 'powershell::windows_reboot' unless node['powershell']['installation_reboot_mode'] == 'no_reboot' - windows_package 'Windows Management Framework Core' do + windows_package 'Windows Management Framework Core' do # ~FC009 source node['powershell']['powershell2']['url'] checksum node['powershell']['powershell2']['checksum'] installer_type :custom diff --git a/recipes/powershell3.rb b/recipes/powershell3.rb index b1786a4..ed983b2 100644 --- a/recipes/powershell3.rb +++ b/recipes/powershell3.rb @@ -24,13 +24,15 @@ case node['platform'] when 'windows' - require 'chef/win32/version' - windows_version = Chef::ReservedNames::Win32::Version.new + nt_version = ::Windows::VersionHelper.nt_version(node) - if windows_version.windows_server_2008? || windows_version.windows_server_2008_r2? || windows_version.windows_7? + # Powershell 3.0 is only compatible with: + # * Windows NT 6.0 server (Windows Server 2008 SP2 not vista) + # * Windows NT 6.1 (Windows Server 2008R2 & Windows 7.1) + if (nt_version == 6.0 && ::Windows::VersionHelper.server_version?(node)) || nt_version == 6.1 # For Windows Server 2008 ensure that Powershell 2 is already installed and so is BITS 4.0 - if windows_version.windows_server_2008? + if nt_version == 6.0 && ::Windows::VersionHelper.server_version?(node) include_recipe 'powershell::powershell2' windows_package 'Windows Management Framework Bits' do @@ -43,12 +45,12 @@ end # WMF 3.0 requires .NET 4.0 - include_recipe 'ms_dotnet4' + include_recipe 'ms_dotnet::ms_dotnet4' # Reboot if user specifies doesn't specify no_reboot include_recipe 'powershell::windows_reboot' unless node['powershell']['installation_reboot_mode'] == 'no_reboot' - windows_package 'Windows Management Framework Core 3.0' do + windows_package 'Windows Management Framework Core 3.0' do # ~FC009 source node['powershell']['powershell3']['url'] checksum node['powershell']['powershell3']['checksum'] installer_type :custom diff --git a/recipes/powershell4.rb b/recipes/powershell4.rb index a9008a2..4e2915e 100644 --- a/recipes/powershell4.rb +++ b/recipes/powershell4.rb @@ -22,18 +22,21 @@ # http://www.microsoft.com/en-us/download/details.aspx?id=40855 if node['platform'] == 'windows' - require 'chef/win32/version' - windows_version = Chef::ReservedNames::Win32::Version.new - if windows_version.windows_server_2008_r2? || windows_version.windows_7? || windows_version.windows_server_2012? + nt_version = ::Windows::VersionHelper.nt_version(node) + # WMF 4.0 is only compatible with: + # * Windows NT 6.1 (Windows Server 2008R2 & Windows 7.1) + # * Windows NT 6.2 Server (Windows Server 2012 not Windows 8) + if nt_version == 6.1 || (nt_version == 6.2 && ::Windows::VersionHelper.server_version?(node)) - # Ensure .NET 4.5 is installed or installation will fail silently per Microsoft. Only necessary for Windows 2008R2 or 7. - include_recipe 'ms_dotnet45' if windows_version.windows_server_2008_r2? || windows_version.windows_7? + # Ensure .NET 4.5 is installed or installation will fail silently per Microsoft. + fail 'Attribute ms_dotnet.v4.version is not configured to install .NET4.5 as required for Powershell4' if node['ms_dotnet']['v4']['version'] < '4.5' + include_recipe 'ms_dotnet::ms_dotnet4' # Reboot if user specifies doesn't specify no_reboot include_recipe 'powershell::windows_reboot' unless node['powershell']['installation_reboot_mode'] == 'no_reboot' - windows_package 'Windows Management Framework Core4.0' do + windows_package 'Windows Management Framework Core 4.0' do # ~FC009 source node['powershell']['powershell4']['url'] checksum node['powershell']['powershell4']['checksum'] installer_type :custom diff --git a/recipes/powershell5.rb b/recipes/powershell5.rb index 2ccda0f..c5a643a 100644 --- a/recipes/powershell5.rb +++ b/recipes/powershell5.rb @@ -21,19 +21,16 @@ # PowerShell 5.0 Preview Download Page # http://www.microsoft.com/en-us/download/details.aspx?id=42316 -include_recipe 'powershell::powershell2' - case node['platform'] when 'windows' - require 'chef/win32/version' - windows_version = Chef::ReservedNames::Win32::Version.new - - if windows_version.windows_server_2012_r2? || windows_version.windows_8_1? + # Handle WMFC install on 2012R2 and 8.1 only (yet) + if ::Windows::VersionHelper.nt_version(node) == 6.3 + include_recipe 'powershell::powershell2' include_recipe 'powershell::windows_reboot' unless node['powershell']['installation_reboot_mode'] == 'no_reboot' - windows_package 'Windows Management Framework Core 5.0' do + windows_package 'Windows Management Framework Core 5.0' do # ~FC009 source node['powershell']['powershell5']['url'] checksum node['powershell']['powershell5']['checksum'] installer_type :custom diff --git a/recipes/winrm.rb b/recipes/winrm.rb index 1b7875e..9ff973b 100644 --- a/recipes/winrm.rb +++ b/recipes/winrm.rb @@ -34,15 +34,13 @@ shell_out = Mixlib::ShellOut.new(winrm_cmd) shell_out.run_command - if !shell_out.stdout.include? 'Transport = HTTPS' - # Create HTTPS listener - if node['powershell']['winrm']['enable_https_transport'] - if node['powershell']['winrm']['thumbprint'].empty? || node['powershell']['winrm']['thumbprint'].nil? - Chef::Log.error('Please specify thumbprint in default attributes for enabling https transport.') - else - powershell_script 'winrm-create-https-listener' do - code "winrm create 'winrm/config/Listener?Address=*+Transport=HTTPS' '@{Hostname=\"#{node['powershell']['winrm']['hostname']}\"; CertificateThumbprint=\"#{node['powershell']['winrm']['thumbprint']}\"}'" - end + # Create HTTPS listener + if !shell_out.stdout.include?('Transport = HTTPS') && node['powershell']['winrm']['enable_https_transport'] + if node['powershell']['winrm']['thumbprint'].nil? || node['powershell']['winrm']['thumbprint'].empty? + Chef::Log.error('Please specify thumbprint in default attributes for enabling https transport.') + else + powershell_script 'winrm-create-https-listener' do + code "winrm create 'winrm/config/Listener?Address=*+Transport=HTTPS' '@{Hostname=\"#{node['powershell']['winrm']['hostname']}\"; CertificateThumbprint=\"#{node['powershell']['winrm']['thumbprint']}\"}'" end end else diff --git a/spec/libraries/powershell_module_provider_spec.rb b/spec/libraries/powershell_module_provider_spec.rb index 768e6d6..c843537 100644 --- a/spec/libraries/powershell_module_provider_spec.rb +++ b/spec/libraries/powershell_module_provider_spec.rb @@ -17,14 +17,17 @@ # require_relative '../../libraries/powershell_module_provider' -require 'tmpdir' describe 'PowershellModuleProvider' do before do + allow(Chef::Config).to receive(:[]).with(:file_cache_path).and_return 'C:/tmp' + allow(Chef::Config).to receive(:[]).with(:why_run).and_return false + @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = PowershellModule.new('testmodule', @run_context) + @new_resource.destination '/' @provider = PowershellModuleProvider.new(@new_resource, @run_context) end @@ -37,7 +40,7 @@ describe 'action_uninstall:' do it 'uninstall module' do - @provider.should_receive(:uninstall_module) + expect(@provider).to receive(:uninstall_module) expect { @provider.run_action(:uninstall) }.to_not raise_error end end @@ -67,78 +70,46 @@ end describe 'download_extract_module:' do - context 'when download_url and target are nil' do - before do - ENV['PROGRAMW6432'] = 'C:\\PROGRAMW6432' - @ps_cmd = double - end + before do + allow(@provider).to receive(:module_exists?).and_return false + allow(@provider).to receive(:unzip) + allow(@provider).to receive(:remove_download) + end + context 'when download_url and target are nil' do it 'downloads the package' do - expect(Dir).to receive(:mktmpdir).and_return('C:/tmp/') - cmd_str = "powershell.exe Invoke-WebRequest testmodule -OutFile C:/tmp/testmodule.zip; $shell = new-object -com shell.application;$zip = $shell.NameSpace('C:\\tmp\\testmodule.zip'); $shell.Namespace('C:\\PROGRAMW6432\\WindowsPowerShell\\Modules').copyhere($zip.items(), 0x14);write-host $shell" - expect(Mixlib::ShellOut).to receive(:new).with(cmd_str).and_return(@ps_cmd) - expect(@ps_cmd).to receive(:run_command) - - expect(@provider.send(:download_extract_module)).to eq('C:/tmp/testmodule.zip') + expect(@provider).to receive(:download).with('testmodule', 'C:/tmp/testmodule.zip') + @provider.send(:download_extract_module) end end context 'when download_url is provided and target is nil' do - before do - ENV['PROGRAMW6432'] = 'C:\\PROGRAMW6432' - @ps_cmd = double - end - it 'downloads the package' do - expect(Dir).to receive(:mktmpdir).and_return('C:/tmp/') - cmd_str = "powershell.exe Invoke-WebRequest https://temp_download.com -OutFile C:/tmp/testmodule.zip; $shell = new-object -com shell.application;$zip = $shell.NameSpace('C:\\tmp\\testmodule.zip'); $shell.Namespace('C:\\PROGRAMW6432\\WindowsPowerShell\\Modules').copyhere($zip.items(), 0x14);write-host $shell" - expect(Mixlib::ShellOut).to receive(:new).with(cmd_str).and_return(@ps_cmd) - expect(@ps_cmd).to receive(:run_command) - - expect(@provider.send(:download_extract_module, 'https://temp_download.com')).to eq('C:/tmp/testmodule.zip') + expect(@provider).to receive(:download_extract_module).with('https://temp_download.com') + @provider.send(:download_extract_module, 'https://temp_download.com') end end context 'when download_url is nil and target is provided' do - before do - ENV['PROGRAMW6432'] = 'C:\\PROGRAMW6432' - @ps_cmd = double - end - it 'downloads the package' do - cmd_str = "powershell.exe Invoke-WebRequest testmodule -OutFile tmp/target1.zip; $shell = new-object -com shell.application;$zip = $shell.NameSpace('tmp\\target1.zip'); $shell.Namespace('C:\\PROGRAMW6432\\WindowsPowerShell\\Modules').copyhere($zip.items(), 0x14);write-host $shell" - expect(Mixlib::ShellOut).to receive(:new).with(cmd_str).and_return(@ps_cmd) - expect(@ps_cmd).to receive(:run_command) - - expect(@provider.send(:download_extract_module, nil, 'tmp/target1.zip')).to eq('tmp/target1.zip') + expect(@provider).to receive(:download).with('testmodule', 'C:/other_tmp/testmodule.zip') + @provider.send(:download_extract_module, nil, 'C:/other_tmp/testmodule.zip') end end context 'when download_url and target are provided' do - before do - ENV['PROGRAMW6432'] = 'C:\\PROGRAMW6432' - @ps_cmd = double - end - it 'downloads the package' do - cmd_str = "powershell.exe Invoke-WebRequest https://temp_download.com -OutFile tmp/target1.zip; $shell = new-object -com shell.application;$zip = $shell.NameSpace('tmp\\target1.zip'); $shell.Namespace('C:\\PROGRAMW6432\\WindowsPowerShell\\Modules').copyhere($zip.items(), 0x14);write-host $shell" - expect(Mixlib::ShellOut).to receive(:new).with(cmd_str).and_return(@ps_cmd) - expect(@ps_cmd).to receive(:run_command) + expect(@provider).to receive(:download).with('https://temp_download.com', 'tmp/target1.zip') - expect(@provider.send(:download_extract_module, 'https://temp_download.com', 'tmp/target1.zip')).to eq('tmp/target1.zip') + @provider.send(:download_extract_module, 'https://temp_download.com', 'tmp/target1.zip') end end end describe 'uninstall_module:' do context 'when module directory exists' do - before do - ENV['PROGRAMW6432'] = 'C:/PROGRAMW6432' - @ps_cmd = double - end - it 'uninstalls module' do - module_dir = 'C:/PROGRAMW6432/WindowsPowerShell/Modules/testmodule' + module_dir = '/testmodule' expect(Dir).to receive(:exist?).with(module_dir).and_return(true) expect(FileUtils).to receive(:rm_rf).with(module_dir) expect(Chef::Log).to receive(:info).with("Powershell Module 'testmodule' uninstallation completed successfully") @@ -148,13 +119,8 @@ end context 'when module directory does not exist' do - before do - ENV['PROGRAMW6432'] = 'C:/PROGRAMW6432' - @ps_cmd = double - end - it 'logs message' do - module_dir = 'C:/PROGRAMW6432/WindowsPowerShell/Modules/testmodule' + module_dir = '/testmodule' expect(Dir).to receive(:exist?).with(module_dir).and_return(false) expect(Chef::Log).to receive(:info).with("Unable to locate module 'testmodule'") @@ -164,32 +130,18 @@ end describe 'install_module:' do - before do - ENV['PROGRAMW6432'] = 'C:/PROGRAMW6432' - @ps_cmd = double - end - context 'install from local source' do - before do - @dir = Dir.tmpdir + '/testmodule' - FileUtils.mkdir_p(@dir) unless File.directory?(@dir) - @module_files = ["#{@dir}/test.psd1", "#{@dir}/test.psm1", "#{@dir}/test.dll"] - @module_files.each do |file| - File.new("#{file}", 'w+') - end - end - - after do - FileUtils.rm_rf(@dir) if File.directory?(@dir) - end - it 'copies module from source to ps module path' do - @new_resource.source(@dir) - ps_module_path = 'C:/PROGRAMW6432/WindowsPowerShell/Modules/testmodule' - expect(Dir).to receive(:exist?).with('/tmp/testmodule').and_return(true) + ps_module_path = '/testmodule' + @new_resource.source(ps_module_path) + + module_files = %W(#{ps_module_path}/*.psd1 #{ps_module_path}/*.psm1 #{ps_module_path}/*.dll) + expect(Dir).to receive(:exist?).with(ps_module_path).and_return(true) expect(FileUtils).to receive(:mkdir_p).with(ps_module_path).and_return(["#{ps_module_path}"]) - @module_files.each do |filename| + expect(Dir).to receive(:[]).with(*module_files).and_return module_files + + module_files.each do |filename| expect(FileUtils).to receive(:cp).with(filename, ps_module_path) end @@ -198,9 +150,12 @@ end context 'source is a url' do it 'downloads module from source and install' do - @new_resource.source('https://testmodule.com') - expect(@provider).to receive(:download_extract_module).and_return('C:/tmp/testmodule') - expect(FileUtils).to receive(:rm_rf).with('C:/tmp') + source = 'https:/testmodule.com' + @new_resource.source source + + expect(Dir).to receive(:exist?).with(source).and_return(false) + expect(@provider).to receive(:download_extract_module) + @provider.send(:install_module) end end diff --git a/spec/recipes/disable_lcm_spec.rb b/spec/recipes/disable_lcm_spec.rb new file mode 100644 index 0000000..e220680 --- /dev/null +++ b/spec/recipes/disable_lcm_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' +require 'chefspec' + +describe 'powershell::disable_lcm' do + let(:chef_run) do + ChefSpec::SoloRunner.new(platform: 'windows', version: '2012') do |node| + node.set['powershell']['powershell5']['url'] = 'https://powershelltest.com' + node.set['powershell']['powershell5']['checksum'] = '12345' + node.set['lcm']['mof']['temp_dir'] = 'c:\\chef\\cache\\lcm_mof' + node.set['lcm']['config']['disable']['refresh_mode'] = 'Disabled' + end.converge(described_recipe) + end + + before do + @config_code = <<-EOH + + Configuration DisableLCM + { + Node "localhost" + { + LocalConfigurationManager + { + RefreshMode = "Disabled" + } + } + } + + DisableLCM -OutputPath "c:\\chef\\cache\\lcm_mof" + + Set-DscLocalConfigurationManager -Path "c:\\chef\\cache\\lcm_mof" + EOH + + guard_condition = <<-EOH + $LCM = (Get-DscLocalConfigurationManager) + $LCM.RefreshMode -eq "Disabled" + EOH + stub_command(guard_condition).and_return(false) + allow_any_instance_of(Chef::Recipe).to receive(:include_recipe).with('powershell::powershell5').and_return(true) + allow_any_instance_of(Chef::Recipe).to receive(:include_recipe).with('powershell::dsc').and_return(true) + end + + it 'disables LCM', skip: not_windows? do + expect(chef_run).to create_directory('Creating temporary directory to store LCM MOF files').with( + path: 'c:\\chef\\cache\\lcm_mof', + rights: [permissions: :read, principals: 'Everyone'] + ) + expect(chef_run).to run_powershell_script('Disable LCM').with( + code: @config_code + ) + expect(chef_run).to delete_directory('Deleting temporary directory which stored LCM MOF files').with( + path: 'c:\\chef\\cache\\lcm_mof', + recursive: true + ) + end +end diff --git a/spec/recipes/dsc_spec.rb b/spec/recipes/dsc_spec.rb index 8cef16c..053fbf1 100644 --- a/spec/recipes/dsc_spec.rb +++ b/spec/recipes/dsc_spec.rb @@ -2,16 +2,22 @@ require 'mixlib/shellout' describe 'powershell::dsc' do - let(:chef_run) { ChefSpec::SoloRunner.new(platform: 'windows', version: '2012').converge(described_recipe) } + let(:chef_run) do + ChefSpec::SoloRunner.new(platform: 'windows', version: '2012')do |node| + node.set['ms_dotnet']['v4']['version'] = '4.5' + end.converge(described_recipe) + end + + before do + winrm_cmd = double('winrm_cmd', run_command: nil, stdout: 'Transport = HTTPS') + allow(Mixlib::ShellOut).to receive(:new).with('powershell.exe winrm enumerate winrm/config/listener').and_return winrm_cmd + allow(Chef::Win32::Registry).to receive(:new).and_return double('registry', data_exists?: false, value_exists?: false, key_exists?: false) + end context 'When listener is enabled' do before do - command = 'powershell.exe winrm get winrm/config/listener?Address=*+Transport=HTTP' - - shell_obj = instance_double('Mixlib::ShellOut') - allow(Mixlib::ShellOut).to receive(:new).with(command).and_return(shell_obj) - allow(shell_obj).to receive(:run_command) - allow(shell_obj).to receive(:exitstatus).and_return(1) + dsc_cmd = double('dsc_cmd', run_command: nil, exitstatus: 1) + allow(Mixlib::ShellOut).to receive(:new).with('powershell.exe winrm get winrm/config/listener?Address=*+Transport=HTTP').and_return dsc_cmd end it 'runs dsc_script' do @@ -22,12 +28,8 @@ context 'When listener is disabled' do before do - command = 'powershell.exe winrm get winrm/config/listener?Address=*+Transport=HTTP' - - shell_obj = instance_double('Mixlib::ShellOut') - allow(Mixlib::ShellOut).to receive(:new).with(command).and_return(shell_obj) - allow(shell_obj).to receive(:run_command) - allow(shell_obj).to receive(:exitstatus).and_return(0) + dsc_cmd = double('dsc_cmd', run_command: nil, exitstatus: 0) + allow(Mixlib::ShellOut).to receive(:new).with('powershell.exe winrm get winrm/config/listener?Address=*+Transport=HTTP').and_return dsc_cmd end it 'runs dsc_script' do diff --git a/spec/recipes/enable_dsc_script_spec.rb b/spec/recipes/enable_dsc_script_spec.rb new file mode 100644 index 0000000..f155892 --- /dev/null +++ b/spec/recipes/enable_dsc_script_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' +require 'chefspec' + +describe 'powershell::enable_dsc_script' do + let(:chef_run) do + ChefSpec::SoloRunner.new(platform: 'windows', version: '2012') do |node| + node.set['ms_dotnet']['v4']['version'] = '4.5' + end.converge(described_recipe) + end + + before do + guard_condition = <<-EOH + $LCM = (Get-DscLocalConfigurationManager) + $LCM.ConfigurationMode -eq "ApplyOnly" -and + $LCM.RefreshMode -eq "Push" + EOH + stub_command(guard_condition).and_return(false) + end + + it 'includes enable_lcm recipe', skip: not_windows? do + expect(chef_run).to include_recipe('powershell::enable_lcm') + end +end diff --git a/spec/recipes/enable_lcm_spec.rb b/spec/recipes/enable_lcm_spec.rb new file mode 100644 index 0000000..f1ed8d7 --- /dev/null +++ b/spec/recipes/enable_lcm_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' +require 'chefspec' + +describe 'powershell::enable_lcm' do + let(:chef_run) do + ChefSpec::SoloRunner.new(platform: 'windows', version: '2012') do |node| + node.set['lcm']['mof']['temp_dir'] = 'c:\\chef\\cache\\lcm_mof' + node.set['lcm']['config']['enable']['config_mode'] = 'ApplyOnly' + node.set['lcm']['config']['enable']['reboot_node'] = false + node.set['lcm']['config']['enable']['refresh_mode'] = 'Push' + end.converge(described_recipe) + end + + before do + @config_code = <<-EOH + + Configuration EnableLCM + { + Node "localhost" + { + LocalConfigurationManager + { + ConfigurationMode = "ApplyOnly" + RebootNodeIfNeeded = $false + RefreshMode = "Push" + } + } + } + + EnableLCM -OutputPath "c:\\chef\\cache\\lcm_mof" + + Set-DscLocalConfigurationManager -Path "c:\\chef\\cache\\lcm_mof" + EOH + + guard_condition = <<-EOH + $LCM = (Get-DscLocalConfigurationManager) + $LCM.ConfigurationMode -eq "ApplyOnly" -and + $LCM.RefreshMode -eq "Push" + EOH + stub_command(guard_condition).and_return(false) + + allow_any_instance_of(Chef::Recipe).to receive(:include_recipe).with('powershell::dsc').and_return(true) + end + + it 'enables LCM', skip: not_windows? do + expect(chef_run).to create_directory('Creating temporary directory to store LCM MOF files').with( + path: 'c:\\chef\\cache\\lcm_mof' + ) + expect(chef_run).to run_powershell_script('Configure and Enable LCM').with( + code: @config_code + ) + expect(chef_run.powershell_script('Configure and Enable LCM')) + .to notify('directory[Deleting temporary directory which stored LCM MOF files]') + .to(:delete) + .immediately + end +end diff --git a/spec/recipes/powershell2_spec.rb b/spec/recipes/powershell2_spec.rb index d008b23..dccf35f 100644 --- a/spec/recipes/powershell2_spec.rb +++ b/spec/recipes/powershell2_spec.rb @@ -1,18 +1,11 @@ require 'spec_helper' -require 'chef/win32/version' describe 'powershell::powershell2' do - let(:chef_run) do - ChefSpec::SoloRunner.new(platform: 'windows', version: '2012') do |node| - node.set['powershell']['powershell2']['url'] = 'https://powershelltest.com' - node.set['powershell']['powershell2']['checksum'] = '12345' - end.converge(described_recipe) - end - - context 'when windows_version is windows_server_2012 and windows_version is core ' do - before do - @windows_version = double(windows_server_2012?: true, windows_8?: false, core?: true) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) + context 'on Windows Server 2012 Core' do + let(:chef_run) do + ChefSpec::SoloRunner.new(platform: 'windows', version: '2012') do |node| + node.automatic['kernel']['os_info']['operating_system_sku'] = 0x0D + end.converge(described_recipe) end it 'installs windows features' do @@ -21,25 +14,14 @@ end end - context 'when windows_version is windows_8 and windows_version is core ' do - before do - @windows_version = double(windows_server_2012?: false, windows_8?: true, core?: true) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) + context 'on Windows Server 2008R2 Core' do + let(:chef_run) do + ChefSpec::SoloRunner.new(platform: 'windows', version: '2008R2') do |node| + node.automatic['kernel']['os_info']['operating_system_sku'] = 0x0D + end.converge(described_recipe) end it 'installs windows features' do - expect(chef_run).to install_windows_feature('MicrosoftWindowsPowerShellV2') - expect(chef_run).to install_windows_feature('MicrosoftWindowsPowerShellV2-WOW64') - end - end - - context 'when windows_version is windows_server_2008_r2 and windows_version is core ' do - before do - @windows_version = double(windows_server_2008_r2?: true, windows_7?: false, core?: true, windows_server_2012?: false, windows_8?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - end - - it 'installs windows feature' do expect(chef_run).to install_windows_feature('NetFx2-ServerCore') expect(chef_run).to install_windows_feature('NetFx2-ServerCore-WOW64') expect(chef_run).to install_windows_feature('MicrosoftWindowsPowerShell') @@ -47,46 +29,36 @@ end end - context 'when windows_version is windows_7 and windows_version is core' do - before do - @windows_version = double(windows_server_2008_r2?: false, windows_7?: true, core?: true, windows_server_2012?: false, windows_8?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) + context 'on Windows Server 2008' do + let(:chef_run) do + # There is no fauxhai info for windows server 2008, so we use 2008R2 and change the platform version + ChefSpec::SoloRunner.new(platform: 'windows', version: '2008R2') do |node| + node.automatic['platform_version'] = '6.0.6001' + node.set['powershell']['powershell2']['url'] = 'https://powershelltest.com' + node.set['powershell']['powershell2']['checksum'] = '12345' + end.converge(described_recipe) end - it 'installs windows feature' do - expect(chef_run).to install_windows_feature('NetFx2-ServerCore') - expect(chef_run).to install_windows_feature('NetFx2-ServerCore-WOW64') - expect(chef_run).to install_windows_feature('MicrosoftWindowsPowerShell') - expect(chef_run).to install_windows_feature('MicrosoftWindowsPowerShell-WOW64') - end - end + context 'when powershell2 does not exist' do + before do + allow(Chef::Win32::Registry).to receive(:new).and_return double('registry', data_exists?: false, value_exists?: false, key_exists?: false) + end - context 'when windows_version is windows_server_2008' do - before do - @windows_version = double(windows_server_2008?: true, windows_server_2003_r2?: false, windows_server_2003?: false, windows_xp?: false, windows_server_2008_r2?: false, windows_7?: false, core?: false, windows_server_2012?: false, windows_8?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(false) + it 'installs windows package' do + expect(chef_run).to include_recipe('ms_dotnet::ms_dotnet2') + expect(chef_run).to install_windows_package('Windows Management Framework Core').with(source: 'https://powershelltest.com', checksum: '12345', installer_type: :custom, options: '/quiet /norestart') + end end - it 'installs windows package when powershell2 doesnot exist' do - expect(chef_run).to include_recipe('ms_dotnet2') - expect(chef_run).to install_windows_package('Windows Management Framework Core').with(source: 'https://powershelltest.com', checksum: '12345', installer_type: :custom, options: '/quiet /norestart') - end - end - - context 'when windows_version is windows_server_2008' do - before do - @windows_version = double(windows_server_2008?: true, windows_server_2003_r2?: false, windows_server_2003?: false, windows_xp?: false, windows_server_2008_r2?: false, windows_7?: false, core?: false, windows_server_2012?: false, windows_8?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(true) - end + context 'when powershell2 exist' do + before do + allow(Chef::Win32::Registry).to receive(:new).and_return double('registry', data_exists?: true, value_exists?: true) + end - it 'only includes ms_dotnet2 when powershell2 exist' do - expect(chef_run).to include_recipe('ms_dotnet2') + it 'only includes ms_dotnet2' do + expect(chef_run).to include_recipe('ms_dotnet::ms_dotnet2') + expect(chef_run).to_not install_windows_package('Windows Management Framework Core') + end end end end diff --git a/spec/recipes/powershell3_spec.rb b/spec/recipes/powershell3_spec.rb index 095a145..5fa5d2a 100644 --- a/spec/recipes/powershell3_spec.rb +++ b/spec/recipes/powershell3_spec.rb @@ -1,87 +1,58 @@ require 'spec_helper' -require 'chef/win32/version' describe 'powershell::powershell3' do - let(:chef_run) do - ChefSpec::SoloRunner.new(platform: 'windows', version: '2012') do |node| - node.set['powershell']['powershell3']['url'] = 'https://powershelltest.com' - node.set['powershell']['powershell3']['checksum'] = '12345' - node.set['powershell']['bits_4']['url'] = 'https://powershellbits.com' - node.set['powershell']['bits_4']['checksum'] = '99999' - end.converge(described_recipe) - end - - context 'when windows_version is windows_server_2008' do - before do - @windows_version = double(windows_server_2008?: true, windows_server_2008_r2?: false, windows_7?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(false) - allow_any_instance_of(Chef::Recipe).to receive(:include_recipe).and_return(true) - end - - it 'installs windows package windows managemet framework bits and windows management framework core 3.0' do - expect(chef_run).to install_windows_package('Windows Management Framework Bits').with(source: 'https://powershellbits.com', checksum: '99999', installer_type: :custom, options: '/quiet /norestart') - expect(chef_run).to install_windows_package('Windows Management Framework Core 3.0').with(source: 'https://powershelltest.com', checksum: '12345', installer_type: :custom, options: '/quiet /norestart') - end - end - - context 'when windows_version is windows_server_2008_r2' do - before do - @windows_version = double(windows_server_2008?: false, windows_server_2008_r2?: true, windows_7?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(true) - end - - it 'only include ms_dotnet4 when powershell 3 is installed' do - expect(chef_run).to include_recipe('ms_dotnet4') - end - end - - context 'when windows_version is windows_server_2008_r2' do - before do - @windows_version = double(windows_server_2008?: false, windows_server_2008_r2?: true, windows_7?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(false) - end - - it 'installs windows package windows management framework core 3.0 when powershell 3 doesnot exist' do - expect(chef_run).to include_recipe('ms_dotnet4') - expect(chef_run).to install_windows_package('Windows Management Framework Core 3.0').with(source: 'https://powershelltest.com', checksum: '12345', installer_type: :custom, options: '/quiet /norestart') - end - end - - context 'when windows_version is windows_7' do - before do - @windows_version = double(windows_server_2008?: false, windows_server_2008_r2?: false, windows_7?: true) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(true) - end - - it 'only include ms_dotnet4 when powershell 3 is installed' do - expect(chef_run).to include_recipe('ms_dotnet4') - end - end - - context 'when windows_version is windows_7' do - before do - @windows_version = double(windows_server_2008?: false, windows_server_2008_r2?: false, windows_7?: true) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(false) - end - - it 'installs windows package windows management framework core 3.0 when powershell 3 doesnot exist' do - expect(chef_run).to include_recipe('ms_dotnet4') - expect(chef_run).to install_windows_package('Windows Management Framework Core 3.0').with(source: 'https://powershelltest.com', checksum: '12345', installer_type: :custom, options: '/quiet /norestart') + { + 'Windows Server 2008R2' => { fauxhai_version: '2008R2', should_install_bits: false }, + # There is no fauxhai info for windows server 2008, so we use 2008R2 and change the platform version + 'Windows Server 2008' => { fauxhai_version: '2008R2', platform_version: '6.0.6001', should_install_bits: true }, + # There is no fauxhai info for windows 7, so we use 2008R2 and change the product type from server to workstation + 'Windows 7' => { fauxhai_version: '2008R2', product_type: 1, should_install_bits: false } + }.each do |windows_version, test_conf| + context "on #{windows_version}" do + let(:chef_run) do + ChefSpec::SoloRunner.new(platform: 'windows', version: test_conf[:fauxhai_version]) do |node| + node.automatic['platform_version'] = test_conf[:platform_version] if test_conf[:platform_version] + node.automatic['kernel']['os_info']['product_type'] = test_conf[:product_type] if test_conf[:product_type] + node.set['powershell']['powershell3']['url'] = 'https://powershelltest.com' + node.set['powershell']['powershell3']['checksum'] = '12345' + node.set['powershell']['bits_4']['url'] = 'https://powershellbits.com' + node.set['powershell']['bits_4']['checksum'] = '99999' + end.converge(described_recipe) + end + + if test_conf[:should_install_bits] + it 'installs windows package Windows Management Framework Bits' do + allow(Chef::Win32::Registry).to receive(:new).and_return double('registry', data_exists?: true, value_exists?: true) + expect(chef_run).to install_windows_package('Windows Management Framework Bits').with(source: 'https://powershellbits.com', checksum: '99999', installer_type: :custom, options: '/quiet /norestart') + end + else + it 'does not install windows package Windows Management Framework Bits' do + allow(Chef::Win32::Registry).to receive(:new).and_return double('registry', data_exists?: true, value_exists?: true) + expect(chef_run).to_not install_windows_package('Windows Management Framework Bits') + end + end + + context 'when powershell 3 is installed' do + before do + allow(Chef::Win32::Registry).to receive(:new).and_return double('registry', data_exists?: true, value_exists?: true) + end + + it 'only include ms_dotnet4' do + expect(chef_run).to include_recipe('ms_dotnet::ms_dotnet4') + expect(chef_run).to_not install_windows_package('Windows Management Framework Core 3.0') + end + end + + context 'when powershell 3 does not exist' do + before do + allow(Chef::Win32::Registry).to receive(:new).and_return double('registry', data_exists?: false, value_exists?: false, key_exists?: false) + end + + it 'installs windows package windows management framework core 3.0' do + expect(chef_run).to include_recipe('ms_dotnet::ms_dotnet4') + expect(chef_run).to install_windows_package('Windows Management Framework Core 3.0').with(source: 'https://powershelltest.com', checksum: '12345', installer_type: :custom, options: '/quiet /norestart') + end + end end end end diff --git a/spec/recipes/powershell4_spec.rb b/spec/recipes/powershell4_spec.rb index 7899573..7bece4f 100644 --- a/spec/recipes/powershell4_spec.rb +++ b/spec/recipes/powershell4_spec.rb @@ -1,249 +1,59 @@ require 'spec_helper' -require 'chef/win32/version' describe 'powershell::powershell4' do - context 'when installation_reboot_mode is no_reboot' do - let(:chef_run) do - ChefSpec::SoloRunner.new(platform: 'windows', version: '2012') do |node| - node.set['powershell']['powershell4']['url'] = 'https://powershelltest.com' - node.set['powershell']['powershell4']['checksum'] = '12345' - node.set['powershell']['installation_reboot_mode'] = 'no_reboot' - end.converge(described_recipe) - end - - context 'when windows_version is windows_server_2008_r2' do - before do - @windows_version = double(windows_server_2008_r2?: true, windows_7?: false, windows_server_2012?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(true) - end - - it 'only includes ms_dotnet45 when powershell 4 is installed' do - expect(chef_run).to include_recipe('ms_dotnet45') - end - end - - context 'when windows_version is windows_server_2008_r2' do - before do - @windows_version = double(windows_server_2008_r2?: true, windows_7?: false, windows_server_2012?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(false) - end - - it 'installs windows package windows managemet framework core 4.0 when powershell 4 is not installed' do - expect(chef_run).to include_recipe('ms_dotnet45') - expect(chef_run).to install_windows_package('Windows Management Framework Core4.0').with(source: 'https://powershelltest.com', checksum: '12345', installer_type: :custom) - end - end - - context 'when windows_version is windows_7' do - before do - @windows_version = double(windows_server_2008_r2?: false, windows_7?: true, windows_server_2012?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(true) - end - - it 'only includes ms_dotnet45 when powershell 4 is installed' do - expect(chef_run).to include_recipe('ms_dotnet45') - end - end - - context 'when windows_version is windows_7' do - before do - @windows_version = double(windows_server_2008_r2?: false, windows_7?: true, windows_server_2012?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(false) - end - - it 'installs windows package windows managemet framework core 4.0 when powershell 4 is not installed' do - expect(chef_run).to include_recipe('ms_dotnet45') - expect(chef_run).to install_windows_package('Windows Management Framework Core4.0').with(source: 'https://powershelltest.com', checksum: '12345', installer_type: :custom) - end - end - - context 'when windows_version is windows_server_2012' do - before do - @windows_version = double(windows_server_2008_r2?: false, windows_7?: false, windows_server_2012?: true) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(false) - end - - it 'installs windows package windows managemet framework core 4.0' do - expect(chef_run).to install_windows_package('Windows Management Framework Core4.0').with(source: 'https://powershelltest.com', checksum: '12345', installer_type: :custom) - end - end - end - - context 'when installation_reboot_mode is delayed_reboot' do - let(:chef_run) do - ChefSpec::SoloRunner.new(platform: 'windows', version: '2012') do |node| - node.set['powershell']['powershell4']['url'] = 'https://powershelltest.com' - node.set['powershell']['powershell4']['checksum'] = '12345' - node.set['powershell']['installation_reboot_mode'] = 'delayed_reboot' - end.converge(described_recipe) - end - - context 'when windows_version is windows_server_2008_r2' do - before do - @windows_version = double(windows_server_2008_r2?: true, windows_7?: false, windows_server_2012?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(true) - end - - it 'only includes ms_dotnet45 when powershell 4 is installed' do - expect(chef_run).to include_recipe('ms_dotnet45') - end - end - - context 'when windows_version is windows_server_2008_r2' do - before do - @windows_version = double(windows_server_2008_r2?: true, windows_7?: false, windows_server_2012?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(false) - end - - it 'installs windows package windows managemet framework core 4.0 when powershell 4 is not installed' do - expect(chef_run).to include_recipe('ms_dotnet45') - expect(chef_run).to install_windows_package('Windows Management Framework Core4.0').with(source: 'https://powershelltest.com', checksum: '12345', installer_type: :custom) - end - end - - context 'when windows_version is windows_7' do - before do - @windows_version = double(windows_server_2008_r2?: false, windows_7?: true, windows_server_2012?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(true) - end - - it 'only includes ms_dotnet45 when powershell 4 is installed' do - expect(chef_run).to include_recipe('ms_dotnet45') - end - end - - context 'when windows_version is windows_7' do - before do - @windows_version = double(windows_server_2008_r2?: false, windows_7?: true, windows_server_2012?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(false) - end - - it 'installs windows package windows managemet framework core 4.0 when powershell 4 is not installed' do - expect(chef_run).to include_recipe('ms_dotnet45') - expect(chef_run).to install_windows_package('Windows Management Framework Core4.0').with(source: 'https://powershelltest.com', checksum: '12345', installer_type: :custom) - end - end - - context 'when windows_version is windows_server_2012' do - before do - @windows_version = double(windows_server_2008_r2?: false, windows_7?: false, windows_server_2012?: true) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(false) - end - - it 'installs windows package windows managemet framework core 4.0' do - expect(chef_run).to install_windows_package('Windows Management Framework Core4.0').with(source: 'https://powershelltest.com', checksum: '12345', installer_type: :custom) - end - end - end - - context 'when installation_reboot_mode is immediate_reboot' do - let(:chef_run) do - ChefSpec::SoloRunner.new(platform: 'windows', version: '2012') do |node| - node.set['powershell']['powershell4']['url'] = 'https://powershelltest.com' - node.set['powershell']['powershell4']['checksum'] = '12345' - node.set['powershell']['installation_reboot_mode'] = 'immediate_reboot' - end.converge(described_recipe) - end - - context 'when windows_version is windows_server_2008_r2' do - before do - @windows_version = double(windows_server_2008_r2?: true, windows_7?: false, windows_server_2012?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(true) - end - - it 'only includes ms_dotnet45 when powershell 4 is installed' do - expect(chef_run).to include_recipe('ms_dotnet45') - end - end - - context 'when windows_version is windows_server_2008_r2' do - before do - @windows_version = double(windows_server_2008_r2?: true, windows_7?: false, windows_server_2012?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(false) - end - - it 'installs windows package windows managemet framework core 4.0 when powershell 4 is not installed' do - expect(chef_run).to include_recipe('ms_dotnet45') - expect(chef_run).to install_windows_package('Windows Management Framework Core4.0').with(source: 'https://powershelltest.com', checksum: '12345', installer_type: :custom) - end - end - - context 'when windows_version is windows_7' do - before do - @windows_version = double(windows_server_2008_r2?: false, windows_7?: true, windows_server_2012?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(true) - end - - it 'only includes ms_dotnet45 when powershell 4 is installed' do - expect(chef_run).to include_recipe('ms_dotnet45') - end - end - - context 'when windows_version is windows_7' do - before do - @windows_version = double(windows_server_2008_r2?: false, windows_7?: true, windows_server_2012?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(false) - end - - it 'installs windows package windows managemet framework core 4.0 when powershell 4 is not installed' do - expect(chef_run).to include_recipe('ms_dotnet45') - expect(chef_run).to install_windows_package('Windows Management Framework Core4.0').with(source: 'https://powershelltest.com', checksum: '12345', installer_type: :custom) - end - end - - context 'when windows_version is windows_server_2012' do - before do - @windows_version = double(windows_server_2008_r2?: false, windows_7?: false, windows_server_2012?: true) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(false) - end - - it 'installs windows package windows managemet framework core 4.0' do - expect(chef_run).to install_windows_package('Windows Management Framework Core4.0').with(source: 'https://powershelltest.com', checksum: '12345', installer_type: :custom) + { + # There is no fauxhai info for windows 7, so we use windows 2008R2 and change the product type from server to workstation + 'Windows 7' => { fauxhai_version: '2008R2', product_type: 1 }, + 'Windows Server 2008R2' => { fauxhai_version: '2008R2' }, + 'Windows Server 2012' => { fauxhai_version: '2012' } + }.each do |windows_version, test_conf| + context "on #{windows_version}" do + let(:normal_attributes) { ::Chef::Node::VividMash.new(double('fake_node').as_null_object) } + let(:chef_run) do + ChefSpec::SoloRunner.new(platform: 'windows', version: test_conf[:fauxhai_version]) do |node| + node.automatic['kernel']['os_info']['product_type'] = test_conf[:product_type] if test_conf[:product_type] + normal_attributes.each { |k, v| node.set[k] = v } + end.converge(described_recipe) + end + + context 'when ms_dotnet::ms_dotnet4 is not configured for .NET 4.5' do + before { normal_attributes['ms_dotnet']['v4']['version'] = '4.0' } + it 'fails' do + expect { chef_run }.to raise_error + end + end + + context 'when ms_dotnet::ms_dotnet4 is configured for .NET 4.5' do + before { normal_attributes['ms_dotnet']['v4']['version'] = '4.5' } + + context 'when powershell 4 is installed' do + before do + allow_any_instance_of(::Chef::Resource).to receive(:reboot_pending?).and_return false + expect_any_instance_of(::Chef::Resource).to receive(:registry_data_exists?).with('HKLM\SOFTWARE\Microsoft\PowerShell\3\PowerShellEngine', name: 'PowerShellVersion', type: :string, data: '4.0').and_return true + end + it 'includes ms_dotnet::ms_dotnet4' do + expect(chef_run).to include_recipe('ms_dotnet::ms_dotnet4') + end + it 'does not install windows package WMFC 4.0' do + expect(chef_run).to_not install_windows_package('Windows Management Framework Core 4.0') + end + end + + context 'when powershell 4 does not exist' do + before do + normal_attributes['powershell']['powershell4']['url'] = 'https://powershelltest.com' + normal_attributes['powershell']['powershell4']['checksum'] = '12345' + normal_attributes['powershell']['installation_reboot_mode'] = 'no_reboot' + allow_any_instance_of(::Chef::Resource).to receive(:reboot_pending?).and_return false + allow_any_instance_of(::Chef::Resource).to receive(:registry_data_exists?).with('HKLM\SOFTWARE\Microsoft\PowerShell\3\PowerShellEngine', name: 'PowerShellVersion', type: :string, data: '4.0').and_return false + end + it 'includes ms_dotnet::ms_dotnet4' do + expect(chef_run).to include_recipe('ms_dotnet::ms_dotnet4') + end + it 'installs windows package WMFC 4.0' do + expect(chef_run).to install_windows_package('Windows Management Framework Core 4.0').with(source: 'https://powershelltest.com', checksum: '12345', installer_type: :custom) + end + end end end end diff --git a/spec/recipes/powershell5_spec.rb b/spec/recipes/powershell5_spec.rb index b69ef4d..7e83ed9 100644 --- a/spec/recipes/powershell5_spec.rb +++ b/spec/recipes/powershell5_spec.rb @@ -1,43 +1,44 @@ require 'spec_helper' -require 'chef/win32/version' describe 'powershell::powershell5' do - let(:chef_run) do - ChefSpec::SoloRunner.new(platform: 'windows', version: '2012') do |node| - node.set['powershell']['powershell5']['url'] = 'https://powershelltest.com' - node.set['powershell']['powershell5']['checksum'] = '12345' - end.converge(described_recipe) - end + { + # There is no fauxhai info for windows 8, so we use windows 2012R2 and change the product type from server to workstation + 'Windows 8.1' => { fauxhai_version: '2012R2', product_type: 1 }, + 'Windows Server 2012R2' => { fauxhai_version: '2012R2' } + }.each do |windows_version, test_conf| + context "on #{windows_version}" do + let(:chef_run) do + ChefSpec::SoloRunner.new(platform: 'windows', version: test_conf[:fauxhai_version]) do |node| + node.automatic['kernel']['os_info']['product_type'] = test_conf[:product_type] if test_conf[:product_type] + node.set['powershell']['powershell5']['url'] = 'https://powershelltest.com' + node.set['powershell']['powershell5']['checksum'] = '12345' + end.converge(described_recipe) + end - before do - allow_any_instance_of(Chef::Recipe).to receive(:include_recipe).with('powershell::powershell2').and_return(true) - end + it 'includes powershell 2 recipe' do + allow(Chef::Win32::Registry).to receive(:new).and_return double('registry', data_exists?: false, value_exists?: false, key_exists?: false) + expect(chef_run).to include_recipe('powershell::powershell2') + end - context 'when windows_version is windows_server_2012_r2' do - before do - @windows_version = double(windows_server_2012_r2?: true, windows_8_1?: false) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(false) - end + context 'when powershell is installed' do + before do + allow(Chef::Win32::Registry).to receive(:new).and_return double('registry', data_exists?: true, value_exists?: true) + end - it 'installs windows package windows managemet framework core 5.0 if powershell 5 not installed' do - expect(chef_run).to install_windows_package('Windows Management Framework Core 5.0').with(source: 'https://powershelltest.com', checksum: '12345', installer_type: :custom, options: '/quiet /norestart') - end - end + it 'does not install WMF 5' do + expect(chef_run).to_not install_windows_package('Windows Management Framework Core 5.0') + end + end - context 'when windows_version is windows_8_1' do - before do - @windows_version = double(windows_server_2012_r2?: false, windows_8_1?: true) - allow(Chef::ReservedNames::Win32::Version).to receive(:new).and_return(@windows_version) - registry = double - allow(Chef::Win32::Registry).to receive(:new).and_return(registry) - allow(registry).to receive(:data_exists?).and_return(false) - end + context 'when powershell does not exist' do + before do + allow(Chef::Win32::Registry).to receive(:new).and_return double('registry', data_exists?: false, value_exists?: false, key_exists?: false) + end - it 'installs windows package windows managemet framework core 5.0 if powershell not installed' do - expect(chef_run).to install_windows_package('Windows Management Framework Core 5.0').with(source: 'https://powershelltest.com', checksum: '12345', installer_type: :custom, options: '/quiet /norestart') + it 'installs windows package windows management framework core 5.0' do + expect(chef_run).to install_windows_package('Windows Management Framework Core 5.0').with(source: 'https://powershelltest.com', checksum: '12345', installer_type: :custom, options: '/quiet /norestart') + end + end end end end diff --git a/spec/recipes/winrm_spec.rb b/spec/recipes/winrm_spec.rb index 6bdb490..9f08bc2 100644 --- a/spec/recipes/winrm_spec.rb +++ b/spec/recipes/winrm_spec.rb @@ -1,11 +1,15 @@ require 'spec_helper' -require 'chef/win32/version' describe 'powershell::winrm' do let(:chef_run) do ChefSpec::SoloRunner.new(platform: 'windows', version: '2012').converge(described_recipe) end + before do + winrm_cmd = double('winrm_cmd', run_command: nil, stdout: 'Transport = HTTPS') + allow(Mixlib::ShellOut).to receive(:new).with('powershell.exe winrm enumerate winrm/config/listener').and_return winrm_cmd + end + it 'installs windows package windows managemet framework core 5.0' do expect(chef_run).to run_powershell_script('enable winrm').with(code: " winrm quickconfig -q\n") end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e667e88..94ac410 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,7 @@ require 'chefspec' require 'chefspec/berkshelf' require 'helpers/matchers' + +def not_windows? + (RUBY_PLATFORM =~ /mswin|mingw|windows/).nil? +end