Tag-Archiv für » powershell «

19 | 09 | 2018

PSConf.EU 2019 – Call for Papers ist offen!

Geschrieben von um 9:46 Uhr

Ab heute (und bis zum 09.12.2018) können Vorträge für die PowerShell Konferenz 2019 in Hannover eingereicht werden: HIER.

Also los, Community – reicht eure Themen ein, Hannover wartet!

Tags » , , , , «

+

10 | 09 | 2018

PowerShell Quirks: Values aus der Pipeline, Object Edition

Geschrieben von um 17:51 Uhr

Heute gab es mal wieder eine interessante Frage im TechNet-Forum. Das wollte ich mal näher untersuchen. Es ging darum, dass beim Pipen eines Objektes nach New-Item der Inhalt des erstellten Items das gesamte Objekt, aufgedröselt als Hashtable, enthielt. Im Thread war es eine Datei, aber mit dem Registry-Provider funktionierte es genau so. Das interessante am Value-Parameter ist, dass er Argumente nicht nur nach Namen, sondern auch nach dem Wert bindet:

Doch ist es nur eine Eigenart von New-Item oder ist das Verhalten generell so? Schreiben wir mal eine kleine Funktion, die ihre Argumente, falls sie aus der Pipeline angeflogen kommen, ganz regulär nach Namen bindet:

function Get-MyArgs {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName=$true)][string]$MyInput,
        [Parameter(ValueFromPipelineByPropertyName=$true)][string]$MyOtherParm
    )
    Write-Host "Value of MyOtherParm:"
    $MyOtherParm
    Write-Host "Value of MyInput:"
    $MyInput
}

Wenn wir der Funktion jetzt ein Objekt verfüttern, das die gewünschten Properties enthält, werden sie auch ordnungsgemäß gebunden:

$x = New-Object PSCustomObject -Property @{
    MyInput="MyInputValue";
    MyOtherParm="MyOtherParmValue";
    ForeignParm="ShouldNotSeeMe"
}
$x | Get-MyArgs

liefert

Value of MyOtherParm:
MyOtherParmValue
Value of MyInput:
MyInputValue

Akzeptieren wir nur die gleichen Parameter, aber nach dem Wert, wartet eine kleine Überraschung auf uns:

function Get-MyArgs {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline=$true)][string]$MyInput,
        [Parameter(ValueFromPipeline=$true)][string]$MyOtherParm
    )
    Write-Host "Value of MyOtherParm:"
    $MyOtherParm
    Write-Host "Value of MyInput:"
    $MyInput
}
$x = New-Object PSCustomObject -Property @{
    MyInput="MyInputValue";
    MyOtherParm="MyOtherParmValue";
    ForeignParm="ShouldNotSeeMe"
}
$x | Get-MyArgs

liefert uns

Value of MyOtherParm:
@{MyInput=MyInputValue; MyOtherParm=MyOtherParmValue; ForeignParm=ShouldNotSeeMe}
Value of MyInput:
@{MyInput=MyInputValue; MyOtherParm=MyOtherParmValue; ForeignParm=ShouldNotSeeMe}

Und jetzt kommt der Quirk:

Was passiert aber, wenn wir, wie bei Value in New-Item, beide Bindungen zulassen? Also

function Get-MyArgs {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)][string]$MyInput,
        [Parameter(ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true)][string]$MyOtherParm
    )
    Write-Host "Value of MyOtherParm:"
    $MyOtherParm
    Write-Host "Value of MyInput:"
    $MyInput
}
$x = New-Object PSCustomObject -Property @{
    MyInput="MyInputValue";
    MyOtherParm="MyOtherParmValue";
    ForeignParm="ShouldNotSeeMe"
}
$x | Get-MyArgs

Anders als bei New-Item, erhalten wir nur die String-Werte der beiden benannten Parameter!
Eine Untersuchung mit

Trace-Command -Name ParameterBinding -Expression {$x | Get-MyArgs} -PSHost

fördert die folgenden Schritte zu Tage:

BIND PIPELINE object to parameters: [Get-MyArgs]
    PIPELINE object TYPE = [System.Management.Automation.PSCustomObject]
    RESTORING pipeline parameter's original values
    Parameter [MyOtherParm] PIPELINE INPUT ValueFromPipeline NO COERCION
    BIND arg [@{MyInput=MyInputValue; MyOtherParm=MyOtherParmValue; ForeignParm=ShouldNotSeeMe}] to parameter [MyOtherParm]
        Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
            result returned from DATA GENERATION: @{MyInput=MyInputValue; MyOtherParm=MyOtherParmValue; ForeignParm=ShouldNotSeeMe}
        BIND arg [@{MyInput=MyInputValue; MyOtherParm=MyOtherParmValue; ForeignParm=ShouldNotSeeMe}] to param [MyOtherParm] SKIPPED
    Parameter [MyInput] PIPELINE INPUT ValueFromPipeline NO COERCION
    BIND arg [@{MyInput=MyInputValue; MyOtherParm=MyOtherParmValue; ForeignParm=ShouldNotSeeMe}] to parameter [MyInput]
        Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
            result returned from DATA GENERATION: @{MyInput=MyInputValue; MyOtherParm=MyOtherParmValue; ForeignParm=ShouldNotSeeMe}
        BIND arg [@{MyInput=MyInputValue; MyOtherParm=MyOtherParmValue; ForeignParm=ShouldNotSeeMe}] to param [MyInput] SKIPPED
    Parameter [MyOtherParm] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
    BIND arg [MyOtherParmValue] to parameter [MyOtherParm]
        Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
            result returned from DATA GENERATION: MyOtherParmValue
        BIND arg [MyOtherParmValue] to param [MyOtherParm] SUCCESSFUL
    Parameter [MyInput] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
    BIND arg [MyInputValue] to parameter [MyInput]
        Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
            result returned from DATA GENERATION: MyInputValue
        BIND arg [MyInputValue] to param [MyInput] SUCCESSFUL
MANDATORY PARAMETER CHECK on cmdlet [Get-MyArgs]

Bei New-Item ist das Value-Argument allerdings kein [string], sondern ein [object[]] (danke an Martin Binder für den Hinweis, dass dieser Test noch aussteht!). Ändert man das im obigen Beispiel, dann ist das Verhalten so wie bei New-Item auch, und zwar unabhängig davon, ob ein einzelnes [object] oder ein Array davon erwartet wird – es wird in beiden Fällen das ganze Objekt gebunden, sobald ValueFromPipeline=$true auftaucht.
Happy argument-passing!

Tags » , , , «

+

16 | 07 | 2018

PowerShell Quirks: Beware of collections, die N+1.

Geschrieben von um 23:31 Uhr

Ich habe ja schon des öfteren über Eigenarten von Sammlungen aller Art in PowerShell berichtet. Heute ein Beispiel aus der Active Directory-Administration.

Nehmen wir einfach mal an, wir haben ein mehrwertiges Attribut und wollen zu diesem einen zusätzlichen Wert hinzufügen. Für diese Beispiel nehme ich msExchExtensionCustomAttribute1, da kann man nichts kaputt machen.

Set-ADUser john.doe -Replace @{msExchExtensionCustomAttribute1=@("A","B","C")}
$user = Get-ADUser john.doe -Properties msExchExtensionCustomAttribute1
$a1 = $user.msExchExtensionCustomAttribute1
Write-Host "Unser Attribut msExchExtensionCustomAttribute1:"
$a1.GetType()
$a1

Nun wollen wir zu $a1 ein viertes Element hinzufügen. IntelliSense in ISE bietet folgendes an:

Auch wenn’s bei nativen PowerShell-Arrays nicht funktioniert, ist Add doch vielversprechend. Probieren wir’s aus:

$a1.Add("D")
Write-Host "`r`nNach dem Add:"
$a1.GetType()
$a1

liefert

Sieht doch super aus, richtig? Der Typ ist derselbe, alle vier Elemente sind wie erwartet da. Nun bleibt also die neue Sammlung zurück in das Attribut zu schreiben und das Ergebnis zu überprüfen:

Set-ADUser john.doe -Replace @{msExchExtensionCustomAttribute1=$a1}
Get-ADUser john.doe -Properties msExchExtensionCustomAttribute1

Doch das Ergebnis ist leider nicht ganz das, was wir erwartet haben:

Das soeben beschriebene Attribut ist leer. Hmmm.

