Introduction
Windows has so many different editions that fit better for all sort of applications but with all the different Windows editions and kernel images, how does the system know which to boot and what settings to use?
The answer is product options key in registry(HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ProductOptions\), by querying the registry values ProductType
, ProductSuite
and ProductPolicy
, the system collects that information that is required to boot the way it should, this information contains the allowed physical memory supported, edition of windows, kernel mui language SKU and much more.
Requirements
Nothing is essential to use the DLL, but to understand it, you would need basic understanding of memorystream
, accessing reg., bitwise operations, datatypes, pinvoking, structs/structlayout.
Background
Microsoft never released any documentation for the ProductPolicy
key which made it hard to figure out the data structure, but after that, it's as easy as calling an unmanaged function in slc.dll called SLGetWindowsInformation
.
HRESULT WINAPI SLGetWindowsInformation(
_In_ PCWSTR pwszValueName, _Out_opt_ SLDATATYPE *peDataType, _Out_ UINT *pcbValue,
_Out_ PBYTE *ppbValue );
In a nutshell, you invoke SLGetWindowsInformation
with a PCSWTR/string
name of the query and it sets the values of datatype, value's byte[]
length and set a point to this value's byte[]
, using the datatype you can easily mainpulate the byte[]
.
For more information, please visit HERE.
The datatype is the same as the usual registry type/enum
but it only includes fewer options. I used it as it was documented in Microsoft, but other than that, you can use the usual registry type/enum
.
This is the usual reg types I usually use:
public const int
REG_NONE = 0,
REG_SZ = 1,
REG_EXPAND_SZ = 2,
REG_BINARY = 3,
REG_DWORD = 4,
REG_DWORD_LITTLE_ENDIAN = 4,
REG_DWORD_BIG_ENDIAN = 5,
REG_LINK = 6,
REG_MULTI_SZ = 7,
REG_RESOURCE_LIST = 8,
REG_FULL_RESOURCE_DESCRIPTOR = 9,
REG_RESOURCE_REQUIREMENTS_LIST = 10,
REG_QWORD = 11,
REG_QWORD_LITTLE_ENDIAN = 11;
Here is the one documented:
typedef enum _tagSLDATATYPE {
SL_DATA_NONE = REG_NONE,
SL_DATA_SZ = REG_SZ,
SL_DATA_DWORD = REG_DWORD,
SL_DATA_BINARY = REG_BINARY,
SL_DATA_MULTI_SZ = REG_MULTI_SZ,
SL_DATA_SUM = 100
} SLDATATYPE;
For more information, please visit HERE:
Using the Code
I've composed the whole thing in a library and named it WINSLMGR
.
How to use? (Check out the demo or follow those steps.)
Include the library as a reference in your project, then call:
WINSLMGR.Initialize();
Now you are able to access the WINSLMGR.SlDATA
list which contains all the information you need, it is a list of simple struct
(name : string
, type : SLDATATYPE
, value : string
)
foreach (var info in WINSLMGR.SlDATA)
{
listView1.Items.Add(new ListViewItem(new string[] { info.Name, info.Type.ToString(), info.value }));
}
Let's see some code snippets (the whole project is posted as open source for everyone to learn from and use)
The initialize
function which is used to retrieve the data from the registry then using a memory stream, I cast that data into its appropriate struct
s, then one last call to GetSLInfo
which pinvokes SLGetWindowsInformation
.
public static void Initialize()
{
ProductPolicy = GetByteArrayREGKey(registryPath, ProductPolicyStr);
ProductSuite = GetStringArrayREGKey(registryPath, ProductSuiteStr);
ProductType = GetStringREGKey(registryPath, ProductTypeStr);
using (var stream = new MemoryStream(WINSLMGR.ProductPolicy))
{
var header = stream.ReadStruct<SLHeader>();
while (stream.Position + 16 <= stream.Length)
{
var InitialPosition = stream.Position;
var DATA = stream.ReadStruct<SLDATA>();
var namebytes = new byte[DATA.NameLen];
stream.Read(namebytes, 0, namebytes.Length);
stream.Seek(InitialPosition + DATA.Size, SeekOrigin.Begin);
SlDATA.Add(GetSLInfo(namebytes));
}
}
}
The GetSLInfo
function simply pinvokes the SLGetWindowsInformation
, then manipulates the data to output the information in the appropriate format and type:
public static SLDATAINFO GetSLInfo(byte[] nameBytes)
{
var name = Encoding.ASCII.GetString(nameBytes).Replace("\0", string.Empty);
SLDATATYPE DataType;
uint cbValue;
IntPtr ValuePtr;
var result = (HRESULT)SLGetWindowsInformation
(name, out DataType, out cbValue, out ValuePtr);
if(result != HRESULT.S_OK || ValuePtr == IntPtr.Zero)
return new SLDATAINFO()
{
Name = name,
Type = SLDATATYPE.SL_DATA_NONE,
value = ValuePtr == IntPtr.Zero ?
"NO DATA, SLGetWindowsInformation return null ptr" : result.ToString()
};
var Value = new byte[cbValue];
Marshal.Copy(ValuePtr, Value, 0, Value.Length);
Marshal.FreeHGlobal(ValuePtr);
var val = string.Empty;
switch (DataType)
{
case SLDATATYPE.SL_DATA_SUM:
case SLDATATYPE.SL_DATA_BINARY:
case SLDATATYPE.SL_DATA_NONE:
val = ByteArrayToHexString.ByteArrayToHexViaLookup32UnsafeDirectWithDumpStyling
(Value);
break;
case SLDATATYPE.SL_DATA_MULTI_SZ:
case SLDATATYPE.SL_DATA_SZ:
val = Encoding.ASCII.GetString(Value).Replace("\0", string.Empty);
break;
case SLDATATYPE.SL_DATA_DWORD:
val = BitConverter.ToUInt32(Value, 0).ToString();
break;
}
return new SLDATAINFO()
{
Name = name,
Type = DataType,
value = val
};
}
Using the library is as easy as:
namespace windows_licensing_manager
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
listView1.View = View.Details;
listView1.GridLines = true;
listView1.FullRowSelect = true;
listView1.Columns.Add("Name", 250);
listView1.Columns.Add("Type", 150);
listView1.Columns.Add("Value", 450);
}
private void Form1_Load(object sender, EventArgs e)
{
WINSLMGR.Initialize();
label1.Text = "ProductSuite : " + WINSLMGR.ProductSuite;
label2.Text = "ProductType : " + WINSLMGR.ProductType;
foreach (var info in WINSLMGR.SlDATA)
{
listView1.Items.Add(new ListViewItem(new string[]
{ info.Name, info.Type.ToString(), info.value }));
}
}
}
}
Credits
Credits go to the following people:
- Remko Weijnen for his productpolicy struct (after 7 hours of testing, found his blog post while I was half way through)
- CodesInChaos for his super efficient
ByteArrayToHexString
functions (edited to change the format)