DSM & PowerShell – Teil 1

Hallo zusammen,

heute möchte ich endlich mal ein Versprechen einlösen, was ich einem Frontrange Mitarbeiter vor langer Zeit gegeben habe, und mal den ersten Teil einer Serie veröffentlichen, wie man DSM über PowerShell Scripte steuern kann. Anfänglich habe ich es mir einfach gemacht, und nur XML Text am dem Webserver geschickt. Nach ein bisschen „Reverse Engeneering“, lässt sich das ganze auch etwas einfacher bewerkstelligen.

Ich möchte allerdings gleich klarstellen, dass ich mich in dieser Phase hauptsächlich an Leute wende, die PowerShell Grundkenntnisse mitbringen. Ich werde meine Funktionen im Laufe der Zeit erweitern, bis man vielleicht irgendwann mal etwas hat, was man einigermaßen „Out-of-the-box“ benutzen kann… aber dafür ist es noch etwas zu früh.

Die einzige Lektüre die man zu diesem Thema bekommt, ist die WebService Definition. Diese findet man in der Regel auf seinem DSM Server unter:

http://localhost:8080/blsAdministration/AdministrationService.asmx

Dort sind auch alle Vorgänge aufgelistet, die man so machen kann. Ich möchte hier gleich eine Warnung aussprechen:

Mit den hier beschriebenen Vorgängen, kann man Sachen machen, die die DSMC Konsole (vermutlich aus gutem Grund) blockieren würde. Beispielsweise ist es möglich, einen Benutzer in eine Computergruppe aufzunehmen. Das Bedeutet, dass Frontrange einige Prüfroutinen direkt in der DSMC und nicht im Webservice verankert hat. Solche Prüfungen müsste man dann Quasi SELBST schreiben.

In Teil eins, möchte ich gerne aufzeigen, wie man sich Grundsätzlich zum Webservice verbindet, und ein paar einfache, lesende Aktionen durchführt. Das ganze am Ende zusammengefasst in ein paar Hilfreiche PowerShell Funktionen.

1. Connect & Login

Für die Verbindung zum Webservice sind erstmal Berechtigungen notwendig. Hierzu ist ein Benutzer erforderlich, der auch innerhalb von DSM die entsprechenden Berechtigungen hat, um die Aktionen durchzuführen, die man durchführen möchte. Im Prinzip kann man hier 2 verschiedene Wege gehen, einerseits den, dass man einen Benutzer und ein Passwort im Script definiert, andererseits, dass man die Credentials des aktuell angemeldeten Benutzers nutzt.

Möchte man den User selbst im Script definieren, benötigt man ein Credential Objekt, welches ich mit einer kleinen Funktion erstelle. Teil 1 des Scriptes sieht dann so aus:

$swv_user = ‚Domäne\Benutzername‘
$swv_pass = ‚Pa$$word‘
$wsdl_url = „http://dsmserver:8080/blsAdministration/AdministrationService.asmx?WSDL

Function Get-PSCredential($User,$Password)
{
$SecPass = convertto-securestring -asplaintext -string $Password -force
$Creds = new-object System.Management.Automation.PSCredential -argumentlist $User,$SecPass
Return $Creds
}

$swv_login = Get-PSCredential -User $swv_user -Password $swv_pass
$global:swv_webservice = New-WebServiceProxy -uri $wsdl_url -Credential $swv_login

Möchte man die Credentials des angemeldeten Benutzers benutzen, kann man sich auf folgende 2 Zeilen beschränken:

$wsdl_url = „http://dsmserver:8080/blsAdministration/AdministrationService.asmx?WSDL
$global:swv_webservice = New-WebServiceProxy -uri $wsdl_url -UseDefaultCredential

Ich definieren die Verbindung zum Webservice hier schon direkt als globale Variable, um sie später innerhalb von Funktionen direkt zu benutzen.

2.Die verschiedenen Objekttypen

