Softwarelizensinformationen in Windows mittels Powershell auslesen


Eigentlich wollte ich nur etwas mit dem KIOSK-Modus bzw. “Sperrmodus für Geräte” (Device Lockdown) von Windows herumspielen und den “Einheitlichen Schreibfilter” (Unified Write Filter) testen. Hier ein schöner Artikel über die Möglichkeiten: http://woshub.com/using-unified-write-filter-uwf-windows-10/. Bei der Recherche bin ich dann auf ein Script gestoßen, welches Softwarelizenzinformationen abruft: https://docs.microsoft.com/de-de/windows/configuration/set-up-a-kiosk-for-windows-10-for-desktop-editions#shell-launcher.

Mich hat vor allem Interessiert, wenn man SLGetWindowsInformationDWORD mit dem Parameter "EmbeddedFeature-ShellLauncher-Enabled" aufruft, dann gibt es da sicherlich noch weitere Parameter. Zunächst wurde also das Script aus dem MSDN-Artikel etwas modifiziert:

$source = @"
using System;
using System.Runtime.InteropServices;

public class CheckLicense
{
    public const int S_OK = 0;

    [DllImport("Slc.dll")]
    public static extern int SLGetWindowsInformationDWORD([MarshalAs(UnmanagedType.LPWStr)]string valueName, out int value);

}
"@

Add-Type -TypeDefinition $source –PassThru

Dadurch kann man SLGetWindowsInformationDWORD direkt aufrufen:

function Check-LicenseEnabled ([String]$License)
{
$enabled=[int]0
If ([CheckLicense]::SLGetWindowsInformationDWORD($license, [ref]$enabled) -ne [CheckLicense]::S_OK) {
  $enabled = 0
}

$enabled -ne 0
}

Durch die Funktion Check-LicenseEnabled kann man nun also den Aufruf durchführen:

Check-LicenseEnabled -License "EmbeddedFeature-ShellLauncher-Enabled"

Aber welche anderen Werte sind noch möglich? Bei der Suche nach EmbeddedFeature-ShellLauncher-Enabled bin ich dann auf diese Liste gestoßen: https://forums.mydigitallife.net/threads/discussion-windows-server-2016-14393-1607.70435/page-39#post-1285324. D. h. da geht extrem viel. Durch obige Funktion kann man nun beliebige Abfragen starten:

Check-LicenseEnabled CodeIntegrity-AllowConfigurablePolicy

Wenn man allerdings die Liste bei mydigitallife.net anschaut, dann fellen einem auch Werte auf die nicht nur 0 oder 1 sind, wie z. B. AppXDeploymentServer-License-MaxSideloadedPackagesCount. D. h. es gibt neben der SLGetWindowsInformationDWORD-Funktion noch eine weitere Funktion, nämlich SLGetWindowsInformation. Eigentlich gibt es eine ganze Palette von Funktionen, hier die Beschreibungen https://msdn.microsoft.com/en-us/library/windows/desktop/aa965829, allerdings wollen wir bei der SLGetWindowsInformation bleiben. Deshalb wurde der obige C#-Code etwas ergänzt:

$source = @"
using System;
using System.Runtime.InteropServices;

public class CheckLicense
{
    public const int S_OK = 0;

    public enum SLDATATYPE
    {
       SL_DATA_NONE      = 0, /* REG_NONE */
       SL_DATA_SZ        = 1, /* REG_SZ */
       SL_DATA_DWORD     = 4, /* REG_DWORD */
       SL_DATA_BINARY    = 3, /* REG_BINARY */
       SL_DATA_MULTI_SZ  = 7, /* REG_MULTI_SZ */
       SL_DATA_SUM       = 100
    };

    [DllImport("Slc.dll", CharSet = CharSet.Unicode)]
    public static extern uint SLGetWindowsInformation(
    string ValueName,
    out SLDATATYPE DataType,
    out uint cbValue,
    out IntPtr Value
    );

    [DllImport("Slc.dll")]
    public static extern int SLGetWindowsInformationDWORD([MarshalAs(UnmanagedType.LPWStr)]string valueName, out int value);

}
"@

Add-Type -TypeDefinition $source -PassThru

Damit steht uns nun SLGetWindowsInformation zur Verfügung. Allerdings ist der Aufruf ziemlich kompliziert, hier ein Beispiel:

# zunächst Variablen initialisieren
$DataType=[CheckLicense+SLDATATYPE]::SL_DATA_NONE
$cbValue=[uint32]0
$ValuePtr=[System.IntPtr]::Zero

# Aufruf, um z. B. "Kernel-EditionName" zu ermitteln:
$res = [uint32][CheckLicense]::SLGetWindowsInformation("Kernel-EditionName",[ref]$DataType,[ref]$cbValue,[ref]$ValuePtr)

Wenn $res 0 bzw. S_OK ist, dann hat man in $cbValue die Anzahl der Bytes, auf welche $ValuePtr zeigt, die vom Typ $DataType sind. Um diese zu erhalten, braucht man noch diese Zeilen:

$Value=New-Object Byte[] $cbValue
[System.Runtime.InteropServices.Marshal]::Copy($ValuePtr, $Value, 0, $Value.Length)
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($ValuePtr)
[System.Text.Encoding]::Unicode.GetString($Value)

Am Ende hat man bei einem Windows 10 Professional die Ausgabe Professional stehen. Die Basis für die Vorgehensweise lieferte dieser Artikel: https://stackoverflow.com/questions/31180457/how-to-pinvoke-slgetwindowsinformation-from-c-sharp. Packen wir die Aufrufe in eine Funktion:

