将我的评论重构为答案...
想象一下,我们有一个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_ComputerSystem
ManagementClass
Win32_ComputerSystem
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();
}
}
通过 a检索Win32_ComputerSystem
实例ManagementClass
只是显式传递它的问题 aManagementPath
还指定NamespacePath
and 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
如果...
- 相应的服务(
RemoteRegistry
或Winmgmt
)未在远程计算机上运行,或者...
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-Command
或Get-CimInstance
cmdlet 时,WinRM
服务必须在远程计算机上运行,并且WINRM-HTTP-In-TCP-NoScope
必须启用适当的防火墙规则(例如 )。此外,当将 IP 地址传递给其中-ComputerName
任何一个 cmdlet 的参数时,该地址必须与WSMan:\localhost\Client\TrustedHosts
. 如果您需要通过 IP 地址扫描整个网络,我测试并发现它TrustedHosts
接受*
通配符但不接受子网掩码、CIDR 表示法或?
通配符。