Damit ist der erste Schritt getan, die Verbindung wurde aufgebaut, und PowerShell hat die „Custom Objekte“ geladen. Da allerdings aus einem mir unerfindlichen Grund, der Webservice ab und zu verschiedene Namen für die Custom Objekte ausspuckt, die wir später benötigen, hole ich mir die Objekte dynamisch in einen Hashtable, das vereinfacht die Benutzung auch später.

$global:swv_types = @{}
foreach ($Type in $global:swv_webservice.GetType().Assembly.GetExportedTypes())
{
$global:swv_types.Add($Type.Name, $Type.FullName);
}

3. Der erste Request & die Antwort

Nun geht des daran eine Anfrage an den DSM Server zu stellen. In der URL die ich ganz oben genannt habe, sieht man, wenn man auf einen Vorgang klickt (z.B. GetObject), welche Eingabe erforderlich ist um die Anfrage durchzuführen. Leider ist diese nicht ganz vollständig. Grundsätzlich können wir den dort angegebenen Platzhalter „Length“ ignorieren, das macht unser Webservice automatisch. Also verbleiben beim Beispiel „GetObject“ noch „ObjectID“ und „RequestedObjectGroupType“. Die wir unserem Request mitgeben müssen…

Leider leider steht aber nirgendwo, dass sich de Request aus mehr als nur diesem XML zusammensetzt.

Im Prinzip benötigt jeder Request den wir erstellen (mit dem passenden Request Objekt):
– Ein „RequestHeader“ Objekt.
– Das „RequestHeader“ Objekt benötigt innen drin nochmal ein „ClientInfo“ Objekt.
– Ein ServerInfo Objekt
– Den eigentlichen Request

Um mir das etwas zu vereinfachen, habe ich mir eine Funktion gebaut, die diesen RequestHeader zusammenbaut. Da dieser für alle Requests gleich ist.

Function Get-DSMRequestHeader($action)
{
$action = $action +“Request“
$request = New-Object $global:swv_types[$action]
$request.Header = New-Object $global:swv_types[„RequestHeader“]
$request.Header.UserCulture = „de-DE“
$request.Header.ClientInfo = New-Object $global:swv_types[„ClientInfo“]
$request.Header.ClientInfo.Name = „powershell.exe“
$request.Header.ClientInfo.Version = „2.0“
$request.ServerInfo = New-Object $global:swv_types[„ServerInfo“]
$request.ServerInfo.CmdbGuid = „{0000000-0000-0000-0000-000000000000}“
$request.ServerInfo.MetaModelVersion = 203
return $request
}

Da Frontrange die Request-Objekte zum Glück durchgängig benannt hat, benötigen wir um eine „GetObject“ Operation auszuführen einen Request Objekt vom Typ „GetObjectRequest“. Das zieht sich von oben nach unten durch. Würde man einen „GetPolicy“ machen wollen, benötigt man ein „GetPolicyRequest“ Objekt usw.

Die Werte die im Header drinstehen, wie die UserCultrue, die CMDB Guid etc. werden benötigt. Allerdings ist es, zumindest so wie ich das sehe, ziemlich egal was dort drin steht, es taucht evtl. nur davon etwas in den Logfiles auf. Von dem her kann man die Funktion oben 1:1 kopieren. Ich habe sie so auf mehreren DSM Umgebungen erfolgreich angewendet.

Die Funktion wird einfach mit der Aktion aufgerufen die man ausführen möchte:

$request = Get-DSMRequestHeader -action „GetObject“

z.B. und man bekommt das passende Objekt zurück geliefert, was man nur noch fertig befüllen muß. Das machen wir im nächsten Schritt:

$request = Get-DSMRequestHeader -action „GetObject“
$request.ObjectId = 12345
$request.RequestedObjectGroupType = „Object“

Man vergleicht wieder mit der am Anfang genannten URL die Beschreibung des „GetObject“… das sind nun genau die 2 Placeholder die wir befüllen, um den Request abzusetzen.
Jetzt ist der Request im Prinzip fertig, und wir müssen ihn nur noch an den Webservice schicken, idealerweise in eine Variable, da wir die Antwort ja wissen wollen 😉

