Powershell Beispiele
Es ist sicher nichts neues, dass die Powershell ein wichtiges Werkzeug des Exchange 2007 Administrators ist. Aber auch außerhalb von Exchange ist die Powershell so nützlich, dass ich heute auf die "DOS-Box" Verzichte und gleich die Powershell nutzen. Auch wenn es nicht um Exchange geht. Um die Einsatzbereiche hier besser deutlich zu machen, versuche ich auf dieser Seite all die Tipps und Codeschnipsel zu sammeln, die ich selbst auch immer wieder einsetze. Es sind keine komplette Programme, sondern Bausteine und Vorlagen.
WSV301 Windows PowerShell for Beginners
http://www.msteched.com/2010/Europe/WSV301
Mastering Powershell
http://powershell.com/Mastering-PowerShell.pdf
Layman’s guide to PowerShell 2.0 remoting
http://www.ravichaganti.com/blog/?p=1780
Befehle und mehrere Zeilen
Wenn man einen Befehl in mehreren Zeilen aufteilen will, weil er dann besser "lesbar" ist, dann muss man den BackTick (`) als Zeilenendezeichen verwenden, welches aber durch ein Leerzeichen abgetrennt sein muss. In VBScript wurde dazu der Underscore "_" am Zeilenende genutzt.
Es dient der Übersichtlichkeit, wenn man das reguläre Ende einer Powershell Zeile mit einem Semikolon (;) abschließt. Die "Pascal-Programmierer" von früher werden sich hier wieder erinnern
Powershell und Dokumentation
Selbst die kleinsten Programmschnipsel und Skripte sollten ein Mindestmaß an Dokumentation enthalten. In Powershell können einzelne Zeilen einfach durch die Raute (#) auskommentiert werden.
# Das ist eine einzelne Dokumentationszeile
Es gibt eigentlich keine Entschuldigung, wenn ein Entwickler die Dokumentation einspart außer dass er ein schlechter Entwickler ist. Seit der Powershell 2 gibt es sogar eine Struktur, mit der Sie selbst ihre Skripte dokumentieren können, so dass die Powershell diese Hilfe auch verwenden kann:
<#
.SYNOPSIS
Retrieves service pack and operating system information from one or more remote computers.
.DESCRIPTION
The Get-Inventory function uses Windows Management Instrumentation (WMI) ...
.PARAMETER
computerNameAccepts a single computer name or an array of computer names
.PARAMETER path
The path and file name of a text file. Any computers that cannot be reached will be logged to this file.
.EXAMPLE
Read computer names from Active Directory and retrieve their inventory information.
Get-ADComputer -filter * | Select{Name="computerName";Expression={$_.Name}} | Get-Inventory
.EXAMPLER
ead computer names from a file (one name per line) and retrieve their inventory information
Get-Content c:\names.txt | Get-Inventory
.NOTES
You need to run this function as a member of the Domain Admins group.
#>
Nur schreiben müssen Sie die Dokumentation und den Code natürlich noch selbst
- WTFM: Das Handbuch gehört dazu
http://technet.microsoft.com/de-de/magazine/ff458353.aspx
Powershell und Debugging
Ehe ich in die tiefen einer Funktionen und Tricks gehe, sollten Sie sich wirklich überlegen eine "Plicht zur Definition" für Variablen vorzugeben.
set-psdebug -strict
Damit bekommen Sie eine Fehlermeldung, wenn Sie eine Variable benutzen wollen, die noch nie belegt worden ist. Zudem kann man an verschiedenen Stellen mit den drei "Write"- befehlen weitere Infos ausgeben, die einfach per Default abschaltbar sind.
write-host "Hallo ich starte"
write-warning "Ich bin eine Warnung"
write-error "Das ist ein Error"
write-debug "Das ist eine Debugmeldung 1"
$DebugPreference = "Continue"
write-debug "Das ist eine Debugmeldung 2"
$DebugPreference = "SilentlyContinue"
write-debug "Das ist eine Debugmeldung 3"
write-host "Hallo, ich bin fertig"
So sieht die Ausgabe dann aus.
Die "write-error"-Meldung beendet das Skript aber nicht.
Wenn Sie bei der Ausgabe eine Zeile immer wieder überschreiben lassen wollten (z. B. als Statusanzeige), dann helfen ihnen Sonderzeichen und die Option "-nonewline"
while ($true) {
write-host "`r " (Get-Date -Format HHMMss) -NoNewline
Start-Sleep 1
}
Weitere Sonderzeichen sind:
`0 Null `a Alert `b Backspace `f Form feed `n New line `r Carriage return `t Horizontal tab `v Vertical tab
- about_Special_Character
http://technet.microsoft.com/en-us/library/dd347558.aspx
Powershell und Breakpoints
Ich erinnere mich noch gut an das "Debugging" unter DOS. In einem Batch hat man einfach an der gewünschten Stelle ein "COMMAND:EXE" eingefügt und damit das Skript unterbrochen und konnte in der Shell bestimmte Dinge nachschauen, z.B. Variablen. Mit einem EXIT wurde dieser Child-Prozess verlassen und das eigentliche Skript konnte weiter arbeiten. Powershell bietet diese Funktion auch aber auf einem viel höheren Niveau.
Hier sind es "Breakpoints"
# Hilfe zum PS Debugger anzeigen get-help about_debuggers # Verfügbaren Commandlets anzeigen get-help *psbreak* Name Category Synopsis ---- -------- -------- Set-PSBreakpoint Cmdlet Legt einen Haltepunkt für eine Z... Get-PSBreakpoint Cmdlet Ruft die Haltepunkte ab, die in ... Remove-PSBreakpoint Cmdlet Löscht Haltepunkte aus der aktue... Enable-PSBreakpoint Cmdlet Aktiviert die Haltepunkte in der... Disable-PSBreakpoint Cmdlet Deaktiviert die Haltepunkte in d... # PSDEbug aktivieren set-psdebug
Allerdings dürften die meisten Powershell und Exchange Einsteiger mit diesen Funktionen anfangs überfordert sein. Vielleicht ist die ISE (Integrated Script Environment) mit dem dort eingebauten Debugger oder ein Programm wie PowerGUI für den Anfang sogar besser geeignet, um die ersten Gehversuche mit Powershell bei der Fehlersuche zu unterstützen.
- Introducing the Windows
PowerShell ISE
http://technet.microsoft.com/en-us/library/dd315244.aspx
Powershell und "Debug"-Schnittstelle
Windows hat intern eine "Debug-Schnittstelle", in welche Programme ihre Ausgaben einstellen können und andere Programme können sich für den Empfang dieser Nachrichten registrieren. Wenn kein Programm lauscht, dann verwirft Windows die Meldungen und es kostet kaum Ressourcen. Ein gutes Programm zum Anzeigen ist z.B. DebugView von Sysinternals. Und es ist aus Powershell denkbar einfach, eine Meldung an diese Schnittstelle zu überstellen.
[System.Diagnostics.Debug]::WriteLine("Nachricht","Katgorie")


