Zum Inhaltsverzeichnis
---------------------------------------------------------------------------------------
2 Laufwerke, Ordner, Dateien und Freigaben (Ohne ACL)
2.1 Dateien und Ordner
Unter
2.1.1 Inhalt und Struktur von Verzeichnissen
Beispiel 1: Größe eines Verzeichnisses
$Path="C:\temp"
if(Test-Path -path $Path){
$Length=(gci c:\temp -Recurse | Measure-Object -Property "Length" -Sum).Sum
$AnzahlDateien=(gci c:\temp -Recurse | Where {$_.PSIsContainer -eq $False} |Measure-Object).Count
$AnzahlOrdner=(gci c:\temp -Recurse | Where {$_.PSIsContainer -eq $True} | Measure-Object).Count
}else{
"Pfad {0} nicht vorhanden" -f $Path
}
"Größe mit Unterverzeichnissen in MB: {0:n2}" -f $($Length/1MB)
"Anzahl Dateien: {0}" -f $AnzahlDateien
"Anzahl Ordner: {0}" -f $AnzahlOrdner
#getestet unter PSH V2.0
#mögliche Ausgabe
Größe mit Unterverzeichnissen in MB: 34,81
Anzahl Dateien: 847
Anzahl Ordner: 14
Technet:
Die Eigenschaft "PSIsContainer" ist eine Möglichkeit festzustellen, ob ein Objekt eine Datei oder ein Container ist. In Beispiel 4 im
Beispiel 2: Dateitypen im Verzeichnis
$RootPath = "c:\temp\homes\"
Get-ChildItem $RootPath -Recurse | Group-Object Extension | Sort-Object count -Descending | Format-Table Count,Name -Auto
#getestet unter PSH V2.0
#mögliche Ausgabe
Count Name
----- ----
112 .txt
20 .nul
4 .ps1
3
2 .xlsx
1 .doc
1 .pdf
Technet:
Beispiel 3: Speicherplatz pro Dateityp im Verzeichnis
$RootPath="C:\temp\homes"
$fileGroups = Get-ChildItem -Path $RootPath -Recurse -Force| Where{ $_.PSIsContainer -eq $False } | Group-Object Extension
$filegroups = $filegroups | sort name
"`nRootPath: $RootPath"
$Headline= "`n{0,-10} {1,-11} {2,-10} " -f "Extension","Anzahl Dateien","Größe in MB"
Write-Host $Headline -Backgroundcolor Red
ForEach ( $fileGroup in $fileGroups ) {
$SumLength=($fileGroup.Group | Measure-Object -Property "Length" -Sum).Sum
"{0,-13} {1,5} {2,17:n2}" -f $fileGroup.Name,$fileGroup.count,$($SumLength/1MB)
}
#getestet unter PSH V2.0
#mögliche Ausgabe
RootPath: C:\temp\homes
Extension Anzahl Dateien Größe in MB
.doc 126 2,72
.html 20 0,37
.log 26 0,68
.nul 180 0,00
.pdf 13 0,23
.ppt 90 9,40
.ps1 20 0,00
.txt 502 1,65
.xls 24 0,49
ausreichende Berechtigungen auf alle Unterverzeichnisse und Dateien sind natürlich Voraussetzung.
Beispiel 4: Alterstruktur der Dateien im Verzeichnis
Leider lässt sich hier das cmdlet Measure-Object nicht so verwenden, wie im letzten Beispiel. Daher ist die Auswertung nicht so elegant und kurz
Remove-Variable -scope script * -EA 0
$RootPath = "c:\temp\homes\"
$Files=@(Get-ChildItem $RootPath -recurse | Where {$_.psIsContainer -eq $false} | select lastaccesstime,fullname,length)
$Files | foreach {
$AlterInTagen=([system.datetime]::today-$_.LastAccessTime).Days
if ($AlterInTagen -ge 1080) {
$3YearsCount++
$3YearsLength += $_.Length
}elseif ($AlterInTagen -ge 360){
$1YearCount++
$1yearLength += $_.Length
}elseif ($AlterInTagen -ge 180){
$6MonthCount++
$6MonthLength += $_.Length
}else{
$0MonthCount++
$0MonthLength += $_.Length
}
}
"`nRootPath: $RootPath"
$Headline= "`n{0,-10} {1,-11} {2,-10} " -f "LastAccessTime","Summe Anzahl","Summe Größe in MB"
Write-Host $Headline -Backgroundcolor Red
"{0,-15} {1,9} {2,13:n2} " -f ">3 Jahre",$3yearsCount,$($3YearsLength/1MB)
"{0,-15} {1,9} {2,13:n2} " -f ">1 Jahr",$1YearCount,$($1YearLength/1MB)
"{0,-15} {1,9} {2,13:n2} " -f ">6 Monate",$6MonthCount,$($6MonthLength/1MB)
"{0,-15} {1,9} {2,13:n2} " -f "<6 Monate",$0MonthCount,$($0MonthLength/1MB)
"`n"
"Gesamtzahl Dateien: $($Files.count)"
"Gesamtzahl Ordner: $($Folder.count)"
Write-Host $Headline -Backgroundcolor Red
"{0,-15} {1,9} {2,13:n2} " -f ">3 Jahre",$3yearsCount,$($3YearsLength/1MB)
"{0,-15} {1,9} {2,13:n2} " -f ">1 Jahr",$1YearCount,$($1YearLength/1MB)
"{0,-15} {1,9} {2,13:n2} " -f ">6 Monate",$6MonthCount,$($6MonthLength/1MB)
"{0,-15} {1,9} {2,13:n2} " -f "<6 Monate",$0MonthCount,$($0MonthLength/1MB)
"`n"
"Gesamtzahl Dateien: $($Files.count)"
"Gesamtzahl Ordner: $($Folder.count)"
#getestet unter PSH V2.0
#mögliche Ausgabe
RootPath: c:\temp\homes\
LastAccessTime Summe Anzahl Summe Größe in MB
>3 Jahre 40 1,48
>1 Jahr 65 3,39
>6 Monate 115 1,06
<6 Monate 651 2,73
Gesamtzahl Dateien: 871
Gesamtzahl Ordner: 5
- Zur Verwendung der Eigenschaft "LastAccessTime" muss ab Windows Vista der RegistryKey NtfsDisableLastAccessUpdate unter HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem auf 0 geändert und der Rechner gebootet werden.
- Die Formatierung ist im Kapitel
- Mit Remove-Variable -scope script * -EA 0 entferne ich alle Variablen aus dem Speicher. Dies ist ganz nützlich, wenn man das Skript in der Powershell_ISE mehrfach hintereinander ausführt, da sich sonst die Werte bei jedem Skriptaufruf aufsummieren würden. -EA 0 unterdrückt Fehlermeldungen, wenn Variable wie $false nicht entfernt werden können.
2.1.2 einfache Operationen mit Dateien und Verzeichnissen (Löschen, Kopieren, Verschieben, Erstellen)
Die folgenden Unterkapitel behandeln die Grundfunktionen wie Kopieren, Löschen, Verschieben, Komprimieren und Zippen von einzelnen Dateien und Verzeichnissen.
Im anschließenden Kapitel
2.1.2.1 Erstellen und Löschen von Objekten im Filesystem
Beispiel 1a: Anlage eines Verzeichnisses (Fileprovider)
Technet:
$Path="C:\Temp\Test"
New-Item $Path -Type Directory -EA 0
if($? -ne "true"){
"Verzeichnis wahrscheinlich schon vorhanden"
}
#getestet unter PSH V2.0
#mögliche Ausgabe
Verzeichnis: C:\Temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 15.10.2011 23:20 Test
#mögliche Ausgabe
Verzeichnis wahrscheinlich schon vorhanden
Falls das Verzeichnis schon existiert, wirft New-Item einen Fehler (IOException), den ich mit der PreferenceVariable -EA 0 (= ErrorAction SilentlyContinue) aber unterdrücke.
Mit der AutomaticVariable $? frage ich ab, ob der letzte Befehl einen Fehler geworfen hat. Falls das Verzeichnis erstellt werden konnte, ist dies nicht der Fall. Andernfalls wird die Meldung "Verzeichnis wahrscheinlich schon vorhanden" ausgegeben.
Technet:
Beispiel 1b: Anlage eines Verzeichnisses (.Net-Klasse Directory)
MSDN:
$Path="C:\Temp\Test"
[System.IO.Directory]::CreateDirectory($Path)
#mögliche Ausgabe
Verzeichnis: C:\Temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 15.10.2011 23:20 Test
Diese Ausgabe wird immer von der Klasse Directory zurückgegeben. Hat die Anlage des Verzeichnisse funktioniert, ist die Eigenschaft "LastWriteTime" aktuell, sonst älter. Die Abfrage eines Fehlers mit $? funktioniert mit der .Net Klasse also nicht.
Beispiel 1c: Anlage eines Verzeichnisses (FileSystemObject)
MSDN:
$Path="C:\Temp\Test"
Try{
$fso=New-Object -Com "Scripting.FileSystemObject"
$fso.CreateFolder($Path)
}
Catch{
"Verzeichnis wahrscheinlich schon vorhanden"
}
#getestet unter PSH V2.0
#mögliche Ausgabe gekürzt
Path : C:\temp\Test
Name : Test
..
Attributes : 16
DateCreated : 15.10.2011 23:59:45
Size : 0
mögliche Ausgabe
Verzeichnis wahrscheinlich schon vorhanden
Das Ergebnis aller drei Methoden ist dann dasselbe, wenn das gewünschte Verzeichnis noch nicht existiert und angelegt werden kann.
Unterschiede gibt es, wie man sieht, im Fehlerhandling
Beispiel 2: explizite Anlage einer Datei
Technet:
Eine Datei gesondert anzulegen, ist ebenso wie beim Verzeichnis recht einfach über den Fileprovider möglich
$FilePath="C:\Temp\File.txt"
New-Item $FilePath -Type File -EA 0
if($? -ne "true"){
"Datei wahrscheinlich schon vorhanden"
}
#getestet unter PSH V2.0
In vielen Fällen ist dieser Schritt wahrscheinlich nicht notwendig, da beim Schreiben von Text in eine Datei, diese -sofern noch nicht vorhanden- automatisch erstellt wird.
Beispiel 3a: automatische Anlage einer Datei
$FilePath="C:\Temp\File.txt"
"Das ist ein Text" >$FilePath
"Das ist noch ein Text" >>$FilePath
Dies ist der gute alte Weg , wie man zu DOS-Batchzeiten Textdateien erstellt hat. Powershell bietet heute natürlich flexiblere Möglichkeiten an, aber es funktioniert noch.
Beispiel 3b: automatische Anlage einer Datei (Set-Content)
Technet:
$FilePath="C:\Temp\File-SetContent.txt"
Get-Childitem C:\Temp | Set-Content $FilePath -Value $_.name
#getestet unter PSH V2.0
Mit dem cmdlet Set-Content wird neuer Text in eine Datei geschrieben und der eventuell vorhandene Text dabei ersetzt. Soll der Text angehängt werden, so ist das cmdlet Add-Content zu verwenden.
Die xyz-Content cmdlets besitzen mit "-include", "-exclude" und "-filter" Möglichkeiten zur Filterung.
Beispiel 3c: automatische Anlage einer Datei (Out-File)
Technet:
$FilePath="C:\Temp\File-OutFile.txt"
Get-Childitem C:\Temp | Out-File -FilePath $FilePath
#getestet unter PSH V2.0
$FilePath2="C:\Temp\File-OutFile2.txt"
$a=Get-Childitem C:\Temp | ft mode,lastwritetime,length,name -auto
Out-File -InputObject $a -FilePath $FilePath2 .
Das cmdlet Out-File ist meiner Meinung nach der flexiblste Weg.
Über den Parameter -Encoding kann beispielsweise der verwendete Zeichensatz angegeben werden. Wenn der Dateiinhalt nicht so aussieht (viele Leerzeichen, unschöne Sonderzeichen), dann hilft die Angabe der richtigen Codierung.
Alle drei Varianten schreiben zwar Text in eine Textdatei. Inhalt und Form des Ergebisses können -bei gleichem Input- jedoch unterschiedlich sein.
Beispiel 4: Löschen eines Verzeichnisses
Technet:
$Path = "c:\temp\test1"
Remove-Item $Path -recurse -EA 0
If($?){
"Pfad erfolgreich gelöscht"
}Else{
"Pfad konnte nicht gelöscht werden"
}
#getestet unter PSH V2.0
#mögliche Ausgabe
Path erfolgreich gelöscht
#mögliche Ausgabe
Pfad konnte nicht gelöscht werden
Interessant ist hier der Positionsparamter "-recurse". Enthält das angegebene Verzeichnis Unterverzeichnisse, so werden diese ebenfalls gelöscht. Fehlt -recurse so erfolgt während des Scriptablaufs eine Rückfrage.
2.1.2.2 Kopieren und Verschieben von Dateien und Verzeichnisse
Powershell bringt zum Kopieren und Verschieben die cmdlets move-item und copy-item mit. Für den Umgang mit diesen cmdlets seht euch bitte in der Technet die Beispiele an:
Technet:
Technet:
Anstelle der cmdlets Move-Item und Copy-Item kann man auch das altbewährte Microsofttool Robocopy.exe verwenden. Robocopy zeichnet sich einerseits durch seine hohe Zuverlässigkeit, beispielsweise beim Bewegen sehr großer Dateien, andererseits durch eine große Flexibilität mittels seiner Vielzahl an Parametern aus.
Beispiel 1: Kopieren einer Verzeichnisstruktur mit Robocopy
$Source = "C:\temp\Homes\HomeUser001"
$Destination = "C:\Temp\HomesNew\HomeUser001\"
$Computername = "."
$LogFile="c:\temp\roblog.txt"
$WaitTime=5
$Retries=2
$command="robocopy $Source $Destination /s /E /W:$Waittime /R:$Retries /log:$Logfile"
#Kopiert die Orginalstruktur unter $Source nach $Destination
$StartupOptions=[WmiClass] "Win32_ProcessStartup"
$StartupOptions.PsBase.Properties["ShowWindow"].Value=0
$MC=[System.Management.ManagementClass] "\\$Computername\ROOT\cimv2:Win32_Process"
# kürzer: $MC=[WmiClass] "Win32_Process"
$null=$MC.create($command,$Null,$StartupOptions)
- Beispiele für robocopy gibt es nahezu unendlich. Mit der Zeile $command lassen sich diese einfach in die Powershell übertragen.
- Durch die das Setzen der StartupOption "ShowWindow=0" flackert kein Commandlinefenster auf
weitere Optionen unter MSDN:
- mehr Erklärungen zu "Robocopy in der Powershell" findet ihr im im Beispiel 4d des Kapitels
2.1.2.3 Existenzprüfung von Elementen
Zu einem ordentlichen Programm gehört es, dass vor irgendeiner Aktion im System die Existenz von notwendigen Elementen im Skript, wie Ordner, Dateien oder Laufwerke geprüft wird. Je nach Anforderung muß möglicherweise ein Ordner neu angelegt, eine Datei verschoben, umbenannt oder gelöscht, ein Laufwerk gemappt oder gar das Skript mit einer Fehlermeldung abgebrochen werden.
Beispiel 1: Verschiedene Arten die Existenz von Ordnern, Dateien oder Laufwerken zu prüfen
$TestFolder="C:\temp\" #existiert in diesem Beispiel
$Testfile="C:\temp\text.txt" #existiert in diesem Beispiel
$TestDrive="E:\" #exisitiert nicht in diesem Beispiel
#Existenzpruefung mit Test-Path
"TestPath - $($Testfolder) - $(test-path $testFolder)" #gibt True oder False zurück
"TestPath - $($Testfile) - $(test-path $TestFile)" #gibt True oder False zurück
"TestPath - $($TestDrive) - $(test-path $TestDrive)" #gibt True oder False zurück
" "
#Existenzpruefung mit get-item
$Folder=get-item -path $TestFolder -EA 0 #wirft bei Nichtvorhandensein einen Fehler
$File=get-item -path $Testfile -EA 0 #wirft bei Nichtvorhandensein einen Fehler
$Drive=get-item $testdrive -EA 0 #wirft bei Nichtvorhandensein einen Fehler
"Get-item - $($Testfolder) - $($Folder.exists)"
"Get-item - $($TestFile) - $($File.exists)"
"Get-item - $($TestDrive) - $($Drive.exists)"
"Get-item - $($TestDrive) - $($(get-item $testdrive -EA 0).exists)" #Abfrage als Einzeiler
" "
#Existenzpruefung mit Klassen der .Net System.IO Bibliothek
$Folder=[system.io.directoryinfo]$TestFolder
"[system.io.directoryinfo] - $($Testfolder) - $($Folder.exists)" #gibt True oder gar nichts zurueck
$File=[system.io.fileinfo]$TestFile
"[system.io.fileinfo] - $($TestFile) - $($file.exists)" #gibt True oder gar nichts zurueck
$File=[system.io.fileinfo]$TestDrive
"[system.io.driveinfo] - $($TestDrive) - $($drive.exists)" #gibt True oder gar nichts zurueck
" "
#Existenzpruefung mit Klassen der Com FileSystemObject Klasse
$fso=new-object -com "Scripting.FileSystemObject"
"`$fso.EolderExists - $($TestFolder) - $($fso.folderexists($Testfolder))" #gibt True oder False zurück
"`$fso.FileExists - $($TestFile) - $($fso.fileexists($Testfile))" #gibt True oder False zurück
"`$fso.DriveExists - $($TestDrive) - $($fso.driveexists($TestDrive))" #gibt True oder False zurück
#getestet unter PSH V2.0
#Ausgabe
TestPath - C:\temp\ - True
TestPath - C:\temp\text.txt - True
TestPath - E:\ - False
Get-item - C:\temp\ - True
Get-item - C:\temp\text.txt - True
Get-item - E:\ -
Get-item - E:\ -
[system.io.directoryinfo] - C:\temp\ - True
[system.io.fileinfo] - C:\temp\text.txt - True
[system.io.driveinfo] - E:\ -
$fso.EolderExists - C:\temp\ - True
$fso.FileExists - C:\temp\text.txt - True
$fso.DriveExists - E:\ - False
Wie man sieht, hat man wieder mehrere Möglichkeiten die Existenz von Objekten des Filesystems zu überprüfen
- Die cmdlets test-path und get-item mit ihrer jeweiligen exists-Methode
- jeweilige Methode Exists der .Net Klassen directoryinfo, fileinfo und driveinfo aus der System.IO - Bibliothek
MSDN:
- Methoden FolderExists, FileExists und DriveExists der COM Klasse FileSystemobject
MSDN:
2.1.2.4 Komprimieren und Dekomprimieren von Dateien und Ordnern
Die .Net Klassen "FileInfo" und "DirectoryInfo" besitzen keine Methoden zur Komprimierung. Daher benötigen wir entweder das Commandlinetool "compress.exe", welches zum Lieferumfang von Windows gehört oder wir benutzen die passende WMI-Klasse.
Um die Instanz eines Verzeichnisses zu erzeugen, benutzen wir Win32_Directory, für eine Dateiinstanz die Klasse CIM_DataFile.
Beispiel 1: ein Verzeichnis samt Inhalt komprimieren (compress.exe)
$Path = "C:\temp\Homes\HomeUser001"
$Command="compact /C /s:$Path /A /i"
$ManagementClass=[WmiClass] "Win32_Process"
$StartupOptions=[WmiClass] "Win32_ProcessStartup"
$StartupOptions.PsBase.Properties["ShowWindow"].Value=0
$Return=$ManagementClass.create($command,$null,$StartupOptions)
#getestet unter PSH V2.0
- Die weiteren Optionen von Compress.exe seht euch bitte mit compress /? an
- Durch das Setzen der StartupOption "ShowWindow=0" flackert beim Ausführen des Skriptes kein zusätzliches Commandlinefenster auf.
Weitere Optionen des Prozessstarts unter MSDN:
Beispiel 2: Komprimieren bestimmter Dateitypen in einem Verzeichnis (CIM_DataFile)
$drive="'C:'" #Anführungszeichen beachten
$Path="'\temp\Homes\HomeUser001\'" #Anführungszeichen beachten
$Extension="'txt'" #Anführungszeichen beachten
$Path=$Path.Replace("\","\\") #Bei Pfadangaben müssen zwei "\" stehen
$files=Get-WmiObject CIM_DataFile -filter "Drive=$drive and Path=$Path and Extension=$extension"
$files | foreach{
$return=$_.compress()
if ($return.returnvalue -eq 0){
"$_.name wurde erfolgreich komprimiert"
}else{
"bei $_.name gab es ein Problem"
}
}
#getestet unter PSH V2.0
mögliche Ausgabe
\\MyPC\root\cimv2:CIM_DataFile.Name="c:\\temp\\homes\\homeuser001\\0ubrkn.txt".name wurde erfolgreich komprimiert
\\MyPC\root\cimv2:CIM_DataFile.Name="c:\\temp\\homes\\homeuser001\\1cnvlo.txt".name wurde erfolgreich komprimiert
\\MyPC\root\cimv2:CIM_DataFile.Name="c:\\temp\\homes\\homeuser001\\1gwfkarlb.txt".name wurde erfolgreich komprimiert
Zum Komprimieren steht die Methode "compress()" zur Verfügung.
Zum Dekomprimieren steht die Methode "uncompress()" zur Verfügung.
MSDN:
Beispiel 3: Komprimieren eines Verzeichnisses (Win32_Directory)
$Path="c:\temp\homes\homeuser003"
$Path=$Path.Replace("\","\\") #Bei Pfadangaben müssen zwei "\" stehen
$Query= "Select * from Win32_Directory Where Name='$Path'"
$Verzeichnis=Get-WmiObject -Query $Query
$Return=$Verzeichnis.Compress()
if ($Return.Returnvalue -eq 0){
"Die Komprimierung wurde erfolgreich abgeschlossen"
}else{
"bei der Komprimierung gab es ein Problem"
}
#getestet unter PSH V2.0
Zum Komprimieren steht die Methode "compress()" zur Verfügung.
Zum Dekomprimieren steht die Methode "uncompress()" zur Verfügung.
MSDN:
2.1.2.5 Packen (Zippen) von Verzeichnissen und Dateien
Für das Packen stellt die Powershell V2.0 nativ keine cmdlets bereit. Dennoch können wir uns auf mindestens drei Arten behelfen, um Archive zu erstellen.
A) Installation der CommunityExtensions
Kapitel:
Damit bekommen wir 4 zusätzliche cmdlets bereitgestellt, die zum Erstellen von Archiven verwendet werden können
Beispiel 1: Anzeige der Zip-cmdlets der CommunityExtension
get-command -module pscx *-*zip* | select Name
get-command -module pscx *-tar | select Name
#Ausgabe
NameName
----
Write-BZip2
Write-GZip
Write-Zip
Write-Tar
Das Beispiel funktioniert nur, wenn die Extensions wie beschrieben installiert sind.
Näheres unter:
B) Benutzung eines externen Packprogramms
die zweite Möglichkeit um Archive anzulegen, ist die Nutzung eines 3-thrd Party Packprogramms, das aus der Powershell heraus parametrisiert aufgerufen werden kann. Für diesen Zweck gibt es mehrere geeignete Programme, ich verwende hier jedoch nur
Näheres unter:
C) Programmieren mit der Klasse GZIPStream
die dritte Variante besteht im direkten Ausnutzen der passenden .Net Klasse GZIPStream aus der Bibliothek [System.IO.Compression]
Näheres unter:
2.1.2.5.1 Packen mit den CommunityExtensions
Ich beschränke meine Beispiele auf das cmdlet "Write-Zip". Die übrigen cmdlets Write-GZip, Write-BZip2 und Write-Tar benutzen nur (soweit ich das beurteilen kann) Zip-Archive in anderen Formaten.
Beispiel 1: Packen eines Verzeichnisses mit Unterverzeichnissen und Pfaden
$Source="c:\temp\homes"
$Destination="c:\temp\homes.zip"
$Level=1
Write-Zip -path $Source -outputpath $Destination -level $Level -Quiet
#getestet mit PSH V2.0
#getestet mit CommunityExtensions 2.0.0.1 Released: May 12 2010
#mögliche Ausgabe
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 24.10.2011 23:07 6006442 homes.zip
- Das Beispiel packt den gesamten Inhalt des Sourceverzeichnisses "c:\temp\homes" in das ZipArchiv "c:\temp\homes.zip" . Die Pfadinformationen der Elemente bleiben bestehen.
- Der Level (gültige Werte 1 bis 9) bestimmt das Verhältnis zwischen Komprimierung und Geschwindigkeit. Bei Level 1 liegt eine optimale Geschwindigkeit bei minimaler Komprimierungsrate vor. Bis Level 9 dreht sich das Verhältnis um.
- Der Parameter Quiet unterdrückt die grafische Verlaufsdarstellung des Packvorgangs
Beispiel 2: Packen eines Verzeichnisses mit Unterverzeichnissen ohne Pfade
$Source="c:\temp\homes"
$Destination="c:\temp\homes.zip"
$Level=1
Write-Zip -path $Source -outputpath $Destination -level $Level -Quiet -FlattenPaths
#getestet mit PSH V2.0
#getestet mit CommunityExtensions 2.0.0.1 Released: May 12 2010
#mögliche Ausgabe
WARNUNG: file001.ps1 already exists in archive; conflicts with 'C:\temp\homes\HomeUser002\file001.ps1', skipping.
WARNUNG: file002.ps1 already exists in archive; conflicts with 'C:\temp\homes\HomeUser002\file002.ps1', skipping.
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 24.10.2011 23:22 5976438 homes.zip
Der Parameter -Flattenpaths eliminiert die Pfadinformationen. Alle Dateien werden als flaches Verzeichnis gepackt. Kommt es zu Namenskonflikten, so werden diese als Warnung ausgegeben. Dateien in tieferen Unterverzeichnissen überschreiben bereits bestehende Dateien im Archiv nicht.
Beispiel 3: Mehrere Ordner eines Verzeichnisse getrennt zippen
$Destination="c:\temp\homes"
$a=@(Get-ChildItem -Path $Path|Where{$_.PSISContainer -Match $true})
$a| foreach{
write-zip -path $_ -level 1 -OutputPath $Destination\$_.zip -quiet
}
#getestet mit PSH V2.0
#getestet mit CommunityExtensions 2.0.0.1 Released: May 12 2010
#mögliche Ausgabe
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 25.10.2011 00:20 2466963 HomeUser001.zip
-a--- 25.10.2011 00:20 1868458 HomeUser002.zip
-a--- 25.10.2011 00:20 1249357 HomeUser003.zip
Es mag an mir liegen: Bei mir funktioniert jedenfalls der Parameter "-Append" nicht, mit dem man Dateien einem bestehenden Archiv hinzufügen können soll. Dieser Parameter wäre interessant, um beispielsweise nach bestimmten Kriterien gefilterte Dateien nacheinander in einer Zip-Datei packen zu können.
2.1.2.5.2 Packen mit 7-Zip
Beispiel 1: Packen eines Verzeichnisses mit 7-zip
$command="C:\Program Files\7-zip\7z a c:\temp\7ztest.zip c:\temp\homes\homeuser001"
$MC=[WmiClass] "Win32_Process"
$StartupOptions=[WmiClass] "Win32_ProcessStartup"
$StartupOptions.PsBase.Properties["ShowWindow"].Value=1
$null=$MC.create($command,$Null,$StartupOptions)
Beispiel 2: Hinzufügen weiterer Elemente in das Archiv
$command="C:\Program Files\7-Zip\7z a c:\temp\7ztest.zip c:\temp\homes\homeuser002"
$MC=[WmiClass] "Win32_Process"
$StartupOptions=[WmiClass] "Win32_ProcessStartup"
$StartupOptions.PsBase.Properties["ShowWindow"].Value=1
$null=$MC.create($command,$Null,$StartupOptions)
Der Code sieht exakt gleich wie in Beispiel 1 aus. Allerdings muss Powershell im Administratorkontekt laufen, sonst erhält man "Zugriff verweigert".
Für weitere Möglichkeiten von 7-Zip seht euch bitte "7-zip Help" (C:\Program Files\7-zip\7-zip.chm) an. Dort sind unter "Commandline Version zahlreiche "Commands" und "Switche" aufgeführt.
2.1.2.5.3 Packen und Entpacken mit der .Net GZipStream-Klasse
.Net bietet natürlich auch eine passende Klasse an, um Dateien und Ordner packen zu können. Diese Klasse ist die GZipStream-Klasse aus der Bibliothek System.IO.Compression.
Im Netz finden sich eine ganze Reihe von Beispielen, wie man mit dieser Klasse Dateien und Verzeichnisse packen und entpacken kann. Die meisten Beispiele davon aber nicht in Powershell, sondern in C# oder VisualBasic. So arg schlimm ist das aber nicht, da man besonders C#-Code relativ leicht nach Powershell konvertieren kann.
Zu Beachten ist hierbei, dass die Stream-Klassen wie [System.IO.Filestream] oder [System.IO.Compression.GZipStream] im Framework 4 erweitert wurden. So haben diese unter anderem die Methode copyto
MSDN:
MSDN:
Mit der Powershell V2.0 nutzt man ohne Anpassungen automatisch die Bibliothek des .Net Frameworks 2. Möchte man die Bibliothek des Framework 4 benutzen (Die Frameworks sind übrigens cumulativ!), so sind unter Kapitel
Beispiel 1a: Komprimieren einer Datei mit der GZipStream-Klasse (Framework 2)
function compress
{
param
(
[String]$Filename,
[String]$OutFile = $($Filename + ".gz")
)
$InFile= New-Object System.IO.FileStream $Filename, ([IO.FileMode]::Open), ([IO.FileAccess]::Read), ([IO.FileShare]::Read)
[byte[]]$buffer = new-object byte[] $infile.Length
# Read the file to ensure it is readable.
[int]$count = $infile.Read($buffer, 0, $buffer.Length)
if ($count -ne $buffer.Length){
$infile.Close()
"Test Failed: Unable to read data from file"
Exit-PSSession
}
$infile.Close()
$ms = New-Object System.IO.MemoryStream #
# Use the newly created memory stream for the compressed data.
$compressedzipStream1 = new-Object System.IO.Compression.GzipStream $ms , ([IO.Compression.CompressionMode]::Compress), $true
$compressedzipStream1.Write($buffer, 0, $buffer.Length)
# Close the stream.
$compressedzipStream1.Close()
$FS = New-Object System.IO.FileStream $outFile, ([IO.FileMode]::Create), ([IO.FileAccess]::Write), ([IO.FileShare]::None)
$compressedzipStream2 = new-Object System.IO.Compression.GzipStream $FS , ([IO.Compression.CompressionMode]::Compress), $true
"Compression"
$compressedzipStream2.Write($buffer, 0, $buffer.Length)
# Close the stream.
$compressedzipStream2.Close()
$FS.close()
"Original size: {0}, Compressed size: {1}" -f $buffer.Length, $ms.Length
} #ende der Compress-Funktion
############# Aufruf der Compress-Funktion
$filename="C:\temp\test.txt"
remove-item "C:\temp\test.txt.gz" -EA 0
if (test-path $filename){
compress -Filename $Filename
}else{
"$filename nicht vorhanden"
}
#getestet unter PSH V2.0
#mögliche Ausgabe
Compression
Original size: 5308, Compressed size: 3025
Das Beispiel ist konvertiert aus MSDN:
Beispiel 1b: Komprimieren einer Datei mit der GZipStream-Klasse (Framework 2)
$FileToBeCompressed = "C:\temp\test\test.txt"
$zipFilename = "C:\temp\test\test.txt.zip"
$target = New-Object System.IO.FileStream $zipFilename, ([IO.FileMode]::Create), ([IO.FileAccess]::Write), ([IO.FileShare]::None)
$alg = New-Object System.IO.compression.GZipStream $target, ([IO.Compression.CompressionMode]::compress)
[byte[]]$data=[System.IO.File]::ReadAllBytes($FileToBeCompressed)
$alg.Write($data, 0, $data.Length)
"unkomprimiert:{0} Byte`nkomprimiert: {1} Byte" -f $data.length,$target.length
$alg.close()
$target.dispose()
Beispiel 1c: Dekomprimieren einer Datei mit der GZipStream-Klasse (Framework 2)
$CompressedFile = "C:\temp\test\test.txt.zip"
$OriginalFileName = "C:\temp\test\test.txt"
$ZipFile = New-Object System.IO.FileStream $CompressedFile, ([IO.FileMode]::Open), ([IO.FileAccess]::Read)
$OriginalFile = New-Object System.IO.FileStream $OriginalFileName, ([IO.FileMode]::Create), ([IO.FileAccess]::Write)
$alg = New-Object System.IO.Compression.GZipStream $ZipFile, ([IO.Compression.CompressionMode]::Decompress)
while($true){
[byte[]]$buffer = new-object byte[] 100
[int]$bytesRead = $alg.Read($buffer,0,$($buffer.Length))
$originalFile.Write($buffer, 0, $bytesRead)
if ($bytesRead -ne $buffer.Length){
break
}
}
$alg.close()
$originalFile.close()
$zipFile.close()
Eine andere Variante von einer, wie ich finde, ziemlich coolen Seite:
Beispiel 2a: Komprimieren einer Datei mit der GZipStream-Klasse (Framework 4)
Falls noch nicht geschehen, müssen diese beiden Registrykeys gesetzt werden, um das Framework 4 verfügbar zu machen. Kapitel
reg add hklm\software\microsoft\.netframework /v OnlyUseLatestCLR /t REG_DWORD /d 1
reg add hklm\software\wow6432node\microsoft\.netframework /v OnlyUseLatestCLR /t REG_DWORD /d 1
function compress
{
param([System.IO.FileInfo]$fi) #Die Typzuweisung mit [System.IO.FileInfo] kann man auch weglassen
# Get the stream of the source file.
[System.IO.FileStream]$inFile = $fi.OpenRead() #Die Typzuweisung mit [System.IO.FileStream] kann man auch weglassen
# Prevent compressing hidden and
# already compressed files.
if (([system.io.file]::getattributes($_.fullname) -notmatch "hidden") -and ($fi.extension -ne ".gz")){
#Create the compressed file.
$outfile= [system.io.file]::create($fi.FullName + ".gz")
$compress=new-object System.IO.Compression.GZipStream $outfile,([IO.Compression.CompressionMode]::Compress),$true
#Copy the source file into
#the compression stream.
$inFile.CopyTo($Compress);
"Compressed {0} from {1} to {2} bytes." -f $fi.Name, $fi.Length, $outFile.length
#$infile.close()
$compress.close()
$outfile.close()
} #if
} #function
$dirpath="C:\temp\test\"
$di = New-Object System.IO.DirectoryInfo($dirpath)
$di.GetFiles() | foreach{
compress($_)
}
#mögliche Ausgabe
Compressed 4MKarlnlJ.txt from 5308 to 3000 bytes.
Compressed test2.txt from 273 to 150 bytes.
Das Beispiel ist konvertiert aus MSDN:
Beispiel 2b: Dekomprimieren einer Datei mit der GZipStream-Klasse (Framework 4)
function decompress
{
param([System.IO.FileInfo]$fi) #Die Typzuweisung mit [System.IO.FileInfo] kann man auch weglassen
# Get the stream of the source file.
[System.IO.FileStream]$inFile = $fi.OpenRead() #Die Typzuweisung mit [System.IO.FileStream] kann man auch weglassen
# Get original file extension, for example
# "doc" from report.doc.gz.
$curFile = $fi.FullName
$origname = $curFile.Remove($curfile.Length - $fi.Extension.Length)
# Create the decompressed file
[System.IO.FileStream]$outfile= [system.io.file]::create($origname)
$decompress=new-object System.IO.Compression.GZipStream $infile,([IO.Compression.CompressionMode]::Decompress)
#$decompress
#Copy the decompression stream
# into the output file.
[System.IO.Compression.GZipStream]$decompress.CopyTo($outFile);
"Decompressed: {0}"-f $fi.Name
$infile.close()
$outfile.close()
}
$dirpath="C:\temp\test3"
$di = New-Object System.IO.DirectoryInfo($dirpath)
$di.GetFiles("*.gz") | foreach{
decompress($_)
}
#mögliche Ausgabe
Decompressed: 4MKarlnlJ.txt.gz
Decompressed: test2.txt.gz
Das Beispiel ist konvertiert aus MSDN:
2.1.3 Eigenschaften von Dateien und Ordnern
Häufig wird im Scriptingforum des MCSEBoards nachgefragt, wie man alle Dateien oder Ordner findet, die bestimmten Kriterien wie Mindestalter, Namen oder einer bestimmten Größe entsprechen. Die Ergebnisse sollen dann meist verschoben, gelöscht, umbenannt oder gezippt werden.
In diesem Kapitel möchte ich in Beispielen zeigen, wie
- Eigenschaften von Dateien und Ordnern bestimmt werden können
- Unterlemente eines Verzeichnisses nach bestimmten Kriterien gefiltert können
- die Ergebnisse einer Filterung weiterverarbeitet werden können
2.1.3.1 Attribute von Verzeichnissen und Dateien
Ein Datei- oder Verzeichnis erstellt man in der Powershell unkompliziert mit dem cmdlet get-item. Untersucht man anschließend -wie im nächsten Beispiel 1- die dahinterliegende Klasse näher mit get-member, so steckt "natürlich wieder" .Net mit [System.IO.DirectoryInfo] bei einem Directory oder entsprechend die Klasse [System.IO.FileInfo] bei einer Datei dahinter.
Interessant ist bei der Ausgabe von get-member, dass Powershell einige Eigenschaften zu den .Net Eigenschaften hinzugefügt hat. Diese erkennt man am Membertype "noteproperty". Auf diese werde ich etwas weiter unten genauer eingehen, aber in Beispiel 1 schonmal anzeigen.
MSDN:
MSDN:
Beispiel 1: Eigenschaften und Methoden von Get-Item
(get-item "c:\temp") | get-member
#Ausgabe gekürzt
TypeName: System.IO.DirectoryInfo
Name MemberType Definition
---- ---------- ----------
Mode CodeProperty System.String Mode{get=Mode;}
...
PSIsContainer NoteProperty System.Boolean PSIsContainer=True
Attributes Property System.IO.FileAttributes Attributes {get;set;}
CreationTime Property System.DateTime CreationTime {get;set;}
Technet:
Die Properties "Mode" und "Attributes" überschneiden sich teilweise, wie man an den beiden folgenden Auflistungen sieht.
- Hinter der Property "Attributes" verbergen sich die sogenannten Fileattribute, wobei der Name "File"attribute etwas irreführend ist, da diese Atrribute sowohl für Dateien, wie für Verzeichnisse gelten.
Einige wichtige FileAttribute:
Membername - Beschreibung
ReadOnly - Die Datei ist schreibgeschützt.
Hidden - Die Datei ist versteckt und daher nicht in einer normalen Verzeichnisliste enthalten.
Directory - Die Datei ist ein Verzeichnis.
Archive - Der Archivstatus der Datei. Anwendungen verwenden dieses Attribut, um Dateien für die Sicherung oder das Entfernen zu markieren.
Compressed - Die Datei ist komprimiert.
Offline - Die Datei ist eine Offlinedatei. Die Daten der Datei sind nicht sofort verfügbar.
Encrypted - Die Datei oder das Verzeichnis ist verschlüsselt. Bei einer Datei bedeutet dies, dass alle Daten in der Datei verschlüsselt sind. Bei einem Verzeichnis bedeutet dies, dass neu erstellte Dateien und Verzeichnisse standardmäßig verschlüsselt werden.
Die vollständige Liste findet ihr unter MSDN:
- die möglichen Werte der Property "Mode" lauten laut Beispiel 1 in
d (directory)
a (archive)
r (read-only)
h (hidden)
s (system)
Beispiel 2a: Attribute eines Verzeichnisses (Get-Item)
$Path="C:\temp\"
$a=Get-Item $Path
# $a=New-Object System.IO.DirectoryInfo("$Path") #gleichwertig
# $a=[System.IO.DirectoryInfo]("$Path") #gleichwertig
$a.Attributes
$a.Mode
#getestet unter PSH V2.0
#Ausgabe
Directory, Archive, Encrypted
da---
- die Eigenschaften "Attributes" und "Mode" sind im Beispiel 1 "Eigenschaften und Methoden von get-item" beschrieben
MSDN:
Beispiel 2b: Attribute eines Verzeichnisses (Win32_Directory)
$Path="C:\temp"
If($(Test-Path -Path $Path) -eq $False){
"Der Pfad $path ist nicht vorhanden"
Exit
}
$Path=$Path.Replace("\","\\") #Bei Pfadangaben müssen in WMI-Klassen zwei "\" stehen
$Query= "Select * from Win32_Directory Where Name='$Path'"
Get-WmiObject -query $query
#getestet unter PSH V2.0
#mögliche Ausgabe
Hidden : False
Archive : True
EightDotThreeFileName : c:\temp
FileSize :
Name : c:\temp
Compressed : True
Encrypted : False
Readable : True
- Mit Test-Path wird die Existenz des Pfades geprüft
- Mit der WMI-Klasse Win32_Directory erstellt man unter WMI Instanzen von Verzeichnissen, die Klasse CIM-Datafile ist für Dateien zuständig. Win32_Directory zeigt viel weniger Attribute eines Verzeichnisses an, als die entsprechende .Net-Klasse [directoryinfo].
Win32_Directory hat aber den Vorteil, Methoden zu komprimieren und dekomprimieren zu besitzen
MSDN:
Beispiel 3a: Attribute einer Datei (Get-Item)
$Path="C:\temp\eula.txt"
$a= Get-Item -Path $Path -force
# $a=New-Object System.IO.FileInfo("$Path") #gleichwertig
# $a=[System.IO.FileInfo]("$Path") #gleichwertig
$a.Mode
$a.Attributes
#getestet unter PSH V2.0
#Ausgabe
-arh-
ReadOnly, Hidden, Archive, Encrypted
- Um auch auf versteckte (hidden) Dateien zugreifen zu können, muß der Positionsparameter -force mitgegeben werden.
- die Eigenschaften "Attributes" und "Mode" sind im Beispiel 1 "Eigenschaften und Methoden von get-item" beschrieben
MSDN:
Beispiel 3b: Attribute einer Datei (Cim_DataFile)
$drive="'C:'" #Anführungszeichen beachten
$Path="'\temp\Homes\HomeUser001\'" #Anführungszeichen beachten
$Extension="'pdf'" #Anführungszeichen beachten
$File="'userguide'" #Der Name ohne Extension
$Path=$Path.Replace("\","\\") #Bei Pfadangaben müssen zwei "\" stehen
$FileObject=Get-WmiObject -Class CIM_DataFile -Filter "Drive=$drive and Path=$Path and Filename=$File and Extension=$Extension"
$FileObject.Name
$FileObject.LastAccessed
#mögliche Ausgabe
c:\temp\homes\homeuser001\userguide.pdf
20111001193813.062062+120
Die WMI-Klasse CIM_DataFile zeigt einige Eigenschaften mehr an, als die .Net-Klasse [FileInfo]
MSDN:
Oft möchte man wissen, ob man es bei einem bestimmten Pfad mit einem Directory oder einer Datei zu tun hat. Dazu hat man wieder mehrere Optionen, die die nächsten Beispiele zeigen
Beispiel 4a: Zeigt ein Pfad auf eine Datei oder ein Directory? - Lösung über das Fileattribut "Directory"
$Path = "c:\temp\eula.txt"
if (Test-Path $Path){
$a=((Get-Item $Path).Attributes)
$b=[System.IO.FileAttributes]::Directory
if ($a -Match $b)
{"Der Pfad existiert und es handelt sich um ein Directory"}
else
{"Der Pfad existiert und es handelt sich um eine Datei"}
}Else{"Der Pfad existiert nicht"}
#getestet unter PSH V2.0
#mögliche Ausgabe
Der Pfad existiert und es handelt sich um eine Datei
Ich habe in diesem Beispiel das Attribut "Directory" abgefragt.
Beispiel 4b: Zeigt ein Pfad auf eine Datei oder ein Directory? - Lösung über die Codeproperty "Mode"
$Path = "c:\temp\File.txt"
if (Test-Path $Path){
$a=((Get-Item $Path).Mode)
if ($a -Match "d")
{"Der Pfad existiert und es handelt sich um ein Directory"}
Else
{"Der Pfad existiert und es handelt sich um eine Datei"}
}Else{"Der Pfad existiert nicht"}
#getestet unter PSH V2.0
#mögliche Ausgabe
Der Pfad existiert und es handelt sich um eine Datei
Wie in Beispiel 1 beschrieben, steht der Wert "d" der Eigenschaft Mode für "Directory"
Beispiel 4c: Zeigt ein Pfad auf eine Datei oder ein Directory? - Lösung über die Noteproperty "PSIsContainer"
Für das Attribut "Directory" bietet Powershell über die noteproperty "PSIsContainer zusätzlich elegante Methode an, die das folgende Beispiel zeigt.
$Path = "c:\temp\"
if (Test-Path $Path)
{
if ((Get-Item $Path).PSISContainer)
{"Der Pfad existiert und es handelt sich um ein Directory"}
Else
{"Der Pfad existiert und es handelt sich um eine Datei"}
}Else{"Der Pfad existiert nicht"}
#getestet unter PSH V2.0
##mögliche Ausgabe
Der Pfad existiert und es handelt sich um ein Directory
Beispiel 5a: Unter einem Verzeichnis alle Ordner filtern
$path="c:\temp\homes\
Get-ChildItem -Path $Path|Where{$_.PSISContainer}
# Get-ChildItem -Path $Path|Where{($_.Attributes -Match [System.IO.FileAttributes]::Directory)}
# Get-ChildItem -Path $Path|Where{$_.Mode -Match "d"}
#getestet unter PSH V2.0
#mögliche Ausgabe
Directory: C:\temp\homes
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 08.09.2011 16:02 HomeUser001
d---- 08.09.2011 16:02 HomeUser002
d---- 08.09.2011 16:02 HomeUser003
Alle drei Varianten liefern dasselbe Ergebnis, wie im Beispiel 4 ausführlich beschrieben. Für recursive Suchen besitzt das cmdlet get-childitem den Positionsparameter "recursive".
Beispiel 5b: Unter einem Verzeichnis alle leeren Ordner filtern
natürlich lässt sich diese Aufgabe über jede der drei in Beispiel a gezeigten Varianten angehen.
$path="c:\temp\homes\
$a = Get-ChildItem $Path -Recurse | Where {$_.PSIsContainer}
$a | Where {$_.GetFiles().Count -eq 0} | Select FullName
#getestet unter PSH V2.0
#mögliche Ausgabe
FullName
--------
C:\temp\homes\Empty02
Die Methode Getfiles der Klasse [System.IO.DirectoryInfo] holt sich übrigens sowohl die normalen, wie auch die versteckten Dateien. Ein -force oder ähnliches ist dafür nicht notwendig
Beispiel 5c: Unter einem Verzeichnis alle Dateien filtern
$Path="c:\temp\homes\"
Get-ChildItem -Path $Path|Where{$_.PSISContainer -Match $False}
#Get-ChildItem -Path $Path|Where{($_.Attributes -NotMatch [System.IO.FileAttributes]::Directory)}
#Get-ChildItem -Path $Path|Where{$_.Mode -NotMatch "d"}
#getestet unter PSH V2.0
#Ausgabe
Directory: C:\temp\homes
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 08.09.2011 16:38 26194 Big150.txt
-a--- 08.09.2011 16:39 261094 Big1500.txt
-a--- 08.09.2011 16:39 2610094 Big15000.txt
Alle drei Varianten liefern dasselbe Ergebnis. Für recursive Suchen besitzt das cmdlet get-childitem den Positionsparameter "recursive".
Beispiel 6a: Alle readonly-Dateien in ein Array schreiben
$path="c:\temp\"
$a=@(get-childitem -path $path -force | where{($_.attributes -match [system.io.fileattributes]::ReadOnly)})
#$a=@(get-childitem -path $path -force | where{($_.attributes -band [system.io.fileattributes]::ReadOnly)})
#$a=@(get-childitem -path $path -force | where{($_.isreadonly -eq $true)})
$a | foreach{
"{0} {1} {2}" -f $_.name,$_.mode,$_.attributes
}
#getestet unter PSH V2.0
#Ausgabe
eula.txt -arh- ReadOnly, Hidden, Archive, Encrypted
Bitte Beachten: Mit "$a=@(....)" wird die Variable a in jedem Fall als ein Array deklariert. Liefert get-childitem mehr als einen Treffer zurück, so passiert dies automatisch und es ist kein Unterschied ob man @(..) setzt oder nicht. Falls aber nur ein einziger Treffer zurückkommt, so wird $a kein Array und man bekommt weder ein Ergebnis und noch eine Fehlermeldung zurück. Das ist ein echter Fallstrick!
Den Parameter -Force setze ich, damit auch versteckte Dateien angezeigt werden.
Mit dem Parameter -Recurse kann man die Unterverzeichnisse in die Suche einschließen.
Beispiel 6b: Alle readonly-Dateien in ein Array schreiben und den Readonly-Flag entfernen
$path="c:\temp\"
$a=@(get-childitem -path $path -force | where{($_.attributes -match [system.io.fileattributes]::ReadOnly)})
$a | foreach{
"{0} {1} {2}" -f $_.name,$_.mode,$_.attributes
}
###... eventuell Prüfen der Dateien im Array $a ... ###
$a | foreach{
$_.attributes = $_.attributes -bxor [system.IO.FileAttributes]::ReadOnly
}
#$a | foreach{ $_.IsReadOnly = $false }
#getestet unter PSH V2.0
2.1.3.2 Zeiteigenschaften (CreationTime, LastAccessTime, LastWriteTime)
Das cmdlet get-item liefert -auf den FileSystemprovider abgewandt- ein Objekt der .Net Klasse [FileInfo]. In der Beschreibung dieser Klasse
- CreationTime: Ruft den Erstellungszeitpunkt der aktuellen Datei oder des aktuellen Verzeichnisses ab oder legt diesen fest.
- LastAccessTime: Ruft den Zeitpunkt des letzten Zugriffs auf die aktuelle Datei oder das aktuelle Verzeichnis ab oder legt diesen fest.
- LastWriteTime: Ruft den Zeitpunkt des letzten Schreibzugriffs auf die aktuelle Datei oder das aktuelle Verzeichnis ab oder legt diesen fest.
Liest man die Beschreibung aufmerksam, so erkennt man, daß die jeweilige Zeitstempel (Creation, LastAccess, LastWrite) abgerufen oder festgelegt werden können. Die Zeiteigenschaften dieser Klasse sind im Filesystem read- und writeable!
Zu beachten ist, daß ab Windows7/ Server 2008 die Eigenschaft "LastAccessTime" nicht mehr automatisch upgedatet wird.
Möchte man eine aktuelle "LastAccessTime" auch unter den neueren Betriebssystemen nutzen, muss der Wert NtfsDisableLastAccessUpdate unter HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem auf 0 verändert und der Rechner gebootet werden.
Beispiel 1: Zeitstempel einer Datei mit .Net und COM auslesen
$filepath="C:\temp\Homes\HomeUser001\book1.xlsx"
"`nDatumsinformationen aus der .Net Klasse [FileInfo]"
$DotNetfile=get-item -path $filepath
$DotNetfile | fl creationtime,lastwritetime,lastaccesstime
"`nDatumsinformationen aus der .COM Klasse [FileSystemObject]`n"
$fso=new-object -com "Scripting.FileSystemObject"
$comFile=$fso.getfile($filepath)
"DateCreated : $($comFile.DateCreated)"
"DateLastModified : $($comfile.DateLastModified)"
"DateLastAccessed : $($comfile.DateLastAccessed)"
#getestet unter PSH V2.0
#Ausgabe
Datumsinformationen aus der .Net Klasse [FileInfo]
CreationTime : 18.09.2011 10:15:09
LastWriteTime : 18.09.2011 10:20:51
LastAccessTime : 18.09.2011 10:20:51
Datumsinformationen aus der .COM Klasse [FileSystemObject]
DateCreated : 09/18/2011 10:15:09
DateLastModified : 09/18/2011 10:20:51
DateLastAccessed : 09/18/2011 10:20:51
Die Eigenschaften des COM-FileSystemObjects liefert dieselben Informationen zurück, wie die .Net Klasse [FileInfo]. Die Zeit-Eigenschaften dieser COM Klasse sind aber nicht beschreibbar.
Die Verwendung der COM-Klasse im unteren Teil des Skripts hat wieder einen mehr erklärenden Hintergrund. In der Praxis wird wohl jeder mit dem cmdlet get-item und damit .Net arbeiten. Trotzdem funktioniert auch COM tadellos!
Beispiel 2: Zeitstempel einer Datei verändern
COM-Klassen lasse ich ab jetzt weg.
$filepath="C:\temp\Homes\HomeUser001\book1.xlsx"
$DotNetfile=get-item -path $filepath
$DotNetFile.lastaccesstime = ([datetime]::now).adddays(-1500)
$DotNetFile.creationtime = ([datetime]::now).adddays(+12202)
$DotNetFile.lastwritetime = ([datetime]::now).adddays(-30000)
"CreationTime: $($DotNetFile.creationtime)"
"LastAccessTime: $($DotNetFile.lastaccesstime)"
"LastWritetime : $($DotNetFile.lastwritetime)"
#getestet unter PSH V2.0
#Ausgabe
CreationTime: 02/13/2045 21:45:45
LastAccessTime: 08/10/2007 21:45:45
LastWritetime : 07/30/1929 21:45:45
Wie man sieht, kann man Dateien mittels .Net Zeitstempel sowohl in der Zukunft wie auch in der Vergangenheit unterjubeln.
Um auf das Thema "Filtern nach Attributen" zurückzukommen: Jeden der drei Zeitstempel darf man nur verwenden, wenn man sicher ist, daß dieser nicht manipuliert wurde und man über die Bedeutung Bescheid weiss. Besonders LastAccessTime beachten! Siehe die Einleitung dieses Kapitels "2.2.2 Zeiteigenschaften (CreationTime, LastAccessTime, LastWriteTime)"
Auch Powershell selbst stellt mit dem cmdlet set-item ebenfalls eine Möglichkeit bereit, Zeitstempel zu verändern
$FilePath="C:\temp\Homes\HomeUser001\test.txt"
$MyTime = [system.datetime]"2005,10,16"
#Setzen der LastWriteTime
Set-ItemProperty $filepath LastWriteTime $Mytime
#Abfrage der LastWriteTime
$a=Get-ItemProperty $FilePath
"Die Eigenschaft LastwriteTime der Datei: {0:d} " -f $a.LastwriteTime
#getestet unter PSH V2.0
#mögliche Ausgabe
LastwriteTime der Datei: 08.09.2010
Technet:
Die Erzeugung des Objekts $MyTime wird im nächsten Beispiel 3a beschrieben
Bei der Formatierung {0:d} bedeutet das "d", dass die Ausgabe als "Short date pattern" formatiert wird. Falls die Schreibweise unklar ist, seht bitte im Kapitel
Beispiel 3a: Zeitstempel einer Datei mit einem festen Datum vergleichen
"$($DotNetFile.creationtime)" aus dem letzten Beispiel stellt ein Objekt der Klasse [system.datetime] dar. Wenn wir die Creationtime einer Datei vergleichen wollen, können wir das natürlich nur gegen ein Objekt derselben Klasse. Für die Erstellung eines Datetime-Objekts, welches das von uns gewünschte Datum und möglicherweise auch die Uhrzeit enthält, stellen Powershell und .Net mehrere Varianten bereit. Hier einige davon zur Auswahl:
Step 1: Erstellen eine DateTimeObjects
$cultureDE = New-Object System.Globalization.CultureInfo("de-DE")
$cultureUS = New-Object System.Globalization.CultureInfo("en-US")
$cultureInvariant= [System.Globalization.CultureInfo]::InvariantCulture
$MyTime1 = get-date -year 2008 -month 12 -day 17 -hour 13 -minute 44 -second 12
$MyTime2 = new-object system.datetime(2007,4,16,14,59,59)
$MyTime3 = [system.datetime]"2005,10,16"
$MyTime4 = [DateTime]::ParseExact("20110908","yyyyMMdd", $cultureInvariant)
$MyTime5 = [DateTime]::ParseExact("20110908052022","yyyyMMddhmmss",$cultureInvariant)
$MyTime6 = [DateTime]::ParseExact("Sun 15 Jun 2008 8:30 AM -06:00","ddd dd MMM yyyy h:mm tt zzz",$cultureInvariant)
$MyTime7 = [DateTime]::ParseExact("17.05.2011","d", $cultureDe)
$MyTime8 = [DateTime]::ParseExact("05/17/2011","d", $cultureUS)
"MyTime1: $MyTime1"
"MyTime2: $MyTime2"
"MyTime3: $MyTime3"
"MyTime4: $MyTime4"
"MyTime5: $MyTime5"
"MyTime6: $MyTime6"
"MyTime7: $MyTime7"
"MyTime8: $MyTime8"
#getestet unter PSH V2.0
#Ausgabe
MyTime1: 12/17/2008 13:44:12
MyTime2: 04/16/2007 14:59:59
MyTime3: 10/16/2005 00:00:00
MyTime4: 09/08/2011 00:00:00
MyTime5: 09/08/2011 05:20:22
MyTime6: 06/15/2008 16:30:00
MyTime7: 05/17/2011 00:00:00
MyTime8: 05/17/2011 00:00:00
- Bei nicht allen Varianten funktioniert eine Stundenangabe bis 24
- hat man gemischte "Kulturen" in seiner Umgebung, sollte man sich die CultureInfo-Klasse in der MSDN genauer ansehen.
- zu den verschiedenen Varianten
$MyTime1 - Mehr Infos unter Technet:
$MyTime2 - Mehr Infos unter MSDN:
$MyTime3 - Mehr Infos unter MSDN:
$MyTime4 bis $MyTime8 - Mehr Infos unter MSDN:
Step 2:Ein selbsterstelltes DateTime-Objekt mit der CreationTime einer Datei vergleichen
#Datetime-Objekt erstellen
$Year=2011
$Month=9
$Day=24
$MyTime = new-object system.datetime($Year,$Month,$Day)
#FileInfo-Objekt erstellen
$filepath="C:\temp\Homes\HomeUser001\test.txt"
$file=get-item -path $filepath
#Vergleich der Eigenschaft "CreationTime mit dem selbsterstellten DateTime-Objekt
if($file.creationtime -gt $Mytime){
"die Datei ist um {0:0} Tage jünger, als der {1:d} " -f $($file.creationtime - $MyTime).days,$MyTime
}elseif($file.creationtime -le $Mytime){
"die Datei ist um {0:0} Tage älter, als der {1:d}" -f $($MyTime-$file.creationtime).days,$MyTime
}
#getestet unter PSH V2.0
#mögliche Ausgabe
die Datei ist um 4 Tage älter, als der 24.09.2011
Bei der Formatierung {0:0} steht die erste "0" für den ersten Parameter nach -f. Die zweite "0" bedeutet, dass keine Nachkommastellen ausgegeben werden sollen
Bei der Formatierung {1:d} steht die "1" für den zweiten Parameter nach dem -f. das "d", dass die Ausgabe als "Short date pattern" formatiert wird. Falls die Schreibweise unklar ist, seht bitte im Kapitel
Beispiel 3b: Zeitstempel einer Datei mit einem relativen Datum vergleichen
Häufig will man kein absolutes Datum in sein Skript schreiben, sondern benötigt beispielsweise die Information, ob ein Objekt älter als ein Zeitspanne X sind. X kann beispielsweise 30 Tage oder 2 Jahre betragen. Daran könnte sich entscheiden, ob eine Datei
- unangetastet bleiben soll (Dateialter ist jünger 30 Tage,
- in ein Archiv verschoben wird (Dateialter ist älter 29 Tage, aber jünger 2 Jahre)
- vernichtet wird(Dateialter ist älter oder gleich 2 Jahre)
Die Zeitspanne X wird üblicherweise vom Zeitpunkt des Skriptaufrufs abgezogen.
Step 1: Zunächst müssen wir ein DateTime des heutigen Datums erstellen:
$Jetzt1=get-date
$Jetzt2=[system.datetime]::now
$Heute=[system.datetime]::today
$Jetzt1
$Jetzt2
$Heute
#getestet unter PSH V2.0
#mögliche Ausgabe
Dienstag, 20. September 2011 17:52:21
Dienstag, 20. September 2011 17:52:21
Dienstag, 20. September 2011 00:00:00
Die Eigenschaften "now" und "today" sind statische Eigenschaften der Klasse [datetime]. Falls ihr euch weitere statische Eigenschaften und Methoden mit der Powershell ansehen wollt, könnt ihr das mit
[datetime] | get-member -static
Statische Eigenschaften und Methoden beziehen sich auf die Klasse, nicht auf ein Objekt.
Step 2: Zeitstempel "Heute minus 30 Tage" und "Heute minus 2 Jahre" berechnen
$Jetzt=get-date
$Jetzt.AddDays(-30)
$Jetzt.AddYears(-2)
#getestet unter PSH V2.0
#mögliche Ausgabe
Sonntag, 21. August 2011 21:40:46
Sonntag, 20. September 2009 21:40:46
Objekte der Klasse [system.state] besitzen unter anderem die folgenden Methoden, um einen Zeitstempel zu verändern:
- AddDays Method System.DateTime AddDays(double value)
- AddMonths Method System.DateTime AddMonths(int months)
- AddYears Method System.DateTime AddYears(int value)
Diese Methoden sind Instanzmethoden, da sie auf ein Objekt angewendet werden. Anzeigen zusammen mit weiteren Methoden könnt ihr euch diese Methoden wieder mit get-member, diesmal ohne den Parameter -static
get-date | get-member
#präziser auch
get-date | get-member -membertype Method | where{$_.name -match "add"}
#getestet unter PSH V2.0
Möchte man Tage, Monate oder Jahre von einem Datum abziehen, verwendet man trotzdem die Add-Methoden, nur mit einem negativen Parameter wie in Step 2 bereits gezeigt.
Step 3: Zeitstempel einer Datei mit relativem Datum vergleichen
Jetzt ist nicht mehr schwer, festzustellen ob eine Datei älter als 30 Tage oder gar älter als 2 Jahre ist.
$Jetzt=get-date
$Filepath="c:\temp\Homes\HomeUser001\test2.txt"
$JetztMinus30Tage=$Jetzt.AddDays(-30)
$JetztMinus2Jahre=$Jetzt.AddYears(-2)
$file=get-item $filepath
if($JetztMinus30Tage -gt $file.LastWriteAccess) {
"Die Datei ist älter als 30 Tage"
}
if($JetztMinus2Jahre -gt $file.LastWriteAccess) {
"Die Datei ist sogar älter als 2 Jahre "
}
#getestet unter PSH V2.0
#mögliches Ergebnis
Die Datei ist älter als 30 Tage
Die Datei ist sogar älter als 2 Jahre
In der Einleitung dieses Kapitels -> Beispiel 1 liegt ein Skript bereit, um eine Ordnerstruktur unter C:\temp zu erstellen, in der sowohl Files älter als 30 Tage und älter als 2 Jahre enthalten sind.
Falls ihr die Eigenschaft LastAccessTime verwenden wollt, lest Euch vorher nochmal die Einleitung zu diesem Unterkapitel 2.2.2 durch.
Beispiel 4a: Filtern aller Dateien älter als 30 Tage
"Skriptaufruf am {0} `n" -f [datetime]::now
$SearchRoot = "c:\temp\Homes\HomeUser001"
$JetztMinus30Tage=(get-date).AddDays(-30)
$Files = Get-ChildItem $SearchRoot | Where{$JetztMinus30Tage -gt $_.LastWriteTime}
$Files | ForEach{
"{0} {1:d} " -f $_.FullName,($_.LastWriteTime)
}
#getestet unter PSH V2.0
#mögliche Ausgabe
Skriptaufruf am 20.09.2011 21:14:11
C:\temp\Homes\HomeUser001\9byBxC.txt 20.08.2011
C:\temp\Homes\HomeUser001\AVTdv9.txt 20.08.2011
C:\temp\Homes\HomeUser001\book1.xlsx 31.07.1929
Beispiel 4b: Etwas Statistik über die Dateien, die älter als 30 Tage sind (Measure-Objekt)
Technet:
"Skriptaufruf am {0} `n" -f [datetime]::now
$SearchRoot = "c:\temp\Homes\"
$JetztMinus30Tage=(get-date).AddDays(-30)
$FilesAelter30Tage = Get-ChildItem $SearchRoot -recurse| Where{$JetztMinus30Tage -gt $_.CreationTime}
Write-Host -BackgroundColor Yellow -ForeGroundColor Blue "Etwas Statistik `n"
$MeasureFiles=$FilesAelter30Tage | measure-object -property length -sum -maximum -minimum -average
"Unter dem RootPfad befinden sich {0} Dateien älter 30 Tage" -f $MeasureFiles.Count
"Die Größe aller Dateien älter 30 Tage beträgt zusammen {0:0.0} KB" -f $($MeasureFiles.Sum / 1KB)
"Die größte Datei ist {0:0.0} KB groß" -f $($MeasureFiles.Maximum / 1KB)
"Die kleinste Datei ist {0:0.0} KB groß" -f $($MeasureFiles.Minimum / 1KB)
#getestet unter PSH V2.0
#Ausgabe
Etwas Statistik
Unter dem RootPfad befinden sich 100 Dateien älter 30 Tage
Die Größe aller Dateien älter 30 Tage beträgt zusammen 62,9 KB
Die größte Datei ist 2,1 KB groß
Die kleinste Datei ist 0,6 KB groß
Das cmdlet Measure-Object ist ein Powershellschmankerl, mit dem einfache statistische Werte aus Arrays gewonnen werden können.
Beispiel 4c: Dateien, die älter als 30 Tage sind, in ein Archivverzeichnis verschieben (Move-Item)
In diesem Beispiel suche ich nach allen Dateien, die älter als 30 Tage alt sind. Diese Dateien sollen in ein Archivverzeichnis unter Beibehaltung der relativen Ordnerstruktur verschoben werden.
$SearchRoot = "C:\temp\Homes\"
$ArchivRoot = "C:\Temp\HomesArchiv\"
$JetztMinus30Tage=(get-date).AddDays(-30)
"Skriptaufruf am {0} `n" -f [datetime]::now
$FilesAelter30Tage = @(Get-ChildItem $SearchRoot -recurse| Where{$JetztMinus30Tage -gt $_.CreationTime})
$FilesAelter30Tage | foreach{
$ArchivPath=$($_.FullName).Replace($SearchRoot,$ArchivRoot)
$ArchivPath = Split-Path $ArchivPath -Parent
new-item -path $ArchivPath -type directory -EA 0 #Keine Fehlermeldung, falls Verzeichnis schon existiert
Move-item -Path $_.FullName -Destination $ArchivPath -EA 0 #Keine Fehlermeldung, falls Move-Item nicht funktioniert hat
If($?){
"{0} wurde erfolgreich nach {1} verschoben" -f $_.FullName,$ArchivPath
}else{
$out= "{0} wurde nicht nach {1} verschoben" -f $_.FullName,$ArchivPath
Write-Host $out -BackgroundColor Red -ForegroundColor Blue
}
}
#getestet unter PSH V2.0
#mögliche Ausgabe
C:\temp\Homes\HomeUser001\QSepGL.txt wurde erfolgreich nach C:\Temp\HomesArchiv\HomeUser001 verschoben
C:\temp\Homes\HomeUser001\S3q8ZKarl.txt wurde erfolgreich nach C:\Temp\HomesArchiv\HomeUser001 verschoben
C:\temp\Homes\HomeUser001\test.txt wurde nicht nach C:\Temp\HomesArchiv\HomeUser001 verschoben
C:\temp\Homes\HomeUser002\4KnwVM.txt wurde erfolgreich nach C:\Temp\HomesArchiv\HomeUser002 verschoben
- Um dieses Beispiel nachzuvollziehen, könnt ihr einfach das Beispiel 1 aus
- Alle Dateien, deren CreationTime größer als 30 Tage ist, werden als Elemente in das Array $FilesAelter30Tage geschrieben. Durch @(...) wird die Variable auf jedenfall zu einem Array, auch wenn Get-ChildItem nur ein einziges Element zurückliefern würde.
$FilesAelter30Tage = @(Get-ChildItem $SearchRoot -recurse| Where{$JetztMinus30Tage -gt $_.CreationTime})
- Von jeder Datei dieses Arrays wird der absolute Pfad ($_.Fullname) so verändert, daß er in das Archivverzeichnis zeigt. Anschließend wird der Dateiname abgeschnitten, so daß man das Zielverzeichnis (=$ArchivPath) erhält
$ArchivPath=$($_.FullName).Replace($SearchRoot,$ArchivRoot)
$ArchivPath = Split-Path $ArchivPath -Parent
- Mit Move-Item wird jede Datei in das Arhivverzeichnis verschoben.Durch den Commonparamer -EA 0 (ErrorAction 0) werden Fehlermeldungen unterdrückt, wenn beispielsweise ein Verzeichnis schon existiert oder eine Datei nicht verschoben werden konnte. Nähere Information im Kapitel "Die Variablen" ->
Move-item -Path $_.FullName -Destination $ArchivPath -EA 0 #Keine Fehlermeldung, falls Move-Item nicht funktioniert hat
- Damit Fehler trotz des "-EA 0" interpretiert werden, überprüfe ich mit der "automatic variable" $?, ob der letzte Befehl -hier Move-Item- erfolgreich ausgeführt wurde, siehe Technet:
If($?){
Beispiel 4d: Dateien, die älter als 30 Tage sind, in ein Archivverzeichnis kopieren (Robocopy)
Anstelle des cmdlets Move-Item zum Verschieben einer Datei kann man auch das altbewährte Microsofttool Robocopy.exe verwenden. Robocopy zeichnet sich einerseits durch eine hohe Zuverlässigkeit auch bei schwierigen Jobs, beispielsweise beim Bewegen sehr großer Dateien, andererseits durch eine große Flexibilität durch eine Vielzahl von Parametern aus.
Diese beiden Faktoren in Powershell nachzubilden, dürfte nicht ganz einfach sein, weswegen ich hier ein Beispiel für die Kombination von Powershell und Robocopy zeige:
$SearchRoot = "C:\temp\Homes\"
$ArchivRoot = "C:\Temp\HomesArchiv\"
$LogFile="c:\temp\roblog.txt"
$JetztMinus30Tage=(get-date).AddDays(-30)
"Skriptaufruf am {0} `n" -f [datetime]::now
$FilesAelter30Tage = @(Get-ChildItem $SearchRoot -recurse| Where{$JetztMinus30Tage -gt $_.CreationTime})
$FilesAelter30Tage | foreach{
$ArchivPath=$($_.FullName).Replace($SearchRoot,$ArchivRoot)
$ArchivPath = Split-Path $ArchivPath -Parent
new-item -path $ArchivPath -type directory -EA 0 #Keine Fehlermeldung, falls Verzeichnis schon existiert
$Quelle = split-path $_.fullname -parent
$Ziel = $ArchivPath
$WaitTime=5
$Retries=2
$FileName=$_.name
$command="robocopy $Quelle $Ziel $FileName /W:$Waittime /R:$Retries /log+:$Logfile"
$MC=[System.Management.ManagementClass] "\\.\ROOT\cimv2:Win32_Process"
# kürzer: $MC=[WmiClass] "Win32_Process"
$StartupOptions=[WmiClass] "Win32_ProcessStartup"
$StartupOptions.PsBase.Properties["ShowWindow"].Value=0
$null=$MC.create($command,$Null,$StartupOptions)
#einfacher kann per RobocopyBefehl mit
#invoke-expression $command
#aufgerufen werden
}
#getestet unter PSH V2.0
- Um externe Programme wie Robocopy mit Parametern zu starten, benutze ich entweder die WMI-Klasse Win32_Process
Näheres dazu, sowie ein Powershell/ RobocopyBeispiel mit weiteren Parametern findet ihr im Kapitel
oder einfacher, aber ohne StartupOptions das cmdlet invoke-expression. Kommen Leer- oder Sonderzeichen im Pfad vor, ist die WMI-Variante die "gutmütigste", die so ziemlich alles schluckt
- Möchte man die Robocopy-Robustheit mit Powershell nachprogrammieren, sind diese Links interessant:
Beispiel 4e: Dateien, die älter als 30 Tage sind, in eine Archivdatei (ZIP-Datei) kopieren
#Wichtig: Dieses Script muß im Administratorkontext ausgeführt werden
"Skriptaufruf am {0} `n" -f [datetime]::now
$Searchroot="C:\temp\"
$destination="c:\temp\archiv30f.zip"
$JetztMinus30Tage=(get-date).AddDays(-1)
$FilesAelter30Tage = @(Get-ChildItem $SearchRoot -recurse| Where{$JetztMinus30Tage -gt $_.CreationTime} | where{$_.PSISContainer -eq $false})
$FilesAelter30Tage | foreach{
$command="C:\Program Files\7-Zip\7z a $Destination $($_.fullname)"
$MC=[WmiClass] "Win32_Process"
$StartupOptions=[WmiClass] "Win32_ProcessStartup"
$StartupOptions.PsBase.Properties["ShowWindow"].Value=0
$null=$MC.create($command,$Null,$StartupOptions)
Remove-item $_.fullname
out-file -filepath c:\temp\Log.txt -inputobject $_.fullname -append -encoding default
}
#getestet unter PSH V2.0
Für dieses Beispiel muss das Programm 7-Zip installiert sein. Mehr grundlegende Informationen, wie man mit Powershell Zippen kann, findet ihr im Kapitel
Das cmdlet get-item liefert -auf den FileSystemprovider abgewandt- ein Objekt der .Net Klasse [FileInfo]. In der Beschreibung dieser Klasse MSDN: FileInfo-Eigenschaften sind die die Zeit betreffenden Eigenschaften beschrieben. - CreationTime: Ruft den Er
Dieses Unterkapitel überschneidet sich inhaltlich mit dem (noch in der frühen Entstehung befindlichen) Kapitel über
Meistens werden Dateinamen meiner Erfahrung keine besonders komplexen Suchkriterien erfordern. Einfache Suchkriterien sollten für die meisten Fälle ausreichen.
Beispiel 1a: Suche nach Dateien, die einen bestimmten String im Namen tragen
remove-variable FoundFilenames -EA 0
$SearchRoot = "C:\temp\Homes\"
$Pattern="File"
$FoundFilenames = @(Get-ChildItem $SearchRoot -recurse| Where{$_.name -match $Pattern})
$FoundFilenames | foreach {
remove-item $_.fullname -force -whatif
}
#mögliche Ausgabe
What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser001\file001.ps1".
What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser001\file002.ps1".
What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser002\file001.ps1".
What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser002\file002.ps1".
- Die Dateinamen im Array "$FoundFilenames" können statt gelöscht natürlich genausogut verschoben oder kopiert werden, wie das schon in den vorigen Beispielen gezeigt wurde. Entscheidend in diesem Skript ist der Ausdruck "Where{$_.name -match $Pattern}"
- $Pattern = "File" ist natürlich ein sehr einfaches Suchkriterium. Die Datei muss mit dem String "File" beginnen, ob und was danach kommt ist egal.
- Bevor ihr Dateien tatsächlich löscht, könnt ihr die Ergebnisse erstmal mit -WhatIf kontrollieren.
Beispiel 1b: Suche nach Dateien mit einem einfachen regulären Ausdruck
remove-variable FoundFilenames -EA 0
$SearchRoot = "C:\temp\Homes\"
$Pattern="File0[03][0-4]"
$FoundFilenames = @(Get-ChildItem $SearchRoot -recurse| Where{$_.name -match $Pattern})
$FoundFilenames | foreach {
remove-item $_.fullname -force -whatif
}
#mögliche Ausgabe
What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser001\file001.ps1".
What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser001\file002.ps1".
What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser002\file001.ps1".
What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser002\file002.ps1".
- Die Dateinamen im Array "$FoundFilenames" können statt gelöscht natürlich genausogut verschoben oder kopiert werden, wie das schon in den vorigen Beispielen gezeigt wurde. Entscheidend in diesem Skript ist der Ausdruck "Where{$_.name -match $Pattern}"
- $Pattern="File0[03][0-4].ps1" ist deutlich spezifischer und ein sogenannter Regulärer Ausdruck.
Der Dateiname muß mit "File0" beginnen, gefolgt von einer Zahl entweder 0 oder 3, gefolgt von einer Zahl zwischen 0 und 4, wieder gefolgt von der Extension ".ps1". Je spezifischer ein Filterkriterium, desto sicherer erhält man auch nur die gewünschten Ergebnisse zurück.
- Der Operator -match erlaubt die Verwendung von regulären Ausdrücken.
Technet:
- Bevor ihr Dateien tatsächlich löscht, könnt ihr die Ergebnisse erstmal mit -WhatIf kontrollieren.
Beispiel 2a: Filtern mit den Parametern -Include und -Recurse von Get-Childitem
Hier wieder eine kleine "Besonderheit", die mir bei der Powershell aufgefallen ist
remove-variable FoundFilenames -EA 0
$SearchRoot = "C:\temp\Homes\"
$Pattern="File"
$FoundFilenames = @(Get-ChildItem $SearchRoot -recurse -include file*)
$FoundFilenames | foreach {
remove-item $_.fullname -force -whatif
}
Anstelle die Ergebnisse von Get-Childitem über eine Pipe weiter zu untersuchen, verwende ich hier den Positionsparameter -include des cmdlets.
Im folgenden Beispiel führt der Include-Parameter jedoch dazu, dass keine Ergebnisse zurückgeliefert werden
Beispiel 2: Filtern nur mit dem Parametern -Include von Get-Childitem und ohne -recurse
Ein wichtiger Hinweis findet sich in der Beschreibung dieses Parameters unter
"Der Include-Parameter ist nur dann wirksam, wenn der Befehl den Recurse-Parameter enthält oder der Pfad auf den Inhalt eines Verzeichnisses zeigt, beispielsweise "C:\Windows\*", wobei das Platzhalterzeichen den Inhalt des Verzeichnisses "C:\Windows" angibt."
remove-variable FoundFilenames -EA 0
$SearchRoot = "C:\temp\Homes\HomeUser003\"
$Pattern="File"
$FoundFilenames = @(Get-ChildItem $SearchRoot -include file[0-9]*.ps1)
$FoundFilenames | foreach {
remove-item $_.fullname -force -whatif
}
#Ausgabe
[leer]
Da runzelt man vielleicht erstmal die Stirn, bis man sich den Hinweis aus der Hilfe nochmal genauer durchliest. Man hat also drei Möglichkeiten:
- Man piped die Ergebnisse zum Where-Object, wie in den Beispielen 1a) und 1b) beschrieben
- Man setzt den -recurse Parameter, durchsucht dann aber nicht mehr nur das SearchRootverzeichnis, sondern auch alle Unterverzeichnisse. Je nach Ordnerstruktur ist das eine mögliche Lösung oder nicht.
- Man muss als SearchRoot nicht "C:\temp\Homes\HomeUser003\" , sondern "C:\temp\Homes\HomeUser003\*" definieren
remove-variable FoundFilenames -EA 0
$SearchRoot = "C:\temp\Homes\HomeUser003\*"
$Pattern="File"
$FoundFilenames = @(Get-ChildItem $SearchRoot -include file[0-9]*.ps1)
$FoundFilenames | foreach {
remove-item $_.fullname -force -whatif
}
#mögliche Ausgabe
WhatIf: Ausführen des Vorgangs "Datei entfernen" für das Ziel "C:\temp\Homes\HomeUser003\file001.ps1".
WhatIf: Ausführen des Vorgangs "Datei entfernen" für das Ziel "C:\temp\Homes\HomeUser003\file002.ps1".
Das ist meiner Meinung eine kleine Gemeinheit von Get-ChildItem
2.1.3.3 Filtern nach Namensmustern
Dieses Unterkapitel überschneidet sich inhaltlich mit dem (noch in der frühen Entstehung befindlichen) Kapitel über
Meistens werden Dateinamen meiner Erfahrung keine besonders komplexen Suchkriterien erfordern. Einfache Suchkriterien sollten für die meisten Fälle ausreichen.
Beispiel 1a: Suche nach Dateien, die einen bestimmten String im Namen tragen
remove-variable FoundFilenames -EA 0
$SearchRoot = "C:\temp\Homes\"
$Pattern="File"
$FoundFilenames = @(Get-ChildItem $SearchRoot -recurse| Where{$_.name -match $Pattern})
$FoundFilenames | foreach {
remove-item $_.fullname -force -whatif
}
#mögliche Ausgabe
What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser001\file001.ps1".
What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser001\file002.ps1".
What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser002\file001.ps1".
What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser002\file002.ps1".
- Die Dateinamen im Array "$FoundFilenames" können statt gelöscht natürlich genausogut verschoben oder kopiert werden, wie das schon in den vorigen Beispielen gezeigt wurde. Entscheidend in diesem Skript ist der Ausdruck "Where{$_.name -match $Pattern}"
- $Pattern = "File" ist natürlich ein sehr einfaches Suchkriterium. Die Datei muss mit dem String "File" beginnen, ob und was danach kommt ist egal.
- Bevor ihr Dateien tatsächlich löscht, könnt ihr die Ergebnisse erstmal mit -WhatIf kontrollieren.
Beispiel 1b: Suche nach Dateien mit einem einfachen regulären Ausdruck
remove-variable FoundFilenames -EA 0
$SearchRoot = "C:\temp\Homes\"
$Pattern="File0[03][0-4]"
$FoundFilenames = @(Get-ChildItem $SearchRoot -recurse| Where{$_.name -match $Pattern})
$FoundFilenames | foreach {
remove-item $_.fullname -force -whatif
}
#mögliche Ausgabe
What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser001\file001.ps1".
What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser001\file002.ps1".
What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser002\file001.ps1".
What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser002\file002.ps1".
- Die Dateinamen im Array "$FoundFilenames" können statt gelöscht natürlich genausogut verschoben oder kopiert werden, wie das schon in den vorigen Beispielen gezeigt wurde. Entscheidend in diesem Skript ist der Ausdruck "Where{$_.name -match $Pattern}"
- $Pattern="File0[03][0-4].ps1" ist deutlich spezifischer und ein sogenannter Regulärer Ausdruck.
Der Dateiname muß mit "File0" beginnen, gefolgt von einer Zahl entweder 0 oder 3, gefolgt von einer Zahl zwischen 0 und 4, wieder gefolgt von der Extension ".ps1". Je spezifischer ein Filterkriterium, desto sicherer erhält man auch nur die gewünschten Ergebnisse zurück.
- Der Operator -match erlaubt die Verwendung von regulären Ausdrücken.
Technet:
- Bevor ihr Dateien tatsächlich löscht, könnt ihr die Ergebnisse erstmal mit -WhatIf kontrollieren.
Beispiel 2a: Filtern mit den Parametern -Include und -Recurse von Get-Childitem
Hier wieder eine kleine "Besonderheit", die mir bei der Powershell aufgefallen ist
remove-variable FoundFilenames -EA 0
$SearchRoot = "C:\temp\Homes\"
$Pattern="File"
$FoundFilenames = @(Get-ChildItem $SearchRoot -recurse -include file*)
$FoundFilenames | foreach {
remove-item $_.fullname -force -whatif
}
Anstelle die Ergebnisse von Get-Childitem über eine Pipe weiter zu untersuchen, verwende ich hier den Positionsparameter -include des cmdlets.
Im folgenden Beispiel führt der Include-Parameter jedoch dazu, dass keine Ergebnisse zurückgeliefert werden
Beispiel 2: Filtern nur mit dem Parametern -Include von Get-Childitem und ohne -recurse
Ein wichtiger Hinweis findet sich in der Beschreibung dieses Parameters unter
"Der Include-Parameter ist nur dann wirksam, wenn der Befehl den Recurse-Parameter enthält oder der Pfad auf den Inhalt eines Verzeichnisses zeigt, beispielsweise "C:\Windows\*", wobei das Platzhalterzeichen den Inhalt des Verzeichnisses "C:\Windows" angibt."
remove-variable FoundFilenames -EA 0
$SearchRoot = "C:\temp\Homes\HomeUser003\"
$Pattern="File"
$FoundFilenames = @(Get-ChildItem $SearchRoot -include file[0-9]*.ps1)
$FoundFilenames | foreach {
remove-item $_.fullname -force -whatif
}
#Ausgabe
[leer]
Da runzelt man vielleicht erstmal die Stirn, bis man sich den Hinweis aus der Hilfe nochmal genauer durchliest. Man hat also drei Möglichkeiten:
- Man piped die Ergebnisse zum Where-Object, wie in den Beispielen 1a) und 1b) beschrieben
- Man setzt den -recurse Parameter, durchsucht dann aber nicht mehr nur das SearchRootverzeichnis, sondern auch alle Unterverzeichnisse. Je nach Ordnerstruktur ist das eine mögliche Lösung oder nicht.
- Man muss als SearchRoot nicht "C:\temp\Homes\HomeUser003\" , sondern "C:\temp\Homes\HomeUser003\*" definieren
remove-variable FoundFilenames -EA 0
$SearchRoot = "C:\temp\Homes\HomeUser003\*"
$Pattern="File"
$FoundFilenames = @(Get-ChildItem $SearchRoot -include file[0-9]*.ps1)
$FoundFilenames | foreach {
remove-item $_.fullname -force -whatif
}
#mögliche Ausgabe
WhatIf: Ausführen des Vorgangs "Datei entfernen" für das Ziel "C:\temp\Homes\HomeUser003\file001.ps1".
WhatIf: Ausführen des Vorgangs "Datei entfernen" für das Ziel "C:\temp\Homes\HomeUser003\file002.ps1".
Das ist meiner Meinung eine kleine Gemeinheit von Get-ChildItem
2.2 Logische Laufwerke
Jetzt fehlen noch die Laufwerke, dann haben wir die wichtigsten Objekte des Filesystems durch. Die interessantesten Eigenschaft eines Laufwerks dürfte die Speicherplatzbelegung beziehungsweise der noch verfügbare Speicherplatz sein. Diese Größen lassen sich mit der Powershell und den passenden Klassen auf mehrere Weisen bequem ermitteln.
Beispiel 1: Freien und belegten Speicherplatz anzeigen (WMI-Klasse: Win32_LogicalDisk )
gwmi win32_logicaldisk | format-table `
@{Label="ID";
Expression={"{0}" -f ($_.DeviceID)}
width=3;
Align="Right";
},
@{Label=$(" "*2) + "Free in MB";
Expression={"{0:0.0}" -f ($_.Freespace/1MB)};
width=13;
Align="Right";
},
@{Label=$(" "*2) + "Size in MB";
Formatstring="{0:0.00}"
Expression= {$_.Size/1MB}
width=13;
Align="Right";
}
#mögliche Ausgabe
ID Free in MB Size in MB
-- ------------ ------------
C: 80950,3 152624,85
E: 0,0 2414,53
I: 48186,0 51191,46
K: 614301,7 614392,09
S: 78860,5 1638400,00
Man ist wahlfrei, ob man, wie in der zweiten Spalte "Free in MB" den Formatoperator -f oder den Formatstring wie in der dritten Spalte Size in MB" benützt. Siehe
- Der Schlüssel "width" gibt die Feldbreite an, die eine Spalte belegt.
- Der Schüssel "align" oder "alignment" legt die Ausrichtung fest ("left","right","center")
Damit lassen sich jetzt schon ansehlich formatierte Tabellen erstellen
Je nach verwendetem cmdlet stehen verschiedene Schlüssel für die Hashtabellen zur Verfügung:
Beispiel 2: Freien und belegten Speicherplatz anzeigen (.Net-Klasse: DriveInfo)
$drives=[system.io.driveinfo]::getdrives()
$computername=get-content env:computername
$width_0=3
$width_1=15
$width_2=20
$width_3=13
$width_4=6+$computername.length
$headline= "{0,$width_0} {1,$($width_1):0.00} {2,$($width_2):0.00} {3,$($width_3):0.0%} {4,$width_4}" -f `
"Drive","Totalsize in GB","Totalfreespace in GB","Percentfree","Computername"
write-host -BackgroundColor darkyellow -foregroundcolor darkred "$headline`n"
$drives | foreach {
if ($_.totalsize -gt 0){ #damit unverbundene LW nicht angezeigt werden
$Drivename=$_.name
$Totalsize=$($_.totalsize)/1GB
$TotalFreeSpace=$($_.totalfreespace)/1GB
$PercentFree=$($_.totalfreespace)/$($_.totalsize)
"{0,$width_0} {1,$($width_1):0.00} {2,$($width_2):0.00} {3,$($width_3):0.0%} {4,$width_4}" -f `
$Drivename,$totalsize,$TotalFreeSpace,$PercentFree,$computername
}else{
$Drivename=$_.name
$computername=get-content env:computername
"{0} {1,55}" -f $Drivename,$computername
}
}
#mögliche Ausgabe
Drive Totalsize in GB Totalfreespace in GB Percentfree Computername
C:\ 149,05 79,20 53,1% DOM2DC01
E:\ 2,36 0,00 0,0% DOM2DC01
I:\ 49,99 47,06 94,1% DOM2DC01
U:\ 1560,00 45,58 2,9% DOM2DC01
2.3 Freigaben/ Shares
Für die Verwaltung von Freigaben gibt es überraschenderweise keine eigenen cmdlets und auch keine .Net-Klasse. Alle Aufgaben rund um Shares müssen über die WMI-Klasse Win32_Share erledigt werden.
In diesem Kapitel gehe ich nicht so sehr auf die WMI Thema ein. Falls es hier Unklarheiten gibt, sehr euch doch bitte das Kapitel
MSDN:
Beispiel 1: Anlage eines neuen Shares
$Verzeichnis="C:\temp"
$ShareName="TestShare"
$ShareType=0 #Standardshare
$MaximumAllowed=8
$description="Das ist ein Test"
$Password=""
$Access=$Null
$class=gwmi -query "Select * From Meta_Class Where __CLASS='Win32_Share'"
$return=$class.create($Verzeichnis,$ShareName,$ShareType,$MaximumAllowed,$Description,$Password,$Null)
Switch($return.returnvalue)
{
0 {"Share erfolgreich angelegt"}
2 {"Zugriff verweigert"}
8 {"unbekannter Fehler"}
9 {"ungültiger Name"}
22 {"Share exisitiert bereits"}
Default {"bitte in der MSDN nach dem ReturnValue von Win32_Share suchen"}
}
#getestet unter PSH V2.0
#mögliche Ausgaben
Share erfolgreich angelegt
#Share exisitiert bereits
Beispiel 2: Löschen eines Shares
$Computer="."
$Namespace="Root\cimV2"
$ShareName="'TestShare'" #Anführungszeichen beachten!
$Query="Select * from Win32_Share Where Name=$ShareName"
$Share=gwmi -query $Query -NameSpace $Namespace -computer $Computer
try{
$return=$Share.Delete()
}
catch{
"Share wahrscheinlich nicht vorhanden"
exit
}
Switch($return.returnvalue)
{
0 {"Share erfolgreich gelöscht"}
2 {"Zugriff verweigert"}
8 {"unbekannter Fehler"}
9 {"ungültiger Name"}
21 {"Invalid Parameter"}
}
#getestet unter PSH V2.0
#mögliche Ausgaben
#Share erfolgreich gelöscht
Share wahrscheinlich nicht vorhanden
Beispiel 3: Eigenschaften eines Shares
$Computer="."
$ShareName="'TestShare'" #Anführungszeichen beachten!
$NameSpace="root\cimv2"
$Query="Select * from Win32_Share Where Name=$ShareName"
try{
$Share=[wmi]"Win32_Share.name=$ShareName"
#$Share=gwmi -query $Query -NameSpace $Namespace -Computer $Computer
#$Share=[wmi]"\\$Computer\$($NameSpace):Win32_Share.Name=$ShareName"
}
catch{
"Vermutlich ist der Share nicht vorhanden"
exit
}
$Share.Properties | ft Name,Value -auto
#getestet unter PSH V2.0
#mögliche Ausgabe
Name Value
---- -----
AccessMask
AllowMaximum False
Caption Mein Test
Description Mein Test
InstallDate
MaximumAllowed 15
Name test
Path C:\temp\test
Status OK
Type 0
#mögliche Ausgabe
Vermutlich ist der Share nicht vorhanden
Die Bedeutung der Eigenschaften sind in der MSDN unter dem angegebenen Link aufgeführt:
- Accessmask: Ist obsolet und immer leer. Statt dieser Eigenschaft die Mehtode getaccessmask() verwenden
($instanz.getaccessmask()).returnvalue
- AllowMaximum: Bei Windows7 liegt die maximale Anzahl bei 20 Benutzern, bei XP waren es maximal 10 Benutzer. Läßt man diese Einstellung unverändert, besitzt diese Eigenschaft den Wert True , ansonsten False. AllowMaximum steht in Zusammenhang mit MaximumAllowed
- Installdate: kann leer bleiben.
- Type: Interessante Typewerte sind 0 (0x0) oder 2147483648 ((0x80000000). Ersteres entspricht einem normalen Share, letzteres einem administrativen Share wie c$. Falls andere Typen auftreten, so sind diese ebenfalls unter MSDN:
Beispiel 4a: Auf welchem Verzeichnis liegt ein Share (mit "Associators of")
$Computer="."
$ShareName="'TestShare'" #Anführungszeichen beachten!
$Query="ASSOCIATORS OF {Win32_Share.Name=$ShareName} WHERE AssocClass=Win32_ShareToDirectory"
Get-WmiObject -Query $Query -computer $Computer -EA 0
if($? -ne "true"){
"Share vermutlich nicht vorhanden"
}
(Get-WmiObject -Query $Query -computer $Computer -EA 0).Name
#getestet unter PSH V2.0
#mögliche Ausgabe
Hidden : False
Archive : True
EightDotThreeFileName : c:\temp
FileSize :
Name : c:\temp
Compressed : False
Encrypted : False
Readable : True
c:\temp
#mögliche Ausgabe
#Share vermutlich nicht vorhanden
Eine nähere Erklärung zu "ASSOCIATORS OF" findet ihr im Kapitel
Beispiel 4b: Auf welchem Verzeichnis liegt ein Share (mit "Select *")
$Computer="."
$ShareName="'TestShare'" #Anführungszeichen beachten!
Get-WmiObject -Query "Select * from Win32_Share Where Name=$ShareName" -Computer $computer| Format-Table Name,Path,Description -auto
#([WmiSearcher] "Select * from Win32_Share Where Name=$ShareName").get() | Format-Table Name,Path,Description -auto #gleichwertig
#mögliche Ausgabe
Name Path Description
---- ---- -----------
TestShare C:\temp Das ist ein Test
weitere Möglichkeiten:
$Computer="."
$ShareName="'TestShare'" #Anführungszeichen beachten!
[WMI]"Win32_Share.Name=$ShareName" | Format-Table Name,Path,Description -auto
[WMI]"\\$Computer\root\cimV2:Win32_Share.Name=$ShareName" | Format-Table Name,Path,Description -auto
Beispiel 5: Welche Shares liegen auf einem Verzeichnis
$Computer="."
$Path="'C:\\Temp'" #anführungszeichen und Backslashes beachten!
$Query="Select * from Win32_Share Where path=$Path"
Get-WmiObject -query $Query -computer $Computer| ft name, Status, description, path -auto
#getestet unter PSH V2.0
#mögliche Ausgabe
name Status description path
---- ------ ----------- ----
Temp OK C:\Temp
'TestShare' OK Das ist ein Test C:\temp
wmi-test OK C:\Temp
Beispiel 6: Welche User sind mit einem Share verbunden
$Computer="."
$ShareName="'TestShare'"
$Query="ASSOCIATORS OF {Win32_Share.Name=$ShareName} WHERE AssocClass=Win32_ConnectionShare"
Get-WmiObject -query $Query -Computer $Computer -EA 0| Format-Table Username,Computername -auto
if($? -ne "true"){
"Share vermutlich nicht vorhanden oder keine User darauf verbunden"
}
#mögliche Ausgabe
Username Computername
-------- ------------
Karl_Napf 192.168.21.76
Heinz_Kunz 192.168.21.10
#mögliche Ausgabe
Share vermutlich nicht vorhanden oder keine User darauf verbunden