Trusted Platform Module (TPM) unter Windows und SpecVersion ermitteln

13 Februar 2018

Um Passwörter und Geheimnisse abzusichern werden immer mehr Hardwaremodule eingesetzt. Eines dieser Module ist z. B. TPM. Die Aufgabe eines Trusted Platform Module ist es bestimmte Schlüssel und Geheimnisse abzuspeichern. Es ist zwar meist ein Hardwaremodul aber es kann im Innern doch auch wieder per Software updatebar sein. Ich hatte bereits vor Jahren versucht TPM unter Hyper-V mit ans laufen zu bekommen https://newyear2006.wordpress.com/2014/05/15/hyper-v-vms-trusted-platform-module-und-das-problem-mit-den-virtual-smart-cards/.  Damals gab es aber noch keine Unterstützung im Hyper-V. Dies sieht heute anders aus. Dadurch kann man sich nun mit TPM beschäftigen, ohne sich viel Gedanken machen zu müssen. Wo TPM bei Windows 10 zum Einsatz kommt, kann man hier nachlesen: https://docs.microsoft.com/en-us/windows/security/hardware-protection/tpm/how-windows-uses-the-tpm.

Eine Frage die sich schnell ergibt, wenn man sich damit beschäftigt, ist, welche Version hat man eigentlich? Gängig sind Version 1.2 oder 2.0. Ruft man mittels TPM.MSC also die “Trusted Platform Module”-Management (TPM)-Konsole auf, dann kann man dort unter TPM-Herstellerinformationen die Spezifikationsversion einsehen. Schön, aber wie geht das auch per Commandline? Da gibt es eigentlich das Cmdlet Get-Tpm:

PS > Get-Tpm

TpmPresent                : True
TpmReady                  : True
ManufacturerId            : 1297303124
ManufacturerIdTxt         : MSFT
ManufacturerVersion       : 8213.275
ManufacturerVersionFull20 : 8213.275.21.18466

ManagedAuthLevel          : Full
OwnerAuth                 : R8yRy4e6DfK2hDS/EbQxdhA=
OwnerClearDisabled        : False
AutoProvisioning          : Enabled
LockedOut                 : True
LockoutHealTime           : 10 minutes
LockoutCount              : 0
LockoutMax                : 31
SelfTest                  : {}

Hier hätte ich erwartet, dass SpecVersion auftaucht, doch leider tut sie es nicht. Ok wir haben ja Powershell, vielleicht versteckt sich die Info woanders:

Get-Command -Module TrustedPlatformModule -ParameterName *spec*

Aber da kommt leider auch nix raus. Zumindest in der Version 2.0 der Cmdlets aus dem TrustedPlatformModule-Modul.

Also was tun? Wie immer liegt die Lösung im WMI, denn dort kann man die Klasse Win32_Tpm finden, zunächst brauchen wir aber eine einfache Möglichkeit um alle Namespaces zu durchlaufen:

Function Get-NamespaceRecursive ($namespace) {
$nss=Get-CimInstance -Namespace $namespace -ClassName __NAMESPACE
$nss | % {"$namespace/$($_.Name)"; Get-NamespaceRecursive "$namespace/$($_.Name)" }
}

Mit dieser können wir mittels diesem Aufruf alle verfügbaren Namespaces ermitteln und dort nach TPM Ausschau halten:

$ns=Get-NamespaceRecursive "root"
$tpm=$ns| % {Get-CimClass -Namespace $_ -ClassName *} | where CimClassName -match tpm

   NameSpace: ROOT/CIMV2/Security/MicrosoftTpm

