将其解决的方法如下:我知道有很多不同的方法可以解决这个问题,比如Get-ChildItem
。
因此,我定义了一个自定义类,以消除一些 PowerShell cmdlet 的开销以及一些 .Net 类。该函数在本地运行良好,但当我尝试将其用作针对Invoke-Command
远程计算机的脚本块定义时,它就会挂起;即使我在自己的计算机上调用它。有一个流程是为WinRM 插件任务管理器中显示的就是这样。以下是一些工作示例:
PS C:\Users\Abraham> Get-FolderSize -Path C:\Users\Abraham
143.98GB
PS C:\Users\Abraham> Invoke-Command -ScriptBlock ${Function:Get-FolderSize} -ArgumentList C:\Users\Abraham
143.98GB
如上所示,这将正常工作并返回所有文件的总和。然后,当我将计算机名称传递Invoke-Command
给远程执行时 - 它只是挂起:
Invoke-Command -ScriptBlock ${Function:Get-FolderSize} -ArgumentList C:\Users\Abraham -ComputerName $env:COMPUTERNAME
不用说, aPSSession
也不起作用;这将是运行该函数的主要方法 -将其传递给开放PSSession
。
我的问题是,到底出了什么问题?哈哈。幕后发生了什么事情,导致无法使用P/调用远程?
实际的功能如下:
Function Get-FolderSize ([parameter(Mandatory)][string]$Path) {
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
namespace ProfileMethods
{
public class DirectorySum
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct WIN32_FIND_DATA
{
public uint dwFileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
public uint nFileSizeHigh;
public uint nFileSizeLow;
public uint dwReserved0;
public uint dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string cAlternateFileName;
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);
[DllImport("kernel32.dll")]
private static extern bool FindClose(IntPtr hFindFile);
public long GetFolderSize(string path)
{
long size = 0;
List<string> dirList = new List<string>();
WIN32_FIND_DATA fileData;
IntPtr hFile = FindFirstFile(path + @"\*.*", out fileData);
if (hFile != IntPtr.Zero)
{
do
{
if (fileData.cFileName == "." || fileData.cFileName == "..")
{
continue;
}
string fullPath = path + @"\" + fileData.cFileName;
if ((fileData.dwFileAttributes & 0x10) == 0x10)
{
dirList.Add(fullPath);
}
else
{
size += ((long)fileData.nFileSizeHigh * (long)uint.MaxValue + (long)fileData.nFileSizeLow);
}
} while (FindNextFile(hFile, out fileData));
FindClose(hFile);
foreach (string dir in dirList)
{
size += GetFolderSize(dir);
}
}
return size;
}
}
}
"@
$program = [ProfileMethods.DirectorySum]::new()
switch ($program.GetFolderSize($Path))
{
{$_ -lt 1GB} { '{0}MB' -f [math]::Round($_/1MB,2); Continue }
{$_ -gt 1GB -and $_ -lt 1TB} { '{0}GB' -f [math]::Round($_/1GB,2); Continue }
{$_ -gt 1TB} { '{0}TB' -f [math]::Round($_/1TB,2); Continue }
}
}
编辑:更新- 因此,它适用于子文件夹,但不适用于根文件夹。示例:
$path = 'C:\Users\Abraham\Desktop' #works
Invoke-Command -ScriptBlock ${Function:Get-FolderSize} -ArgumentList $path -ComputerName $env:COMPUTERNAME
...可以工作,但是根文件夹C:\Users\Abraham
不工作。
笔记:将 UNC 路径传递给函数/方法将会起作用。
答案1
这是一个猜测,因为您还没有尝试调查任何事情,因此没有足够的信息来提供准确的答案。
但根文件夹 C:\Users\Abraham 没有。
if ((fileData.dwFileAttributes & 0x10) == 0x10)
现代 Windows 系统可能需要在此处进行额外检查 - 您需要注意重新解析点,它有各种类型(符号链接、连接点、挂载点、OneDrive 占位符……)并且除了“ReparsePoint”标志(即 0x400)之外还可能具有“Directory”标志。
特别是,由于 Vista 将旧的“~\Local Settings”目录移动到了~\AppData\Local,为了兼容,它在“~\AppData\Local\Application Data”处放置了一个目录连接点,该目录连接点指向...同一个“~\AppData\Local”。
连接点比符号链接稍微特殊一些,可以拥有自己的 ACL,因此通常此连接点会有一个“拒绝 ACE”来阻止您执行任何操作FindFirstFile(@"Application Data\*.*")
(即它只允许直接访问已知路径)。但是,如果连接点上的 ACL 曾经被重置,则任何试图枚举 AppData 内容的程序最终都会陷入无限循环,永远陷入同一个连接点(直到超出路径长度限制)。
FindFirstFile(path + @"\*.*", out fileData);
请记住,文件名中不一定要有.
。 FindFirstFile() 特意这样做(通过允许.*
匹配空字符串)以帮助仍停留在“8.3 filename.ext”时代的程序,但对于实现自己的通配符扩展的程序来说情况并非如此。