Machen wir das gleiche Spiel und nehmen statt der Methode .Add das PowerShell-eigene Anfügen:

$a1 += "D"

so ist das Ergebnis wie erwartet:

Der alles entscheidende Unterschied ist anscheinend, dass die Operation += die ADPropertyValueCollection in ein System.Array konvertiert. Und dieses wird offenbar von Set-ADUser korrekt interpretiert, sein nativer Datentyp hingegen nicht!

Happy updating!

Tags » , , , , , «

+

07 | 10 | 2017

Great news: VSCode beherrscht nun den #region Tag!

Geschrieben von um 16:55 Uhr

Ein Grund weniger, VSCode nicht zu nutzen: Mit der aktuellen Version 1.17 beherrscht #VSCode nun auch den #region / #endregion Markup!

Details hier: http://tommymaynard.com/visual-studio-code-regions-2017/

Happy scripting!

Tags » , , «

+

23 | 08 | 2017

PowerShell: Wie schnell sind Arrays?

Geschrieben von um 11:13 Uhr

Die Herausforderung

Neulich im TechNet Forum: Jemand hat eine Liste, die sehr viele Einträge enthält, die sich oft wiederholen. In diesem Fall ging es um IP-Adressen. Wenn er versucht, die eindeutigen Werte mit

$list | select -Unique

zu generieren, dauert das sehr lange. Da er das normale PowerShell-Array verwendet hat, war es wenig überraschend. Im Forum-Thread kam natürlich sofort der (berechtigte) Hinweis auf den ArrayList-Typ, aber auch ein paar ausgefallenere Sachen. Da wollte ich natürlich genau wissen, wie sich das tatsächlich auswirkt.

Die Testmethodik

Da ich auf die Schnelle keine Quelle mit vielen IP-Adressen zur Hand hatte, habe ich beschlossen, einfach mit Strings zu arbeiten. Um die ursprüngliche Herausforderung zu simulieren, generieren wir einige (viele) Male eine Zufallszahl. Wenn der Bereich der Zufallszahlen deutlich kürzer ist als die Anzahl der Versuche, werden garantiert viele Dubletten auftreten.

Für die Generierung der Listen verwenden wir die folgende Logik:

$passes = 100000 # Anzahl der Durchläufe
$maxval = 10000 # Länge des Zufallszahlen-Bereiches
$a = <Initialisierung des jeweiligen Listentyps>
$timer = [System.Diagnostics.Stopwatch]::StartNew()
(1..$passes) | foreach {
    $r = (Get-Random -Minimum 1 -Maximum $maxval).ToString().PadLeft(15,'0')
    <$r wird an $a angefügt, evtl. mit Prüfung>
}
$s1 = $timer.Elapsed.TotalMilliseconds # das ist die Zeit der Listen-Erstellung
$a = $a | select -Unique
$s2 = $timer.Elapsed.TotalMilliseconds # das ist die Gesamtzeit

Bei den Methoden, wo von vornherein nur eindeutige Elemente auf die Liste kommen, entfällt natürlich der Teil mit Select und die „Zwischenzeit“. Diese Methodik führt zu den folgenden Skripten:

Methode 0: PowerShell-Array mit Select

Das ist die primitivste Methode. Das komplette Skript dieht wie folgt aus:

$passes = 100000
$maxval = 10000
$a = @()
$timer = [System.Diagnostics.Stopwatch]::StartNew()
(1..$passes) | foreach {
    $r = (Get-Random -Minimum 1 -Maximum $maxval).ToString().PadLeft(15,'0')
    $a += $r
}
$s1 = $timer.Elapsed.TotalMilliseconds
$a = $a | select -Unique
$s2 = $timer.Elapsed.TotalMilliseconds
"Zeit zum erstellen: $s1, Gesamtzeit: $s2"

Methode 1: ArrayList mit Select

Wie wir alle wissen, kann man die Bestückung von Arrays deutlich beschleunigen, wenn man statt des PowerShell-Array den .Net-Typ System.Collections.ArrayList verwendet. Nicht nur erweitert sich solch ein Array dynamisch, sondern man kann Elemente auch nachträglich löschen. Und schneller ist es obendrein. Das folgende Skript wird uns zeigen, wie sehr:

