Kochbuch

In diesem Kapitel sind Skript-Beispiele zusammengestellt, wie durch den Einsatz verschiedener opsi-script Funktionen gewisse Aufgaben, die sich in ähnlicher Weise immer wieder stellen, bewältigt werden können.

Löschen einer Datei in allen Userverzeichnissen

Seit opsi-script Version 4.2 gibt es für diese Aufgabe eine einfache Lösung: Wenn etwa die Datei alt.txt aus allen Userverzeichnissen gelöscht werden soll, so kann der folgende Files-Sektions-Aufruf verwendet werden:

files_delete_Alt /allNtUserProfiles

[files_delete_Alt]
delete "%UserProfileDir%\alt.txt"

Für ältere opsi-script Versionen sei hier noch ein Workaround dokumentiert, der hilfreiche Techniken enthält, die eventuell für andere Zwecke dienen können. Folgende Zutaten werden benötigt:

  • Eine ShellScript-Sektion, in der ein dir-Befehl die Liste aller Verzeichnisnamen produziert.

  • Eine Files-Sektion, die das Löschen der Datei alt.txt in einem bestimmten Verzeichnis anstößt.

  • Eine String-Listen-Verarbeitung, die alles miteinander verknüpft.

Das Ganze kann z.B. so aussehen:

[Actions]

; Variable für den Dateinamen:
DefVar $loeschDatei$
set $loeschDatei$ = "alt.txt"

; Variablendeklaration für die String-Listen
DefStringList list0
DefStringList list1

; Einfangen der vom Dos-dir-Befehl produzierten Zeilen
Set list0 = getOutStreamFromSection ('ShellScript_profiledir')

; Aufruf einer Files-Sektion für jede Zeile
for $x$ in list0 do files_delete_x

; Und hier die beiden benötigten Spezialsektionen:
[ShellScript_profiledir]
dir "%ProfileDir%" /b

[files_delete_x]
delete "%ProfileDir%\$x$\$LoeschDatei$"

Überprüfen, ob ein spezieller Service läuft

Wenn wir überprüfen wollen, ob eine spezieller Service (beispielsweise der "opsiclientd") läuft und ihn, falls er nicht läuft, starten wollen, müssen wir folgendes Skript verwenden.

Um eine Liste der laufenden Services angezeigt zu bekommen, müssen wir das Kommando
net start
in einer ShellScript Sektion starten und das Ergebnis in der $list0$ erfassen. Wir gleichen die Liste ab und iterieren die Elemente, um zu sehen, ob der spezielle Service beinhaltet ist. Wenn er nicht da ist, wird er gestartet.

[Actions]
DefStringList $list0$
DefStringList $list1$
DefStringList $result$
Set $list0$=getOutStreamFromSection('ShellScript_netcall')
Set $list1$=getSublist(2:-3, $list0$)

DefVar $myservice$
DefVar $compareS$
DefVar $splitS$
DefVar $found$
Set $found$ ="false"
set $myservice$ = "opsiclientd"


comment "============================"
comment "search the list"
; for developping loglevel = 7
; setloglevel=7
; in normal use we dont want to log the looping
setloglevel = 5
for %s% in $list1$ do sub_find_myservice
setloglevel=7
comment "============================"

if $found$ = "false"
   set $result$ = getOutStreamFromSection ("ShellScript_start_myservice")
endif


[sub_find_myservice]
set $splitS$ = takeString (1, splitStringOnWhiteSpace("%s%"))
Set $compareS$ = $splitS$ + takeString(1, splitString("%s%", $splitS$))
if $compareS$ = $myservice$
   set $found$ = "true"
endif


[ShellScript_start_myservice]
net start "$myservice$"


[ShellScript_netcall]
@echo off
net start

Installationen im Kontext eines lokalen Benutzers

In manchen Situationen kann es sinnvoll oder notwendig sein, ein opsi-script Skript als lokal eingeloggter Benutzer auszuführen, anstatt wie üblich im Kontext eines Systemdienstes. Beispielsweise kann es sein, dass Softwareinstallationen, die vom opsi-script aus aufgerufen werden, zwingend einen Benutzerkontext benötigen oder dass bestimmte Dienste, die für den Installationsvorgang wichtig sind, erst nach dem Login eines Benutzers zur Verfügung stehen.

