How do I detect whether the machine is joined to an Active Directory domain (versus in Workgroup mode)?
Don't fool with pinvoke if you don't have to.
Reference System.DirectoryServices, then call:
System.DirectoryServices.ActiveDirectory.Domain.GetComputerDomain()
Throws an ActiveDirectoryObjectNotFoundException if the machine is not domain-joined.
The Domain object that's returned contains the Name property you're looking for.
You can PInvoke to Win32 API's such as NetGetDcName which will return a null/empty string for a non domain-joined machine.
Even better is NetGetJoinInformation which will tell you explicitly if a machine is unjoined, in a workgroup or in a domain.
Using NetGetJoinInformation I put together this, which worked for me:
public class Test
{
public static bool IsInDomain()
{
Win32.NetJoinStatus status = Win32.NetJoinStatus.NetSetupUnknownStatus;
IntPtr pDomain = IntPtr.Zero;
int result = Win32.NetGetJoinInformation(null, out pDomain, out status);
if (pDomain != IntPtr.Zero)
{
Win32.NetApiBufferFree(pDomain);
}
if (result == Win32.ErrorSuccess)
{
return status == Win32.NetJoinStatus.NetSetupDomainName;
}
else
{
throw new Exception("Domain Info Get Failed", new Win32Exception());
}
}
}
internal class Win32
{
public const int ErrorSuccess = 0;
[DllImport("Netapi32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
public static extern int NetGetJoinInformation(string server, out IntPtr domain, out NetJoinStatus status);
[DllImport("Netapi32.dll")]
public static extern int NetApiBufferFree(IntPtr Buffer);
public enum NetJoinStatus
{
NetSetupUnknownStatus = 0,
NetSetupUnjoined,
NetSetupWorkgroupName,
NetSetupDomainName
}
}
Can also be called by using system.net
string domain = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().DomainName
If the domain string is empty the machine isn't bound.
Documentation on the property returned https://learn.microsoft.com/en-us/dotnet/api/system.net.networkinformation.ipglobalproperties.domainname?view=netframework-4.7.2#System_Net_NetworkInformation_IPGlobalProperties_DomainName
ManagementObject cs;
using(cs = new ManagementObject("Win32_ComputerSystem.Name='" + System.Environment.MachineName + "'" ))
{
cs.Get();
Console.WriteLine("{0}",cs["domain"].ToString());
}
That should allow you to get the domain. I believe it will be null or empty if you are part of a workgroup and not a domain.
Make sure to reference System.Management
Just wanted to drop Rob's Code in VB:
Public Class Test
Public Function IsInDomain() As Boolean
Try
Dim status As Win32.NetJoinStatus = Win32.NetJoinStatus.NetSetupUnknownStatus
Dim pDomain As IntPtr = IntPtr.Zero
Dim result As Integer = Win32.NetGetJoinInformation(Nothing, pDomain, status)
If (pDomain <> IntPtr.Zero) Then
Win32.NetApiBufferFree(pDomain)
End If
If (result = Win32.ErrorSuccess) Then
If (status = Win32.NetJoinStatus.NetSetupDomainName) Then
Return True
Else
Return False
End If
Else
Throw New Exception("Domain Info Get Failed")
End If
Catch ex As Exception
Return False
End Try
End Function
End Class
Public Class Win32
Public Const ErrorSuccess As Integer = 0
Declare Auto Function NetGetJoinInformation Lib "Netapi32.dll" (ByVal server As String, ByRef IntPtr As IntPtr, ByRef status As NetJoinStatus) As Integer
Declare Auto Function NetApiBufferFree Lib "Netapi32.dll" (ByVal Buffer As IntPtr) As Integer
Public Enum NetJoinStatus
NetSetupUnknownStatus = 0
NetSetupUnjoined
NetSetupWorkgroupName
NetSetupDomainName
End Enum
End Class
As Well as Stephan's code here:
Dim cs As System.Management.ManagementObject
Try
cs = New System.Management.ManagementObject("Win32_ComputerSystem.Name='" + System.Environment.MachineName + "'")
cs.Get()
dim myDomain as string = = cs("domain").ToString
Catch ex As Exception
End Try
I believe that only the second code will allow you to know what domain the machine joined, even if the current user IS NOT a domain member.
The Environment variables could work for you.
Environment.UserDomainName
MSDN Link for some more details.
Environment.GetEnvironmentVariable("USERDNSDOMAIN")
I'm not sure this environment variable exists without being in a domain.
Correct me if I'm wrong Windows Admin geeks -- I believe a computer can be in several domains so it may be more important to know what domain, if any, you are in instead of it being in any domain.
You can check the PartOfDomain property of Win32_ComputerSystem WMI class.
The MSDN says :
PartOfDomain
Data type: boolean
Access type: Read-only
If True, the computer is part of a domain. If the value is NULL, the computer is not in a domain or the status is unknown. If you
unjoin the computer from a domain, the value becomes false.
/// <summary>
/// Determines whether the local machine is a member of a domain.
/// </summary>
/// <returns>A boolean value that indicated whether the local machine is a member of a domain.</returns>
/// <remarks>http://msdn.microsoft.com/en-us/library/windows/desktop/aa394102%28v=vs.85%29.aspx</remarks>
public bool IsDomainMember()
{
ManagementObject ComputerSystem;
using (ComputerSystem = new ManagementObject(String.Format("Win32_ComputerSystem.Name='{0}'", Environment.MachineName)))
{
ComputerSystem.Get();
object Result = ComputerSystem["PartOfDomain"];
return (Result != null && (bool)Result);
}
}
Here's my methods with exception handling / comments which I developed based on several of the answers in this post.
Gets you the domain the computer is connected to.
Only returns the domain name if the user is actually logged in on a domain account.
/// <summary>
/// Returns the domain of the logged in user.
/// Therefore, if computer is joined to a domain but user is logged in on local account. String.Empty will be returned.
/// Relavant StackOverflow Post: http://stackoverflow.com/questions/926227/how-to-detect-if-machine-is-joined-to-domain-in-c
/// </summary>
/// <seealso cref="GetComputerDomainName"/>
/// <returns>Domain name if user is connected to a domain, String.Empty if not.</returns>
static string GetUserDomainName()
{
string domain = String.Empty;
try
{
domain = Environment.UserDomainName;
string machineName = Environment.MachineName;
if (machineName.Equals(domain,StringComparison.OrdinalIgnoreCase))
{
domain = String.Empty;
}
}
catch
{
// Handle exception if desired, otherwise returns null
}
return domain;
}
/// <summary>
/// Returns the Domain which the computer is joined to. Note: if user is logged in as local account the domain of computer is still returned!
/// </summary>
/// <seealso cref="GetUserDomainName"/>
/// <returns>A string with the domain name if it's joined. String.Empty if it isn't.</returns>
static string GetComputerDomainName()
{
string domain = String.Empty;
try
{
domain = System.DirectoryServices.ActiveDirectory.Domain.GetComputerDomain().Name;
}
catch
{
// Handle exception here if desired.
}
return domain;
}
You might want to try using the DomainRole WMI field. Values of 0 and 2 show standalone workstation and standalone server respectively.
We are using this for XIA Configuration our network audit software so I've cribbed the method here...
/// <summary>
/// Determines whether the local machine is a member of a domain.
/// </summary>
/// <returns>A boolean value that indicated whether the local machine is a member of a domain.</returns>
/// <remarks>http://msdn.microsoft.com/en-gb/library/windows/desktop/aa394102(v=vs.85).aspx</remarks>
public bool IsDomainMember()
{
ManagementObject ComputerSystem;
using (ComputerSystem = new ManagementObject(String.Format("Win32_ComputerSystem.Name='{0}'", Environment.MachineName)))
{
ComputerSystem.Get();
UInt16 DomainRole = (UInt16)ComputerSystem["DomainRole"];
return (DomainRole != 0 & DomainRole != 2);
}
}
The proposed solution above returns false on a domain machine if a local user is logged in.
The most reliable method i have found is via WMI:
http://msdn.microsoft.com/en-us/library/aa394102(v=vs.85).aspx (see domainrole)
You can check using WMI:
private bool PartOfDomain()
{
ManagementObject manObject = new ManagementObject(string.Format("Win32_ComputerSystem.Name='{0}'", Environment.MachineName));
return (bool)manObject["PartOfDomain"];
}
Domain.GetComputerDomain() can be extremely slow. In some environments it can take more than 30 seconds.
If performance matters, use GetComputerNameEx function:
bool IsComputerInDomain()
{
uint domainNameCapacity = 512;
var domainName = new StringBuilder((int)domainNameCapacity);
GetComputerNameEx(COMPUTER_NAME_FORMAT.ComputerNameDnsDomain, domainName, ref domainNameCapacity);
return domainName.Length > 0;
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool GetComputerNameEx(
COMPUTER_NAME_FORMAT NameType,
StringBuilder lpBuffer,
ref uint lpnSize);
enum COMPUTER_NAME_FORMAT
{
ComputerNameNetBIOS,
ComputerNameDnsHostname,
ComputerNameDnsDomain,
ComputerNameDnsFullyQualified,
ComputerNamePhysicalNetBIOS,
ComputerNamePhysicalDnsHostname,
ComputerNamePhysicalDnsDomain,
ComputerNamePhysicalDnsFullyQualified
}
It returns the same value as systeminfo shell command.
Related
I am trying to fetch Windows version with C# on my Windows 10 machine.
I always get those values (with C#\C++):
Major: 6
Minor: 2
Which is Windows 8 OS, accordingly to MSDN
C# code:
var major = OperatingSystem.Version.Major
var minor = OperatingSystem.Version.Minor
C++ code
void print_os_info()
{
//http://stackoverflow.com/questions/1963992/check-windows-version
OSVERSIONINFOW info;
ZeroMemory(&info, sizeof(OSVERSIONINFOW));
info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW);
LPOSVERSIONINFOW lp_info = &info;
GetVersionEx(lp_info);
printf("Windows version: %u.%u\n", info.dwMajorVersion, info.dwMinorVersion);
}
Windows 10 suppose to be with those:
Major: 10
Minor: 0*
(When I am taking a dump file from running process I can see that the OS version of that file is set to 10.0)
built by: 10.0.10586.0 (th2_release.151029-1700)
What am I missing here?
In my scenario I needed my application to capture computer info for possible bug-reports and statistics.
I did not find the solutions where an application manifest had to be added satisfactory. Most of the suggestions I found while googling this suggested just that, unfortunately.
Thing is, when using a manifest, each OS version has to be added manually to it in order for that particular OS version to be able to report itself at runtime.
In other words, this becomes a race condition: A user of my app may very well be using a version of my app that pre-dates the OS in use. I would have to upgrade the app immediately when a new OS version was launched by Microsoft. I would also have to force the users to upgrade the app at the same time as they updated the OS.
In other words, not very feasible.
After browsing through the options I found some references (surprisingly few compared to the app manifest) that instead suggested using registry lookups.
My (chopped down) ComputerInfo class with only WinMajorVersion, WinMinorVersion and IsServer properties looks like this:
using Microsoft.Win32;
namespace Inspection
{
/// <summary>
/// Static class that adds convenient methods for getting information on the running computers basic hardware and os setup.
/// </summary>
public static class ComputerInfo
{
/// <summary>
/// Returns the Windows major version number for this computer.
/// </summary>
public static uint WinMajorVersion
{
get
{
dynamic major;
// The 'CurrentMajorVersionNumber' string value in the CurrentVersion key is new for Windows 10,
// and will most likely (hopefully) be there for some time before MS decides to change this - again...
if (TryGetRegistryKey(#"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", out major))
{
return (uint) major;
}
// When the 'CurrentMajorVersionNumber' value is not present we fallback to reading the previous key used for this: 'CurrentVersion'
dynamic version;
if (!TryGetRegistryKey(#"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentVersion", out version))
return 0;
var versionParts = ((string) version).Split('.');
if (versionParts.Length != 2) return 0;
uint majorAsUInt;
return uint.TryParse(versionParts[0], out majorAsUInt) ? majorAsUInt : 0;
}
}
/// <summary>
/// Returns the Windows minor version number for this computer.
/// </summary>
public static uint WinMinorVersion
{
get
{
dynamic minor;
// The 'CurrentMinorVersionNumber' string value in the CurrentVersion key is new for Windows 10,
// and will most likely (hopefully) be there for some time before MS decides to change this - again...
if (TryGetRegistryKey(#"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMinorVersionNumber",
out minor))
{
return (uint) minor;
}
// When the 'CurrentMinorVersionNumber' value is not present we fallback to reading the previous key used for this: 'CurrentVersion'
dynamic version;
if (!TryGetRegistryKey(#"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentVersion", out version))
return 0;
var versionParts = ((string) version).Split('.');
if (versionParts.Length != 2) return 0;
uint minorAsUInt;
return uint.TryParse(versionParts[1], out minorAsUInt) ? minorAsUInt : 0;
}
}
/// <summary>
/// Returns whether or not the current computer is a server or not.
/// </summary>
public static uint IsServer
{
get
{
dynamic installationType;
if (TryGetRegistryKey(#"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "InstallationType",
out installationType))
{
return (uint) (installationType.Equals("Client") ? 0 : 1);
}
return 0;
}
}
private static bool TryGetRegistryKey(string path, string key, out dynamic value)
{
value = null;
try
{
using(var rk = Registry.LocalMachine.OpenSubKey(path))
{
if (rk == null) return false;
value = rk.GetValue(key);
return value != null;
}
}
catch
{
return false;
}
}
}
}
You'll need to add an app.manifest to your application:
then uncomment the following line:
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
Registry.GetValue(#"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentBuildNumber", string.Empty).ToString()
same code for all OSes from XP till current 10.16299,
over scenarios not properly work from windows 8
The OSVersion property reports the same version number (6.2.0.0) for both Windows 8 and Windows 8.1 and the same major and minor version number for Windows 10.
https://msdn.microsoft.com/library/system.environment.osversion.aspx
As the accepted answer is only for C#, here is a solution for C++.
It uses the RtlGetVersion in the ntdll.dll that uses the same structure as GetVersionEx (name is different, but the elements are the same) and gives you the correct version.
As this function is normally used for driver development, the function is declared in the DDK and not in the SDK. So I used a dynamic solution to call the function.
Please be aware that the ntdll.dll is loaded and released in every call. So if you need the function more often, keep the library loaded.
The structure pOSversion is pointing to must be initialized like for GetVersionEx.
BOOL GetTrueWindowsVersion(OSVERSIONINFOEX* pOSversion)
{
// Function pointer to driver function
NTSTATUS (WINAPI *pRtlGetVersion)(
PRTL_OSVERSIONINFOW lpVersionInformation) = NULL;
// load the System-DLL
HINSTANCE hNTdllDll = LoadLibrary("ntdll.dll");
// successfully loaded?
if (hNTdllDll != NULL)
{
// get the function pointer to RtlGetVersion
pRtlGetVersion = (NTSTATUS (WINAPI *)(PRTL_OSVERSIONINFOW))
GetProcAddress (hNTdllDll, "RtlGetVersion");
// if successfull then read the function
if (pRtlGetVersion != NULL)
pRtlGetVersion((PRTL_OSVERSIONINFOW)pOSversion);
// free the library
FreeLibrary(hNTdllDll);
} // if (hNTdllDll != NULL)
// if function failed, use fallback to old version
if (pRtlGetVersion == NULL)
GetVersionEx((OSVERSIONINFO*)pOSversion);
// always true ...
return (TRUE);
} // GetTrueWindowsVersion
You can do this in C# the same way C++ answer has it
[SecurityCritical]
[DllImport("ntdll.dll", SetLastError = true)]
internal static extern bool RtlGetVersion(ref OSVERSIONINFOEX versionInfo);
[StructLayout(LayoutKind.Sequential)]
internal struct OSVERSIONINFOEX
{
// The OSVersionInfoSize field must be set to Marshal.SizeOf(typeof(OSVERSIONINFOEX))
internal int OSVersionInfoSize;
internal int MajorVersion;
internal int MinorVersion;
internal int BuildNumber;
internal int PlatformId;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
internal string CSDVersion;
internal ushort ServicePackMajor;
internal ushort ServicePackMinor;
internal short SuiteMask;
internal byte ProductType;
internal byte Reserved;
}
...
var osVersionInfo = new OSVERSIONINFOEX { OSVersionInfoSize = Marshal.SizeOf(typeof(OSVERSIONINFOEX)) };
if (!RtlGetVersion(ref osVersionInfo))
{
// TODO: Error handling, call GetVersionEx, etc.
}
You can read from registry through code and do specific action what you intended.
Say for example:
Registry key is located at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion and then look for "ProductName".
You can open registry information by giving regedit.exe in run (Windows+r)
var reg =Registry.LocalMachine.OpenSubKey(#"SOFTWARE\Microsoft\WindowsNT\CurrentVersion");
string productName = (string)reg.GetValue("ProductName");
if (productName.StartsWith("Windows 10"))
{
}
else
{
}
Based on the MSDN documentation of File.Exists, the File.Exists method should return false on any error, including the caller not having access to read the file.
I would expect it to return false both when the file is set to FullControl denied to the user and FullControl denied to the user to the directory the file lives in.
What I'm seeing is when the user has access to the directory, but not the file, File.Exists returns true; however, if the user has no access to the directory, File.Exists returns false.
I wrote a small program that demonstrates what I'm talking about:
using System;
using System.DirectoryServices;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
namespace ConsoleApplication1
{
internal class Program
{
private const string DirName = "TestDir";
private const string FileName = "File.txt";
private const string Password = "Password1";
private const string UserName = "PermissionTestUser";
private static WindowsImpersonationContext Identity = null;
private static IntPtr LogonToken = IntPtr.Zero;
public enum LogonProvider
{
LOGON32_PROVIDER_DEFAULT = 0,
LOGON32_PROVIDER_WINNT35 = 1,
LOGON32_PROVIDER_WINNT40 = 2,
LOGON32_PROVIDER_WINNT50 = 3
};
public enum LogonType
{
LOGON32_LOGON_INTERACTIVE = 2,
LOGON32_LOGON_NETWORK = 3,
LOGON32_LOGON_BATCH = 4,
LOGON32_LOGON_SERVICE = 5,
LOGON32_LOGON_UNLOCK = 7,
LOGON32_LOGON_NETWORK_CLEARTEXT = 8, // Win2K or higher
LOGON32_LOGON_NEW_CREDENTIALS = 9 // Win2K or higher
};
public static void Main(string[] args)
{
string filePath = Path.Combine(DirName, FileName);
try
{
CreateUser();
CreateDir();
CreateFile(filePath);
// grant user full control to the dir
SetAccess(DirName, AccessControlType.Allow);
// deny user full control to the file
SetAccess(filePath, AccessControlType.Deny);
// impersonate user
Impersonate();
Console.WriteLine("File.Exists (with dir permissions): {0}", File.Exists(filePath));
UndoImpersonate();
// deny access to dir
SetAccess(DirName, AccessControlType.Deny);
// impersonate user
Impersonate();
Console.WriteLine("File.Exists (without dir permissions): {0}", File.Exists(filePath));
UndoImpersonate();
}
finally
{
UndoImpersonate();
DeleteDir();
DeleteUser();
}
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool CloseHandle(IntPtr handle);
private static void CreateDir()
{
Directory.CreateDirectory(DirName);
}
private static void CreateFile(string path)
{
File.Create(path).Dispose();
}
private static void CreateUser()
{
DirectoryEntry ad = new DirectoryEntry("WinNT://" + Environment.MachineName + ",computer");
DirectoryEntry newUser = ad.Children.Add(UserName, "user");
newUser.Invoke("SetPassword", new object[] { Password });
newUser.Invoke("Put", new object[] { "Description", "Test user" });
newUser.CommitChanges();
}
private static void DeleteDir()
{
Directory.Delete(DirName, true);
}
private static void DeleteUser()
{
DirectoryEntry ad = new DirectoryEntry("WinNT://" + Environment.MachineName + ",computer");
DirectoryEntries users = ad.Children;
DirectoryEntry user = users.Find(UserName, "user");
if (user != null)
{
users.Remove(user);
}
}
private static void Impersonate()
{
if (LogonUser(UserName, ".", Password, (int)LogonType.LOGON32_LOGON_INTERACTIVE, (int)LogonProvider.LOGON32_PROVIDER_DEFAULT, ref LogonToken))
{
Identity = WindowsIdentity.Impersonate(LogonToken);
return;
}
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool LogonUser(string lpszUserName,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
private static void SetAccess(string path, AccessControlType type)
{
FileSecurity fs = File.GetAccessControl(path);
FileSystemAccessRule far = new FileSystemAccessRule(UserName, FileSystemRights.FullControl, type);
fs.AddAccessRule(far);
File.SetAccessControl(path, fs);
}
private static void UndoImpersonate()
{
if (Identity != null)
{
Identity.Undo();
Identity = null;
}
if (LogonToken != IntPtr.Zero)
{
CloseHandle(LogonToken);
LogonToken = IntPtr.Zero;
}
}
}
}
The result of running this program is:
File.Exists (with dir permissions): True
File.Exists (without dir permissions): False
Can anyone explain why they differ? In both instances, the user doesn't have read access to the file.
That is the default behavior of the File.Exist. According to MSDN:
File.Exist
Return Value Type: System.Boolean
true if the caller has
the required permissions and path contains the name of an existing
file; otherwise, false. This method also returns false if path is
null, an invalid path, or a zero-length string. If the caller does not
have sufficient permissions to read the specified file, no exception
is thrown and the method returns false regardless of the existence of
path.
And additionally
The Exists method should not be used for path validation, this method
merely checks if the file specified in path exists. Passing an invalid
path to Exists returns false.
In other words, the required permission here, is the required permission to know the existence of the file (as the method name implies, File.Exist). And this means that as long as a user has access to the directory, it can know if the file exists or not.
Whether the user has file access or not doesn't affect the user's knowledge of the existence of the file, given the directory permission. But without directory permission, a user cannot know the existence of the file, and thus File.Exist returns false
Edit (after feedback from comments):
And probably the rather confusing part would be the last sentence:
If the caller does not
have sufficient permissions to read the specified file, no exception
is thrown and the method returns false regardless of the existence of
path.
The sufficient permissions to read the specified file is depending on the read-access of the parent directory rather than read-access of the specified file. (Additional comment by Mr. Rob). The word "sufficient" may give some hint about the behavior that it will only depend on read-access to the parent directory is needed, not the read-access to the specified file.
But I admit that the explanation and the choice of word may sound rather counter-intuitive as people may intuitively interpret "sufficient permissions to read the specified file" as the read-access to the specified file rather than to the parent directory.
If the file does exist but File.Exists(filePath) returns false, that means the application has the read permission to neither directory nor file, which can be proved.
if you really want to test if a file exists or not, you can invoke File.GetAccessControl(filePath). If the call returns without exception, it means the file does exist.
I have a driver that has both x86 and x64 bit drivers. The installer checks for Is64BitOS && Is64BitProcess. If it is a true then it grabs my x64 drivers and does something a bit strange to me. it would disable DisableWow64FSRedirection, copy the x64 driver to c:\windows\system32\drivers, then RevertWow64FSRedirection
that seems wrong to me. oh and after it gets done copying said file it creates a Kernel service. I'm not sure why the installer would do it, or even if it should. When I look into my registry (out of curiosity I installed both of them on my x64 machine) at HKLM\System\CurrentControlSet\Services\driver64 the Image path is \??\C:\Windows\System32\drivers\driver64.sys
but when I look at HKLM\System\CurrentControlSet\Services\driver86 the ImagePath is just simply driver86 not even a sys extension... although I do see a WOW64 flag that the other one doesn't have... interesting.
Long story short. I don't like that. If Microsoft decided to put in redirection then I'm also sure that when I create a SafeFileHandle to said driver that it would redirect to the correct driver. Am I being crazy?
I made the installer in C#. and for reference here is the installing bit of code
private static void InstallDriver()
{
string name = GetCorrectDriverName();
byte[] driver = is64Bit ? Properties.Resources.inpoutx64 : Properties.Resources.inpout32;
string path = Kernel32.CopyDriverToSystem32(is64Bit, name, driver);
ServiceInstaller.InstallAndStart(name, name, path);
}
Kernel32.cs
/// <summary>
/// Copies a driver with a specific name to the System32\Driver folder
/// </summary>
/// <param name="is64Bit">Value to determine if system is 32 or 64 bit. SYSTEM not program</param>
/// <param name="driverName">the name of the driver. file extension and path will be added to this, so just the name</param>
/// <param name="driver">the driver itself. In this case it is an embedded resource.</param>
/// <returns>the full path name of the driver that was intalled.</returns>
public static string CopyDriverToSystem32(bool is64Bit, string driverName, byte[] driver)
{
bool oldValue = false;
if (is64Bit) DisableWow64FSRedirection(out oldValue);
string path = Path.Combine(Environment.SystemDirectory, "drivers");
path = Path.Combine(path, driverName + ".sys");
File.WriteAllBytes(path, driver);
if (is64Bit) RevertWow64FSRedirection(oldValue);
return path;
}
ServiceInstaller.cs
public static void InstallAndStart(string serviceName, string displayName, string fileName)
{
IntPtr scm = OpenSCManager(ScmAccessRights.AllAccess);
try
{
IntPtr service = OpenService(scm, serviceName, ServiceAccessRights.AllAccess);
if (service == IntPtr.Zero)
{
service = CreateService(scm, serviceName, displayName, ServiceAccessRights.AllAccess, SERVICE_KERNEL_DRIVER, ServiceBootFlag.AutoStart, ServiceError.Normal, fileName, null, IntPtr.Zero, null, null, null);
}
if (service == IntPtr.Zero)
throw new ApplicationException("Failed to install service.");
try
{
StartService(service);
}
finally
{
CloseServiceHandle(service);
}
}
finally
{
CloseServiceHandle(scm);
}
}
private static IntPtr OpenSCManager(ScmAccessRights rights)
{
IntPtr scm = OpenSCManager(null, null, rights);
if (scm == IntPtr.Zero)
throw new ApplicationException("Could not connect to service control manager.");
return scm;
}
after it is done installing using it is super simple, and that is what i hope to keep.
string driverName = GetCorrectDriverName();
try
{
var driverHandle = Kernel32.CreateExclusiveRWFile(driverName);
}
catch (System.IO.FileNotFoundException)
{
InstallDriver();
}
How do I detect whether the machine is joined to an Active Directory domain (versus in Workgroup mode)?
Don't fool with pinvoke if you don't have to.
Reference System.DirectoryServices, then call:
System.DirectoryServices.ActiveDirectory.Domain.GetComputerDomain()
Throws an ActiveDirectoryObjectNotFoundException if the machine is not domain-joined.
The Domain object that's returned contains the Name property you're looking for.
You can PInvoke to Win32 API's such as NetGetDcName which will return a null/empty string for a non domain-joined machine.
Even better is NetGetJoinInformation which will tell you explicitly if a machine is unjoined, in a workgroup or in a domain.
Using NetGetJoinInformation I put together this, which worked for me:
public class Test
{
public static bool IsInDomain()
{
Win32.NetJoinStatus status = Win32.NetJoinStatus.NetSetupUnknownStatus;
IntPtr pDomain = IntPtr.Zero;
int result = Win32.NetGetJoinInformation(null, out pDomain, out status);
if (pDomain != IntPtr.Zero)
{
Win32.NetApiBufferFree(pDomain);
}
if (result == Win32.ErrorSuccess)
{
return status == Win32.NetJoinStatus.NetSetupDomainName;
}
else
{
throw new Exception("Domain Info Get Failed", new Win32Exception());
}
}
}
internal class Win32
{
public const int ErrorSuccess = 0;
[DllImport("Netapi32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
public static extern int NetGetJoinInformation(string server, out IntPtr domain, out NetJoinStatus status);
[DllImport("Netapi32.dll")]
public static extern int NetApiBufferFree(IntPtr Buffer);
public enum NetJoinStatus
{
NetSetupUnknownStatus = 0,
NetSetupUnjoined,
NetSetupWorkgroupName,
NetSetupDomainName
}
}
Can also be called by using system.net
string domain = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().DomainName
If the domain string is empty the machine isn't bound.
Documentation on the property returned https://learn.microsoft.com/en-us/dotnet/api/system.net.networkinformation.ipglobalproperties.domainname?view=netframework-4.7.2#System_Net_NetworkInformation_IPGlobalProperties_DomainName
ManagementObject cs;
using(cs = new ManagementObject("Win32_ComputerSystem.Name='" + System.Environment.MachineName + "'" ))
{
cs.Get();
Console.WriteLine("{0}",cs["domain"].ToString());
}
That should allow you to get the domain. I believe it will be null or empty if you are part of a workgroup and not a domain.
Make sure to reference System.Management
Just wanted to drop Rob's Code in VB:
Public Class Test
Public Function IsInDomain() As Boolean
Try
Dim status As Win32.NetJoinStatus = Win32.NetJoinStatus.NetSetupUnknownStatus
Dim pDomain As IntPtr = IntPtr.Zero
Dim result As Integer = Win32.NetGetJoinInformation(Nothing, pDomain, status)
If (pDomain <> IntPtr.Zero) Then
Win32.NetApiBufferFree(pDomain)
End If
If (result = Win32.ErrorSuccess) Then
If (status = Win32.NetJoinStatus.NetSetupDomainName) Then
Return True
Else
Return False
End If
Else
Throw New Exception("Domain Info Get Failed")
End If
Catch ex As Exception
Return False
End Try
End Function
End Class
Public Class Win32
Public Const ErrorSuccess As Integer = 0
Declare Auto Function NetGetJoinInformation Lib "Netapi32.dll" (ByVal server As String, ByRef IntPtr As IntPtr, ByRef status As NetJoinStatus) As Integer
Declare Auto Function NetApiBufferFree Lib "Netapi32.dll" (ByVal Buffer As IntPtr) As Integer
Public Enum NetJoinStatus
NetSetupUnknownStatus = 0
NetSetupUnjoined
NetSetupWorkgroupName
NetSetupDomainName
End Enum
End Class
As Well as Stephan's code here:
Dim cs As System.Management.ManagementObject
Try
cs = New System.Management.ManagementObject("Win32_ComputerSystem.Name='" + System.Environment.MachineName + "'")
cs.Get()
dim myDomain as string = = cs("domain").ToString
Catch ex As Exception
End Try
I believe that only the second code will allow you to know what domain the machine joined, even if the current user IS NOT a domain member.
The Environment variables could work for you.
Environment.UserDomainName
MSDN Link for some more details.
Environment.GetEnvironmentVariable("USERDNSDOMAIN")
I'm not sure this environment variable exists without being in a domain.
Correct me if I'm wrong Windows Admin geeks -- I believe a computer can be in several domains so it may be more important to know what domain, if any, you are in instead of it being in any domain.
You can check the PartOfDomain property of Win32_ComputerSystem WMI class.
The MSDN says :
PartOfDomain
Data type: boolean
Access type: Read-only
If True, the computer is part of a domain. If the value is NULL, the computer is not in a domain or the status is unknown. If you
unjoin the computer from a domain, the value becomes false.
/// <summary>
/// Determines whether the local machine is a member of a domain.
/// </summary>
/// <returns>A boolean value that indicated whether the local machine is a member of a domain.</returns>
/// <remarks>http://msdn.microsoft.com/en-us/library/windows/desktop/aa394102%28v=vs.85%29.aspx</remarks>
public bool IsDomainMember()
{
ManagementObject ComputerSystem;
using (ComputerSystem = new ManagementObject(String.Format("Win32_ComputerSystem.Name='{0}'", Environment.MachineName)))
{
ComputerSystem.Get();
object Result = ComputerSystem["PartOfDomain"];
return (Result != null && (bool)Result);
}
}
Here's my methods with exception handling / comments which I developed based on several of the answers in this post.
Gets you the domain the computer is connected to.
Only returns the domain name if the user is actually logged in on a domain account.
/// <summary>
/// Returns the domain of the logged in user.
/// Therefore, if computer is joined to a domain but user is logged in on local account. String.Empty will be returned.
/// Relavant StackOverflow Post: http://stackoverflow.com/questions/926227/how-to-detect-if-machine-is-joined-to-domain-in-c
/// </summary>
/// <seealso cref="GetComputerDomainName"/>
/// <returns>Domain name if user is connected to a domain, String.Empty if not.</returns>
static string GetUserDomainName()
{
string domain = String.Empty;
try
{
domain = Environment.UserDomainName;
string machineName = Environment.MachineName;
if (machineName.Equals(domain,StringComparison.OrdinalIgnoreCase))
{
domain = String.Empty;
}
}
catch
{
// Handle exception if desired, otherwise returns null
}
return domain;
}
/// <summary>
/// Returns the Domain which the computer is joined to. Note: if user is logged in as local account the domain of computer is still returned!
/// </summary>
/// <seealso cref="GetUserDomainName"/>
/// <returns>A string with the domain name if it's joined. String.Empty if it isn't.</returns>
static string GetComputerDomainName()
{
string domain = String.Empty;
try
{
domain = System.DirectoryServices.ActiveDirectory.Domain.GetComputerDomain().Name;
}
catch
{
// Handle exception here if desired.
}
return domain;
}
You might want to try using the DomainRole WMI field. Values of 0 and 2 show standalone workstation and standalone server respectively.
We are using this for XIA Configuration our network audit software so I've cribbed the method here...
/// <summary>
/// Determines whether the local machine is a member of a domain.
/// </summary>
/// <returns>A boolean value that indicated whether the local machine is a member of a domain.</returns>
/// <remarks>http://msdn.microsoft.com/en-gb/library/windows/desktop/aa394102(v=vs.85).aspx</remarks>
public bool IsDomainMember()
{
ManagementObject ComputerSystem;
using (ComputerSystem = new ManagementObject(String.Format("Win32_ComputerSystem.Name='{0}'", Environment.MachineName)))
{
ComputerSystem.Get();
UInt16 DomainRole = (UInt16)ComputerSystem["DomainRole"];
return (DomainRole != 0 & DomainRole != 2);
}
}
The proposed solution above returns false on a domain machine if a local user is logged in.
The most reliable method i have found is via WMI:
http://msdn.microsoft.com/en-us/library/aa394102(v=vs.85).aspx (see domainrole)
You can check using WMI:
private bool PartOfDomain()
{
ManagementObject manObject = new ManagementObject(string.Format("Win32_ComputerSystem.Name='{0}'", Environment.MachineName));
return (bool)manObject["PartOfDomain"];
}
Domain.GetComputerDomain() can be extremely slow. In some environments it can take more than 30 seconds.
If performance matters, use GetComputerNameEx function:
bool IsComputerInDomain()
{
uint domainNameCapacity = 512;
var domainName = new StringBuilder((int)domainNameCapacity);
GetComputerNameEx(COMPUTER_NAME_FORMAT.ComputerNameDnsDomain, domainName, ref domainNameCapacity);
return domainName.Length > 0;
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool GetComputerNameEx(
COMPUTER_NAME_FORMAT NameType,
StringBuilder lpBuffer,
ref uint lpnSize);
enum COMPUTER_NAME_FORMAT
{
ComputerNameNetBIOS,
ComputerNameDnsHostname,
ComputerNameDnsDomain,
ComputerNameDnsFullyQualified,
ComputerNamePhysicalNetBIOS,
ComputerNamePhysicalDnsHostname,
ComputerNamePhysicalDnsDomain,
ComputerNamePhysicalDnsFullyQualified
}
It returns the same value as systeminfo shell command.
This is similar to, but not a dupe of, this question - however, where it sought information on manually joining a server to a domain (and was rightly redirected) I am looking for help with some code that programmatically joins a machine to a domain.
The scenario is that we have a launcher service that instantiates Amazon EC2 Server2008R1 VMs, optionally passing a Machine Name in through the User-Data stream. A process is baked into our images that checks User-Data for a name on bootup - If none is present then the VM remains outside of our Cloud domain, but if the name is present then the machine is renamed as specified and auto-joined to the domain.
Here's the problem - if I run this process manually any time after instance start, it works exactly as described; the machine name is changed, and the VM is joined to the domain (we force a restart to make this happen).
However, when running as a Scheduled Task (triggered on startup) the machine rename happens as expected, but the subsequent call to JoinDomainOrWorkgroup (see below) picks-up the old randomised machine name given to the VM by EC2 instead of the new name it has just been assigned.
This results in a WMI return code of 8525, we get a disconnected misnamed entry in the AD repository (of that randomised name) and the machine is not joined to the domain. The VM then restarts, and a second pass through the startup process (abnormally triggered because there is content in User-Data but the machine is not yet in the domain) executes all the same steps and succeeds.
It looks like the machine name is set in the first pass but not 'finalised', and JoinDomainOrWorkgroup still sees the original name. On the second pass, the machine name is already set properly, and so JoinDomainOrWorkgroup works as expected. Quite why the process behaves this way during startup, but works perfectly when run manually on an already-started VM, is I think the nub of the problem.
I've tried inserting a delay between the rename and join steps in case the call to JoinDomainOrWorkgroup was happening before the rename was finalised behind the scenes, but this hasn't helped - and I didn't really expect it to, since the whole process works perfectly when run manually. So it's probably a combination of a subtle difference in machine state during bootup and something silly in the code.
Maybe using System.Environment.MachineName in the SetDomainMembership method is inadvisable? But it stil fails even if I pass the new name in as a string as I do for SetMachineName. So I'm stumped.
Here's the WMI code that renames the machine:
/// <summary>
/// Set Machine Name
/// </summary>
public static bool SetMachineName(string newName)
{
_lh.Log(LogHandler.LogType.Debug, string.Format("Setting Machine Name to '{0}'...", newName));
// Invoke WMI to populate the machine name
using (ManagementObject wmiObject = new ManagementObject(new ManagementPath("Win32_ComputerSystem.Name='" + System.Environment.MachineName + "'")))
{
ManagementBaseObject inputArgs = wmiObject.GetMethodParameters("Rename");
inputArgs["Name"] = newName;
// Set the name
ManagementBaseObject outParams = wmiObject.InvokeMethod("Rename", inputArgs, null);
// Weird WMI shennanigans to get a return code (is there no better way to do this??)
uint ret = (uint)(outParams.Properties["ReturnValue"].Value);
if (ret == 0)
{
// It worked
return true;
}
else
{
// It didn't work
_lh.Log(LogHandler.LogType.Fatal, string.Format("Unable to change Machine Name from '{0}' to '{1}'", System.Environment.MachineName, newName));
return false;
}
}
}
And here's the WMI code that joins it to the domain:
/// <summary>
/// Set domain membership
/// </summary>
public static bool SetDomainMembership()
{
_lh.Log(LogHandler.LogType.Debug, string.Format("Setting domain membership of '{0}' to '{1}'...", System.Environment.MachineName, _targetDomain));
// Invoke WMI to join the domain
using (ManagementObject wmiObject = new ManagementObject(new ManagementPath("Win32_ComputerSystem.Name='" + System.Environment.MachineName + "'")))
{
try
{
// Obtain in-parameters for the method
ManagementBaseObject inParams = wmiObject.GetMethodParameters("JoinDomainOrWorkgroup");
inParams["Name"] = "*****";
inParams["Password"] = "*****";
inParams["UserName"] = "*****";
inParams["FJoinOptions"] = 3; // Magic number: 3 = join to domain and create computer account
// Execute the method and obtain the return values.
ManagementBaseObject outParams = wmiObject.InvokeMethod("JoinDomainOrWorkgroup", inParams, null);
_lh.Log(LogHandler.LogType.Debug, string.Format("JoinDomainOrWorkgroup return code: '{0}'", outParams["ReturnValue"]));
// Did it work? ** disabled so we restart later even if it fails
//uint ret = (uint)(outParams.Properties["ReturnValue"].Value);
//if (ret != 0)
//{
// // Nope
// _lh.Log(LogHandler.LogType.Fatal, string.Format("JoinDomainOrWorkgroup failed with return code: '{0}'", outParams["ReturnValue"]));
// return false;
//}
return true;
}
catch (ManagementException e)
{
// It didn't work
_lh.Log(LogHandler.LogType.Fatal, string.Format("Unable to join domain '{0}'", _targetDomain), e);
return false;
}
}
}
Apologies if this code looks mind-numbingly stupid - I'm new to WMI, and this is largely cribbed from examples I've found on the interwebs; if there's a smarter/neater way to do this then by all means demonstrate. If you can cure the problem at the same time, bonus points!
OK, here it is.
Firstly, the order of the fields in System Properties is a little misleading - you see Machine Name first, and Domain/Workgroup below that. This subconsciously affected my thinking, and meant my code copied that ordering by trying to set the name first, and then join the machine to the domain. Whilst this does work under some circumstances, it's not consistent or reliable. So the biggest lesson learned here is...
Join the domain first - then change
the machine name.
Yep, that's actually all there is to it. After numerous test iterations, it finally dawned on me that it might work better if I tried it this way around. I tripped-up on the change of name on my first pass, but quickly realised that it was still using the local system credentials - but now that the machine was joined to the domain at this point, it needed the same domain credentials as were used to join the domain itself. A fast bit of code-tweaking later, and we now have a consistently-reliable WMI routine that joins the domain and then changes the name.
It might not be the neatest implementation (feel free to comment on improvements) but it works. Enjoy.
/// <summary>
/// Join domain and set Machine Name
/// </summary>
public static bool JoinAndSetName(string newName)
{
_lh.Log(LogHandler.LogType.Debug, string.Format("Joining domain and changing Machine Name from '{0}' to '{1}'...", Environment.MachineName, newName));
// Get WMI object for this machine
using (ManagementObject wmiObject = new ManagementObject(new ManagementPath("Win32_ComputerSystem.Name='" + Environment.MachineName + "'")))
{
try
{
// Obtain in-parameters for the method
ManagementBaseObject inParams = wmiObject.GetMethodParameters("JoinDomainOrWorkgroup");
inParams["Name"] = "domain_name";
inParams["Password"] = "domain_account_password";
inParams["UserName"] = "domain_account";
inParams["FJoinOptions"] = 3; // Magic number: 3 = join to domain and create computer account
_lh.Log(LogHandler.LogType.Debug, string.Format("Joining machine to domain under name '{0}'...", inParams["Name"]));
// Execute the method and obtain the return values.
ManagementBaseObject joinParams = wmiObject.InvokeMethod("JoinDomainOrWorkgroup", inParams, null);
_lh.Log(LogHandler.LogType.Debug, string.Format("JoinDomainOrWorkgroup return code: '{0}'", joinParams["ReturnValue"]));
// Did it work?
if ((uint)(joinParams.Properties["ReturnValue"].Value) != 0)
{
// Join to domain didn't work
_lh.Log(LogHandler.LogType.Fatal, string.Format("JoinDomainOrWorkgroup failed with return code: '{0}'", joinParams["ReturnValue"]));
return false;
}
}
catch (ManagementException e)
{
// Join to domain didn't work
_lh.Log(LogHandler.LogType.Fatal, string.Format("Unable to join domain '{0}'", _targetDomain), e);
return false;
}
// Join to domain worked - now change name
ManagementBaseObject inputArgs = wmiObject.GetMethodParameters("Rename");
inputArgs["Name"] = newName;
inputArgs["Password"] = "domain_account_password";
inputArgs["UserName"] = "domain_account";
// Set the name
ManagementBaseObject nameParams = wmiObject.InvokeMethod("Rename", inputArgs, null);
_lh.Log(LogHandler.LogType.Debug, string.Format("Machine Rename return code: '{0}'", nameParams["ReturnValue"]));
if ((uint)(nameParams.Properties["ReturnValue"].Value) != 0)
{
// Name change didn't work
_lh.Log(LogHandler.LogType.Fatal, string.Format("Unable to change Machine Name from '{0}' to '{1}'", Environment.MachineName, newName));
return false;
}
// All ok
return true;
}
}
Ok little update after all these years if somebody would need it.
WMI does not contain JoinDomain anymore only workgroup (WIN 10, Build 1909). You can use netapi32.dll
More info here:
https://learn.microsoft.com/en-us/windows/win32/api/lmjoin/nf-lmjoin-netjoindomain
Little fast example:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
[DllImport("netapi32.dll", CharSet = CharSet.Unicode)]
static extern uint NetJoinDomain(
string lpServer,
string lpDomain,
string lpAccountOU,
string lpAccount,
string lpPassword,
JoinOptions NameType);
[Flags]
enum JoinOptions
{
NETSETUP_JOIN_DOMAIN = 0x00000001,
NETSETUP_ACCT_CREATE = 0x00000002,
NETSETUP_ACCT_DELETE = 0x00000004,
NETSETUP_WIN9X_UPGRADE = 0x00000010,
NETSETUP_DOMAIN_JOIN_IF_JOINED = 0x00000020,
NETSETUP_JOIN_UNSECURE = 0x00000040,
NETSETUP_MACHINE_PWD_PASSED = 0x00000080,
NETSETUP_DEFER_SPN_SET = 0x10000000
}
public static uint domainjoin(string server, string domain, string OU, string account, string password)
{
try
{
uint value1 = NetJoinDomain(server, domain, OU, account, password, (JoinOptions.NETSETUP_JOIN_DOMAIN | JoinOptions.NETSETUP_DOMAIN_JOIN_IF_JOINED | JoinOptions.NETSETUP_ACCT_CREATE));
return value1;
}
catch (Exception e)
{
MessageBox.Show(e.Message);
return 11;
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var succes = domainjoin(null, "mydomain.local", null, "administrator", "UltraSecretPasword");
MessageBox.Show(succes.ToString());
}
}