通过 P/Invoke 定义的类是否不允许远程执行?

通过 P/Invoke 定义的类是否不允许远程执行?

将其解决的方法如下:我知道有很多不同的方法可以解决这个问题,比如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”时代的程序,但对于实现自己的通配符扩展的程序来说情况并非如此。

相关内容