Dr. Watson und seine Erben

Werbung
Dr. Watson und seine Erben
Release 1
STE
26.07.2016
Inhaltsverzeichnis
1
.
.
.
.
.
1
2
3
4
4
5
2
WER goes Datenbank
2.1 Ausgangssituation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2 Refaktoring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
7
8
3
WER darstellen
3.1 Ausgangssituation . . . . . . .
3.2 Grafische Mockups/WireFrames
3.3 Umsetzung in PrimalForms . .
3.4 PS und DB-Anbindung . . . . .
3.5 Exportieren nach Excel . . . . .
4
5
Vorgeschichte
1.1 Error Report Dateien *.wer . . . . . . . . . . . . . .
1.2 Konfiguration von Windows Error Reporting (WER) .
1.3 Windows Error Reporting (WER) Tools . . . . . . . .
1.4 Windows PowerShell Error Reporting (WER) Cmdlets
1.5 Logfiles anstatt *.wer Dateien nutzen . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
15
15
16
17
20
23
Implementierung
4.1 Runde 1 . . . . . . . . . . . . . . . . . .
4.2 Runde 2 . . . . . . . . . . . . . . . . . .
4.3 Runde 3 . . . . . . . . . . . . . . . . . .
4.4 Runde 4 - Optimierung . . . . . . . . . .
4.5 Runde 5 - Ändern von Daten in tbl_report
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
25
26
29
31
34
38
Grafische Auswertung von EventLogs
5.1 Ausgangssituation . . . . . . . .
5.2 Ist-Analyse . . . . . . . . . . . .
5.3 Soll-Analyse . . . . . . . . . . .
5.4 Umsetzung . . . . . . . . . . . .
5.5 Fazit . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
39
39
40
40
41
41
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6
Aufgabe
43
7
Glossar
47
i
ii
KAPITEL 1
Vorgeschichte
Bis Windows XP und Server2003 war Dr. Watson das Programm zur Crash-Analyse. Ab Windows Vista ist nun
Windows Error Reporting (WER) für das Hardware und Software Problemreporting, an die Stelle von Dr. Watson
getreten.
Hier muss man aber beachten, das Programme die mit dem .NET Framework entwickelt worden sind meist keine
Error-Reports erzeugen!
Windows Error Reporting (WER) ist für 2 Aufgaben zuständig. Die Fehlerberichtsfunktion ermöglicht es Anwendungsfehler, Kernel-Fehler, nicht mehr reagierende Anwendungen und andere anwendungsspezifische Problemberichte über einen Service an Microsoft zu senden.
Problematik von WER-Report
notes_on_wer
Werden die Problemberichte an Microsoft gesendet, dann wird in der Datenbank bei Microsoft nach Lösungen für
das aufgetretene Problem gesucht und es werden Lösungen vorschlagen, wenn welche gefunden wurden. Das Senden
dieser Berichte kann seitens des Systemadministrators verhindert werden.
Version=1
EventType=APPCRASH
EventTime=130467955473684710
ReportType=2
Consent=1
UploadTime=130467955480403784
ReportIdentifier=4a222852-efdd-11e3-8281-78dd08b4da52
IntegratorReportIdentifier=4a222851-efdd-11e3-8281-78dd08b4da52
WOW64=1
NsAppName=daVinci.exe
Response.BucketId=4f5befa6735ab77e0d40a74ab08834b6
Response.BucketTable=1
Response.LegacyBucketId=73313869235
Response.type=4
Sig[0].Name=Anwendungsname
Sig[0].Value=daVinci.exe
Sig[1].Name=Anwendungsversion
Sig[1].Value=6.0.2.79
....
....
Die Probleme werden anhand einer so genannten Bucket-ID Identifiziert. Die Bucket-ID (oder auch fault Bucket ID)
wird aus der Version des Programms, das abgestürzt ist und anderen technischen Informationen erzeugt. Dies schafft
1
Dr. Watson und seine Erben, Release 1
eine einzigartige Signatur für den Absturz. Die Bucket-ID erscheint in den Details des Windows-Crash-Dialogs und
den Ereignisprotokollen. Diese Bucket-ID ist wichtig für Microsoft um das Problem eindeutig erkennen zu können.
1.1 Error Report Dateien *.wer
Windows Error Reporting (WER) sammelt auch die Reportdateien mit der Endung “.wer”, an verschiedenen Orten auf
dem Computer.
# Per user archived Windows Error Reports
$env:USERPROFILE\AppData\Local\Microsoft\Windows\WER\ReportArchive
# Per user queued Windows Error Reports
$env:USERPROFILE\AppData\Local\Microsoft\Windows\WER\ReportQueue
# System archived Windows Error Reports
$env:ALLUSERSPROFILE\Microsoft\Windows\WER\ReportArchive
# System queued Windows Error Reports
$env:ALLUSERSPROFILE\Microsoft\Windows\WER\ReportQueue
Möchte man alle WER Reports erfassen, so muss man in jedem User Profil auf dem Rechner nach den *.wer Dateien
suchen und alle genannten Ordner “abklappern”.
# liste mit allen Windows Error Reporting User-Pfaden dieses Rechner erstellen
$WerReportPfade = Get-ChildItem $env:SystemDrive\users | Where-Object `
{$_.PSIsContainer} | ForEach-Object { $Path = $_.Fullname
# Testen ob der richtige Unter-Pfad im User Ordner vorhanden ist
If(Test-Path (Join-Path -Path $Path -ChildPath 'AppData\Local\Microsoft\Windows\WER\ReportArchive') )
{
# Pfad ausgeben
(Join-Path -Path $Path -ChildPath 'AppData\Local\Microsoft\Windows\WER\ReportArchive')
}
# Testen ob der richtige Unter-Pfad im User Ordner vorhanden ist
2
Kapitel 1. Vorgeschichte
Dr. Watson und seine Erben, Release 1
If(Test-Path (Join-Path -Path $Path -ChildPath `
'AppData\Local\Microsoft\Windows\WER\ReportQueue') )
{
# Pfad ausgeben
(Join-Path -Path $Path -ChildPath 'AppData\Local\Microsoft\Windows\WER\ReportQueue')
}
}
# Das System hat noch 2 Windows Error Reporting andere Pfade wo .wer Dateien liegen
# diese werden zur liste zugefügt
# System archived Windows Error Reports
$WerReportPfade += "$env:ALLUSERSPROFILE\Microsoft\Windows\WER\ReportArchive"
# System queued Windows Error Reports
$WerReportPfade += "$env:ALLUSERSPROFILE\Microsoft\Windows\WER\ReportQueue"
# leeres Array erstellen in dem die Dateipfade gesammelt werden
$WerDateien = [System.Collections.ArrayList]@()
# nun holen wir aus allen Ordnern alle .wer Dateien
ForEach($Path in $WerReportPfade)
{
$WerDateien.AddRange((Get-ChildItem -Path $Path -Filter '*.wer' `
-Recurse | Select-Object -ExpandProperty Fullname))
}
# jeden einzelnen Windows Error Reporting Bericht mit Windows PoweShell Anzeigen
ForEach($WerDatei in $WerDateien)
{
# Pfad anzeigen:
$WerDatei
#inhalt der datei anzeigen
Get-Content -Path $WerDatei
# Trennlinie
"---------------------------------------------"
}
1.2 Konfiguration von Windows Error Reporting (WER)
Windows Error Reporting (WER) kann auf verschiedene Weisen konfiguriert werden. So kann man z.B. Abschalten
das Problemberichte an Microsoft deaktivieren oder die Orte ändern, an denen die *.wer Dateien abgelegt werden.
Einen guten Artikel hat Herr Sommergut von WindowsPro verfasst: Problembericht senden: Windows Error Reporting
konfigurieren oder deaktivieren http://www.windowspro.de/wolfgang-sommergut/problembericht-senden-windowsfehlerberichterstattung-konfigurieren-deaktivieren
Herr Sommergut beschreibt auch das man die WER Reportdateien über Gruppenrichtlinien so konfigurieren kann, das
Fehlerberichte an einen internen Server anstatt an Microsoft gesendet werden. Damit wird eine Funktion des Corporate
Windows Error Reporting (CER) genutzt. Man kann damit ein NTFS-Share oder ein SSL-Verbindung nutzen, um die
Reports an den eigenen Server zu senden.
Weitere Dokumentation, wie man das Windows Error Reporting (WER) mit Group Policies oder Registry Schlüsseln
einstellen kann, findet man in der Microsoft Dokumentation im Developer Center:
Windows Error Reporting (WER) http://msdn.microsoft.com/en-us/library/windows/desktop/bb513641%28v=vs.85%29.aspx
1.2. Konfiguration von Windows Error Reporting (WER)
3
Dr. Watson und seine Erben, Release 1
Collecting User-Mode Dumps
http://msdn.microsoft.com/en-us/library/windows/desktop/bb787181%28v=vs.85%29.aspx
Den Speicherort von den *.wer Dateien stellt man z.B. mit den folgenden Schlüsseln in der Windows Registry um:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\DumpFolder
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\DumpType
1.3 Windows Error Reporting (WER) Tools
NirView
http://www.nirsoft.net/utils/app_crash_view.html
notes_on_wer
Ein gutes Tool zum Verarbeiten von den Windows Error Reporting Dateien, hat Nir Sofer hergestellt. Dieses Tool
nennt sich AppCrashView und kann auch mit der Windows PowerShell eingesetzt werden. Da es die *.wer Dateien in
CSV oder XML umwandeln kann und von der Kommandozeile aus, gesteuert werden kann.
1.4 Windows PowerShell Error Reporting (WER) Cmdlets
In Windows 8 und Server 2012 R2 gibt es nun auch 3 sehr bescheidene Windows PowerShell Cmdlets um das Reporting an oder abzuschalten.
4
Kapitel 1. Vorgeschichte
Dr. Watson und seine Erben, Release 1
Windows Error Reporting
us/library/jj590827.aspx
(WER)
Cmdlets
in
Windows
PowerShell
http://technet.microsoft.com/en-
• Disable-WindowsErrorReporting schaltet das Error Reporting an Microsoft ab.
• Enable-WindowsErrorReporting schaltet das Error Reporting an Microsoft wieder ein.
• Get-WindowsErrorReporting zeigt an ob das Errorreporting ein oder ausgeschaltet ist.
1.5 Logfiles anstatt *.wer Dateien nutzen
Zentrale Verwaltng von Errors
http://www.faq-o-matic.net/2011/03/28/eventlog-monitoring-via-smtp-zentral-verwaltet-und-kostenfrei/
http://www.msxfaq.de/tools/logparser.htm
Die *.wer Dateien zu sammeln und mit der Windows PowerShell zu analysieren, ist aber eigentlich der falsche Ansatz.
Immer wenn ein Programm crasht, dann wird das in den Ereignisprotokollen(Eventlog) von Windows verzeichnet.
Windows PowerShell kann hervorragend mit den Windows Ereignisprotokollen (Eventlog) von Windows umgehen,
da es für diese Aufgabe spezielle Cmdlets hat.
Ein abgestürzte Applikation, die einen Eintrag im Applikations-Ereignisprotokoll mit der ID 1000 und der ReportQuelle “Application Error” hinterlässt, könnte mit folgenden Befehl abgefragt werden.
Get-EventLog -LogName Application -InstanceId 1000 -Source 'Application Error'
Das Reporting selbst hinterlässt auch Einträge im Applikations-Ereignisprotokoll mit der ID 1001 und der ReportQuelle ” Windows Error Reporting”.
Get-EventLog -LogName Application -InstanceId 1001 -Source 'Windows Error Reporting'
Einen schönen Artikel darüber hat Aaron Paul Rykhus geschrieben: Finding useful crash data and Windows Error Reporting (WER) http://blogs.technet.com/b/arykhus/archive/2008/12/11/finding-useful-crash-data-and-windows-errorreporting-wer.aspx
1.5. Logfiles anstatt *.wer Dateien nutzen
5
Dr. Watson und seine Erben, Release 1
6
Kapitel 1. Vorgeschichte
KAPITEL 2
WER goes Datenbank
2.1 Ausgangssituation
Bisher existierten in Ihrer Firma Powershell-Skripte, die eine Ausgabe verschiedenster Informationen der WERReports über die Konsole möglich machen. Diese Informationen beziehen sich aber nur auf den jeweiligen Einzel-PC.
Eine Aggregation von Informationen über alle Computer ist nicht möglich. Der Leiter der IT-Abteilung beschließt
daher, ihr Skript des letzten Jahres als Ausgangspunkt für eine Erweiterung zu nehmen. Die Daten sollen nun in einer
Mysql-Datenbank gespeichert werden.
Zur Information erhalten Sie vom IT-Leiter noch das folgende PS-Skript
Aufgabe
Analysieren Sie das dargestellte Skript werreports_10.ps1. Notieren Sie sich Bemerkungen.
7
Dr. Watson und seine Erben, Release 1
2.2 Refaktoring
Die oben dargestellten Überlegungen führen zu Veränderungen des Quellcodes sowie zu einem ER-Modell, welche
die Datenhaltung widerspiegeln könnte.
Aufgabe
Erstellen Sie ein ER-Modell bzw. Klassendiagramm.
2.2.1 Umsetzung MySQL-Datenbank
Nachdem die Daten nun in einer objektorientierten Struktur vorliegen, müsen diese nun anschließend in Datenbanktabellen gespeichert werden. Das zuvor erstellte SQL-Skript erzeugt eine leere Datenbankstruktur.
Aufgabe
Diskutieren Sie nochmals die Vor- und Nachteile des oben dargestellten Datenmodells
figure/klasse_11/datamodell_1.png
8
Kapitel 2. WER goes Datenbank
Dr. Watson und seine Erben, Release 1
2.2.2 Klassen
Da die Daten in Annäherung an das Datenmodell gehalten werden sollen, bietet es sich an, die Zsammenhänge mit
Hilfe von Klassen zu modellieren.
Die many-Beziehung in der Klasse User zu den Reports wird mit Hilfe einer ArrayList abgebildet. Die Klasse Computer erhält einen Konstruktor, der beim Erzeugen eines Objektes gleich die Werte für den Computer holt. Dabei wird die
bisher vorhandene Methode getOtherData/SetComputer wiederverwendet. Auch die Klasse Report erhält über einen
Konstruktor die erforderlichen Daten. Die Klasse User erzeugt sich im Konstruktor einen neuen Computer und weist
ihn der Referenz ps zu.
class User
{
[string]$Name
$Reports = [System.Collections.ArrayList]::New()
[Computer]$pc
User([string]$uname)
{
$this.Name = $uname
$this.pc = [Computer]::new()
}
}
class Report
{
[string]$ReportID
[int]$ReportType
[string]$EventType
[string]$EventTime
[string]$BucketID
[string]$Appname
Report($repid, $repType, $evType, $evTime, $buckId, $appnam)
{
$this.ReportID = $repid
$this.ReportType = $repType
$this.EventType = $evTime
$this.EventTime = $evTime
$this.BucketID = $buckId
$this.Appname = $appnam
}
}
class Computer
{
[string]$mac
[string]$OpSys
[string]$Name
Computer()
{
$macadresse = get-wmiobject -class "Win32_NetworkAdapterConfiguration" | Where {$_.IpEnabled -Ma
$this.mac = $macadresse[0].MacAddress
$this.OpSys = (Get-WmiObject Win32_OperatingSystem).Name
$this.Name = (Get-WmiObject Win32_OperatingSystem).CSName
}
2.2. Refaktoring
9
Dr. Watson und seine Erben, Release 1
}
2.2.3 Anpassungen bestehender Funktionen
Die Funktionen GetReportInnerData sowie GetWERPath müssen nicht geändert werden.
GetUsers
Die Funktion muss nun User-Objekte liefern. Sie erwartet eine ArrayList, welche sie mit USer-Objekten füllt.
function GetUsers($Benutzer)
{
<#
.Synopsis
Erstellt aus dem Ordnernamen unterhalb von C:\Users eigenständige User-Objekte und weist Sie einer ArrayList zu.
#>
$tempUser = Get-ChildItem "C:\Users" | Select-Object Name
foreach($username in $tempUser)
{
[User]$u = [User]::New($username.Name)
$Benutzer.Add($u)
}
}
GetReportData
Neben der verwendeten ArrayList für das Halten der User-Objekte wird die Zuweisung der Reports durch die Verwendung der Report-Klasse ersetzt. Am Ende hat man eine komplettes Objektmodell
#Wegen Problemen mit Rückgabe von ArrayListen wird eine eigene erzeugt und der aufgerufenen Funktion
function GetReportData($Benutzer)
{
GetUsers $Benutzer
foreach($_user in $Benutzer)
{
$paths = GetWERPath $_user.Name;
foreach($_path in $paths) # $_users hier stehen alle benuter vom Rechner und werden n
{
if($_path -ne $null)
{
$_reportid = GetReportInnerData $_path "ReportIdentifier";
$_reporttype = GetReportInnerData $_path "ReportType";
$_eventtype = GetReportInnerData $_path "EventType"; # z.B AppCrash
$_eventtime = GetReportInnerData $_path "EventTime";
$_bucketid = GetReportInnerData $_path "Response.BucketId";
$_appname = GetReportInnerData $_path "AppName";
[Report]$rep = [Report]::new($_reportid, $_reporttype, $_eventtype, $
$_user.Reports += $rep
}
10
Kapitel 2. WER goes Datenbank
Dr. Watson und seine Erben, Release 1
}
}
return $Benutzer
}
2.2.4 Erweiterungen um datenbankspezifische Funktionen
Aus Vereinfachungsgründen werden die Datenbank-Funktionen mit in das Skript geschrieben. Es wäre besser, diese
in eine eigene Datei auszulagern und diese in das Hauptskript mit einzubinden.
Zunächst wird die dll referenziert und Objekte wie Connection und Command erzeugt. Die Funktionen open() und
close() dienen zum Kapseln der Open() und Vlose()-Methode des Connection-Objektes.
Die Funktion saveData erhält die ArrayList aller Benutzerobjekte. Sie öffnet einmalig die Connection und geht dann in
einer Schleife alle Userobjkete durch. Mit Hilfe von setUser wird dann zunächst eine USer in die Datenbank angelegt
(siehe später mehr) und anschließend die dazugehörigen Report-Objekte gespeichert.
Die setUser-Funktion operiert mit dem replace-Befehl von mysql. Dieser stellt sicher, dass bereits bestehende UserDatensätze nicht neu hinzugefügt werden, sondern lediglich Änderungen umgesetzt werden (replace = delete und
insert).
setReports erhält ein User-Objekt übergeben und geht mit Hilfe einer Schleife die Reports-Liste des User durch durch.
Sie erzeugt jeweils ein Insert-Statement. Das sie Zugriff auf die User-Eigenschaften hat, kann sie die MAC-Adresse
des Computers sowie den Usernamen mit in das Statement aufnehmen.
Die setComputer-Funktion funktioniert analog zur setUser-Funktion.
#mysql_model.ps1
#Datenbankverbindung öffnen und Datenübertragung vorbereiten
#Bibliothek zur Datenbankeinbindung einbinden
[void][system.reflection.Assembly]::LoadFrom("C:\Program Files (x86)\MySQL\MySQL Connector Net
#Skriptweite Variablen deklarieren
#Connectionvariable
$connstring = "Server=localhost;Uid=watson;Pwd='watson';Database=watson_11FI3"
$con = New-Object Mysql.Data.MysqlClient.MysqlConnection;
#Commandobjekt
$command = New-Object MySql.Data.MySqlClient.MySqlCommand;
function open()
{
#Funktion zum Öffnen der Datenbankverbindung
try
{
$script:con.ConnectionString = $connstring;
$con.Open();
Write-Debug "Datenbankverbindung geöffnet"
}
catch
{
Write-Debug "Achtung Fehler: $_.ExceptionMessage"
Write-Debug "Daten müssen später übertragen werden"
}
}
function close()
2.2. Refaktoring
11
Dr. Watson und seine Erben, Release 1
{
#Funktion zum Schliessen der Datenbankverbindung
try
{
$con.Close();
Write-Debug "Datenbankverbindung geschlossen"
}
catch
{
Write-Debug "Achtung Fehler: $_.ExceptionMessage"
Write-Debug "Daten müssen später übertragen werden"
}
}
function saveData($Users)
{
open
foreach($u in $Users)
{
write-debug $u.Name
setUser $u
setReports $u
}
close
}
function setUser($user)
{
$name = $user.Name
[string]$sql = "replace into User(Anmeldename) values ('$name');"
$command.CommandText = $sql
$command.Connection = $con;
$command.ExecuteNonQuery()
}
function setReports($u)
{
$m = $u.pc.mac
$uname = $u.Name
foreach($rep in $u.Reports)
{
$repid = $rep.ReportID
$repType = $rep.ReportType
$evTime = $rep.EventTime
$evType = $rep.EventType
$buckID = $rep.BucketID
$app = $rep.Appname
[string]$sql = "insert into report(ReportID, Appname, EventTime, BucketID, ReportType, User, Co
$sql += "values('$repid', '$app', '$evTime', '$buckID', '$repType', '$uname', '$m');"
$command.CommandText = $sql
12
Kapitel 2. WER goes Datenbank
Dr. Watson und seine Erben, Release 1
$command.Connection = $con;
$command.ExecuteNonQuery()
}
}
function setComputer([Computer]$c)
{
$m = $c.mac
$sys = $c.OpSys
$n = $c.Name
[string]$sql = "replace into computer(MAC, OSName, HostName) values ('$m', '$sys','$n')
#[string]$sql = "Replace into computer values ($c.'mac', $c.'OpSys',$c.'Name');"
$command.CommandText = $sql
$command.Connection = $con;
open
$command.ExecuteNonQuery()
close
}
2.2.5 Ablaufsteuerung
Aufgrund der objektorientierten Struktur der Daten ergibt sich eine logische Abfolge des Einfügens der Daten.
Die User-Objekte enthalten nicht nur die Daten des ausgelesenen Benutzers, sondern referenzieren den jeweiligen
Computer sowie alle Report-Objekte, die der jeweilige User auf dem Rechner hatte. Aufgrund der Foreign-KeyBeziehungen zwischen den Tabellen muss sichergestellt werden, dass neue User und Computer zunächst als Erstes
in die DB geschrieben werden. Um sich aufwendiges Abfragen zu sparen (ist der DS schon vorhanden ?), wird beim
User und Computer ein replace-Staement verwendet.
Danach können die Report-Datensätze mit den Informationen geschrieben werden. Dieser Vorgang muss für jedes
User-Objekt in der Users-Liste durchgeführt werden. Da davon auszugehen ist, dass das Skript immer nur auf einem
Computer läuft, müssen die Computerdaten nur einmal geschrieben werden.
Folgende Abbildung veranschaulicht den Prozess des Einfügens.
2.2. Refaktoring
13
Dr. Watson und seine Erben, Release 1
Zum Ende des Skriptes erfolgt deshalb der Aufruf der Hauptfunktionen. Zunächst holt man sich aller User mit zugehörigen Reports. Anschließend speichert/ersetzt man einmalig den Datensatz in der Tabelle Computer. Zuletzt speichert
saveData() alle User sowie die dazugehörigen Reports.
$Benutzer = [System.Collections.ArrayList]::New()
GetReportData $Benutzer
# untenstehende Routine könnte auch in der setUSer-Methode für jeden User ausgeführt werden
# da das Skript auf nur auf einem Rechner läuft, kann man sich die wiederholten
# Statements aber sparen
setComputer ($Benutzer[0]).pc
# und jetzt der ganze Rest
saveData $Benutzer
14
Kapitel 2. WER goes Datenbank
KAPITEL 3
WER darstellen
3.1 Ausgangssituation
In ihrer Firma existieren Skripte, die Windows Error Reports sammeln und in einer MySQL-Datenbank abspeichern.
Nachdem mit dem Speichern der Daten der Sammelvorgang abgeschlossen ist, wird nun vom IT-Leiter der Wunsch
geäußert, diese Daten in einer einfachen Form abfragen und nach bestimmten Kriterien auswerten zu können. Da
dieses Projekt keine hohe Priorität in der Firma hat, können keine Programmierer mit dieser Aufgabe betraut werden.
Sie erhalten deshalb vom IT-Leiter die Aufgabe, für diese Auswertung eine einfache grafische Lösung zu programmieren.
Zur Information erhalten Sie vom IT-Leiter noch das folgende Datenmodell.
Aufgabe
Analysieren Sie das folgende Datenmodell
15
Dr. Watson und seine Erben, Release 1
Aufgabe
Überlegen Sie sich mit Hilfe eines Mockups, wie diese “einfache grafische Lösung” aussehen könnte. gehen Sie
dabei von folgender Fragestellung aus: Was könnte der IT-Leiter mit dieser Anwendung vorhaben; wie könnte er sie
benutzen ?
3.2 Grafische Mockups/WireFrames
Mit “Mockup” wird die Technik beschrieben, mit einfachen Hilfsmitteln eine Vorstellung von einer GUI-Anwendung
zu erhalten. Es können damit Bedienkonzepte dargestellt und diskutiert werden.
Ein bekanntes und für unsere Zwecke ausreichendes Tool ist das Programm PENCIL, welches Sie unter
16
Kapitel 3. WER darstellen
Dr. Watson und seine Erben, Release 1
http://pencil.evolus.vn/ downloaden können.
Eine damit erzeugte GUI könnte wie folgt aussehen:
GUI
GUI
3.3 Umsetzung in PrimalForms
PrimalForms Community Edition ist ein Werkzeug, mit dem man grafische Oberflächen (WinForm) für die Powershell
zusammenklicken kann. Neben den grafischen Elementen und deren Eigenschaften können auch die auf diese Komponenten wirkenden Ereignisse (z.B. Click) definiert werden. PrimalForms generiert daraus Quellcode, der dann in
der Powershell ausgeführt werden kann.
3.3. Umsetzung in PrimalForms
17
Dr. Watson und seine Erben, Release 1
Mit Hilfe des Buttons ExportToPowershell kann der das Formular beschreibende Quellcode als Powershell-Skript
gespeichert werden.
Download
Quellcode
#Generated Form Function
function GenerateForm {
########################################################################
# Code Generated By: SAPIEN Technologies PrimalForms (Community Edition) v1.0.10.0
# Generated On: 26.11.2014 21:24
# Generated By: Karl
########################################################################
#region Import the Assemblies
[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null
[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null
#endregion
#region Generated Form Objects
$form1 = New-Object System.Windows.Forms.Form
$lblAusgabe = New-Object System.Windows.Forms.Label
$txtNameEingabe = New-Object System.Windows.Forms.TextBox
$Hello = New-Object System.Windows.Forms.Button
$InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState
#endregion Generated Form Objects
#---------------------------------------------#Generated Event Script Blocks
#---------------------------------------------#Provide Custom Code for events specified in PrimalForms.
$Hello_OnClick=
{
#TODO: Place custom script here
}
$OnLoadForm_StateCorrection=
{#Correct the initial state of the form to prevent the .Net maximized form issue
$form1.WindowState = $InitialFormWindowState
}
18
Kapitel 3. WER darstellen
Dr. Watson und seine Erben, Release 1
#---------------------------------------------#region Generated Form Code
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 328
$System_Drawing_Size.Width = 395
$form1.ClientSize = $System_Drawing_Size
$form1.DataBindings.DefaultDataSourceUpdateMode = 0
$form1.Name = "form1"
$form1.Text = "Primal Form"
$lblAusgabe.DataBindings.DefaultDataSourceUpdateMode = 0
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 199
$System_Drawing_Point.Y = 120
$lblAusgabe.Location = $System_Drawing_Point
$lblAusgabe.Name = "lblAusgabe"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 24
$System_Drawing_Size.Width = 137
$lblAusgabe.Size = $System_Drawing_Size
$lblAusgabe.TabIndex = 2
$lblAusgabe.Text = "label1"
$form1.Controls.Add($lblAusgabe)
$txtNameEingabe.DataBindings.DefaultDataSourceUpdateMode = 0
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 43
$System_Drawing_Point.Y = 125
$txtNameEingabe.Location = $System_Drawing_Point
$txtNameEingabe.Name = "txtNameEingabe"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 20
$System_Drawing_Size.Width = 109
$txtNameEingabe.Size = $System_Drawing_Size
$txtNameEingabe.TabIndex = 1
$form1.Controls.Add($txtNameEingabe)
$Hello.DataBindings.DefaultDataSourceUpdateMode = 0
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 99
$System_Drawing_Point.Y = 178
$Hello.Location = $System_Drawing_Point
$Hello.Name = "Hello"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 64
$System_Drawing_Size.Width = 173
$Hello.Size = $System_Drawing_Size
$Hello.TabIndex = 0
$Hello.Text = "btnHello"
$Hello.UseVisualStyleBackColor = $True
$Hello.add_Click($Hello_OnClick)
$form1.Controls.Add($Hello)
3.3. Umsetzung in PrimalForms
19
Dr. Watson und seine Erben, Release 1
#endregion Generated Form Code
#Save the initial state of the form
$InitialFormWindowState = $form1.WindowState
#Init the OnLoad event to correct the initial state of the form
$form1.add_Load($OnLoadForm_StateCorrection)
#Show the Form
$form1.ShowDialog()| Out-Null
} #End Function
#Call the Function
GenerateForm
3.4 PS und DB-Anbindung
Lediglich für den SQL-Server bietet Microsoft eine Sammlung von Commandlets zum Umgang mit dem DatenbankServer an. Bei anderen Datenbank-Systemen kann man aber mit Hilfe des .NET-Frameworks und der von den Herstellern angebotenen Datenbanktreibern eine Zusammenarbeit bewerkstelligen.
Dazu sind aber grundlegende Verständnisse des Datenbankzugriffes unter .NET notwendig.
3.4.1 ADO .NET
ADO .NET stellt das grundlegende Denkmodell von Microsoft zum Datenbankzugriff dar. Es besteht aus einer ganzen
Reihe von durch Objekten gekapselten Zugriffsverfahren, die durch die jeweiligen Datenbank-Treiber (Provider) zur
Verfügung gestellt werden. Die wichtigsten werden kurz vorgestellt.
20
Kapitel 3. WER darstellen
Dr. Watson und seine Erben, Release 1
figure/kd_ado_net_modell.jpg
Damit werden für den lesenden und schreibenden Zugriff auf die Daten einer Datenbank unterschiedliche Objketmodelle seitens von .NET verwendet.
3.4.2 Herstellen einer Verbindung
Link
http://vwiki.co.uk/MySQL_and_PowerShell
In jedem Falle ist ein Verbindungsaufbau zum Datenbankserver notwendig. Es müssen zumindest Username/Passwort,
Rechnername des Datenbankservers sowie der Datenbankname übergeben werden.
Function con_db ()
{
[void][system.reflection.Assembly]::LoadFrom("C:\Program Files (x86)\MySQL\MySQL Connector Ne
$connstring = "Server=localhost;Uid=root;Pwd='XXXXX';Database=XXXXXX";
$con = New-Object Mysql.Data.MysqlClient.MysqlConnection;
$con.ConnectionString = $connstring;
return $con;
}
Die Verbindungsdaten werden in .NET in einem Objekt gespeichert; dieses kann dann von anderen Teilen des Skriptes
verwendet werden.
3.4. PS und DB-Anbindung
21
Dr. Watson und seine Erben, Release 1
3.4.3 Auslesen von Daten
Der SELECT-Befehl kann mit Hilfe verschiedener Objekte abgewickelt werden. Dies sind DataReader bzw. DataTables.
DataReader
function mysql_select($query)
{
$cmd = New-Object MySql.Data.MySqlClient.MySqlCommand;
$cmd.CommandText = $query
$results = $cmd.ExecuteReader()
$cmd.Dispose()
while ($results.Read()) {
//Zugriff über die Spalten eines Datensatzes
for ($i= 0; $i -lt $results.FieldCount; $i++) {
write-output $results.GetValue($i).ToString()
}
//So würde es auch gehen
write-output %results["feld1"]
write-output $results["feld2"]
}
$select_string = "SELECT feld1, feld2 FROM table;"
mysql_select $select_string
Problematische Elemente des DataReaders sind:
• Der Reader kann nur über eine Schleife durchlaufen werden; die Daten müssen deshalb zur weiteren Verwendung irgendwie gespeichert werden.
• der Zugriff auf die Feldwerte erfolgt innerhalb der Schleife über einen Indexwert, was nicht besonders komfortabel ist
DataSet
function Execute-MySQLQuery([string]$query, $con) {
# Create SQL command
$cmd = New-Object MySql.Data.MySqlClient.MySqlCommand($query, $con)
# Create data adapter from query command
$dataAdapter = New-Object MySql.Data.MySqlClient.MySqlDataAdapter($cmd)
# Create dataset
$dataSet = New-Object System.Data.DataSet
# Fill dataset from data adapter, with name "data"
$dataAdapter.Fill($dataSet, "data")
$cmd.Dispose()
# Returns an array of results
return $dataSet.Tables["data"]
}
$query = "SELECT feld1, feld2 FROM table;"
$result = Execute-MySQLQuery $query $con
22
Kapitel 3. WER darstellen
Dr. Watson und seine Erben, Release 1
Write-Host ("Found " + $result.rows.count + " rows...")
$result | Format-Table
ExecuteScalar
Falls nur die Rückgabe eines einzigen Wertes erwartet wird (count, avg, etc.) kann auch mit der ExecuteScalar gearbeitet werden.
function Execute-MySQLScalar($query, $con)
{
# Create command object
$cmd = $con.CreateCommand()
# Load query into object
$cmd.CommandText = $query
# Execute command
$cmd.ExecuteScalar()
}
Execute-MSQLScalar "$select count(*) from table;" $con
3.4.4 Einfügen/Ändern/Löschen von Daten
Zum Ändern/Löschen und Einfügen von Daten muss die ExecuteNonQuery()-Methode des Command-Objektes verwendet werden.
Function Executebefehl ($String)
{
$con = con_db;
$con.Open();
$command = New-Object MySql.Data.MySqlClient.MySqlCommand;
$command.CommandText = $String;
#command muss die connection kennen
$command.Connection = $con;
#ExecuteNonQuery führt den Befehl auf der DB aus und gibt das
$command.ExecuteNonQuery();
$con.Close();
}
$SQLstring = "insert into server (BS, SerialNr, CPU_Temp, CPU_Workload, UpTime, HDUsage, Process_Coun
#$SQLstring;
Executebefehl $SQLstring;
3.5 Exportieren nach Excel
Zum Export der jeweiligen Inhalte des DataGrids kann recht elegant ein bekanntes Konzept der Powershell verwendet
werden, nämlich das der Pipeline.
3.5. Exportieren nach Excel
23
Dr. Watson und seine Erben, Release 1
Während ein DataGrid-Objekt zunächst einmal nicht einfach nach Excel exportiert werden kann, ist es der darunter
liegenden DataTable des DataSet-Objektes durchaus möglich, seine Inhalte in eine csv-Datei zu speichern.
Der entsprechende Code in der control.ps1 ist dann nur noch ein Einzeiler:
function exportToExcel
{
$DataSet.Tables[0] | export-csv c:\temp\flam_stein\file.csv -notypeinformation -UseCulture
}
24
Kapitel 3. WER darstellen
KAPITEL 4
Implementierung
Aufgrund der sich steteig ändernden GUI ist es nicht ratsam, den GUI-Quellcode von Primalforms mit der eigenen
Logik zu verbinden. Die verschiedenen Bestandteile des Skriptes sollten in eigenen Dateien (.ps1) gespeichert werden,
um sie vor Überschreiben zu schützen und eine Wiederverwendbarkeit zu erreichen.
Für unser Beispiel ist zunächst folgende Vorgehensweise sinnvoll.
• View.ps1:
Handelt die GUI sowie den Aufrufhandler für die eigenen Funktionen
• Model.ps1:
Regelt alle Zugriffe auf die Datenbank
• Control.ps1:
Vermittelt zwischen dem Quellcode von View.ps1 und Model.ps1 Implementiert die notwendige Verarbeitungslogik
Mit dieser Dreiteilung wird ein klassischs Muster der Programmierung umgesetzt, das sog. MVC-Muster. Prinzipiell geht es darum, die unterschiedlichen Aufgabenbereiche eines Programmes zu unterscheiden und damit auch
austauschbar zu machen.
======>
View.ps1
======>
Control.ps1
<======
Model.ps1
<======
25
Dr. Watson und seine Erben, Release 1
4.1 Runde 1
26
Kapitel 4. Implementierung
Dr. Watson und seine Erben, Release 1
In der ersten Runde geht es darum, eine Verbindung mit dem Datenbankserver herzustellen und das DataGrid des
Hauptfensters mit den Daten der Tabelle tblReports zu füllen.
4.1.1 View
Beim Starten des Formulars soll im DataGrid eine Liste der letzten 20 Einträge gezeigt werden. Das von PF generiete
Skript muss im Form_Load-Ereignis einen Datenbankabruf vornehmen. Der Eventhandler des Formulars ruft deshalb
im Controler eine entsprechende Funktion get_reports auf, die die Daten aus der Datenbank holt (r1_model.ps1). Der
Controler bindet anschließend die Ergebnisse an das DataGrid der View.
...
$handler_form1_Load=
{
Write-Debug "in Handler_form_load"
getReports
...
4.1.2 Controler
Der Controler leitet den Aufruf der View an das Model weiter und sorgt anschließend für das Anzeigen der Daten im
DataGrid des View-Skriptes. Dazu wird dessen Fähigkeit genutzt, sich per DataSource-Eigenschaften an die “Tabellen” des DataSets zu binden
. .\r1_model.ps1
function getReports
{
getReportModel
updateReport
}
....
function updateReport()
{
$dataGridWER.DataSource = $null;
$dataGridWER.DataSource = $DataSet.Tables[0];
$dataGridWER.DataBind
$form1.refresh()
}
4.1.3 Model
Im Model werden alle ausgeführten Datenbankzugriffe implementiert. Die entsprechend notwendigen Objekte wie
Connection, Command, DataSet und DataAdapter werden erzeugt und mit den daten gefüllt. Zum Füllen des DataGrids wird mit Hilfe eines DataAdapters ein DataSet erstellt. Dies stellt sozusagen eine “virtuelle” Tabelle im Speicher
dar und kann von vielen Steuerlementen des .NET-Frameworks genutzt werden. Funktionalitäten wie das Öffnen und
Schließen von Datenbankverbindungen werden ausgelagert, da sie in Zukunft noch häufiger genutzt werden.
#Erste Einrichtung der Datenbankverbindung
#$connstring = "Server=10.161.8.17;Uid='root';Pwd='steinam';Database=dr_watson";
$connstring = "Server=localhost;Uid='root';Pwd='patricia1234';Database=drwatson";
4.1. Runde 1
27
Dr. Watson und seine Erben, Release 1
$con = New-Object Mysql.Data.MysqlClient.MysqlConnection;
$con.ConnectionString = $connstring;
$DataSet = New-Object System.Data.DataSet
############################################################################
#Neu: Dataadapter muss bereits hier erzeugt werden, da bei Änderungen etc. auf diesen zurückgegriffen
#muss, damit das dann später benutzte Commandobjekt darauf zugreifen kann.
#Siehe: file:///C:/Fp/OpenBooks/C%20Sharp/Visual%20C%20Sharp%202012/1997_35_001.html#dodtp44f9b504-b6
$SqlAdapter = New-Object MySql.Data.MySqlClient.MySqlDataAdapter
#Verbindung öffnen
function verbindungOeffnen()
{
$con.Open();
Write-Debug "Datenbankverbindung geöffnet"
}
#Verbindung schließen
function verbindungschliessen()
{
$con.Close();
Write-Debug "Datenbankverbindung geschlossen"
}
#Computerdaten besorgen
function getReportModel()
{
#Verbindung öffnen
verbindungOeffnen
#SQL-Statement eingeben
$SqlQuery = "select * from tbl_report"
#Commandobjekt anlegen und Connectionobjekt sowie Abfrage zuordnen
$SqlCmd = New-Object MySql.Data.MySqlClient.MySqlCommand;
$SqlCmd.CommandText = $SqlQuery
$SqlCmd.Connection = $con
#Datenadapter instantiieren und Commandreferenz zuweisen
$SqlAdapter.SelectCommand = $SqlCmd
#Dataset leeren, sonst werden die alten Daten noch angezeigt
$DataSet.Reset()
#Dataset instantiieren und füllen lassen
$SqlAdapter.Fill($DataSet)
#Verbindung schließen
verbindungSchliessen
}
28
Kapitel 4. Implementierung
Dr. Watson und seine Erben, Release 1
4.2 Runde 2
Nach dem Füllen des DataGrids sollen die ComboBoxen zur Auswahl der User und Rechner mit den Werten aus den
entsprechenden Tabellen gefüllt werden.
4.2.1 View
Da das Füllen der ComboBoxen bereits beim Start erfolgen soll, muss die Form_Load-Handlermethode des Views um
entsprechende Aufrufe von Contrer-Funktionen ergänzt werden.
$handler_form1_Load=
{
Write-Debug "in Handler_form_load"
getReports
#neuer Code
getCboUser
getCboHosts
}
4.2.2 Controler
Der Controler ruft eine Methode des Models auf. Seine Daten erhält er von dieser Methode in Form eines Arrays.
Diese werden über die updateCboUser-Funktion in die Items-Liste der ComboBox übertragen.
function getCboUser()
{
# Aufruf einer Funktion des Models
$UserList = getUserModel
updateCboUser $UserList
4.2. Runde 2
29
Dr. Watson und seine Erben, Release 1
}
function updateCbouser($list)
{
foreach($user in $list)
{
$cboUser.Items.Add($user)
}
}
4.2.3 Model
Im Model werden die Daten mit Hilfe eines SQL-Statements und einem DataReader aus der entsprechenden Tabelle
gelesen. Die Daten des Readers werden in ein Array kopiert und an die aufrufende Funktion des Controlers zurückgegeben. Diese Vorgehensweise wird für das Füllen beider ComboBoxen angewendet. Anstellen des Umwandelns in
einen Array hätte man auch den DataReader direkt zum Controler zurückgeben können.
function getUserModel
{
$Userlist = @();
verbindungOeffnen
#SQL-Statement eingeben
$SqlQuery = "select Anmeldename from tbl_user;"
#Commandobjekt anlegen und Connectionobjekt sowie Abfrage zuordnen
$SqlCmd = New-Object MySql.Data.MySqlClient.MySqlCommand;
$SqlCmd.CommandText = $SqlQuery
$SqlCmd.Connection = $con
#Datenadaptar instantiieren und Commandreferenz zuweisen
$reader = $SqlCmd.ExecuteReader();
while($reader.Read())
{
Write-Host $reader["Anmeldename"]
#$Userlist.Add($reader["Anmeldename"] = $reader["Anmeldename"])
$Userlist += $reader["Anmeldename"]
}
verbindungschliessen
return $Userlist
}
30
Kapitel 4. Implementierung
Dr. Watson und seine Erben, Release 1
4.3 Runde 3
Im DataGrid sollen nun nach der Auswahl der jeweiligen ComboBox die Daten angezeigt werden.
Da hierzu grundsätzlich keine neuen Features hinzukommen, müsste dies leicht zu implementieren sein.
4.3.1 View
Im GUI-Teil sind die On_Cklick()-Handler der beiden Buttons zu bearbeiten. Der Wert des markierten Eintrags der
ComboBoxen ist an den Controler weiterzugeben.
#r3_view.ps1
$btnSelectUser_OnClick=
{
#TODO: Code for dataGrid for selected User
getReportsForUser $cboUser.SelectedItem
}
...
$btnSelectRechner_OnClick=
{
getReportsForRechner $cboRechner.SelectedItem
}
4.3.2 Controler
Der Controler nimmt den Wert der ComboBoxen entgegen und reicht ihn an die Model-Funktionen weiter. Da diesmal
wieder mit DataSets gearbeitet wird, sorgt er zum Schluss noch für das Updaten des DataGrids
4.3. Runde 3
31
Dr. Watson und seine Erben, Release 1
#r3_control.ps1
function getReportsForUser($selectedUser)
{
getReportsForUserModel $selectedUser
updatereport
}
function getReportsForRechner($selectedRechner)
{
getReportsForRechnerModel $selectedRechner
updatereport
}
4.3.3 Model
Im Model werden die Daten mit Hilfe einer SQL-Query geholt, in ein DataSet verpackt und an das Controler-Skript
zur weiteren Verarbeitung zurückgegeben.
Beim SQL-Statement ist darauf zu achten, dass für die Ausgabe der Rechner ein INNER JOIN-Statement zu wählen
ist, da die Tabellen tbl_reports und tbl_computer über die MAC-Adressen verknüpft sind - die Auswahl der ComboBox
bezieht sich allerdings auf den Hostnamen.
Die Zuordnung des übergebenen Rechner- bzw. Usernamens in den SQL-String erfolgt mit Hilfe der ‘” “’ Schreibweise
#r3_model.ps1
function getReportsForUserModel($user)
{
verbindungOeffnen
$SqlQuery = "select * from tbl_report where Anmeldename = '" + $user +"';"
#Commandobjekt anlegen und Connectionobjekt sowie Abfrage zuordnen
$SqlCmd = New-Object MySql.Data.MySqlClient.MySqlCommand;
$SqlCmd.CommandText = $SqlQuery
$SqlCmd.Connection = $con
#Datenadapter instantiieren und Commandreferenz zuweisen
$SqlAdapter.SelectCommand = $SqlCmd
#Dataset leeren, sonst werden die alten Daten noch angezeigt
$DataSet.Reset()
#Dataset instantiieren und füllen lassen
$SqlAdapter.Fill($DataSet)
verbindungschliessen
}
function getReportsForRechnerModel($Rechner)
{
verbindungOeffnen
#Ein equi join gibt falsche Ergebnisse
#$SqlQuery = "select r.ReportID, r.Appname, r.Eventtime,
#r.Anmeldename, r.MAC, r.ReportType "
#$SqlQuery += "from tbl_report r,tbl_computer c "
#$SqlQuery += "where r.MAC = c.MAC and c.Hostname = '" + $Rechner +"';"
32
Kapitel 4. Implementierung
Dr. Watson und seine Erben, Release 1
#der inner join machts richtig
$SqlQuery ="select r.ReportID, r.Appname, r.Eventtime, r.Anmeldename, r.MAC, r.ReportType "
$SqlQuery += "from tbl_report r inner join tbl_computer c "
$SqlQuery += "on r.MAC = c.MAC "
$SqlQuery += "where c.Hostname = '" + $Rechner +"';"
#Commandobjekt anlegen und Connectionobjekt sowie Abfrage zuordnen
$SqlCmd = New-Object MySql.Data.MySqlClient.MySqlCommand;
$SqlCmd.CommandText = $SqlQuery
$SqlCmd.Connection = $con
#Datenadapter instantiieren und Commandreferenz zuweisen
$SqlAdapter.SelectCommand = $SqlCmd
#Dataset leeren, sonst werden die alten Daten noch angezeigt
$DataSet.Reset()
#Dataset instantiieren und füllen lassen
$SqlAdapter.Fill($DataSet)
verbindungschliessen
}
4.3. Runde 3
33
Dr. Watson und seine Erben, Release 1
4.4 Runde 4 - Optimierung
Nachdem das Skript lauffähig ist, wird in einer Zwischenrunde zunächst redundanter Code refaktorisiert.
Unter Refaktorisierung versteht man das Umgestaltung von Quellcode zur Vermeidung von redundanten Code-Teilen
bzw. generell eine Verbesserung der Code-Strukturen im Hinblick auf die Erweiterung von Skripten.
4.4.1 Model
Im Model-Skript fallen sofort die Funktionen zum Befüllen der KomboBoxen sowie die Funktionen zum Befüllen des
DataGrids auf.
Beide unterscheiden sich eigentlich nur in der Formulierung des SQL-Statements sowie in der Art des Parameters.
Refaktorisierung der KomboBoxen
In den beiden Funktionen zum Holen der Daten für die KomboBoxen User und Rechner ist die gleiche Logik implementiert.
Durch Parametrisierung der Funktion und Einbau einer Fallabfrage kann die Aufgabe mit Hilfe einer einzigen Funktion
erledigt werden.
#r4_model.ps1
function getCboModel ($comboBox)
{
$SqlQuery = ""
$FeldName = ""
switch($comboBox)
{
"User" {
#SQL-Statement zum Befüllen der ComboBox cboUser
$SqlQuery = "select Anmeldename from tbl_user;"
$Feldname = "Anmeldename";
break
}
34
Kapitel 4. Implementierung
Dr. Watson und seine Erben, Release 1
"Rechner" {
#SQL-Staement zum Befüllen der ComboBox
$SqlQuery = "select Hostname from tbl_computer;"
$Feldname = "Hostname";
break
}
}
$list = @();
verbindungOeffnen
#Commandobjekt anlegen und Connectionobjekt sowie Abfrage zuordnen
$SqlCmd = New-Object MySql.Data.MySqlClient.MySqlCommand;
$SqlCmd.CommandText = $SqlQuery
$SqlCmd.Connection = $con
#Datenadaptar instantiieren und Commandreferenz zuweisen
$reader = $SqlCmd.ExecuteReader();
while($reader.Read())
{
$list += $reader[$Feldname]
}
verbindungschliessen
return $list
}
Das Befüllen des DataGrids wird zur Zeit durch 3 verschiedene Methoden ausgeführt
• getReportModel()
• getReportsForUserModel($user)
• getReportsForRechnerModel($Rechner)
Diese Methoden sind gleich ! Lediglich der verwendete SQL-String unterscheidet sich. Man kann deswegen lediglich eine dieser 3 Methoden (getReportModel()) weiterverwenden; der jeweils notwendige SQL-String wird in den
Controler-Methoden generiert und an die Methode übergeben(getReportModel($SqlQuery).
r4_model.ps1
function getReportModel($SqlQuery)
{
#Verbindung öffnen
verbindungOeffnen
#Commandobjekt anlegen und Connectionobjekt sowie Abfrage zuordnen
$SqlCmd = New-Object MySql.Data.MySqlClient.MySqlCommand;
$SqlCmd.CommandText = $SqlQuery
$SqlCmd.Connection = $con
#Datenadapter instantiieren und Commandreferenz zuweisen
$SqlAdapter.SelectCommand = $SqlCmd
#Dataset leeren, sonst werden die alten Daten noch angezeigt
$DataSet.Reset()
#Dataset instantiieren und füllen lassen
$SqlAdapter.Fill($DataSet)
4.4. Runde 4 - Optimierung
35
Dr. Watson und seine Erben, Release 1
#Verbindung schließen
verbindungSchliessen
}
4.4.2 Control
Die beiden Funktionen getCbouser sowie getCboHost könnten in einer Funktion zusammengefasst werden, wenn man
den jeweiligen ComboBox-typ als Parameter übergibt.
r4_control.ps1
....
function getCbo($type)
{
$list = getcboModel $type
if($type -eq "Rechner")
{
updateCboHost $list
}
else
{
updateCboUser $List
}
}
Für den Aufruf zum Befüllen des DataGrids im Model werden die SQL-Statements aus den Model in die bereits
bestehenden Funktionen des Controlers ausgelagert.
#r4_control.ps1
function getReports
{
getReportModel "select * from tbl_report"
updateReport
}
function getReportsForUser($selectedUser)
{
$SqlQuery = "select * from tbl_report where Anmeldename = '" + $selectedUser +"';"
getReportModel $SqlQuery
updatereport
}
function getReportsForRechner($selectedRechner)
{
$SqlQuery
$SqlQuery
$SqlQuery
$SqlQuery
="select r.ReportID, r.Appname, r.Eventtime, r.Anmeldename, r.MAC, r.ReportType "
+= "from tbl_report r inner join tbl_computer c "
+= "on r.MAC = c.MAC "
+= "where c.Hostname = '" + $selectedRechner +"';"
getReportModel $sqlQuery
updatereport
36
Kapitel 4. Implementierung
Dr. Watson und seine Erben, Release 1
}
4.4.3 View
Der Aufruf der *getCbo.. * - Methoden in der View müssen wie folgt geschrieben werden.
r4_view.ps1
.....
$handler_form1_Load=
{
getReports #Erstes Befüllen des DataGrids
#getCboUser
#getCboHosts
getcbo "Rechner" #Aufruf der refaktorisierten Methode und Übergabe
getcbo "User"
#ds Parameters für die jeweils zu füllende KomboBox
.....
4.4.4 Fazit
Durch die Refaktorisierung wurde der Quellcode im Controler etwas mehr; es konnten jedoch einige Funktionen
eingespart werden.
Im Model wurden erhebliche Zeilen Quellcode eingespart, welches zur besseren Verständlichkeit des Quellcode beiträgt.
4.4. Runde 4 - Optimierung
37
Dr. Watson und seine Erben, Release 1
4.5 Runde 5 - Ändern von Daten in tbl_report
Die Verwendung von Objekten des .NET-Frameworks erhöhen zwar den Komplexitätsgrad des Skriptes; sie machen
aber manche Aufgaben realtiv einfach, wie beispielsweise das Ändern von Einträgen im DataGrid und das Abspeichern
dieser Änderungen in der Datenbank.
Durch die Verwendung des DataAdapter-Konzeptes zum Befüllen des DataGrids, stellt .NET quasi eine logische Tabelle im Arbeitsspeicher zur Verfügung. Der Adapter hat damit das Wissen um den Aufbau der Tabelle und kann dieses
Wissen auch zum Ändern und Löschen von Einträgen in dieser Tabelle verwenden. Das verwendete DataAdapterObjekt muss dazu einem sog. CommandBuilder übergeben werden, der die ‘lästige’ Arbeit des Formulierens von SQL
INSERT-, UPDATE und DELETE-Anweisungen für den DataAdapter übernimmt.
function updateDatenbankModel()
{
verbindungOeffnen
$commandbuilder = new-object MySql.Data.MySqlClient.MySqlCommandBuilder $SqlAdapter
$SqlAdapter.UpdateCommand = $commandbuilder.GetUpdateCommand()
$SqlAdapter.InsertCommand = $commandbuilder.GetInsertCommand()
$SqlAdapter.DeleteCommand = $commandbuilder.GetDeleteCommand()
$null = $SqlAdapter.Update($DataSet)
verbindungSchliessen
}
38
Kapitel 4. Implementierung
KAPITEL 5
Grafische Auswertung von EventLogs
5.1 Ausgangssituation
Fehler werden nicht nur mit Hilfe von WER-Reports abgebildet, sondern sie werden normalerweise in das sog. Eventlog eines Rechners geschrieben. Sie sind nach verschiedenen Kategorien sortiert und können mit Hilfe der Ereignisanzeige kontrolliert werden.
39
Dr. Watson und seine Erben, Release 1
5.2 Ist-Analyse
Die grafische Oberfläche der Ereignisanzeige lässt vielfältige Ansichten zu; eine grafische Darstellung über größere
Zeiträume ist allerdings nicht möglich. Hier setzt jetzt der Wunsch des IT-Leiter ein, der gerne wissen möchte, wie
häufig ein bestimmter Fehler über einen bestimmten Zeitraum erfolgt.
Ein erstes Mockup (Prototyp) der grafischen Benutzeroberfläche könnte er sich wie folgt vorstellen.
Im linken Teil der GUI wählt man die gewünschte EventID, den Log-Typ und den Zeitraum aus. Mit dem Klick
auf den Button Continue soll im Eventlog überprüft werden, ob für den entsprechenden Event Daten vorhanden sind.
Während in einer Detailsicht die Textinformationen angezeit werden, soll in einer grafischen Visualisierung die Anzahl
der Ereignisse pro Tag als Liniendiagramm abgebildet werden.
5.3 Soll-Analyse
Bei der Realisierung der Vorgaben stellen sich schnell einige kritische Punkte heraus.
• Wie erstellt man mit Hilfe der Powershell eine grafische Oberfläche
• Wie erzeugt man die grafischen Daten und integriert sie in die GUI
• Wie gewinnt man die Daten aus der grafischen Oberfläche heraus
40
Kapitel 5. Grafische Auswertung von EventLogs
Dr. Watson und seine Erben, Release 1
5.3.1 GUI-Oberfläche
Powershell ist in der Lage, die GUI-Frameworks von .NET anzusprechen. Ein recht leichtes GUT-Framework ist die
WinForm-Library
5.3.2 Grafische Darstellung
5.3.3 Datengewinnung und Übergabe
5.4 Umsetzung
5.4.1 GUI
5.4.2 Logik
5.4.3 Excel
5.4.4 Grafische Ausgabe
5.5 Fazit
5.4. Umsetzung
41
Dr. Watson und seine Erben, Release 1
42
Kapitel 5. Grafische Auswertung von EventLogs
KAPITEL 6
Aufgabe
Sie planen ein Verwaltungstool für die UserAnlage innerhalb ihres ActiveDirectory. Die bisherige Vorgehensweise ist
wie folgt:
• Es wird eine csv-datei produziert, die von weiteren Poweshell-Skripten abgearbeitet wird. Aus dieser csv-Datei
werden dann die entsprechenden Container im AD erzeugt
• Das Editieren der csv-Datei war bisher recht aufwändig und fehleranfällig. Sie planen deshalb, die Inhalte der
csv-Datei in einer Datenbank zu speichern und Änderungen/Anpassungen der Werte mit Hilfe einer GUI nur
innerhalb der Datenbank vorzunehmen.
• Zuletzt wird aus den Datensätzen der Tabelle wiederum eine csv-Datei erzeugt.
• Die Datenbank mit der Tabelle data ist bereits vorhanden.
• Der Systembetreuer hat mit Ihnen bereits ein Konzept einer GUI erarbeitet.
43
Dr. Watson und seine Erben, Release 1
Folgende Idee steckt hinter dieser GUI.
44
Kapitel 6. Aufgabe
Dr. Watson und seine Erben, Release 1
Datei
GUI
Aufgabe
Eine GUI-Beschreibung mit Hilfe von PrimalForms liegt vor, wenngleich aber noch nicht alle Ereignishandler definiert
wurden. Benutzen sie diese Datei als Ausgangspunkt für ihr Programm.
45
Dr. Watson und seine Erben, Release 1
46
Kapitel 6. Aufgabe
KAPITEL 7
Glossar
Command Repräsentiert im .NET-Framework den SQL-Befehl, der auf eine Datenbank abgegeben wird. Er braucht
dazu noch mindestens ein Connection-Objekt
$cmd = New-Object MySql.Data.MySqlClient.MySqlCommand;
$cmd.CommandText = $query
$results = $cmd.ExecuteReader()
Connection Objekt des .NET-Frameworks, welches die Verbindungsinformationen zu einer Datenbank aufnimmt.
Er hat häufig eine genau definierte Syntax. Dazu gehören Username/Passwort/Hostname/Datenbankname/Port
$connstring = "Server=localhost;Uid=root;Pwd='XXXXX';Database=XXXXXX";
$con = New-Object Mysql.Data.MysqlClient.MysqlConnection;
$con.ConnectionString = $connstring;
DataReader .NET-Klasse, die Daten eines Command-Objektes aufnimmt. Sie muss über eine Schleife in eine andere
Datenstruktur, meist ein Array, übertragen werden, das sie nur forward-only zu lesen ist.
PrimalForms Desktop-Programm der Firma Sapien, welches auf einfache Weise GUI-Oberfl#ächen (WinForm) für
Powershell erstellt. Leider nicht mehr erhältlich.
Provider .NET-Bezeichnung für einen spezifischen Datenbanktreiber eines Herstellers
Refaktorisierung Umwandlung bestehenden Quellcodes zur - besseren Verständlichkeit - besseren Weiterverwendbarkeit
47
Dr. Watson und seine Erben, Release 1
48
Kapitel 7. Glossar
Stichwortverzeichnis
C
Command, 47
Connection, 47
D
DataReader, 47
P
PrimalForms, 47
Provider, 47
R
Refaktorisierung, 47
49
Herunterladen