MSI-Installationen, die einen lokalen User benötigen, lassen sich häufig (aber leider nicht immer) durch die Option 'ALLUSERS=1' dazu "überreden" auch ohne auszukommen. Beispiel:

[Actions]
DefVar $LOG_LOCATION$
Set $LOG_LOCATION$ = %opsiLogDir% + "\myproduct.log"
winbatch_install_myproduct

[winbatch_install_myproduct]
msiexec "%SCRIPTPATH%\files\myproduct.msi" /qb ALLUSERS=1 /l* $LOG_LOCATION$ /i

opsi-template-with-userlogin

Eine andere, aufwendigere Möglichkeit dieses Problem zu lösen, ist temporär einen lokalen Benutzer anzulegen und diesen zur Installation des Programms zu verwenden. Dazu bieten wir als Vorlage das Produkt opsi-template-with-userlogin an. Dieses löst das bisher verwendete Produkt opsi-template-with-admin ab.

Verwenden Sie immer die aktuellste Version von opsi-template-with-userlogin!

Erzeugen eines angepassten Produktes

Um das Template an Ihre Bedürfnisse anzupassen empfiehlt sich das Erzeugen eines neuen Produktes auf Basis von opsi-template-with-userlogin:

opsi-package-manager -i --new-product-id myproduct opsi-template-with-userlogin_4.x.x.x-x.opsi

Ablauf

Das Produkt durchläuft die folgenden Schritte während der Installation:

  • Sicherung der folgenden Werte:

    • Bisherige Auto Logon Einstellungen.

    • Zuletzt eingeloggter Benutzer.

    • User Account Control Einstellungen.

    • Hostparameter opsiclientd.event_software_on_demand.shutdown_warning_time.

  • Temporäres Setzen des Hostparameters opsiclientd.event_software_on_demand.shutdown_warning_time auf den Wert "0", um unnötige Wartezeit zu vermeiden.

  • Generieren eines nach konfigurierbaren Kriterien zufälligen Passwortes für den opsiSetupUser.

  • Anlegen des lokalen Benutzers opsiSetupUser.

  • Einrichten der Auto Logon Funktion für den Benutzer opsiSetupUser.

  • Erstellen eines Scheduled Tasks für die Installation in der Aufgabenplanung.

  • Je nach Einstellung der Product Property execution_method kopieren der Installationsdateien auf den Client.

  • Neustart des Clients damit die Einstellungen für den Auto Logon in Kraft treten.

  • Automatisches Einloggen des opsiSetupUsers.

  • Ausführen der Installation über den angelegten Scheduled Task. Der Task startet mit einer Minute Verzögerung nach dem Login, damit alle Dienste genügend Zeit haben um zu starten.

  • Abschließendender Reboot des Clients.

  • Aufräumen und Wiederherstellen des ursprünglichen Zustands.

    • Löschen des opsiSetupUsers inklusive Benutzerprofils und Registry Einträgen.

    • Löschen aller verwendeten lokalen Dateien.

    • Wiederherstellen der gesicherten Werte von Auto Logon, zuletzt eingeloggtem Benutzer und der User Account Control.

    • Wiederherstellen des ursprünglichen Werts des Hostparameters opsiclientd.event_software_on_demand.shutdown_warning_time.

Product Properties

Das Verhalten des Produktes kann über die folgenden Product Properties beeinflusst werden:

debug

  • False (Default)

    • Sperrt Keyboard und Maus Eingaben während des Auto Logons des opsiSetupUsers um Benutzerinteraktion zu vermeiden. Das Passwort des opsiSetupUsers wird nicht im Klartext im Logfile angezeigt.

  • True

    • Keyboard und Maus bleiben während des Auto Logons zum Debuggen im Fehlerfall aktiv. Das Passwort des opsiSetupUsers wird im Logfile im Klartext angezeigt.

execution_method

  • event_starter_local_files

    • Während des Auto Logons wird die Installation über die opsiclientd_event_starter_asInvoker.exe getriggert, die den Server kontaktiert und dort ein on_demand Ereignis auslöst.

    • Die Installation wird im Kontext des System Users ausgeführt.

    • Der opsiSetupUser wird ohne Administrator Rechte angelegt.

    • Die Installationsdateien werden lokal auf dem Client gespeichert.

  • event_starter_smb_share

    • Während des Auto Logons wird die Installation über die opsiclientd_event_starter_asInvoker.exe getriggert, die den Server kontaktiert und dort ein on_demand Ereignis auslöst.

    • Die Installation wird im Kontext des System Users ausgeführt.

    • Der opsiSetupUser wird ohne Administrator Rechte angelegt.

    • Die Installationsdateien liegen auf dem opsi_depot Share.

  • local_winst_local_files (Default)

    • Während des Auto Logons erfolgt die Installation über den lokal installierten opsi-script.

    • Die Installation wird im Kontext des opsiSetupUsers ausgeführt.

    • Der opsiSetupUser wird mit Administrator Rechten angelegt.

    • Die Installationsdateien werden lokal auf dem Client gespeichert.

  • Befindet sich ein Client im WAN/VPN Modus (automatische Erkennung) wird diese Product Property ignoriert und die Installation mit folgenden Optionen durchgeführt:

    • Während des Auto Logons erfolgt die Installation über den lokal installierten opsi-script.

    • Die Installation wird im Kontext des opsiSetupUsers ausgeführt.

    • Der opsiSetupUser wird mit Administrator Rechten angelegt.

    • Es werden die lokal im Cache vorhandenen Installationsdateien verwendet.

uninstall_before_install

  • False (Default)

    • Vor der Installation wird keine Deinstallation ggf. bereits installierter Versionen durchgeführt.

  • True

    • Vor der Installation wird geprüft ob die Software auf dem Client bereits vorhanden ist. Falls ja wird diese vor der Installation deinstalliert.

Aufbau des Produktes

Das Produkt gliedert sich in ein Hauptscript, das den Auto Logon und die Installation vorbereitet und das eigentliche Installationsscript, das während des Auto Logons des lokalen Benutzers ausgeführt wird.

Hauptscript

Der besseren Übersicht halber verteilt sich das Hauptscript auf die folgenden Dateien:

  • declarations.opsiinc (Auslagerung der Definition aller Variablen des Hauptscripts)

  • sections.opsiinc (Auslagerung aller Sektionen des Hauptscripts)

  • setup.opsiscript

Die einzigen Änderungen, die am Hauptscript vorgenommen werden müssen sind die Angabe des benötigten freien Speicherplatzes und die Parameter für die Generierung des für den Auto Login benötigten zufälligen Passwortes. Diese werden in der Datei declarations.opsiinc vorgenommen:

; ----------------------------------------------------------------
; - Please edit the following values                             -
; ----------------------------------------------------------------
;Available free disk space required
	Set $ProductSizeMB$ = "1000"

;Number of digits
	Set $RandomStrDigits$ = "3"

;Number of lower case characters
	Set $RandomStrLowerCases$ = "3"

;Minimum lenght of the generated string
	Set $RandomStrMinLength$ = "12"

;Number of special case characters
	Set $RandomStrSpecialChars$ = "3"

;Number of upper case characters
	Set $RandomStrUpperCases$ = "3"
; ----------------------------------------------------------------
Installationsscript

Auch das Installationsscript unterteilt sich der Übersicht halber auf mehrere Dateien:

  • declarations-local.opsiinc (Auslagerung der Definition aller Variablen des Installationsscripts)

  • sections-local.opsiinc (Auslagerung aller Sektionen des Installationsscripts)

  • setup-local.opsiinc

  • delsub-local.opsiinc

  • uninstall-local.opsiscript

Einfügen der Installationsdateien

Öffnen Sie das Verzeichnis des Produktes in Ihrem Depot und legen Sie die Installationsdateien in das Unterverzeichnis localsetup\files. Die Dateien Testfolder1 und Testfile1.txt können bedenkenlos gelöscht werden.

Anpassen der Variablen

Passen Sie die Variablen in der Datei localsetup\declarations-local.opsiinc an Ihre Bedürfnisse an:

; ----------------------------------------------------------------
; - Please edit the following values                             -
; ----------------------------------------------------------------
;The name of the software
	Set $ProductId$ = "opsi-template-with-userlogin"

;The folder that the software installs itself to
	Set $InstallDir$ = "%ProgramFilesSysNativeDir%\" + $ProductId$

;Path to the installed executable
	Set $InstalledExecutable$ = $InstallDir$ + "\" + $ProductId$ + ".exe"

;Name of the license pool to be used
	Set $LicensePool$ = "p_" + $ProductId$