Diese Schnittstelle bietet sich also für viele Detailausgaben, die im Fehlerfall oder zur Analyse herangezogen werden können aber ansonsten nicht besonders beachtet werden müssen. Das ist insbesondere interessant, wenn mehrere Prozesse im Hintergrund laufen.
Eine weitere Option ist die Aktivierung eines Trace mit.
Set-PSDebug -trace 0
Spielen Sie einfach mal damit herum, welches Hilfsmitteln ihnen besser hilft. Ich habe mir mittlerweile eine eigene Funktion für das Debugging gebaut, der ich die Meldung und einen Schweregrad übergebe, die dann optional einen Bildschirmausgabe erzeugt, eine Eventlogeintrag schreibt oder eine Mail versendet.
Zuletzt können Sie natürlich auch alles protokollieren lassen, was auf der Konsole passiert.
Start-Transcript -Path e2k7rus-$starttime.log -Append
Stop-Transcript
Allerdings werden das dann eventuell schon etwas umfangreichere Textdateien. Aber wenn ein Skript viel Ausgaben produziert und der Bildschirmbuffer überläuft, ist es oft die einfachste Möglichkeit, etwas nach zu vollziehen.
Powershell Debugger
Es gibt von Microsoft keine richtige "Entwicklungsumgebung" und schon gar keinen "Debugger" für Powershell. Allerdings wurde in der Version 2. eine Debug-Funktion eingebaut, mit der Sie ein Skript bei Fehlern oder über Vorgaben unterbrechen können. Die sich dann öffnende Box ist eine "Untershell", so dass Sie alle Variablen zum aktuellen Stand einsehen und auch verändern können. Mit einem EXIT geht es dann einfach weiter und mit einem "{break}" können Sie das Skript abbrechen. Das ist natürlich kein Vergleich zu den Debug-Funktionen von PowerGUI und anderen Programmen. Weitere Details finden Sie auf folgendem TechNet Artikel.
- The Windows PowerShell Debugger
http://www.microsoft.com/technet/scriptcenter/topics/winpsh/debug.mspx
Error-Handling mit TRAP, TROW und TRY/CATCH
Powershell protokolliert im Gegensatz zu VBScript bis zu 256 Fehler in der eigens dazu bestimmten Variable $ERROR mit. Daher gibt es auch die Konstruktion "on error resume" nicht mehr. Statt dessen kann man bei diversen Commandlets eine Option "-ErrorVariable" und "-ErrorAction" mitgeben um Fehlerausgaben und Abbruchverhalten individuell zu bestimmten.
Das hilft aber nicht viel weiter, wenn man in einem Skript einen Aufruf tätigt, der einen Fehler wirft, z.B.
$user = [adsi]"LDAP://cn=administrator,cn=users,dc=msxfaq,dc=test"
$user.get("FeldGibtesnicht")
Dabei ist es egal, ob es das Feld nicht gibt, oder einfach nicht gefüllt ist. Leider liefert die Powershell kein "Leer" oder "$null" zurück, sondern eine Fehlermeldung, die auf einer Konsole immer unschön aussieht. Ich habe dazu eigentlich nur folgende Lösung, auch wenn man damit globale Einstellungen verbiegt.
$user = [adsi]"LDAP://cn=administrator,cn=users,dc=msxfaq,dc=test"
$ErrorActionPreference = "SilentlyContinue"
$error[0]=""
$wert=""
# sicherheitshalber initialisieren
$wert=$user.get("Feldnichtbelegt")
$ErrorActionPreference = "Continue"
if($error[0]-ne "") {
write-host "Fehler aufgetreten"
}
Alternativ kann man mit "$?" immer den letzten Fehler auslesen.
Alternativ kann Powershell natürlich auch mit TRAP und THROW arbeiten. Auf http://huddledmasses.org/trap-exception-in-powershell/ finden Sie eine gute Beschreibung, weil dieser Bereich in der Microsoft Dokumentation wohl ziemlich vergessen wurde.
Write-Host "Beispiel trapThrow startet"
trap [Exception] {
write-host
write-error $("Fehler aufgetreten:" + $_.Exception.GetType().FullName);
write-error $("Fehler aufgetreten:" + $_.Exception.Message);
continue;
}
Write-Host "Beispiel trapThrow dazwischen1"
throw (new-object IO.DirectoryNotFoundException); write-host "Fehler1";
Write-Host "Beispiel trapThrow beendet"
So kann man für einen Code-Teil eine Ausnahmebehandlung hinterlegen.
- Default Error Handling
http://powershell.com/cs/blogs/tobias/archive/2008/09/29/trapping-errors.aspx - Documenting TRAP and THROW
http://blogs.msdn.com/b/powershell/archive/2006/12/29/documenting-trap-and-throw.aspx - Trap [Exception] { “In PowerShell” }
http://huddledmasses.org/trap-exception-in-powershell/
Alternativ kann man auch etwas wie "TRY" und "CATCH" programmieren, , bei denen im TRY-Block etwas ausgeführt wird und bei einem Fehler der "CATCH"-Block dies wieder beseitigen kann. Wenn ich aber meinen Entwicklern glauben darf, sollte man diese Konstruktionen nicht übermäßig benutzen sondern eher um noch nicht behandelte Fehler zu erkennen und ansonsten sauber eine Werteprüfung implementieren.
- PowerShell: Try...Catch...Finally Comes To Life
http://weblogs.asp.net/adweigert/archive/2007/10/10/powershell-try-catch-finally-comes-to-life.aspx
Powershell Variablen
Neben $ERROR gibt es noch einige weitere Variablen, die ganz hilfreich sein können:
| Variable | Description |
|---|---|
| $_ | Das aktuelle Objekte einer Pipeline, eines Filter und den verschiedenen Schleifen. |
| $$ | Enthält das letzte Token der Shell-Eingabe |
| $? | Enthält den erfolg/Fehler des letzten Befehls |
| $Args | Enthält übergebene Parameter einer Funktion bzw. eines Skripts |
| $Error | Enthält die bis zu 256 letzten Fehlermeldungen |
| $HOME | Heimatverzeichnis des Anwenders (%HOMEDRIVE%\%HOMEPATH%) |
| $Input | Input piped einer Funktion oder eines Codeblocks |
| $Match | Eine Hashtabelle, die alle Elemente enthält, welche durch den "-match"-Operator gefunden wurde. |
| $MyInvocation | Informationen über das aktuelle Skript oder die Kommandozeile |
| $Host | Informationen über den aktuellen Computer |
| $LastExitCode | Exitcode der zuletzt aufgerufenen Anwendung (Nicht Powershellbefehl) |
| $true | Boolean WAHR |
| $false | Boolean FALSCH |
| $null | "nichts", also ein NULL-Objekt. Ideal um Objekte zu zerstören oder in IF-Abfragen zu vergleichen |
| $OFS | "Output Field Separator". Hiermit geben Sie das Trennzeichen für die Konvertierung von Arrays zu String und umgekehrt an. Standardeinstellung ist das "Leerzeichen" |
| $ShellID | Identifizierung die Shell. Normalweise "Microsoft.PowerShell" |
| $StackTrace | Stacktrace der letzten Aktion |
| $env:variablenname | Umgebungsvariablen abfragen |
| $global:varname | Variablen "global" setzen und lesen |
Powershell und Tastatureingaben für Pause
Es ist gar nicht so einfach, ein "PAUSE" zu machen, wie dies in BAT und CMD-Dateien schon immer möglich ist. Es gibt gar mehrere Optionen aber eine kommt da wohl am nächsten dran:
function Pause ($Message="Press any key to continue...")
# Quelle https://blogs.msdn.com/b/powershell/archive/2007/02/25/pause.aspx
{
Write-Host -NoNewLine $Message
#$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
Write-Host ""
}
Alternativ können Sie Auch den gesamten Puffer mit $Host.UI.RawUI.FlushInputBuffer() leeren
- Wait For Key Press
http://powershell.com/cs/blogs/tips/archive/2009/07/08/wait-for-key-press.aspx - DEMO: Trap Ctrl+C by Joel
Bennett
http://poshcode.org/542 - Windows PowerShell Tip of
the Week - using a Script Until
the User Presses a Key
http://technet.microsoft.com/en-us/library/ff730938.aspx
Messagebox und Nachfrage
Im Gegensatz zu VBScript kann die Powershell nicht direkt eine "Messagebox" auf den Bildschirm bringen. Aber über den Umweg ein COM-Objekt zu instanzieren, geht es dann doch:
$wshshell = new-object -comobject wscript.shell
$Answer = $wshshell.popup("Mitteilung",0,"Kopfzeile",4)

Die erste Zahl definiert dabei den Timeout, ab wann die Messagebox alleine "zusagt" und die vierte Stelle bestimmt die Buttons. Spielen Sie einfach etwas interaktiv damit herum. Alternativ kann man sich natürlich auch dem .NET Framework bedienen:
[void][reflection.assembly]::LoadWithPartialName("System.Windows.Forms")
$result = [windows.forms.messagebox]::Show("Meldung","Kopfzeile")
Wenn Sie keine Eingabe per Messagebox möchte, sondern auf der Kommandozeile eine Tastatureingabe anfordern wollen, dann ist "Read-Host" ihr Freund:

Einfache Eingaben können So auf der Konsole abgefragt werden. Allerdings wartet "Read-Host" immer ein "CRLF" am Ende ab, d.h. der Code steht bis sie eine Eingabe gemacht haben.
Reagieren auf Tastatureingabe mit $Host.UI.RawUI
Über das "$host"-Objekt können Sie sogar prüfen, ob gerade eine Tastatureingabe an den Prozess "ansteht" und auch diese Taste dann abrufen.
if ($host.UI.RawUI.KeyAvailable}) {
$taste = $host.UI.RawUI.ReadKey()
write-host "Tastendruck erkannt $taste"
}
ReadKey wartet aber, bis ein Tastendruck angekommen ist, d.h. sie müssen schon vorher auf KeyAvailible prüfen. um z.B. in einer Schleife auf Tasten zu reagieren ohne den Code anzuhalten.