Function Get-LicenseInformation ([string]$License) {
  $DataType=[CheckLicense+SLDATATYPE]::SL_DATA_NONE
  $cbValue=[uint32]0
  $ValuePtr=[System.IntPtr]::Zero
  $res = [uint32][CheckLicense]::SLGetWindowsInformation($License,[ref]$DataType,[ref]$cbValue,[ref]$ValuePtr)
  If ($res -eq [CheckLicense]::S_OK) {
    $Value=New-Object Byte[] $cbValue
    [System.Runtime.InteropServices.Marshal]::Copy($ValuePtr, $Value, 0, $Value.Length)
    [System.Runtime.InteropServices.Marshal]::FreeHGlobal($ValuePtr) # Calls Localfree!
    [System.Text.Encoding]::Unicode.GetString($Value)
  }
}

Damit kann man nun diese einfachen Aufrufe starten:

Get-LicenseInformation -License Kernel-EditionName

oder

Get-LicenseInformation -License Kernel-MUI-Language-SKU

Allerdings fällt auf, dass noch nicht alle Aufrufe funktionieren. Denn teilweise kommt Müll zurück, wie z. B. bei

Get-LicenseInformation -License Kernel-ExpirationDate

Aus diesem Grund muss Get-LicenseInformation noch etwas verbessert werden, vor allem muss auf $DataType reagiert werden:

Function Get-LicenseInformation ([string]$License) {
  $DataType=[CheckLicense+SLDATATYPE]::SL_DATA_NONE
  $cbValue=[uint32]0
  $ValuePtr=[System.IntPtr]::Zero
  $res = [uint32][CheckLicense]::SLGetWindowsInformation($License,[ref]$DataType,[ref]$cbValue,[ref]$ValuePtr)
  If ($res -eq [CheckLicense]::S_OK) {
    $Value=New-Object Byte[] $cbValue
    [System.Runtime.InteropServices.Marshal]::Copy($ValuePtr, $Value, 0, $Value.Length)
    [System.Runtime.InteropServices.Marshal]::FreeHGlobal($ValuePtr) # Calls Localfree!
    Switch ($DataType) {
      ([CheckLicense+SLDATATYPE]::SL_DATA_SZ)     {[System.Text.Encoding]::Unicode.GetString($Value)}
      ([CheckLicense+SLDATATYPE]::SL_DATA_BINARY) {$Value -join ‚-‚}
      ([CheckLicense+SLDATATYPE]::SL_DATA_DWORD)  {[System.BitConverter]::ToInt32($Value,0)}
      default       {Write-Error "Unknown DatatType: $DataType"}
    }
   
  }
}

Damit können nun alle gängigen Werte abgefragt werden. Wenn SL_DATA_BINARY zurückgegeben werden soll, könnte man ein Bytearray zurückgeben, aber ich habe mich entschieden gleich einen String zurückzugeben. Der einfacheren Lesbarkeit wegen. Damit man alles beieinander hat hier nochmal der Code komplett:

$source = @"
using System;
using System.Runtime.InteropServices;

public class CheckLicense
{
    public const int S_OK = 0;

    public enum SLDATATYPE
    {
       SL_DATA_NONE      = 0, /* REG_NONE */
       SL_DATA_SZ        = 1, /* REG_SZ */
       SL_DATA_DWORD     = 4, /* REG_DWORD */
       SL_DATA_BINARY    = 3, /* REG_BINARY */
       SL_DATA_MULTI_SZ  = 7, /* REG_MULTI_SZ */
       SL_DATA_SUM       = 100
    };

    [DllImport("Slc.dll", CharSet = CharSet.Unicode)]
    public static extern uint SLGetWindowsInformation(
    string ValueName,
    out SLDATATYPE DataType,
    out uint cbValue,
    out IntPtr Value
    );

    [DllImport("Slc.dll")]
    public static extern int SLGetWindowsInformationDWORD([MarshalAs(UnmanagedType.LPWStr)]string valueName, out int value);

}
"@

Add-Type -TypeDefinition $source -PassThru

Function Check-LicenseEnabled ([String]$License)
{
$enabled=[int]0
If ([CheckLicense]::SLGetWindowsInformationDWORD($license, [ref]$enabled) -ne [CheckLicense]::S_OK) {
  $enabled = 0
}

$enabled -ne 0
}

Function Get-LicenseInformation ([string]$License) {
  $DataType=[CheckLicense+SLDATATYPE]::SL_DATA_NONE
  $cbValue=[uint32]0
  $ValuePtr=[System.IntPtr]::Zero
  $res = [uint32][CheckLicense]::SLGetWindowsInformation($License,[ref]$DataType,[ref]$cbValue,[ref]$ValuePtr)
  If ($res -eq [CheckLicense]::S_OK) {
    $Value=New-Object Byte[] $cbValue
    [System.Runtime.InteropServices.Marshal]::Copy($ValuePtr, $Value, 0, $Value.Length)
    [System.Runtime.InteropServices.Marshal]::FreeHGlobal($ValuePtr) # Calls Localfree!
    Switch ($DataType) {
      ([CheckLicense+SLDATATYPE]::SL_DATA_SZ)     {[System.Text.Encoding]::Unicode.GetString($Value)}
      ([CheckLicense+SLDATATYPE]::SL_DATA_BINARY) {$Value -join ‚-‚}
      ([CheckLicense+SLDATATYPE]::SL_DATA_DWORD)  {[System.BitConverter]::ToInt32($Value,0)}
      default       {Write-Error "Unknown DatatType: $DataType"}
    }
   
  }
}

Advertisements

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s


%d Bloggern gefällt das: