Quantcast
Channel: Neuste Artikel
Viewing all 119 articles
Browse latest View live

Ein niedrig priveligiertes Konto zum Entsperren von Domänenbenutzern anlegen und damit Benutzer-Sperren retten

$
0
0

Das Sperren von Benutzerkonten ist ein bewährtes Mittel, um Brute-Force-Attacken gegen Kennwörter im Unternehmen zu verhindern. Allerdings empfehlen sowohl Microsoft als auch das BSI inzwischen, Kontosperrungen nicht zu verwenden, da eine Malware oder ein Hacker durch das Sperren sämtlicher Benutzerkonten ein Netzwerk komplett lahm lagen kann - man spricht auch von einem denial of Service.

Die alternative Empfehlung lautet, lange Kennwörter in Form von Passphrases und am Besten Multifaktor-Authentifizierung einzusetzen. Grundsätzlich ist das ein vernünftiger Ansatz, aber leider scheitert er in vielen Netzwerken immer noch an der Praxis. Zum einen muß man den Benutzern erklären, dass Sie jetzt lange komplexe Kennwort-Sätze verwenden sollen, zum Anderen muß man mit dieser Variante auch für eine ordentliche Anmeldungs-Überwachung sorgen, da ein großangelegter Angriff auf die Benutzerkennwörter sonst gar nicht auffällt. Mit aktivierter Kennwortsperrung merkt man normalerweise sehr schnell, dass etwas nicht stimmt.

Mein pragmatischer alternativer Ansatz, um die Kontosperrungen weiterhin nutzen zu können, arbeitet mit automatisierten Prozessen und Powershell. Denn mit Powershell ist es sehr schnell und einfach möglich, Benutzerkonten zu entsperren. Verwenden Sie dafür die Cmdlets Search-ADAccount und Unlock-ADAccount. Search-ADAccount kann mit dem Parameter -Lockedout alle gesperrten Benutzerkonten in einem Rutsch auflisten, Unlock-ADAccount kann sie  direkt entsperren. Mit Hilfe des Cmdlets Out-Gridview können Sie sich auch gleich noch eine grafische Benutzeroberfläche einrichten, um nur ausgewählte Benutzer zu entsperren.

Search-ADAccount -Lockedout | Out-Gridview -Passthru | Unlock-ADAccount

1   locked User

Damit wird das Entsperren nach einem Denial of Service zu einem Kinderspiel. Allerdings muß noch ein zweites Problem gelöst werden. Wenn Sie nämlich das Standard-Administratorkonto auf der Domäne deaktivieren, haben Sie eventuell gar keine Möglichkeit mehr, die Domäne zu entsperren, weil Ihr Administrator auch gesperrt ist. Deswegen legen Sie sich nun ein niedrig privilegiertes Konto an, dass neben dem Entsperren von Benutzern in der Domäne nichts kann, aber nicht gesperrt wird.

Im ersten Schritte benötigen Sie eine granulierte Kennwortrichtlinie und eine Gruppe, der Sie die Richtlinie zuordnen. Diese können Sie entweder mit dem Active Directory administrative Center anlegen, oder mit Powershell. Hier der Weg mit Powershell:

New-ADGroup -Name UnlockAccount -GroupCategory Security -GroupScope Global
New-ADFineGrainedPasswordPolicy -Name NeverLock -ComplexityEnabled $true -Precedence 1 -LockoutDuration -1 -PasswordHistoryCount 24 -MinPasswordAge 0:00:00:00 -MaxPasswordAge 365:00:00:00 -MinPasswordLength 20
Add-ADFineGrainedPasswordPolicySubject -Identity Neverlock -Subjects UnlockAccount

New-ADGroup legt eine neue Gruppe an, New-ADFineGrainedPasswordPolicy die Kennwortrichtlinie im AD im Container CN=Password Settings Container,CN=System,DC=Netz-weise,DC=de. Das Cmdlet Add-ADFineGrainedPasswordPolicySubject weist die Richlninie der neu erstellen Gruppe zu. Damit wird die Kennwortrichtlinie für alle Mitglieder der Gruppe aktiv. Alternativ können Sie auch einen einzelnen Benutzer anlegen und diesen direkt hinzufügen.

Mit Get-ADFineGrainedPasswordPolicy können Sie überprüfen, ob die Kennwortrichtlinie angelegt wurde.

PS > Get-ADFineGrainedPasswordPolicy -Identity Neverlock

AppliesTo                   : {CN=UnlockAccount,CN=Users,DC=netz-weise,DC=de}
ComplexityEnabled           : True
DistinguishedName           : CN=NeverLock,CN=Password Settings Container,CN=System,DC=netz-weise,DC=de
LockoutDuration             : 1.00:00:00
LockoutObservationWindow    : 00:30:00
LockoutThreshold            : 0
MaxPasswordAge              : 365.00:00:00
MinPasswordAge              : 00:00:00
MinPasswordLength           : 20
Name                        : NeverLock
ObjectClass                 : msDS-PasswordSettings
ObjectGUID                  : 15c25024-850e-4bd6-b674-4207684f9bbc
PasswordHistoryCount        : 24
Precedence                  : 1
ReversibleEncryptionEnabled : True

Im nächsten Schritt müssen Sie die Berechtigungen zum Entsperren von Benutzerkonten über die Benutzerattribute "Read Lockouttime" und "Write Lockouttime" delegieren. Dazu starten Sie Active Directory Benutzer und Computer und öffnen das Kontextmenü der Domäne. Wählen Sie Objektverwaltung zuweisen.

2   Objektverwaltung zuweisen

Klicken Sie im nächsten Fenster weiter und wählen Sie den Benutzer oder die Gruppe aus, die die Berechtigungen erhalten soll 

3   Gruppe waehlen

Sie müssen "Benutzerdefinierte Aufgaben zum Zuweisen erstellen" starten.

4   Benutzerdefinierte Aufgaben

Da Sie das Entsperren von Benutzerkonten zulassen wollen, wählen Sie unter "Active Directory Objekttyp" Benutzer.

5   Benutzer Objekte

Suchen Sie aus der Berechtigungsliste "Eigenschaftspezifisch" und dann die Berechtigungen "lockout Time lesen" und "lockout time schreiben" aus. Auf einem englischen System heißen die Eigenschaften ead lockoutTime and Write lockoutTime.

6   Lockout Properties

Bestägigen Sie nun die Berechtigungsdelegierung. Abschliessend können Sie der Gruppe UnlockAccount Benutzer hinzufügen, die zum Entsperren verwendet werden können. Wenn Sie es besonders sicher haben wollen, können Sie jetzt z.B. mit Authentifizierungssilos die Anmeldung der Entsperr-Benutzer noch auf einen spezifischen PC binden, so dass ein Hacker, der Kennwörter entsperren wollte, sich auch noch Zugriff auf diesen Rechner verschaffen müßte,


Eine Zahlenreihe in zwei alternierende (wechselnde) Gruppen aufteilen mit Powershell

$
0
0

Stellen Sie sich vor, Sie haben eine aufsteigende Reihe natürlicher (ganzer) Zahlen. Sie wollen die Zahlenreihe in 2 Blöcke aufteilen, die nach einer bestimmten Menge von Zahlen jeweils wechselt. Dazu ein Beispiel:

Gruppe 1 Gruppe 2
1-500 501-1000
1001-1500  1501-2000
2001-2500 2501-3000

 

 

 

 

Das kann z.B. interessant sein, wenn Sie eine If-Abfrage definieren möchten, die nur zwei Ausgänge hat, die aber für aufeinanderfolgende Zahlenblöcke abwechselnd auftreten.

Um diese Ergebnis zu erreichen, schauen Sie sich zuerst die Abstände zwischen den Gruppen an. Jeder Gruppe besteht aus jeweils 500 natürlichen Zahlen. Daher teilen Sie eine gegebene Zahl zuerst durch 500. Möchten Sie z.B. die Zahl 1789 in eine Gruppe einsortieren, rechnen Sie 1798/500. Das Ergebnis ist 3,578. Ignorieren Sie den Nachkommateil, denn der interessiert uns nicht. Durch das Teilen mit 500 haben immer 500 aufeinanderfolgende Zahlen die gleiche Vorkommastelle. Jetzt haben wir aber immer noch eine unendliche große Gruppe von Zahlen von jeweils 500. Um die Gruppen in lediglich zwei Gruppen aufzuteilen, teilen wir das Endergebnis durch 2 und schauen und jetzt nur den Rest an, der bei der Division übrig bleibt.

3 / 2 = 1,5 > Rest 5.

