Spectre und Meltdown schönes Powershell-Script welches auch den Zugriff und vor allem die Mächtigkeit von NtQuerySystemInformation verdeutlicht

10 Januar 2018

Microsoft hat für die geplagten Administratoren von Windowssystemen ein kleines Powershellscript herausgegeben. Damit werden einige Infos aus dem Prozessor, BIOS/UEFI und von Windows ermittelt und dargestellt. Damit lässt sich zum momentanen Zeitpunkt dann leicht nachvollziehen, ob ein Rechner noch Updates oder Änderungseinstellungen braucht.

Hier zunächst der Link zum Powershell-Script: https://gallery.technet.microsoft.com/scriptcenter/Speculation-Control-e36f0050. Hier noch die offizielle Beschreibung dazu von MS: https://support.microsoft.com/en-us/help/4073119/protect-against-speculative-execution-side-channel-vulnerabilities-in. Gleich noch der Link zur Powershell Gallery: https://www.powershellgallery.com/packages/SpeculationControl/1.0.3.

Wenn man Updates von einem Hardwarehersteller benötigt, ist auch diese Liste noch hilfreich: https://www.heise.de/newsticker/meldung/Meltdown-und-Spectre-Die-Sicherheitshinweise-und-Updates-von-Hardware-und-Software-Herstellern-3936141.html. Hier eine Liste mit Links von MS: https://support.microsoft.com/en-us/help/4073757/protect-your-windows-devices-against-spectre-meltdown.

Aber wo ich eigentlich drauf hinaus wollte, war diese nette PInvoke-Definition im Powershell-Skript:

    $NtQSIDefinition = @‘
    [DllImport("ntdll.dll")]
    public static extern int NtQuerySystemInformation(uint systemInformationClass, IntPtr systemInformation, uint systemInformationLength, IntPtr returnLength);
‚@
   
    $ntdll = Add-Type -MemberDefinition $NtQSIDefinition -Name ’ntdll‘ -Namespace ‚Win32‘ –PassThru

Um NtQuerySystemInformation sinnvoll anwenden zu können, braucht man noch etwas drumherum:

[System.IntPtr]$systemInformationPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(4)
[System.IntPtr]$returnLengthPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(4)
[System.UInt32]$systemInformationClass = 201
[System.UInt32]$systemInformationLength = 4

Nun kann man einen Aufruf wagen:

$retval = $ntdll::NtQuerySystemInformation($systemInformationClass, $systemInformationPtr, $systemInformationLength, $returnLengthPtr)

Ist $retval vom Wert 0 ist alles gut, bei 0xc0000003 oder 0xc0000002 ist $systemInformationClass nicht vorhanden. Alle anderen Werte stellen einen Fehler dar.

Nach dem Aufruf zeigt $systemInformationPtr auf einen Speicherbereich, welche die Daten vom System enthält. Diese Daten kann man z. b. so abfragen:

[System.UInt32]$flags = [System.UInt32][System.Runtime.InteropServices.Marshal]::ReadInt32($systemInformationPtr)

$flags enthält anschließend die ersten 4Bytes.

Hat man die Informationen, die einen interessieren, dann gibt man den Speicher wieder brav frei:

if ($systemInformationPtr -ne [System.IntPtr]::Zero) {
  [System.Runtime.InteropServices.Marshal]::FreeHGlobal($systemInformationPtr)
}
 
if ($returnLengthPtr -ne [System.IntPtr]::Zero) {
  [System.Runtime.InteropServices.Marshal]::FreeHGlobal($returnLengthPtr)
}

Die Freigabe sollte auch im Fehlerfall erfolgen, weshalb man ein try catch drumherum bauen sollte.

Jetzt stellt sich die Frage, wie kommt man an die gültigen SysteminformationClass-Werte und die zurückgegebenen Strukturen? Diese findet man zum Beispiel hier: http://www.exploit-monday.com/2013/06/undocumented-ntquerysysteminformation.html. Weitere Infos gibt es hier: http://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/
sysinfo/query.htm
.

Wer mehr mit der Geschichte spielen möchte, der nimmt Get-NtSystemInformation aus dem PowershellArsenal: https://github.com/mattifestation/PowerShellArsenal.

Ach wenn ich schon am verlinken bin, hier noch wie man damit PerformanceCounter abfragen kann: http://blog.whatsupduck.net/2010/05/querying-peak-commit-bytes-with.html. Oder wie man gelockte Filehandles in Erfahrung bringen kann: https://blogs.technet.microsoft.com/heyscriptingguy/2013/12/01/weekend-scripter-determine-process-that-locks-a-file/. Der Kernel weiß eben über alles Bescheid Smiley.

Wer sich für weitere Möglichkeiten von NtQuerySystemInformation interessiert, der sollte sich noch nach ZWAPI.H umschauen…

Advertisements

Beschreibungen zu Ergebnissen der letzten Ausführung bei Aufgaben unter Windows, oder die bessere Aufgabenplanung-Übersicht!

9 Januar 2018

Windows 10 setzt immer mehr auf die Aufgabenplanung. Oft hat man einen Ergebniswert am Bildschirm unter der Spalte “Ergebnis der letzten Ausführung” und fragt sich dann, wie das zu seinen Powershellergebnissen passt, welche mittels der Eigenschaft LastTaskResult vom Get-ScheduledTaskInfo seinen Wert als dezimale Zahl liefert. Das Problem liegt darin, dass der Ergebniswert am Bildschirm als Hexwert angegeben wird. Damit kämpft man dann immer im Kopf ob die beiden Werte zueinander passen. Um die Sache etwas einfacher zu gestalten kann man diesen Aufruf verwenden:

