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, das_s 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
Powershell Cookbook
http://www.pavleck.net/powershell-cookbook/

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

Powershell im Dauereinsatz

Die meisten Powershell-Skripte werden gestartet und beenden sich danach wieder. Wer hingegen Powershell als "Dauerläufer" einsetzen will, muss sich genauso um die Speicherverwaltung kümmern, wie andere Programmierer auch. Wer Objekte instanziert und nicht explizit wieder frei gibt, der gibt auch den Speicher dazu nicht frei. Das gilt insbesondere für COM und NET-Objekte. Beachten Sie daher den Speicherbedarf ihrer Skripte, die nicht immer wieder manuell oder durch den Taskmanager gestartet und beendet werden.

# Variablen werden wie folgt frei gegeben
Remove-Variable Variablenname

# Freigeben von ADSI-Objekten
mySearchRoot.Dispose(); 
myDirectoryEntry.Dispose();

# COM-Objekte (z.B: Word) werden mit $null geleert
$objWord = $null

# Der Garbage Collection-Prozess muss manuell gestartet werden
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()

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

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)

Messagebox

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:

Read-Host

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 Audiodatein.

# 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 `
    -smtpServer mail.example.com `
    -from "User01@example.com" `
    -to "User02@example.com", "User03@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

Mit Powershell 3 ist das Comandlet "Get-Content" leistungsfähiger geworden und kann nun auch direkt ans Ende springen oder sogar eine vorgegebene Anzahl von Zeilen zurück lesen und warten.

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

Powershell und FileSystemWatcher 

Natürlich kann man durch die Angabe einer Zeit ("all",10000) nach 10 Sekunden eine Rückkehr erzwingen oder gleich über Events arbeiten.

Aktueller Benutzer ermitteln

Solche Funktionen sind natürlich besonders interessant, wenn z.B. Powershell für Automatisierungen, Anmeldeskripte o.ä., genutzt wird. Dabei hilft natürlich wieder .NET mit der Klasse "[System.Security.Principal.WindowsIdentity]::GetCurrent()" und ihren Properties.

PS C:\>[System.Security.Principal.WindowsIdentity]::GetCurrent()

AuthenticationType : Kerberos
ImpersonationLevel : None
IsAuthenticated    : True
IsGuest            : False
IsSystem           : False
IsAnonymous        : False
Name               : NETATWORK\fcarius
Owner              : S-1-5-21-19119449-30417519-71842111-1009
User               : S-1-5-21-19119449-30417519-71842111-1009
Groups             : {S-1-5-21-19119449-30417519-71842111-513, 
                      S-1-1-0, S-1-5-21-1894181349-4012846640-3243972333-1008, 
                      S-1-5-32-545...}
Token              : 2268

Wie sie sehen ist es ganz einfach den aktuellen Benutzernamen und die Anmeldung zu ermitteln. Sogar die "Tokensize" ist direkt ablesbar. Mit wenigen weiteren Funktionen lassen Sich z.B. die SIDs der Gruppen entsprechend konvertieren. Denn jede Gruppe in der Auflistung "Groups" hat wiederum eine "Translate"-Methode.

$group = [System.Security.Principal.WindowsIdentity]::GetCurrent().groups[0]
Write-host "Erster Gruppe:" $group.Translate([System.Security.Principal.NTAccount]).tostring()

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:

function Funktionsname ([int]$param1=10,[string]param2=msxfaq.de) {
    # Befehle
}

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.

[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

[string]$variablenname = "Beispielwert"

[Parameter(
   Position=0,'
   Mandatory=$true,
   HelpMessage="Dies ist eine Hilfe",
   ValueFromPipeline=$false,
   ParameterSetName="Setname"]
)][string]$variablenname

# 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]

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.

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.

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 Code-Teil 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. Oder sie setzen den Wert der Schleifenvariable vorher mit "@()" auf eine leere Liste. Trotzdem würde ich das als "unerwartet" ansehen.

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:

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 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 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.

Export, Import und Convert