Wenn wir durch 2 teilen, kann es tatsächlich immer nur zum Rest 5 oder zu gar keinem Rest kommen, je nachdem, ob die Stelle vor dem Komma gerade oder ungerade ist. Damit haben wir genau zwei Gruppen geschaffen, nämlich die Zahlen, bei denen beim Teilen der ganzzahligen Anteils nach der Divison durch 500 ein gerades Ergebnis herauskommt, und die Zahlen, bei denen ein ungerades Ergebnis herauskommt.

Um den ganzzahlen Anteil einer Zahl programmatisch zu berechnen, stellt und das .Net-Framework in der Klasse [math] die Methode Floor() zur Verfügung. Den Rest einer Division wird auch als Modulo oder Mod bezeichnet. Der mathematische Operator in Powershell ist das %-Zeichen. Hier die beiden Berechnungen:

$Ganzzahl: [math]::floor(1798) # Gibt den ganzzahlen Anteil der Zahl zurück, ohne zu runden
$Ganzzahl % 2 # Gibt den  Rest einer Division zurück, bei geraden Zahlen also Null, bei ungeraden Zahlen 5

Wenn man diese Berechnungen für eine große Zahl von Zahlen durchführen muß, baut man sich am Besten eine Funktion. Wenn Sie nicht bei 0 anfangen wollen zu zählen, sondern bei 1, subtrahiern Sie eins von allen zu testenden Zahlen:

function test-number
{
param
(
  [int]$number
)
  ([math]::floor((($number-1) / 500))) % 2
}

Die Funktion gibt für Zahlen im unteren 500er-Block 0 zurück, für Zahlen im oberen 500er-Block 1.

Um die nächstgrößere Ganzzahl auszugeben, verwenden Sie übrigens den Funktion [math]::Ceiling(<Double>).

 

Für Powershell Cmdlet-Parameter Standardwerte definieren

$
0
0

Das Feature ist nicht neu, aber trotzdem noch mal eine Erinnerung wert. Wenn Sie in einem Skript für einen Parameter immer wieder das gleiche Argument verwenden, können Sie es auch als Standard-Wert hinterlegen. Konkret könnte das z.B. der Domänencontroller sein, der die Einzelbetriebsmasterrolle PDC-Emulator inne hat. Powershell stellt Ihnen dafür die Systemvariable $PSDefaultParameterValues zur Verfügung. Sie wird mit einem Hash-Array befüllt, das für jedes Cmdlet als Schlüsselwert den Cmdlet-Namen und, mit einem ":" getrennt, den Parameternamen enthält, sowie als Wert das Standard-Argument.

$Pdc = Get-ADDomainController -Service PrimaryDC -Discover
$ServerDefaults = @{ "Get-ADUser:Server"=$pdc.HostName
   "Get-ADComputer:Server"=$Pdc.HostName
   "Get-ADObject:Server"=$Pdc.Hostname
 }
$PSDefaultParameterValues = $ServerDefaults
$PSDefaultParameterValues
Get-ADUser -Filter *

Kalenderdaten aus Outlook in Excel übertragen und Tage zählen

$
0
0

Letztes Wochenende wollte ich wissen, wie viele Tage ich im letzten Jahr geschult habe. Da alle Schulungstermine in einem Outlook-Kalender gepflegt sind, liegt es Nahe, die Termine von Outlook nach Excel zu übertragen und dann per Excel auszuwerten. Das geht tatsächlich, ist aber nicht unbedingt intuitiv.

Zuerst müssen Sie Ihren Kalender öffnen und Outlook im Menü Ansicht über den Eintrag "Ansicht ändern" in die Listenansicht umstellen. Anschließend können Sie die Ansicht über das Suchfenster filtern und die Termine, die Sie in Excel übertragen wollen, markieren und kopieren. Im Suchfenster können Sie mehrere mit Leerzeichen getrennte Wörter als einen zusammengehörigen Suchbegriff angeben, indem Sie ihn in Anführungszeichen setzen. Soll Outlook also nicht alle Termine mit Trainer oder alle Termine mit Holger im Titel finden, sondern alle des Trainers Holger, geben Sie an: "Trainer: Holger". Der Text muss dann genau so in der Terminbeschreibung eingegeben sein.

Outlook Kalender   1

In Excel sollten sie Daten korrekt als Tabelle eingefügt werden. Sie verfügen jetzt mehrere Spalten wie Betreff, Ort, Beginn, Ende usw.

Excel   2

Erstellen Sie jetzt in einer neuen Spalte eine Funktion, die das Start- und Enddatum, die als Text übertragen wurden, in einen Datumswert umwndelt. Dazu benötigen Sie die Funktion Datum(), die Text in ein Datum umwandlet. Datum() hätte das Datum aber gerne im Format (Jahre;Monat;Tag), während Beginn und Ende in der Tabelle im Format "Mo 04.09.2017 09:00" vorliegen. Wir müssen aus dem Text also den Tag, den Monat und das Jahr extrahieren. Das geht mit der Funktion Teil(), die 3 Übergabewerte benötigt - Den Text oder die Zelle, die bearbeitet werden soll, das Startzeichen und die Menge der Zeichen, die augeschnitten werden sollen.Um das Jahr aus dem angegebenen Datumsformat auszuschneiden, brauchen wir die Spalte C, als Startwert 10 und 4 Zeichen:

Teil(C2,10,4)

Das machen wir für Jahr, Monat und Tag und fügen es in die Datum-Funktion ein:

=DATUM(TEIL(C2;10;4);TEIL(C2;7;2);TEIL(C2;4;2))

Die Formel können Sie jetzt über alle Felder der Spalte automatisch anpassen, indem Sie mit der Maus auf die untere rechte Ecke des Feldes C2 fahren, und dann die Formel über alle Zeilen herunterziehen, für die ein Startwert definiert ist. Danach wiederholen Sie das ganze in einer neuen Spalte für die Ende-Spalte (D) mit der Formel

=DATUM(TEIL(D2;10;4);TEIL(D2;7;2);TEIL(D2;4;2))

Die Differenz berechnen Sie in einer weiteren Spalten mit der Funktion DateDif(), die zwei Datumswerte nimmt und die Differenz berechnet. Neben den beiden Datumswerten muß DateDif() auch noch übergeben werden, in welcher Einheit man die Differenz berechnet haben möchte. Für Tag wählt man "d" und addiert 1, da nicht die Differenz berechnet werden soll, sondern die Anzahl der Tage, und die ist um eins größer als der Abstand der Datumswerte.

=DATEDIF(G2;H2;"d")+1

Um das Ergebnis korrekt darzustellen, müssen Sie die Felder, in der die Tagesdifferenzen eingetragen sind, noch als Zahl formatieren. Danach können Sie alle Tage markieren und sich die Summe bilden lassen. Sie können auch die Beispieldatei herunterladen.

Den Windows Lizenzkey aus der Firmware aka Bios auslesen und aktivieren pwer Powershell

$
0
0

Letzte Woche habe ich durch Zufall festgestellt, dass der Rechner einer Kollegin, der schon vor Jahren per automatischem Update auf Windows 10 aktualisiert worden ist, immer noch nicht aktiviert war, obwohl das vorher installierte Windows 8.1 es war und Windows 10 sich außerdem den Key automatisch aus der Firmware ziehen soll, wenn er da hinterlegt ist. Glücklicherweise ist die Aktivierung von Windows 10 immer noch mit den Keys voriger Versionen möglich. Der Key selbst ist im BIOS nicht einsehbar, läßt sich aber über WMI einfach auslesen. Er ist in der Klasse SoftwareLicensingService in der Eigenschaft OA3xOriginalProductKey hinterlegt. Mit Powershell nutzen Sie für das Auslesen einfach Get-WMIObject oder Get-CIMClass:

(Get-WmiObject -Class SoftwareLicensingService).OA3xOriginalProductKey

oder ab Powershell 4 auch

(Get-CimInstance -ClassName SoftwareLicensingService ).OA3xOriginalProductKey

Mit dem Tool slmgr.vbs können Sie die LIzenz auch gleich installieren und aktivieren:

$key = (Get-WmiObject -Class SoftwareLicensingService).OA3xOriginalProductKey
cscript.exe slmgr.vbs /ipk $key  # ersetzt den vorhanden Key durch den ausgelesenen
cscript.exe slmgr.vbs /ato       # Aktiviert Windows online

 

USB-Geräte mit Powershell und WMI auslesen

$
0
0

Am USB angeschlossene Geräte kann man per WMI auslesen. WMI stellt dafür die WMI Association Klasse Win32_USBControllerDevice zur Verfügung, die zwei Klassen miteinander verbindet - in diesem Fall die Daten des USB-Controllers und die installierten Treiber. Die verknüpften Treiber kann man aus der Eigenschaft Dependent auslesen.

