Class: PuppetX::Dsc::PowerShellManager
- Inherits:
-
Object
- Object
- PuppetX::Dsc::PowerShellManager
- Defined in:
- lib/puppet_x/puppetlabs/powershell_manager.rb
Constant Summary collapse
- @@instances =
{}
Class Method Summary collapse
- .compatible_version_of_powershell? ⇒ Boolean
- .init_path ⇒ Object
- .instance(cmd, debug = false, pipe_timeout = 30) ⇒ Object
- .supported? ⇒ Boolean
- .win32console_enabled? ⇒ Boolean
Instance Method Summary collapse
- #alive? ⇒ Boolean
- #execute(powershell_code, timeout_ms = nil, working_dir = nil) ⇒ Object
- #exit ⇒ Object
-
#initialize(cmd, debug, pipe_timeout) ⇒ PowerShellManager
constructor
A new instance of PowerShellManager.
- #make_ps_code(powershell_code, timeout_ms = nil, working_dir = nil) ⇒ Object
Constructor Details
#initialize(cmd, debug, pipe_timeout) ⇒ PowerShellManager
Returns a new instance of PowerShellManager.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 41 def initialize(cmd, debug, pipe_timeout) @usable = true named_pipe_name = "#{SecureRandom.uuid}PuppetPsHost" ps_args = ['-File', self.class.init_path, "\"#{named_pipe_name}\""] ps_args << '"-EmitDebugOutput"' if debug # @stderr should never be written to as PowerShell host redirects output stdin, @stdout, @stderr, @ps_process = Open3.popen3("#{cmd} #{ps_args.join(' ')}") stdin.close Puppet.debug "#{Time.now} #{cmd} is running as pid: #{@ps_process[:pid]}" pipe_path = "\\\\.\\pipe\\#{named_pipe_name}" # wait for the pipe server to signal ready, and fail if no response in 10 seconds # wait up to 30 seconds in 0.2 second intervals to be able to open the pipe # If the pipe_timeout is ever specified as less than the sleep interval it will # never try to connect to a pipe and error out as if a timeout occurred. sleep_interval = 0.2 (pipe_timeout / sleep_interval).to_int.times do begin # pipe is opened in binary mode and must always @pipe = File.open(pipe_path, 'r+b') break rescue sleep sleep_interval end end if @pipe.nil? # Tear down and kill the process if unable to connect to the pipe; failure to do so # results in zombie processes being left after the puppet run. We discovered that # Closing @ps_process via .kill instead of using this method actually kills the watcher # and leaves an orphaned process behind. Failing to close stdout and stderr also leaves # clutter behind, so explicitly close those too. @stdout.close if !@stdout.closed? @stderr.close if !@stderr.closed? Process.kill("KILL", @ps_process[:pid]) if @ps_process.alive? raise "Failure waiting for PowerShell process #{@ps_process[:pid]} to start pipe server" end Puppet.debug "#{Time.now} PowerShell initialization complete for pid: #{@ps_process[:pid]}" at_exit { exit } end |
Class Method Details
.compatible_version_of_powershell? ⇒ Boolean
31 32 33 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 31 def self.compatible_version_of_powershell? @compatible_powershell_version ||= PuppetX::PuppetLabs::Dsc::CompatiblePowerShellVersion.compatible_version? end |
.init_path ⇒ Object
133 134 135 136 137 138 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 133 def self.init_path # a PowerShell -File compatible path to bootstrap the instance path = File.('../../templates/dsc', __FILE__) path = File.join(path, 'init_ps.ps1').gsub('/', '\\') "\"#{path}\"" end |
.instance(cmd, debug = false, pipe_timeout = 30) ⇒ Object
12 13 14 15 16 17 18 19 20 21 22 23 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 12 def self.instance(cmd, debug = false, pipe_timeout = 30) key = cmd + debug.to_s manager = @@instances[key] if manager.nil? || !manager.alive? # ignore any errors trying to tear down this unusable instance manager.exit if manager rescue nil @@instances[key] = PowerShellManager.new(cmd, debug, pipe_timeout) end @@instances[key] end |
.supported? ⇒ Boolean
35 36 37 38 39 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 35 def self.supported? Puppet::Util::Platform.windows? && compatible_version_of_powershell? && !win32console_enabled? end |
.win32console_enabled? ⇒ Boolean
25 26 27 28 29 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 25 def self.win32console_enabled? @win32console_enabled ||= defined?(Win32) && defined?(Win32::Console) && Win32::Console.class == Class end |
Instance Method Details
#alive? ⇒ Boolean
86 87 88 89 90 91 92 93 94 95 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 86 def alive? # powershell process running @ps_process.alive? && # explicitly set during a read / write failure, like broken pipe EPIPE @usable && # an explicit failure state might not have been hit, but IO may be closed self.class.is_stream_valid?(@pipe) && self.class.is_stream_valid?(@stdout) && self.class.is_stream_valid?(@stderr) end |
#execute(powershell_code, timeout_ms = nil, working_dir = nil) ⇒ Object
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 97 def execute(powershell_code, timeout_ms = nil, working_dir = nil) code = make_ps_code(powershell_code, timeout_ms, working_dir) # err is drained stderr pipe (not captured by redirection inside PS) # or during a failure, a Ruby callstack array out, native_stdout, err = exec_read_result(code) # an error was caught during execution that has invalidated any results return { :exitcode => -1, :stderr => err } if !@usable && out.nil? out[:exitcode] = out[:exitcode].to_i if !out[:exitcode].nil? # if err contains data it must be "real" stderr output # which should be appended to what PS has already captured out[:stderr] = out[:stderr].nil? ? [] : [out[:stderr]] out[:stderr] += err if !err.nil? out[:native_stdout] = native_stdout out end |
#exit ⇒ Object
118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 118 def exit @usable = false Puppet.debug "PowerShellManager exiting..." # pipe may still be open, but if stdout / stderr are dead PS process is in trouble # and will block forever on a write to the pipe # its safer to close pipe on Ruby side, which gracefully shuts down PS side @pipe.close if !@pipe.closed? @stdout.close if !@stdout.closed? @stderr.close if !@stderr.closed? # wait up to 2 seconds for the watcher thread to fully exit @ps_process.join(2) end |
#make_ps_code(powershell_code, timeout_ms = nil, working_dir = nil) ⇒ Object
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 140 def make_ps_code(powershell_code, timeout_ms = nil, working_dir = nil) begin timeout_ms = Integer(timeout_ms) # Lower bound protection. The polling resolution is only 50ms if (timeout_ms < 50) then timeout_ms = 50 end rescue timeout_ms = 300 * 1000 end # PS side expects Invoke-PowerShellUserCode is always the return value here <<-CODE $params = @{ Code = @' #{powershell_code} '@ TimeoutMilliseconds = #{timeout_ms} WorkingDirectory = "#{working_dir}" } Invoke-PowerShellUserCode @params CODE end |