$passes = 100000
$maxval = 10000
$a = New-Object System.Collections.ArrayList
$timer = [System.Diagnostics.Stopwatch]::StartNew()
(1..$passes) | foreach {
    $r = (Get-Random -Minimum 1 -Maximum $maxval).ToString().PadLeft(15,'0')
    $a.Add($r) > $null
}
$s1 = $timer.Elapsed.TotalMilliseconds 
$a = $a | select -Unique 
$s2 = $timer.Elapsed.TotalMilliseconds 
"Zeit zum erstellen: $s1, Gesamtzeit: $s2"

Methode 2: ArrayList mit -notcontains

Ich nehme ein Ergebnis schon mal vorweg: der Teil mit Select dauert eine Weile. Das war auch das, was den ursprünglichen Thread ausgelöst hat. Es liegt daher natürlich nahe, vor dem Hinzufügen von Elementen zu checken, ob sie nicht schon drin sind, statt hinterher zu filtern. PowerShell hatt dafür den Operator -notcontains im Angebot:

$passes = 100000
$maxval = 10000
$a = New-Object System.Collections.ArrayList
$timer = [System.Diagnostics.Stopwatch]::StartNew()
(1..$passes) | foreach {
    $r = (Get-Random -Minimum 1 -Maximum $maxval).ToString().PadLeft(15,'0')
    if ($a -notcontains $r) { $a.Add($r) > $null }
}
$s2 = $timer.Elapsed.TotalMilliseconds 
"Gesamtzeit: $s2"

Methode 3: ArrayList mit .Contains()

Wir haben schon oft gehört, dass .Net-Methoden von Klassen schneller sein können als PowerShell-Äquivalente. Und da wir mit ArrayList arbeiten, können wir zur Prüfung auch auf die .Contains()-Methode zurückgreifen:

$passes = 100000
$maxval = 10000
$a = New-Object System.Collections.ArrayList
$timer = [System.Diagnostics.Stopwatch]::StartNew()
(1..$passes) | foreach {
    $r = (Get-Random -Minimum 1 -Maximum $maxval).ToString().PadLeft(15,'0')
    if (!($a.Contains($r))) { $a.Add($r) > $null }
}
$s2 = $timer.Elapsed.TotalMilliseconds 
"Gesamtzeit: $s2"

Methode 4: Hashtable

Schon bald im Thread kam der Einwurf, man könnte doch eine Hashtable verwenden, wenn die Werte eindeutig sein sollen. Das wäre ein Stück weit „mißbräuchliche Nutzung“, da wir nur den Key verwenden würden, aber nicht den Value. Aber sei’s drum, wenn es ans Ziel führt, ist jedes Mittel recht. Beim Versuch, einen bereits vorhandenen Key-Wert einzufügen, generiert die Hashtable eine Ausnahme. Diese fangen wir ab und… machen nichts weiter:

$passes = 100000
$maxval = 10000
$a = @{}
$timer = [System.Diagnostics.Stopwatch]::StartNew()
(1..$passes) | foreach {
    $r = (Get-Random -Minimum 1 -Maximum $maxval).ToString().PadLeft(15,'0')
    try {$a.Add($r,"") > $null} catch {}
}
$s2 = $timer.Elapsed.TotalMilliseconds 
"Gesamtzeit: $s2"

Methode 5: Hashtable mit Prüfung

Ausnahmen sind natürlich häßlich und in ordentlicher Programmierung zu vermeiden. Zum Glück kann man in einer Hashtable bestimmen, ob ein Key bereits vorhanden ist, nämlich mit der Methode .ContainsKey(). Ob es schneller ist als Ausnahme, wird der Test zeigen:

$passes = 100000
$maxval = 10000
$a = @{}
$timer = [System.Diagnostics.Stopwatch]::StartNew()
(1..$passes) | foreach {
    $r = (Get-Random -Minimum 1 -Maximum $maxval).ToString().PadLeft(15,'0')
    if (!($a.ContainsKey($r))) { $a.Add($r,"") > $null }
}
$s2 = $timer.Elapsed.TotalMilliseconds 
"Gesamtzeit: $s2"

Methode 6: Hashset

Zum Schluss hatte Denniver Reining noch das Hashset ins Gespräch gebracht. Das ist eine eindimensionale Liste, deren Mitglieder stets eindeutig sind, ohne das Ausnahmen generiert werden – vorhandene Werte werden einfach nicht hinzugefügt:

