diff options
-rw-r--r-- | lib/chef/mixin/powershell_out.rb | 95 | ||||
-rw-r--r-- | lib/chef/mixin/windows_architecture_helper.rb | 7 | ||||
-rw-r--r-- | spec/functional/mixin/powershell_out_spec.rb | 43 | ||||
-rw-r--r-- | spec/unit/mixin/powershell_out_spec.rb | 70 |
4 files changed, 213 insertions, 2 deletions
diff --git a/lib/chef/mixin/powershell_out.rb b/lib/chef/mixin/powershell_out.rb new file mode 100644 index 0000000000..8984ab2039 --- /dev/null +++ b/lib/chef/mixin/powershell_out.rb @@ -0,0 +1,95 @@ +#-- +# Copyright:: Copyright (c) 2015 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# 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. + +class Chef + module Mixin + module PowershellOut + include Chef::Mixin::ShellOut + include Chef::Mixin::WindowsArchitectureHelper + + # Run a command under powershell with the same API as shell_out. The + # options hash is extended to take an "architecture" flag which + # can be set to :i386 or :x86_64 to force the windows architecture. + # + # @param script [String] script to run + # @param options [Hash] options hash + # @return [Mixlib::Shellout] mixlib-shellout object + def powershell_out(*command_args) + script = command_args.first + options = command_args.last.is_a?(Hash) ? command_args.last : nil + + run_command_with_os_architecture(script, options) + end + + # Run a command under powershell with the same API as shell_out! + # (raises exceptions on errors) + # + # @param script [String] script to run + # @param options [Hash] options hash + # @return [Mixlib::Shellout] mixlib-shellout object + def powershell_out!(*command_args) + cmd = powershell_out(*command_args) + cmd.error! + cmd + end + + private + + # Helper function to run shell_out and wrap it with the correct + # flags to possibly disable WOW64 redirection (which we often need + # because chef-client runs as a 32-bit app on 64-bit windows). + # + # @param script [String] script to run + # @param options [Hash] options hash + # @return [Mixlib::Shellout] mixlib-shellout object + def run_command_with_os_architecture(script, options) + options ||= {} + options = options.dup + arch = options.delete(:architecture) + + with_os_architecture(nil, architecture: arch) do + shell_out( + build_powershell_command(script), + options + ) + end + end + + # Helper to build a powershell command around the script to run. + # + # @param script [String] script to run + # @retrurn [String] powershell command to execute + def build_powershell_command(script) + flags = [ + # Hides the copyright banner at startup. + "-NoLogo", + # Does not present an interactive prompt to the user. + "-NonInteractive", + # Does not load the Windows PowerShell profile. + "-NoProfile", + # always set the ExecutionPolicy flag + # see http://technet.microsoft.com/en-us/library/ee176961.aspx + "-ExecutionPolicy RemoteSigned", + # Powershell will hang if STDIN is redirected + # http://connect.microsoft.com/PowerShell/feedback/details/572313/powershell-exe-can-hang-if-stdin-is-redirected + "-InputFormat None" + ] + + "powershell.exe #{flags.join(' ')} -Command \"#{script}\"" + end + end + end +end diff --git a/lib/chef/mixin/windows_architecture_helper.rb b/lib/chef/mixin/windows_architecture_helper.rb index a0ac34f627..c5f3e1bd79 100644 --- a/lib/chef/mixin/windows_architecture_helper.rb +++ b/lib/chef/mixin/windows_architecture_helper.rb @@ -42,7 +42,7 @@ class Chef is_i386_process_on_x86_64_windows? end - def with_os_architecture(node) + def with_os_architecture(node, architecture: nil) node ||= begin os_arch = ENV['PROCESSOR_ARCHITEW6432'] || ENV['PROCESSOR_ARCHITECTURE'] @@ -51,9 +51,12 @@ class Chef n[:kernel][:machine] = os_arch == 'AMD64' ? :x86_64 : :i386 end end + + architecture ||= node_windows_architecture(node) + wow64_redirection_state = nil - if wow64_architecture_override_required?(node, node_windows_architecture(node)) + if wow64_architecture_override_required?(node, architecture) wow64_redirection_state = disable_wow64_file_redirection(node) end diff --git a/spec/functional/mixin/powershell_out_spec.rb b/spec/functional/mixin/powershell_out_spec.rb new file mode 100644 index 0000000000..9cc8aeed7e --- /dev/null +++ b/spec/functional/mixin/powershell_out_spec.rb @@ -0,0 +1,43 @@ +# +# Copyright:: Copyright (c) 2014 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# 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. +# + +require 'spec_helper' +require 'chef/mixin/powershell_out' + +describe Chef::Mixin::PowershellOut, windows_only: true do + include Chef::Mixin::PowershellOut + + describe "#powershell_out" do + it "runs a powershell command and collects stdout" do + expect(powershell_out("get-process").run_command.stdout).to match /Handles\s+NPM\(K\)\s+PM\(K\)\s+WS\(K\)\s+VM\(M\)\s+CPU\(s\)\s+Id\s+ProcessName/ + end + + it "does not raise exceptions when the command is invalid" do + powershell_out("this-is-not-a-valid-command").run_command + end + end + + describe "#powershell_out!" do + it "runs a powershell command and collects stdout" do + expect(powershell_out!("get-process").run_command.stdout).to match /Handles\s+NPM\(K\)\s+PM\(K\)\s+WS\(K\)\s+VM\(M\)\s+CPU\(s\)\s+Id\s+ProcessName/ + end + + it "raises exceptions when the command is invalid" do + expect { powershell_out!("this-is-not-a-valid-command").run_command }.to raise_exception(Mixlib::ShellOut::ShellCommandFailed) + end + end +end diff --git a/spec/unit/mixin/powershell_out_spec.rb b/spec/unit/mixin/powershell_out_spec.rb new file mode 100644 index 0000000000..3ce641ee8f --- /dev/null +++ b/spec/unit/mixin/powershell_out_spec.rb @@ -0,0 +1,70 @@ +# +# Copyright:: Copyright (c) 2015 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# 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. +# + +require 'spec_helper' +require 'chef/mixin/powershell_out' + +describe Chef::Mixin::PowershellOut do + let(:shell_out_class) { Class.new { include Chef::Mixin::PowershellOut } } + subject(:object) { shell_out_class.new } + let(:architecture) { "something" } + let(:flags) { + "-NoLogo -NonInteractive -NoProfile -ExecutionPolicy RemoteSigned -InputFormat None" + } + + describe "#powershell_out" do + it "runs a command and returns the shell_out object" do + ret = double("Mixlib::ShellOut") + expect(object).to receive(:shell_out).with( + "powershell.exe #{flags} -Command \"Get-Process\"", + {} + ).and_return(ret) + expect(object.powershell_out("Get-Process")).to eql(ret) + end + + it "passes options" do + ret = double("Mixlib::ShellOut") + expect(object).to receive(:shell_out).with( + "powershell.exe #{flags} -Command \"Get-Process\"", + timeout: 600 + ).and_return(ret) + expect(object.powershell_out("Get-Process", timeout: 600)).to eql(ret) + end + end + + describe "#powershell_out!" do + it "runs a command and returns the shell_out object" do + mixlib_shellout = double("Mixlib::ShellOut") + expect(object).to receive(:shell_out).with( + "powershell.exe #{flags} -Command \"Get-Process\"", + {} + ).and_return(mixlib_shellout) + expect(mixlib_shellout).to receive(:error!) + expect(object.powershell_out!("Get-Process")).to eql(mixlib_shellout) + end + + it "passes options" do + mixlib_shellout = double("Mixlib::ShellOut") + expect(object).to receive(:shell_out).with( + "powershell.exe #{flags} -Command \"Get-Process\"", + timeout: 600 + ).and_return(mixlib_shellout) + expect(mixlib_shellout).to receive(:error!) + expect(object.powershell_out!("Get-Process", timeout: 600)).to eql(mixlib_shellout) + end + end +end |