puppetlabs/puppet · error · Puppet::Util::Windows::Error

QueryFullProcessImageNameW(phandle, #{use_win32_path_format}

Error message

QueryFullProcessImageNameW(phandle, #{use_win32_path_format}, exe_name_ptr, #{max_chars}

What it means

Raised by Process.get_process_image_name_by_pid (lib/puppet/util/windows/process.rb:136) when QueryFullProcessImageNameW returns FALSE. The method already runs under SE_DEBUG_NAME via Security.with_privilege, opens the pid with PROCESS_QUERY_INFORMATION, and sizes the buffer at MAX_PATH_LENGTH + 1 = 32768 wchars, so the realistic failures are: the process exited between OpenProcess and the query (ERROR_INVALID_HANDLE / stale), or the target is a protected process that refuses the query even with SeDebugPrivilege (ERROR_ACCESS_DENIED).

Source

Thrown at lib/puppet/util/windows/process.rb:136

    # all handles have been closed, so nothing to safely return
    nil
  end
  module_function :with_process_token

  def get_process_image_name_by_pid(pid)
    image_name = ''.dup

    Puppet::Util::Windows::Security.with_privilege(Puppet::Util::Windows::Security::SE_DEBUG_NAME) do
      open_process(PROCESS_QUERY_INFORMATION, false, pid) do |phandle|
        FFI::MemoryPointer.new(:dword, 1) do |exe_name_length_ptr|
          # UTF is 2 bytes/char:
          max_chars = MAX_PATH_LENGTH + 1
          exe_name_length_ptr.write_dword(max_chars)
          FFI::MemoryPointer.new(:wchar, max_chars) do |exe_name_ptr|
            use_win32_path_format = 0
            result = QueryFullProcessImageNameW(phandle, use_win32_path_format, exe_name_ptr, exe_name_length_ptr)
            if result == FFI::WIN32_FALSE
              raise Puppet::Util::Windows::Error, "QueryFullProcessImageNameW(phandle, #{use_win32_path_format}, " \
                                                  "exe_name_ptr, #{max_chars}"
            end
            image_name = exe_name_ptr.read_wide_string(exe_name_length_ptr.read_dword)
          end
        end
      end
    end

    image_name
  end
  module_function :get_process_image_name_by_pid

  def lookup_privilege_value(name, system_name = '', &block)
    FFI::MemoryPointer.new(LUID.size) do |luid_ptr|
      result = LookupPrivilegeValueW(
        wide_string(system_name),
        wide_string(name.to_s),
        luid_ptr

View on GitHub (pinned to e227c27540)

Solutions

  1. Rescue Puppet::Util::Windows::Error and treat failure as 'process gone / not inspectable' — return nil and let the caller skip.
  2. In polling loops, tolerate gaps: the next tick usually succeeds for long-lived processes.
  3. Confirm the agent runs elevated (SeDebugPrivilege acquirable) when inspecting other sessions' processes.
  4. Branch on e.code: 6 (invalid handle) means an exit race, 5 (access denied) means a protected process.

Example fix

# before
image = Process.get_process_image_name_by_pid(pid)

# after — tolerate the exit race
begin
  image = Process.get_process_image_name_by_pid(pid)
rescue Puppet::Util::Windows::Error
  image = nil # process exited or is protected
end
Defensive patterns

Strategy: fallback

Try / catch

def safe_image_name(pid)
  Process.get_process_image_name_by_pid(pid)
rescue Puppet::Util::Windows::Error => e
  Puppet.debug "image lookup failed for pid #{pid} (#{e.code}): #{e.message}"
  nil # caller falls back to cached data or skips this tick
end

Prevention

When it happens

Trigger: Polling the image name of short-lived processes that vanish mid-call; querying protected-process-light binaries such as antivirus or lsass; the caller lacking the ability to acquire SeDebugPrivilege (not elevated), which surfaces when targeting other sessions' processes.

Common situations: Process-monitoring loops racing process exit; agents checking which executable owns a lock or a port; inventory scripts hitting protected services.

Related errors


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/1a127818bd2aae59. Report an issue: GitHub.