Get-WmiObject Win32_USBControllerDevice | Foreach-Object { [Wmi]$_.Dependent }

[WMI] Wandelt den String, der in der Eigenschaft $_.Dependent hinerlegt ist, wieder eine WMI-Klasse um. Um einen überschaubaren Überblick über die installierten Geräte zu bekommen, wählt man am Besten erst einmal die Eigenschaften Descritption und DeviceID aus.

Get-WmiObject Win32_USBControllerDevice | ForEach-Object { [wmi]$_.dependent } | select-Object description,deviceid

Das Ergebnis sieht dann ungefähr so aus:

USB Devices

Links

Win32_USBControllerDevice Referenz

 

Zwei XML-Dateien mit Powershell vergleichen und einen HTML-Report erzeugen

$
0
0

Das XML-Format ist allgegenwärtig. Als Windows-Administrator stolpert man regelmäßig über Eventlogs im XML-Format, Anweisungsdateien für die unbeaufsichtigte Installation, Vorlagen für Gruppenrichtlinien usw. Und manchmal wäre es ganz schön, wenn man sich den Unterschied zwischen zwei ähnlichen XML-Dateien einfach anzeigen lassen könnte. Mit Powershell und ein bißchen .net ist das in der Tag auch gar kein Problem, denn Microsoft hat vor fast 15 Jahren eine .Net-Bibliothek zur Verfügung gestellt, die genau das tut - das XML Diff & Patch GUI Tool. Das Tool stellt eine Klasse zur Verfügung, über die es möglich ist, zwei XML-Dateien zu vergleichen und die Unterschiede in der XML DIfference Language (Diffgram) auszugeben. Mit einer weiteren Klasse kann man aus einer Diffgram-Datei und einer der beiden Vergleichsdateien eine HTML-Datei erzeugen, die die Unterschiede grafisch darstellt.

So sieht ein HTML Vergleich aus

Wenn Sie die heruntergeladene Bibliothek entpacken, finden Sie im zwei .dlls, die Sie laden müssen, die XmlDiffPath.dll, die die Compare()-Methode zur Verfügung stellt, und die XmlDiffPath.View.dll, die die Methode GetHtml() bereitstellt. GetHtml erstellt aus einer Diffgram-Datei eine HTML-Datei. Laden Sie die Klassen und erstellen Sie zwei neue Objekte.

Add-Type -Path "xmldiffpatch.dll"
$XmlDiff = New-Object -TypeName Microsoft.XmlDiffPatch.XmlDiff
Add-Type -Path "XmlDiffPatch.View.dll"
$XmlDiffView = New-Object -TypeName Microsoft.XmlDiffPatch.XmlDiffView

Anschließend können Sie die Methode Compare() aufrufen. Compare hat eine Reihe von Überladungen (verschiedene Parameter-Kombinationen). Zum Erstellen eines Diffgramwriters benötigen Sie die beiden zu vergleichenden XML-Dateien, $false und einen .Net-Streamwriter zum Schreiben der Diffgram-Datei:

$DiffGramWriter = [System.Xml.XmlWriter]::Create( 'C:\temp\Diffgram.xml' )
#call Compare method from Microsoft.XmlDiffPatch.XmlDiff object
$XmlDiff.Compare('C:\temp\File1.xml','C:\Temp\File2.xml',$false,$DiffGramWriter)
$DiffGramWriter.Close()

Anschließend erstellen Sie mit Hilfe der Methode GetHtml() die Ausgabedatei. GetHTML() benötigt als Parameter nur einen Streamwriter für die Ausgabe, allerdings müssen vorher mit Load() eine der beiden Vergleichsdateien und die generierte Diffgram-Datei in die Klasse geladen werden.

# Laden der Dateien mit Hilfe von Streamreadern:
$Orig = [System.Xml.XmlTextReader]::Create('C:\Temp\File1.xml')
$DiffGram = [System.Xml.XmlTextReader]::Create('C:\temp\Diffgram.xml')
$StreamWriter = New-object -TypeName System.IO.StreamWriter -ArgumentList 'C:\Temp\Result.html'
$XmlDiffView.Load($Orig,$DiffGram)

# Schreiben der Differenz-Datei
$XmlDiffView.GetHtml($StreamWriter)
$StreamWriter.Close()
$Orig.Close()
$DiffGram.Close() 

Da das ganze nicht besonders gut formatiert ist, kann man noch einen HTML-Header und Footer einfügen. Der einfachheit halber habe ich gleich eine Funktion aus dem Code gebaut. Sie müssen dann allerdings den Pfad zu den Bibliotheken anpassen. Alternativ laden Sie die Funktion einfach direkt als Modul herunter.

Function Compare-XML
{
  param(
    [String]$XmlFile1,
    [String]$XmlFile2,
    [string]$ResultFile,
    [string]$DiffDataGramPath = ( "$env:TEMP\DataDiff.xml" )
  )
 
  Add-Type -Path "$PSScriptRoot\xmlDiff\xmldiffpatch.dll"
  $XmlDiff = New-Object -TypeName Microsoft.XmlDiffPatch.XmlDiff
  Add-Type -Path "$PsScriptroot\xmlDiff\XmlDiffPatch.View.dll"
  $XmlDiffView = New-Object -TypeName Microsoft.XmlDiffPatch.XmlDiffView
 
  $HtmlHeader = @"
<html><body>
<p><b>Legend:</b>
<font style='background-color: yellow' color='black'> added</font>
&nbsp;&nbsp;<font style='background-color:red' color='black'>removed</font>
&nbsp;&nbsp;<font style='background-color:lightgreen' color='black'>changed</font>&nbsp;&nbsp;
<font style='background-color: red' color='blue'>moved from</font>
&nbsp;&nbsp;<font style='background-color: yellow' color='blue'>moved to</font>&nbsp;&nbsp;
<font style='background-color: white' color='#AAAAAA'>ignored</font>
</p>
<table width='100%'>
<tr><td colspan='2' align='center'>
"@

  $HtmlFooter = @"
</table></body></html>
"@
 
  #create XmlWriter object with path where to create the resulting XML file
  $DiffGramWriter = [System.Xml.XmlWriter]::Create( $DiffDataGramPath )
  #call Compare method from Microsoft.XmlDiffPatch.XmlDiff object
  $XmlDiff.Compare($XmlFile1,$XmlFile2,$false,$DiffGramWriter)
  $DiffGramWriter.Close()
 
  $Orig = [System.Xml.XmlTextReader]::Create($XmlFile1)
  $DiffGram = [System.Xml.XmlTextReader]::Create($DiffDataGramPath)
  $StreamWriter = New-object -TypeName System.IO.StreamWriter -ArgumentList $ResultFile
  $XmlDiffView.Load($Orig,$DiffGram)
 
  $StreamWriter.Write($htmlHeader)
  $XmlDiffView.GetHtml($StreamWriter)
  $StreamWriter.Write($HtmlFooter)
  $StreamWriter.Close()
  $Orig.Close()
  $DiffGram.Close()  
}

Das XML Diff-und Patch GUI Tool kann übrigens noch mehr, wie z.B. XML-Dateien synchronisieren.

Links

Compare and Patch XML-Documents

Mit Powershell ein Kennwort gegen AD prüfen

$
0
0

Wenn Sie mit Powershell überprüfen möchten, ob eine gegebene Kombination aus einem AD-Benutzernamen und Kennwort korrekt ist (z.B. aus einer GUI-Eingabe), hilft Ihnen die .NET-Assembly System.Directore.Accountmanagement weiter (mind. .NET-Framwork 3.5). Die Klasse "PrincipalContext" enthält eine Methode ValidateCredentials, die die Überprüfung übernimmt. Die Funktion check_AdUserPassword liefert True oder False zurück und kann z.B. direkt in einem IF-Statement weiter genutzt werden.

Function Test-AdUserPassword
{

param (
    [string]
    $UserName,
    [string]
    $Password,
    [string]
    $UserDomain)

   # Lädt die Assembly "System.DirectoryServices.AccountManagement" in Powershell
   $Null = Add-Type -AssemblyName System.DirectoryServices.AccountManagement
   $ContextType = [System.DirectoryServices.AccountManagement.ContextType]::Domain
   $PrincipalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext($ContextType, $UserDomain)
   $PrincipalContext.ValidateCredentials($UserName,$Password)

}

 

Kontoinformationen über den angemeldeten Benutzer ausgeben

$
0
0