Achtung: Readkey kommt auch bei "Halbtasten" zurück, z.B. wenn die die ALT-Taste auch nur drücken, um ,mit "ALT-TAB" in ein anderes Fenster zu springen.
Ping mit Powershell
Wenn man nicht gleich wieder das "PING.EXE" als Prozess starten will, können Sie auch per Powershell über WMI einen PING absetzen, .z.B. mit
$Pingresult = Get-WmiObject -Class Win32_PingStatus
-Filter "Address='servername'"
if ($Pingresult.statuscode -eq 0) {
Write-Host "Ping erfolgreich"
}
Ein Statuscode von "0" meldet einen erfolgreichen PING. Viel einfacher ist aber natürlich das passende Commandlet
Test-Connection -computername srv01
Powershell tönt
Auch die Ausgabe von Tönen ist möglich. Hierzu gibt es sogar eine ganze Menge von Optionen. Vom einfachen "Biep" bis zu Systemsounds und ganzen Audiodatien.
# Minimalistischer Piep
write-host `a
# einfach nur Piep"
[System.Console]::Beep()
[System.Console]::Beep(1000,300) # erste Zahl ist die
Frequenz in Hz, die Zeite Zahl die Dauer
# System Sounds verwenden
[system.media.systemsounds]::Beep.play()
[system.media.systemsounds]::Asterisk.play()
[system.media.systemsounds]::Exclamation.play()
[system.media.systemsounds]::Hand.play()
[system.media.systemsounds]::Question.play()
#Sound mit MediaPlayer wiedergeben
$mplay=New-Object -ComObject 'Mediaplayer.Mediaplayer'
$mplay.Filename=$Filename
$mplay.Play()
Es sollten also genug Spielraum von einem Lebenszeichen bis zur kompletten Steuerung einer Audioausgabe.
Testmails versenden
das folgende Skript versendet 100 Mails und schreibt die laufende Nummer mit in den betreff. Das ist ideal, um die Laufzeit von Mails zu messen, die Leitwegewahl zu prüfen oder auch einfach Verluste und Aussetzer bei Cluster und NLB-Konfigurationen zu erkennen.
# fillout the parameters and go
$SmtpClient = new-object system.net.mail.smtpClient
$SmtpClient.Host = "smarthost"
foreach ($count in (1..50)) {
write-host "Sending Mail $count"
$MailMessage = New-Object system.net.mail.mailmessage
$mailmessage.from = ("sender@firma.tld")
$mailmessage.To.add("empfaenger@firma.tld")
$mailmessage.Subject = "MSXFAQ SMTPSTORM.PS1 $count"
$mailmessage.Body = "MSXFAQ SMTPSTORM.PS1 $count"
$smtpclient.Send($mailmessage)
}
Das ist natürlich ein einfaches Beispiel, wie man eine Klasse des .NET Frameworks direkt verwenden kann und mit einer schnellen For-Schleife kombiniert.
Mit Powershell 2 ist dies jedoch noch weiter vereinfacht worden, weil es nun einfach ein "Send-MailMessage"-Commandlet gibt.
- Send-MailMessage
http://technet.microsoft.com/de-de/library/dd347693.aspx
send-mailmessage `
-smtpServer mail.example.com `
-from "User01@example.com
-to "User02@example.com
-subject "Betreff der Nachricht" `
-body "Dies ist der Nachrichteninhalt" `
-Attachment "anlage1.txt" `
-priority High `
-dno onSuccess, onFailure
Natürlich müssen nicht alle Parameter angegeben werden. Wer mag kann natürlich auch weiterhin "CDO" verwenden. Dazu muss aber CDO lokal installiert sein, z.B. in Form des Windows 2003 virtuellen SMTP-Servers.
$mail = new-object -comobject "cdo.message"
$mail.From = "Sender@example.com"
$mail.To = "recipient@example.com"
$mail.Subject = "Betreff"
$mail.TextBody = "Dies ist der Textbody"
$mail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
$mail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "smtp.sample.com"
$mail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
$mail.Configuration.Fields.Update()
$mail.Send()
Schöner ist aber schon der Ansatz über die .NET Klasse, da man damit komplett auf CDO verzichten kann.
Dateien per HTTP automatisiert übertragen
Diese Beispiel nutzt den WebClient von .NET, um eine Datei von einem Webserver herunter zu laden, die in einer CSV-Datei hinterlegt sind. Natürlich kann man das Beispiel auch mit einer Datei ganz ohne CSV-Datei schreiben. Aber so sehen Sie, wie eine externe Datendatei einfach importiert werden kann.
clnt = new-object system.net.webclient
$colFiles = Import-csv C:\temp\DL.csv
foreach ($file in $colFiles) {
$clnt.DownloadFile($file.QuellURL, $file.Ziel)
}
Inhalt der CSV-Datei.priv:QuellURL,Ziel http://www.msxfaq.net/sitemap.xml,c:\temp\sitemap.xml http://www.msxfaq.net/index.htm,c:\temp\index.htm
Diese Funktion kann z.B. interessant sein, wenn Sie für einen Verzeichnisabgleich Dateien abholen wollen.
TAIL mit Filesystemwatcher
Eine weitere Funktion eines Skripts kann es sein, Dateien in einem Verzeichnis auf Veränderungen zu überwachen, z.B. um einen Echtzeitstatus von IISLogs oder Message Tracking Logs zur Auswertung zu erhalten. Auch hier hilft Powershell und das .NET Framework mit fertigen Klassen weiter:
$FileSystemWatcher = New-object System.IO.FileSystemWatcher "c:\temp"
$result = $FileSystemWatcher.WaitForChanged("all")
Das Skript wartet also auf eine beliebige Änderung im Verzeichnis und kehrt auch erst dann zurück. Die Ausgabe von $result enthält
Natürlich kann man durch die Angabe einer Zeit ("all",10000) nach 10 Sekunden eine Rückkehr erzwingen oder gleich über Events arbeiten.
- MSH directory watcher with popup-balloon
ttp://mow001.blogspot.com/2005/10/msh-directory-watcher-with-popup.html - PSEventing 0.5 Beta
http://www.codeplex.com/PSEventing/Wiki/View.aspx?title=Home&version=22
Trap and respond to synchronous & asynchronous .NET, COM and WMI events within your powershell scripts with this easy to use suite of cmdlets - Directory Mirror using the FileSystemWatcher class
http://www.codeproject.com/KB/files/directorymirror.aspx - MSDN Beschreibung
http://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher.aspx
Powershell und Funktionen und Parameter
Wer eine Funktion baut, macht die mit dem Ziel bestimmte Codeteile häufiger zu verwenden und den Code zu strukturieren. Man kann damit aber auch interne Verarbeitungen mit privaten Variablen aus dem Hauptprogramm auslagern. Die zur Verarbeitung erforderlichen Werte sind dann als Parameter an die Funktion zu übergeben. Hierzu gibt es zwei Wege, Parameter an eine Powershell Funktion zu übergeben:
- Definition im Funktionskopf
Die erforderlichen Variablen können direkt hinter dem Funktionsnamen spezifiziert werden.
function Funktionsname ([int]$param1=10,[string]param2=msxfaq.de)
{
# Befehle
}
- Definition als PARAMS-Block
Alternativ können Parameter auch als eigener Block angegeben werden. Oft ist die übersichtlicher. Dies geht auch, wenn Sie direkt ein Powershell Skript ganz ohne Funktionen einsetzen, d.h. Parameter an das aufgerufene Skript selbst übergeben wollen
function Funktionsname {
PARAM (
[int]$param1=10,
[string]$param2=msxfaq.de
)
# Befehle
}
Wenn Sie aber die Parameter schon per Powershell definieren, dann kann ab Powershell 2.0 auch die "Autocomplete-"Funktion helfen, d.h. nach der Eingabe des Skripts starten Sie die Parameter mit einem "-" und nutzen dann die TAB-Taste, um durch die einzelnen Parameter zu laufen.
Perfekt wird es dann, wenn Sie die Parameter gleich auf "Gültigkeit" prüfen lassen.
- Typisierung
Schon eine Typisierung der Parameters verhindert viele Fehlzuweisungen:
[int]$variable # 32-bit integer mit Vorzeichen [long]$variable # 64-bit integer mit Vorzeichen [string]$variable # string mit Unicode characters [char]$variable # A Unicode 16-bit character [byte]$variable # 8-bit Zahl [bool]$variable # Boolean True/False [decimal]$variable # 128-bit Dezimalzahl [single]$variable # Single-precision 32-bit Fließkommazahl [double]$variable # Double-precision 64-bit Fließkommazahl [xml]$variable # Xml object [array]$variable # Array [hashtable]$variable # Hashtabelle # Denkbar sind aber auch Arrays [String[]]$Userliste
- Vorbelegung
Sie können einen Parameter schon mit Daten "vorbelegen". Die Angabe auf der Kommandozeile oder beim Funktionsaufruf überschreibt dann diese Vorbelegung
[string]$variablenname = "Beispielwert"
- Steuerung über "Parameter",
z.B.: Zwang und Position und
Hilfe
Weiterhin kann vorgegeben werden, ob ein Parameter zwingend angegeben werden muss und welche Position er hat, wenn Daten ohne Parameterangabe übergeben werden. Dazu setzen Sie einfach ein "[Parameter()]" davor.
Siehe auch Parameter Attribute Declaration http://msdn.microsoft.com/en-us/library/ms714348(v=VS.85).aspx
Den "Parameterset-Namen" kann man im Code einfach über $PsCmdlet.ParameterSetName z.B. in einer Switch-Anweisung abfragen
[Parameter(
Position=0,'
Mandatory=$true,
HelpMessage="Dies ist eine Hilfe",
ValueFromPipeline=$false,
ParameterSetName="Setname"]
)][string]$variablenname
- Erweiterte Validierung
Über die Angabe des Typs hinaus ist sogar noch eine Validierung direkt in der Parametrisierung möglich. So kann der Aufruf eines Scripts schon bei falschen Angaben gestoppt werden und muss nicht mehr im Code selbst später geprüft werden.
# Check gegen regular
expressions
[Validatepattern("regexdausdruck")][string]$variablenname
# erweiterte Datumsabfrage
[ValidateRange( "06/10/2010 02:00:00 PM",
"06/17/2010 03:00:00 PM")][DateTime]$Datum
# das ganze mit errechneten Min und Max Werten
[ValidateRange( [DateTime]::Now.AddDays(-7), [DateTime]::Now)]
# Check gegen eine Liste von Optionen
[ValidateSet("Ja", "Nein", "Vielleicht",
IgnoreCase = $true)]
# Verifizierung per Code, z.B. dass die Zeit
nach dem 1.1. 2010 ist
[ValidateScript({$_ -le [DateTime]::Now -and $_
-ge "1/1/2010"})
# Begrenzung der Element in einem Array
[ValidateCount(1,3)][String[]]$UserName
# erwartet einen Text mit 5-8 Buchstaben
[ValidateLength(5,8)][string]$variablenname
# weitere Funktionen
[ValidateNotNullOrEmpty], [ValidateNotNull], [AllowNull],
[AllowEmptyString], [AllowEmptyCollection]
- Dynamische Parameter
Wem das dann immer noch nicht reicht, kann Parameter dynamisch ein und ausschalten, indem der Wert eines anderen Parameter ausgewertet wird.
Interessanterweise steht viel davon in der Powershell Online Hilfe:
get-help about_Functions_Advanced_Parameters
Beim Aufruf der Funktion sollte man sich aber an Powershell und "Komma" erinnern. Parameter werden nicht durch Komma getrennt übergeben sondern mit Leereichen. Ein Komma macht aus den zwei oder mehr Parametern ein Array, welches die Funktion als ersten Parameter erhält.
# FALSCH
funktionsname wert1, wert2, wert3
#Richtig
funktionsname wert1 wert2 wert3
Die Rückübergabe von Ergebnissen erfolgt ebenfalls einfach über die Pipeline, d.h. alles was das Skript einfach "ausgibt" bekommt der aufrufende Prozess zurück. Insofern muss an Ende keine Zuweisung des Ergebnisses an den Funktionsnamen stehen, sondern einfach die Variable mit dem Inhalt.
- about_Functions_Advanced_Parameters
http://technet.microsoft.com/en-us/library/dd347600.aspx - Parameter Attribute Declaration
http://msdn.microsoft.com/en-us/library/ms714348(v=VS.85).aspx - Validate PowerShell Parameters Before Running the Script
http://blogs.technet.com/b/heyscriptingguy/archive/2011/01/11/validate-powershell-parameters-before-running-the-script.aspx - Simplify Your PowerShell Script with Parameter
Validation
http://blogs.technet.com/b/heyscriptingguy/archive/2011/05/15/simplify-your-powershell-script-with-parameter-validation.aspx - Validating Parameter Input
http://msdn.microsoft.com/en-us/library/ms714432(v=vs.85).aspx - Parameter Validation
http://blog.usepowershell.com/2011/05/parameter-validation/
Powershell und Kommandozeile
Analog zur Parameterübergabe für eine Funktion können Sie natürlich auch einem PS1-Skript direkt die Parameter übergeben. Da es hier aber keine Funktionsüberschrift gibt, wird der PARAM -Block direkt am Anfang geschrieben.
PARAM(
[int]$Interval=60 ,
[string]$domain="msxfaq.de" .
[ScriptBlock]$skriptteil={(get-mailboxserver| select name)}
)
Der Aufruf kann dann wie gewohnt mit Parametrisierung erfolgen.
.\scriptname -interval 30 -domain andere.domain
Es müssen auch nicht alle Parameter übergeben werden. Überzählige Parameter werden einfach ignoriert, d.h. vorsicht bei Tippfehlern. Erst die Powershell 2 wertet die Powershell diese Parameterblöcke im Skript auch derart aus, dass Sie über die AutoComplete-Funktion der Kommandozeile erreichbar werden.
Man kann hier auch die Defaults angeben, wenn der aufrufende Prozess keine Daten hinterlegt. Interessant ist hier auch die Funktion, direkt einen [Scriptblock] mit zu übergeben, welcher später einfach mit "$skriptteil" aufgerufen werden kann.
write-host "Argumente sind $args"
"Switch"-Parameter
Bei einer Funktion könne Sie auch "[switch]" als Typ angeben. In diesem Fall heisst ein vorhandensein eines Parameters, dass dieser "true" ist. Ein explizites "$true" muss nicht mehr mit angegeben werden.
function switchdemo([switch]$test ) {
if( $test ) { 'true' }
else { 'false' }
}
PS C:\> switchdemo -test:$true
true
PS C:\> switchdemo -test:$false
false
PS C:\> switchdemo -test
true
PS C:\> switchdemo
false
PS C:\> switchdemo -test $false
true
Bei der Angabe von "$true" oder "$False" ist aber nur die Schreibweise mit einem Doppelpunkt (":") zulässig. Bei einem Aufruf mit Leerzeichen als Trenner wird das $False z.B. als zweiter Parameter betrachtet.
Powershell und Aufzählungen
An vielen Stellen werden Variablen übergeben. Anders als bei VBScript, wo es nur wenige Basistypen (Integer, String und die universellen Variants etc.) gibt, kann man in .NET mit Aufzählungen arbeiten, die nur bestimmte Werte annehmen können. Eine Funktion kann damit streng prüfen, ob die Parameter überhaupt "geeignet" sind. Also Administrator bin ich an verschiedenen Stellen daher eingeschränkt und kann nicht einfach einen String übergeben, wenn eigentlich ein Wert einer Aufzählung erforderlich ist. Also muss ich heraus bekommen, welche Inhalte eine Aufzählung hat:
[System.Enum]::GetValues([System.Diagnostics.EventLogEntryType])
In der Powershell sieht das dann wie folgt aus:

Sie können natürlich auch weiter mit der MSDN die Klassen und Aufzählungen suchen und betrachten.
For-Schleife mit "$null"
Manchmal bekommt man Dinge, die glaubt man erst mal nicht. Oft erhält man als Ergebnis einer Aktion eine Liste, die man mit einer "ForEach"-Schleife gerne abarbeiten möchte. Das funktioniert, solange es wirklich eine Aufzählung ist. Interessant wird es aber, wenn das Ergebnis "$null" ist, wie das folgende Beispiel beweist:
foreach ($wert in $null) {
write-host "wird doch ausgeführt"
}
Der Codeteil in der Schleife wird tatsächlich einmal ausgeführt. Da muss man dran denken und entweder vorher ein "$null" abfragen oder in der Schleife drin den Sonderfall abfangen. Trotzdem würde ich das als "unerwartet" ansehen.
- http://blogs.msdn.com/b/powershell/archive/2007/01/23/array-literals-in-powershell.aspx
- http://powershellcrashkurs.wordpress.com/2009/07/08/geheimnisvolle-powershell-teil-3-wenn-null-doch-etwas-ist/
Hastables
Eine sehr leistungsfähige Funktion sind so genannte "Hash-Tabellen" oder unter VBScript als Dictionary-Objekt bezeichnet. Dieser Datenspeicher besteht aus mehreren Informationen, die durch einen eindeutigen, einmaligen Schlüssel adressierbar sind. Ich nutze diese sehr gerne um Informationen für eine spätere Verwendung zu puffern.
Wenn ich z.B. Informationen über die Postfachgröße einer Datenbank auslese und später beim Durchlaufen der Benutzer diese Information abrufen will, dann lege ich diese am Anfang mit z.B. dem Namen der Mailbox als Schlüssel ab. Der Wert kann dabei ein beliebiger Typ sein. Sogar komplexe Objekte sind möglich.
- Microcode: PowerShell
Scripting Tricks: The Joy of
using Hashtables with Windows
PowerShell
http://blogs.msdn.com/b/mediaandmicrocode/archive/2008/11/27/microcode-powershell-scripting-tricks-the-joy-of-using-hashtables-with-windows-powershell.aspx
Parallele Verarbeitung
Windows ist ja schon länger ein "Multitasking"-Betriebssystem. Da darf ja die Frage erlaubt sein, ob ein Admin z.B. eine Aktion, die auf vielen Systemen ausgeführt werden soll, nicht einfach auch parallelisieren kann. Das ist sogar "erschreckend" einfach. Über das "Start-Job"-Commandlet kann einfach ein Skriptblock in den "Hintergrund" gesendet werden. Über GET-Job lässt sich der Status des Jobs abfrage und über "Receive-Job" kann das Ergebnis des Jobs (Pipelineausgabe) einfach wieder abgerufen werden.
# Start einer Ausgabe
PS C:\> start-job {write-host "test"}
Id Name State HasMoreData Location
-- ---- ----- ----------- --------
11 Job11 Running True localhost
PS C:\> Get-Job
Id Name State HasMoreData Location
-- ---- ----- ----------- --------
11 Job11 Completed True localhost
PS C:\> receive-Job 11
test
PS C:\> Remove-Job 11
Ausgaben der Jobs mit "Write-Host" landen aber nicht im Fenster des aufrufenden Programms, sondern müssen mit "Receive-Job" abgeholt werden. Der Anruf leer dann aber die bis dahin angefallene Warteschlange, wenn Sie dies nicht mit "-keep" unterbinden. Interessant bei Start-Job ist die Option, alternative Credentials anzugeben.
Einige Commandlets können auch mit dem Parameter "-AsJob" als Auftrag gestartet werden.
Das eigene Skript sollte also die Funktionen einfach als "Scriptblock" definieren und dann über Start-Job aufrufen. Sie müssen sich aber dann natürlich um die Jobs und deren Status selbst kümmern.
- Start-Job
http://technet.microsoft.com/de-de/library/dd347692.aspx - Can -Powershell run
commands in parallel
http://stackoverflow.com/questions/4016451/can-powershell-run-commands-in-parallel - Parallel “For Each”
http://snipplr.com/view/47915/parallel-for-each/
Powershell und Pipeline
Interessant wird es nun, wenn man die Pipeline der Powershell etwas umfangreicher nutzen möchte, dann bietet es sich natürlich an, die Ausgaben eines Kommandos per "PIPE" auf ein weiteres Kommando zu übergeben. Wer nun ein PS1-Skript selbst entwickelt, kann über zwei Wege an die Daten der Pipe kommen.
- $INPUT-Variable
Diese Variable enthält quasi die Eingaben und kann direkt wieder weiter verarbeitet werden, z.B.:
# Einfachbeispiel im die Ausgabe einer PIPE als Tabelle
$input | fl
- begin, process, end-Blöcke mit $_
Etwas mehr Kontrolle hat man, wenn man die Daten über diese geschützten Kennzeichen verarbeitet. So kann man als Entwickler z.B. globale Variablen und Objekte instanzieren und effektiv nutzen.
begin {
Write-Host 'Testskript: ============== started ============== '
}
process {
Write-Host "Testskript: ------ Pipelinedata --------"
$pipedata = $_
$fields = ($_ | gm -MemberType NoteProperty)
foreach($item in $fields) {
Write-Host "Name:" $item.name " Daten: " $pipedata.($item.name)
}
}
end {
Write-Host "Testskript: ============== ended ============== "
}
Interessanter ist bei der Konstruktur die Arbeitsweise. Wer damit mehrere Prozesse miteinander verkettet, wird erkennen, dass alle Prozesse parallel ablaufen, d.h. es ist eine permanente Abarbeitung möglich. Hier ein Beispiel. Kopieren sie die beiden Skripte in ein Verzeichnis
script1.ps1.txt
script2.ps1.txt
Starten Sie diese dann mit folgendem Aufruf:
script1.ps1 | script2.ps1
Und sie werden sehen, dass Skript 1 startet aber dann auch gleich Skript 2 und jede Ausgabe, die Skript 1 produziert von von Skript 2 gleich weiter verarbeitet wird.
Wenn Sie in solch einer Konstruktion noch eigene Funktionen addieren wollen, dann sind diese sinnvoll nur innerhalb des "begin"-Blocks zu definieren. Wenn die sie Blöcke weg lassen, dann setzt die Powershell das Skript automatisch als "END"-Block.
Pipeline und $_
Ich habe mir angewöhnt, das Pipelineobjekt in eine Variable namens $pipeline
zu kopieren und dann damit zu arbeiten. Zu oft habe ich innerhalb des "process"-Blocks
wieder mit FOR-Schleifen gearbeitet und mir so die Variable
überschrieben.
- $input gotchas
http://dmitrysotnikov.wordpress.com/2008/11/26/input-gotchas/ - PowerShell, $input vs. process
http://msgoodies.blogspot.com/2007/07/powershell-input-vs-process.html - Using Script Functions in the PowerShell Pipeline (
Take Two )
http://huddledmasses.org/using-script-functions-in-the-powershell-pipeline-take-two/
Es ist meines Wissens nicht möglich, die Pipeline bidirektional zu nutzen, d.h. ein direkter "Rückweg" zum aufrufenden Prozess ist verbaut. Denkbar wäre aber z.B. der Umweg über eine Datei (Prozess2 schreibt) und Prozess1 prüft.
Powershell und "zwei Pipelines"
Es kann eigentlich nur eine aktive Pipeline geben. Und so stoßen Sie bei Exchange 2010 z.B. bei folgendem Befehl auf einen Fehler:
get-mailbox | $userlist | %{ set-mailbox -Identity $_.identity -SimpleDisplayName $_.displayname}
ERROR: Pipeline not executed because a pipeline is already executing.
Pipelines cannot be executed concurrently.
Trennen Sie aber die Zeilen dann funktioniert es
$userlist = get-mailbox
$userlist | %{ set-mailbox -Identity $_.identity -SimpleDisplayName $_.displayname}
Eine andere Lösung ist der Verzicht auf die Pipeline mit einer Forschleife:
foreach ($mb in get-mailbox) {
set-mailbox -Identity $mb.identity -SimpleDisplayName
$mb.displayname}
}
Ein einfaches get-mailbox | set-mailbox hingegen funktioniert. Das liegt an der Kombination der Pipeline mit einem FOR-Operator, der dann in der Befehlsgruppe wieder ein eigenständiges Commandlet startet. Dieses versucht nun auch auf die gleiche Pipeline zuzugreifen bzw. diese zu erstellen und das geht nicht. Das ist aber nur ein "Sonderfall" beim Einsatz mit RBAC.
- Exchange Management Shell Error: Pipelines Cannot be
Executed Concurrently
http://www.mikepfeiffer.net/2010/02/exchange-management-shell-error-pipelines-cannot-be-executed-concurrently/
Powershell und Output Pipeline
Genauso kann es natürlich interessant sein, Ausgaben in eine Pipeline zu schreiben. Wie wäre es, wenn ein Skript z.B.: die Mitglieder einer Verteilerliste extrahiert und nicht stumpf in eine Datei schreibt, sondern Sie in eine PIPE übergibt, so dass Sie selbst mit Export-CSV oder Export-CliXML oder andere Tools darauf zugreifen können ? Auch das geht per Powershell.
Man muss einfach nur ein Powershell Objekt erstellen, mit Properties und Werten versehen und "ausgeben
Write-Host "Testskript: ------ Pipelinedata --------" $pso = New-Object PSObject $pso | Add-Member NoteProperty "ColumnA" "Data1" $pso | Add-Member NoteProperty "ColumnB" "Data2" $pso | Add-Member NoteProperty "ColumnC" "Data3" Write-Output $pso
Wen die Schreibweise mit der PIPE nach Add-Member nicht gefällt, kann die Werte auch anders zuweisen:
$pso = New-Object PSObject
Add-Member -InputObject $pso -MemberType NoteProperty -Name Feld1
-Value Wert1
Add-Member -InputObject $pso -MemberType NoteProperty -Name Feld2 -Value
Wert2
# Oder Kürzer
Add-Member -InputObject $pso noteproperty feld1 wert2
Add-Member -InputObject $pso noteproperty feld2 wert2
# Bestehende Felder können einfach dann zugewiesen werden
$pso.feld1 = "wert1neu"
#und ausgegeben werden
write-host $pso.feld1
Siehe auch http://technet.microsoft.com/en-us/magazine/cc510337.aspx
Es geht sogar noch einfacher durch einen kleinen Kniff. Man gibt einfach einen Leerstring an "select" aus und wählt die Felder. Powershell erstellt so ein leeres Objekt mit den Feldern. Das Beispiel erstellt nicht nur ein Objekt sonder addiert es auch an eine Tabelle. So können also sehr einfach viele Ausgaben schnell aufgestellt und später ausgegeben werden.
$ergebnistabelle = @() $ergebnis = "" | select Feld1,Feld2,Feld3 $ergebnis.Feld1 = "Inhalt1" $ergebnis.Feld2 = "Inhalt2" $ergebnis.Feld2 = "Inhalt3" $ergebnistabelle += $ergebnis
Übrigens können Sie die Ausgabe auch an CScript übergeben, welches dann über wscript.stdin.readall darauf zugreifen kann. Allerdings sollten Sie nicht zu viel erwarten. Mehr als die Bildschirmausgabe des vorherigen Powershell Skripts kommt in VBScript nicht an. Also ist dieser Weg nicht wirklich sinnvoll zu nutzen.
- Microcode: PowerShell
Scripting Tricks: Select-Object
(Note Properties) vs Add-Member
(Script Properties)
http://blogs.msdn.com/b/mediaandmicrocode/archive/2008/11/26/microcode-powershell-scripting-tricks-select-object-note-properties-vs-add-member-script-properties.aspx
Powershell und grafische Ausgaben
Auch wenn Powershell eigentlich "nur" eine Kommandozeile ist, so kann man damit wie mit VBScript auch grafische Ausgaben gestalten. Während wir bei VBScript auf die Verwendung einer COM-Komponenten wie dem Internet Explorer oder anderen Tools aufsetzt, kann man mit der Powershell direkt .NET Klassen ansprechen. Insofern gibt es meines Wissens nach drei Optionen:
- COM
Wie zu VBScript-Zeiten kann der Programmierer z.B. einen Internet Explorer Aufrufen und ihn quasi "fernsteuern" um Daten und Ausgaben in Tabellen und anderen HTML-Tags zu schreiben. - NET
Aus Powershell können aber auch direkt Windows Forms genutzt werden. Das ist zwar etwas aufwändiger, da man alle Objekte manuell erzeugen und platzieren muss, aber für einfache Oberflächen ist das sicher schon einmal ausreichend. - WPF
Mit dem .NET Framework 3.5 gibt es eine ganz neue Option, grafische Oberflächen zu erstellen. So wird das Layout in einer XAML-Datei abgelegt, welche dann vom Code genutzt werden kann. Auf der einen Seite können also GUI-Designer das Aussehen der Anwendung ohne den Code-Hintergrund entwerfen und Programmierer steuern einfach den Code bei, der die Objekte verändert. Das kann auch die Powershell machen.
Siehe auch http://blogs.technet.com/robcost/archive/2008/05/28/powershell-wpf-cool.aspx
Da ich schon einige VBSkripte mit COM-Ausgabe auf der MSXFAQ veröffentlicht habe, verschränke ich mich hier auf die .NET Version. Sie legt ein Fenster mit einer Tabelle an und ein Druck auf den Button "Update" startet die Funktion "UpdateStatus", die hier natürlich nur eine Zeile füllt. Hier kann aber natürlich noch viel mehr erfolgen.
function UpdateStatus(){
write-host "------- Start Update ---------"
$fsTable.clear()
$fsTable.Rows.add($wert1,$wert2,$wert3)
write-host "------- Start Done ---------"
}
# Declare Form
[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") | out-null
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")| out-null
$form = new-object System.Windows.Forms.form
$form.Text = "Exchange 2007 CCR Statusmonitor"
$form.Location = new-object System.Drawing.Size(10,30)
$form.size = new-object System.Drawing.Size(1000,620)
$form.autoscroll = $true
#$form.topmost = $true
$form.Add_Shown({$form.Activate()})
# UpdateNow Button
$UpdateButton = new-object System.Windows.Forms.Button
$UpdateButton.Location = new-object System.Drawing.Size(10,5)
$UpdateButton.Size = new-object System.Drawing.Size(120,23)
$UpdateButton.Text = "UpdateNow"
$UpdateButton.visible = $True
$UpdateButton.Add_Click({UpdateStatus})
$form.Controls.Add($UpdateButton)
# Declare Table
$fsTable = New-Object System.Data.DataTable
$fsTable.TableName = "Status" | out-null
$fsTable.Columns.Add("Feld1",[string]) | out-null
$fsTable.Columns.Add("Feld2",[string]) | out-null
$fsTable.Columns.Add("Feld3",[string]) | out-null
#$Dataset = New-Object System.Data.DataSet
#$Dataset.tables.add($fsTable)
$Dataview = New-Object System.Data.DataView($fsTable)
$Dataview.allowEdit = $false
#$Dataview.allowAdd = $false
$Dataview.allowDelete = $false
# Add DataGrid View
$dgDataGrid = new-object System.windows.forms.DataGridView
$dgDataGrid.Location = new-object System.Drawing.Size(10,30)
$dgDataGrid.size = new-object System.Drawing.Size(950,550)
$dgDataGrid.autosize = $true
$dgDataGrid.DataSource = $Dataview
#$dgDataGrid.DataSource = $fsTable
$form.Controls.Add($dgDataGrid)
UpdateStatus
$form.ShowDialog()
Eine WPF-Version habe ich noch nicht erstellt, aber wird nachgereicht, sobald ich damit schon gearbeitet habe.
- PowerShell GUI lock Monitor Script
http://thepowershellguy.com/blogs/posh/archive/2007/01/21/powershell-gui-scripblock-monitor-script.aspx - Mailbox Folder Size comparison Powershell GUI
http://gsexdev.blogspot.com/2007/12/mailbox-folder-size-comparison.html - Powershell mit WPF
http://blogs.technet.com/b/heyscriptingguy/archive/2010/03/24/hey-scripting-guy-march-24-2010.aspx - Write-Progress & WPK
http://blogs.msdn.com/b/mediaandmicrocode/archive/2009/12/28/write-progress-wpk.aspx - Quick UI with WPK in Windows PowerShell
http://channel9.msdn.com/posts/LarryLarsen/Quick-UI-with-WPK-in-Windows-PowerShell - Multitouch Fingerpaint in 30 Lines of PowerShell Script
http://channel9.msdn.com/posts/LarryLarsen/Multitouch-Fingerpaint-in-30-Lines-of-PowerShell-Script/ - Geheimtipps für die Erstellung einer WPF-Anwendung in Windows
PowerShell
http://msdn.microsoft.com/de-de/magazine/hh288074.aspx - PowerShellPack
http://archive.msdn.microsoft.com/PowerShellPack
Powershell und Registry
Über einen PSDrive-Provider ist ein direkter Zugriff auf die Registrierung möglich
PS HKLM:\> Get-PSDrive Name Used (GB) Free (GB) Provider Root ---- --------- --------- -------- ---- HKCU Registry HKEY_CURRENT_USER HKLM Registry HKEY_LOCAL_MACHINE
Entsprechen kann direkt per *-Item und *-Itemproperty darauf zugegriffen werden. Hier ein paar Beispiele. Allerdings sind alle Einträge, also sowohl die Werte aus auch die Schlüssel erst mal Objekte.
# Anlegen eines Keys
New-Item -type directory HKCU:\Test
Get-Item HKCU:\test
# Wert setzen
New-ItemProperty HKCU:\test -Name parameter -value 59322 -propertyType
dword
Get-ItemProperty HKCU:\Test\Parameter
#Und wieder aufräumen
Remove-ItemProperty HKCU:\Test\ -name Parameter
remove-Item HKCU:\test
Powershell und Zertifikatspeicher
Wer sich die Ausgabe von Get-PSDrive mal genauer ansieht, erkennt auch einen "Cert:"-Procider.
PS C:\> Get-PSDrive
Name Used (GB) Free (GB) Provider
---- --------- --------- --------
cert Certificate
PS C:\> cd cert:
PS cert:\> dir
Location : CurrentUser
StoreNames : {SmartCardRoot, UserDS, AuthRoot, CA...}
Location : LocalMachine
StoreNames : {SmartCardRoot, AuthRoot, CA, Trust...}
PS cert:\> Get-childitem -Path cert:\currentuser\my
Directory: Microsoft.PowerShell.Security\Certificate::currentuser\my
Thumbprint Subject
---------- -------
C577080F325940C811942507EE5ECCDF07BB7100 OU=EFS File Encryption Certificate, L=EFS, CN=Administrator
Über ein einfaches "CD CERT:" kommen Sie in den Zertifikatspeicher durchlaufen und auch hier mit Get-Item etc. die Objekte anschauen und natürlich auch verändern.
Powershell und Performance Counter
Seit Powershell 2 gibt es nun auch ein GET-Counter-Commandlet, welches einen direkten lesenden Zugriff auf die Performance Counter erlaubt:
PS> (Get-Counter "\prozessor(_total)\prozessorzeit (%)").countersamples[0] Path InstanceName CookedValue ---- ------------ ----------- \\nawnbfc\prozessor(_to... _total 4,24059311090853
NAtürlich kann man auch über das .NET-Framework auf Performancecounter zugreifen, was anscheinend auch schneller aber vor allem einfacher ist, als der WMI-Zugriff. Hier ein Muster auf einem englischen Server
[PS] $perf = new-object System.Diagnostics.PerformanceCounter
[PS] $perf.countername = "% Processor Time"
[PS] $perf.Categoryname = "Processor"
[PS] $perf.Instancename = "_Total"
oder
[PS] $perf = new-object System.Diagnostics.PerformanceCounter "Processor","% Processor Time", "_Total"
[PS] $perf
CategoryName : Processor
CounterHelp : % Processor Time is the percentage of elapsed time that the
processor spends to execute a non-Idle thread. It is calcula
ted by measuring the duration of the idle thread is active i
n the sample interval, and subtracting that time from interv
al duration. (Each processor has an idle thread that consum
es cycles when no other threads are ready to run). This coun
ter is the primary indicator of processor activity, and disp
lays the average percentage of busy time observed during the
sample interval. It is calculated by monitoring the time th
at the service is inactive, and subtracting that value from
100%.
CounterName : % Processor Time
CounterType : Timer100NsInverse
InstanceLifetime : Global
InstanceName : _Total
ReadOnly : True
MachineName : .
RawValue : 36131046250000
Site :
Container :
Das ist ja noch einfach, so lange es genau ein Counter ist und die Namen als auch Instanzen gleich sind. Nun gibt es aber Performancecounter, die mehrere Instanzen haben, z.B.: die Exchange 2007 CCR Replikation. Da muss man sich dann selbst durch die Instanzen arbeiten.
[PS] $perfcat = new-object System.Diagnostics.PerformanceCounterCategory("MSExchange Replication")
[PS] $perfcat.GetInstanceNames()
| %{$perfcat.GetCounters($_)}
| ft Cat*,countername,Instancename,rawvalue
CategoryName CounterName InstanceName RawValue
------------ ----------- ------------ --------
MSExchange Repli... CopyNotification... _total 83317
MSExchange Repli... CopyGenerationNu... _total 83317
MSExchange Repli... InspectorGenerat... _total 83317
MSExchange Repli... ReplayNotificati... _total 83317
MSExchange Repli... ReplayGeneration... _total 83317
MSExchange Repli... ReplayQueueLength _total 0
MSExchange Repli... Suspended _total 0
MSExchange Repli... TruncatedGenerat... _total 0
MSExchange Repli... CopyNotification... s1 sg1 83317
MSExchange Repli... CopyGenerationNu... s1 sg1 83317
MSExchange Repli... InspectorGenerat... s1 sg1 83317
MSExchange Repli... ReplayNotificati... s1 sg1 83317
MSExchange Repli... ReplayGeneration... s1 sg1 83317
MSExchange Repli... ReplayQueueLength s1 sg1 0
MSExchange Repli... ReplayBatchSize s1 sg1 0
MSExchange Repli... CopyQueueLength s1 sg1 0
Auch hierzu finden sich im Internet umfangreiche Beschreibungen
- How to Read Performance Counters [Ryan Byington]
http://blogs.msdn.com/bclteam/archive/2006/06/02/618156.aspx - Beginning System.Diagnostics
http://www.codeproject.com/KB/dotnet/SystemDiagnostics_basics.aspx - Performance Counter Interface
http://www.informit.com/guides/content.aspx?g=dotnet&seqNum=247 - PerfmonCounter mit WPF und Powershell
http://huddledmasses.org/wpf-from-powershell-updating-windows/
Powershell und Eventlog
Über das Commandlet "Get-Eventlog" kann man natürlich sehr einfach die Daten der verschiedenen Eventlogs von Windows extrahieren. Seit Powershell 2 und Windows Vista und höher gibt es mit "Get-WinEvent" eine noch leistungsfähigere Version.
Der größte Vorteil ist dabei die Möglichkeit, alternative Credentials mitzugeben, was speziell bei der Suche auf anderen Computern hilfreich sein kann. Aber auch ein Schreiben in das Eventlog ist über die.NET-Klassen schon als trivial zu bezeichnen.
# Initialize Eventlog for reporting and Debugging
$evt=new-object System.Diagnostics.EventLog("Application")
$evt.Source="MSXFAQ"
$infoevent=[System.Diagnostics.EventLogEntryType]::Information
$warnevent=[System.Diagnostics.EventLogEntryType]::Warning
$errorevent=[System.Diagnostics.EventLogEntryType]::Error
$evt.WriteEntry("SKRIPT gestartet",$infoevent,0)
Trotzdem gibt es zur Vereinfachung noch ein "Write-Eventlog", welches aber eine "Source" benötigt, die zuerst zu definieren ist. Ich nutze dazu gerne folgenden Baustein:
# Source bei Bedarf definieren
if (![System.Diagnostics.EventLog]::SourceExists("msxfaq")) {
new-eventlog -logname 'Application' -Source 'msxfaq'
#[System.Diagnostics.EventLog]::CreateEventSource($source, "Application")
}
# Eventlogeintrag erzeugen
write-eventlog `
-entrytype Information`
-logname Application `
-eventID 1 `
-Source 'msxfaq' `
-Message "Meldung"
Wer also heute ein Powershell-Skript schreibt und die Ausgabe von Fehlern oder Status als Eventlog vergisst, sollte sich mal fragen wie er all die schönen Skripte überwachen möchte wenn diese später automatisch laufen.
- Processing Eventlogs in Powershell
http://blogs.msdn.com/b/powershell/archive/2009/05/21/processing-event-logs-in-powershell.aspx - Creating Your Own Eventlog
http://powershell.com/cs/blogs/tips/archive/2009/09/29/creating-your-own-eventlog.aspx - How to: Determine If an Event Source Exists
http://msdn.microsoft.com/en-us/library/9t766zhb.aspx - What Can I Do With Windows PowerShell?
http://www.microsoft.com/technet/scriptcenter/topics/msh/cmdlets/get-eventlog.mspx - PowerShell Team Blog - Monitor the Event Log
http://blogs.msdn.com/powershell/archive/2006/04/25/583266.aspx - Hey, PowerShell Guy ! How Can I Monitor Event Log Messages for
Specific Words?
http://thepowershellguy.com/blogs/posh/archive/2007/02/26/hey-powershell-guy-how-can-i-monitor-event-log-messages-for-specific-words.aspx - PowerShell: Get-WinEvent vs.
Get-EventLog
http://www.mcbsys.com/techblog/2011/04/powershell-get-winevent-vs-get-eventlog/ - Use PowerShell Cmdlet to
Filter Event Log for Easy Parsing
http://blogs.technet.com/b/heyscriptingguy/archive/2011/01/24/use-powershell-cmdlet-to-filter-event-log-for-easy-parsing.aspx - Windows Event Log in
PowerShell – Part II (MSDN)
http://blogs.msdn.com/b/powershell/archive/2009/06/11/windows-event-log-in-powershell-part-ii.aspx - Chapter 23. Event Logs
http://www.pavleck.net/powershell-cookbook/ch23.html
Wer statt eines Eventlogs die Meldungen an einen Syslog Server senden will, findet auf Syslog entsprechende Informationen. Umgekehrt kann man sehr einfach im Eventlog auch nach Werten suchen. Hier suche ich 1221 Events der letzten 14 Tage. (Ergebnisse der Online Defragmentierung von Exchange:
$WMIDATEAGE = [System.Management.ManagementDateTimeConverter]::ToDmtfDateTime `
([DateTime]::UtcNow.AddDays(-14))
Get-WmiObject -computer SRV01 ´
-query ("Select * from Win32_NTLogEvent Where Logfile='Application' and
Eventcode = '1221' and TimeWritten >='" + $WMIDATEAGE + "'") |
foreach-object{
write-host $_.message
}
Aber man muss sich gar nicht auf die Tiefen von WMI begeben. Mit dem Powershell Commandlets "Get-Eventlog" bzw. "Get-WinEvent" lässt sich jedes Eventlog einfach auslesen. Denken Sie aber daran, auch hier mit Filtern zu arbeiten.

Interessant ist bei Get-WinEvent auch die Nutzung von Filtern bei der Abfrage. Speziell bei WAN-Zugriffen kann dann der Quellserver schon Filtern und sendet nicht alle Einträge, die dann mit einem "Where" lokal gefiltert werden können.
- PowerShell: Get-WinEvent vs.
Get-EventLog
http://www.mcbsys.com/techblog/2011/04/powershell-get-winevent-vs-get-eventlog/
Eventlog und Windows
2008/Vista
Das Script funktioniert leider nicht auf Windows
2008 oder Vista sondern nur auf Windows 208R2
oder Windows 7, da ein "Bug" in GET-WinEvent den
Einsatz des Filters mit "FilterHashtable" nicht
zulässt und ich aus Gründen der Lesbarkeit nicht
den Filter als XML-Struktur verwenden wollte.
Siehe auch
https://connect.microsoft.com/PowerShell/feedback/details/422072/get-winevent-on-vista-does-not-with-vista-x64-using-ctp3
Interessant ist aber auch die Funktion, einfach auf Events zu warten und die dann automatisch zu verarbeiten. Auch wenn dies mit Powershell 2.0 noch einfacher geht (Register-WMIEvent auf http://www.microsoft.com/technet/scriptcenter/topics/winpsh/events.mspx), ist auch Powershell 1.0 schon sehr einfach dazu zu verwenden. Hier ein Beispiel:
$eventwatcher = new-object
system.management.ManagementEventWatcher
$eventwatcher.query = "Select * From __InstanceCreationEvent Where
TargetInstance ISA 'Win32_NTLogEvent'"
$event = $eventwatcher.WaitForNextEvent()
$event.targetinstance
Das Skript in einer Powershell-Box gestartet, liefert den nächsten Event, der ein einem beliebigen Eventlog des lokalen Servers auftritt.

Mit so wenig Code kann man natürlich schöne weitere Funktionen bauen:
Powershell und Syslog
Mit Powershell können Sie relativ einfach mit der .NET Klasse "System.Net.Sockets.UDPClient" ein UDP-Paket versenden und damit natürlich auch Syslog senden.
[string]$sysloghost = "1.2.3.4"
[string]$syslogmesage = "Meldung an Syslog"
$udpclient = New-Object system.net.sockets.udpclient($sysloghost,514)
$rawmessage = [System.Text.Encoding.ASCII]::GetBytes($syslogmesage)
$udpclient.send($rawmessage, $rawmessage.length)
$udpclient.close()
$udpclient = $null
Es ist schon erstaunlich, wie leistungsfähig in Powershell mal eben schnell ein Syslog-Sender geschrieben werden. Noch faszinierender ist, dass man sogar einen einfachen Syslog-Server in Powershell schreiben kann
- Syslog
- syslogsender.ps1
http://wannemacher.us/?page_id=27
http://wannemacher.us/wp-content/uploads/2009/01/syslogsender.ps1 - SysLogServer in PowerShell
http://www.indented.co.uk/index.php/2009/12/01/syslog-in-powershell/
Powershell und SQL
Neben den CSV und XML-Dateien gibt es natürlich noch andere Datenquellen, die man "Anzapfen" kann. Dazu zählt natürlich auch SQL, welcher über den "System.Data.SqlClient.SqlDataAdapter" sehr einfach angesprochen werden kann. Die folgende Codesequenz ist stark verkürzt und erstellt keine Hilfsklasse für die SQL-Connection und SQL-Commands, sondern nutzt direkt den SQLDataAdapter. Für einfache einmalige Anfragen sollte das aber akzeptabel sein.
write-host "Reading ExludeUser from SQL"
[string]$SQLConnection = "Server=Server\instanzname,port;Database=datenbank;Integrated Security=True"
[string]$SQLQuery = "Select * from tabellenname"
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter ($SQLQuery,$SQLConnection)
$DataSet = New-Object System.Data.DataSet
$result = $SqlAdapter.Fill($DataSet)
if ($result -ne 1) {
write-error "Keine SQL-Daten erhalten"
exit
}
write-host " erhaltene tabellen: $result"
$count = 0
$DataSet.Tables[0] | %{
$count = $count +1
write-host " SQLTable ($count):" $_
}
Powershell und Excel
Viele Firmen und Admins nutzen natürlich Excel, um Daten strukturiert zu verwalten. Natürlich kann man auch diese Datenquellen per Powershell ansprechen. Hier muss man sich überlegen, ob die Datenquelle als "Datenbank (ODBC)" oder über die Excel-Objektmodelle angesprochen werden sollen. Der Zugriff über die COM-Schnittstelle von Excel erfordert natürlich ein installiertes Excel. Der Zugriff über die Datenbanktreiber geht über die MDAC-Schnittstelle, aber erfordert dass die Tabelle dann auch entsprechend aufbereitet ist.
# Excel starten
$excel = New-Object -comobject Excel.Application
# für die Fehlersuche sollte man Excel vielleicht anzeigen
$excel.Visible = $True
# ExcelDatei laden
$arbeitsmappe = $excel.Workbooks.Open("c:\temp\test\test.xlsx")
# Erstes Worksheet öffnen
$seite = $arbeitsmappe.Worksheets.Item(1)
write-Host "Aktuelles Worksheet ist " $seite.name
write-Host "Inhalte von Zelle1, Spalte1 " $seite.Cells.item(1,1).value()
# Excel verlassen
$excel.quit()
Aufgrund der "interpretierenden Funktion" der Powershell kann man natürlich sehr einfach "schrittweise" die Funktionen auch ausführen.
- MDAC 2.8: (Windows 2003)
http://www.microsoft.com/downloads/details.aspx?familyid=6C050FE3-C795-4B7D-B037-185D0506396C&displaylang=en
http://msdn.microsoft.com/en-us/data/default.aspx - How Can I Use Windows PowerShell to Automate
Microsoft Excel?
http://blogs.technet.com/heyscriptingguy/archive/2006/09/08/how-can-i-use-windows-powershell-to-automate-microsoft-excel.aspx - Kent Finkle's Code Page
http://kentfinkle.com/PowershellAndExcel.aspx
Export, Import und Convert
Eine der stärksten Funktionen der Powershell sind die leistungsfähigen Comandlets zum Export und Import von Daten.
- Export-CSV und Import-CSV
Einfache Funktion, um Objekte als Listen auszugeben. Ideal für Benutzerlisten, Gruppen etc. zur weiteren Auswertung oder Import in Excel oder andere Datenbanken. Per Import wird eine CSV-Datei schon fast zum universellen Transferformat z.B. für Adressen und Kontakten. - ExportCliXML und Import-CLiXML
Die erzeugte XML-Datei ist nicht unbedingt einfach zu lesen, aber sowohl für "Imports" als auch zur automatischen Weiterverarbeitung oder Anzeige per Stylesheets geeignet. Hier ein Beispile
<Objs Version="1.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
<Obj RefId="RefId-0">
<MS>
<S N="ColumnA">Data1</S>
<S N="ColumnB">Data2</S>
<S N="ColumnC">Data3</S>
</MS>
</Obj>
</Objs>
- Convert-HTML
Diese nur als "Export" nutzbare Funktion erzeugt aus den Daten eine einfache HTML-Datei, die auch direkt angezeigt werden kann.
Das Schöne ist, dann die Commandlets ebenfalls wieder "Objekte" liefern. Wenn man mit Export-CSV oder Export-CliXML die Ergebnisse eines Prozesses als Datei ablegt, kann man sie sehr einfach wieder importieren und weiterverwenden.
Powershell und "Includes"
Als alter VBScript-Programmierer habe ich natürlich die Funktion von "Klassen" schätzen gelernt. Leider steht hierzu in der Powershell Hilfe wörtlich.
Although it is possible to create a class in Windows PowerShell, it’s not a very straightforward process and definitely goes beyond the scope of this introductory manual. So, for the time being, forget we even mentioned it.
Vielleicht soll man Klassen einfach als Comandlet mit Visual Studio entwickeln. Aber man kann Funktionen in anderen Skripten auslagern und einfach einbinden. Man muss nur darauf achten ,dass man die Funktionen VOR dem Aufruf einbindet. Dabei hilft einem das Commandlet "Invoke-Expression", welches einen String als Befehl ausführt. Damit man sicher ist, dass der String auch als Powershell Skript verwendet wird kann man bei der Deklaration mit "[scriptblock]" arbeiten
$Script = "get-Process"
Invoke-Expression $Script
Man kann sogar ganze Dateien damit einbinden. Hier ein "Hauptprogramm", welches Code einer Subrouine einbindet und aufruft bzw. nutzt.
# include-main.ps1 Write-Host "Include-main started" $test1="test1" write-host "Include-main Test1=$test1" get-content -Path ".\include-part.ps1" | Invoke-Expression write-host "Include-main Test2=$test2" Write-Host "Include-main ended"
# include-part.ps1 Write-Host "Include-part started" write-host "Include-part Test1=$test1" $test2 = "test2" write-host "Include-part Test2=$test2" Write-Host "Include-part ended"
Zwar unterstützt Powershell auch Klassen und Sie können direkt COM und.NET Klassen nutzen, die sie auch it Visual Studio Express entwickeln können, aber so kann man als "Skript only" auch sich eine Sammlung mit "Libraries" erstellen und einbinden, wenn man sie nicht direkt mit Add-PSSnapin in die Umgebung addieren will.
Es gibt noch einen weiteren Weg: Man kann einfach das andere Skript direkt aus dem eigenen Skript aufrufen.
&C:\temp\tmp.ps1
Leistungsfähig wird diese Funktion, wenn man den Code sogar auf einer Kommandozeile angeben kann. Insofern können viele Dinge so für Anwender geöffnet werden, z.B. indem der Anwender selbst Code "einbinden" kann. Analog gibt es noch Invoke-Item.
- Protecting Against Malicious Code Injection
http://blogs.msdn.com/powershell/archive/2006/11/23/protecting-against-malicious-code-injection.aspx
Powershell signieren und Execution Policy
Je nach Einstellung führt die Powershell ein Skript gar nicht, mit einer Warnung oder ohne Rückfrage aus. Man kann die Powershell nämlich anweisen, eine digitale Signatur eines Skripts zu prüfen und entsprechend die Ausführung zu erlauben oder zu unterbinden. Gesteuert wird das Verhalten über den Befehl "SET-ExecutionPolicy", welcher die Einstellung annimmt.
- Set-ExecutionPolicy Restricted
Es werden gar keine Skripte ausgeführt. Nur interaktive Befehle sind möglich - Set-ExecutionPolicy AllSigned
Alle ausgeführten Skripte müssen durch eine vertrauenswürdige Quelle signiert sein. Ansonsten erfolgt eine Rückfrage. Unsignierte Skripte werden nicht ausgeführt - Set-ExecutionPolicy RemoteSigned
Skripte werden ausgeführt. Vor Skripten, die per Internet Explorer o.ä. herunter geladen wurden (und damit einer Internet Zone zugewiesen sind) wird erwartet, dass Sie eine digitale Signatur haben. - Set-ExecutionPolicy Unrestricted
Alle Skripte werden ausgeführt. Vor Skripten, die per Internet Explorer o.ä. herunter geladen wurden (und damit einer Internet Zone zugewiesen sind) wird gewarnt. Es wird keine digitale Signatur erwartet.
Über den Befehl "GET-EXECUTIONPOLICY" können Sie die aktuelle Einstellung abfragen.
Bei einem Windows 2008 R2 Domain Controller ist per Default die Execution Policy auf "restricted" gestellt.
Powershell COM-Objekte
So leistungsfähig die direkt nutzbaren .NET-Klassen sind, so sind viele wichtige Lösungen heute nur als COM-Objekte verfügbar. Dazu zählen natürlich Outlook, Word, Excel aber auch viele Hilfsprogramme, die über die COM-Schnittstelle einfach wieder zu verwenden sind. In den nächsten Abschnitten sehen Sie, wie Powershell sogar für die einfache Ausgabe einer Messagebox den Windows Skripting Host missbrauchen muss.
Es ist sehr einfach, eine Instanz eines COM-Objekts zu erhalten. Weisen Sie es einfach zu:
$variable = new-object -com name_des.comobjects
Danach können Sie "fast" unbegrenzt über das COM-Objekt verfügen.
- PowerShell - Ask Merlin: a cool demo of using COM objects
http://community.bartdesmet.net/blogs/bart/archive/2006/12/02/PowerShell-_2D00_-Ask-Merlin_3A00_-a-cool-demo-of-using-COM-objects.aspx - Introduction to Scripting - COM Objects with PowerShell
http://www.computerperformance.co.uk/powershell/powershell_com.htm
Aber beachten Sie dazu auch den folgenden Abschnitt über STA und MTA
Single Thread Appartment (STA) und Multi Thread Appartment (MTA)
Durch den Einsatz von "Redemption" und anderen COM-Objekten wurde ich unfreiwillig auf einen maßgeblichen Unterschied der Powershell im Vergleich zu VBScript aufmerksam. Ein kleiner 5-Zeiler, welcher einfach nur per COM-Objekt eine Mailbox öffnet hat mich mehrere Stunden suche gekostet. Folgendes war passiert:
- VBScript funktioniert
- Powershell liefert Fehler
- C# Konsolenanwendung liefert Fehler
- C# Windows Forms Anwendung funktioniert
Zuerst dachte ich, dass ein Skript ohne "Windows Form" natürlich auch keine "Windows Message Pump" hat, mit der die Fenster untereinander Meldungen senden können. Aber mit dem Entwickler von Redemption (gleiches Problem) und der Powershell Newsgroup konnte ich dann die C# Konsolenanwendung "tauglich" machen. Mit einem "[STATHREAD]" vor "main" lief auch diese Anwendung durch.
Insofern scheint VBScript per Default immer im STA-Mode zu laufen, während Powershell 1 per Default "MTA" nutzt. Erst Powershell 2 kann man mit dem Parameter "-STA" in die Single Thread Betriebsart zwingen. Den Status kann man mit folgendem Befehl abfragen:
[System.Management.Automation.Runspaces.Runspace]::DefaultRunspace
Hier eine Beispielausgabe:

Auf dem Powershell Blog wird eine alternative Lösung mit einem Commandlet "Invoke-Appartment" angeboten.
Folgendes kleines Skript prüft den aktuellen Betriebszustand und startet das Skript im STA-Mode neu, wenn dies erforderlich sein sollte
if ($host.Runspace.ApartmentState -neq 'STA') {
write-host "Script is not running in STA mode. Switching "
$Script = $MyInvocation.MyCommand.Definition
Start-Process powershell.exe -ArgumentList "-sta $Script"
Exit
}
Der Abschnitt kann einfach am Anfang des Powershell-Skripts eingefügt werden.
- Thread.ApartmentState and PowerShell Execution Thread
http://blogs.msdn.com/powershell/archive/2007/03/23/thread-apartmentstate-and-powershell-execution-thread.aspx - Calling FolderBrowserDialog from a PowerShell script
http://www.manning-sandbox.com/thread.jspa?messageID=62451 - Checking -STA Mode
http://powershell.com/cs/blogs/tips/archive/2011/01/17/checking-sta-mode.aspx - http://www.cauldwell.net/patrick/blog/ResolvedCallingAzManThroughMMCGtPowerShell.aspx
- http://stackoverflow.com/questions/1508704/does-powershell-sta-mode-eliminate-sharepoint-memory-leak-issue
- http://stackoverflow.com/questions/127188/could-you-explain-sta-and-mta
Powershell und Globaler Katalog
Ich habe immer wieder die Aufgabenstellung, im Active Directory nach Objekten zu suchen und diese zu verarbeiten oder zu berichten. Hier ist ein Codeschnipsel, mit dem ich einfach den GC finde und befrage und danach zu jedem Objekt ein Feld ausgeben lasse.
$root = [system.directoryservices.activedirectory.forest]::getcurrentforest().rootdomain.name
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"GC://$root")
#$objSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"GC://$root",$sourceuser,$sourcepass)
$objSearcher.PageSize = 1000
$objSearcher.Filter = "(&(mail=*))"
$objSearcher.PropertiesToLoad.Add("mail") | Out-Null
$objSearcher.PropertiesToLoad.Add("ProxyAddresses") | Out-Null
$colResults = $objSearcher.FindAll()
foreach ($objResult in $colResults) {
$mail = $objResult.properties.mail[0]
write-host "Processing $mail"
$adobject = [adsi]$objResult.path
write-host "Bound " + $adobject.displayname
}
Natürlich kann man mit dem Directory Searcher auch "schmutzig" programmieren und mit einer Zeile ein Objekt instanzieren, Suchbedingungen vorgeben und das Ergebnis erhalten:
(New-Object System.DirectoryServices.DirectorySearcher("Samaccountname=Administrator")).findone()
Schauen Sie sich einfach die verschiedenen Overloads für die Instanzierung des Objekts an. Interessant ist in dem Zuge auch ein weiterer Accelerator, der den Code noch weiter verkürzt.
([adsisearcher]"Samaccountname=Administrator").findone()
Trotzdem würde ich schon "Get-ADUser" aus den Windows 2008 R2 Powerpacks vorziehen und solche direkten LDAP-Anfragen auf Objekte begrenzen, auf die kein kontrollierter Zugriff per Comandlet möglich ist.
- RSAT AD-Powershell
- Use the PowerShell [adsiSearcher] Type Accelerator
to Search Active Directory
http://blogs.technet.com/b/heyscriptingguy/archive/2010/08/24/use-the-powershell-adsisearcher-type-accelerator-to-search-active-directory.aspx - PowerShell Tutorial 12 – Part 1: Active Directory Management
http://www.powershellpro.com/powershell-tutorial-introduction/powershell-tutorial-active-directory/
Kennworte nutzen
Sehr viele Routinen benötigen ein Kennwort, welches aber "sicher" gespeichert werden muss. Es ist denkbar ungünstig, ein Kennwort im Skript direkt zu hinterlegen. Daher kann man Kennwort z.B. direkt von der Console einlesen und gesichert speichern.
$password = read-host "Enter Password" -asSecureString
Alternativ kann man mit den beiden Commandlets "convertto-securestring" und "convertfrom-securestring" die Werte umwandeln. Wird also bei einem Commandlet ein Parameter "Passwort" erwartet, dann hilft der geklammerte Ausdruck:
new-mailbox -password (convertto-securestring -string "kennwort" -asplaintext -force)
Allerdings ist es generell eine schlechte Idee, Kennworte in einem Skript fest zu hinterlegen. Wenn Sie nicht gleich die "integrierte Authentifizierung" nutzen möchten, d.h. der gerade angemeldete Benutzer, der das Skript ausführt, dann können Sie das Kennwort auch in einer Datei (reversibel) verschlüsselt speichern und importieren.
Powershell und Klassen
In VBScript gibt die die Möglichkeit, Klassen zu definieren und damit Code hinter Objekten zu verstecken und vor allem bestimmte andere Teile aktiv zu halten (z.B. eine LDAP-Verbindung). Das Prinzip der objektorientierten Entwicklung hat die früher oft genutzte prozedurale Programmierung (Funktionen und Prozeduren) bei Hochsprachen fast komplett abgelöst.
Auch Powershell unterstützt Klassen. Allerdings steht dazu in der Dokumentation recht vielsagend:
Class Definition:
Declares the name of a class, as well as a definition of the variables,
properties, and methods that comprise the class.
Although it is possible to create a class in Windows PowerShell, it’s
not a very straightforward process and definitely goes beyond the scope
of this introductory manual. So, for the time being, forget we even
mentioned it.
Auch wenn es syntaktisch geht, scheinen die Entwickler diesen Weg nicht weiter öffnen zu wollen. Gut finde ich es nicht aber wenn man heute also eigene Klassen bauen will, dann wird man eben Visual Studio anwerfen und sich die passenden Klassen als .NET-DLLs oder Commandlets schreiben.
Powershell und Outlook
Alternativ kann man mit der Powershell natürlich auch das Outlook Objektmodell ansprechen. Allerdings kommt hier wieder verschiedene Warnungen, z.B. wenn sie automatisch auf den Kontaktordner lesend zugreifen wollen.$olAppointmentItem = 1;
$olFolderDeletedItems = 3;
$olFolderOutbox = 4;
$olFolderSentMail = 5;
$olFolderInbox = 6;
$olFolderCalendar = 9;
$olFolderContacts = 10;
$olFolderJournal = 11;
$olFolderNotes = 12;
$olFolderTasks = 13;
$olFolderDrafts = 16;
$olPublicFoldersAllPublicFolders = 18;
$olFolderConflicts = 19;
$olFolderSyncIssues = 20;
$olFolderLocalFailures = 21;
$olFolderServerFailures = 22;
$olFolderJunk = 23;
$outlook = new-object -com outlook.application;
$ns = $outlook.GetNameSpace("MAPI");
$inbox = $ns.GetDefaultFolder($olFolderInbox)
$inbox.Items | foreach { $_.subject }
Ein Zugriff auf die gleichen Funktionen per CDO ist übrigens nicht möglich, da hier das Multithreading von Powershell mit CDO nicht kompatibel ist.
Powershell User Guide page 33,
"Not all COM objects are supported. Objects that are based on Exchange
Collaboration Data Objects (CDO) are not supported in this release."
813349 Support policy for Microsoft Exchange APIs with the .NET
Framework applications
Exchange Proxy Addresses
Gerade die Anpassung von Proxy-Addresses ist eine häufige Aufgabenstellung für Exchange Administratoren. Auch das gestaltet sich relativ einfach, z.B.:
$mb = get-mailbox fcarius
$mb.EmailAddresses += "smtp:frank.carius@msxfaq.test","fkcarius@msxfaq.test"
$mb | set-mailbox
Wenn es nur eine Adresse ist, kann man das auch direkt auf der Powershell machen
Set-Mailbox fcarius -EmailAddresses
@{Add=’fkarius@msxfaq.test’}
Set-Mailbox fcarius -EmailAddresses
@{Remove=’fkarius@msxfaq.test’}
Selbst in Exchange 2007 und höher schon lange nicht mehr geläufige Adressen wie X.400, MSMail, CC:Mail können so einfach addiert werden
$mb.emailaddresses += "X400:C=DE;.. "
- HOW TO: Add additional email
addresses to a recipient
http://exchangepedia.com/2007/03/how-to-add-additional-email-addresses-to-a-recipient.html
Microsoft PowerShellPack
Auch Microsoft ist ja nicht ganz inaktiv. Auf http://code.msdn.microsoft.com/PowerShellPack gibt es ein Paket mit zusätzlichen Befehlen, die sie einfach einbinden können. Hier ein Auszug der "FileSystem"-Optionen
PS > Import-Module FileSystem PS > Get-Command -Module FileSystem CommandType Name Definition ----------- ---- ---------- Function Copy-ToZip ... Function Get-DuplicateFile ... Function Get-FreeDiskSpace ... Function Get-SHA1 ... Function Mount-SpecialFolder ... Function New-Zip ... Function Rename-Drive ... Function Resolve-ShortcutFile ... Function Start-FileSystemWatcher ...
Interessant ist hier z.B. die Möglichkeit zu Packen und Entpacken aber auch der "FileSystemWatcher" ist durchaus nicht zu verachten.
Weitere Links
-
PowerShell4Admin
Powershell - Eine Einführung für Admins - PSRemote
- WinRM
-
Jeffrey Snover: Der Mann der Powershell quasi "erfunden" hat
http://www.microsoft.com/presspass/exec/de/snover/default.mspx -
Powershell Blog
http://blogs.msdn.com/b/powershell/ -
TechNet Magazin: Remote Powershell
http://technet.microsoft.com/de-de/magazine/2009.08.windowspowershell.aspx -
Layman’s guide to PowerShell 2.0 remoting
http://www.ravichaganti.com/blog/?p=1780 -
Mastering PowerShell eBook'
http://powershell.com/Mastering-PowerShell.pdf -
Administrator's Guide to PowerShell Remoting (Kostenfreies ebook)
http://powershell.com/cs/blogs/news/archive/2010/03/03/administrator-s-guide-to-powershell-remoting.aspx -
Powershell und Visual Studio
Templates für eigene Powershell Snapins in Visual Studio installieren - http://gsexdev.blogspot.com/2005/11/using-monad-and-wmi-with-exchange-2003.html
- http://dmitrysotnikov.wordpress.com/2007/09/06/wmi-powershell-for-exchange-2003/
- PowerShell Tutorial 12 – Part 1: Active Directory Management
http://www.powershellpro.com/powershell-tutorial-introduction/powershell-tutorial-active-directory/ - Mastering PowerShell in your Lunch Break (L>INK NICHT MEHR GÜLTIG, Suche
nach einem Spiegel)
Day 1: Getting Organized http://powershelllive.com/blogs/lunch/archive/2007/03/26/day-1-getting-organized.aspx
Day 2: Writing Scripts and Translating VBScript
http://powershelllive.com/blogs/lunch/archive/2007/03/28/day-2-writing-scripts-and-translating-vbscript.aspx
Day 3: Discovering objects (COM, WMI & ADSI)
http://powershelllive.com/blogs/lunch/archive/2007/03/29/day-3-objects-wmi-and-adsi.aspx
Day 4: Ins and Outs of the Windows Registry
http://powershelllive.com/blogs/lunch/archive/2007/03/31/day-5-ins-and-outs-of-the-windows-registry.aspx
Day 5: Using WMI
http://powershelllive.com/blogs/lunch/archive/2007/04/02/day-5-using-wmi.aspx
Day 6: ADSI Connecting to Domains/Computers and Binding to Objects
http://powershelllive.com/blogs/lunch/archive/2007/04/04/day-6-adsi-connecting-to-domains-computers-and-binding-to-objects.aspx
Day 7: Manage Users
http://powershelllive.com/blogs/lunch/archive/2007/04/05/day-7-manage-users.aspx -
Powershell Challenge (Phyton Challenge)
http://thepowershellguy.com/blogs/posh/archive/tags/PoSH+Challenge/default.aspx - Windows Forms Table
http://gsexdev.blogspot.com/2007/12/mailbox-folder-size-comparison.htm - Amazing But True: Things You Never Dreamt You Could Do With
Windows PowerShell.
https://www.livemeeting.com/cc/mseventsbmo/view?id=1032313506&role=attend&pw=48928A1E - Windows PowerShell Looking Good
http://technet.microsoft.com/en-us/magazine/cc510337.aspx - Windows PowerShell Week Question and Answer Log
http://www.microsoft.com/technet/scriptcenter/webcasts/psweek/day3qanda.mspx - Managing Exchange 2007 Recipients with C#
http://knicksmith.blogspot.com/2007/03/managing-exchange-2007-recipients-with.html - ASP.Net nutzt Powershell für Anlegen einer Mailbox
http://blogs.msdn.com/akashb/archive/2009/01/30/howto-using-powershell-in-asp-net-net-framework-2-0.aspx - Active Directory Administrative Center for Windows Server 2008
R2 implemented using ADPowershell!
http://blogs.msdn.com/adpowershell/archive/2009/03/27/active-directory-administrative-center-for-windows-server-2008-r2-implemented-using-adpowershell.aspx - Active Directory Administrative Center: Getting Started
http://technet.microsoft.com/en-us/library/dd560651.aspx - Packet Sniffer For Powershell
http://blog.robbiefoust.com/?p=68 - Keith Hill Blog
http://keithhill.spaces.live.com
Umfangreiche Beschreibung zu Powershell Schritten und im SkyDrive liegen einige interessante Samples - Peter’s PowerShell Blog (German only)
http://powershellcrashkurs.wordpress.com/









