puppetlabs/puppet · error · Puppet::Error

ads_object must be an IAdsUser or IAdsGroup instance

Error message

ads_object must be an IAdsUser or IAdsGroup instance

What it means

Raised by Puppet::Util::Windows::SID.ads_to_principal when the argument is nil, is not a WIN32OLE object (does not respond to ole_respond_to?), or lacks either the objectSID or Name property that IAdsUser/IAdsGroup expose. Unlike sibling errors this one is a non-translated plain Puppet::Error. The method exists to convert ADSI user/group COM objects into SID::Principal objects.

Source

Thrown at lib/puppet/util/windows/sid.rb:119

      if !bytes || !bytes.respond_to?('pack') || bytes.empty?
        raise Puppet::Util::Windows::Error, _("Octet string must be an array of bytes")
      end

      Principal.lookup_account_sid(bytes)
    end
    module_function :octet_string_to_principal
    class << self; alias octet_string_to_sid_object octet_string_to_principal; end

    # Converts a COM instance of IAdsUser or IAdsGroup to a SID::Principal object,
    # Raises an Error for nil or an object without an objectSID / Name property.
    # This method returns a SID::Principal with the account, domain, SID, etc
    # This method will return instances even when the SID is unresolvable, as
    # may be the case when domain users have been added to local groups, but
    # removed from the domain
    def ads_to_principal(ads_object)
      if !ads_object || !ads_object.respond_to?(:ole_respond_to?) ||
         !ads_object.ole_respond_to?(:objectSID) || !ads_object.ole_respond_to?(:Name)
        raise Puppet::Error, "ads_object must be an IAdsUser or IAdsGroup instance"
      end

      octet_string_to_principal(ads_object.objectSID)
    rescue Puppet::Util::Windows::Error => e
      # if the error is not a lookup / mapping problem, immediately re-raise
      raise if e.code != ERROR_NONE_MAPPED

      # if the Name property isn't formatted like a SID, OR
      if !valid_sid?(ads_object.Name) ||
         # if the objectSID doesn't match the Name property, also raise
         ((converted = octet_string_to_sid_string(ads_object.objectSID)) != ads_object.Name)
        raise Puppet::Error.new("ads_object Name: #{ads_object.Name} invalid or does not match objectSID: #{ads_object.objectSID} (#{converted})", e)
      end

      unresolved_principal(ads_object.Name, ads_object.objectSID)
    end
    module_function :ads_to_principal

View on GitHub (pinned to e227c27540)

Solutions

  1. Filter before converting — only call ads_to_principal for members whose Class is 'User' or 'Group': `member.Class == 'User' || member.Class == 'Group'`.
  2. Verify the ADSI path points at a user or group object (WinNT://DOMAIN/user,user).
  3. Check for nil / failed WIN32OLE.connect results before use.
  4. For non-user/group members, resolve them via name_to_principal instead.

Example fix

# before
group_members.each { |m| Puppet::Util::Windows::SID.ads_to_principal(m) }
# raises for computer accounts in the group

# after
group_members.each do |m|
  next unless %w[User Group].include?(m.Class)
  Puppet::Util::Windows::SID.ads_to_principal(m)
end
Defensive patterns

Strategy: type-guard

Validate before calling

# only convert real user/group COM objects
next unless ads.respond_to?(:ole_respond_to?) &&
             ads.ole_respond_to?(:objectSID) && ads.ole_respond_to?(:Name)

Type guard

def iads_user_or_group?(obj)
  !obj.nil? &&
    obj.respond_to?(:ole_respond_to?) &&
    obj.ole_respond_to?(:objectSID) &&
    obj.ole_respond_to?(:Name) &&
    %w[User Group].include?(obj.Class rescue nil)
end

Try / catch

begin
  principal = Puppet::Util::Windows::SID.ads_to_principal(member)
rescue Puppet::Error => e
  raise unless e.message.include?('IAdsUser or IAdsGroup')
  principal = Puppet::Util::Windows::SID.name_to_principal(member.Name) # resolve non-user/group members by name
end

Prevention

When it happens

Trigger: Enumerating members of a WinNT:// group and passing each member to ads_to_principal: computer accounts (IAdsComputer) and schema objects do not have objectSID/Name in the required shape; passing the result of WIN32OLE.connect on a non-user/group path; passing a Ruby hash or nil instead of a COM object.

Common situations: Group members that are machines (NT4-style domains put computers in groups), one-off COM objects returned from ADSI queries (e.g. WinNT://WORKGROUP/LANMANSERVER), code refactors that swap in plain Ruby objects, nil returned by a failed ADSI bind.

Related errors


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