puppetlabs/puppet · error · Puppet::Util::Windows::Error
Octet string must be an array of bytes
Error message
Octet string must be an array of bytes
What it means
Raised by Puppet::Util::Windows::SID.octet_string_to_principal when its argument is nil, does not respond to 'pack' (i.e. is not an Array-like byte container), or is empty. The method expects the raw binary SID as an array of bytes (e.g. [1,1,0,0,0,0,0,5,18,0,0,0]) and converts it to a SID::Principal via LookupAccountSid. This is an input-contract error, not a Win32 failure.
Source
Thrown at lib/puppet/util/windows/sid.rb:102
Puppet.debug("Could not retrieve raw SID bytes from '#{name}': #{e.message}") unless e.code == ERROR_INVALID_SID_STRUCTURE
end
raw_sid_bytes ? Principal.lookup_account_sid(raw_sid_bytes) : Principal.lookup_account_name(name)
rescue => e
Puppet.debug(e.message.to_s)
(allow_unresolved && raw_sid_bytes) ? unresolved_principal(name, raw_sid_bytes) : nil
end
module_function :name_to_principal
class << self; alias name_to_sid_object name_to_principal; end
# Converts an octet string array of bytes to a SID::Principal object,
# e.g. [1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0] is the representation for
# S-1-5-18, the local 'SYSTEM' account.
# Raises an Error for nil or non-array input.
# This method returns a SID::Principal with the account, domain, SID, etc
def octet_string_to_principal(bytes)
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"
endView on GitHub (pinned to e227c27540)
Solutions
- Convert the value to bytes before calling: use `str.bytes` (or `Array(value)` packing) so the argument responds to pack.
- Use the string API instead: Puppet::Util::Windows::SID.name_to_principal or octet_string_to_sid_string for string-form handling.
- Guard upstream: if reading ADSI objectSID, normalize with `sid = ads.objectSID; sid = sid.bytes if sid.is_a?(String)`.
- Nil-check the source of the bytes (e.g. verify the ADSI object actually has a SID) before converting.
Example fix
# before principal = Puppet::Util::Windows::SID.octet_string_to_principal(ads_user.objectSID) # raises 'Octet string must be an array of bytes' when WIN32OLE returns a String # after raw = ads_user.objectSID raw = raw.bytes if raw.is_a?(String) principal = Puppet::Util::Windows::SID.octet_string_to_principal(raw)
Defensive patterns
Strategy: type-guard
Validate before calling
# normalize the SID bytes before calling
bytes = raw
bytes = bytes.bytes if bytes.is_a?(String)
bytes = bytes.unpack('C*') if bytes.is_a?(String) # alternative for packed strings
raise ArgumentError, 'SID bytes missing' if bytes.nil? || bytes.empty? Type guard
def sid_bytes?(val)
val.is_a?(Array) && !val.empty? && val.all? { |b| b.is_a?(Integer) && b >= 0 && b <= 255 } && val.respond_to?(:pack)
end
raise ArgumentError, 'expected octet string of bytes' unless sid_bytes?(input) Try / catch
begin
Puppet::Util::Windows::SID.octet_string_to_principal(bytes)
rescue Puppet::Util::Windows::Error => e
raise unless e.message.include?('Octet string must be an array of bytes')
raise ArgumentError, "objectSID came back as #{bytes.class}; call .bytes on it"
end Prevention
- Always normalize WIN32OLE objectSID with `.bytes if raw.is_a?(String)` — Ruby upgrades have changed this return type historically.
- Prefer the string-based APIs (name_to_principal / valid_sid?) when you hold 'S-1-...' strings.
- Nil-check the ADSI source before converting — empty objectSID means the principal cannot be resolved.
When it happens
Trigger: Passing nil (e.g. an ADSI object whose objectSID property was missing/empty), passing a SID string 'S-1-5-18' instead of bytes, or passing a String — notably newer Ruby/Win32OLE returns objectSID as a String where older Rubys returned an Array of bytes, so ads_to_principal's call octet_string_to_principal(ads_object.objectSID) starts raising this after a Ruby upgrade.
Common situations: Ruby upgrades changing WIN32OLE VARIANT mapping for byte arrays (objectSID comes back as a binary String); code copying group member SIDs from ADSI into local groups; passing .sid (string form) where .sid_bytes was expected; handling principals whose SID is genuinely absent.
Related errors
- ads_object must be an IAdsUser or IAdsGroup instance
- Byte array for lookup_account_sid must not be nil and must b
- Invalid SID
- Invalid SID
- Failed to convert string SID: %{string_sid}
AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21).
Data as JSON: /api/errors/49fc29711e31b707.
Report an issue: GitHub.