使用 PowerShell 查找修改的文件
这是我似乎经常使用 PowerShell 处理的另一项任务。我昨天处理了哪些文件?或者过去 48 小时内修改了哪些文件?显然,Get-ChildItem 将成为主要命令。从给定文件夹路径获取基于扩展名的文件非常简单,甚至可以递归获取。但要按年龄进行筛选,我必须使用Where-Object 并将LastWriteTime 属性(在本例中)与DateTime 值进行比较。顺便说一句,我所在的 PowerShell 工作组专注于 cmdlet,并且有人请求将日期时间过滤添加到 Get-ChildItem。不幸的是,.NET Framework 不提供创建或修改日期的早期过滤机制。任何过滤都必须在 Get-ChildItem 返回所有文件后完成,这与使用Where-Object 没有什么不同。最终结果将是在 Get-ChildItem 上进行大量工作而没有性能提升,因此不会实现此功能。但没关系。我有一个功能!
获取最后修改的文件
此函数应该适用于 Windows PowerShell 5.1 和 PowerShell 7.x。这是完整的文件。
#requires -version 5.1
Function Get-LastModifiedFile {
[cmdletbinding()]
[alias("glm")]
[OutputType("System.IO.FileInfo")]
Param(
[Parameter(Position = 0, HelpMessage = "Specify a file filter like *.ps1.")]
[ValidateNotNullOrEmpty()]
[string]$Filter = "*",
[Parameter(Position = 1, HelpMessage = "Specify the folder to search.")]
[ValidateScript({
#this will write a custom error message if validation fails
If ((Test-Path -path $_ -PathType Container) -AND ((Get-Item -path $_).psprovider.name -eq 'Filesystem')) {
return $True
}
else {
Throw "The specified Path value $_ is not a valid folder or not a file system location."
return $False
}
})]
[string]$Path = ".",
[Parameter(HelpMessage = "Specify the search interval based on the last write time.")]
[ValidateSet("Hours", "Minutes", "Days", "Months", "Years")]
[string]$Interval = "Hours",
[Parameter(HelpMessage = "Specify the number of intervals.")]
[alias("ic")]
[ValidateScript({$_ -ge 1})]
[int32]$IntervalCount = 24,
[Parameter(HelpMessage = "Recurse from the specified path.")]
[switch]$Recurse
)
Write-Verbose "Starting $($myinvocation.mycommand)"
$msg ="Searching {0} for {1} files modified in the last {2} {3}." -f (Convert-Path $Path),$filter,$IntervalCount,$Interval
Write-Verbose $msg
switch ($Interval) {
"minutes" { $last = (Get-Date).AddMinutes(-$IntervalCount) }
"hours" { $last = (Get-Date).AddHours(-$IntervalCount) }
"days" { $last = (Get-Date).AddDays(-$IntervalCount) }
"months" { $last = (Get-Date).AddMonths(-$IntervalCount) }
"years" { $last = (Get-Date).AddYears(-$IntervalCount) }
}
Write-Verbose "Cutoff date is $Last"
#remove bound parameters that don't belong to Get-ChildItem
"IntervalCount", "Interval" | ForEach-Object {
if ($PSBoundParameters.ContainsKey($_)) {
[void]$PSBoundParameters.Remove($_)
}
}
#add -File to PSBoundParameters
$PSBoundParameters.Add("file", $True)
if ($recurse) {
Write-Verbose "Recursing..."
}
else {
Write-Verbose "Searching..."
}
#get the files and filter on the LastWriteTime using the Where() method for
#better performance
(Get-ChildItem @PSBoundParameters).Where({$_.LastWriteTime -ge $last})
Write-Verbose "Ending $($myinvocation.mycommand)"
}
我在整个代码中添加了注释和帮助消息,因此您应该能够理解我在做什么。但我想指出几点。
首先,我在 Path 参数上使用 ValidateScript() 属性。
If ((Test-Path -path $_ -PathType Container) -AND ((Get-Item -path $_).psprovider.name -eq 'Filesystem')) {
return $True
}
else {
Throw "The specified Path value $_ is not a valid folder or not a file system location."
return $False
}我正在测试 $Path 值是一个容器并且它是一个 FileSystem 对象。我本来可以简单地使用 IF 条件,但我想在验证失败时更好地控制错误消息。代码应该很清楚。这是它产生的结果。
这是来自 PowerShell 7。Windows PowerShell 还显示自定义错误消息以及更多异常信息。
绘制 PSBoundParameters
我的函数中的另一个功能是重用 PSBound 参数。因为几乎所有参数都与 Get-ChildItem 相同,所以我可以 splat $PSBoundParameters。但是,我需要删除无效参数 IntervalCount 和 Interval。
"IntervalCount", "Interval" | ForEach-Object {
if ($PSBoundParameters.ContainsKey($_)) {
[void]$PSBoundParameters.Remove($_)
}
}我还想添加 Get-ChildItem File 参数。
$PSBoundParameters.Add("file", $True)从技术上讲,PowerShell 允许您进行操作并指定参数,但我想保持代码干净。除非我指定不同的值,否则具有默认值(例如 Filter 和 Path)的参数不会被检测为 PSBoundParameters。但这没关系,因为默认值与 Get-ChildItem 相同。
切换间隔
我的函数使用 ValidateSet() 属性作为间隔,并且我使用默认值“小时”。设置默认值时,请确保它属于您的验证集。这确保了我有一个可以在 Switch 语句中使用的已知值。
switch ($Interval) {
"minutes" { $last = (Get-Date).AddMinutes(-$IntervalCount) }
"hours" { $last = (Get-Date).AddHours(-$IntervalCount) }
"days" { $last = (Get-Date).AddDays(-$IntervalCount) }
"months" { $last = (Get-Date).AddMonths(-$IntervalCount) }
"years" { $last = (Get-Date).AddYears(-$IntervalCount) }
}因为我有多个可能的值,所以这是比使用更复杂的 If/ElseIf/ElseIf/Else 语句更干净的方法。 Switch 语句生成一个 DateTime 值作为我的截止日期。我还应该指出, $IntervalCount 之前的破折号并不表明它是一个参数。它是减号。我从 $IntervalCount 添加一个负值。
使用Where()方法
我在最终过滤中使用 $Last 值。
(Get-ChildItem @PSBoundParameters).Where({$_.LastWriteTime -ge $last})这段代码对您来说可能有点不同。我使用的是Where() 方法,而不是Where-Object cmdlet。我本来可以用这个:
Get-ChildItem @PSBoundParameters | Where-Object {$_.LastWriteTime -ge $last}并得到相同的结果。 Where() 是集合或数组的内置方法。这就是 Get-ChildItem 表达式用括号括起来的原因。 Where() 方法中的语法与Where-Object 相同。区别在于性能。通常,该方法比使用 cmdlet 更快。在我的测试中,我们谈论的是毫秒差异,但越快越好,所以我会同意它。
函数输出是经过过滤的 Get-ChildItem 对象到管道,因此我可以以任何需要的方式使用它。
这些是我在过去 6 个月中处理过的文件类型。这 153 个没有扩展名的文件来自一个未隐藏的 .git 文件夹,我必须修复它。
概括
这就是我喜欢 PowerShell 的原因。经过一点努力,我创建了一个命令行工具,我可以在控制台上快速运行以找到我需要的东西。我什至定义了一个别名 glm,以使其在命令提示符下使用起来更加容易。我希望您能学到一两个可以在您自己的 PowerShell 脚本中使用的技巧。要了解有关参数验证选项的更多信息,请阅读 About_Functions_Advanced_Parameters 帮助主题。