"0x{0:x}" -f (Get-ScheduledTask -TaskName "Schedule Scan"|Get-ScheduledTaskInfo).lasttaskresult

Damit wird der Wert von LastTaskResult Hexadezimal ausgegeben.

Stellt sich die Frage, welche Werte gibt es überlicherweise und welche Bedeutung haben Sie beim Aufgabenplaner? So kann man alle bekannten Werte ermitteln:

(Get-ScheduledTask|Get-ScheduledTaskInfo).lasttaskresult|select -Unique| % {"0x{0:x}" -f $_ }

Auf einem frischen Windows 10 v1709 ergibt dies:

0x41303
0x0
0x80090030
0x800710e0
0x800f0950
0x41301
0x80070005
0x80070057
0x420

Unten eine Tabelle der Dezimalen- und Hexadezimalen-Werte sowie deren Beschreibung aus der Spalte “Ergebnis der letzten Ausführung”. Wichtig zu beachten, wenn man den Hexwert in Dezimal wandelt, bekommt man bei den 0x80000000 und höheren Werten einen Überlauf und dadurch negative Zahlen, welche dann nicht mehr mit LastTaskResult übereinstimmen, da LastTaskResult vom Typ UInt32 ist. Um die korrekten Werte zu bekommen kann man 0x entfernen und den restlichen Hexadezimalen-Wert über diese Funktion wandeln lassen:

[int64]::Parse("0x80070002".Replace("0x",“), ‚Hexnumber‘)

oder einfacher:

[UInt32]"0x80070002"

LastTaskResult (UInt32) Hexadezimal Beschreibung aus “Ergebnis der letzten Ausführung”
0 0x0 Der Vorgang wurde erfolgreich beendet. (0x0)
267011 0x41303 Die Aufgabe wurde noch nicht ausgeführt. (0x41303)
2148073520 0x80090030 Das Gerät, das von diesem kryptografischen Anbieter angefordert wird, kann nicht verwendet werden. (0x80090030)
2147946720 0x800710E0 Die Anforderung wurde vom Operator oder Administrator zurückgewiesen. (0x800710E0)
2148469072 0x800F0950 (0x800F0950)
267009 0x41301 Die Aufgabe wird momentan ausgeführt. (0x41301)
2147942405 0x80070005 Zugriff verweigert (0x80070005)
2147942487 0x80070057 Falscher Parameter. (0x80070057)
1056 0x420 (0x420)
2147942402 0x80070002 Das System kann die angegebene Datei nicht finden. (0x80070002)
2147746132 0x80040154 Klasse nicht registriert (0x80040154)
268435456 0x10000000 (0x10000000)
259 0x103 (0x103)
1 0x1 (0x1)
2147943467 0x8007042B Der Prozess wurde unerwartet beendet. (0x8007042B)
2147943140 0x800702E4 Der angeforderte Vorgang erfordert erhöhte Rechte. (0x800702E4)
2379777 0x245001 (0x245001)
     

Wenn in obiger Tabelle ein Wert fehlt, der könnte hier mit samt Erklärung fündig werden: https://msdn.microsoft.com/en-us/library/windows/desktop/aa383604.

Da ich mir jetzt schon die Mühe gemacht habe, das alles zusammenzutragen, dann kann ich auch noch gleich eine Funktion daraus basteln:

Function Get-ScheduledTaskLastRunResultText ([UInt32]$LastRunResult) {
  Switch ($LastRunResult) {
     0          {"Der Vorgang wurde erfolgreich beendet. (0x0)"}
     267011     {"Die Aufgabe wurde noch nicht ausgeführt. (0x41303)"}
     2148073520 {"Das Gerät, das von diesem kryptografischen Anbieter angefordert wird, kann nicht verwendet werden. (0x80090030)"}
     2147946720 {"Die Anforderung wurde vom Operator oder Administrator zurückgewiesen. (0x800710E0)"}
     2148469072 {"(0x800F0950)"}
     267009     {"Die Aufgabe wird momentan ausgeführt. (0x41301)"}
     2147942405 {"Zugriff verweigert (0x80070005)"}
     2147942487 {"Falscher Parameter. (0x80070057)"}
     1056       {"(0x420)"}
     2147942402 {"Das System kann die angegebene Datei nicht finden. (0x80070002)"}
     2147746132 {"Klasse nicht registriert (0x80040154)"}
     268435456  {"(0x10000000)"}
     259        {"(0x103)"}
     1          {"(0x1)"}
     2147943467 {"Der Prozess wurde unerwartet beendet. (0x8007042B)"}
     2147943140 {"Der angeforderte Vorgang erfordert erhöhte Rechte. (0x800702E4)"}
     2379777    {"(0x245001)"}
     Default {"Unknown LastRunResult: $LastRunResult"}
  }
}

Dadurch kann man nun mittels Aufruf von

Get-ScheduledTaskLastRunResultText -LastRunResult 267011

der Text

Die Aufgabe wurde noch nicht ausgeführt. (0x41303)

erhalten werden.

Will man einen hexadezimalen Wert übergeben, dann sollte dies in folgender Form erfolgen:

Get-ScheduledTaskLastRunResultText -LastRunResult ([UInt32]’0x800710E0′)

