Windows Shell Items: Lua parsing library

Windows Shell Items (AKA "shellbags") is an undocumented set of structures used internally within Windows to identify paths in Windows Folder Hierarchy. It is widely used in Windows Shell (and most visible in File Explorer), both as in-memory and in-file structures. Some formats embed them, namely:

  • Windows Shell link files (.lnk) Windows registry
  • Windows registry "ShellBags" keys

The format is mostly undocumented, and is known to vary between various Windows versions.

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of Windows Shell Items using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Lua source code to parse Windows Shell Items

windows_shell_items.lua

-- This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
--
-- This file is compatible with Lua 5.3

local class = require("class")
require("kaitaistruct")
local stringstream = require("string_stream")

-- 
-- Windows Shell Items (AKA "shellbags") is an undocumented set of
-- structures used internally within Windows to identify paths in
-- Windows Folder Hierarchy. It is widely used in Windows Shell (and
-- most visible in File Explorer), both as in-memory and in-file
-- structures. Some formats embed them, namely:
-- 
-- * Windows Shell link files (.lnk) Windows registry
-- * Windows registry "ShellBags" keys
-- 
-- The format is mostly undocumented, and is known to vary between
-- various Windows versions.
-- See also: Source (https://github.com/libyal/libfwsi/blob/main/documentation/Windows%20Shell%20Item%20format.asciidoc)
WindowsShellItems = class.class(KaitaiStruct)

function WindowsShellItems:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function WindowsShellItems:_read()
  self.items = {}
  local i = 0
  while true do
    local _ = WindowsShellItems.ShellItem(self._io, self, self._root)
    self.items[i + 1] = _
    if _.len_data == 0 then
      break
    end
    i = i + 1
  end
end

-- 
-- See also: Section 2.2.1 (https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-SHLLINK/[MS-SHLLINK].pdf)

WindowsShellItems.ShellItemData = class.class(KaitaiStruct)

function WindowsShellItems.ShellItemData:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function WindowsShellItems.ShellItemData:_read()
  self.code = self._io:read_u1()
  local _on = self.code
  if _on == 31 then
    self.body1 = WindowsShellItems.RootFolderBody(self._io, self, self._root)
  end
  local _on = (self.code & 112)
  if _on == 32 then
    self.body2 = WindowsShellItems.VolumeBody(self._io, self, self._root)
  elseif _on == 48 then
    self.body2 = WindowsShellItems.FileEntryBody(self._io, self, self._root)
  end
end


-- 
-- See also: Section 2.2.2 (https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-SHLLINK/[MS-SHLLINK].pdf)
WindowsShellItems.ShellItem = class.class(KaitaiStruct)

function WindowsShellItems.ShellItem:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function WindowsShellItems.ShellItem:_read()
  self.len_data = self._io:read_u2le()
  if self.len_data >= 2 then
    self._raw_data = self._io:read_bytes((self.len_data - 2))
    local _io = KaitaiStream(stringstream(self._raw_data))
    self.data = WindowsShellItems.ShellItemData(_io, self, self._root)
  end
end


-- 
-- See also: Source (https://github.com/libyal/libfwsi/blob/main/documentation/Windows%20Shell%20Item%20format.asciidoc#32-root-folder-shell-item)
WindowsShellItems.RootFolderBody = class.class(KaitaiStruct)

function WindowsShellItems.RootFolderBody:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function WindowsShellItems.RootFolderBody:_read()
  self.sort_index = self._io:read_u1()
  self.shell_folder_id = self._io:read_bytes(16)
end


-- 
-- See also: Source (https://github.com/libyal/libfwsi/blob/main/documentation/Windows%20Shell%20Item%20format.asciidoc#33-volume-shell-item)
WindowsShellItems.VolumeBody = class.class(KaitaiStruct)

function WindowsShellItems.VolumeBody:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function WindowsShellItems.VolumeBody:_read()
  self.flags = self._io:read_u1()
end


-- 
-- See also: Source (https://github.com/libyal/libfwsi/blob/main/documentation/Windows%20Shell%20Item%20format.asciidoc#34-file-entry-shell-item)
WindowsShellItems.FileEntryBody = class.class(KaitaiStruct)

function WindowsShellItems.FileEntryBody:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function WindowsShellItems.FileEntryBody:_read()
  self._unnamed0 = self._io:read_u1()
  self.file_size = self._io:read_u4le()
  self.last_mod_time = self._io:read_u4le()
  self.file_attrs = self._io:read_u2le()
end

WindowsShellItems.FileEntryBody.property.is_dir = {}
function WindowsShellItems.FileEntryBody.property.is_dir:get()
  if self._m_is_dir ~= nil then
    return self._m_is_dir
  end

  self._m_is_dir = (self._parent.code & 1) ~= 0
  return self._m_is_dir
end

WindowsShellItems.FileEntryBody.property.is_file = {}
function WindowsShellItems.FileEntryBody.property.is_file:get()
  if self._m_is_file ~= nil then
    return self._m_is_file
  end

  self._m_is_file = (self._parent.code & 2) ~= 0
  return self._m_is_file
end