如何正确过滤 PowerShell 复制脚本中的多个字符串

我正在使用来自 这个答案的 PowerShell 脚本进行文件复制。当我想使用过滤器包含多个文件类型时,就会出现问题。

Get-ChildItem $originalPath -filter "*.htm"  | `
foreach{ $targetFile = $htmPath + $_.FullName.SubString($originalPath.Length); `
New-Item -ItemType File -Path $targetFile -Force;  `
Copy-Item $_.FullName -destination $targetFile }

但是,当我想使用过滤器包含多个文件类型时,问题就出现了。

Get-ChildItem $originalPath `
-filter "*.gif","*.jpg","*.xls*","*.doc*","*.pdf*","*.wav*",".ppt*")  | `
foreach{ $targetFile = $htmPath + $_.FullName.SubString($originalPath.Length); `
New-Item -ItemType File -Path $targetFile -Force;  `
Copy-Item $_.FullName -destination $targetFile }

给我以下错误:

Get-ChildItem : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Filter'. Specified method is not supported.
At F:\data\foo\CGM.ps1:121 char:36
+ Get-ChildItem $originalPath -filter <<<<  "*.gif","*.jpg","*.xls*","*.doc*","*.pdf*","*.wav*",".ppt*" | `
+ CategoryInfo          : InvalidArgument: (:) [Get-ChildItem], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.GetChildItemCommand

我有各种各样的圆括号迭代,没有圆括号,-filter-include,定义包含为变量(例如,$fileFilter) ,每次都得到上述错误,并总是指向 -filter之后的内容。

有趣的例外是当我编写 -filter "*.gif,*.jpg,*.xls*,*.doc*,*.pdf*,*.wav*,*.ppt*"代码时。有没有错误,但我和得到没有结果,没有任何回到控制台。我怀疑我无意中用这句话编码了一个隐含的 and

那么,我到底做错了什么,又该如何纠正呢?

145863 次浏览

-Filter only accepts a single string. -Include accepts multiple values, but qualifies the -Path argument. The trick is to append \* to the end of the path, and then use -Include to select multiple extensions. BTW, quoting strings is unnecessary in cmdlet arguments unless they contain spaces or shell special characters.

Get-ChildItem $originalPath\* -Include *.gif, *.jpg, *.xls*, *.doc*, *.pdf*, *.wav*, .ppt*

Note that this will work regardless of whether $originalPath ends in a backslash, because multiple consecutive backslashes are interpreted as a single path separator. For example, try:

Get-ChildItem C:\\\\\Windows

Something like this should work (it did for me). The reason for wanting to use -Filter instead of -Include is that include takes a huge performance hit compared to -Filter.

Below just loops each file type and multiple servers/workstations specified in separate files.

##
##  This script will pull from a list of workstations in a text file and search for the specified string




## Change the file path below to where your list of target workstations reside
## Change the file path below to where your list of filetypes reside


$filetypes = gc 'pathToListOffiletypes.txt'
$servers = gc 'pathToListOfWorkstations.txt'


##Set the scope of the variable so it has visibility
set-variable -Name searchString -Scope 0
$searchString = 'whatYouAreSearchingFor'


foreach ($server in $servers)
{


foreach ($filetype in $filetypes)
{


## below creates the search path.  This could be further improved to exclude the windows directory
$serverString = "\\"+$server+"\c$\Program Files"




## Display the server being queried
write-host “Server:” $server "searching for " $filetype in $serverString


Get-ChildItem -Path $serverString -Recurse -Filter $filetype |
#-Include "*.xml","*.ps1","*.cnf","*.odf","*.conf","*.bat","*.cfg","*.ini","*.config","*.info","*.nfo","*.txt" |
Select-String -pattern $searchstring | group path | select name | out-file f:\DataCentre\String_Results.txt


$os = gwmi win32_operatingsystem -computer $server
$sp = $os | % {$_.servicepackmajorversion}
$a = $os | % {$_.caption}


##  Below will list again the server name as well as its OS and SP
##  Because the script may not be monitored, this helps confirm the machine has been successfully scanned
write-host $server “has completed its " $filetype "scan:” “|” “OS:” $a “SP:” “|” $sp




}


}
#end script
Get-ChildItem $originalPath\* -Include @("*.gif", "*.jpg", "*.xls*", "*.doc*", "*.pdf*", "*.wav*", "*.ppt")

Let's go over the options:

  • -Filter only takes one pattern, so it doesn't work for this problem.

  • -Include works but is very slow (which is totally fine in many cases).

  • Piping to Where-Object is much faster than -Include. It is also the most powerful option because it gives you access to regex pattern matching (instead of the normal wildcard matching) and any other logic you might need, such as in the example below:

    # checking extension with regex
    Get-ChildItem $dir |
    Where-Object { $_.Extension -match '\.(xlsx?|jpe?g)$' }
    
    
    # checking extension and creation time
    Get-ChildItem $dir | Where-Object {
    $_.Extension -in '.xls', '.xlsx', '.jpg', '.jpeg' -and
    $_.CreationTime -gt $yesterday
    }
    
  • -Path is slightly faster still but takes full paths rather than filenames, which is a pain to work with (see examples below) and only works for simple cases because path patterns can't match a variable number of directory levels. This is in contrast to typical shells, in which * matches a single directory and ** matches any number of nested directories.

    # simpler
    $paths = $dir\*.xls, $dir\*.xlsx, $dir\*.jpg, $dir\*.jpeg
    Get-ChildItem $paths
    
    
    # less repetitive
    $paths = 'xls', 'xlsx', 'jpg', 'jpeg' | % { Join-Path $dir *.$_ }
    Get-ChildItem $paths