2

我正在尝试找到一种从 C# 中的 IP 地址检索计算机名称的方法,但是所有在线标记为检索计算机名称或计算机名称的答案实际上都获得了主机名,而不是计算机名称。如果您转到“控制面板”>“系统”,则该菜单“计算机名”中有一个属性...我正在远程计算机上查找此值。AFAIK,如果没有 DNS 映射,主机名将 =完整的计算机名称。问题是我正在处理的这些服务器确实具有 DNS 映射,因此主机名返回它们的 DNS 地址。

如果我说错了,请随时纠正我的技术细节,但问题仍然存在。

我试过这个:

IPHostEntry hostEntry = Dns.GetHostEntry(_ip);

_hostname = hostEntry.HostName;

但显然返回主机名,而不是计算机名。我还可以满足返回的“完整计算机名称”属性,然后简单地去掉字符串中不需要的部分以显示“计算机名称”。

此外,如果您知道如何使用 PowerShell 执行此操作,我也可以使用您的帮助。无论如何,我在我的应用程序中托管 PowerShell 引擎......所以可以简单地将您的命令传递到PowerShellInstance.AddScript(_yourCommandHere);并将其返回到我的应用程序中。

请告知是否可以这样做。

@DanielAWhite 编辑:这是列出答案的副本吗?那篇文章中的答案正是我发布的这个问题的问题。不,这不是重复的,因为我不是在寻找主机名。我在我的 OP 中特别告诉过你,我不是在寻找那个,他们也没有问我在问什么。如果没有办法从 .NET 中的 IP 获取计算机名称,那么只需用它来回答问题。

从“重复”:

好吧,并不是每个 IP 地址都有名称。但是,给定 IPAddress 您可以使用 >Dns.GetHostEntry 尝试解决它。另请注意,如果它是 NAT > 路由器,您将获得路由器的 IP 地址,而不是它们的实际 > 机器。

看看我的 OP... .GetHostEntry 不起作用。这就是我花时间打字的全部原因。

谢谢

双重编辑:培根有一个如何做到这一点的答案;这篇文章被锁定了,因为有人没有花时间真正阅读我写的内容。由于它被锁定,您也无法给出更好的答案。但我是这样做的,将其保存在这里以供将来参考:

        //declare a string to be our machinename
        string machineName;
        //declare a string which we will pass into powershell later as script
        //assigns the hostname or IP
        string getComputer = "$ip = " + "\"" + ip + "\"" + "\r\n";
        //add to the string this, which gets the Win32_ComputerSystem.. @BACON knew what I was after
        //we pipe that back using |select -expand Name
        getComputer += "get-wmiobject -class Win32_ComputerSystem -property Name -ComputerName " + "$ip " +
            "|select -expand Name";
        //create a powershell instance using
        using (PowerShell PowerShellInstance = PowerShell.Create())
        {
            //add the script into our instance of ps
            PowerShellInstance.AddScript(getComputer);
            //instantiate a collection to house our output from PS
            //you could also probably just instantiate a PSObject instead of a collection.. but this might be useful if modified to get an array of computer names... and this is how I did it so can't verify
            Collection<PSObject> psOutput;
            //assign psOutput from .Invoke() method
            psOutput = PowerShellInstance.Invoke();

            //you could trim this loop and get rid of it for only one IP
            foreach (var item in psOutput)
            {
               //machineName = MachineName||ComputerName string NOT hostname
                machineName = item.BaseObject.ToString();
            }
        }

哦,根据评论中的培根,您必须允许 WMI 通过 Windows 防火墙才能正常工作。它对我来说非常有效。

4

1 回答 1

4

将我的评论重构为答案...

想象一下,我们有一个interface这样的...

namespace SO56585341
{
    public interface IComputerInfoSource
    {
        string GetComputerName();
    }
}

有几种方法可以实现此功能以获取本地计算机的机器名称。最简单的就是返回Environment.MachineName属性的值...

namespace SO56585341
{
    public class EnvironmentClassComputerInfoSource : IComputerInfoSource
    {
        public string GetComputerName()
        {
            return System.Environment.MachineName;
        }
    }
}

您还可以使用该Environment.GetEnvironmentVariable()方法检索%ComputerName%环境变量的值...

namespace SO56585341
{
    public class EnvironmentVariableComputerInfoSource : IComputerInfoSource
    {
        public string GetComputerName()
        {
            return System.Environment.GetEnvironmentVariable("ComputerName");
        }
    }
}

您可以p/invoke GetComputerName()Windows API 函数,这是在幕后Environment.MachineName做的......

using System.Runtime.InteropServices;
using System.Text;

namespace SO56585341
{
    public class WinApiComputerInfoSource : IComputerInfoSource
    {
        private const int MAX_COMPUTERNAME_LENGTH = 15;