$return = $global:swv_webservice.GetObject($request)

Nun haben wir die Antwort vom Server, diese ist im Groben auch auf der erstgenannten URL zu sehen. Aber z.B. würde jetzt ein:

$return.RetrievedObject.Name

Ganz simpel den Namen des Objektes ausspucken, das wir aus DSM geholt haben. Das Objekt beinhaltet natürlich mehr, wie z.B. die Basis-Inventarisierungsdaten, allerdings sind die etwas weiter verschachtelt, aber mit ein paar for-schleifen bekommt man diese auch raus.

4. Die erste vollständige Funktion

Um das oben genannte in einer Funktion abzubilden könnte man dies wie folgt im Script darstellen:

Function Get-DSMObject($id)
{
$request = Get-DSMRequestHeader -action „GetObject“
$request.ObjectId = $id
$request.RequestedObjectGroupType = „Object“
return $global:swv_webservice.GetObject($request)
}

Damit haben wir eine fertige Funktion und können in unseren Scripten einfach mit folgendem Befehl arbeiten:

$return = Get-DSMObject -id „12345“

5. Objektanzahl ermitteln

Ich würde gerne hier in Teil 1 noch 2 weitere Funktionen aufzeigen, um Daten abzuholen, nmlich „GetObjectList“ und „GetObjectCount“. Für diese beiden Funktionen wird das Request Objekt mit einem LDAP Filter aufgebaut.

Beginnen wir mit „GetObjectCount“… da ich bei mir alles in Funktionen verbaue, erkläre ich hier nicht nochmal alles im Detail. Die Funktion sieht so aus:

Function Get-DSMObjectCount($filter)
{
$request = Get-DSMRequestHeader -action „GetObjectCount“
$request.LdapQuery = „<LDAP://rootDSE>;“+$filter+“;UniqueId;subtree
return $global:swv_webservice.GetObjectCount($request)
}

Wie man vielleicht schon sieht benötigt man hier einen Suchstring im LDAP Format. Dieser setzt sich im allgemeinen zusammen aus:
1. Dem LDAP Server der abgerufen werden soll, DSM-Intern benötigt man aber nur „rootDSE“, man muss keinen eigenen Server angeben.
2. Dem eigentlichen Filter an sich. Diesen kann man sich, wenn man nicht ganz in LDAP bewandert ist, auch in der DSM Konsole zusammenklicken, in dem man einfach seinen Suchfilter zusammen klickt, anwendet, und dann in den Suchfilter einmal rein klickt. Ich habe mir für dieses Beispiel einen Filter angelegt der mir alle Fujitsu Geräte raussuchen soll, die Windows 7 x86 installiert haben.

(&(BasicInventory.Vendor:IgnoreCase=Fujitsu)(BasicInventory.InstalledOS=266)(objectClass=Computer))

3. Der Teil an dem hier nur „UniqueID“ steht, beschreibt normal alle Attribute, die bei der Suche zurückgeholt werden sollen(Kommagetrennt). Da es sich hier um eine Suche handelt, die nur eine Anzahl zurückgeben soll, reicht es hier ein einzelnes Attribut.
4. Das „subtree“ hier ist der SearchScope. Der beschreibt, wie tief soll gesucht werden. Ich habe meine Suchen so gestaltet, das ich immer alles durchsuche. Hier bedarf es etwas modifikation, wenn jemand andere Anforderungen hat. Subtree durchsucht auch alle untergeordneten OUs

Da die Funktion schon fertig ist, hier mal noch wie man sie anwendet:

$Filter = „(&(BasicInventory.Vendor:IgnoreCase=Fujitsu)(BasicInventory.InstalledOS=266)(objectClass=Computer))“
$return = Get-DSMObjectCount -filter $Filter

Und schon hat man die Anzahl der Objekte die auf den Filter zutreffen.

