我有一台运行 Windows 2012 R2 的应用程序服务器,它会生成大量日志文件,以至于它会定期耗尽应用程序卷的可用空间。由于应用程序本身的限制,我无法移动或重命名日志文件或启用 NTFS 数据重复数据删除,而且由于这已经不是十年前的事情了,我不想使用批处理或 vbscript 来帮我做这些事情。
日志文件都位于应用程序安装目录的各个子文件夹中,具有不同的扩展名(一个组件将日期添加为日志文件扩展名),并且应用程序安装目录中有一个空格,因为应用程序开发人员是恶意的。写入日志的子文件夹至少专门用于写入日志。这也是一个严重依赖 CPU 的应用程序,所以我不想压缩日志文件夹本身,并承担与为日志写入压缩文件相关的 CPU 惩罚。
如何使用 PowerShell 对超过 x 天的日志文件启用 NTFS 压缩?
答案1
由于 PowerShell 对文件操作的支持仍然相当缺乏,最简单的解决方案是创建一个 PowerShell 脚本来调用该compact.exe
实用程序并将其设置为计划任务。由于路径名中有空格,您需要compact.exe
直接调用,而不是使用Invoke-WMIMethod
和CIM_DataFile
类(这将导致大量额外的工作来处理路径中的空格)。
假设 X 的年龄为 3 天,您的 PowerShell 脚本将如下所示:
$logfolder="[location of the first logging subfolder]"
$age=(get-date).AddDays(-3)
Get-ChildItem $logfolder | where-object {$_.LastWriteTime -le $age -AND $_.Attributes -notlike "*Compressed*"} |
ForEach-Object {
compact /C $_.FullName
}
$logfolder="[location of the next logging subfolder]"
Get-ChildItem $logfolder | where-object {$_.LastWriteTime -le $age -AND $_.Attributes -notlike "*Compressed*"} |
ForEach-Object {
compact /C $_.FullName
}
...
第二个条件是通过跳过已压缩的文件(在第一次运行此脚本后会出现)来加快脚本执行速度。如果您愿意,或者有很多不同的日志子文件夹,那么用重复的 PowerShell 代码创建一个函数可能很有意义,这将是一个相当简单的练习。
答案2
可以使用数组和 foreach 循环来避免重复的代码:
$logfolders=("D:\Folder\One","D:\Folder\Two")
$age=(get-date).AddDays(-3)
foreach ($logfolder in $logfolders) {
Get-ChildItem $logfolder | where-object {$_.LastWriteTime -le $age -AND $_.Attributes -notlike "*Compressed*"} |
ForEach-Object {
compact /C $_.FullName
}
}
.....
答案3
无需依赖 compact.exe,即可通过直接调用 NTFS 压缩来执行此操作,这是一种“纯 powershell”方法。这还可以处理文件名中的空格以及来自日本的 unicode 文件名,后者很难提供给 compact.exe 命令行。请参阅https://docs.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_set_compression也一样。
$MethodDefinition= @'
public static class FileTools
{
private const int FSCTL_SET_COMPRESSION = 0x9C040;
private const short COMPRESSION_FORMAT_DEFAULT = 1;
private const short COMPRESSION_FORMAT_DISABLE = 0;
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern int DeviceIoControl(
IntPtr hDevice,
int dwIoControlCode,
ref short lpInBuffer,
int nInBufferSize,
IntPtr lpOutBuffer,
int nOutBufferSize,
ref int lpBytesReturned,
IntPtr lpOverlapped);
public static bool Compact(IntPtr handle)
{
int lpBytesReturned = 0;
short lpInBuffer = COMPRESSION_FORMAT_DEFAULT;
return DeviceIoControl(handle, FSCTL_SET_COMPRESSION,
ref lpInBuffer, sizeof(short), IntPtr.Zero, 0,
ref lpBytesReturned, IntPtr.Zero) != 0;
}
public static bool Uncompact(IntPtr handle)
{
int lpBytesReturned = 0;
short lpInBuffer = COMPRESSION_FORMAT_DISABLE;
return DeviceIoControl(handle, FSCTL_SET_COMPRESSION,
ref lpInBuffer, sizeof(short), IntPtr.Zero, 0,
ref lpBytesReturned, IntPtr.Zero) != 0;
}
}
'@
$Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name ‘Kernel32’ -Namespace ‘Win32’ -PassThru
$logfilespec = "c:\Logfolder\*.log"
# compact anything older than three days
foreach ($File in (Get-ChildItem -Path $logfilespec -Recurse -File).Where({$_.LastWriteTime -lt (Get-Date).AddDays(-3) -and $_.Attributes -notmatch [System.IO.FileAttributes]::Compressed})) {
$FileObject = [System.IO.File]::Open($File.FullName,'Open','ReadWrite','None')
$Method = [Win32.Kernel32+FileTools]::Compact($FileObject.Handle)
$FileObject.Close()
}
# decompact
foreach ($File in (Get-ChildItem -Path $logfilespec -Recurse -File).Where({$_.Attributes -match [System.IO.FileAttributes]::Compressed})) {
$FileObject = [System.IO.File]::Open($File.FullName,'Open','ReadWrite','None')
$Method = [Win32.Kernel32+FileTools]::Uncompact($FileObject.Handle)
$FileObject.Close()
}
答案4
如果这些日志文件不在 C: 上,请使用 Server 2012 R2 重复数据删除功能。然后,您可以将其配置为仅删除三天前的重复 .log 文件(默认)。第二种方法是控制这种情况,或者当它位于 C: 上时:将日志目录移动到其他驱动器并使用 JUNCTION 指向新位置,最简单的方法是使用 Hardlink-Shell-Extension 从https://schinagl.priv.at/nt/hardlinkshellext/linkshellextension.html- 然后在上面使用 2012 R2 重复数据删除。我发现日志文件和 SQl-dump-for-backup 驱动器的重复数据删除率远高于 90%。