SCM-Manager Repositories von Usern finden

Ich brauchte eine Übersicht aller Repositories, bei denen eine Gruppe von Usern direkt oder indirekt (über Gruppen) als Owner eingetragen waren.

Die User holte ich mir mit Powershell aus dem Active Directory:

$users = Get-AdGroup Hauptgruppe | Get-AdGroupMember -Recursive | Get-AdUser -Properties memberof

Die Gruppen, in der die User Mitglieder waren holte ich mir auf ähnliche Weise:

$users.memberof | Sort | Unique | Foreach-Object { $_ -Replace 'CN=(.+?),.+','$1' }

Damit hatte ich eine simple Liste aller User und ihrer Gruppen die ich als Textdatei abspeichern konnte.

Für den SCM-Manager gibt es einen Command Line client. Leider ist er inzwischen nicht so einfach zu finden, auch die Doku dazu ist extrem dürftig. Aktuell war er hier zu finden.

Die Usernames und die Gruppennamen speicherte ich in zwei Textdateien, dann erstellte ich noch ein Template für die Ausgabe von scm-cli-client anzupassen. Als Templates wird hier FreeMarker verwendet.

#!/bin/bash
SERVER=https://scm.example.com/scm/
USERNAME=admin
PASSWORD=GEHEIM
CLI="java -jar /opt/scm-cli-client-1.60-jar-with-dependencies.jar --server $SERVER --user $USERNAME --password $PASSWORD"
# Nutzt FreeMarker Templates https://www.vogella.com/tutorials/FreeMarker/article.html
REPOS=`$CLI list-repositories --template-file ./grouptemplate.tpl`
GROUPNAMES=$(<groupnames.txt)
USERNAMES=$(<usernames.txt)
for GROUPNAME in "$GROUPNAMES"; do
echo "$REPOS" |grep "$GROUPNAME"
done;
for USERNAME in "$USERNAMES"; do
echo "$REPOS" |grep "$USERNAME"
done;

Das Template das ich dafür erstellt habe sieht folgendermaßen aus:

<#list repositories as repository>${repository.name}<#if repository.permissions??> <#list repository.permissions as permission><#if permission.type?starts_with("OWNER")>${permission.name} </#if></#list>
</#if>
</#list>

Damit wird schlicht und einfach der Name des Repositorys ausgegeben, gefolgt von allen Ownern. Das lässt sich einfach mittels grep filtern. Die Ausgabe davon habe ich dann noch gefiltert um jedes Repository nur einmal zu bekommen, für den Fall das jemand sowohl über eine Gruppe als auch über seinen Username Rechte hat

./findrepos.sh | sort | uniq

Voilá, alle Repositories in denen die User direkt oder indirekt Owner-Rechte haben.

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.