;Does the installation require a license?
	Set $LicenseRequired$ = "false"

;GUID of the installed MSI (Can be found in either HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall or HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall or determined by the opsi-setup-detector)
	Set $MsiId$ = '{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}'

;Name of the uninstaller executable
	Set $Uninstaller$ = $InstallDir$ + "\uninstall.exe"
; ----------------------------------------------------------------
Anpassen der Datei setup-local.opsiinc

Die Installation der Software erfolgt über die Datei setup-local.opsiinc Diese beinhaltet neben dem Handling der Installation und des Lizenzmanagements auskommentierte Beispiele für das Kopieren von Dateien, das Erstellen von Registry Einträgen und das Anlegen von Verknüpfungen. Diese können, je nach Bedarf einkommentiert, gelöscht, oder auskommentiert gelassen werden.

Anpassen der Datei sections-local.opsiinc

Diese Datei beinhaltet alle von der Installation verwendeten Sektionen. In der Sektion [Sub_Check_ExitCode] muss die zum Installations Typ der verwendeten Software passende Funktion zur Auswertung des Exit Codes einkommentiert werden. Es können die Exit Codes für die folgenden Installations Typen ausgewertet werden:

  • Inno Setup

  • InstallShield

  • MSI

  • Nullsoft Scriptable Install System (NSIS)

Der Installations Typ kann über das Tool opsi-setup-detector ermittelt werden.

In diesem Beispiel wurde die Funktion isMsiExitcodeFatal einkommentiert:

[Sub_Check_ExitCode]
Set $ExitCode$ = getlastexitcode
;if stringtobool(isInnoExitcodeFatal($ExitCode$, "true", $ErrorString$ ))
;if stringtobool(isInstallshieldExitcodeFatal($ExitCode$, "true", $ErrorString$ ))
if stringtobool(isMsiExitcodeFatal($ExitCode$, "true", $ErrorString$ ))
;if stringtobool(isNsisExitcodeFatal($ExitCode$, "true", $ErrorString$ ))
  Set $ErrorFlag$ = $ErrorString$
  Registry_Save_Fatal_Flag /32Bit
  ExitWindows /ImmediateReboot
else
  Comment $ErrorString$
endif

Die Sektionen Winbatch_Install und Winbatch_Uninstall enthalten auskommentierte Beispiele für die Installations- bzw. Deinstallationskommados der unterschiedlichen Installations Typen. Hier muss das passende Installations- bzw. Deinstallationskommando für den entsprechenden Installations Typ einkommentiert und angepasst werden.

[Winbatch_Install]
;Choose one of the following examples as basis for your installation
;You can use the variable $LicenseKey$ to pass a license key to the installer

;======== Inno Setup =========
;"%ScriptPath%\localsetup\files\setup.exe" /sp- /silent /norestart

;======== InstallShield =========
;Create an setup.iss answer file by running: setup.exe /r /f1"c:\setup.iss"
;"%ScriptPath%\localsetup\files\setup.exe" /s /sms /f1"%ScriptPath%\localsetup\files\setup.iss" /f2"$LogDir$\$ProductId$.install_log.txt"

;======== MSI package =========
;msiexec /i "%ScriptPath%\localsetup\files\setup.msi" /qb! /l* "$LogDir$\$ProductId$.install_log.txt" ALLUSERS=1 REBOOT=ReallySuppress

;======== Nullsoft Scriptable Install System (NSIS) =========
;"%ScriptPath%\localsetup\files\setup.exe" /S <additional_parameters>

[Winbatch_Uninstall]
;Choose one of the following examples as basis for your uninstallation

;======== Inno Setup =========
;"$Uninstaller$" /silent /norestart

;======== InstallShield =========
;Create an uninstall.iss answer file by running: setup.exe /uninst /r /f1"c:\uninstall.iss"
;"%ScriptPath%\localsetup\files\setup.exe" /uninst /s /f1"%ScriptPath%\localsetup\files\uninstall.iss" /f2"$LogDir$\$ProductId$.uninstall_log.txt"

;======== MSI =========
;msiexec /x $MsiId$ /qb! /l* "$LogDir$\$ProductId$.uninstall_log.txt" REBOOT=ReallySuppress

;======== Nullsoft Scriptable Install System (NSIS) =========
;"$Uninstaller$" /S
Anpassen der Datei delsub-local.opsiinc