Eine der stärksten Funktionen der Powershell sind die leistungsfähigen Comandlets zum Export und Import von Daten.

<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>

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" mit "Invoke-Expression"

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.
Quelle: Converting VBScript's Class Statement http://technet.microsoft.com/en-us/library/ee156807.aspx

Es geht aber doch, Klassen in Powershell 2 zu definieren. Siehe Powershell Klassen.

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"

Achtung:
Der Include-Block wird ausgeführt, als wäre er im Hauptprogramm selbst enthalten. Es ist ein echter Include zur Laufzeit.

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.

Powershell und andere DLLs und Klassen

Ein großer Vorteil von Powershell ist die Möglichkeit, jede .NET-DLL relativ einfach einzubinden und die darin enthaltenen Objekte zu nutzen. Auf PS Klassen habe ich zwar gezeigt, wie man mit einer Hilfskonstruktion auch eine Art Klasse in Powershell selbst erstellen kann, aber ratsam ist dies nur für ganz kleine Aufgabenstellungen. Früher oder später sollten Sie doch in C# mit Visual Studio oder einer anderen Umgebung größere Aufgaben als DLL erstellen.

[Reflection.Assembly]::LoadFile("D:\mydll.dll")
$obj = [msxfaq.sampleclass]::Global.AnyBuddy

Oder Sie nutzen die Funktion direkt in Powershell Klassen mit "Add-Type" zu erstellen.

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.

Ü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.

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:

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.

Powershell und Globaler Katalog (GC:)

Leider funktioniert in Powershell nicht der VBScript weg einfach ein "GC://" zu verwenden. Es muss schon ein bestimmter Server mit gegeben werden.

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()

Allerdings nutzt dieser Searcher dann wieder nur die Default Domäne des angemeldeten Benutzers und keinen GC. Um dies zu ändern, muss man aber nun New-Object verwenden, da ADSISEARCHER keine erweiterten Konstruktoren kann.

(New-Object System.DirectoryServices.DirectorySearcher([ADSI]"GC://servername","Samaccountname=Administrator")).findone()

So geht es dann auch schnell und relativ schmutzig. Zugegeben, ein Zugriff per Get-ADUser aus den Windows 2008 Commandlets ist strukturierter aber leider auch viel langsamer, da diese Commandlets die Active Directory Web Services nutzen. Aber dafür funktionieren Sie auch über HTTP/HTTPS und benötigen keine direkte LDAP-Verbindung.

32bit/64bit

Seit Exchange 2010 gibt es die Commandlets und Management Console nur noch als 64bit Version. Für Exchange 2007 gab es noch 32bit Versionen. Als Administrator muss man sich aber dennoch mit solchen "Besonderheiten" herum schlagen, da Windows eine Funktion hat, um immer die "richtige" Version zu starten. Zumindest nach dem was Windows als "Richtig" ansieht. Wer also aus einem 32bit Programm heraus einfach "Powershell.exe" aufruf, bekommt auch immer die 32bit Version. Startet man hingegen aus einer 64bit Umgebung die "Powershell.exe", dann hat man auch eine 64bit Powershell. Was mache ich aber nun, wenn ich aus einem 32bit Programm auch wirklich die 64bit Version einer Powershell anstarten will, z.B. weil ich einen PRTG Custom Sensor schreiben will, der Exchange Funktionen nutzt. ?

Wer nun etwas im Windows Verzeichnisbaum rumstöbert, wird mehrere "Powerhell.exe"-Dateien finden.

C:\Windows>dir powershell.exe /s
 Datenträger in Laufwerk C: ist NAWNBFC-C
 Volumeseriennummer: xxxx-xxxx

 Verzeichnis von C:\Windows\System32\WindowsPowerShell\v1.0

14.07.2009  02:39           473.600 powershell.exe
               1 Datei(en),        473.600 Bytes

 Verzeichnis von C:\Windows\SysWOW64\WindowsPowerShell\v1.0

14.07.2009  02:14           452.608 powershell.exe
               1 Datei(en),        452.608 Bytes

Wer nun aber glaubt er startet per direkter Pfadangabe einfach die "richtige" Powershell, der Irrt. Hier mal ein Test unter Windows 7 64bit. Es kann zudem irritieren, dass das Verzeichnis "System32" die 64bit-Versionen beinhaltet während SysWOW64 den 32bit Code enthält. Die folgende Tabelle soll zeigen, welches Ergebnis beim Aufruf der DLLs erhalten wird.

Startumgebung Aufruf von Ergebnis
CME.EXE 64bit C:\Windows\System32\cmd.exe 64bit CMD
CME.EXE 64bit C:\Windows\SysWOW64\cmd.exe 32bit CMD
CME.EXE 64bit C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe 64bit PS
CME.EXE 64bit C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe 32bit PS
CME.EXE 32bit C:\Windows\System32\cmd.exe 32bit CMD !
CMD.EXE 32bit C:\Windows\SysWOW64\cmd.exe 32bit CMD
CMD.EXE 32bit C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe 32bit PS !
CMD.EXE 32bit C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe 32bit PS
CMD.EXE 32bit C:\Windows\Sysnative\WindowsPowerShell\v1.0\powershell.exe 64bit !!

Interessant dabei ist, dass eine 64bit Startumgebung bei Aufrufen der 32bit Versionen auch 32bit Umgebungen startet. Umgekehrt muss man aber den Spezialordner "SysNative" verwenden, da ansonsten ein 32bit Process zwar die 64bit Version aufrufen kann, aber der File System Redirector doch wieder die 32bit Version startet.

Welche Version der Powershell gerade läuft, lässt sich mit der Größenbestimmung einer Variable einfach ermitteln.

if ([System.IntPtr]::Size -eq 8) {
    write-host 64,":64bit Session"
}
else {
    write-host 32,":32bit Session"
}

[System.IntPtr]::Size hat den Wert 8 bei 64bit uud den Wert 4 bei 32bit Powershell.

You can temporarily disable filesystem redirection around the call to Process. Start, the appropriate API's to P/Invoke are Wow64DisableWow64FsRedirection and Wow64RevertWow64FsRedirection. Another option is to use %windir%\sysnative, which is available on Windows Vista and above.
Quelle: Verschiedene Beiträge in Newsgroups. Urheber nicht mehr auszumachen.

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 GUIDs

An vielen Stellen unter Windows wird mit GUIDs gearbeitet. Diese aufgrund ihrer Länge eigentlich als "eindeutig" anzusehenden Nummer kommt bei RPC-Protokollen, COM+ Schnittstellen, Exchange Mailboxen, AD-Objekten und anderswo vor. Powershell und .NET erlauben sehr viel einfacher als VBScript einen Umgang damit. Ein paar Beispiele:

$objUser = [adsi]"GC:// CN=Administrator,CN=Users,DC=msxfaq,DC=net"
 
# Ausgabe der GUID als String
$objUser.guid
 
# Ausgabe als Array of byte
$objUser.objectguid
 
# Ausgabe als GUID mit Bindestrichen
[guid]($objUser.guid)

# Neue GUID erstellen
[guid]::NewGuid()

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 Events

Es gibt noch andere "Events" in Powershell, welche automatisch Code ausführen können. Die können auf Timer aber auch WMI-Vorgänge triggern. Allerdings sind diese nur solange aktiv, wie die Powershell-Box auch offen ist. Bislang setzte ich diese nicht ein.

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;.. "

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.

Quest Powershell Tools

Quest ist ja nun eine nicht ganz unbekannte Firma im Bereich Migration, Management, Monitoring etc. Für Powershell Administratoren gibt es zwei sehr interessante Produkte, die zudem kostenfrei sind und ich auch selbst gerne verwende:

Add-PSSnapin Quest.Activeroles.ADmanagement

Nicht vergessen darf man natürlich auch das Wiki und die Beispielcodes rund um PowerGUI und natürlich das Blog von Dimitrji Sotnikov, dem Kopf hinter PowerGui und Powershell MVP.

Weitere Links

Keywords:Powershell Tools Samples