Sie hätten gerne Informationen über Ihre Benutzerkonto, wie Gruppenmitgliedschaften, SID usw? Wenn Sie Informationen über den gerade angemeldeten Benutzer haben möchten, wie z.B., ob der User als Gastkonto angemeldet ist, den Benutzernamen, die Domäne oder die Gruppenmitgliedschaften, hilft Ihnen die Funktion getcurrent() aus der Klasse [system.security.principal.windowsidentity] weiter.

[system.security.principal.windowsidentity]::getcurrent()

liefert folgende Properties zurück:

Name
----
Actor
AuthenticationType
BootstrapContext
Claims
DeviceClaims
Groups
ImpersonationLevel
IsAnonymous
IsAuthenticated
IsGuest
IsSystem
Label
Name
NameClaimType
Owner
RoleClaimType
Token
User
UserClaims

Eine Variante, die Ihnen entweder den Benutzer oder die Benutzermitgliedschaften zurückliefert, ist das Kommandozeilentool whoami. Mit den Parameter /User erhalten Sie Ihre SID, mit /Groups Ihre Gruppenmitgliedschaften. Alle wichtigen Informationen inklusive der Benutzerrechte spuckt der Parameter /all aus. Mit dem Parameter /Fo können Sie auch die Ausgabe im CSV-Format erzwingen, so dass Sie die Ausgabe mit Powershell direkt in Objekte konvertieren können:

Whoami.exe /groups /fo csv | ConvertFrom-CSV | Out-Gridview

whoami

 

Geschweifte Klammern im Powershell Format-Operator benutzen am Beispiel GUIDs

$
0
0

Powershell hat mit dem Format-Operator -f eine geniale Möglichkeit, Texte zu formatieren. Der Format-Operator erlaubt es, Daten in einen Text zu "kopieren" und Zahlen auch gleich nach Bedarf in ein passendes Format zu bringen. Hier ein kleines Beispiel, um das Ergebnis einer Berechnung zu runden und in einen Text einzufügen:

$FolderSize = Get-Childitem -Path $env:programFiles -file -recurse | Measure-Object -Property Length -Sum
'Der Ordner {0} ist {1:0.000} MB groß' -f $env:programFiles,($Foldersize.Sum/1MB)
Der Ordner C:\Program Files ist 9169,447 MB groß

Die Syntax ist recht simpel - Sie generieren einen String, schreiben aber statt der Variablen, die in den Text eingefügt werden sollen, Platzhalter. Die Platzhalter sind durchnummeriert, beginnen bei 0 und sind in geschweifte Klammern eingefasst. Genau genommen sind die Nummern in geschweiften Klammern der Index eines Arrays, nämlich des Arrays, das Sie hinter dem Format-Operator -f angeben. {0} entspricht also dem ersten Wert hinter -f, {1} dem zweiten usw. Zahlen können Sie formatieren lassen. Geben Sie hierfür hinter dem Platzhalter einen Doppelpunkt an, und dann für jede Pflichtstellt vor dem Komma eine 0, und für jede Pflichtstelle hinter dem Komma ebenfalls eine Null. Komma wird aufgrund der amerikanischen Schreibweise mit . (Punkt) angegeben. Damit eine Zahl immer mit einer Stelle vor und drei Stellen hinter dem Komma angegeben wird, schreiben Sie also: {0:0.000}. Es gibt auch eine ganze Reihe anderer Optionen. So können Sie mit {0:C} z.B. automatisch ein Währungsformatierung (Currency) mit zwei Stellen hinter dem Komme und dem Währungssymbol der einstellten Sprache erzwingen. Eine Auflistung der Operatoren finden Sie unter https://social.technet.microsoft.com/wiki/contents/articles/7855.powershell-using-the-f-format-operator.aspx

Der Format-Operator lässt sich auch prima zum Einfügen von Zeichen verwenden, z.B. um eine Variable in Klammern oder Anführungszeichen einzufassen:

'[{0}]' -f "Ein Text in Klammern"
'"{0}"' -f "Ein Text in Anführungszeichen"

Interessant wird es, wenn man einen Text in geschweifte Klammern einfügen möchte, denn dann sperrt sich der Formatoperator.

'{{0}}' -f (New-guid).guid
{0}

Um einen Wert in geschweifte Klammern einzufügen, müssen Sie doppelte geschweifte Klammern verwenden, insgesamt also drei öffnende und drei schließende Klammern:

'{{{0}}}' -f (New-guid).guid
{ee1b6042-4c1d-4838-a824-1aab1c9ec0b0}

 

Windows Freigaben auf einen neuen Pfad migrieren mit Powershell

$
0
0

Wenn Sie auf einem Dateiserver Ordner umziehen müssen, kann das Anpassen der Freigaben auf untergeordneten Ordnern sehr aufwändig werden, z.B. wenn es sich um eine große Menge von Home-Shares handelt. Einfacher als über die GUI geht es, wenn Sie die Registry-Schlüssel unter HKEY_LocalMachine\SYSTEM\CurrentControlSet\Services\LanmanServer\Shares\ anpassen. Alternativ können einfach die folgende Powershell-Funktion verwenden, die einen gegebenen Pfad in dem Registry-Schlüssel durch einen neuen ersetzt. Da es sich um eine reine Musterersetzung handelt, können Sie beliebige Teilpfade ersetzen lassen.

