Endlosschleife bei Gruppen im Active Directory finden

Im Active Directory kann man bekanntlich Gruppen als Mitglied von anderen Gruppen eintragen. Wenn man dabei nicht aufpasst hat man in Active Directory ruck zuck eine Endlosschleife gebastelt, indem man Gruppe A als Mitglied von Gruppe B und Gruppe B als Mitglied von Gruppe A einträgt. Scripte oder Programme, die Benutzergruppen auflösen können hier schnell Probleme mit haben.

Ein schönes PowerShell-Script, das die Gruppen durchgeht und nach Endlosschleifen sucht gibt auf der Website von Richard L. Mueller.

Maximale Feldlänge im Active Directory

Bereits Freitag beschäftigte mich das Problem, dass ich bei einigen Usern keine neuen Einträge in das Notizfeld im Active Directory schreiben konnte (das Feld wird von uns als Protokoll für den Benutzer betreffenden Aktionen genutzt). Anfangs ging es recht problemlos mit diesen Befehlen:

$user = Get-QADUser -Identity $uid
$note += "`r`n" + $user.Info
Set-QADUser $user -Notes $note

Hier wird einfach das Info-Feld ausgelesen, die neue Zeile davor gesetzt und dann beim User wieder gesetzt.

Neuerdings trat dabei immer wieder der recht wenig aussagekräftigen Fehler A constraint violation occurred auf. Ein Umschreiben auf die entsprechenden Befehle von Microsoft gab dann eine etwas brauchbarere Meldung: A value for the attribute was not in the acceptable range of values.

$user = Get-ADUser -Identity $uid
$note += "`r`n" + $user.Info
$user |Set-ADUser -Replace @{Info=$note}

Diese Information brachte mich auf die richtige Spur: Attribute im AD haben (nicht wirklich überraschend) eine Maximallänge, beim Info-Feld sind es anscheinend 1024 Zeichen.

Als Workaround habe ich das Skript so umgeschrieben, dass direkt im Notizfeld nur die letzten 10 Meldungen gespeichert werden. Die Meldungen wurden ohnehin schon zusätzlich in einer Datenbank abgelegt, so dass hier nichts verloren geht. Hier das vollständige Skript:

param( [string]$uid, [string]$note )

# Fehler abfangen und als sauberen Text ausgeben
trap [Exception] { 
	"ERROR: " + $_.Exception.Message
	exit
}
# Einschränken was vom AD-Modul geladen wird
$env:ADPS_LoadDefaultDrive = 0
Import-Module ActiveDirectory -Cmdlet Get-ADUser,Set-ADUser

# User-Objekt holen
$user = Get-ADUser -Identity $uid -Properties Info
# Neue Notiz am Anfang einfügen
$note += "`r`n" + $user.Info
# notiz in zeilen auftrennen, erste 10 nehmen, wieder zu string zusammenfügen
$note = (($note -split '[\r\n]+') | Select-Object -First 10) -join [environment]::NewLine
# Neue Notizen wieder ins User-Objekt schreiben
$user |Set-ADUser -Replace @{Info=$note}
"SUCCESS: Die Notiz wurde hinzugefuegt"

Import-PSSession beschleunigen

In ein paar Powershell-Scripten, die Befehle an Domain-Controller und Exchange-Server absetzen störte es mich schon seit geraumer Zeit, dass das initiieren der Scripte relativ lange dauert. Besonders bei Exchange-Zugriffen fiel es extrem auf, also fügte ich ein paar Zeitausgaben ein und analysierte die einzelnen Befehle.

param( [string]$uid, [string]$exhost )

function GetElapsedTime() {
    $runtime = $(get-date) - $script:StartTime
    $retStr = [string]::format("{3}.{4} seconds", `
        $runtime.Days, `
        $runtime.Hours, `
        $runtime.Minutes, `
        $runtime.Seconds, `
        $runtime.Milliseconds)
    $retStr
}

$script:startTime = get-date
write-host "Start: $(GetElapsedTime)"

$session = New-PSSession -Configurationname Microsoft.Exchange -ConnectionUri http://$exhost/powershell
write-host "New-PSSession: $(GetElapsedTime)"

Import-PSSession $session -CommandName "Get-CASMailbox" | Out-Null
write-host "Import-PSSession: $(GetElapsedTime)"

$mailbox = Get-CASMailbox $uid |Select-Object ActiveSyncMailboxPolicy,ActiveSyncEnabled,Name
write-host "Get-CASMailbox: $(GetElapsedTime)"

[string]::format("{0}: {1}, {2}", $mailbox.Name, $mailbox.ActiveSyncEnabled, $mailbox.ActiveSyncMailboxPolicy)

Ausgabe:

Start: 0.0 seconds
New-PSSession: 0.562 seconds
Import-PSSession: 11.296 seconds
Get-CASMailbox: 11.406 seconds
jdoe: True, Default

Fazit: Das größte Problem ist Import-PSSession wenn es sich mit dem Exchange-Server verbindet, es braucht meistens zwischen 11 und 13 Sekunden bis das Script weiter laufen kann. Nach einigem Suchen und Ausprobieren stieß ich auf den Parameter -CommandName, der einschränkt welche Befehle man importieren möchte.

$session = New-PSSession
Import-PSSession $session -CommandName "Get-CasMailBox"

holt nur den angegebenen Befehl in die Session, dafür dauert der Import dann aber auch nur ca 1,5 Sekunden, was die Gesamtlaufzeit des Scriptes immerhin auf ein Zehntel reduziert. Ergebnis:

Start: 0.15 seconds
New-PSSession: 0.468 seconds
Import-PSSession: 1.937 seconds
Get-CASMailbox: 2.156 seconds
jdoe: True, Default

Das ganze bringt natürlich nur einen Vorteil wenn man nur wenige Befehle in einem Script benötigt.

Textausgabe in Powershell unterdrücken

Da ich es immer wieder raussuchen muss: Es gibt in Powershell verschiedene Möglichkeiten die Ausgabe eines Befehls zu unterdrücken:

Befehl > $null
Befehl | Out-Null
$null = Befehl
[void] (Befehl)

Um die Ausgabe von mehreren Zeilen zu unterdrücken gibt es auch diese Möglichkeit:

$(
Befehl 1
...
Befehl n
) | Out-Null

Quelle: Thread in microsoft.public.windows.powershell.