【问题标题】:How can I retrieve the machine name from a remote Windows computer?如何从远程 Windows 计算机检索机器名称?
【发布时间】:2019-10-28 08:00:47
【问题描述】:

我正在尝试找到一种从 C# 中的 IP 地址检索计算机名称的方法,但所有在线标记为检索计算机名称或计算机名称的答案实际上获取的是主机名,而不是计算机姓名。如果您转到“控制面板”>“系统”,则该菜单“计算机名”中有一个属性...我正在远程计算机上查找此值。 AFAIK,如果没有 DNS 映射,HOSTNAME 将 = 完整 计算机名。问题是我正在处理的这些服务器确实有 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 不起作用。这就是我花时间打字的全部原因。

谢谢

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

        //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();
            }
        }

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

【问题讨论】:

  • this 的方向是否正确?
  • 我认为标题中包含“来自 IP 地址”(而不是“远程机器”)和带有 System.Net 类的代码 sn-p 会使这看起来像是对某人略读的重复迅速地。为了澄清,您正在寻找机器名称 - 即操作系统用来识别其自己的计算机的名称 - 不一定(或绝对不)与 DNS 相关,对吧? DNS 可能只涉及到 连接 到远程计算机,但您不希望名称 根据 到 DNS,是吗?这是仅限 Windows 的环境吗? Active Directory 会发挥作用吗?
  • 在我的脑海中,有%COMPUTERNAME%/$Env:COMPUTERNAME 环境变量,.NET 有Environment.MachineName property,然后无论注册表值最终存储返回的文本。更好的是单例Win32_ComputerSystem className 属性,它应该更容易远程查询。 (请注意,同一个类也提供了 DNSHostName 属性。)
  • @BACON 不错...是的 Environment.MachineName 正是我正在寻找的。因此,您正在考虑对 IP 地址运行查询,然后返回 Win32_ComputerSystem Name 属性?感谢先生的帮助
  • 我测试并发现TrustedHosts 接受通配符,但不接受子网掩码或 CIDR 表示法。 Get-WmiObject -Class Win32_ComputerSystem -Property Name -ComputerName $nameOrIp 也可以正常工作,无需将 $nameOrIp 的值添加到 TrustedHosts,尽管它确实需要在 Windows 防火墙中启用 Windows Management Instrumentation (WMI-In) 规则。

标签: c# .net windows computer-name


【解决方案1】:

重构我的 cmets 作为答案...

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

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

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

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

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

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

您可以p/invokeGetComputerName() Windows API function,这就是Environment.MachineName 所做的behind the scenes...

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 检索单例 Win32_ComputerSystem className 属性。您可以通过为 Win32_ComputerSystem 类实例化一个 ManagementClass 实例并在其上调用 GetInstances() 来检索包含唯一实例的数组来做到这一点...

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();
    }
}

通过ManagementClass 检索Win32_ComputerSystem 实例只需将ManagementPath 显式传递给它,该ManagementPath 还指定NamespacePathServer...

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"];
        }
    }
}

ManagementObjectSearcher 可以通过传递一个类似的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-CimInstance cmdlet 时,WinRM 服务必须在远程计算机上运行,​​并且必须启用适当的防火墙规则(例如 WINRM-HTTP-In-TCP-NoScope)。此外,当将 IP 地址传递给任一 cmdlet 的 -ComputerName 参数时,该地址必须与 WSMan:\localhost\Client\TrustedHosts 的值匹配。如果您需要通过 IP 地址扫描整个网络,我测试发现 TrustedHosts 接受 * 通配符,但不接受子网掩码、CIDR 表示法或 ? 通配符。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多