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)

2.1 Dateien und Ordner

Zum Nachvollziehen der folgenden Beispiele ist eine Beispielordnerstruktur mit mehreren Unterordnern samt Dateien recht nützlich. Unter 0 Einleitung liegen einige Skripte, mit denen eine solche Ordnerstruktur samt Dateien unterschiedlichen Namens, sowie auch einzelne große Dateien erstellt werden können.,

 

2.1.1 Inhalt und Struktur von Verzeichnissen

In diesem Kapitel habe ich Beispiele erstellt, mit denen man sich einen Überblick über ein Verzeichnis samt Unterverzeichnis verschaffen kann.
 

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 Kapitel 2.1.3.1 Attribute von Verzeichnissen und Dateien werden diese Variante sowie zwei weitere Möglichkeiten genauer beschrieben. Ab Powershell V3.0 gibt es dazu die Positionsparameter -File und -Directory

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"
Clear-Host

Function Get-FolderSize{
   <#
   .synopsis
   The function gives back the size (including subfolders) of a folder using FSO
   #>

   Param($Path="c:\temp")

   $Fso = New-Object -com "Scripting.FileSystemObject"
   $Folder=$Fso.GetFolder($Path)
   $Size = ($Folder.size)/1Mb

   $Folder.DateCreated
   $Size.ToString("0.00") +"MB"
}

Get-FolderSize c:\temp

#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"
Clear-Host
 
$Path = "c:\temp"
$ExclusionListMembers  = @(".xlsx",".docx",".pptx",".xls",".doc",".ppt")
 
#Anlage der DataTabelle mit Spalten für jede Property in Properties
 
$Files = gci -path $Path -recurse -force -file
ForEach ($ExclusionListMember in $ExclusionListMembers){
  $Files = $Files | Where {$_.Extension -ne $ExclusionListMember}
}
 
$GroupInfos = $Files | Group-Object {$_.LastWriteTime.ToString("yyyy-MM")} | sort name

#$GroupInfos = $Files | Group-Object {$_.LastAccessTime.ToString("yyyy-MM")} | sort name
#$GroupInfos = $Files | Group-Object {$_.LastWriteTime.ToString("yyyy")} | sort name
 
$MyInfos = @()
ForEach ($YearMonth in $GroupInfos ) {
  [psobject]$MyInfo = ""| Select date,Size,count
  $Size = ($YearMonth.Group | Measure-Object -Property "Length" -Sum).Sum/1MB
  $MyInfo.Size = $Size
 
  $MyInfo.date = $YearMonth.Name
  $MyInfo.Count = $YearMonth.Count
 
  $MyInfos += $MyInfo
}
 
$MyInfos | Ft date,count,
    @{
    Label="FileSize in MB";
    Expression={"{0:0.00}" -f $($_.Size)};
    Align="Right"
  } -autosize
 

#mögliche Ausgabe

date    count FileSize in MB
----    ----- --------------
2014-06     6           0,84
2014-07    10           0,04
2014-08     5         269,62
2014-09   479           8,44
2014-10    11           0,34
2014-11    12           0,32
2014-12     3           0,09
2015-01    11           0,90
2015-02     5           0,75

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.

 

2.1.2 einfache Operationen mit Dateien und Verzeichnissen

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.3 Eigenschaften von Dateien und Ordnern wird es darum gehen, mehrerere Objekte mit einer bestimmten gleichen Eigenschaft zu Kopieren, Verschieben, Komprimieren und zu Zippen.

 

2.1.2.1 Erstellen und Löschen von Objekten im Filesystem,

 

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.

 

2.1.2.2 Kopieren und Verschieben von Dateien und Verzeichnissen

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)

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

 Diese Datei herunterladen (Kapitel_2.1.2.3 Beispiel_1 Existenzprüfung.ps1.txt) 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

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

 

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)

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

 

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

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: 2.1.2.5.1 Packen und Entpacken mit den CommunityExtensions


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: 2.1.2.5.2 Packen mit 7-Zip

 
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.3 Packen und Entpacken mit der .Net GZipStream-Klasse
 

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

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 
  •  

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: Stream.CopyTo-Methode (Stream) dazu bekommen, die in einigen C#-Beispielen verwendet wird.

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 MyPowershell -> 3.4 .Net Framework Versionen in Powershell zwei Möglichkeiten beschrieben, dies zu ermöglichen. In den weiteren Beispielen zeige ich den Schritt auch noch in diesem 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 MyPowershell -> 3.4 .Net Framework Versionen in Powershell.

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,

- 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

 

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: 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  Kapitel 2.1.2.3 Existenzprüfung von Elementen im FileSystem -> Beispiel 1

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

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 Formatierung des Outputs -> 1.1.1 StandardFormatierung von Datum und Uhrzeit nach.


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.

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 Formatierung des Outputs -> 1.1.1 StandardFormatierung von Datum und Uhrzeit nach.
 

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  Kapitel 0 Einleitung ausführen. Dann habt ihr eine kleine Filestruktur, auf die ihr das Skript hier anwenden könnt.

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" -> 2.2.3 Erroraction (PreferenceVariable: $ErrorActionPreference).

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


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 Prozesse & Dienste -> Prozesse -> 4.2.4 Verwendung von WMI zum Starten eines Prozesses (SchemaQuery)

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

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:

  1. Man piped die zu filternden Ergebnisse zum Where-Object, wie in den Beispielen 1a) und 1b) beschrieben
  2. 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)
  3. 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