$passes = 100000
$maxval = 10000
$a = New-Object System.Collections.Generic.HashSet[object]
$timer = [System.Diagnostics.Stopwatch]::StartNew()
(1..$passes) | foreach {
    $r = (Get-Random -Minimum 1 -Maximum $maxval).ToString().PadLeft(15,'0')
    $a.Add($r) > $null
}
$s2 = $timer.Elapsed.TotalMilliseconds 
"Gesamtzeit: $s2"

Die Ergebnisse

Die obigen Skripte könnt ihr euch hier herunterladen und selbst Messungen vornehmen. Bei $passes = 100000 und $maxval = 10000 (also im Schnitt 10 Dubletten pro Eintrag) erhalten wir folgende Ergebnisse:

MethodeZeit gesamt (sek.)davon sortieren (sek.)
Baseline (Schleife und Generierung der Zufallszahlen)6,5-
PowerShell Array und select -unique51590
ArrayList und select -unique9590
ArrayList mit -notcontains58-
ArrayList mit .Contains()11-
Hashtable mit Exception21-
Hashtable mit .ContainsKey()7,5-
Hashset6,8-

Erhöhen wir die Werte auf $passes = 400000 und $maxval = 20000 (damit verdoppelt sich sowohl die Länge der eindeutigen Liste als auch die durchschnittliche Anzahl der Dubletten), ändern sich die Ergebnisse wie folgt:

MethodeZeit gesamt (sek.)davon sortieren (sek.)
Baseline (Schleife und Generierung der Zufallszahlen)27,8-
PowerShell Array und select -unique6280768
ArrayList und select -unique762734
ArrayList mit -notcontains520-
ArrayList mit .Contains()57-
Hashtable mit Exception86-
Hashtable mit .ContainsKey()32-
Hashset28,6-

Man sieht also sehr deutlich, dass die Bestückung eines Hashset auch bei einer größeren Menge an Ergebnissen kaum Zeit in Anspruch nimmt!

Das Fazit

Die „Ausbeute“ aus diesen Zahlen ist wie folgt:

  1. Das Erweitern eines PowerShell-Array mit $array += $item ist extrem langsam
  2. Die .Contains()-Methode ist viel schneller als der Operator -contains
  3. Beim Hinzufügen zu einer Hashtable ist es deutlisch schneller, vorher die Existenz des Key abzuprüfen als eine Ausnahme zu generieren.
  4. Will man von vorhnherein eindeutige Werte auf der Liste haben, ist ein Hashset optimal. Der Geschwindigkeitsgewinn gegenüber Hashtable mit Prüfung ist zwar nicht mehr weltbewegend, aber er ist da, man benutzt die Typen richtig und mißbraucht den Key nicht als Datenspeicher…

Also: Wenn lange Listen generiert und verarbeitet werden müssen, lohnt es sich auf jeden Fall, das bequeme Konstrukt des PowerShell-Arrays zu verlassen und sich fortgeschritteneren Typen von Sammlungen zuzuwenden.

Happy listing!

Tags » , , , , , , «

+

12 | 08 | 2017

PowerShell Quirks: New-PSDrive und der Speicherplatz

Geschrieben von um 11:26 Uhr

Wenn man in einer PowerShell-Sitzung mit

New-PSDrive -Name Z -Root "\\SERVER\SHARE" -PSProvider FileSystem

ein Netzlaufwerk mappt, stellt man fest, dass die Spalten „Used Space“ und „Free Space“ leer sind:

Möchte man die Werte haben, funktioniert es mit dem Argument -Persist:

Ich hätte sogar eine theoretische Erklärung dafür, aber Gernot Meyer hat im TechNet-Forum eine Beobachtung zitiert, die diese Erklärung widerlegt, daher halte ich mich mal zurück und recherchiere weiter.

Happy drive mapping!

Tags » , , , , «

+

19 | 07 | 2017

PowerShell Quirks: Move-ADDirectoryServerOperationMasterRole und der Schema Master

Geschrieben von um 20:54 Uhr

Heute mal ein Quickie, nur als Merker für den Hinterkopf:

Auch wenn es im Internet von Anleitungen nur so wimmelt, die den Befehl