Wichtig sind die Anführungsstriche bei der Hexadezimalzahl!! Beim Aufruf von dieser Zeile, erhält man eine Fehlermeldung:

Get-ScheduledTaskLastRunResultText -LastRunResult ([UInt32]0x800710E0)
Der Wert "-2147020576" kann nicht in den Typ "System.UInt32" konvertiert werden. Fehler: "Der Wert für einen UInt32
war zu groß oder zu klein."
In Zeile:1 Zeichen:1
+ Get-ScheduledTaskLastRunResultText -LastRunResult ([UInt32]0x800710E0 …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvalidCastIConvertible

Also Vorsicht! Bei Verwendung von Hexadezimalzahlen und UINT32!

So aber nun zum Abschluss noch was sinnvolles mit der Funktion Get-ScheduledTaskLastRunResultText. Was mich schon immer an der Aufgabenplanung gestört hat, das man nicht einfach eine Ansicht ohne Gruppierungen hat, wo einfach in einer Tabelle alles dargestellt wird. Nun dies ist jetzt mit diesem Aufruf möglich:

Get-ScheduledTask | Select Taskname, @{Name="LastTaskResult";E={Get-ScheduledTaskLastRunResultText (Get-ScheduledTaskInfo $_).LastTaskResult}}  | Out-GridView

Ich denke das Prinzip ist nun klar. Also machen wir nun die große Lösung:

Get-ScheduledTask | Select Taskname, State, @{Name="LastTaskResult";E={Get-
ScheduledTaskLastRunResultText (Get-ScheduledTaskInfo $_).LastTaskResult}}, @{Name="LastRunTime";E={(Get-ScheduledTas
kInfo $_).LastRunTime}}, @{Name="NextRunTime";E={(Get-ScheduledTaskInfo $_).NextRunTime}}, TaskPath  | Out-GridView

Bang! Da geht der Rechner zwar kurz in die Knie aber es sind auf einen Schlag alle wichtigen Informationen verfügbar um schnell einen Überblick zu bekommen. Vor allem kann man nun endlich Suchen und nach Spalten sortieren!

Automatische Windows-Updates bei Windows 10 (v1709) unterdrücken

8 Januar 2018

Bei Windows 10 mit der Version v1709 ist es nicht mehr so einfach möglich Automatische Windows-Updates zu verschieben, zu verzögern oder gar komplett zu unterbinden. Ok man könnte dem Rechner den Internetzugang unterbinden aber macht das Sinn? Leider herrscht hier absolutes Chaos und es ist nicht leicht sinnvolle Informationen dazu zu erhalten. Vor allem, da bei jedem Halbjahresupdate Änderungen am Updatemechanismus stattfinden. OK in der Knowledge Base ist eine Methode beschrieben, Problembehaftete Updates loszuwerden: https://support.microsoft.com/en-us/help/3183922/how-to-temporarily-prevent-a-windows-update-from-reinstalling-in-windo das gleiche für Treiber: https://support.microsoft.com/en-us/help/3073930/how-to-temporarily-prevent-a-driver-update-from-reinstalling-in-window.

Es gibt jedoch momentan jedoch noch eine einfachere Möglichkeit Automatische Updates zu unterbinden, man muss nur den Zugriff auf den Update-Session-Orchestrator Client (USOClient.EXE) unterbinden.

Öffnet man als Administrator eine Powershell-Eingabeaufforderung, so führt man einfach folgende Befehle aus:

cd "$($Env:windir)\System32"
.\takeown.exe /f .\UsoClient.exe /a
.\icacls.exe "$($Env:windir)\System32\UsoClient.exe" /inheritance:r /remove "Administratoren" "
Authentifizierte Benutzer" "Benutzer" "System"

Wichtig! Obige Befehle funktionieren nur auf einer deutschen Windowsversion! Nach dem Aufruf kann UsoClient.exe nicht mehr aufgerufen werden und es erscheint eine Fehlermeldung:

PS C:\Windows\System32> .\UsoClient.exe
Fehler beim Ausführen des Programms "UsoClient.exe": Zugriff verweigertIn Zeile:1 Zeichen:1
+ .\UsoClient.exe
+ ~~~~~~~~~~~~~~~.
In Zeile:1 Zeichen:1
+ .\UsoClient.exe
+ ~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceUnavailable: (:) [], ApplicationFailedException
    + FullyQualifiedErrorId : NativeCommandFailed

Möchte man die Automatischen Updates wieder zulassen, so kann man UsoClient.exe wieder die Standardrechte zukommen lassen, indem man diesen Aufruf startet:

.\icacls.exe "$($Env:windir)\System32\UsoClient.exe" /reset

Wie funktioniert die Sache? Im Grunde entscheidend ist der Entzug der Ausführungsrechte des Benutzers bei UsoClient.EXE, d. h. es wird effektiv VORDEFINIERT\Benutzer:(RX) von UsoClient.EXE entzogen. Die aktuellen Einstellungen kann man so einsehen:

.\icacls.exe "$($Env:windir)\System32\UsoClient.exe"

Grundlage für diese Vorgehensweise war hier beschrieben: https://www.tenforums.com/tutorials/8013-enable-disable-windows-update-automatic-updates-windows-10-a-post1224927.html#post1224927. Es gibt auch noch eine Hammermethode generell Updates loszuwerden, diese ist hier beschrieben: https://www.tenforums.com/tutorials/8013-enable-disable-windows-update-automatic-updates-windows-10-a-50.html#post1236513. D. h. solange man bei oben beschriebener Methode bleibt und auf die Hammermethode verzichtet, kann man manuelle Updates über Einstellungen->Updates noch anstoßen, auch die Defenderupdates klappen problemlos.

In der Aufgabenplanung sind unter Microsoft\Windows\UpdateOrchestrator verschiedene Aufgaben welche mindestens einmal am Tag ausgeführt werden, welche nach Updates Ausschau halten. Dabei wird UsoClient.EXE aufgerufen. Wenn die Rechte entzogen sind, kann der Aufruf nicht erfolgen und die Aufgabenplanung meldet den Fehler “Zugriff verweigert (0x80070005)”, prüfbar mit diesem Befehl:

"0x{0:x}" -f (Get-ScheduledTask -TaskName "Schedule Scan"|Get-ScheduledTaskInfo).lasttaskresult

Der Beweis, dass dem tatsächlich so ist, kann hiermit erbracht werden:

Get-ScheduledTask -TaskName "Schedule Scan"| select taskname -ExpandProperty actions

taskname         : Schedule Scan
Id               :
Arguments        : StartScan
Execute          : %systemroot%\system32\usoclient.exe
WorkingDirectory :
PSComputerName   :

UsoClient.EXE kennt folgende Parameter:

StartScan
StartDownload
StartInstall
ScanInstallWait
RefreshSettings
ResumeUpdate

Zum Thema gäbe es noch mehr zu schreiben, weil einige zusätzliche Aufgaben je nach Status des Vorgangs erzeugt, bzw. geändert werden, deshalb hier ein paar Aufgabennamen, welche hier teilweise auftauchen können:

Reboot
Schedule Scan
USO_Broker_Display
AC Power Download
AC Power Install
Maintenance Install
MusUx_LogonUpdateResults

Neben obigen Aufgaben kann man davon ausgehen, wenn es AC Power gibt, dass es bei Notebooks evtl. auch DC Power Aufgaben, also

DC Power Download
DC Power Install

geben könnte. Daneben gab es beim Testen auch noch einmal

Schedule Scan Retry

als keine Netzwerkverbindung zur Verfügung stand.

Obige Ausführungen zu den Aufgabennamen beziehen sich auf eine Testmaschine. Auf einem Rechner welcher in einer Domäne war, welcher bereits auch Windows 7 und mehrere Windows 10 Updates gesehen hatte, hatte folgende Aufgaben zu bieten:

Combined Scan Download Install
Maintenance Install
MusUx_UpdateInterval
Policy Install
Reboot
Refresh Settings
Resume On Boot
Schedule Scan
USO_UxBroker_Display
USO_UxBroker_ReadyToReboot

Man kann davon ausgehen, dass z. B. Policy Install und Refresh Settings mit Gruppenrichtlinien etwas zu tun haben.

Die Aufgabennamen kann man mittels

Get-ScheduledTask | where taskpath -match UpdateOrchestrator
# oder
Get-ScheduledTask -TaskPath "\Microsoft\Windows\UpdateOrchestrator\"

ermitteln. Weitere Powershell-Aufrufe in diesem Zusammenhang seien hier noch kurz genannt:

# Dienststatus:
Get-Service UsoSvc,wuauserv
# Ermitteln von LastRuntime, LastTaskResult und NextRuntime:
Get-ScheduledTask | where taskpath -match UpdateOrchestrator|Get-ScheduledTaskInfo
# Status der Aufgaben:
Get-ScheduledTask | where taskpath -match UpdateOrchestrator|fl *
# Aktionen der einzelnen Aufgaben:
Get-ScheduledTask -TaskPath "\Microsoft\Windows\UpdateOrchestrator\"| select taskname -ExpandProperty actions

Windows hat mittlerweile auch Powershell-Cmdlets zum Updates aufrufen, diese sollen noch kurz genannt werden:

Get-WUIsPendingReboot
Get-WULastInstallationDate
Get-WULastScanSuccessDate
Start-WUScan
Install-WUUpdates
# schon länger verfügbar
Get-Hotfix

Um alles abzurunden wären jetzt noch Windows Ereigniseinträge vom UpdateOrchestrator eine schöne Sache, doch leider gibt es dazu noch keinen Provider. Komisch oder ich bin blind…

Nein, da war noch was. Unter C:\ProgramData\UsoShared\Logs finden sich .ETL-Dateien. D. h. da gibt es was! Dann sei noch schnell C:\ProgramData\USOPrivate\UpdateStore genannt. Aber dazu vielleicht irgendwann mehr.

Softwarelizensinformationen in Windows mittels Powershell auslesen

7 Januar 2018

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"}
    }
   
  }
}

