2 Laufwerke, Ordner, Dateien und Freigaben
2.1 Dateien und Ordner
2.1.1 Inhalt und Struktur von Verzeichnissen
Beispiel 1a: Größe eines Verzeichnisses bestimmen (Measure-Object)
Beispiel 1b: Größe eines Verzeichnisses bestimmen (FileSystemObject)
Beispiel 2: Unterordner nach ihrer Größe/ Anzahl Dateien sortieren
Beispiel 3: Dateitypen im Verzeichnis (Group-Object)
Beispiel 4: Speicherplatz pro Dateityp im Verzeichnis
Beispiel 5: Alterstruktur der Dateien im Verzeichnis
2.1.2 einfache Operationen mit Dateien und Verzeichnissen (Löschen, Kopieren, Verschieben, Erstellen)
2.1.2.1 Erstellen und Löschen von Objekten im Filesystem
Beispiel 1a: Anlage eines Verzeichnisses (Fileprovider)
Beispiel 1b: Anlage eines Verzeichnisses (.Net-Klasse Directory)
Beispiel 1c: Anlage eines Verzeichnisses (FileSystemObject)
Beispiel 2a: explizite Anlage einer Datei
Beispiel 2b: explizite Anlage einer Datei mit Zeitstempel/ TimeStamp
Beispiel 3a: automatische Anlage einer Datei
Beispiel 3b: automatische Anlage einer Datei (Set-Content)
Beispiel 3c: automatische Anlage einer Datei (Out-File)
Beispiel 4: Löschen eines Verzeichnisses
2.1.2.2 Kopieren und Verschieben von Dateien und Verzeichnissen
Beispiel 1: Kopieren einer Verzeichnisstruktur mit Robocopy
2.1.2.3 Existenzprüfung von Elementen
Beispiel 1: Verschiedene Arten die Existenz von Ordnern, Dateien oder Laufwerken zu prüfen
Beispiel 2: Prüfen der installierten .Net Versionen
2.1.2.4 Komprimieren und Dekomprimieren von Dateien und Ordnern
Beispiel 1: ein Verzeichnis samt Inhalt komprimieren (compress.exe)
Beispiel 2: Komprimieren bestimmter Dateitypen in einem Verzeichnis (CIM_DataFile)
Beispiel 3: Komprimieren eines Verzeichnisses (Win32_Directory)
2.1.2.5 Packen (Zippen) von Verzeichnissen und Dateien
Beispiel 1: Anzeige der Zip-cmdlets der CommunityExtension
2.1.5.2.1 Packen mit den CommunityExtensions
Beispiel 1: Packen eines Verzeichnisses mit Unterverzeichnissen und Pfaden
Beispiel 2: Packen eines Verzeichnisses mit Unterverzeichnissen ohne Pfade
Beispiel 3: Mehrere Ordner eines Verzeichnisse getrennt zippen
2.1.5.2.2 Packen mit 7-Zip
Beispiel 1: Packen eines Verzeichnisses mit 7-Zip
Beispiel 2: Hinzufügen weiterer Elemente in das Archiv
2.1.5.2.3 Packen mit der .Net GZipStream-Klasse
Beispiel 1a: Komprimieren einer Datei mit der GZipStream-Klasse (Framework 2)
Beispiel 1b: Komprimieren einer Datei mit der GZipStream-Klasse (Framework 2)
Beispiel 1c: Dekomprimieren einer Datei mit der GZipStream-Klasse (Framework 2)
Beispiel 2a: Komprimieren einer Datei mit der GZipStream-Klasse (Framework 4)
Beispiel 2b: Dekomprimieren einer Datei mit der GZipStream-Klasse (Framework 4)
2.1.2.6 Kopieren einer Datei unter Alternate-Credentials
Beispiel1: Mappen eines Laufwerks
2.1.2.7 Dateien auf Gleichheit prüfen
Beispiel 1: MD5-Hash berechnen
2.1.3 Eigenschaften von Dateien und Ordnern
2.1.3.1 Attribute von Verzeichnissen und Dateien
Beispiel 1: Eigenschaften und Methoden von Get-Item
Beispiel 2a: Attribute eines Verzeichnisses (Get-Item)
Beispiel 2b: Attribute eines Verzeichnisses (Win32_Directory)
Beispiel 3: Attribute einer Datei
Beispiel 4a: Zeigt ein Pfad auf eine Datei oder ein Directory? - Lösung über das Fileattribut "Directory"
Beispiel 4b: Zeigt ein Pfad auf eine Datei oder ein Directory? - Lösung über die Codeproperty "Mode"
Beispiel 4c: Zeigt ein Pfad auf eine Datei oder ein Directory? - Lösung über die Noteproperty "PSIsContainer"
Beispiel 5a: Unter einem Verzeichnis alle Ordner filtern
Beispiel 5b: Unter einem Verzeichnis alle leeren Ordner filtern
Beispiel 5c: Unter einem Verzeichnis alle Dateien filtern
Beispiel 6a: Alle readonly-Dateien in ein Array schreiben
Beispiel 6b: Alle readonly-Dateien in ein Array schreiben und den Readonly-Flag entfernen
2.1.3.2 Zeiteigenschaften (CreationTime, LastAccessTime, LastWriteTime)
Beispiel 1: Zeitstempel einer Datei mit .Net und COM auslesen
Beispiel 2: Zeitstempel einer Datei verändern
Beispiel 3a: Zeitstempel einer Datei mit einem festen Datum vergleichen
Beispiel 3b: Zeitstempel einer Datei mit einem relativen Datum vergleichen
Beispiel 4a: Filtern aller Dateien älter als 30 Tage
Beispiel 4b: Etwas Statistik über die Dateien, die älter als 30 Tage sind (Measure-Objekt)
Beispiel 4c: Dateien, die älter als 30 Tage sind, in ein Archivverzeichnis verschieben (move-item)
Beispiel 4d: Dateien, die älter als 30 Tage sind, in ein Archivverzeichnis kopieren (Robocopy)
Beispiel 4e: Dateien, die älter als 30 Tage sind, in eine Archivdatei (ZIP-Datei) kopieren
2.1.3.3 Suchen und Filtern nach Namensmustern
Beispiel 1a: Suche nach Dateien, die einen bestimmten String im Namen tragen
Beispiel 1b: Suche nach Dateien mit einem einfachen regulären Ausdruck
Beispiel 2: Filtern mit den Parametern -Include und -Recurse von Get-Childitem
2.1.3.4 Ein paar Tricks zu Get-ChildItem
Beispiel 1: Nach dem ersten Treffer die Suche beenden
Beispiel 2a: Recursive Suche nur bis zu einem bestimmten Level
Beispiel 2b: Alle Userprofile anzeigen
2 Laufwerke, Ordner, Dateien und Freigaben (Ohne ACL)
Zum Nachvollziehen der folgenden Beispiele ist eine Beispielordnerstruktur mit mehreren Unterordnern samt Dateien recht nützlich. Unter
2.1.1 Inhalt und Struktur von Verzeichnissen
Beispiel 1a: Größe eines Verzeichnisses bestimmmen (Measure-Object)
Set-StrictMode -Version "2.0" Clear-Host Function Get-FolderSize{ <# .synopsis This function gives back the size of a folder, the number of files and the number of subfolders #> Param($Path="c:\temp") If(Test-Path -path $Path){ $Length=(gci $Path -recurse | Measure-Object -Property "Length" -Sum).Sum #$SumFiles=(gci $Path -recurse | Where {$_.PSIsContainer -eq $False} |Measure-Object).Count $SumFiles=(gci $Path -recurse -File | Measure-Object).Count # min. PS V3.0 erforderlich #$SumFolders=(gci $Path -recurse | Where {$_.PSIsContainer -eq $True} | Measure-Object).Count $SumFolders=(gci $Path -recurse -Directory | Measure-Object).Count # min. PS V3.0 erforderlich }Else{ "Path {0} existiert nicht" -f $Path } "Ordner: {0}" -f $Path "Größe mit Unterverzeichnissen in MB: {0:n2}" -f $($Length/1MB) "Anzahl Dateien: {0}" -f $SumFiles "Anzahl Ordner: {0}" -f $SumFolders } Get-FolderSize c:\repos |
#mögliche Ausgabe Ordner: c:\repos Größe mit Unterverzeichnissen in MB: 14,33 Anzahl Dateien: 1653 Anzahl Ordner: 205 |
Technet: Measure-Object
Die Eigenschaft "PSIsContainer" ist eine Möglichkeit festzustellen, ob ein Objekt eine Datei oder ein Container ist. In Beispiel 4 im
Beispiel 1b: Größe eines Verzeichnisses bestimmmen (Filesystemobject)
Alternativ kann man auch das gute alte Filesystemobject benutzen, um Eigenschaften von Files und Foldern zu bestimmen. Irgendwann werden aber COM-Objekte wahrscheinlich in Windows nicht mehr unterstützt.
Set-StrictMode -Version "2.0" |
#mögliche Ausgabe Montag, 30. September 2013 22:50:42 0,62MB |
siehe auch: 1.2 COM-Objekte der Script Runtime
Beispiel 2: Unterordner nach ihrer Größe/ Anzahl Dateien sortieren
#requires -Version 3.0 Set-StrictMode -Version "2.0" Clear-Host $RootPath = "C:\Temp\Homes" Function Main{ Param($RootPath) $Subfolders = @(Get-SubFolders -RootPath $RootPath) $Subfolders | sort SizeInMB -Descend | Format-Table Name, @{ Label="Size(MB)"; Expression={"{0:n2}" -f $_.SizeInMB}; },FolderCount,FilesCount -auto }#End Main Function Get-SubFolders{ <# .Synopsis Lists subdirectories of a folder #> Param($RootPath) $Results = @() $Folders = (Get-ChildItem $RootPath -Recurse | Where {$_.PSIsContainer -eq $True}) Foreach($Folder in $Folders){ $FilesCount = ((gci $Folder.Fullname -recurse | Where {$_.PSIsContainer -eq $False}) |Measure-Object).Count $FolderCount = ((gci $Folder.Fullname -recurse | Where {$_.PSIsContainer -eq $True}) |Measure-Object).Count If ($FilesCount -ge 1){ $SizeInMB = $(((gci $Folder.FullName -recurse | Measure-Object -Property "Length" -Sum).Sum)/1MB) }Else{ $SizeInMB =0 }#Else/ endif $CheckedFolder = New-Object -TypeName Psobject -Property @{ Name = $Folder.FullName FilesCount = $FilesCount FolderCount = $FolderCount SizeInMB = $SizeInMB } #psobject $Results += $CheckedFolder } Return ,$Results }#End Subfolders Main $RootPath |
#mögliche Ausgabe Name Size(MB) FolderCount FilesCount ---- -------- ----------- ---------- C:\temp\Homes\HomeUser003 0,04 0 16 C:\temp\Homes\HomeUser002 0,22 1 12 C:\temp\Homes\HomeUser001 0,02 5 19 |
Kapitel_2.1.1_Beispiel2_SortSubfolder.ps1.txt
Beispiel 3: Dateitypen im Verzeichnis
Set-StrictMode -Version "2.0" Clear-Host $Path = "c:\temp\homes\" gci $Path -Recurse | Group-Object Extension | Sort-Object Count -Descending | Ft Count,Name -Auto |
#mögliche Ausgabe Count Name ----- ---- 112 .txt 20 .nul 4 .ps1 3 2 .xlsx 1 .doc 1 .pdf |
Technet: Using the Group-Object Cmdlet
Beispiel 4: Speicherplatz pro Dateityp im Verzeichnis
Set-StrictMode -Version "2.0"
Clear-Host
$Path="C:\temp\"
#$GroupInfos = gci -path $Path -recurse -force| Where{ $_.PSIsContainer -eq $False } | Group-Object Extension
$GroupInfos = gci -path $Path -recurse -force -file | Group-Object Extension ##requires PS V3.0
$MyInfos = @()
ForEach ($Extension in $GroupInfos ) {
[psobject]$MyInfo = ""|Select Extension,Count,Size
$Size = ($Extension.Group | Measure-Object -Property "Length" -Sum).Sum/1MB
$MyInfo.Extension = $Extension.Name
$MyInfo.Count = $Extension.Count
$MyInfo.Size = $Size
$MyInfos += $MyInfo
}
$MyInfos | Ft Extension,count,
@{
Label="FileSize in MB";
Expression={"{0:0.00}" -f $($_.Size)};
#Width=10
} -AutoSize
|
#mögliche Ausgabe Extension Count Size in MB .log 36 0,30 .nul 31 0,00 .ps1 4 0,00 .txt 213 0,15 |
- ausreichende Berechtigungen auf alle Unterverzeichnisse und Dateien sind natürlich Voraussetzung.
Beispiel 5: Alterstruktur der Dateien im Verzeichnis
Set-StrictMode -Version "2.0"
#$GroupInfos = $Files | Group-Object {$_.LastAccessTime.ToString("yyyy-MM")} | sort name |
#mögliche Ausgabe |
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. MSDN:Disabling Last Access Time Stamps. Laut dem MSDN-Artikel hat diese Einstellung auch Einfluss auf die Performance des FileSystems, ist also aus Performancegründen ohne gewichtige Gründe nicht unbedingt empfehlenswert.
Für Backupkonzepte können solche Erhebungen ganz interessant sein.
Die folgenden Unterkapitel behandeln die Grundfunktionen wie Kopieren, Löschen, Verschieben, Komprimieren und Zippen von einzelnen Dateien und Verzeichnissen.
Im anschließenden Kapitel
Beispiel 1a: Anlage eines Verzeichnisses (Fileprovider: New-Item)
Technet: New-Item
Set-StrictMode -Version "2.0" Clear-Host $Path="C:\Temp\Test" New-Item $Path -Type Directory -EA 0 if($? -ne $True){ "Verzeichnis wahrscheinlich schon vorhanden. Daher wurde kein neues Verzeichnnis erstellt" } |
#mögliche Ausgabe Verzeichnis: C:\Temp Mode LastWriteTime Length Name ---- ------------- ------ ---- d---- 15.10.2011 23:20 Test |
#mögliche Ausgabe Verzeichnis wahrscheinlich schon vorhanden. Daher wurde kein neues Verzeichnnis erstellt |
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: about_automatic_variable
Beispiel 1b: Anlage eines Verzeichnisses (.Net-Klasse System.IO.Directory)
MSDN: Directory.CreateDirectory-Methode
$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 $? wie im Beispiel 1a funktioniert mit der .Net Klasse nicht.
Beispiel 1c: Anlage eines Verzeichnisses (FileSystemObject)
MSDN: CreateFolder Method
Set-StrictMode -Version "2.0" Clear-Host $Path="C:\Temp\Test" Try{ $fso=New-Object -Com "Scripting.FileSystemObject" $fso.CreateFolder($Path) } Catch{ "Verzeichnis wahrscheinlich schon vorhanden" } |
#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 (1a bis 1c) ist dann dasselbe, wenn das gewünschte Verzeichnis noch nicht existiert und angelegt werden kann.
Unterschiede gibt es, wie man sieht, im Errorhandling
Beispiel 2a: explizite Anlage einer Datei
Technet: New-Item
Eine Datei gesondert anzulegen, ist ebenso wie beim Verzeichnis recht einfach über den Fileprovider und New-Item möglich
Set-StrictMode -Version "2.0" Clear-Host $FilePath = "C:\Temp\File.log" New-Item $FilePath -Type File -EA 0 If($? -ne $True){ "Datei wahrscheinlich schon vorhanden" } |
Hat mein eine Logdatei, in die im Laufe eines Skriptes mehrere Textzeilen angehängt werden, möchte man zu Skriptbeginn oft diese Lodatei neu erstellen, um alten Inhalt zu verwerfen.
Beispiel 2b: explizite Anlage einer Datei mit Zeitstempel/ TimeStamp
Set-StrictMode -Version "2.0" Clear-Host $TimeStamp = $("{0:yyyyMMdd-HHmmss}" -f [DateTime]::Now) $FilePath="C:\Temp\File_$TimeStamp.log" New-Item $FilePath -Type File |
So wird bei jedem Skriptlauf eine neue Log-Datei erstellt. Weitere Information über die benutzerdefinierte Formatierung von Datumswerten findet ihr unter Grundlagen -> Formatierung des Outputs
Beispiel 3a: automatische Anlage einer Datei (MSDOS-Piping)
$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: Set-Content
$FileLogPath="C:\File.Log" Get-Childitem C:\Temp | Set-Content $FileLogPath -Value $_.Name |
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" interessante Möglichkeiten zur Filterung.
Beispiel 3c: automatische Anlage einer Datei (Out-File)
Technet: Out-File
Set-StrictMode -Version "2.0" Clear-Host $FilePath_1="C:\Temp\File-OutFile_1.txt" $FilePath_2="C:\Temp\File-OutFile_2.txt" #Variante1 Get-Childitem C:\Temp | Format-Table -auto | Out-File -FilePath $FilePath_1 #Variante2 $Childs = Get-Childitem C:\Temp | Format-Table -auto Out-File -InputObject $Childs -FilePath $FilePath_2 |
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 und die Verwendung des cmdlets Format-Table
Alle drei Varianten (Beispiel 3a, 3b und 3c) schreiben zwar Text in eine Textdatei. Inhalt und Form des Ergebisses können -bei gleichem Input- jedoch unterschiedlich sein. Wenn einem der Output einer Methode nicht gefällt, lohnt es sich auch die beiden anderen gezeigten Methoden zu probieren.
Beispiel 4: Löschen eines Verzeichnisses (Remove-Item)
Technet: Remove-Item
Set-StrictMode -Version "2.0" Clear-Host $Path = "C:\temp\Homes\HomeUser001" Remove-Item $Path -recurse #-EA 0 If($?){ "Pfad erfolgreich gelöscht" }Else{ "Pfad konnte nicht (komplett) gelöscht werden" } |
#mögliche Ausgabe Pfad erfolgreich gelöscht |
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.
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:
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
Set-StrictMode -Version "2.0" Clear-Host #Setzen der Variablen $Source = "C:\temp\Homes\HomeUser002" $Destination = "C:\Temp1\Homes\HomeUser001\" $Computername = "." $LogFile="c:\temp\roblog.txt" $WaitTime=5 $Retries=2 #Kopiert die Orginalstruktur unter $Source nach $Destination $Command="robocopy $Source $Destination /s /E /W:$WaitTime /R:$Retries /log:$Logfile" #Optionen $StartupOptions=[WmiClass] "Win32_ProcessStartup" $StartupOptions.PsBase.Properties["ShowWindow"].Value=0 #$Command ausführen $ManagementClass=[System.Management.ManagementClass] "\\$Computername\ROOT\cimv2:Win32_Process" # kürzer: $$ManagementClass=[WmiClass] "Win32_Process" $null=$ManagementClass.create($Command,$Null,$StartupOptions) |
- Beispiele für robocopy gibt es nahezu unendlich. In der Zeile "$command="..." lassen sich diese einfach in die Powershell übertragen.
- Wenn der Destinationpfad nicht existiert, wird er angelegt
- Durch die das Setzen der StartupOption "ShowWindow=0" flackert kein Commandlinefenster auf
- weitere Optionen zum ProcessStart unter MSDN: Win32_ProcessStartup class
-
mehr Erklärungen zu "Robocopy in der Powershell" findet ihr im im Beispiel 4d des Kapitels
2.1.3.2 Zeiteigenschaften (CreationTime, LastAccessTime, LastWriteTime)
- Für robocopy gibt es auch eine unter Powershell geschriebene GUI: Technet - PowerShell Robocopy GUI Tool - PowerCopy
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: Mehrere Arten die Existenz von Ordnern, Dateien oder Laufwerken zu prüfen
Set-StrictMode -Version "2.0" Clear-Host Function Main{ #Hier die zu prüfenden Parameter eingeben $TestFolder="C:\temp\" #existiert in diesem Beispiel $Testfile="C:\temp\test123.txt" #existiert in diesem Beispiel $TestDrive="Y:\" #exisitiert nicht in diesem Beispiel #Test mit dem Cmdlet "Test-Path" Checkby_TestPath $TestFolder $Testfile $TestDrive #Test mit dem Cmdlet "Get-Iten" Checkby_GetItem $TestFolder $Testfile $TestDrive #Test mit DotNet-Klassen Checkby_DotNet $TestFolder $Testfile $TestDrive #Test mit Com-Klassen Checkby_COM $TestFolder $Testfile $TestDrive }#End Main Function Checkby_TestPath{ Param($TestFolder,$Testfile,$TestDrive) Write-Host "1. Existenzpruefung mit Test-Path" -ForegroundColor DarkRed -BackgroundColor DarkYellow "{0} -- {1}" -f $($Testfolder),$(Test-Path $TestFolder) #gibt True oder False zurück "{0} -- {1}" -f $($TestFile), $(Test-Path $TestFile) #gibt True oder False zurück "{0} -- {1}" -f $($TestDrive), $(Test-Path $TestDrive) #gibt True oder False zurück " " } #end Checkby_TestPath Function Checkby_GetItem{ Param($TestFolder,$Testfile,$TestDrive) Write-Host "2. Existenzpruefung mit Get-Item" -ForegroundColor DarkRed -BackgroundColor DarkYellow $Null = Get-Item -Path $TestFolder -EA 0 #wirft bei Nichtvorhandensein einen Fehler If($?) { "{0} -- $?" -f $($TestFolder) }Else{ "{0} -- $?" -f $($Testfolder) } $Null = Get-Item -Path $TestFile -EA 0 #wirft bei Nichtvorhandensein einen Fehler If($?) { "{0} -- $?" -f $($TestFile) }Else{ "{0} -- $?" -f $($TestFile) } $Null = Get-Item -Path $TestDrive -EA 0 #wirft bei Nichtvorhandensein einen Fehler If($?) { "{0} -- $?" -f $($TestDrive) }Else{ "{0} -- $?" -f $($TestDrive) } "" } #End Checkby_GetItem Function Checkby_DotNet{ Param($TestFolder,$Testfile,$TestDrive) $Text="3. Existenzpruefung mit Klassen der .Net System.IO Bibliothek" Write-Host $Text -ForeGroundColor DarkRed -BackGroundColor DarkYellow $Folder=[System.Io.DirectoryInfo]$TestFolder "{0} -- {1}" -f $($Testfolder), $($Folder.Exists) $File=[system.io.FileInfo]$Testfile "{0} -- {1}" -f $($TestFile), $($File.Exists) $Drive=[system.io.Driveinfo]$TestDrive "{0} -- {1}" -f $($TestDrive),$($Drive.IsReady) "" } #End Checkby_DotNet Function Checkby_COM{ Param($TestFolder,$Testfile,$TestDrive) $Text="4. Existenzpruefung mit Klassen der Com FileSystemObject Klasse" Write-Host $Text -ForeGroundColor DarkRed -BackGroundColor DarkYellow $Fso=New-Object -Com "Scripting.FileSystemObject" "{0} -- {1}" -f $($TestFolder), $($Fso.FolderExists($Testfolder)) "{0} -- {1}" -f $($TestFile), $($Fso.FileExists($Testfile)) "{0} -- {1}" -f $($TestDrive), $($Fso.DriveExists($TestDrive)) }#End Check_Com Main |
#mögliche Ausgabe 1. Existenzpruefung mit Test-Path C:\temp\ -- True C:\temp\test123.txt -- False Y:\ -- False 2. Existenzpruefung mit Get-Item C:\temp\ -- True C:\temp\test123.txt -- False Y:\ -- False 3. Existenzpruefung mit Klassen der .Net System.IO Bibliothek C:\temp\ -- True C:\temp\test123.txt -- False Y:\ -- False 4. Existenzpruefung mit Klassen der Com FileSystemObject Klasse C:\temp\ -- True C:\temp\test123.txt -- False Y:\ -- False |
Kapitel_2.1.2.3 Beispiel_1 Existenzprüfung.ps1.txt
Wie so oft, hat man wieder mehrere Möglichkeiten die Existenz von Objekten des Filesystems zu überprüfen
- Technet: Test-Path, Get-Item
- MSDN: System.IO - Namespace
- MSDN: FileSystemObject Methods
In der Function CheckBy_GetItem benutze ich außerden die automatic Variable $?, auch bekannt als "DollarHook"
$?
Enthält den Ausführungsstatus des letzten Vorgangs. Enthält
TRUE, wenn der letzte Vorgang erfolgreich war; FALSE, wenn
beim letzten Vorgang ein Fehler aufgetreten ist.
Den DollarHook sollte man sich merken. Mit dem Ausführungsstatus eines Befehls kann man manch If-Abfrage vereinfachen
Beispiel 2a: Prüfen auf das Vorhandensein bestimmter .Net Versionen
Entwickler arbeiten meist an den modernsten Maschinen im Unternehmen. Anwender oder Kunden, an die man ein Skript weitergibt, liegen möglicherweise ein oder zwei Betriebssystemgenerationen hinterher. Dann kann die Prüfung und Beachtung der benötigten .Net-Version sinnvoll sein.
Set-StrictMode -Version "2.0" Clear-Host If (Test-Path "$Env:Windir\Microsoft.Net\Framework\v4.0*"){ Write-Host -foreground Green "Net 4.0 ist installiert" }Else{ Write-Host -foreground Red "Net 4.0 fehlt" } if (Test-Path "$Env:Windir\Microsoft.Net\Framework\v3.5"){ Write-Host -foreground Green "Net 3.5 ist installiert" }else{ Write-Host -foreground Red "Net 3.5 fehlt" } |
#mögliche Ausgabe auf Windows8 Net 4.0 fehlt Net 3.5 ist installiert |
An einer solchen Maschine lässt sich beispielsweise nicht Powershell V3.0 installieren, da diese das Vorhandensein von .Net 4.0 voraussetzt (ebenso mindestens Windows7 mit ServicePack1)
Beispiel 2b: Anzeigen der installierten .Net Versionen
Set-StrictMode -Version "2.0" Clear-Host #gci -directory $env:windir\Microsoft.Net\framework | Format-Table -auto #ab PS3.0 gci $env:Windir\Microsoft.Net\framework | Where {$_.Mode -match "d"} | ft -auto |
#mögliche Ausgabe Verzeichnis: C:\Windows\Microsoft.Net\framework Mode LastWriteTime Length Name ---- ------------- ------ ---- d---- 26.07.2012 10:13 v1.0.3705 d---- 26.07.2012 10:13 v1.1.4322 d---- 26.07.2012 10:12 v2.0.50727 d---- 13.01.2013 09:58 v4.0.30319 |
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)
Set-StrictMode -Version "2.0" Clear-Host $Path = "C:\temp\Homes\" $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) If($Return.ReturnValue -eq 0){ "Aktion erfolgreich" }else{ "ReturnValue = $Return.ReturnValue" } |
- Die weiteren Optionen von Compress.exe seht euch bitte mit compact /? 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: Win32_ProcessStartup class
Noch etwas Theorie zu dem Thema habe ich hier zusammengetragen: 4.2.4 Verwendung von WMI zum Starten eines Prozesses (SchemaQuery)
Beispiel 2: Komprimieren bestimmter Dateitypen in einem Verzeichnis (CIM_DataFile)
Set-StrictMode -Version "2.0" Clear-Host #Filtervariablen setzen. Anführungszeichen beachten! $Drive = "'C:'" $Path="'\temp\bilder\'" $Extension = "'jpg'" #Bei Pfadangaben müssen zwei "\" stehen $Path=$Path.Replace("\","\\") #Array mit den gefilterten Dateien erstellen $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" } } If($Files.Count -eq 0){"keine Dateien zum komprimieren gefunden"} |
c:\temp\bilder\29.jpg wurde erfolgreich komprimiert c:\temp\bilder\30.jpg wurde erfolgreich komprimiert c:\temp\bilder\86.jpg wurde erfolgreich komprimiert |
Zum Komprimieren steht die Methode "compress()" der Klasse CIM_DataFile zur Verfügung.
Zum Dekomprimieren steht die Methode "uncompress()" zur Verfügung.
MSDN: CIM_DataFile -> Methods
Beispiel 3: Komprimieren eines Verzeichnisses (Win32_Directory)
Set-StrictMode -Version "2.0" Clear-Host #Variable setzen $Path = "c:\temp\Bilder" #bitte keinen abschließenden "\" am Pfadenende z.B. c:\temp If($(Test-Path $Path) -ne $True){ "Auf den Pfad $path kann nicht zugegriffen werden" exit } #Bei Pfadangaben müssen zwei "\" stehen $Path=$Path.Replace("\","\\") $Query= "Select * from Win32_Directory Where Name='$Path'" $Folder=Get-WmiObject -Query $Query $Return=$Folder.Compress() If ($Return.Returnvalue -eq 0){ "Die Komprimierung von $($Folder.Name) wurde erfolgreich abgeschlossen" }Else{ "bei der Komprimierung $($Folder.Name) gab es ein Problem. Value: $Return.Returnvalue" } |
- Zum Komprimieren steht die Methode "compress()" aus Win32_Directory zur Verfügung.
- Zum Dekomprimieren steht die Methode "uncompress()" aus Win32_Directory zur Verfügung.
Sollten Fehler auftreten, so gibt der ReturnValue nähere Auskunft über die Ursache: MSDN: Compress method of the Win32_Directory class (Windows)
Die in den letzten Beispielen gezeigten Methoden, führen zum selben Ergebnis wie das Packen über den Windows-Explorer
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
Zuerst müssen die kostenlosen CommunityExtensions heruntergeladen und importiert werden. Unter Grundlagen - My Powershell -> 3.3.3 Module und SnapIns -> B) Module: Community-Extensions (PSCX) Version 2.1.1
ist dieser Vorgang Installation der Community Extensions genauer beschrieben
Damit bekommen wir 4 zusätzliche cmdlets bereitgestellt, die zum Erstellen von Archiven verwendet werden können
Beispiel 1: Anzeige der Komprimierungs-CmdLets der CommunityExtension
Set-StrictMode -Version "2.0" Clear-Host 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 7-Zip . Das Programm ist Freeware, OpenSource, hat eine ordentliche Komprimierungsrate und ist weit verbreitet.
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:
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
Set-StrictMode -Version "2.0" Clear-Host Import-Module pscx $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.1.1 Production |
#mögliche Ausgabe Mode LastWrite Time Length Name ---- ---------- ---- ------ ---- -a-- 18.01.2013 16:38 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
Set-StrictMode -Version "2.0" Clear-Host Import-Module pscx $Source="c:\temp\homes" $Destination="c:\temp\homes2.zip" $Level=1 Write-Zip -path $Source -outputpath $Destination -level $Level -Quiet -FlattenPaths #getestet mit PSH V2.0 #getestet mit CommunityExtensions 2.1.1 2.1.1 Production |
#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 LastWrite Time Length Name ---- --------- ---- ------ ---- -a--- 18.01.2013 23:22 5976438 homes.zip |
Der Positionsüarameter -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
Set-StrictMode -Version "2.0" Clear-Host Import-Module pscx $Source="c:\temp\homes" $Destination="c:\temp\ziphomes" #muss existieren $Level=1 $Folders=@(Get-ChildItem -Path $Source|Where{$_.PSISContainer -Match $true}) $Folders | Foreach{ Write-Zip -path $_ -level 1 -OutputPath $Destination\$_.zip -quiet } #getestet mit PSH V2.0 #getestet mit CommunityExtensions 2.1.1 |
#mögliche Ausgabe Mode LastWriteTime Length Name ---- ------------------- ------ ---- -a--- 18.01.2013 16:51 HomeUser001.zip -a--- 18.01.2013 16:51 HomeUser002.zip -a--- 18.01.2013 16:51 HomeUser003.zip |
Es mag an mir liegen: Bei mir funktioniert jedenfalls der Parameter "-Append" von Write-Zip 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. Solltet ihr diese Aufgabenstellung bekommen, so seht euch die nächsten Beispiele mit 7-Zip an
2.1.2.5.2 Packen mit 7-Zip (www.7-zip.de)
Wie oben schon erwähnt, arbeite ich gerne mit 7-Zip, da das Programm Freeware (OpenSource) ist, eine ordentliche Komprimierungsrate besitzt und außerdem weit verbreitet ist.
Beispiel 1: Packen eines Verzeichnisses mit 7-zip
Set-StrictMode -Version "2.0" Clear-Host $7Zip_ProgramPath = "C:\Program Files\7-zip\7z" $Destination = "c:\temp\7ztest41.zip" $Source = "c:\temp\homes\homeuser002" $Option = "a" $Command = "$7Zip_ProgramPath $Option $Destination $Source" #$Command = "C:\Program Files\7-zip\7z a c:\temp\7ztest.zip c:\temp\homes\homeuser002" $ManagementClass=[System.Management.ManagementClass] "\\.\ROOT\cimv2:Win32_Process" #kürzer: $$ManagementClass=[WmiClass] "Win32_Process" $StartupOptions=[WmiClass] "Win32_ProcessStartup" $StartupOptions.PsBase.Properties["ShowWindow"].Value=1 $null = $ManagementClass.Create($Command,$Null,$StartupOptions) |
Beispiel 2: Hinzufügen weiterer Elemente in das Archiv
Set-StrictMode -Version "2.0" Clear-Host $7Zip_ProgramPath="C:\Program Files\7-zip\7z" $Destination = "c:\temp\7ztest41.zip" $Source = "c:\temp\homes\homeuser002" $Option = "a" $Command = "$7Zip_ProgramPath $Option $Destination $Source" #$Command="C:\Program Files\7-zip\7z a c:\temp\7ztest.zip c:\temp\homes\homeuser002" $ManagementClass=[System.Management.ManagementClass] "\\.\ROOT\cimv2:Win32_Process" # kürzer: $$ManagementClass=[WmiClass] "Win32_Process" $StartupOptions=[WmiClass] "Win32_ProcessStartup" $StartupOptions.PsBase.Properties["ShowWindow"].Value=0 $null=$ManagementClass.Create($Command,$Null,$StartupOptions) |
Der Code sieht exakt gleich wie in Beispiel 1 aus. Allerdings muss für Beispiel 2 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.
- Durch die das Setzen der StartupOption "ShowWindow=0" flackert kein Commandlinefenster auf
- weitere Optionen zum ProcessStart unter MSDN: Win32_ProcessStartup class
.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: Stream.CopyTo-Methode (Stream) dazu bekommen, die in einigen C#-Beispielen verwendet wird.
- MSDN: GZipStream-Klasse (Framework 2)
- MSDN: GZipStream-Klasse (Framework 4)
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)
Set-StrictMode -Version "2.0" Clear-Host 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 } #Rnde 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 /PSH V3.0 |
#mögliche Ausgabe Compression Original size: 5308, Compressed size: 3025 |
Kapitel_2.1.2.5.3 Beispiel_1a Datei komprimieren.ps1.txt
Das Beispiel ist konvertiert aus MSDN: GZipStream-Klasse (Framework 2)
Beispiel 1b: Komprimieren einer Datei mit der GZipStream-Klasse (Framework 2)
die beiden nächsten Beispiele habe ich in C# auf dieser. wie ich finde, ziemlich coolen Seite gefunden: CodeGain: Programmatically Compress and Decompress Files
Set-StrictMode -Version "2.0" Clear-Host $FileToCompress = "C:\temp\Homes\HomeUser001\test.log" $ZipFilename = "C:\temp\test.log.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($FileToCompress) $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)
Set-StrictMode -Version "2.0" Clear-Host $CompressedFile = "C:\temp\test.log.zip" $DecompressedFileName = "C:\temp\test.log" $ZipFile = New-Object System.IO.FileStream $CompressedFile, ([IO.FileMode]::Open), ([IO.FileAccess]::Read) $DecompressedFile = New-Object System.IO.FileStream $DecompressedFileName, ([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)) $DecompressedFile.Write($Buffer, 0, $BytesRead) if ($BytesRead -ne $Buffer.Length){ break } } $Alg.close() $DecompressedFile.close() $ZipFile.close() |
auch die Logik dieses Beispiel stammt von der von der genannten Website
Beispiel 2a: Komprimieren aller Dateien eines Ordners mit der GZipStream-Klasse (Framework 4)
Falls noch nicht geschehen, müssen bis Win7 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
Ab Windows8 ist dieser Schritt nicht mehr nowendig
Set-StrictMode -Version "2.0" Clear-Host 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() #[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: GZipStream-Klasse (Framwork 4)
Beispiel 2b: Dekomprimieren einer Datei mit der GZipStream-Klasse (Framework 4)
Set-StrictMode -Version "2.0" Clear-Host function Decompress { Param([System.IO.FileInfo]$FileInfo) #[System.IO.FileInfo] kann man auch weglassen # Get the stream of the source file. [System.IO.FileStream]$inFile = $FileInfo.OpenRead() # Get original file extension, for example # "doc" from report.doc.gz. $CurFile = $FileInfo.FullName $OrigName = $curFile.Remove($curfile.Length - $FileInfo.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) #Copy the decompression stream into the output file. [System.IO.Compression.GZipStream]$Decompress.CopyTo($OutFile); "Decompressed: {0}"-f $FileInfo.Name $InFile.Close() $OutFile.Close() } $DirPath="C:\temp" $DirectoryInfo =New-Object System.IO.DirectoryInfo($dirpath) $DirectoryInfo.GetFiles("*.gz") | foreach{ Decompress($_) } |
#mögliche Ausgabe Decompressed: 4MKarlnlJ.txt.gz Decompressed: test2.txt.gz |
Das Beispiel ist konvertiert aus MSDN: GZipStream-Klasse (Framwork 4)
2.1.2.6 Kopieren einer Datei unter Alternate-Credentials
Obwohl in der OnlineHilfe von Copy-Item der Parameter "Credential" dabei ist, scheint er nicht akzeptiert zu werden.
Zumindest erhalte ich immer folgende Fehlermeldung:
The FileSystem provider supports credentials only on the New-PSDrive cmdlet. Perform the operation again without specifying credentials. |
Googelt man nach dieser Meldung, scheint es sich offenbar um einen Fehler zumindest bis Powershell 3.0 zu handeln. Alternativ kann man sich zuerst ein Laufwerk mit der COM-Klasse WScript.Network mappen und darüber den Kopiervorgang durchführen
Beispiel1: Mappen eines Laufwerks/ Kopieren einer Datei unter veränderten Credentials
Clear-Host Set-Strictmode -Version "2.0" Function Main{ $MainParams = @{ "ServerName" = "10.10.10.20"; "UserName" = "dom1\Administrator"; "SourceFile" = "C:\Temp\TestFile.ini"; "DestinationPath" = "C$\Temp"; #aus: http://www.powershellmagazine.com/2012/01/12/find-an-unused-drive-letter/ "TempDriveLetter" = ls function:[d-z]: -n|?{!(test-path $_)}|random } MapDriveAndCopy $MainParams } #End Main Function MapDriveAndCopy{ Param($Params) $Credentials = Get-Credential $Params.Username $UserName = $CredEntials.GetNetworkCredential().UserName $Password = $Credentials.GetNetworkCredential().Password $WshNetwork = New-Object -com WScript.Network $Drive = $Params.TempDriveLetter $Path = "\\$($Params.ServerName)\$($Params.DestinationPath)" $SourceFile = $Params.SourceFile $WshNetwork.MapNetworkDrive($Drive, $Path, "true", $($Params.UserName), $Password) Copy-Item $SourceFile -Destination $Drive $WshNetwork.RemoveNetworkDrive($drive,"true","true") } #End MapDriveAndCopy Main |
- Einen zufälligen freien Laufwerksbuchstaben erhalte ich mit der grün eingefärbten Funktion, die ich hier im Powershellmagazine gefunden habe
- Die wesentlichen Aufgaben des Skriptes (Laufwerk mit zufälligem, noch freien Buchstaben mappen, Datei kopieren, Laufwerksmapping wieder entfernen) erledigen die hellorange eingefärbten Codezeilen,
- Technet: Copy-Item
- MSDN: MapNetworkDrive Method
- MSDN: RemoveNetworkDrive Method
- Um die Übergabe der Variablen von der Main- in die MapDriveAndCopy-Funktion übersichtlich zu halten, verwalte ich die Parameter/ Variablen in den Hashtables $MainParams und $Params. siehe: Grundlagen -> Eine Frage des Stils -> 2.3.1 Hashes zum Strukturieren von Variablen verwenden
2.1.2.7 Dateien auf Gleichheit prüfen
Um beispielsweise den Erfolg eines Kopiervorgangs abzusichern, kann man für die Quelle und die Source jeweils einen Hash erzeugen und die beiden Hashs gegeneinander vergleichen
Beispiel 1: MD5-Hash berechnen
Set-StrictMode -Version "2.0"
Clear-Host
$SourcePath = "C:\temp\test.txt"
$md5 = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
$SourceHash = [System.BitConverter]::ToString($md5.ComputeHash([System.IO.File]::ReadAllBytes($SourcePath)))
#copying c:\temp\test.txt d:\temp\test.txt
$DestinationPath = "D:\temp\test.txt"
$md5 = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
$DestinationHash = [System.BitConverter]::ToString($md5.ComputeHash([System.IO.File]::ReadAllBytes($DestinationPath)))
If($SourceHash -eq $DestinationHash){
"Kopiervorgang erfolgreich, beide Dateien sind identisch "
"Hash: $DestinationHash"
}else{
"Dateien sind nicht identisch"
"SourceHash: $SourceHash"
"DestHash : $DestinationHash"
}
|
#mögliche Ausgabe
Dateien sind nicht identisch
SourceHash: D4-1D-8C-D9-8F-00-B2-04-E9-80-09-98-EC-F8-42-7E
DestHash : 86-7A-DE-73-AC-0A-79-30-CE-73-1A-69-C2-51-AE-E9
|
Sobald sich die Dateien nur um ein einziges Zeichen unterscheiden, unterscheidet sich auch der Hash
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
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: DirectoryInfo Class
MSDN: FileInfo Class
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;} |
Die Properties "Mode" und "Attributes" überschneiden sich teilweise, wie man an den beiden folgenden Auflistungen sieht:Technet: Get-Item
Hinter der Property "Attributes" verbergen sich die sogenannten Fileattribute, wobei der Name "File/ Datei" 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: FileAttributes-Enumeration
die möglichen Werte der Property "Mode" lauten laut Beispiel 1 in Technet: Get-ChildItem
d (directory)
a (archive)
r (read-only)
h (hidden)
s (system)
Beispiel 2a: Attribute eines Verzeichnisses bestimmen (mit Get-Item)
Set-StrictMode -Version "2.0" Clear-Host $Path="C:\temp\" $DirectoryInfo=Get-Item $Path # $DirectoryInfo=New-Object System.IO.DirectoryInfo("$Path") #gleichwertig # $DirectoryInfo=[System.IO.DirectoryInfo]("$Path") #gleichwertig $DirectoryInfo.Attributes $DirectoryInfo.Mode |
#Ausgabe Directory, Archive, Encrypted da--- |
die Eigenschaften "Attributes" und "Mode" sind im Beispiel 1 "Eigenschaften und Methoden von Get-Item" beschrieben
MSDN: DirectoryInfo-Klasse
Beispiel 2b: Attribute eines Verzeichnisses bestimmen (mit Wmi - Win32_Directory)
Set-StrictMode -Version "2.0" Clear-Host $Path = "C:\temp" #kein abschließendes "\" setzen! 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 |
#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 dafür den Vorteil, Methoden zum Komprimieren und Dekomprimieren zu besitzen
MSDN: Win32_Directory class
Beispiel 3a: Attribute einer Datei bestimmen (mit Get-Item)
Set-StrictMode -Version "2.0" Clear-Host $Path="C:\temp\eula.txt" $FileInfo= Get-Item -Path $Path -force #$FileInfo=New-Object System.IO.FileInfo("$Path") #gleichwertig #$FileInfo=[System.IO.FileInfo]("$Path") #gleichwertig $FileInfo.Mode $FileInfo.Attributes |
#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: FileInfo-Class
Beispiel 3b: Attribute einer Datei (Cim_DataFile)
Set-StrictMode -Version "2.0" Clear-Host $Drive="'C:'" #Anführungszeichen beachten $Path="'\temp\'" #Anführungszeichen beachten $File="'mappe1'" #Der Name ohne Extension $Extension="'xlsx'" #Anführungszeichen beachten $Path=$Path.Replace("\","\\") #Bei Pfadangaben müssen zwei "\" stehen $Filter = "Drive=$Drive and Path=$Path and Filename=$File and Extension=$Extension" $FileObject=Get-WmiObject -Class CIM_DataFile -Filter $Filter #Ausgabe $FileObject.Name $FileObject.LastAccessed "" "AccessMask (Dez): {0}" -f $FileObject.psbase.Properties["AccessMask"].value "AccessMask (Hex): {0:x}" -f $FileObject.psbase.Properties["AccessMask"].value "AccessMask (Binary): {0}" -f ([Convert]::ToString($FileObject.psbase.Properties["AccessMask"].value, 2)) |
#mögliche Ausgabe c:\temp\mappe1.xlsx 20130125230733.613729+060 AccessMask (Dez): 1507775 AccessMask (Hex): 1701bf AccessMask (Binary): 101110000000110111111 |
- Die WMI-Klasse CIM_DataFileenthält einige Eigenschaften mehr, als die .Net-Klasse [FileInfo]
- Auf Accessmasken gehe ich hier näher ein: 3 Datei- und Verzeichnisberechtigungen
MSDN: CIM_DataFile class
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"
Clear-Host Set-StrictMode -Version "2.0" $Path = "c:\temp\eula.txt" If (Test-Path $Path){ $Attributes=((Get-Item $Path).Attributes) $AttributeDirectory=[System.IO.FileAttributes]::Directory If ($Attributes -Match $AttributeDirectory) {"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"} |
#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"
Clear-Host Set-StrictMode -Version "2.0" $Path = "c:\temp\eula.txt" If (Test-Path $Path){ $Attributes=((Get-Item $Path).Mode) if($Attributes -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"} |
#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 mit der NoteProperty "PSIsContainer zusätzlich eine elegante Methode an, die das folgende Beispiel zeigt.
Clear-Host Set-StrictMode -Version "2.0" $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"} |
#mögliche Ausgabe Der Pfad existiert und es handelt sich um ein Directory |
Beispiel 5a: Unter einem Verzeichnis alle Ordner filtern
Clear-Host Set-StrictMode -Version "2.0" $Path="c:\temp\homes\" Get-ChildItem -path $Path | Where{$_.PSISContainer} #Get-ChildItem $Path -directory # ab Powershell V3.0 # Get-ChildItem -path $Path|Where{($_.Attributes -match [System.IO.FileAttributes]::Directory)} # Get-ChildItem -path $Path|Where{$_.Mode -match "d"} |
Clear-Host Set-StrictMode -Version "2.0" $Path="c:\temp\homes\" Get-ChildItem -path $Path -directory # ab PS V3.0 , -File oder -Directory" #getestet unter PSH V3.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 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.
Clear-Host Set-StrictMode -Version "2.0" $Path="c:\temp\homes\" $Folders = Get-ChildItem $Path -Recurse | Where {$_.PSIsContainer} $Folders | Where {$_.GetFiles().Count -eq 0} | Select FullName |
#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
Clear-Host Set-StrictMode -Version "2.0" $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"} |
Clear-Host Set-StrictMode -Version "2.0" $Path="c:\temp\homes\" Get-ChildItem -path $Path -file # ab PS V3.0 , -File oder -Directory" |
#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 Varianten liefern dasselbe Ergebnis. Für recursive Suchen besitzt das cmdlet get-childitem den Positionsparameter "recursive".
Beispiel 6a: Alle readonly-Dateien eines Ordners in ein Array schreiben
Clear-Host Set-StrictMode -Version "2.0" $path="c:\temp\" $RO_Files=@(Get-ChildItem -path $Path -force | where{($_.Attributes -match [System.Io.FileAttributes]::ReadOnly)}) #$RO_Files=@(Get-ChildItem -path $Path -force | where{($_.Attributes -band [System.Io.FileAttributes]::ReadOnly)}) #$RO_Files=@(Get-ChildItem -path $Path -force | where{($_.IsReadonly -eq True)}) $RO_Files | foreach{ "{0} {1} {2}" -f $_.Name,$_.Mode,$_.Attributes } |
#mögliche Ausgabe eula.txt -arh- ReadOnly, Hidden, Archive, Encrypted |
Bitte Beachten: Mit "$a=@(....)" wird die Variable RO_Files (=ReadOnly_Files) 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 $RO_Files 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
Clear-Host Set-StrictMode -Version "2.0" $Path="c:\temp\" #Files auslesen, die den ReadOnly- (=Schreibgeschützt-) Flag gesetzt haben $RO_Files=@(gci -path $path -force | Where{($_.Attributes -match [System.Io.FileAttributes]::ReadOnly)}) $RO_Files | foreach{ "{0} {1} {2}" -f $_.Name,$_.Mode,$_.Attributes } #ReadOnlyFlag entfernen $RO_Files | foreach{ $_.Attributes = $_.Attributes -bxor [system.IO.FileAttributes]::ReadOnly Write-Host "ReadonlyFlag von $($_.Name) entfernt" -ForeGroundcolor Red } #Alternative zum Entfernen #$RO_Files | foreach{ $_.IsReadOnly = $false } |
2.1.3.2 Zeiteigenschaften (CreationTime, LastAccessTime, LastWriteTime)
Jede Objekt im NTFS-FileSystem besitzt vier Zeitstempel, die als Metadaten im NTFS-Inhaltsverzeichnis (MFT) abgelegt sind (CreationTime, LastAccessTime, LastWriteTime + LastChange des MFT-Eintrags). Mit geeigneten NTFS-Tools kann man sich jeden dieser 1024-bit großen Einträge ansehen. Die LastAccessTime wird seit Windows Vista als nicht mehr besonders wichtig eingestuft und ist daher default deaktiviert, kann aber aktiviert werden (siehe Ende dieses Abschnittes)
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 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, dass ab Windows7/ Server 2008 die Eigenschaft "LastAccessTime" nicht mehr automatisch upgedatet wird. MSDN: File System Functionality Comparison
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. MSDN:Disabling Last Access Time Stamps. Laut dem MSDN-Artikel hat diese Einstellung auch Einfluss auf die Performance des FileSystems, ist also aus Performancegründen ohne gewichtige Gründe nicht unbedingt empfehlenswert
Beispiel 1: Zeitstempel einer Datei mit .Net und COM auslesen
Clear-Host Set-StrictMode -Version "2.0" $Path="C:\temp\Homes\HomeUser001\book1.xlsx" "`nDatumsinformationen aus der .Net Klasse [FileInfo]" $DotNetfile=Get-Item -path $Path $DotNetfile | Format-List CreationTime,LastWriteTime,LastAccessTime "`nDatumsinformationen aus der .COM Klasse [FileSystemObject]`n" $Fso=New-Object -Com "Scripting.FileSystemObject" $ComFile=$Fso.GetFile($Path) "DateCreated : $($ComFile.DateCreated)" "DateLastModified : $($Comfile.DateLastModified)" "DateLastAccessed : $($Comfile.DateLastAccessed)" |
#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 mit .Net arbeiten. Trotzdem funktioniert auch COM tadellos!
Beispiel 2: Zeitstempel einer Datei verändern
COM-Klassen lasse ich ab jetzt weg.
Clear-Host Set-StrictMode -Version "2.0" $Path = "C:\temp\Homes\HomeUser001\book1.xlsx" $DotNetfile = Get-Item -path $Path #Manipulation der Zeitstempel $DotNetFile.LastAccessTime = ([datetime]::Now).AddDays(-1500) $DotNetFile.CreationTime = ([datetime]::Now).AddDays(+12202) $DotNetFile.LastWriteTime = ([datetime]::Now).AddDays(-30000) #Ausgabe "CreationTime: $($DotNetFile.CreationTime)" "LastAccessTime: $($DotNetFile.LastAccessTime)" "LastWritetime : $($DotNetFile.LastWriteTime)" |
#mögliche 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 aus der Vergangenheit unterjubeln. Richtig angewandt, kann man damit den ein oder anderen Kollegen durchaus zum Grübeln bringen :-)
Um auf das Thema "Filtern nach Attributen" zurückzukommen: Jeden der drei Zeitstempel sollte man im skript nur verwenden, wenn man sicher ist, dass 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-ItemProperty ebenfalls eine Möglichkeit bereit, Zeitstempel zu verändern
Clear-Host Set-StrictMode -Version "2.0" $Path="C:\temp\Homes\HomeUser001\test.txt" $NewTime = [System.DateTime]"1810,10,16" #Setzen der LastWriteTime Set-ItemProperty $Path LastWriteTime $NewTime #Abfrage der LastWriteTime $Properties = Get-ItemProperty $Path "Die Eigenschaft LastwriteTime der Datei: {0:d} " -f $Properties.LastwriteTime |
#mögliche Ausgabe LastwriteTime der Datei: 08.09.2010 |
Technet: Set-Item
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 aus einem String
Clear-Host Set-StrictMode -Version "2.0" #Culturevaraiblen definieren $CultureDE = New-Object System.Globalization.CultureInfo("de-DE") $CultureUS = New-Object System.Globalization.CultureInfo("en-US") $CultureInvariant= [System.Globalization.CultureInfo]::InvariantCulture #verschiedene Möglichkeiten, einen String in ein [DateTime]-Objekt zu konvertieren $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) #Ausgabe "MyTime1: $MyTime1" "MyTime2: $MyTime2" "MyTime3: $MyTime3" "MyTime4: $MyTime4" "MyTime5: $MyTime5" "MyTime6: $MyTime6" "MyTime7: $MyTime7" "MyTime8: $MyTime8" |
#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.
- $MyTime1 - Mehr Infos unter Technet: Get-Date
- $MyTime2 - Mehr Infos unter MSDN: DateTime-Konstruktor (Int32, Int32, Int32, Int32, Int32, Int32)
- $MyTime3 - Mehr Infos unter MSDN: DateTime-Konstruktor (Int32, Int32, Int32)
- $MyTime4 bis $MyTime8 - Mehr Infos unter MSDN: DateTime.ParseExact-Methode (String, String, IFormatProvider)
Step 2:Ein selbsterstelltes DateTime-Objekt mit der CreationTime einer Datei vergleichen
Clear-Host Set-StrictMode -Version "2.0" #Datetime-Objekt erstellen $Year=2011 $Month=09 $Day=24 $MyTime = New-Object System.DateTime($Year,$Month,$Day) #FileInfo-Objekt erstellen $Path="C:\temp\Homes\HomeUser001\test.txt" $FileItem=Get-Item -path $Path #Vergleich der Eigenschaft "CreationTime mit dem selbsterstellten DateTime-Objekt If($FileItem.CreationTime -gt $Mytime){ "die Datei ist um {0:0} Tage jünger, als {1:d} " -f $($FileItem.CreationTime - $MyTime).Days,$MyTime }ElseIf($FileItem.CreationTime -le $MyTime){ "die Datei ist um {0:0} Tage älter, als {1:d}" -f $($MyTime-$FileItem.CreationTime).Days,$MyTime } |
#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 ist. 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:
Clear-Host Set-StrictMode -Version "2.0" $Jetzt1 = Get-Date $Jetzt2 = [System.Datetime]::Now $Heute = [System.Datetime]::Today #Ausgabe $Jetzt1 $Jetzt2 $Heute |
#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
[System.DateTime] | Get-Member -static |
oder auch der Msdn: DateTime - Eigenschaften . Statisch sind die Eigenschaften, die unter Name ein oranges "S" enthalten, wie "now" oder "today"
Statische Eigenschaften und Methoden beziehen sich auf die Klasse, nicht auf ein Objekt!
Beispielsweise liefert [System.DateTime]::MinValue die Information, dass mit dieser Klasse Datumswerte ab dem 01.01.0001 00:00:00 verarbeiten werden können. Für Datumswerte aus der vorchristlichen Zeit ist diese Klasse also nicht geeignet.
Step 2: Zeitstempel "Heute minus 30 Tage" und "Heute minus 2 Jahre" berechnen
$Jetzt=Get-Date $Jetzt.AddDays(-30) $Jetzt.AddYears(-2) |
#mögliche Ausgabe Sonntag, 21. August 2011 21:40:46 Sonntag, 20. September 2009 21:40:46 |
Objekte der Klasse [System.Date] besitzen unter anderem die folgenden Methoden, um einen Zeitstempel zu verändern:
- AddDays System.DateTime AddDays(double value)
- AddMonths System.DateTime AddMonths(int months)
- AddYears 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, oder auch wieder in der MSDN
Get-Date | Get-Member #präziser Get-Date | Get-Member -MemberType Method | where{$_.Name -match "add"} |
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.
Set-StrictMode -Version "2.0" Clear-Host $Jetzt = Get-Date $Path="c:\temp\Homes\HomeUser001\test.txt" $JetztMinus30Tage=$Jetzt.AddDays(-30) $JetztMinus2Jahre=$Jetzt.AddYears(-2) $File=Get-Item $Path if($JetztMinus30Tage -gt $File.CreationTime) { "Die Datei ist älter als 30 Tage" } if($JetztMinus2Jahre -gt $File.Creationtime) { "Die Datei ist sogar älter als 2 Jahre" "nämlich genau {0} Tage:" -f ($Jetzt - $File.Creationtime).TotalDays.ToString("#.##") } |
#mögliches Ergebnis Die Datei ist älter als 30 Tage Die Datei ist sogar älter als 2 Jahre nämlich genau 754,03 Tage |
Unter 2.1.3.2 Zeiteigenschaften (CreationTime, LastAccessTime, LastWriteTime) in diesem Kapitel findet ihr Skripte um die Zeitstempel von Dateien zu verändern und damit Testfiles zu erstellen.
Falls ihr die Eigenschaft LastAccessTime verwenden wollt, lest Euch vorher nochmal die Einleitung zu dem eben genannten Kapitel durch. Es muss zuerst der Registryschlüssel HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem auf 0 gesetzt werden
Im Kapitel 2.1.1 Inhalt und Struktur von Verzeichnissen findet ihr das Beispiel 5: "Alterstruktur der Dateien im Verzeichnis", mit dem ihr ein ganzes Verzeichnis untersuchen könnt
Beispiel 4a: Filtern aller Dateien älter als 30 Tage
Set-StrictMode -Version "2.0" Clear-Host $Path = "c:\temp\Homes\HomeUser001" $JetztMinus30Tage=(Get-Date).AddDays(-30) "Skriptaufruf am {0} `n" -f [DateTime]::Now $Files = Get-ChildItem $Path | Where{$JetztMinus30Tage -gt $_.LastWriteTime} $Files | ForEach{ "{0} {1:d} " -f $_.FullName,($_.LastWriteTime) } |
#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: Measure-Object
Set-StrictMode -Version "2.0" Clear-Host $Path = "c:\temp\Homes\" $JetztMinus30Tage=(Get-Date).AddDays(-30) $FilesAelter30Tage = Get-ChildItem $Path -recurse| Where{$JetztMinus30Tage -gt $_.CreationTime} #Ausgabe Write-Host -BackgroundColor Yellow -ForeGroundColor Blue "Etwas Statistik `n" $MeasureFiles=$FilesAelter30Tage | Measure-Object -property Length -sum -maximum -minimum -average "Unter dem Pfad $Path 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) |
#Ausgabe Etwas Statistik Unter dem Pfad c:\temp\Homes\ befinden sich 669 Dateien älter 30 Tage Die Größe aller Dateien älter 30 Tage beträgt zusammen 3099,2 KB Die größte Datei ist 12,8 KB groß Die kleinste Datei ist 0,6 KB groß |
Technet: Measure-ObjectDas cmdlet Measure-Object ist ein Powershellschmankerl, mit dem einfach 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.
Set-StrictMode -Version "2.0" Clear-Host $Path = "C:\temp\Homes\" $ArchivPath = "C:\Temp\HomesArchiv\" $JetztMinus30Tage = (Get-Date).AddDays(-30) $FilesAelter30Tage = @(Get-ChildItem $Path -recurse| Where{$JetztMinus30Tage -gt $_.CreationTime}) $FilesAelter30Tage | ForEach{ $ArchivPath=$($_.FullName).Replace($Path,$ArchivPath) $ArchivPath = Split-Path $ArchivPath -Parent New-Item -path $ArchivPath -type Directory -EA 0 #keine Fehlermeldung Move-item -Path $_.FullName -Destination $ArchivPath -EA 0 #keine Fehlermeldung 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 } } |
#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: Erzeugen einer Ordnerstruktur mit zufällig gefüllten Dateien verschiedenen Alters" 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.
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 dass man das Zielverzeichnis (=$ArchivPath) erhält
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" ->
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:
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:
Set-StrictMode -Version "2.0" Clear-Host $Path = "C:\temp\Homes\" $ArchivPath = "C:\Temp\HomesArchiv\" $LogFile="c:\temp\roblog.txt" $JetztMinus30Tage=(Get-Date).AddDays(-30) $FilesAelter30Tage = @(Get-ChildItem $Path -recurse| Where{$JetztMinus30Tage -gt $_.CreationTime}) $FilesAelter30Tage | ForEach{ $ArchivPath=$($_.FullName).Replace($Path,$ArchivPath) $ArchivPath = Split-Path $ArchivPath -Parent New-Item -path $ArchivPath -type directory -EA 0 #Keine Fehlermeldung $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 der RobocopyBefehl mit #Invoke-Expression $command #aufgerufen werden } |
- Um externe Programme wie Robocopy mit Parametern zu starten, benutze ich entweder die WMI-Klasse Win32_Process 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
Näheres dazu, sowie ein Powershell/ RobocopyBeispiel mit weiteren Parametern findet ihr im Kapitel
Möchte man die Robocopy-Robustheit mit Powershell nachprogrammieren, sind diese Links interessant:
- blog.usepowershell.com - Using the Sync Framework from PowerShell
- The ProwerShell Guy - PowerShell and Robocopy Part 4 - Leider scheint der Link nicht mehr zu existieren.
Beispiel 4e: Dateien, die älter als 30 Tage sind, in eine Archivdatei (ZIP-Datei) kopieren
Wichtig: Dieses Script muß im Administratorkontext ausgeführt werden
Set-StrictMode -Version "2.0" Clear-Host $Path = "C:\temp\" $Destination = "c:\temp\archiv30f.zip" $JetztMinus30Tage = (Get-Date).AddDays(-1) $FilesAelter30Tage = @(Get-ChildItem $Path -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 } |
Für dieses Beispiel muss das Programm 7-Zip installiert sein. Mehr grundlegende Informationen, wie man mit Powershell Zippen kann, findet ihr hier im Kapitel unter 2.1.2.5 Packen (Zippen) von Verzeichnissen und Dateien
2.1.3.3 Filtern nach Namensmustern
Meistens werden Dateinamen meiner Erfahrung keine besonders komplexen Suchkriterien erfordern. Einfache Suchkriterien sollten für die meisten Fälle ausreichen. Falls nicht, besucht mal in das Kapitel "String- und Textanalysen"
Beispiel 1a: Suche nach Dateien, die einen bestimmten String im Namen tragen
Set-StrictMode -Version "2.0" Clear-Host $Path = "C:\temp\homes" $Pattern="File" $FoundFilenames = @(Get-ChildItem $Path -recurse| Where{$_.Name -match $Pattern}) # Parameter -whatif im folgenden Code entfernen, dann werden die gefundenen Dateien gelöscht $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 kontrollieren, solange bei Remove-Item noch -whatif steht
Beispiel 1b: Suche nach Dateien mit einem einfachen regulären Ausdruck
Set-StrictMode -Version "2.0" Clear-Host $Path = "C:\temp\Homes\" $Pattern="File1[03][0-4]*" $FoundFilenames = @(Get-ChildItem $Path -recurse| Where{$_.Name -match $Pattern}) $FoundFilenames $FoundFilenames | foreach { Remove-Item $_.Fullname -force -whatif } |
#mögliche Ausgabe What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser001\file101.ps1". What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser001\file102.ps1". What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser002\file101.ps1". What if: Performing operation "Remove File" on Target "C:\temp\Homes\HomeUser002\file102.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="File1[03][0-4]" ist deutlich spezifischer und ein sogenannter Regulärer Ausdruck.
Der Dateiname muß mit "File1" beginnen, gefolgt von einer Zahl entweder 0 oder 3, gefolgt von einer Zahl zwischen 0 und 4. Je spezifischer ein Filterkriterium, desto zuverlässiger erhält man auch nur die gewünschten Ergebnisse zurück.
- Der Operator -match erlaubt die Verwendung von regulären Ausdrücken.
Technet: about_Regular_Expressions
- 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
Set-StrictMode -Version "2.0" Clear-Host $Path = "C:\temp\Homes\Home001\" $Pattern = "File*" $FoundFilenames = @(Get-ChildItem $Path -recurse -include $Pattern) $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. Ohne den Parameter -recurse bekommt man allerdings keine Ergebnisse zurück, selbst wenn im Verzeichnis $Path zutreffende Dateiobjekte liegen.
Der Include-Parameter führt nämlich dazu, dass nur Ergebnisse zurückgeliefert werden, wenn auch der -recurse Parameter vorhanden ist
Beispiel 2b: Filtern nur mit dem Parametern -Include von Get-Childitem und ohne -recurse
Ein wichtiger Hinweis findet sich in der Beschreibung dieses Parameters unter Technet: Get-ChildItem
"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."
Set-StrictMode -Version "2.0" Clear-Host $Path = "C:\temp\Homes\Home001\*" $Pattern = "file[0-9]*.txt" $FoundFilenames = @(Get-ChildItem $Path -include $Pattern) $FoundFilenames | ForEach { Remove-Item $_.FullName -force -whatif } |
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 zu filternden Ergebnisse zum Where-Object, wie in den Beispielen 1a) und 1b) beschrieben
- Man setzt den -recurse Parameter, durchsucht dadurch aber nicht mehr nur das SearchRootverzeichnis, sondern auch alle Unterverzeichnisse. Je nach Ordnerstruktur ist das eine mögliche Lösung oder nicht. (Beispiel 2a)
- Man muss als SearchRoot nicht "C:\temp\Homes\HomeUser001\" , sondern "C:\temp\Homes\HomeUser001\*" definieren (mit Stern, wie im Skript)
Das ist meiner Meinung eine kleine Gemeinheit von Get-ChildItem
2.1.3.4 Ein paar Tricks zu Get-ChildItem
Beispiel 1: Nach dem ersten Treffer die Suche beenden
Sucht Ihr nach genau einer einzigen bestimmten Datei suchen oder wollt die Suche nach dem ersten Treffer abbrechen, so könnt ihr dies mit "| Select -First 1" erreichen
Set-StrictMode -Version "2.0"
Clear-Host
$Path = "C:\Temp"
$Pattern = "C*.cer"
Get-ChildItem -recurse -Path $Path $Pattern | Select -First 1
|
Wenn die nachfolgenden Treffer uninteressant sind oder keine weiteren Treffer erwartet werden, kann man so die Resourcen für eine weitere Durchsuchung des Filesystem sparen.
Beispiel 2a: Recursive Suche nur bis zu einem bestimmten Level
Get-ChildItem c:\*\* |
Hier wird das Verzeichnis unter C:\ bis zum 2.-ten Level ausgegeben
Beispiel 2b: Alle Userprofile anzeigen
Set-StrictMode -Version "2.0"
Clear-Host
Get-ChildItem c:\users\* | Select Name
|
#mögliche Ausgabe
Name
----
InstallUsr
Public
Kaiuser
Kaiadmin
testuser1
|