        [DllImport("Kernel32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool GetComputerName(
            StringBuilder lpBuffer,
            ref int nSize
        );

        public string GetComputerName()
        {
            int maxCapacity = MAX_COMPUTERNAME_LENGTH + 1;
            StringBuilder nameBuilder = new StringBuilder(maxCapacity, maxCapacity);

            if (!GetComputerName(nameBuilder, ref maxCapacity))
            {
                // TODO: Error handling...
                throw new System.ComponentModel.Win32Exception();
            }

            return nameBuilder.ToString();
        }
    }
}

您可以使用 WMI 来检索单例Name的属性。您可以通过为类实例化一个实例并调用它来检索包含唯一实例的数组来做到这一点......Win32_ComputerSystemManagementClassWin32_ComputerSystemGetInstances()

using System.Linq;
using System.Management;

namespace SO56585341
{
    public class WmiClassComputerInfoSource : IComputerInfoSource
    {
        public string GetComputerName()
        {
            using (ManagementClass computerSystemClass = new ManagementClass("Win32_ComputerSystem"))
            using (ManagementObjectCollection computerSystemCollection = computerSystemClass.GetInstances())
            using (ManagementObject computerSystem = computerSystemCollection.Cast<ManagementObject>().Single())
                return (string) computerSystem["Name"];
        }
    }
}

...或者通过创建一个ManagementObjectSearcher并将其用于Get()单独的Win32_ComputerSystem实例...

using System.Linq;
using System.Management;

namespace SO56585341
{
    public class WmiSearcherComputerInfoSource : IComputerInfoSource
    {
        public string GetComputerName()
        {
            ObjectQuery computerSystemQuery = new SelectQuery("Win32_ComputerSystem");

            using (ManagementObjectSearcher computerSystemSearcher = new ManagementObjectSearcher(computerSystemQuery))
            using (ManagementObjectCollection computerSystemCollection = computerSystemSearcher.Get())
            using (ManagementObject computerSystem = computerSystemCollection.Cast<ManagementObject>().Single())
                return (string) computerSystem["Name"];
        }
    }
}

最后,上述所有方法返回的值似乎最终都存储在注册表中,所以如果你不介意依赖那个实现细节,你可以直接从那里检索它......

using Microsoft.Win32;

namespace SO56585341
{
    public class RegistryComputerInfoSource : IComputerInfoSource
    {
        public string GetComputerName()
        {
            // See also @"SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName\"
            // https://www.oreilly.com/library/view/windows-nt-workstation/9781565926134/10_chapter-07.html
            const string valueParentKeyPath = @"SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName\";

            using (RegistryKey parentKey = Registry.LocalMachine.OpenSubKey(valueParentKeyPath, false))
                return (string) parentKey.GetValue("ComputerName");
        }
    }
}

至于从远程计算机获取相同的值,只有上面的最后三个实现可以工作,尽管需要进行最少的调整。首先,为了完成这个IComputerInfoSource例子,让我们创建一个abstract类来保存远程机器名称/地址“参数”......

namespace SO56585341
{
    public abstract class RemoteComputerInfoSource : IComputerInfoSource
    {
        public string RemoteNameOrIp
        {
            get;
        }

        protected RemoteComputerInfoSource(string nameOrIp)
        {
            RemoteNameOrIp = nameOrIp ?? throw new System.ArgumentNullException(nameof(nameOrIp));  
        }

        public abstract string GetComputerName();
    }
}

通过 a检索Win32_ComputerSystem实例ManagementClass只是显式传递它的问题 aManagementPath还指定NamespacePathand Server...

using System.Linq;
using System.Management;

namespace SO56585341
{
    public class RemoteWmiClassComputerInfoSource : RemoteComputerInfoSource
    {
        public RemoteWmiClassComputerInfoSource(string nameOrIp)
            : base(nameOrIp)
        {
        }

        public override string GetComputerName()
        {
            ManagementPath computerSystemPath = new ManagementPath() {
                ClassName = "Win32_ComputerSystem",
                NamespacePath = @"root\cimv2",
                Server = RemoteNameOrIp
            };

            using (ManagementClass computerSystemClass = new ManagementClass(computerSystemPath))
            using (ManagementObjectCollection computerSystemCollection = computerSystemClass.GetInstances())
            using (ManagementObject computerSystem = computerSystemCollection.Cast<ManagementObject>().Single())
                return (string) computerSystem["Name"];
        }
    }
}

AManagementObjectSearcher可以通过传递一个类似的ManagementPath包裹在ManagementScope...

using System.Linq;
using System.Management;

namespace SO56585341
{
    public class RemoteWmiSearcherComputerInfoSource : RemoteComputerInfoSource
    {
        public RemoteWmiSearcherComputerInfoSource(string nameOrIp)
            : base(nameOrIp)
        {
        }

        public override string GetComputerName()
        {
            ManagementScope computerSystemScope = new ManagementScope(
                new ManagementPath() {
                    NamespacePath = @"root\cimv2",
                    Server = RemoteNameOrIp
                }
            );
            ObjectQuery computerSystemQuery = new SelectQuery("Win32_ComputerSystem");

            using (ManagementObjectSearcher computerSystemSearcher = new ManagementObjectSearcher(computerSystemScope, computerSystemQuery))
            using (ManagementObjectCollection computerSystemCollection = computerSystemSearcher.Get())
            using (ManagementObject computerSystem = computerSystemCollection.Cast<ManagementObject>().Single())
                return (string) computerSystem["Name"];
        }
    }
}

查询远程注册表只需要一个额外的调用来OpenRemoteBaseKey()获取远程配置单元根的句柄......

using Microsoft.Win32;

namespace SO56585341
{
    public class RemoteRegistryComputerInfoSource : RemoteComputerInfoSource
    {
        public RemoteRegistryComputerInfoSource(string nameOrIp)
        : base(nameOrIp)
        {
        }