$return.NumberOfResults

6. Objektlisten erzeugen

Die zweite Funktion die angesprochen haben „GetObjectList“, ist Ähnlich, gibt aber anstatt der Anzahl, die Liste der Objekte raus, die auf den Filter zutreffend sind. Ander als beim GetObject, gibt man hier allerdings noch zusätzlich an welche Attribute abgeholt werden sollen.

Auch hier möchte ich euch meine Funktion nicht vorenthalten:

Function Get-DSMObjectList($Filter,$Attributes,$Order)
{
$request = Get-DSMRequestHeader -action „GetObjectList“
$request.LdapQuery = „<LDAP://rootDSE>;“+$Filter+“;“+$Attributes+“;subtree
if($Order -eq $null) { $request.OrderBy = „Name“ }
else { $request.OrderBy = $Order }
$request.MaxResults = 2147483647
return $global:swv_webservice.GetObjectList($request)
}

Benötigt werden neben dem LDAP Filter hier die Attribute, es werden zwar immer gewisste Attribute per default abgeholt, manche müssen extra definiert werden. Wie diese genau heißen, bekommt man am besten über die DSMC raus. Wenn man sich die Eigenschaften eines Objektes anschaut, macht man einen Rechtsklick auf das Attribut. Der LDAP Name setzt sich dann zusammen: Grouptag.Tag also z.B. wenn man den Hersteller abholen will: BasicInventory.Vendor

Des Weiteren habe ich hier in die Funktion die Sortierung mit eingebaut. Standardmäßig bzw. wenn nicht angegeben, sortiere ich hier nach „Name“.
Zu guter letzt wird eine Anzahl benötigt, wie viele Suchergebnisse zurückgeliefert werden. Was ich hier verwendete ist der Standardwert, den auch die DSMC nimmt. Ich sehe jetzt auch nicht wirklich einen Grund diesen zu ändern.

Im Nachfolgenden Beispiel, definiere ich einen LDAP Filter der mir alle Computerobjekte abholt,  mit den Attributen „Hersteller“, „Initiale MAC Addresse“ und „Computer-System“ (Namen aus der DSMC).

$ldap_filter = „(objectClass=Computer)“
$ldap_att = „Computer.ComputerType,BasicInventory.InitialMACAddress,BasicInventory.Vendor“
$return = Get-DSMObjectList -Filter $ldap_filter -Attributes $ldap_att -Order „Name“

Die Antwort vom Server ist nun eine Liste, die man wie ein Array, mit einer for-Schleife durchlaufen kann.

$return.ObjectList

7. Schlusswort

Soviel erstmal zu Teil 1. Ich hoffe das bringt erstmal ein Grundverständnis für den Webservice Zugriff. Ich denke die größten Hürden sind genommen, und wer nicht auf die nächsten Teile warten möchte, kann natürlich gerne schonmal anfangen selbst neue Funktionen zu entwerfen. Über den Webservice geht vieles am Anfang leider nicht so komfortabel wie über die DSMC. Beispielsweise, wenn man einen Computer in eine Gruppe aufnehmen möchte, funktioniert das leider nur in mindestens 3 Schritten:
1. ID des Computerobjektes herausfinden
2. ID des Gruppenobjektes herausfinden
3. Die 2. ID zum Mitglied der ersten ID machen.
Idealerweise baut man hier noch Abfragen ein, bei der man herausfindet, ob es sich um eine Computer oder Usergruppe handelt, und ob es sich bei dem Objekt um einen User oder Computer handelt. Wie in meiner Warnung am Anfang bereits erwähnt, hat Frontrange diese Prüfung im Webservice leider nicht drin. Ich hatte es getestet, und es geht einen User in eine Computergruppe aufzunehmen, wird dann auch in der DSMC angezeigt. Aber ich möchte an der Stelle nicht weiter testen, was für Auswirkungen es dann auf den Installer etc. hat. Von dem her ist wie bereits gesagt vorsicht geboten.