Beim Handling der Deinstallation wird entweder nach einer bereits installierten ausführbaren Datei, oder nach einer in der Registry vorhandenen MSI GUID gesucht. Hier muss, je nach Installations Typ die entsprechende Zeile ein- und die ander auskommentiert werden. Im folgenden Beispiel wurde die Zeile für MSI einkommentiert:

Comment "Searching for already installed version"
;if FileExists($InstalledExecutable$)
if NOT(GetRegistryStringValue("[HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" + $MsiId$ + "] DisplayName") = "")
  Comment "Starting the uninstallation"
    Winbatch_Uninstall /SysNative
    Sub_Check_ExitCode

    Comment "License handling"
      if NOT($LicenseRequired$ = "false")
        Comment "Licensing required, free license used"
          Sub_Free_License
      endif

    ;Comment "Deleting files"
    ;	Files_Delete /SysNative

    ;Comment "Deleting registry entries"
    ;	Registry_Delete /SysNative

    ;Comment "Deleting links"
    ;	LinkFolder_Delete
endif

Analog zur Datei setup-local.opsiinc finden sich auch hier neben dem Handling der Deinstallation und des Lizenzmanagements auskommentierte Beispiele für das Löschen von Dateien, Registry Einträgen und Verknüpfungen. Diese können ebenfalls, je nach Bedarf einkommentiert, gelöscht, oder auskommentiert gelassen werden.

Die Deinstallation erfolgt nicht im Kontext eines angemeldeten Benutzers, da dies meist nicht erforderlich ist.

Verhalten im Fehlerfall

Wird das Script angepasst muss dringend darauf geachtet werden dass die Funktion isFatalError in den lokalen Installationsscripten nicht verwendet wird! isFatalError bricht die Ausführung des Scripts sofort ab, was dazu führt dass die Cleanup Phase, in der ggf. die Tastatur und Maus Eingaben wieder aktiviert werden, die vorherigen Werte für z.B. den Auto Logon wiederhergestellt und der lokale opsiSetupUser gelöscht wird nicht ausgeführt wird! Das führt dazu dass der Client sich immer wieder als opsiSetupUser einloggt. Um dies zu unterbinden wird im Falle eines Fehlers die Fehlermeldung in der Variablen $ErrorFlag$ abgelegt, in der Registry gespeichert und der Client per ExitWindows /ImmediateReboot sofort neu gestartet. Das führt dazu dass nach dem Reboot die Cleanup Phase durchlaufen, und dort der Fehler ausgewertet wird.

Set $ErrorFlag$ = "Installation not successful"
Registry_Save_Fatal_Flag /32Bit
ExitWindows /ImmediateReboot

XML-Datei patchen: Setzen des Vorlagenpfades für OpenOffice.org 2.0

Das Setzen des Vorlagenpfades kann mithilfe der folgenden Skriptteile erfolgen:

[Actions]
; ....

DefVar $oooTemplateDirectory$
;--------------------------------------------------------
;set path here:

Set $oooTemplateDirectory$ = "file://server/share/verzeichnis"
;--------------------------------------------------------
;...

DefVar $sofficePath$
Set $sofficePath$= GetRegistryStringValue ("[HKEY_LOCAL_MACHINE\SOFTWARE\OpenOffice.org\OpenOffice.org\2.0] Path")
DefVar $oooDirectory$
Set $oooDirectory$= SubstringBefore ($sofficePath$, "\program\soffice.exe")
DefVar $oooShareDirectory$
Set $oooShareDirectory$ = $oooDirectory$ + "\share"

XMLPatch_paths_xcu $oooShareDirectory$+"\registry\data\org\openoffice\Office\Paths.xcu"
; ...


[XMLPatch_paths_xcu]
OpenNodeSet
- error_when_no_node_existing false
- warning_when_no_node_existing true
- error_when_nodecount_greater_1 false
- warning_when_nodecount_greater_1 true
- create_when_node_not_existing true
- attributes_strict false

documentroot
all_childelements_with:
elementname: "node"
attribute:"oor:name" value="Paths"
all_childelements_with:
elementname: "node"
attribute: "oor:name" value="Template"
all_childelements_with:
elementname: "node"
attribute: "oor:name" value="InternalPaths"
all_childelements_with:
elementname: "node"