Daten aus Starmoney mittels Powershell bearbeiten, Problem mit Delimiter sowie doppelten Feldnamen Kommentar und Originalbetrag

5 Januar 2018

In Starmoney kann man schon immer über den Punkt Verwaltung den Datenexport aufrufen und Daten im CSV-Format exportieren. Allerdings bin ich gerade auf ein Problem gestoßen, bei dem Versuch, die Daten als CSV-Datei zu speichern und mittels Powershell zu verarbeiten. Zum Einsatz kam Starmoney 10, ob dies bei anderen Versionen auch ein Problem ist, keine Ahnung.

Export aus Starmoney
Man geht also auf Verwaltung, Datenexport, wählt das Konto aus, bei Dateiformat verwendet man “Konfigurierbare CSV-Datei (UTF-8)(*.csv)”, als Konfigurationsname beim Format “Alle Spalten”, bestimmt den Zeitraum und klickt dann unten rechts auf Export. Die CSV-Datei findet sich dann im Verzeichnis C:\ProgramData\StarMoney 10\profil\export mit dem Dateinamen “Kontonummer_Währung_Datum_Uhrzeit.CSV”, also z. B. 99999999_EUR_20180105_155401.csv. Damit könnte die Sache bereits erledigt sein.

Import der CSV-Daten in Powershell
Ein einfaches