Move-ADDirectoryServerOperationMasterRole <DomainController> -OperationMasterRole SchemaMaster

zeigen, gibt es dazu ein Wort zu sagen: Das funktioniert nur, wenn man

  • entweder den Befehl auf dem DC aufruft, zu dem die Rolle transferiert werden soll
  • oder den Parameter -Force mit angibt.

Alle anderen Rollen lassen sich auch ohne -Force beliebig hin und her transferieren.

Happy FSMOing!

Tags » , , «

+

29 | 06 | 2017

CipherSuites in Windows Server vorgeben und überwachen – Teil 1

Geschrieben von um 20:13 Uhr

Wir haben die Aufgabe bekommen, eine ganz bestimmte Auswahl an Cipher Suites für einige Applikationsserver zuzulassen. Gesagt, getan. Nach Prüfung der Kompatibilität zu anderen Applikationen und einem Test in der Testumgebung haben wir das Gewünschte umgesetzt…

…nur um festzustellen, dass anscheinend bei jedem Patchday irgendetwas dabei ist, das die Liste wieder auf den vorgegebenen Zustand zurück stellt. Eine Automatisierungs- und nach Möglichkeit Überwachungslösung musste also her.

Die benötigte Einstellung kann man ganz einfach mit einem PowerShell-Einzeiler

Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Cryptography\Configuration\SSL\00010002" -Name "Functions" -Value "TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_3DES_EDE_CBC_SHA,TLS_RSA_WITH_RC4_128_SHA"

oder visuell durch IISCrypto erledigen. Beide Verfahren tun das gleiche: Den String für den Wert „Functions“ als kommagetrennte Liste der Cipher Suites zusammenbasteln und in die Registry schreiben. Der Haken an der Sache: damit das Ganze auch in Kraft tritt, wird ein Reboot der Maschine benötigt.

Die Erzwingung der korrekten Werte in der Registry erreicht man einfach per Group Policy Preference:

Somit ist erst einmal der Automatisierungsteil erledigt. Nun könnte aber jemand Böses (OK, Böses mit Adminrechten) die Cipher Suites verstellen, die Kiste rebooten, und die veränderte Einstellung würde bis zum nächsten Reboot in Kraft bleiben. Zur Überwachung ist es also nicht ausreichend, den Wert in der Registry abzufragen, man muss schauen, was wirklich aktiv ist.

Der erste Wurf war die Nutzung der Windows-API. Nach einigem Googlen fand ich zwar immer noch kein PowerShell-Modul dafür, aber immerhin den folgenden vielversprechenden Thread auf StackOverflow: https://stackoverflow.com/questions/19695623/how-to-call-schannel-functions-from-net-c

Leicht modifiziert, wird daraus ein binäres Modul:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Management.Automation;

namespace CipherSuites
{
    [Cmdlet(VerbsCommon.Get, "CipherSuites")]
    public class GetCipherSuitesCmdlet: Cmdlet
    {
        [DllImport("Bcrypt.dll", CharSet = CharSet.Unicode)]
        static extern uint BCryptEnumContextFunctions(uint dwTable, string pszContext, uint dwInterface, ref uint pcbBuffer, ref IntPtr ppBuffer);
        [DllImport("Bcrypt.dll")]
        static extern void BCryptFreeBuffer(IntPtr pvBuffer);
        [StructLayout(LayoutKind.Sequential)]
        public struct CRYPT_CONTEXT_FUNCTIONS
        {
            public uint cFunctions;
            public IntPtr rgpszFunctions;
        }
        public const uint CRYPT_LOCAL = 0x00000001;
        public const uint NCRYPT_SCHANNEL_INTERFACE = 0x00010002;
        public const uint CRYPT_PRIORITY_TOP = 0x00000000;
        public const uint CRYPT_PRIORITY_BOTTOM = 0xFFFFFFFF;
        static uint cbBuffer = 0;
        static IntPtr ppBuffer = IntPtr.Zero;
        string so;
        uint Status;