CimClassName                        CimClassMethods      CimClassProperties
————                        —————      ——————
Win32_Tpm                           {IsEnabled, IsOwn… {IsActivated_Initial…

Damit erhalten wir zwei Treffer, einer davon Win32_TPM. Diesen schauen wir uns genauer an:

(Get-CimClass -Namespace ROOT/CIMV2/Security/MicrosoftTpm -ClassName Win32_Tpm).CimClassProperties | where name -match specversion

Name               : SpecVersion
Value              :
CimType            : String
Flags              : Property, NullValue
Qualifiers         : {Description, Implemented}
ReferenceClassName :

Also Volltreffer. Es gibt sie also, die gesuchte Information.

Um in Zukunft die SpecVersion des TPM zu ermitteln verwendet man diesen einfachen Aufruf:

wmic /namespace:\\root\CIMV2\Security\MicrosoftTpm path Win32_Tpm get /value

IsActivated_InitialValue=TRUE
IsEnabled_InitialValue=TRUE
IsOwned_InitialValue=TRUE
ManufacturerId=1297303124
ManufacturerIdTxt=MSFT
ManufacturerVersion=8213.275
ManufacturerVersionFull20=8213.275.21.18466
ManufacturerVersionInfo=496f5420536f6674776172652054504d00
PhysicalPresenceVersionInfo=1.3
SpecVersion=2.0, 0, 1.15

Ups, das war der falsche Befehl 🙂 Bin da etwas in alte Zeiten ohne Powershell abgerutscht… Trotz allem könnte obiger Befehl in Situationen wichtig sein, wenn man mittels WinPE oder WinRE unterwegs ist und kein Powershell an Bord hat. In diesem Fall sollte man aber noch drvload x:\windows\inf\tpm.inf im Hinterkopf behalten, denn ohne Treiber geht evtl. nix. Zurück aber zum Thema.

Gemeint war dieser:

(Get-CimInstance -Namespace ROOT/CIMV2/Security/MicrosoftTpm -ClassName Win32_Tpm).SpecVersion
2.0, 0, 1.15

Schön nun haben wir Version 2.0, 0, 1.15 und das bedeutet nun? Ist es 2.0 oder eine verkappte 1.2 Version? Tja, Aufklärung gibt es auf der Microsoft Seite zur Win32_Tpm-Klasse https://msdn.microsoft.com/en-us/library/windows/desktop/aa376484, dort steht:

The version of the Trusted Computing Group (TCG) specification that the TPM supports. This value includes the major and minor TCG specification version, the specification revision level, and the errata revision level. All values are in hexadecimal. For example, a version information of "1.2, 2, 0" indicates that the device was implemented to TCG specification version 1.2, revision level 2, and with no errata.

When the data is unavailable, "Not Supported" is returned.

Aha, wir bekommen also ziemlich viel geboten. Nur stellt sich die Frage, was für Erratas und Revisionen gibt es? Nun, wenn man beim Informationsimperium anfragt, dann bekommt man diese Seite geliefert: https://trustedcomputinggroup.org/tpm-library-specification/. Übrigens hatte ich die Trusted Computing Group schon mal mit SED im Programm: https://newyear2006.wordpress.com/2016/10/01/self-encrypting-drives-sed-und-probleme-mit-gelschten-bitlocker-partitionen/.

Obige SpecVersion mit 2.0, 0, 1.15 sagt also aus, dass wir es mit der 2.0er-Familie, dem Level 0 und der Revision 01.16 vom Oktober 2014 zu tun haben. Wobei die Angaben sich mit der Beschreibung von Microsoft oben nicht decken.

Hier die aktuell bekannten Versionen:

Familie/Version Level Revision vom
2.0 0 01.38 September 2016
2.0 0 01.16 Oktober 2014
2.0 0 00.99 Oktober 2013
2.0 0 00.96 März 2013
1.2 2 116  
1.2 2 103  
1.2   94  
1.2   85  
1.2   62  
1.1b      

Die Werte für 1.2 sind von hier: https://trustedcomputinggroup.org/tpm-main-specification/.

Wer sich tiefergehend mit TPM 2.0 beschäftigen möchte, der sollte sich unbedingt die Architekturbeschreibung und die Einrichtungsrichtlinien anschauen, https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-1-Architecture-01.38.pdf und https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf.

TPM 2.0 und Linux sind dann nochmal ein Thema für sich: https://blog.hansenpartnership.com/tpm2-and-linux/.

Nicht zu vergessen sind auch die CVEs mit TPMs, welche es zu beachten gibt: https://www.google.de/search?q=cve+tpm.

Advertisements

Apple iPhone taucht nicht im Windows Explorer auf, kein Zugriff auf Bilder möglich weil MTP fehlt

9 Februar 2018

Auf einem Rechner mit Windows 10 v1709 (64-Bit) schloss man per USB ein iPhone 7 an, es tauchte aber kein Eintrag im Windows Explorer auf. iTunes mit der aktuellen Version war auch installiert, also sollten alle Treiber aktuell verfügbar sein. Das manuelle aktualisieren der Treiber im Gerätemanager wurde auch immer nur mit der Meldung bestätigt, dass man bereits den aktuellen Treiber hat. Was tun?

Die Lösung ist relativ simpel. Man muss nur im Gerätemanager bei den USB-Controllern auf den Eintrag “Apple Mobile Device USB Driver” gehen und die Eigenschaften öffnen. Nun geht man auf das Register Treiber und klickt auf “Treiber aktualisieren”. Im nächsten Dialog klickt man “Auf dem Computer nach Treibern suchen” und dann auf “Aus einer Liste der verfügbaren Treiber auf meinem Computer auswählen”. Es stehen nun zwei Treiber zur Auswahl, der bereits bekannte “Apple Mobile Device USB Driver” und der “MTP-USB-Gerät”-Treiber. Wir wählen den MTP-Treiber. Wird der Treiber installiert, taucht automatisch im Gerätemanager der Eintrag “Tragbare Geräte” auf unter dem dann “Apple iPhone” zu sehen ist.

Nun ist auch das iPhone im Windows Explorer zu finden und nach Freigabe vom iPhone kann man nun problemlos auf den DCIM-Ordner des iPhone zugreifen.

Wichtige Unterschiede zwischen Powershell 5.1 und Powershell Core 6.x bei Invoke-WebRequest und Invoke-RestMethod

3 Februar 2018

Wenn man Invoke-WebRequest oder Invoke-RestMethod unter Powershell Core 6.x benutzt, sollte man im Zweifel nachfolgendes wissen. Die Namen sind die gleichen aber im Hintergrund haben sich einige Dinge geändert.

Ein sehr ausführlicher Artikel beschreibt die Unterschiede der Web-Cmdlets hier: https://get-powershellblog.blogspot.de/2017/11/powershell-core-web-cmdlets-in-depth.html.

So werden z. B. Header Variablen in Powershell Core strenger überprüft, siehe https://get-powershellblog.blogspot.com/2017/11/powershell-core-web-cmdlets-in-depth.html#L08. Um bestehende Scripte möglichst einfach weiterverwenden zu können, könnte man diese Umgebungsvariable setzen:

$PSDefaultParameterValues[‚Invoke-RestMethod:SkipHeaderValidation‘] = $true
$PSDefaultParameterValues[‚Invoke-WebRequest:SkipHeaderValidation‘] = $true

Damit kann man die strikte Header Prüfung abschalten.

Man muss sich auch bewusst sein, dass nun die Rückgabetypen andere sind als bisher. Man sollte also obigen Artikel unter https://get-powershellblog.blogspot.de/2017/11/powershell-core-web-cmdlets-in-depth.html#L09 genauer studieren. Vor allem kann dadurch teilweise die Logik eines Scripts einen ganz anderen Pfad bekommen!!

Nicht weniger wichtig ist das geänderte Verhalten bei Fehlern und Statuscodes: https://get-powershellblog.blogspot.com/2017/11/powershell-core-web-cmdlets-in-depth.html#L13.

Der zweite Artikel in der Blogserie beschreibt welche Features in Powershell Core 6.x im Vergleich zu Windows Powershell 5.1 nicht mehr vorhanden sind. https://get-powershellblog.blogspot.de/2017/12/powershell-core-web-cmdlets-in-depth.html.

Der wichtigste Punkt dabei ist, dass Invoke-WebRequest und Invoke-RestMethod nun keine file:// bzw. ftp://-Protokollle mehr unterstützen!! Nur noch http:// bzw. https:// werden unterstützt. Der zweite wichtige Punkt ist, dass ParsedHTML nicht mehr unterstützt wird, dieser Punkt ist logisch, weil auf den anderen Platformen kein Internet Explorer mit seinem DOM zur Verfügung steht. Dritter Punkt ist die komplette Abwesenheit von New-WebServiceProxy zur Abfrage von SOAP-Endpunkten.

Ein größeres Thema könnten Zertifikate werden, denn aufgrund der Crossplattform-Unterstützung kommen unterschiedliche Bibliotheken zum Einsatz, wo nicht alle Informationen in .Net Core durchgereicht werden und somit Powershell Core nicht zur Verfügung stehen!

OK den Wegfall von SSL 3.0 sollte zu verschmerzen sein, was allerdings blöd ist, ist der Wegfall der Unterstützung von ServicePointManager https://get-powershellblog.blogspot.com/2017/12/powershell-core-web-cmdlets-in-depth.html#L12. Diese Methode hatte früher hier z. B. Anwendung gefunden: https://newyear2006.wordpress.com/2014/07/26/bei-powershell-ssltls-zertifikate-prfung-einfach-ignorieren/. Eine mögliche Lösung ist allerdings Artikel 3 der Serie bei Parameter –SkipCertificateCheck beschrieben.

Der dritte Teil der Blogserie beschreibt Erweiterungen bzw. neue Features die bei Powershell Core in Invoke-WebRequest und Invoke-RestMethod Einzug gehalten haben https://get-powershellblog.blogspot.de/2017/12/powershell-core-web-cmdlets-in-depth_24.html.

Insgesamt haben die beiden Cmdlets 12 neue Parameter erhalten. Die wahrscheinlich wichtigsten sind die Unterstützung für OAuth Authentifizierung und Link-Parameter um REST-Aufrufen über mehrere Ergebnisseiten folgen zu können.

Darüberhinaus wird es mit Powershell Core 6.1.x noch weitere tolle neue Features geben, vor allem bezogen auf Formulare, siehe hier: https://get-powershellblog.blogspot.de/2018/01/powershell-core-61-web-cmdlets-roadmap.html.

Hyper-V Ereignisanzeige-Einträge erklärt

24 Januar 2018

Klappt bei einem Hyper-V etwas nicht, dann helfen auch mal die Ereigniseintragungen von Windows zur Problemlösung. Allerdings gibt es nicht einen, sondern je nach Problem verschiedene Eventlogs. Hier eine aktuelle Übersicht mit kurzen Beschreibungen für 2016: https://blogs.technet.microsoft.com/virtualization/2018/01/23/looking-at-the-hyper-v-event-log-january-2018-edition/

Hier noch die Verwendung in Powershell:

Get-WinEvent -ProviderName Microsoft-Windows-Hyper-V-VMMS
Get-WinEvent -ProviderName Microsoft-Windows-VHDMP

Ausgabe alle verfügbaren Hyper-V LogProvider (ok, VHDMP fehlt):

Get-WinEvent -ListProvider *hyper-v*

Noch kurz zur Verwendung der Cmdlets: Wenn man sich vertippt, erhält man diese Meldung:

PS> Get-WinEvent -ProviderName Microsoft-Windows-Hyper-V-GibtesNicht
Get-WinEvent : Auf dem Computer "localhost" wurde kein Ereignisanbieter gefunden, der "Microsoft-Windows-Hyper-V-GibtesNicht" entspricht.
In Zeile:1 Zeichen:1
+ Get-WinEvent -ProviderName Microsoft-Windows-Hyper-V-GibtesNicht
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (Microsoft-Windows-Hyper-V-GibtesNicht:String) [Get-WinEvent], Exception
    + FullyQualifiedErrorId : NoMatchingProvidersFound,Microsoft.PowerShell.Commands.GetWin
EventCommand

Wohingegen, wenn man einen gültigen Ereignisanbieter verwendet, aber keine Ereigniseinträge vorhanden sind, diese Meldung erscheint:

PS > Get-WinEvent -ProviderName Microsoft-Windows-Hyper-V-Config
Get-WinEvent : Es wurden keine Ereignisse gefunden, die den angegebenen Auswahlkriterien entsprechen.
In Zeile:1 Zeichen:1
+ Get-WinEvent -ProviderName Microsoft-Windows-Hyper-V-Config
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (:) [Get-WinEvent], Exception
    + FullyQualifiedErrorId : NoMatchingEventsFound,Microsoft.PowerShell.Commands.GetWinEvent
Command

Poorman’s Addblocker – oder wenn einen der laute Lüfter am Rechner stört

11 Januar 2018

Wenn man mehrere Browserinstanzen geöffnet hat und beim Surfen viele verschiedene Tabs geöffnet hat, dann steigert sich unweigerlich die Prozessorlast. Im Normalfall nicht von den eigentlichen Seiten, sondern von den Millionen Werbeeinblendungen. Aus diesem Grund setze ich von Zeit zu Zeit häufig auftauchende Adware-Adressen auf 127.0.0.1 und lasse sie quasi ins Leere laufen.

Die Vorgehensweise benötigt Adminrechte. Um die aktuellen DNS-Anfragen einsehen zu können, hilft einem mal wieder Powershell. Seit Windows 8 gibt es das Cmdlet Get-DnsClientCache. Allerdings liefert Get-DnsClientCache im Zweifel zu viele Daten zurück. Deshalb muss die Abrage etwas modifiziert werden:

Get-DnsClientCache|where type -ne 12|where Data -ne "127.0.0.1"|where Data -ne $null

Bei “type –ne 12” werden die PTR-Records aus dem Ergebnis gestrichen, weil diese keinen Sinn machen. Danach werden noch alle leeren Einträge gefiltert. Diese werden mit der Zeit immer mehr, je mehr man Adressen gegen 127.0.0.1 laufen lässt.

Man geht also in seinen Browser, ruft eine Seite auf, wo man analysieren möchte und führt dann obige Zeile aus.

Ein Ergebnis stellt sich dann so dar:

Entry                     RecordName                Record Status    Section TimeTo Data   Data
                                                    Type                     Live   Length
—–                     ———-                —— ——    ——- —— —— —-
safebrowsing.googleapi… safebrowsing.googleapi… A      Success   Answer     166      4 216.58.207.74
www.gstatic.com           www.gstatic.com           A      Success   Answer      80      4 216.58.214.99

Sieht scheiße aus aber entscheidend ist die Spalte Entry, dort stehen die DNS-Namen welche auf die bei Data angegebene IP-Adresse aufgelöst werden. Setzt man also diese DNS-Namen auf 127.0.0.1 findet keine Kommunikation nach extern mehr statt.

Um die Auswahl etwas komfortabler zu gestalten, benutzen wir Out-GridView:

Get-DnsClientCache|where type -ne 12|where Data -ne "127.0.0.1"|where Data -ne $null |Out-GridView -PassThru -OutVariable Selected | select -Property @{N="HOSTSExclusion";E={"127.0.0.1`t$($_.Entry)"}} | Select -ExpandProperty HostsExclusion| Set-Clipboard

Man wählt einfach die gewünschten Einträge aus und erhält diese in $Selected zur Weiterverarbeitung zurück. Noch besser, es wird gleich ein passender Eintrag in die Windowszwischenablage mit den ausgewählten Daten gelegt. Nun braucht man nur noch

notepad $env:windir\system32\drivers\etc\hosts

aufrufen und kann dort die Daten von der Zwischenablage am Ende einfügen und speichern. Man könnte die Daten auch gleich direkt in die HOSTS-Datei schreiben aber manche Virenscanner reagieren darauf allergisch, dann also doch besser die Zwischenablage benutzen.

Ein Problem stellt sich aber noch. Wie kann man feststellen, dass eine DNS-Adresse tatsächlich eine Werbeadresse ist? Tja, da ist leider Handarbeit angesagt. In der Regel erhält man beim Aufruf so einer Adresse im Browser nur eine leere Seite oder die Homepage einer Werbe- oder Addagentur. Dieser Aufruf hilft nach der Auswahl eine Entscheidung zu treffen:

$selected| select -ExpandProperty Entry| foreach {start microsoft-edge:http://$_}

Dabei werden alle bei Out-GridView ausgewählten Seiten automatisch im Edge geöffnet, damit man kurz den Inhalt gegenprüfen kann. Hier das gleiche für Chrome:

$selected| select -ExpandProperty Entry| foreach {start chrome $_}

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…

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 ‚;‘