$b=Import-Csv ‚C:\ProgramData\StarMoney 10\profil\export\99999999_EUR_20180105_155401.csv‘
$b

sollte nun bereits alle Daten anzeigen. Doch leider ist dem nicht so! Denn es wird nur

Kontonummer;"Bankleitzahl";"Betrag";"Buchungstext";"Betrag – Währung";"Buchungstag";"Begünstigter/Absender –
Bankleitzahl";"Begünstigter/Absender – Kontonummer";"Begünstigter/Absender –
Name";"Internet";"Kategorie";"Kommentar";"Kostenstelle";"Laufende Nummer";"Marker";"Originalbetrag";"Originalbetrag –
Währung";"Primanota";"Saldo";"Saldo – Währung";"Storno";"Storno – Originalbetrag";"Splittbuchung – Auftraggeber /
Name";"Splittbuchung – Kategorie";"Splittbuchung – Kostenstelle";"Splittbuchung – Originalbetrag";"Splittbuchung –
Unterkategorie";"Splittbuchung – Verwendungszweckzeile 1";"Textschlüssel";"Unterkategorie";"Verwendungszweckzeile
1";"Verwendungszweckzeile 2";"Verwendungszweckzeile 3";"Verwendungszweckzeile 4";"Verwendungszweckzeile
5";"Verwendungszweckzeile 6";"Verwendungszweckzeile 7";"Verwendungszweckzeile 8";"Verwendungszweckzeile
9";"Verwendungszweckzeile 10";"Verwendungszweckzeile 11";"Verwendungszweckzeile 12";"Verwendungszweckzeile
13";"Verwendungszweckzeile
14";"Wertstellungstag";"Steuersatz";"SteuersatzWaehr";
"Steuerbetrag";"SteuerbetragWaehr";"Fibu-Nr.";"Splittbuchung –
Steuersatz";"Splittbuchung – SteuersatzWaehr";"Splittbuchung – Steuerbetrag";"Splittbuchung –
SteuerbetragWaehr";"Splittbuchung – Fibu-Nr.";"Kommentar";"Abweichender Auftraggeber";"Ende zu Ende
Referenz";"Kundenreferenz";"Mandatsreferenz";"Purpose Code";"Rückgabegrund";"Rückgabegrund (Code)";"Entgelt
(fremd)";"Entgelt (fremd) Währung";"Entgelt (eigen)";"Entgelt (eigen)
Währung";"Zinskompensationsbetrag";"Zinskompensationsbetrag Währung";"Originalbetrag";"Originalbetrag
Währung";"Gläubiger-Identifikation"
———————————————————————————————————————–
99999999;"99999999";100

ausgegeben. Wo ist das Problem?

Erstes Problem der Feldtrenner
Wenn man aus Starmoney exportiert, wird der Semikolon (;) Feldtrenner verwendet. Man könnte dies nun beim Export aus Starmoney ändern, aber einfacher ist es Powershell zu sagen, dass ; verwendet werden soll. Dazu gibt es zwei Möglichkeiten, entweder über den Parameter –Useculture oder durch direkte Angabe von –Delimiter ‚;‘. Den aktuellen Delimiter kann man mittels (Get-Culture).textinfo.listseparator in Erfahrung bringen. Wir wählen aber die direkte Angabe des Delimiter beim Import, damit die Sachlage eindeutig ist:

$b=Import-Csv ‚C:\ProgramData\StarMoney 10\profil\export\99999999_EUR_20180105_155401.csv‘ –Delimiter ‚;‘
Import-Csv : Das Element "Kommentar" ist bereits vorhanden.
In Zeile:1 Zeichen:4
+ $b=Import-Csv .\test.csv -Delimiter ‚;‘
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Import-Csv], ExtendedTypeSystemException
    + FullyQualifiedErrorId : AlreadyPresentPSMemberInfoInternalCollectionAdd,Microsoft.Power
Shell.Commands.ImportCsvCommand

Zweites Problem doppelte Feldnamen
Oha, was geht da? Doppelte Feldnamen? Das sollte eigentlich schon vom exportierenden Programm unterbunden werden. Doch Starmoney lässt sich davon nicht beeindrucken. Selbst beim Versuch über das umständliche Konfigurieren innerhalb von Starmoney und entfernen des zweiten Kommentarfelds und Zuweisung über den Konfigurationsnamen und mehrfachen Versuchen, bestand Starmoney darauf, dass Kommentarfeld doppelt zu exportieren. Selbst als das Kommentarfeld in der CSV-Datei manuell geändert wurde, wartete Starmoney mit der nächsten Fehlermeldung auf:

$b=Import-Csv ‚C:\ProgramData\StarMoney 10\profil\export\99999999_EUR_20180105_155401.csv‘ –Delimiter ‚;‘
Import-Csv : Das Element "Originalbetrag" ist bereits vorhanden.
In Zeile:1 Zeichen:4
+ $b=Import-Csv .\test.csv -Delimiter ‚;‘
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Import-Csv], ExtendedTypeSystemException
    + FullyQualifiedErrorId : AlreadyPresentPSMemberInfoInternalCollectionAdd,Microsoft.Power