        protected override void BeginProcessing()
        {
             cbBuffer = 0;
             ppBuffer = IntPtr.Zero;
        }
        protected override void ProcessRecord()
        {
        }
        protected override void EndProcessing()
        {
            Status = BCryptEnumContextFunctions(
                         CRYPT_LOCAL,
                         "SSL",
                         NCRYPT_SCHANNEL_INTERFACE,
                         ref cbBuffer,
                         ref ppBuffer);
            if (Status == 0)
            {
                CRYPT_CONTEXT_FUNCTIONS functions = (CRYPT_CONTEXT_FUNCTIONS)Marshal.PtrToStructure(ppBuffer, typeof(CRYPT_CONTEXT_FUNCTIONS));
                IntPtr pStr = functions.rgpszFunctions;
                for (int i = 0; i < functions.cFunctions; i++)
                {
                    so = Marshal.PtrToStringUni(Marshal.ReadIntPtr(pStr));
                    WriteObject(so);
                    pStr = new System.IntPtr((pStr.ToInt64() + (IntPtr.Size)));
                }
                BCryptFreeBuffer(ppBuffer);
            }
            GC.Collect();
            GC.WaitForFullGCComplete();
        }
    }
}

Das Ergebnis könnt ihr ohne jede Gewähr hier herunterladen: CipherSuites.dll. Ich werde das nicht offiziell auf Gallery veröffentlichen – warum, erkläre ich unten. Für das Monitoring wird das noch NAGIOS-konform in PowerShell verpackt:

$root = (get-item $PSScriptRoot).parent.FullName.ToString()
if (!(Get-Command "Get-CipherSuites" -EA SilentlyContinue)) { Import-Module ($root + "\modules\CipherSuites.dll") }
$additional_cs_is_critical = $true
$missing_cs_is_critical = $false
$cs_list = "TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_3DES_EDE_CBC_SHA,TLS_RSA_WITH_RC4_128_SHA"
$exitCodes = @{
"UNKNOWN" = 3;
"CRITICAL" = 2;
"WARNING" = 1;
"OK" = 0
}
if (Get-Command "Get-CipherSuites" -EA SilentlyContinue) { $cs = Get-CipherSuites } else { $cs = @() }
$cs_string = $cs -join ","
if ($cs_list-like $cs_string) {
    $state = "OK"
    $status_msg = "CipherSuites are in order"
    $perfdata_msg = "ciphersuites=$($cs.count);0;0"
} elseif (!($cs)) {
    $state = "UNKNOWN"
    $status_msg = "Could not retrieve CipherSuites"
    $perfdata_msg = "ciphersuites=-1;-1;-1"
} else {
    $nadd = 0
    $nmiss = 0
    $csl = $cs_list -split ","
    foreach ($cx in $cs) {
        if ($csl -notcontains $cx) {
            $nadd++
        }
    }
    foreach ($cx in $csl) {
        if ($cs -notcontains $cx) {
            $nmiss++
        }
    }
    if (($nadd -eq 0) -and ($nmiss -eq 0)) {
        $status_msg = "CipherSuites reordered!"
        $perfdata_msg = "ciphersuites=$($cs.count);0;0"
        if ($reordered_cs_is_critical) {
            $state = "CRITICAL"
        } else {
            $state = "WARNING"
        }
    } else {
        $status_msg = "CipherSuites mismatched: $nmiss missing, $nadd additional"
        $perfdata_msg = "ciphersuites=$($cs.count);$nmiss;$nadd"
        if (($additional_cs_is_critical -and ($nadd -gt 0)) -or ($missing_cs_is_critical -and ($nmiss -gt 0))) {
            $state = "CRITICAL"
        } else {
            $state = "WARNING"
        }
    }
}
Write-Host $state": $status_msg|$perfdata_msg"
exit $exitCodes[$state]

Funktioniert soweit, bereits die ersten Tests ließen aber den dringenden Verdacht aufkommen, dass die Windows-API lediglich die konfigurierten, nicht jedoch die ausgeführten Cipher Suites zurückgeben. Ich wollte es genau wissen und habe schnell den Process Monitor angeworfen. Und siehe da…
Hat sich ja wirklich gelohnt, dafür noch eine API zu schreiben. Dann müssen wir also für die Überwachung etwas bemühen, das agiert wie https://testssl.sh. Wie das ausgeht, berichte ich im nächsten Teil.

Happy decrypting!

Tags » , , , , , , «

+

21 | 06 | 2017

PowerShell: Ein schneller Versuch, ADMX zu parsen

