如何从远程Windows计算机检索计算机名称?

时间:2019-06-13 16:57:29

标签: c# .net windows computer-name

我正在尝试找到一种从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();
            }
        }

哦,在评论中的培根中,您必须通过Windows防火墙允许WMI才能起作用。对我来说效果很好。

1 个答案:

答案 0 :(得分:1)

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

想象一下我们有一个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/invoke GetComputerName() Windows API functionEnvironment.MachineNamebehind 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();
    }
}

通过Win32_ComputerSystem检索ManagementClass实例就变成了显式传递一个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可以通过传递包裹在ManagementScope中的类似ManagementPath来使用...

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的便利性)并将它们包装在对{{3 }},因为该代码将在远程计算机本地执行。例如...

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还具有Invoke-Command ...

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

...和Get-WmiObject cmdlet ...

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

...这使得使用WMI更加容易。通常,我建议使用WMI ,因为它很容易从C#和PowerShell中用于本地和远程查询,并且正是出于检索系统详细信息的目的而存在的,而无需了解有关WMI的信息。基础API调用或数据表示形式。

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