Shell.Commands.ImportCsvCommand

Ja da kommt Freude auf.

Automatisch doppelte Feldnamen entfernen
Da der Export möglichst einfach sein sollte, wurde darauf verzichtet zu versuchen Starmoney das korrekte Verhalten beizubringen statt dessen wird nun über dieses Powershellscript der Header so geändert, dass die beiden doppelten Feldnamen eindeutig werden:

$filename=’C:\ProgramData\StarMoney 10\profil\export\99999999_EUR_20180105_155401.csv‘
[regex]$pattern=’"Kommentar"‘
$c=$pattern.Replace([IO.File]::ReadAllText($filename),’"Kommentar1"‘,1)
[regex]$pattern=’"Originalbetrag"‘
$c=$pattern.Replace($c,’"Originalbetrag1"‘,1)
$c|set-content (Join-Path (Split-Path $filename) HeaderOK.csv) -Encoding UTF8

Dabei wird der erste Feldnamen Kommentar durch Kommentar1 und der zweite Feldname Originalbetrag durch Originalbetrag1 ersetzt sowie anschließend in der Datei HeaderOK.csv gespeichert.

Dadurch können nun die Daten problemlos eingelesen werden:

$b=import-csv (Join-Path (Split-Path $filename) HeaderOK.csv) -Delimiter ‚;‘

Passwörter in Powershell generieren

28 Dezember 2017

Über eine .Net-Framework-Funktion kann man ganz einfach relativ sichere Passwörter erzeugen.

Add-Type -AssemblyName System.Web
$pw=[System.Web.Security.Membership]::GeneratePassword(10,1)
$pw

$pw enthält nun ein Passwort mit 10 Stellen und mindestens einem Sonderzeichen. Die 1 gibt immer nur die Mindestanzahl an Sonderzeichen an, es kann auch passieren, dass mehrere Sonderzeichen enthalten sind.

Um die Einfachheit eines Passworts bestimmen zu können, kann man diese Entropie-Funktion benutzen:

function Get-Entropy
{
    Param (
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [Byte[]]
        $Bytes
   )
 
   $FrequencyTable = @{}
   foreach ($Byte in $Bytes) {
       $FrequencyTable[$Byte]++
   }
   $Entropy = 0.0
 
   foreach ($Byte in 0..255)
   {
       $ByteProbability = ([Double]$FrequencyTable[[Byte]$Byte])/$Bytes.Length
       if ($ByteProbability -gt 0)
       {
           $Entropy += -$ByteProbability * [Math]::Log($ByteProbability, 2)
       }
   }
   $Entropy
}

Sie stammt aus diesem tollen Artikel, der sich mit Get-Random und Zufallszahlen beschäftigt: http://www.powershellmagazine.com/2014/07/28/testing-the-effectiveness-of-get-random/.

Damit Get-Entropy verwendet werden kann, brauchen wir noch das Passwort als Byte-Array:

$pwb=[System.Text.Encoding]::ASCII.GetBytes($pw)
Get-Entropy $pwb

Verwendet man also ein einfaches Passwort wie z. B. nur Leerzeichen:

$pw="         "
Get-Entropy ([System.Text.Encoding]::ASCII.GetBytes($pw))

dann erhält man als Entropie 0 was schlecht ist. Verwendet man statt dessen aber obige GeneratePassword-Methode, dann erhält man in der Regel eine Entropie um 3,2:

$pw=[System.Web.Security.Membership]::GeneratePassword(10,1)
Get-Entropy ([System.Text.Encoding]::ASCII.GetBytes($pw))

Durch Verwendung von weiteren Stellen kann man die Entropie erhöhen. Wenn man den Passwordstring auch noch auf das Vorhandensein bestimmter Zeichen prüft, die zwingend enthalten sein müssen, könnte man die Entropie weiter erhöhen.

Wenn man eine Variante braucht, welche auch die Entropie von Dateien berechnen kann, der kann diese Funktion benutzen: https://www.powershellgallery.com/packages/PowerSploit/1.0.0.0/Content/ReverseEngineering%5CGet-Entropy.ps1. Im Prinzip die gleiche wie oben, nur um FileInfo mit ReadAllBytes erweitert.

Falschmeldungen von Virenscannern

19 Dezember 2017

Übereifrige Virenscanner können schon mal eine harmlose Software als gefährlich einstufen. Wenn man sich absolut sicher ist, dass eine Software Opfer einer falschen Klassifikation wurde, dann kann man solche sogenannten False Positives dem entsprechenden Antivirensoftwarehersteller melden.

Hier ein Link zu einer Seite, wo sich jemand viel Mühe zum Thema gemacht hat und von allen aktuellen Anbietern Adressen und Vorgehensweisen zum Einsenden von Fehlalarmen dokumentiert hat: https://www.techsupportalert.com/content/how-report-malware-or-false-positives-multiple-antivirus-vendors.htm

LoaderException bei Add-Type in Powershell

11 Dezember 2017

Will man in Powershell mittels Add-Type eine DLL Datei laden, so kann es passieren, dass der Versuch mit einer Ausnahme abgebrochen wird:

PS> add-type -path ‚C:\Program Files\PackageManagement\NuGet\Packages\MimeKit.1.22.0\lib\net451\MimeKit.dll‘
add-type : Mindestens ein Typ in der Assembly kann nicht geladen werden. Rufen Sie die LoaderExceptions-Eigenschaft ab, wenn Sie weitere Informationen benötigen.
In Zeile:1 Zeichen:1
+ add-type -path ‚C:\Program Files\PackageManagement\NuGet\Packages\Mim …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Add-Type], ReflectionTypeLoadException
    + FullyQualifiedErrorId : System.Reflection.ReflectionTypeLoadException,Microsoft.PowerShell.Commands.AddTypeComma
   nd

Schön solche umfassenden Fehlermeldungen, wo bekomme ich nun aber die LoaderException-Eigenschaft her? Das Fehlerobjekt liefert zunächst nix brauchbares. Der Parameter –Force ist wichtig damit alle Eigenschaften angezeigt werden:

PS> $error|fl * -Force

PSMessageDetails      :
Exception             : System.Reflection.ReflectionTypeLoadException: Mindestens ein Typ in der Assembly kann nicht
                        geladen werden. Rufen Sie die LoaderExceptions-Eigenschaft ab, wenn Sie weitere Informationen
                        benötigen.
                           bei System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)
                           bei System.Reflection.Assembly.GetTypes()
                           bei Microsoft.PowerShell.Commands.AddTypeCommand.LoadAssemblyFromPathOrName(List`1
                        generatedTypes)
                           bei Microsoft.PowerShell.Commands.AddTypeCommand.EndProcessing()
                           bei System.Management.Automation.CommandProcessorBase.Complete()
TargetObject          :
CategoryInfo          : NotSpecified: (:) [Add-Type], ReflectionTypeLoadException
FullyQualifiedErrorId : System.Reflection.ReflectionTypeLoadException,Microsoft.PowerShell.Commands.AddTypeCommand
ErrorDetails          :
InvocationInfo        : System.Management.Automation.InvocationInfo
ScriptStackTrace      : bei <ScriptBlock>, <Keine Datei>: Zeile 1
PipelineIterationInfo : {}

Aber es gibt bei der Exception-Eigenschaft etwas:

PS> $Error.Exception|fl -Force

Types            : {MimeKit.MimeParser, MimeKit.AttachmentCollection, MimeKit.BodyBuilder,
                   MimeKit.ContentDisposition…}
LoaderExceptions : {System.IO.FileNotFoundException: Die Datei oder Assembly "BouncyCastle.Crypto, Version=1.8.1.0,
                   Culture=neutral, PublicKeyToken=0e99375e54769942" oder eine Abhängigkeit davon wurde nicht
                   gefunden. Das System kann die angegebene Datei nicht finden.
                   Dateiname: "BouncyCastle.Crypto, Version=1.8.1.0, Culture=neutral, PublicKeyToken=0e99375e54769942"

                   WRN: Protokollierung der Assemblybindung ist AUS.
                   Sie können die Protokollierung der Assemblybindungsfehler aktivieren, indem Sie den
                   Registrierungswert [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) auf 1 festlegen.
                   Hinweis: Die Protokollierung der Assemblybindungsfehler führt zu einer gewissen Leistungseinbuße.
                   Sie können dieses Feature deaktivieren, indem Sie den Registrierungswert
                   [HKLM\Software\Microsoft\Fusion!EnableLog] entfernen.
                   , System.IO.FileNotFoundException: Die Datei oder Assembly "BouncyCastle.Crypto, Version=1.8.1.0,
                   Culture=neutral, PublicKeyToken=0e99375e54769942" oder eine Abhängigkeit davon wurde nicht
                   gefunden. Das System kann die angegebene Datei nicht finden.
                   Dateiname: "BouncyCastle.Crypto, Version=1.8.1.0, Culture=neutral, PublicKeyToken=0e99375e54769942"

                   WRN: Protokollierung der Assemblybindung ist AUS.
                   Sie können die Protokollierung der Assemblybindungsfehler aktivieren, indem Sie den
                   Registrierungswert [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) auf 1 festlegen.
                   Hinweis: Die Protokollierung der Assemblybindungsfehler führt zu einer gewissen Leistungseinbuße.
                   Sie können dieses Feature deaktivieren, indem Sie den Registrierungswert
                   [HKLM\Software\Microsoft\Fusion!EnableLog] entfernen.
                   , System.IO.FileNotFoundException: Die Datei oder Assembly "BouncyCastle.Crypto, Version=1.8.1.0,
                   Culture=neutral, PublicKeyToken=0e99375e54769942" oder eine Abhängigkeit davon wurde nicht
                   gefunden. Das System kann die angegebene Datei nicht finden.
                   Dateiname: "BouncyCastle.Crypto, Version=1.8.1.0, Culture=neutral, PublicKeyToken=0e99375e54769942"

                   WRN: Protokollierung der Assemblybindung ist AUS.
                   Sie können die Protokollierung der Assemblybindungsfehler aktivieren, indem Sie den
                   Registrierungswert [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) auf 1 festlegen.
                   Hinweis: Die Protokollierung der Assemblybindungsfehler führt zu einer gewissen Leistungseinbuße.
                   Sie können dieses Feature deaktivieren, indem Sie den Registrierungswert
                   [HKLM\Software\Microsoft\Fusion!EnableLog] entfernen.
                   , System.IO.FileNotFoundException: Die Datei oder Assembly "BouncyCastle.Crypto, Version=1.8.1.0,
                   Culture=neutral, PublicKeyToken=0e99375e54769942" oder eine Abhängigkeit davon wurde nicht
                   gefunden. Das System kann die angegebene Datei nicht finden.
                   Dateiname: "BouncyCastle.Crypto, Version=1.8.1.0, Culture=neutral, PublicKeyToken=0e99375e54769942"

                   WRN: Protokollierung der Assemblybindung ist AUS.
                   Sie können die Protokollierung der Assemblybindungsfehler aktivieren, indem Sie den
                   Registrierungswert [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) auf 1 festlegen.
                   Hinweis: Die Protokollierung der Assemblybindungsfehler führt zu einer gewissen Leistungseinbuße.
                   Sie können dieses Feature deaktivieren, indem Sie den Registrierungswert
                   [HKLM\Software\Microsoft\Fusion!EnableLog] entfernen.
                   …}
Message          : Mindestens ein Typ in der Assembly kann nicht geladen werden. Rufen Sie die
                   LoaderExceptions-Eigenschaft ab, wenn Sie weitere Informationen benötigen.
Data             : {}
InnerException   :
TargetSite       : System.RuntimeType[] GetTypes(System.Reflection.RuntimeModule)
StackTrace       :    bei System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)
                      bei System.Reflection.Assembly.GetTypes()
                      bei Microsoft.PowerShell.Commands.AddTypeCommand.LoadAssemblyFromPathOrName(List`1
                   generatedTypes)
                      bei Microsoft.PowerShell.Commands.AddTypeCommand.EndProcessing()
                      bei System.Management.Automation.CommandProcessorBase.Complete()