        public override string GetComputerName()
        {
            // See also @"SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName\"
            // https://www.oreilly.com/library/view/windows-nt-workstation/9781565926134/10_chapter-07.html
            const string valueParentKeyPath = @"SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName\";

            using (RegistryKey baseKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, RemoteNameOrIp))
            using (RegistryKey parentKey = baseKey.OpenSubKey(valueParentKeyPath, false))
                return (string) parentKey.GetValue("ComputerName");
        }
    }
}

如果将以上所有代码编译到一个项目中,则可以使用以下Program类对其进行测试...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace SO56585341
{
    public static class Program
    {
        private const string TestHost = "127.0.0.1";

        public static void Main()
        {
            // Get all non-abstract classes in the executing assembly that implement IComputerInfoSource
            IEnumerable<Type> computerInfoSourceTypes = Assembly.GetExecutingAssembly().GetTypes()
                .Where(type => type.IsClass && !type.IsAbstract && typeof(IComputerInfoSource).IsAssignableFrom(type));

            // For each constructor in each candidate class...
            foreach (Type computerInfoSourceType in computerInfoSourceTypes)
                foreach (ConstructorInfo constructor in computerInfoSourceType.GetConstructors())
                {
                    ParameterInfo[] constructorParameters = constructor.GetParameters();
                    object[] instanceParameters;

                    // If the constructor takes no parameters...
                    if (!constructorParameters.Any())
                        instanceParameters = Array.Empty<object>();
                    // ...or a single string parameter...
                    else if (constructorParameters.Length == 1 && constructorParameters[0].ParameterType == typeof(string))
                        instanceParameters = new object[1] { TestHost };
                    // ...otherwise skip this constructor
                    else
                        continue;

                    // Instantiate the class using the constructor parameters specified above
                    IComputerInfoSource computerInfoSource = (IComputerInfoSource) constructor.Invoke(instanceParameters);
                    string result;

                    try
                    {
                        result = computerInfoSource.GetComputerName();
                    }
                    catch (Exception ex)
                    {
                        result = ex.ToString();
                    }

                    Console.WriteLine(
                        "new {0}({1}).{2}(): \"{3}\"",
                        computerInfoSourceType.Name,
                        string.Join(
                            ", ",
                            instanceParameters.Select(value => $"\"{value}\"")
                        ),
                        nameof(IComputerInfoSource.GetComputerName),
                        result
                    );
                }
        }
    }
}

TestHost我发现无论设置为机器名称、CNAME 还是 IP 地址,此代码都可以正常工作。请注意,Remote*ComputerInfoSource如果...

  • 相应的服务(RemoteRegistryWinmgmt)未在远程计算机上运行,​​或者...
  • WMI-WINMGMT-In-TCP远程计算机上未启用适当的防火墙规则(例如),或者...
  • 该代码不是以具有访问远程服务权限的用户身份运行的。

至于 PowerShell,应该能够从 C# 移植上述任何方法的代码(直接翻译或使用 PowerShell 的便利)并将它们包装在调用中,Invoke-Command因为该代码将在远程计算机本地执行。例如...

Invoke-Command -ComputerName $nameOrIp -ScriptBlock { $Env:COMPUTERNAME }

...或者...

Invoke-Command -ComputerName $nameOrIp -ScriptBlock {
    # See also 'HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName\'
    # https://www.oreilly.com/library/view/windows-nt-workstation/9781565926134/10_chapter-07.html
    Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName\' -Name 'ComputerName'
}

PowerShell 还具有Get-WmiObject...

Get-WmiObject   -Class 'Win32_ComputerSystem' -ComputerName $nameOrIp -Property 'Name'

...和Get-CimInstance​​cmdlet...

Get-CimInstance -Class 'Win32_ComputerSystem' -ComputerName $nameOrIp -Property 'Name'

...这使得使用 WMI 变得更加容易。一般来说,我会推荐使用 WMI,因为它很容易从 C# 和 PowerShell 用于本地和远程查询,并且它的存在正是为了检索系统详细信息而无需了解底层 API 调用或数据表示。

请注意,使用Invoke-CommandGet-CimInstancecmdlet 时,WinRM服务必须在远程计算机上运行,​​并且WINRM-HTTP-In-TCP-NoScope必须启用适当的防火墙规则(例如 )。此外,当将 IP 地址传递给其中-ComputerName任何一个 cmdlet 的参数时,该地址必须与WSMan:\localhost\Client\TrustedHosts. 如果您需要通过 IP 地址扫描整个网络,我测试并发现它TrustedHosts接受*通配符但不接受子网掩码、CIDR 表示法或?通配符。

于 2020-01-14T04:55:27.203 回答