end

SetAttribute "oor:name" value="$oooTemplateDirectory$"

XML-Konfiguration für eine MsSql-Anwendung patchen: Ein Beispiel mit irreführend benannten Attributen

Die Ausgangsdatei für den Patch hat z.B: folgende Form, DataSource und InitialCatalog sollen dynamisch gesetzt werden mithilfe der Variablen $source$ und $catalog$.

<?xml version="1.0"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
  </startup>
  <appSettings>
    <add key="Database.DatabaseType" value="MsSqlServer"/>
    <add key="Database.DataSource" value="[db-servername]\[db-instance]"/>
    <add key="Database.InitialCatalog" value="TrustedData"/>
    <add key="ActiveDirectory.Enabled" value="false"/>
    <add key="ActiveDirectory.LdapRoot" value=""/>
  </appSettings>
</configuration>

Dann kann man mit folgender XMLPatch-Sektion arbeiten:

[XMLPatch_db_config]
openNodeSet
	documentroot
	all_childelements_with:
		elementname:"appSettings"
	all_childelements_with:
		elementname:"add"
		attribute: "key" value ="Database.DataSource"
end
SetAttribute "value" value="$source$"

openNodeSet
	documentroot
	all_childelements_with:
		elementname:"appSettings"
	all_childelements_with:
		elementname:"add"
		attribute: "key" value ="Database.InitialCatalog"
end
SetAttribute "value" value="$catalog$"

XML-Datei einlesen mit dem opsi-script

Wie bereits im vorangehenden Kapitel "XML-Datei patchen" beschrieben, lassen sich auch XML-Dateien mit dem opsi-script einlesen. Hier soll nun exemplarisch gezeigt werden, wie man die Werte eines bestimmten Knotens ausliest. Als Quelle dient dazu folgende XML-Datei:

<?xml version="1.0" encoding="utf-16" ?>
<Collector xmlns="http://schemas.microsoft.com/appx/2004/04/Collector" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:schemaLocation="Collector.xsd" UtcDate="04/06/2006 12:28:17" LogId="{693B0A32-76A2-4FA0-979C-611DEE852C2C}"  Version="4.1.3790.1641" >
   <Options>
      <Department></Department>
      <IniPath></IniPath>
      <CustomValues>
      </CustomValues>
   </Options>
   <SystemList>
      <ChassisInfo Vendor="Chassis Manufacture" AssetTag="System Enclosure 0" SerialNumber="EVAL"/>
      <DirectxInfo Major="9" Minor="0"/>
   </SystemList>
   <SoftwareList>
      <Application Name="Windows XP-Hotfix - KB873333" ComponentType="Hotfix" EvidenceId="256" RootDirPath="C:\WINDOWS\$NtUninstallKB873333$\spuninst" OsComponent="true" Vendor="Microsoft Corporation" Crc32="0x4235b909">
         <Evidence>
            <AddRemoveProgram DisplayName="Windows XP-Hotfix - KB873333" CompanyName="Microsoft Corporation" Path="C:\WINDOWS\$NtUninstallKB873333$\spuninst" RegistryPath="HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\KB873333" UninstallString="C:\WINDOWS\$NtUninstallKB873333$\spuninst\spuninst.exe" OsComponent="true" UniqueId="256"/>
         </Evidence>
      </Application>
      <Application Name="Windows XP-Hotfix - KB873339" ComponentType="Hotfix" EvidenceId="257" RootDirPath="C:\WINDOWS\$NtUninstallKB873339$\spuninst" OsComponent="true" Vendor="Microsoft Corporation" Crc32="0x9c550c9c">
         <Evidence>
            <AddRemoveProgram DisplayName="Windows XP-Hotfix - KB873339" CompanyName="Microsoft Corporation" Path="C:\WINDOWS\$NtUninstallKB873339$\spuninst" RegistryPath="HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\KB873339" UninstallString="C:\WINDOWS\$NtUninstallKB873339$\spuninst\spuninst.exe" OsComponent="true" UniqueId="257"/>
         </Evidence>
      </Application>
   </SoftwareList>
</Collector>

Möchte man nur die Elemente und deren Werte aller „Application“-Knoten auslesen, kann man dies mit folgendem Code bewerkstelligen (nur Ausschnitt):

[Actions]
DefStringList $list$