HelpLink         :
Source           : mscorlib
HResult          : –2146232830

Da der ganze kram Drumherum in diesem Fall nicht interessiert, ist diese Zeile zielführend:

PS> $error[0].exception.loaderexceptions[0]
Die Datei oder Assembly "BouncyCastle.Crypto, Version=1.8.1.0, Culture=neutral, PublicKeyToken=0e99375e54769942" oder
eine Abhängigkeit davon wurde nicht gefunden. Das System kann die angegebene Datei nicht finden.

Also kurz gesagt, es fehlt eine andere Assembly bzw. DLL-Datei, in diesem Fall BouncyCastle. Wenn also bei einer LoaderException bei Add-Type ein Problem auftaucht hilft diese Zeile:

$error[0].exception.loaderexceptions[0]

Bildschirminhalt einer virtuellen Maschine im Hyper-V Server abrufen

6 Dezember 2017

Wenn man auf einem Microsoft Hyper-V Server (https://docs.microsoft.com/en-us/windows-server/virtualization/hyper-v/hyper-v-server-2016) unterwegs ist, also der frei verfügbaren Version, dann hat man manchmal das Problem, dass man wissen möchte, warum eine virtuelle Maschine noch nicht ansprechbar ist. Meist hat es mit einem Hänger oder mit blöden Updates zu tun. Falls man nun aber direkt auf der Konsole des Servers ist, dann gibt es keine direkten grafischen Mittel die passenden Infos abzurufen.

Aus diesem Grund hab ich ein Script gebastelt, damit man in Zukunft die passenden Funktionen direkt verfügbar hat. Hier zunächst die Ausgangslage, wie man das Bild einer virtuellen Maschine speichert (https://blogs.msdn.microsoft.com/virtual_pc_guy/2016/05/27/capturing-a-hyper-v-vm-screen-to-a-file/) daraus resultierte dieses abgewandelte Skript:

Daraus ergeben sich die Funktionen:

# speichern in eine Datei
Save-VMScreen –VMName SampleVMName –Filename ".\test.bmp"

# direkte Ausgabe am Bildschirm
Show-VMScreen  –VMName SampleVMName

Man kann noch einen Parameter –Index angeben, dieser ist als Vorgabe 0 und spricht einen spezifischen Videohead an. Wie dieser genau definiert ist, ließ sich leider nicht herausfinden. Aber kurz gesagt, kann man damit höher aufgelöste Bilder erhalten.

Man kann sogar das aktuelle Bild in die Windows-Zwischenablage legen, damit man es für Dokuzwecke woanders wieder einfügen kann:

[System.Windows.Forms.Clipboard]::SetImage((Get-VMScreenBMP "SampleVMName"))

Letzte abgestürzte Chrome Sessions Tabs wiederherstellen

5 Dezember 2017

Wenn man, wie ich, Chrome Tabs sammelt um ja nichts zu vergessen, dann kann es in einer ungünstigen Situation passieren, dass man diese z. B. durch einen Neustart des Rechners verliert, oder wenn jemand anderes einfach herumklickt und Chrome öffnet ohne die alten Sessions wiederherzustellen. Zum Glück gibt es aber eine recht einfache Methode die alten Sitzungen wiederherzustellen.

Hier wird der grundsätzliche Weg beschrieben: https://superuser.com/questions/342437/how-to-restore-chrome-without-restore-button-and-the-files-last-session-and/639956#639956.

Hier nochmal der Weg in meinen Worten:

Bestehende Chrome Instanz im Taskmanager beenden

Stop-Process Chrome* –Force

und ins Verzeichnis

CD "$Env:LocalAppData\Google\Chrome\User Data\Default"

wechseln, dann

ren "Current Session" "Current Session.old"
ren "Current Tabs" "Current Tabs.old"
ren "Last Session" "Current Session"
ren "Last Tabs" "Current Tabs"

Nun kann man Chrome wieder starten.