Geschrieben von um 19:45 Uhr

Hier ein bißchen Code, um alle Einstellungen aus ADMXen inklusive Registry-Pfad und -Typ zu exportieren. Ein paar Typen könnten noch fehlen …

Achtung! In Zeilen 10 und 11 frißt der Syntax Highlighter den Typ (quadratische Klammer auf)xml(quadratische Klammer zu) unmittelbar vor der runden Klammer. Noch weiß ich nicht, wie ich ihn überredet bekomme, das anzuzeigen. Bis dahin hier der Quelltext zum Download: admx_parser

$language = "de-DE"
$root = "C:\Windows\PolicyDefinitions"
$output = @()

$admx_files = Get-ChildItem $root -Filter "*.admx"
foreach ($admx in $admx_files) {
    $admx_file = Split-Path $admx -Leaf
    $adml = "C:\Windows\PolicyDefinitions\$($language)\$($admx_file.TrimEnd('x'))l"
    if (Test-Path $adml) {
        $admx_data = (Get-Content $admx.FullName)
        $adml_data = (Get-Content $adml -Encoding UTF8)
        $adml_strings = $adml_data.policyDefinitionResources.resources.stringTable.GetEnumerator()
        $policies = $admx_data.policyDefinitions.policies
        foreach ($pol in $policies.policy) {
            $dn_string = $pol.displayName.Substring(9, ($pol.displayName.Length - 10))
            $et_string = $pol.explainText.Substring(9, ($pol.explainText.Length - 10))
            $policy_name = ($adml_strings.Where({$_.id -eq $dn_string})).'#text'
            $adml_strings.Reset()
            $policy_desc = ($adml_strings.Where({$_.id -eq $et_string})).'#text'
            $adml_strings.Reset()
            if ($pol.elements.HasChildNodes) {
                $els = $pol.elements.GetEnumerator()
                foreach ($el in $els) {
                    $reg_value = "$($pol.Key)\$($el.valueName)"
                    switch ($el.Name) {
                        'boolean' { $reg_type = 'REG_DWORD (1)' }
                        'decimal' { $reg_type = 'REG_DWORD' }
                        'text' {
                            if ($el.expandable) {
                                $reg_type = 'REG_EXPAND_SZ'
                            } else {
                                $reg_type = 'REG_SZ'
                            }
                        }
                        'enum' {
                            $ex = $el.FirstChild.FirstChild.FirstChild.Name
                            switch ($ex) {
                                'decimal' { $reg_type = 'REG_DWORD' }
                                'text' {
                                    if ($el.expandable) {
                                        $reg_type = 'REG_EXPAND_SZ'
                                    } else {
                                        $reg_type = 'REG_SZ'
                                    }
                                }
                            }
                        }
                        'list' {
                            $reg_type = 'REG_SZ (list)'
                            $reg_value = $el.Key
                        }
                        default { 
                            Write-Host $el.Name -ForegroundColor Cyan
                            $reg_type = $el.Name
                            $reg_value = $el.Key
                        }
                    }
                    $out_item = New-Object PSObject -Property @{'RegPath' = $reg_value; 'RegType' = $reg_type; 'PolicyTitle' = $policy_name; 'PolicyDescription' = $policy_desc; 'ADMXFile' = $admx_file}
                    $output += $out_item
                }
            } else {
                $reg_value = "$($pol.Key)\$($pol.valueName)"
                $reg_type = 'REG_DWORD (1)'
                $out_item = New-Object PSObject -Property @{'RegPath' = $reg_value; 'RegType' = $reg_type; 'PolicyTitle' = $policy_name; 'PolicyDescription' = $policy_desc; 'ADMXFile' = $admx_file }
                $output += $out_item
            }
        }
    } else {
        Write-Host "ADML in $language not found: $adml" -ForegroundColor Yellow
    }
}
$output | Export-CSV c:\temp\admx.csv -Encoding UTF8

Enjoy!

Tags » , , «

2

07 | 05 | 2017

PSConfEU 2017: Das war schön

Geschrieben von um 23:08 Uhr

Drei unvergessliche Tage in Hannover, Inspiration für das ganze Jahr, tolle Gespräche, neue und alte Freunde:

Danke an @TobiasPSP, das Orga-Team, die Speaker!

Tags » , , , «

+