...

set $list$ = getReturnListFromSection ('XMLPatch_findProducts '+$TEMP$+'\test.xml')
for $line$ in $list$ do Sub_doSomething

[XMLPatch_findProducts]
openNodeSet
	; Knoten „Collector“ ist der documentroot
	documentroot
	all_childelements_with:
	  elementname:"SoftwareList"
	all_childelements_with:
	  elementname:"Application"
end
return elements

[Sub_doSomething]
set $escLine$ = EscapeString:$line$
; hier kann man nun diese Elemente in $escLine$ bearbeiten

Hier sieht man auch eine weitere Besonderheit. Es sollte vor dem Benutzen der eingelesenen Zeilen erst ein EscapeString der Zeile erzeugt werden, damit enthaltene Sonderzeichen nicht vom opsi-script interpretiert werden. Die Zeile wird nun gekapselt behandelt, sonst könnten reservierte Zeichen wie $,%,“ oder \' leicht zu unvorhersehbaren Fehlfunktionen führen.

'

Einfügen einer Namensraumdefinition in eine XML-Datei

Die opsi-script XMLPatch-Sektion braucht eine voll ausgewiesenen XML Namensraum (wie es im XML RFC gefordert wird). Aber es gibt XML Konfigurationsdateien, in denen „nahe liegende“ Elemente nicht deklariert werden (und auslesende Programme, die auch davon ausgehen, dass die Konfigurationsdatei entsprechend aussieht).

Besonders das Patchen der meisten XML/XCU Konfigurationsdateien von OpenOffice.org erweist sich als sehr schwierig. Um dieses Problem zu lösen hat A. Pohl (Vielen Dank!) die Funktionen XMLaddNamespace und XMLremoveNamespace entwickelt. Die Funktionsweise ist im folgenden Beispiel demonstriert:

DefVar $XMLFile$
DefVar $XMLElement$
DefVar $XMLNameSpace$
set $XMLFile$ = "D:\Entwicklung\OPSI\winst\Common.xcu3"
set $XMLElement$ = 'oor:component-data'
set $XMLNameSpace$ = 'xmlns:xml="http://www.w3.org/XML/1998/namespace"'

if XMLAddNamespace($XMLFile$,$XMLElement$, $XMLNameSpace$)
  set $NSMustRemove$="1"
endif
;
; now the XML Patch should work
; (commented out since not integrated in this example)
;
; XMLPatch_Common $XMLFile$
;
; when finished we rebuild the original format
if $NSMustRemove$="1"
  if not (XMLRemoveNamespace($XMLFile$,$XMLElement$,$XMLNameSpace$))
    LogError "XML-Datei konnte nicht korrekt wiederhergestellt werden"
    isFatalError
  endif
endif

Es ist zu beachten, dass die XML Datei so formatiert wird, dass der Element-Tag-Bereich keine Zeilenumbrüche enthält.

Herausfinden, ob ein Skript im Kontext eines bestimmten Events läuft

Der opsiclientd bestimmt und weiß, welches Event gerade aktiv ist. opsi-script kann sich mittels eines opsiservicecall mit dem opsiclientd verbinden und Events abfragen:

[actions]
setLogLevel=5
DefVar $queryEvent$
DefVar $result$

;==================================
set $queryEvent$ = "gui_startup"

set serviceInfo = getReturnListFromSection('opsiservicecall_event_on_demand_is_running /opsiclientd')
set $result$ = takestring(0, serviceInfo)
if $result$ = "true"
	comment "event " + $queryEvent$ + " is running"
else
	comment "NOT running event " + $queryEvent$
endif

;==================================
set $queryEvent$ = "on_demand"

set serviceInfo = getReturnListFromSection('opsiservicecall_event_on_demand_is_running /opsiclientd')
set $result$ = takestring(0, serviceInfo)
if $result$ = "true"
	comment "event " + $queryEvent$ + " is running"
else
	comment "NOT running event " + $queryEvent$
endif

;==================================
set $queryEvent$ = "on_demand{user_logged_in}"

set serviceInfo = getReturnListFromSection('opsiservicecall_event_on_demand_is_running /opsiclientd')
set $result$ = takestring(0, serviceInfo)
if $result$ = "true"
	comment "event " + $queryEvent$ + " is running"
else
	comment "NOT running event " + $queryEvent$
endif