function Replace-SharePath
{
<#
    .SYNOPSIS
    Replace the Filesystem-Path of a Share

    .Description
    Replace-Sharepath can change the Filesystem-Path of shares. This can be helpful if you need to move
    Files to a new Drive or have to move files to a subfolder and you have multiple Shares to touch. The
    Script will change the given String into the new string, so all shares under the given Path will be
    affected.
             
    .EXAMPLE
    To move Files from C:\Shares to D:\NewSharesFolder:
    Replace-SharePath -Path c:\Shares -NewPath D:\NewSharesFolder
#>
param(
    [ValidateScript({ if ( ! ( Test-Path -path $_ -PathType Container )) {Throw "Bitte den Quellpfad prüfen!"}; $true })]
    [Parameter(mandatory=$true)]
    # The Path to Change.
    $Path,

    [ValidateScript({ if ( ! ( Test-Path -path $_ -PathType Container )) {Throw "Bitte den Quellpfad prüfen!"}; $true })]
    [Parameter(mandatory=$true)]
    # The New Path
    $Newpath
)

    $path = $Path.Replace('\','\\')
    $Newpath = $Newpath.Replace('\','\')
    $ShareList = Get-Item HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Shares\
    ForEach ( $Share in $ShareList.Property )
    {
        Get-ItemProperty -Path $ShareList.pspath -Name $share
        $newValue = ( Get-ItemProperty -Path $ShareList.pspath -Name $share ).$Share -replace "(^Path=)($path)","Path=$newpath"
        Set-ItemProperty -Path $ShareList.pspath -Name $Share -Value $Newvalue -PassThru
    }
}

Führen Sie die Funktion einmal aus und starten Sie sie anschließend über folgende Eingabe:

Replace-Sharepath -Path c:\OldSharePath -NewPath D:\NewSharePath

 

Powershell-Skriptmodule erstellen

$
0
0

Ein extrem cooles Features sind die in Powershell 2.0 eingeführten Module. Module sind Erweiterungen, die in Powershell geladen werden und weitere Cmdlets zur Verfügung stellen. Ab Powershell 3.0 können über das Modul-Autoloading Module sogar ohne weiteres zutun des Benutzers beim Starten einer Powershell automatisch geladen werden. 

Module gibt es in Form von Binärmodulen, die z.B. in C# geschrieben werden, und in Form von sogenanten Skriptmodulen. Ein Skriptmodul ist dabei eigentlich nichts anderes als ein Powershell-Skript, dass global aufgerufen wird und im Normalfall Funktionen enthält, die dann wie Cmdlets gestartet werden können. Funktionen verhalten sich dabei wie ein Skript, dass überall in der Powershell-Konsole mit über den Namen aufgerufen werden kann, ohne dass man sich im Skriptpfad befinden müsste, da die Funktionen direkt in den Arbeitsspeicher geladen und von dort aus gestartet werden. 

Ein Skriptmodule zu erstellen, ist sehr einfach und braucht auch keine erweiterten Powershell-Kenntnisse. Prinzipiell kann jedes beliebige Powershell-Skript wie ein Module verwendet werden, indem man es einfach über das Cmdlet Import-Module <Skriptdatei> startet. Sinniger ist es aber, das Skript als automatisch ladendes Skript abzulegen. Dazu muß die Skriptdatei in einem der Module-Ordner abgelegt werden, die von Powershell automatisch beim Starten geprüft werden. Welche Ordner Module-Ordner sind, legt dabei die Umgebungsvariable PSModulePath fest. Sie können die Modulorder in Powershell dabei übersichtlich anzeigen lassen, indem Sie sich mit der String-Methode Split() den String in seine Einzelpfade aufsplitten lassen. Geben Sie dafür folgende Codezeile in und bestätigen Sie mit Enter:

$env:psModulePath.split(";")
C:\Users\admin.NETZ-WEISE\Documents\WindowsPowerShell\Modules
C:\Program Files\WindowsPowerShell\Modules
C:\Windows\system32\WindowsPowerShell\v1.0\Modules

Wie Sie im Beispiel sehen können, verwendet Powershell standardmäßig drei Modul-Pfade - einen für Windows-eigene Module im Powershell-Ordner, einen für Nicht-Windows-Module unter "Program Files" und einen benutzerspezifischen im Dokumente-Ordner des Benutzers. Sie können Ihre eigenen Skripte als Module unter "Program Files" oder unter Documents ablegen, wenn das Module nur für den Benutzer zur Verfügung stehen soll. Legen Sie hierfür im Module-Ordner einen neuen Unterordner an, und speichern Sie Ihr Skript innerhalb dieses Ordners mit der Endung .psm1. Sie können als Beispiel die Funktion Replace-SharePath aus dem Tipp Windows Freigaben auf einen neuen Pfad migrieren verwenden. Als Dateinamen verwenden Sie den gleichen Namen wie für den Ordner. Das Modul "KleineHelfer" muß also als "%ProgramFiles%\WindowsPowershell\Modules\KleineHelfer\KleineHelfer.psm1" gepeichert werden. Beim Start der nächsten Powershell-Konsole wird Ihr Modul automatisch geladen. Ist das nicht der Fall, können Sie die Fehlermeldung erzwingen, indem Sie das Modul von Hand mit dem Parameter -force laden:

Import-Module -Name KleineHelfer -force

Da das Modul letztendlich nur ein Skript ist, kann z.B. die Ausführungsrichtlinie das Starten des Moduls verhindern. Außerdem sollten Sie prüfen, ob der Name Ihrer Skriptdatei tatsächlich mit dem Ordnernamen übereinstimmt. Wenn das Modul geladen wurde, können Sie die Funktion in der Konsole direkt unter Ihrem Namen "Replace-SharePath" aufrufen. Sie können auch mehrere Funktionen in der .psm1-Datei speichern - sie stehen nach dem Starten der Powershell alle als Cmdlets zur Verfügung. 

Module verhalten sich zwar prinzipiell ähnlich wie Autostart-Skripte, sind aber dafür gedacht, Cmdlets nachzuladen. Daher sollten Ihre Module normalerweise auch keinen reinen Skriptcode enthalten, sondern immer Funktionen, die bei Bedarf gestartet werden können. Für das automatische Starten von Kommandos beim Öffnen der Konsole stellt Ihnen Powershell Profilskripte zur Verfügung. 

Mehr über Module erfahren Sie in der Powershell-Hilfe mit 

Get-Help about-modules

 

Virtuelle Festplatten (vhdx) als Datencontainer verwenden - nicht nur für VMs

$
0
0

Virtuelle Festplatten (Vhdx-Dateien) in Windows sind sehr praktisch. Man kann Sie nämlich nicht nur für virtuelle Maschinen verwenden, sondern auch als universelles Containerformat, um Daten zu transportieren oder sogar, um Windows aus einer VHD-Datei zu booten (s. https://www.netz-weise-it.training/weisheiten/tipps/item/377-boot-von-vhd-auf-einem-leeren-datentraeger-einrichten.html). Das Erstellen einer VHD-Datei ist allerdings etwas mühselig, weil man erst die Datei erstellen, dann einrichten und anschließend eine Partition erstellen muss. Da man das aber auch mit Powershell erledigen kann, und ich dieses Feature sehr oft verwende, habe ich eine kleine Funktion dafür geschrieben. 

#requires -Modules Hyper-V, Storage -RunAsAdministrator

function Get-Freedrive
{
<#
  .SYNOPSIS
  Get-Freedrives lists the first free driveletter in the alphabet.
  .DESCRIPTION 
  Get-Freedrive can be used to test for the first available free driveletter. The Parameter $inverse searches backwards through the alphabet.
  .EXAMPLE
  get-freedrive -inverse -startLetter Z
  Starts the search with letter z searching backwards. Returns a character.
  .NOTES
  Version: 1.0
  Author: Holger Voges
  Date: 2018-08-17
  www.netz-weise-it.training/weisheiten/
#>

[cmdletbinding()]
param(
  # The letter to start the search from - default is C or Z, if -inverse is chosen
  [char]$startLetter = 'C',

  # Inverse forces a backwards search through the alphabet
  [switch]$inverse

)


  # Setting Start-Letter to Z when using inverse Search
  If ( -not ( $PSBoundParameters.ContainsKey('Startletter') ) -and ( $inverse ) )
  { $startLetter = 'Z' }

  [int]$Counter = ([Convert]::ToByte( $startLetter ))
  if ( $inverse )
  { $Chararray = $Counter..67 } 
  Else { $Chararray = $Counter..90 }
  $drives = Get-PSDrive -PSProvider FileSystem
  foreach ( $letter in $CharArray )
  { 
    if ( [Char]$letter -notin $drives.Name )
    {
      [char]$letter
      break
    }
  }
}

Function New-ContainerVHD
{
<#
  .SYNOPSIS
  Creates a new VHD-Container-File
  .DESCRIPTION
  This Function Creates a VHD-File, creates a partition and formats it with ntfs. The Drive
  is mounted afterwards.
  .EXAMPLE
  Create a new VHDX-File as a 500GB Differencing File and mount it as X:
  New-ContainerVHD -Path D:\Sourcen.vhdx -DriveLetter X

  .EXAMPLE
  Create a new VHDX-File as a 100GB Differencing File and mount it as X: with a label Isos
  New-ContainerVHD -Path D:\Sourcen.vhdx -DriveLetter X -Label isos

  .NOTES
  Version: 1.0
  Author: Holger Voges
  Date: 2019-04-26
  www.netz-weise-it.training/weisheiten/
#>
param(
  # The Path for the Container-VHD
  [Parameter(Mandatory=$true)]
  [string]$Path,

  # The Driveletter the Drive is mounted to
  [ValidateScript({ if ( $_ -in (( Get-PSDrive ).Name ))
                             { Throw "$_ is already used" }
                             $true
                          })]
  [Char]$DriveLetter = 'M',

  # The Size of the Disk in Bytes. Minimum is 100MB, Maximum is 64TB.
  [ValidateRange(104857600,70368744177664)]
  [int64]$SizeBytes = 500GB,

  # The Name which will be shown in the File-Explorer
  [ValidateLength(1,32)]
  [String]$FileSytemLabel = 'Data'
)

  $vhd = New-VHD -Path $Path -SizeBytes $SizeBytes | Mount-VHD -Passthru
  $disk = $vhd | Initialize-Disk -PartitionStyle GPT -PassThru
  $part = $disk | New-Partition -UseMaximumSize
  $part | Format-Volume -FileSystem NTFS -NewFileSystemLabel $FileSytemLabel -Confirm:$false
  Set-Partition -DiskNumber $vhd.Number -PartitionNumber $part.PartitionNumber -NewDriveLetter $DriveLetter
}

Sie können die beiden Funktionen in einer Skriptdatei ausführen und anschließend über New-ContainerVHD speichern. Alternativ können Sie die Skriptdatei auch einfach als Modul speichern. Wie das geht beschreibe ich im Artikel Ein Powershell Skriptmodul erstellen

Beachten Sie, dass das Skript nur dann funktioniert, wenn Sie Hyper-V installiert haben und eine administrative Powershell-Konsole verwenden. Hyper-V können Sie ab Windows 8 in der Professional-Version und aufwärts einfach nachinstallieren. Verwenden Sie hierfür das Kommando

Install-WindowsOptionalFeature -FeatureName Microsoft-Hyper-V-All -online

 

Binärdateien einlesen und Base64-Codieren mit Powershell

$
0
0

Grundsätzlich unterscheidet man zwischen Textdateien und Binärdateien. Technisch gesehen sind beide Dateitypen Bitströme, also Reihen von aufeinanderfolgenden Bits. Textdaten werden allerdings nach einem festen Muster kodiert. Jeweils ein Block von 8 oder 16 Bits (oder je nach Codierung auch mehr) werden zu einem Zeichen zusammengefasst. Die klassische Codierung ist dabei die sehr alte ASCII-Codierung, die ein Zeichen in jeweils 7 Bit codiert und insgesamt 8 Bit für die Speicherung verwendet. Wenn man Get-Content verwendet, um eine Textdatei einzulesen, versucht Get-Content automatisch, die Datei als Text zu interpretieren und darzustellen. 

get-content -Path $einv:Windir\lsasetup.log
[ 1 1 / 4 1 5 : 3 1 : 1 4 ] 5 9 6 . 6 0 0 > - I n L s a p S e t R a n d o m D o m a i n S i d ( )

[ 1 1 / 4 1 5 : 3 1 : 1 4 ] 5 9 6 . 6 0 0 > - L s a p G e n e r a t e R a n d o m D o m a i n S i d : R t l A l l o c a t e A n d I n i t i a l i z e S i d r e t u r n e d 0 x 0

Die Ausgabe dieses Beispiels sieht allerdings sehr merkwürdig aus - Powershell hat zwischen jedes Zeichen ein Leerzeichen gesetzt. Das liegt daran, dass die Datei in UTF16 und nicht in ASCII codiert ist. UTF16 ist eine Form der Unicode-Codierung, die statt 8 16 Bit verwendet, um ein Zeichen zu speichern. Dadurch wird es möglich, eine deutlich größere Zahl von Zeichen zu codieren. UTF16 ist abwärtskompatibel zu ASCII, verwendet also für die ersten 127 Zeichen die gleiche Muster. Das zweite Bit wird beim Einlesen mit ASCII-Codierung dann als Leerzeichen interpretiert. Das Problem lässt sich lösen, indem man Get-Content mti dem Parameter -Endoding anweist, den Text als UTF16 zu dekodieren. 

get-content -Path .\lsasetup.log -Encoding Unicode
[11/ 4 15:31:14] 596.600> - In LsapSetRandomDomainSid()
[11/ 4 15:31:14] 596.600> - LsapGenerateRandomDomainSid: RtlAllocateAndInitializeSid returned 0x0

Das sieht schon viel besser aus. Wenn man mit Get-Content allerdings versucht, eine Binärdatei einzulesen, versucht Get-Content, auch den Binärcode in Textzeichen zu übersetzen. Das ist allerdings sinnlos, da der Binärcode ein Bitstrom ist und keinen Text darstellt. Das Ergebnis ist Kauderwelsch. 

Get-Content -Path $env:Windir\notepad.exe

tipp466 1

Wie man sehen kann, sind aber auch in der ausführbaren Datei Ausgabetexte enthalten, die dann durchaus interpretiert werden können (Im Bild rot markiert). Das das sauber funktioniert liegt daran, dass auch der Binärcode in Bytes und nicht Bits gespeichert wird und die Bytegrenzen damit gleich bleiben.

Man kann Powershell anweisen, eine Binärdatei nicht als Text zu interpretieren, indem man als Encoding Byte angibt:

get-content -Encoding Byte -Path C:\windows\notepad.exe

Das Cmdlet gibt die Daten dann als Byte-Array zurück, die man z.B. in einer Variablen speichern kann. Beschleunigen können Sie das Einlesen, indem Sie Get-Content anweisen, die Datei in einem Stück (Raw) und nicht Zeilenweise einzulesen:

$notepad = get-content -Encoding Byte -Path $env:Windir\\notepad.exe -Raw

Alternativ kann man auch auf das .net-Framework und die Klasse IO.File zurückgreifen, um die Datei einzulesen:

[IO.File]::ReadAllBytes('C:\Windows\notepad.exe')

Aber wofür sollte man eine Binärdatei überhaupt in Powershell einlesen? Z.B. kann man die Datei wieder durch eine Codierung laufen lassen, nämlich Base64. Sie kann Binärdaten wieder als Text codieren, um sie dann in einer Textdatei speichern zu können. Base64 wird z.B. verwendet, um Binärdateien über das rein textbasierte Mailprotokoll SMTP übertragen zu können. So ist es z.B. möglich, eine ausführbare Datei komplett in ein Powershell-Skript zu kodieren, um Sie bei Bedarf dann wieder im Dateisystem zu speichern und zu starten. 

$base64string[Convert]::ToBase64String([IO.File]::ReadAllBytes("$env:Windir\notepad.exe"))

Um die Datei ins Dateisystem zurückzuschreiben, verwenden Sie die Methode [IO.File]::WriteAllBytes():

[IO.File]::WriteAllBytes($FileName, [Convert]::FromBase64String($base64string))

Ob Sie die Datei direkt mit der Methode "ReadAllBytes()" oder Get-Content einlesen, macht im Übrigen von der Performance keinen Unterschied, wenn Sie den Parameter -Raw benutzen, da Get-Content im Hintergrund auf die gleiche Methode zurückgreift.

 

 

Programme und Code in Base64 und zurück konvertieren und ausführen mit Powershell

$
0
0

Base64 ist ein Format, das es erlaubt, Binärdaten als Text darzustellen. Den Zweck von Base64 habe ich bereits im Artikel Binärdateien einlesen und Base64-Codieren mit Powershell beschrieben. Eine sehr gute und ausführliche Beschreibung finden Sie außerdem bei Wikipedia.

Das Umwandeln von Binärdaten in Text ist im Powershell-Kontext aus mehreren Gründen spannend. Zum Einen kann man mit Base64 ausführbare Programme in einem Skript mitliefern, ohne sie als eigenständige Dateien mitliefern zu müssen. Zum Anderen kann Powershell Base64-kodierten Code direkt ausführen, indem man die Powershell.exe mit dem Parameter -encodedCommand aufruft.

Lesen Sie zuerst die Datei ein, die in Text umgewandelt werden soll. Hierfür verwenden Sie entweder das Powershell-Cmdlet Get-Content mit dem Parameter -Encoding Byte und -Raw, oder Sie benutzen die Powershell-Klasse [IO.File]:

$BinaryData = Get-Content -Path C:\Tools\Drivesnapshot64.exe -Encoding Byte -Raw
# Alternativ:
$BinaryData = [IO.File]::ReadAllBytes('C:\Tools\Drivesnapshot64.exe')

Der Parameter -Raw sorgt dafür, dass die Datei komplett und nicht zeilenweise eingelesen wird, -Encoding Byte sagt Powershell, dass es sich um eine Binärdatei handelt. 

Die Konvertierung findet mit der [Convert]-Klasse aus dem .Net-Framework statt. Die Methode ToBase64string() wandlet die Binärdaten in Text um:

$base64string = [Convert]::ToBase64String($BinaryData)

Zurückschreiben können Sie die Daten mit Set-Content oder der Methode WriteAllBytes():

$BinaryData = [Convert]::FromBase64String($base64string)
Set-Content -Value $BinaryData -Path 'C:\Temp\Snapshot.exe' -Encoding Byte
# Alternativ
[IO.File]::WriteAllBytes('C:\Temp\Snapshot.exe', [Convert]::FromBase64String($base64string))

Um einen Powershell-Befehl zu kodieren, wandeln Sie ihn zuerste in Unicode um. Das geht mit der Klasse [System.Text.Encoding]. 

$code = 'Write-Warning "Ich bin versteckter Code"'
$UTFCode = [System.Text.Encoding]::Unicode.GetBytes($code)
$Base64Code = [Convert]::ToBase64String($UTFCode)

Das kodierte Kommando ist jetzt vollkommen unverständlich:

VwByAGkAdABlAC0AVwBhAHIAbgBpAG4AZwAgACIASQBjAGgAIABiAGkAbgAgAHYAZQByAHMAdABlAGMAawB0AGUAcgAgAEMAbwBkAGUAIgA=

Aber Powershell kann es trotzdem ausführen.

Powershell.exe -EncodedCommand $Base64Code
WARNUNG: Ich bin versteckter Code

Das ist nicht ungefährlich, weil ein Base64-codierte Schadcode nicht als solcher zu erkennen ist. Mit AMSI (Anti Malware Scan Interface) eine Schnittstelle geschaffen, die es Virenscanner erlaubt, auf den auszuführenden Code zuzugreifen, und nicht auf den übergebenen Textwert. Die gleiche Funktion macht sich übrigens auch Powershell Skriptblocklogging zunutze, um ausgeführen Code im Eventlog anzuzeigen, und nicht den an Powershell übergebenen sinnlosen Aufruf. Mehr zu AMSI finden Sie unter https://blogs.technet.microsoft.com/poshchap/2015/10/16/security-focus-defending-powershell-with-the-anti-malware-scan-interface-amsi/.

 


Binärdaten in der Registry speichern mit Powershell

$
0
0

In den letzten beiden Artikeln habe ich gezeigt, wie man mit Powershell Binärdaten einlesen kann. Die eingelesenen Daten können auch in der Registry gespeichert werden, und zwar direkt binär oder Base64-codiert als String. Diese Beispiele stammen aus einem Webcast, den ich für die Firmat Netwrix unter dem Titel "Die Windows Registry als Angriffsvektor" am 2. Mai gehalten habe. Die Aufzeichnung kann man unter https://www.netwrix.com/webinars.html#featured anschauen. 

Um Daten direkt im Binärformat zu speichern, legen Sie einen neuen Registry-Wert mit dem Cmdlet New-Itemproperty an. Der Schlüssel (Ordner), in dem der Wert erzeugt werden soll, muß dafür existieren.

$FindExe = Get-Content -Path C:\Windows\System32\find.exe -Encoding Byte -Raw
$RegKey = New-Item -Path Registry::Hkey_local_machine\SOFTWARE -Name NetzWeise
New-ItemProperty -Path $RegKey.PSPath -Name Find -Value $FindExe -PropertyType Binary

Um die Datei wieder als Exe-Datei zu speichern, lesen Sie den Schlüssel einfach aus. 

$FindBinaryData = (Get-ItemProperty -Path Registry::Hkey_Current_User\SOFTWARE\NetzWeise -Name Find).Find
Set-Content -Value $FindExe -Path c:\temp\find.exe -Encoding Byte

Alternativ können Sie auch die .net-Klasse IO.File verwenden (s. Binärdateien einlesen und Base64-Codieren mit Powershell)

Sie können die Binärdateien auch als Text speichern, indem Sie sie mit der Convert-Klasse ins Base64-Format konvertieren. 

$ExeToHide = "$env:Windir\System32\find.exe"
$Base64string = [Convert]::ToBase64String(( Get-Content -Path $ExeToHide -Raw -Encoding Byte ))
$key = New-ItemProperty -Path $RegKey.PSPath -Name FindasString -Value $base64string -PropertyType ExpandString

Die Rückkonvertierung findet dann mit der Methode FromBase64STring() aus der Convert-Klasse statt. 

$OutFile = "$env:Temp\HiddenCode.exe"
$Malware = ( Get-ItemProperty -Path $key.PSPath -Name FindAsString ).FindasString

Set-Content -Path $OutFile -Value ( [Convert]::FromBase64String( $MalWare )) -Encoding Byte

Um zu verhindern, dass die Textschlüssel zu groß werden, kann man den Base64-Code auch einfach auf mehrere Zeilen aufteilen. 

$key2 = New-Item -Path Registry::Hkey_Current_User\SOFTWARE\ -Name EvilNetzWeise
$ExeToHide = "$env:Windir\System32\find.exe"

$Base64string = [Convert]::ToBase64String(( Get-Content -Path $ExeToHide -Raw -Encoding Byte ), 'InsertLineBreaks' )
$NewString = $Base64string -split "`r`n"
For ( $i=0; $i -lt $NewString.Length; $i++ )
{
   $null = New-ItemProperty -Path $Key2.PsPath -Name $i -Value $NewString[$i] -PropertyType String
}

Hier wird der Parameter 'InsertLineBreaks' an die Methode ToBase64String() übergeben, der den Text alle 78 Zeichen umbricht. Der Zeilenumbruch wird kodiert mit CarriageReturn,Newfeed und in Powershell dargestellt als `r`n. Der -Split-Operator wird dann verwendet, um den Code an genau diesen Zeichen aufzubrechen. -Split erzeugt ein Array und entfernt die Trennzeichen, so dass wir am Ende für jeweils 78 Zeichen eine neue Zeile erhalten, die dann per For-Schleife in jeweils einen eigenen Registry-Wert geschrieben wird. 

Um die Daten wieder zusammenzuführen, brauchen die einzelnen Zeilen jetzt nur wieder aneinandergefügt zu werden. 

$Key2Properties = Get-Item -path $key2.PSPath
$EvilCode = For ( $i=0; $i -le $key2Properties.ValueCount-1 ; $i++ )
{
   ( Get-ItemProperty -Path $key2.PSPath -Name $i ).$i
}
Set-Content -Path $OutFile -Value ([Convert]::FromBase64String($EvilCode)) -Encoding Byte

Sie sehen also, dass es mit Powershell ein Kinderspiel ist, ausführbaren Code in der Registry abzulegen, so dass er keine Spuren auf der Festplatte hinterlässt. Sie haben natürlich Recht, wenn Sie anmerken, dass Ihr Virenscanner spätestens beim Speichern auf der Festplatte anschlägt, aber wer sagt denn, dass ein Programm auf der Festplatte liegen muß, um gestartet zu werden? https://powersploit.readthedocs.io/en/latest/CodeExecution/Invoke-ReflectivePEInjection/

Remote Desktop per Powershell Remote aus der Ferne aktivieren

$
0
0

Sie möchten sich auf einen Computer per RDP remote aufschalten, aber RDP ist nicht aktiviert? Kein Problem, solange der Computer per Powershell-Remoting erreichbar ist. RDP ist nämlich über einen einzigen Registry-Eintrag steuerbar. Er heißt fDenyTSConnections und gehört zum Schlüssel Hkey_Local_Machine\System\CurrentControlSet\Control\Terminal Server. Setzen Sie ihn auf 0, so werden RDP-Verbindungen nicht mehr blockiert. Die Einstellung wirkt sofort, ein Neustart ist nicht notwendig, allerdings blockiert die Windows Firewall den Port 3389 dann weiterhin. Es gibt eine Gruppe von Regeln mit dem sprechenden Namen "RemoteDesktop", die Sie aktivieren müssen, um die Ausnahmen für eingehende RDP-Verbindungen inklusive Remote-Unterstützung zu aktivieren. Das geht mit Powershell in zwei Zeilen:

$RdpKey = "Registry::Hkey_Local_Machine\System\CurrentControlSet\Control\Terminal Server"
Set-ItemProperty -Path $RDPKey -Name "fDenyTSConnections" –Value 0
Enable-NetFirewallRule -DisplayGroup "RemoteDesktop"

Ja, Sie haben natürlich Recht, das sind 3 Zeilen, aber nur, um den Code hier ein wenig übersichtlicher darstellen zu können. ;-)

Wenn Sie am betroffenen Rechner sitzen, können Sie das natürlich auch von Hand machen. Sitzen Sie nicht an der betreffenden Maschine, aber Powershell-Remoting ist aktiviert (ab allen Servern ab 2012 Standard), können Sie das aber per Invoke-Command auch aus der Ferne erledigen. Das kann man natürlich auch wieder ein eine Funktion packen und in ein Modul speichern. Als einfache Funktion könnte das so aussehen - auch gleich mit Unterstützung für SSL.

Function Enable-RemoteDesktop
{
param(
  [String]$computername,

  [Switch]$useSSL,

  [PsCredential]$Credential
)

  $InvokeParam = @{
    Computername = $computername
    Credential = $Credential
  }

  If ( $useSSL )
  {
    $InvokeParam["UseSSL"] = $true
  }

  Invoke-Command @InvokeParam -ScriptBlock {
    $RdpKey = "Registry::Hkey_Local_Machine\System\CurrentControlSet\Control\Terminal Server"
    Set-ItemProperty -Path $RDPKey -Name "fDenyTSConnections" –Value 0
    Enable-NetFirewallRule -DisplayGroup "RemoteDesktop"
  }
}

 

Eine MAC-Adresse unter Windows oder Windows PE auslesen und vereinheitlichen

$
0
0

Heute stand ich vor der Aufgabe, eine MAC-Adresse einzulesen. Das Einlesen der MAC-Adresse gestaltet sich dabei einfach. Unter Windows am einfachsten geht das über Get-Netadapter:

PS > Get-NetAdapter

Name         InterfaceDescription                ifIndex Status   MacAddress         LinkSpeed
----         --------------------                ------- ------   ----------         ---------
Internal     Microsoft Hyper-V Network Adapter        10 Up       00-15-5D-64-98-00  10 Gbps

Unter Windows PE steht das Cmdlet leider nicht zur Verfügung, da es zu den CDXML-Datei basierten Cmdlets gehört. Glücklicherweise kann man sich behelfen, indem man einfach auf Get-WMIObject zurückgreift:

PS > Get-WmiObject -Class win32_networkadapter

Get-WmiObject -Query "select * from win32_networkadapter where NetConnectionStatus = 2"

ServiceName      : netvsc
MACAddress       : 00:15:5D:64:98:00
AdapterType      : Ethernet 802.3
DeviceID         : 1
Name             : Microsoft Hyper-V Network Adapter
NetworkAddresses :
Speed            : 10000000000

Dummerweise liefern die beiden Ausgabe unterschiedliche Formate zurück. Während Get-NetAdapter jedes Byte mit einem Bindestrich trennt, verwendet WMI als Trennzeichen einen Doppelpunkt. Um das Format zu vereinheitlichen, habe ich mit dem Operator -replace einfach Doppelpunkte und Bindestriche entfernt.

$NIC = Get-WmiObject -Class win32_networkadapter
$NIC.MACAddress -replace ':|-',''

Das Schöne an -replace ist, dass er reguläre Ausdrücke verwendet. ':|-' bedeutet : oder -, und der Ersatz ist ein Leerstring: ''. Letztlich werden also alle MAC-Adresse auf das gleiche Format heruntergebrochen, nämlich ohne Trennzeichen.

Wenn Sie nur die Netzwerkkarten abfragen möchten, die Online sind, können Sie mit WMI einfach auf den Netconnectionstatus filtern:

Get-WmiObject -Query "select * from win32_networkadapter where NetConnectionStatus = 2"

Mit Get-Networkadapter können Sie sehr einfach nur die Netzwerkkarten ermitteln, die eine Adresse per DHCP bekommen haben:

Get-NetIPAddress -PrefixOrigin Dhcp | Get-NetAdapter

Im nächsten Tipp Einen String mit Split in gleich Fragmente teilen zeige ich Ihnen, wie Sie eine flache MAC-Adresse (ohne Trennzeichen) wieder in ein beliebiges Format zurücküberführen können, indem Sie den String an bestimmten Stellen (jedem zweiten Zeichen) auftrennen.

Fehlende virtuelle Switche für Hyper-V VMs ermitteln und automatisch anlegen

$
0
0

Wenn eine virtuelle Maschine in Hyper-V von einem Host auf einen anderen per Import übertragen werden soll, müssen alle virtuellen Switche, an die die Maschine auf dem Quellhost angeschlossen war, auf dem Zielhost existierten. Existieren bedeutet in diesem Fall, dass ein Switch mit identischem Namen vorhanden sein muß. Ist das nicht der Fall, quittiert Hyper-V den Import mit einer Fehlermeldung:

Es ist ein Fehler beim Import aufgetreten
Der virtuelle Computer kann aufgrund von Konfigurationsfehlern nicht importiert werden. Verwenden Sie "Compare-VM", um den virtuellen Computer zu reparieren

Compare-VM ist ein Powershell-Kommando, dass zusammen mit dem Hyper-V Modul ausgeliefert wird, und das vor dem Import angewendet werden kann, um zu prüfen, ob es Probleme bei der Konfiguration gibt. Dafür pipen Sie das vmcx-File der virtuellen Maschine einfach in Compare-VM. Man erhält dann ein Rückgabeobjekt mit den Konfigurationsdetails der VM:

PS > Dir M:\Hyper-V\LON-AP1\Virtual Machines\4C9790D7-48CD-4D31-8BA2-02D8C9C245AE.vmcx | compare-VM

CheckpointPath     : M:\Hyper-V\LON-AP1\Snapshots
VM                 : VirtualMachine (Name = 'LON-AP1') [Id = '4c9790d7-48cd-4d31-8ba2-02d8c9c245ae']
OperationType      : ImportVirtualMachine
Destination        : R1WS3
Path               : M:\Hyper-V\LON-AP1\Virtual Machines\4C9790D7-48CD-4D31-8BA2-02D8C9C245AE.vmcx
SnapshotPath       : M:\Hyper-V\LON-AP1\Snapshots
VhdDestinationPath : M:\Hyper-V\LON-AP1\Virtual Hard Disks
VhdSourcePath      :
Incompatibilities  : {33012}

Interessant ist hier die Eigenschaft Incompatibilities, denn Sie zeigt, welche Konfigurationen nicht mit dem Zielhost kompatibel sind. Incompatibilities ist ein Objekt.

PS > $VM = Dir M:\Hyper-V\LON-AP1\Virtual Machines\4C9790D7-48CD-4D31-8BA2-02D8C9C245AE.vmcx | compare-VM
PS > $VM.Incompatibilities

Message                                                      MessageId Source
-------                                                      --------- ------
Der Ethernet-Switch "External Network" wurde nicht gefunden.     33012 VMNetworkAdapter (Name = 'Network Adapter', VMName = 'LON-AP1') [VMId = '4c9790d7-48cd-4d31-8ba2-02d8c9c245ae']

Hier sieht man, dass die Message-ID 33012 bedeutet, dass ein virtueller Switch nicht vorhanden ist, und wie dieser heißen muß. Mit diesen Daten ist es jetzt nicht mehr schwer, mit Hilfe eines regulären Ausdrucks den Switchnamen aus der Fehlermeldung zu extrahieren. Das folgende kleine Skript erledigt das für alle virtuellen Maschinen in einem Quellordner:

$VMFolder = "M:\Hyper-V"
$SwitchNameFilter = '"(.*)"'
$MessageList = get-Childitem -path $VMFolder -Include '*.vmcx' -Recurse |
    Compare-VM |
    Where-Object { $_.Incompatibilities.MessageID -eq "33012" } |
    select @{name="Message";expression={ $_.Incompatibilities.Message }}
$SwitchNames = Foreach ( $Message in $MessageList )
{
    $null = $message -match $SwitchNameFilter
    $matches[1]
}
$SwitchNames | Select-Object -Unique

Wenn Sie noch mit Windows Server 2012 (R2) arbeiten, kommen Sie ohne Compare-VM aus, da Server 2012 und 2012R2 die Konfiguration noch in einer leicht einlesbaren XML-Datei gespeichert haben. Hier verwenden Sie statt eines regulären Ausdrucks einfach eine Xpath-Abfrage:

$VMFolder = "M:\Hyper-V"
$VmConfigFiles = get-Childitem -path $VMFolder -Include '*.vmcx' -Recurse | Where-Object { $_.Fullname -NotLike "*Snap*" }
$SwitchNames = Foreach ( $Config in $VmConfigFiles )
{
    [xml]$config = Get-Content -Path $vm -Raw
    (select-xml -xml $config -XPath '//AltSwitchName').node.'#text'
}
$SwitchNames | Select-Object -Unique

 

Den Datenträgertyp (SSD,HDD) per Powershell und WMI/CIM bestimmen

$
0
0

Um in einem Skript zwischen HDDs und SSDs zu unterscheiden, kann man seit Windows 8 auf das Cmdlet Get-PhysicalDisk zurückgreifen. Es liefert ein MSFT_PhysicalDisk-Objekt zurück, das unter https://docs.microsoft.com/en-us/previous-versions/windows/desktop/stormgmt/msft-physicaldisk beschrieben ist. Die Eigenschaft MediaType enthält den Datenträgertyp (HDD,SSD).

Das Cmdlet Get-Physicaldisk ist als CDXML-basiertes Cmdlet implementiert und fragt im Prinzip einfach nur die Klasse MSFT_PhyscialDisk ab. Das kann man auch direkt erledigen. 

Get-CimInstance MSFT_Physicaldisk -Namespace root\Microsoft\Windows\Storage

Wichtig - Die Klasse MSFT_PhysicalDisk befindet sich nicht im Standard-Namespace CimV2, daher ist es wichtig, den Namespace mit anzugeben. 

Warum sollte man die Klasse direkt abfragen? Z.B. wenn einem die Storage-Cmdlets nicht zur Verfügung stehen, oder wenn man nicht sicher weiß, ob die Cmdlets verfügbar sind. Außerdem kann man mit Get-CimClass die Abfrage auch direkt einschränken, was schneller geht als über Get-PhysicalDisk und Where-Object zu filtern:

Get-CimInstance -Query "select * from MSFT_PhysicalDisk Where MediaType = 4" -Namespace root\Microsoft\Windows\Storage

Achten Sie darauf, dass der Mediatype in der Klasse mit einer Zahl (HDD3, SSD=4) repräsentiert ist!

Unter Windows 7 steht diese Klasse noch nicht zur Verfügung. Die einzige Möglichkeit, die Sie hier haben, ist über die Klasse Win32_DiskDrive zu gehen und zu hoffe, dass die Modellbezeichnung SSD beinhaltet: 

Get-WmiObject -Class Win32_diskdrive | Where-Object { $_.caption -match "ssd" }

Hier ist es prinzipiell natürlich auch möglich, Get-CIMClass zu benutzen, aber dafür muß unter Windows 7 auch Powershell 3 installiert sein. 

Viewing all 119 articles
Browse latest View live