Powershell 使用黑名单(排除)和白名单(包括)复制文件
Powershell Copy Files with a Blacklist (Exclude) and a Whitelist (Include)
我正在将一些 msbuild 脚本翻译成 powershell。
在 msbuild 中,我可以生成黑名单 and/or 白名单文件,其中包含我要(递归)复制到目标文件夹的文件。
如下图:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="AllTargetsWrapped">
<PropertyGroup>
<!-- Always declare some kind of "base directory" and then work off of that in the majority of cases -->
<WorkingCheckout>.</WorkingCheckout>
<WindowsSystem32Directory>c:\windows\System32</WindowsSystem32Directory>
<ArtifactDestinationFolder>$(WorkingCheckout)\ZZZArtifacts</ArtifactDestinationFolder>
</PropertyGroup>
<Target Name="AllTargetsWrapped">
<CallTarget Targets="CleanArtifactFolder" />
<CallTarget Targets="CopyFilesToArtifactFolder" />
</Target>
<Target Name="CleanArtifactFolder">
<RemoveDir Directories="$(ArtifactDestinationFolder)" Condition="Exists($(ArtifactDestinationFolder))"/>
<MakeDir Directories="$(ArtifactDestinationFolder)" Condition="!Exists($(ArtifactDestinationFolder))"/>
<Message Text="Cleaning done" />
</Target>
<Target Name="CopyFilesToArtifactFolder">
<ItemGroup>
<MyExcludeFiles Include="$(WindowsSystem32Directory)\**\EventViewer_EventDetails.xsl" />
</ItemGroup>
<ItemGroup>
<MyIncludeFiles Include="$(WindowsSystem32Directory)\**\*.xsl" Exclude="@(MyExcludeFiles)"/>
<MyIncludeFiles Include="$(WindowsSystem32Directory)\**\*.xslt" Exclude="@(MyExcludeFiles)"/>
<MyIncludeFiles Include="$(WindowsSystem32Directory)\**\*.png" Exclude="@(MyExcludeFiles)"/>
<MyIncludeFiles Include="$(WindowsSystem32Directory)\**\*.jpg" Exclude="@(MyExcludeFiles)"/>
</ItemGroup>
<Copy
SourceFiles="@(MyIncludeFiles)"
DestinationFiles="@(MyIncludeFiles->'$(ArtifactDestinationFolder)\%(RecursiveDir)%(Filename)%(Extension)')"
/>
</Target>
</Project>
我可以在 powershell 中做同样的事情吗?
我试过下面的方法,但它创建了一个名为 "C:\work9\MsBuildExamples\FileCopyRecursive\PowershellResults" 的文件
(它是一个没有扩展名的文件,不是目录)
$sourceDirectory = 'c:\windows\System32\*'
$destinationDirectory = 'C:\work9\MsBuildExamples\FileCopyRecursive\PowershellResults'
$excludeFiles = @('EventViewer_EventDetails.xsl')
$includeFiles = @('*.xsl','*.xslt','*.png','*.jpg')
Copy-Item $sourceDirectory $destinationDirectory -Recurse -Include $includeFiles -Exclude $excludeFiles
# -Container:$false
追加:
我试过这个:
$sourceDirectory = 'c:\windows\System32'
$destinationDirectory = 'C:\work9\MsBuildExamples\FileCopyRecursive\PowershellResults'
$excludeFiles = @('EventViewer_EventDetails.xsl')
$includeFiles = @('*.xsl','*.xslt','*.png','*.jpg')
Copy-Item $sourceDirectory $destinationDirectory -Recurse -Include $includeFiles -Exclude $excludeFiles
(没有结果,甚至没有没有扩展名的文件)
我试过这个:
$sourceDirectory = 'c:\windows\System32'
$destinationDirectory = 'C:\work9\MsBuildExamples\FileCopyRecursive\PowershellResults'
$excludeFiles = @('EventViewer_EventDetails.xsl')
$includeFiles = @('*.xsl','*.xslt','*.png','*.jpg')
Copy-Item $sourceDirectory $destinationDirectory -Recurse -Include $includeFiles -Exclude $excludeFiles -Container:$false
(没有结果,甚至没有没有扩展名的文件)
Copy-Item -Recurse
,从 Windows PowerShell v5.1 / PowerShell Core 6.2.0 开始,有其 怪癖和限制;这是我发现的:
如果您有更多信息或更正,请告诉我们。
有两种基本的调用方式Copy-Item -Recurse
:
(a) 指定 目录路径 作为源 - c:\windows\system32
(b) 使用 通配符表达式 作为解析为多个项目的来源 在 源目录中 - c:\windows\system32\*
有两个基本问题:
复制行为根据目标目录是否已经存在[=156]而变化 =] - 见下文。
-Include
参数无法正常工作,-Exclude
也无法正常工作,但 -Exclude
更可能出现问题=16=];参见 GitHub issue #8459。
如果您需要使用 -Include
,请不要使用下面的解决方案 - 如果您确实需要 -Include
,请使用 。
案例 (a) - 单个目录路径作为源
如果源是单个目录(或者是通配符模式解析到的项目中的唯一目录),Copy-Item
也隐含地将 目标 解释为一个目录。
但是,如果目标目录已经存在,复制的项目将被放置在一个子目录中source 目录,在你的例子中意味着:C:\work9\MsBuildExamples\FileCopyRecursive\PowershellResults\System32
GitHub issue #2934 - 理所当然地 - 抱怨这种违反直觉的行为
有两个基本的解决方法:
如果可以接受,首先删除目标目录,如果它存在——这显然是要小心(删除-WhatIf
一旦您确信该命令按预期工作):
# Remove a pre-existing destination directory:
if (Test-Path $destinationDirectory) {
Remove-Item $destinationDirectory -Recurse -WhatIf
}
# Now Copy-Item -Recurse works as intended.
# As stated, -Exclude works as intended, but -Include does NOT.
Copy-Item $sourceDirectory $destinationDirectory -Recurse
警告:Remove-Item -Recurse
,遗憾的是,它可能会间歇性地异步操作,甚至可能会失败 - 如需更可靠的替代方案,请参阅 this answer。
如果你想保留一个预先存在的目标目录。 - 例如,如果你想要添加到目标目录的内容,
- 创建目标目录。一经请求;也就是说,只有当它不存在时才创建它。
- 使用
Copy-Item
将源目录的内容复制到目标目录
# Ensure that the target dir. exists.
# -Force is a quiet no-op, if it already exists.
$null = New-Item -Force -ItemType Directory -LiteralPath $destinationDirectory
# Copy the *contents* of the source directory to the target, using
# a wildcard.
# -Force ensures that *hidden items*, if any, are included too.
# As stated, -Exclude works as intended, but -Include does NOT.
Copy-Item -Force $sourceDirectory/* $destinationDirectory -Recurse
案例 (b) - 作为源的通配符表达式
注:
如果已解决的项目中恰好有1个目录,规则与相同案例(a)适用。
否则,行为仅在 目标项目尚不存在时才会出现问题。 - 见下文.
因此,解决方法是确保事先目标目录存在:
New-Item -Force -Path $destinationDirectory -ItemType Directory
如果目标项(-Destination
参数)尚不存在:
若解析项多个目录,Copy-Item
复制first 目录,然后在第二个 fails 上出现以下错误消息:
Container cannot be copied onto existing leaf item
如果源是单个文件或仅解析为 个文件,Copy-Item
隐式 将不存在的目的地解释为文件.
对于多个文件,这意味着创建一个单个目标文件,其内容是刚好被复制的文件的内容last - 即 数据丢失.
使用我从这里找到的建议(“触摸”方法)
Should Copy-Item create the destination directory structure?
我得到了下面的工作。
我会将这个问题保留为“未回答”几天,也许有人有更好的答案。 New-Item 对我来说似乎很奇怪,因为 Copy-Item 不应该出现在包含和排除列表恕我直言中。
(根据原始问题使用变量设置器)
IF (Test-Path -Path $destinationDirectory -PathType Container)
{
Write-Host "$destinationDirectory already exists" -ForegroundColor Red
}
ELSE
{
New-Item -Path $destinationDirectory -ItemType directory
}
# the two scripts below are same except for "New-Item" vs "Copy-Item" , thus the "New-Item" trick-it-to-work voodoo
Get-ChildItem $sourceDirectory -Include $includeFiles -Exclude $excludeFiles -Recurse -Force |
New-Item -ItemType File -Path {
Join-Path $destinationDirectory $_.FullName.Substring($sourceDirectory.Length)
} -Force
Get-ChildItem $sourceDirectory -Include $includeFiles -Exclude $excludeFiles -Recurse -Force |
Copy-Item -Destination {
Join-Path $destinationDirectory $_.FullName.Substring($sourceDirectory.Length)
}
为了保持概览,我缩短了您的变量(并更改了 dst 以适合我的环境)
$src = 'c:\windows\System32'
$dst = 'Q:\Test17-03\PowershellResults'
$exc = @('EventViewer_EventDetails.xsl')
$inc = @('*.xsl','*.xslt','*.png','*.jpg')
Get-ChildItem -Path $src -Rec -Inc $inc -Exc $exc -ea silentlycontinue|ForEach{
$DestPath = $_.DirectoryName -replace [regex]::escape($src),$dst
If (!(Test-Path $DestPath)){MkDir $DestPath}
$_|Copy-Item -Destination $DestPath -Force
}
编辑和你的例子基本一样,可能更密集一些
我正在将一些 msbuild 脚本翻译成 powershell。
在 msbuild 中,我可以生成黑名单 and/or 白名单文件,其中包含我要(递归)复制到目标文件夹的文件。
如下图:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="AllTargetsWrapped">
<PropertyGroup>
<!-- Always declare some kind of "base directory" and then work off of that in the majority of cases -->
<WorkingCheckout>.</WorkingCheckout>
<WindowsSystem32Directory>c:\windows\System32</WindowsSystem32Directory>
<ArtifactDestinationFolder>$(WorkingCheckout)\ZZZArtifacts</ArtifactDestinationFolder>
</PropertyGroup>
<Target Name="AllTargetsWrapped">
<CallTarget Targets="CleanArtifactFolder" />
<CallTarget Targets="CopyFilesToArtifactFolder" />
</Target>
<Target Name="CleanArtifactFolder">
<RemoveDir Directories="$(ArtifactDestinationFolder)" Condition="Exists($(ArtifactDestinationFolder))"/>
<MakeDir Directories="$(ArtifactDestinationFolder)" Condition="!Exists($(ArtifactDestinationFolder))"/>
<Message Text="Cleaning done" />
</Target>
<Target Name="CopyFilesToArtifactFolder">
<ItemGroup>
<MyExcludeFiles Include="$(WindowsSystem32Directory)\**\EventViewer_EventDetails.xsl" />
</ItemGroup>
<ItemGroup>
<MyIncludeFiles Include="$(WindowsSystem32Directory)\**\*.xsl" Exclude="@(MyExcludeFiles)"/>
<MyIncludeFiles Include="$(WindowsSystem32Directory)\**\*.xslt" Exclude="@(MyExcludeFiles)"/>
<MyIncludeFiles Include="$(WindowsSystem32Directory)\**\*.png" Exclude="@(MyExcludeFiles)"/>
<MyIncludeFiles Include="$(WindowsSystem32Directory)\**\*.jpg" Exclude="@(MyExcludeFiles)"/>
</ItemGroup>
<Copy
SourceFiles="@(MyIncludeFiles)"
DestinationFiles="@(MyIncludeFiles->'$(ArtifactDestinationFolder)\%(RecursiveDir)%(Filename)%(Extension)')"
/>
</Target>
</Project>
我可以在 powershell 中做同样的事情吗?
我试过下面的方法,但它创建了一个名为 "C:\work9\MsBuildExamples\FileCopyRecursive\PowershellResults" 的文件 (它是一个没有扩展名的文件,不是目录)
$sourceDirectory = 'c:\windows\System32\*'
$destinationDirectory = 'C:\work9\MsBuildExamples\FileCopyRecursive\PowershellResults'
$excludeFiles = @('EventViewer_EventDetails.xsl')
$includeFiles = @('*.xsl','*.xslt','*.png','*.jpg')
Copy-Item $sourceDirectory $destinationDirectory -Recurse -Include $includeFiles -Exclude $excludeFiles
# -Container:$false
追加:
我试过这个:
$sourceDirectory = 'c:\windows\System32'
$destinationDirectory = 'C:\work9\MsBuildExamples\FileCopyRecursive\PowershellResults'
$excludeFiles = @('EventViewer_EventDetails.xsl')
$includeFiles = @('*.xsl','*.xslt','*.png','*.jpg')
Copy-Item $sourceDirectory $destinationDirectory -Recurse -Include $includeFiles -Exclude $excludeFiles
(没有结果,甚至没有没有扩展名的文件)
我试过这个:
$sourceDirectory = 'c:\windows\System32'
$destinationDirectory = 'C:\work9\MsBuildExamples\FileCopyRecursive\PowershellResults'
$excludeFiles = @('EventViewer_EventDetails.xsl')
$includeFiles = @('*.xsl','*.xslt','*.png','*.jpg')
Copy-Item $sourceDirectory $destinationDirectory -Recurse -Include $includeFiles -Exclude $excludeFiles -Container:$false
(没有结果,甚至没有没有扩展名的文件)
Copy-Item -Recurse
,从 Windows PowerShell v5.1 / PowerShell Core 6.2.0 开始,有其 怪癖和限制;这是我发现的:
如果您有更多信息或更正,请告诉我们。
有两种基本的调用方式Copy-Item -Recurse
:
(a) 指定 目录路径 作为源 -
c:\windows\system32
(b) 使用 通配符表达式 作为解析为多个项目的来源 在 源目录中 -
c:\windows\system32\*
有两个基本问题:
复制行为根据目标目录是否已经存在[=156]而变化 =] - 见下文。
-Include
参数无法正常工作,-Exclude
也无法正常工作,但-Exclude
更可能出现问题=16=];参见 GitHub issue #8459。
如果您需要使用 -Include
,请不要使用下面的解决方案 - 如果您确实需要 -Include
,请使用
案例 (a) - 单个目录路径作为源
如果源是单个目录(或者是通配符模式解析到的项目中的唯一目录),Copy-Item
也隐含地将 目标 解释为一个目录。
但是,如果目标目录已经存在,复制的项目将被放置在一个子目录中source 目录,在你的例子中意味着:C:\work9\MsBuildExamples\FileCopyRecursive\PowershellResults\System32
GitHub issue #2934 - 理所当然地 - 抱怨这种违反直觉的行为
有两个基本的解决方法:
如果可以接受,首先删除目标目录,如果它存在——这显然是要小心(删除-WhatIf
一旦您确信该命令按预期工作):
# Remove a pre-existing destination directory:
if (Test-Path $destinationDirectory) {
Remove-Item $destinationDirectory -Recurse -WhatIf
}
# Now Copy-Item -Recurse works as intended.
# As stated, -Exclude works as intended, but -Include does NOT.
Copy-Item $sourceDirectory $destinationDirectory -Recurse
警告:Remove-Item -Recurse
,遗憾的是,它可能会间歇性地异步操作,甚至可能会失败 - 如需更可靠的替代方案,请参阅 this answer。
如果你想保留一个预先存在的目标目录。 - 例如,如果你想要添加到目标目录的内容,
- 创建目标目录。一经请求;也就是说,只有当它不存在时才创建它。
- 使用
Copy-Item
将源目录的内容复制到目标目录
# Ensure that the target dir. exists.
# -Force is a quiet no-op, if it already exists.
$null = New-Item -Force -ItemType Directory -LiteralPath $destinationDirectory
# Copy the *contents* of the source directory to the target, using
# a wildcard.
# -Force ensures that *hidden items*, if any, are included too.
# As stated, -Exclude works as intended, but -Include does NOT.
Copy-Item -Force $sourceDirectory/* $destinationDirectory -Recurse
案例 (b) - 作为源的通配符表达式
注:
如果已解决的项目中恰好有1个目录,规则与相同案例(a)适用。
否则,行为仅在 目标项目尚不存在时才会出现问题。 - 见下文.
因此,解决方法是确保事先目标目录存在:
New-Item -Force -Path $destinationDirectory -ItemType Directory
如果目标项(-Destination
参数)尚不存在:
若解析项多个目录,
Copy-Item
复制first 目录,然后在第二个 fails 上出现以下错误消息:
Container cannot be copied onto existing leaf item
如果源是单个文件或仅解析为 个文件,
Copy-Item
隐式 将不存在的目的地解释为文件.
对于多个文件,这意味着创建一个单个目标文件,其内容是刚好被复制的文件的内容last - 即 数据丢失.
使用我从这里找到的建议(“触摸”方法)
Should Copy-Item create the destination directory structure?
我得到了下面的工作。
我会将这个问题保留为“未回答”几天,也许有人有更好的答案。 New-Item 对我来说似乎很奇怪,因为 Copy-Item 不应该出现在包含和排除列表恕我直言中。
(根据原始问题使用变量设置器)
IF (Test-Path -Path $destinationDirectory -PathType Container)
{
Write-Host "$destinationDirectory already exists" -ForegroundColor Red
}
ELSE
{
New-Item -Path $destinationDirectory -ItemType directory
}
# the two scripts below are same except for "New-Item" vs "Copy-Item" , thus the "New-Item" trick-it-to-work voodoo
Get-ChildItem $sourceDirectory -Include $includeFiles -Exclude $excludeFiles -Recurse -Force |
New-Item -ItemType File -Path {
Join-Path $destinationDirectory $_.FullName.Substring($sourceDirectory.Length)
} -Force
Get-ChildItem $sourceDirectory -Include $includeFiles -Exclude $excludeFiles -Recurse -Force |
Copy-Item -Destination {
Join-Path $destinationDirectory $_.FullName.Substring($sourceDirectory.Length)
}
为了保持概览,我缩短了您的变量(并更改了 dst 以适合我的环境)
$src = 'c:\windows\System32'
$dst = 'Q:\Test17-03\PowershellResults'
$exc = @('EventViewer_EventDetails.xsl')
$inc = @('*.xsl','*.xslt','*.png','*.jpg')
Get-ChildItem -Path $src -Rec -Inc $inc -Exc $exc -ea silentlycontinue|ForEach{
$DestPath = $_.DirectoryName -replace [regex]::escape($src),$dst
If (!(Test-Path $DestPath)){MkDir $DestPath}
$_|Copy-Item -Destination $DestPath -Force
}
编辑和你的例子基本一样,可能更密集一些