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

Failed to unload user profile %{user}

Error message

Failed to unload user profile %{user}

What it means

Raised by Puppet::Util::Windows::User.load_profile when UnloadUserProfile fails after the profile was successfully loaded. Unloading decrements the hive's reference count; the call fails when other handles into the user's registry hive or profile directories are still open, so the hive stays loaded. Puppet::Util::Windows::Error attaches the Win32 reason (commonly ERROR_ACCESS_DENIED while the hive is in use).

Source

Thrown at lib/puppet/util/windows/user.rb:139

  private_class_method :logon_user_by_logon_type

  def load_profile(user, password)
    logon_user(user, password) do |token|
      FFI::MemoryPointer.from_string_to_wide_string(user) do |lpUserName|
        pi = PROFILEINFO.new
        pi[:dwSize] = PROFILEINFO.size
        pi[:dwFlags] = 1 # PI_NOUI - prevents display of profile error msgs
        pi[:lpUserName] = lpUserName

        # Load the profile. Since it doesn't exist, it will be created
        if LoadUserProfileW(token, pi.pointer) == FFI::WIN32_FALSE
          raise Puppet::Util::Windows::Error, _("Failed to load user profile %{user}") % { user: user.inspect }
        end

        Puppet.debug("Loaded profile for #{user}")

        if UnloadUserProfile(token, pi[:hProfile]) == FFI::WIN32_FALSE
          raise Puppet::Util::Windows::Error, _("Failed to unload user profile %{user}") % { user: user.inspect }
        end
      end
    end
  end
  module_function :load_profile

  def get_rights(name)
    user_info = Puppet::Util::Windows::SID.name_to_principal(name.sub(/^\.\\/, "#{Puppet::Util::Windows::ADSI.computer_name}\\"))
    return "" unless user_info

    rights = []
    rights_pointer = FFI::MemoryPointer.new(:pointer)
    number_of_rights = FFI::MemoryPointer.new(:ulong)
    sid_pointer = FFI::MemoryPointer.new(:byte, user_info.sid_bytes.length).write_array_of_uchar(user_info.sid_bytes)

    new_lsa_policy_handle do |policy_handle|
      result = LsaEnumerateAccountRights(policy_handle.read_pointer, sid_pointer, rights_pointer, number_of_rights)
      check_lsa_nt_status_and_raise_failures(result, "LsaEnumerateAccountRights")

View on GitHub (pinned to e227c27540)

Solutions

  1. Ensure every key/file/COM handle opened during the profile block is closed before the block returns.
  2. Close and retry once after a short delay — AV/indexer handles are often transient.
  3. If the hive remains loaded, unload it with `reg unload HKU\<SID>` from an elevated shell once nothing holds it.
  4. Identify the holder with Process Explorer / handle.exe on ntuser.dat or HKU subkeys.
  5. Reboot as a last resort for stubborn leaked handles.

Example fix

# before
Puppet::Util::Windows::User.load_profile(user, password) do
  run_some_code_that_opens_profile_resources # leaks handles -> unload fails
end

# after
Puppet::Util::Windows::User.load_profile(user, password) do
  run_some_code_that_opens_profile_resources
ensure
  cleanup_opened_handles # close registry keys, files, COM objects
end
Defensive patterns

Strategy: retry

Validate before calling

# before unloading, confirm nothing in-process still holds profile resources
# (close keys opened under HKU and files under the profile dir in the block)

Try / catch

attempts = 0
begin
  Puppet::Util::Windows::User.load_profile(user, password) { |token| do_work(token) }
rescue Puppet::Util::Windows::Error => e
  raise unless e.message.include?('Failed to unload user profile') && (attempts += 1) < 3
  GC.start # prompt COM finalization of profile-backed objects
  sleep 2 # let AV/indexer release transient handles
  retry
end

Prevention

When it happens

Trigger: Something inside the yielded block (or elsewhere in the process) opened HKU\<SID> keys, files under the profile directory, or COM objects backed by the profile and did not close them before UnloadUserProfile ran; antivirus/indexing briefly holding profile files; the profile being in use by another logon session.

Common situations: Code calling load_profile and creating shell/COM objects without releasing them; previous failed runs leaving handles open; roaming profile contention between two machines/sessions; SCM-launched processes keeping the hive referenced.

Related errors


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