CreateService location for x64 driver - c#

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

Related

Getting Windows OS version programmatically

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
{
}

Watin Internet Explorer how to start in-private mode?

I am writing a few automated web tests using C# Watin framework.
I was hoping anyone can help me out,
How do i open new IE instance in "in private" browse mode? (i.e incognito mode)
The need for "in private" browsing, is because some of the tests, require log in. (and i run a few in parallel)
I could not find any resources on the matter. (other than half patch i found in some forum)
Thanks for your help!
The only solution i was able to find, is to open IE instance via command in incognito mode, then attach Watin to it.
//gen random url so we can find the window later
Random rnd = new Random();
int id = rnd.Next(1000, 10000);
string url = "id" + id+".com";
//opening explorer
Process.Start("IExplore.exe", "-private -nomerge " + url);
browser = Browser.AttachTo<IE>(Find.ByUrl(new Regex(url)));
browser.GoTo("http://www.google.com");
I downloaded WatiN source codes, opened IE.cs and edited the method CreateIExploreInNewProcess
private static Process CreateIExploreInNewProcess()
{
var arguments = "about:blank";
if (GetMajorIEVersion() >= 8 && Settings.MakeNewIe8InstanceNoMerge)
arguments = "-nomerge " + arguments;
if (Settings.OpenInIncognitoMode == true)
{
arguments = "-private " + arguments;
}
var m_Proc = Process.Start("IExplore.exe", arguments);
if (m_Proc == null) throw new WatiNException("Could not start IExplore.exe process");
return m_Proc;
}
Then, added this to Settings.cs:
/// <summary>
/// Gets or sets a value indicating whether the browser will be opened in incognito mode.
/// </summary>
/// <value>
/// <c>true</c> if IE instance needs to be opened in incognito mode, otherwise <c>false</c>.
/// </value>
public static bool OpenInIncognitoMode
{
get { return Instance.OpenInIncognitoMode; }
set { Instance.OpenInIncognitoMode = value; }
}
After that, added this to ISettings.cs
/// <summary>
/// Gets or sets a value indicating whether the browser will be opened in incognito mode.
/// </summary>
/// <value>
/// <c>true</c> if IE instance needs to be opened in incognito mode, otherwise <c>false</c>.
/// </value>
bool OpenInIncognitoMode { get; set; }
Finally, edited DefaultSettings.cs like this:
private struct settingsStruct
{
...
...
public bool makeNewIe8InstanceNoMerge;
public bool closeExistingFireFoxInstances;
public bool incognitoMode;
}
public bool OpenInIncognitoMode
{
get { return settings.incognitoMode; }
set { settings.incognitoMode = value; }
}
private void SetDefaults()
{
settings = new settingsStruct
{
...
...
makeNewIe8InstanceNoMerge = true,
closeExistingFireFoxInstances = true,
incognitoMode = false
};
}
Compile it and add the new DLL to your project. After this, all you need to do in your project is this:
Settings.OpenInIncognitoMode = true;
var browser = new IE(url, true);
The trick is to pass the argument -private to the call of IExplore.exe like here:
string argument = "-private -nomerge about:blank";
process = Process.Start("IExplore.exe", argument);
if (process == null)
throw new InvalidOperationException("The IExplore.exe process can't be started");
Thread.Sleep(3000);
handle = process.MainWindowHandle.ToInt32();
var allBrowsers = new SHDocVw.ShellWindows();
if (handle != 0)
{
foreach (InternetExplorer browser in allBrowsers)
if (browser.HWND == handle)
{
ie = browser;
iehandle = (IntPtr)ie.HWND;
break;
}
}

How does one authenticate a machine with active directory? [duplicate]

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.

How to detect if machine is joined to domain?

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.

How to associate a file extension to the current executable in C#

I'd like to to associate a file extension to the current executable in C#.
This way when the user clicks on the file afterwards in explorer, it'll run my executable with the given file as the first argument.
Ideally it'd also set the icon for the given file extensions to the icon for my executable.
Thanks all.
There doesn't appear to be a .Net API for directly managing file associations but you can use the Registry classes for reading and writing the keys you need to.
You'll need to create a key under HKEY_CLASSES_ROOT with the name set to your file extension (eg: ".txt"). Set the default value of this key to a unique name for your file type, such as "Acme.TextFile". Then create another key under HKEY_CLASSES_ROOT with the name set to "Acme.TextFile". Add a subkey called "DefaultIcon" and set the default value of the key to the file containing the icon you wish to use for this file type. Add another sibling called "shell". Under the "shell" key, add a key for each action you wish to have available via the Explorer context menu, setting the default value for each key to the path to your executable followed by a space and "%1" to represent the path to the file selected.
For instance, here's a sample registry file to create an association between .txt files and EmEditor:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\.txt]
#="emeditor.txt"
[HKEY_CLASSES_ROOT\emeditor.txt]
#="Text Document"
[HKEY_CLASSES_ROOT\emeditor.txt\DefaultIcon]
#="%SystemRoot%\\SysWow64\\imageres.dll,-102"
[HKEY_CLASSES_ROOT\emeditor.txt\shell]
[HKEY_CLASSES_ROOT\emeditor.txt\shell\open]
[HKEY_CLASSES_ROOT\emeditor.txt\shell\open\command]
#="\"C:\\Program Files\\EmEditor\\EMEDITOR.EXE\" \"%1\""
[HKEY_CLASSES_ROOT\emeditor.txt\shell\print]
[HKEY_CLASSES_ROOT\emeditor.txt\shell\print\command]
#="\"C:\\Program Files\\EmEditor\\EMEDITOR.EXE\" /p \"%1\""
Also, if you decide to go the registry way, keep in mind that current user associations are under HKEY_CURRENT_USER\Software\Classes. It might be better to add your application there instead of local machine classes.
If your program will be run by limited users, you won't be able to modify CLASSES_ROOT anyway.
If you use ClickOnce deployment, this is all handled for you (at least, in VS2008 SP1); simply:
Project Properties
Publish
Options
File Associatons
(add whatever you need)
(note that it must be full-trust, target .NET 3.5, and be set for offline usage)
See also MSDN: How to: Create File Associations For a ClickOnce Application
Here's a complete example:
public class FileAssociation
{
public string Extension { get; set; }
public string ProgId { get; set; }
public string FileTypeDescription { get; set; }
public string ExecutableFilePath { get; set; }
}
public class FileAssociations
{
// needed so that Explorer windows get refreshed after the registry is updated
[System.Runtime.InteropServices.DllImport("Shell32.dll")]
private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2);
private const int SHCNE_ASSOCCHANGED = 0x8000000;
private const int SHCNF_FLUSH = 0x1000;
public static void EnsureAssociationsSet()
{
var filePath = Process.GetCurrentProcess().MainModule.FileName;
EnsureAssociationsSet(
new FileAssociation
{
Extension = ".binlog",
ProgId = "MSBuildBinaryLog",
FileTypeDescription = "MSBuild Binary Log",
ExecutableFilePath = filePath
},
new FileAssociation
{
Extension = ".buildlog",
ProgId = "MSBuildStructuredLog",
FileTypeDescription = "MSBuild Structured Log",
ExecutableFilePath = filePath
});
}
public static void EnsureAssociationsSet(params FileAssociation[] associations)
{
bool madeChanges = false;
foreach (var association in associations)
{
madeChanges |= SetAssociation(
association.Extension,
association.ProgId,
association.FileTypeDescription,
association.ExecutableFilePath);
}
if (madeChanges)
{
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, IntPtr.Zero, IntPtr.Zero);
}
}
public static bool SetAssociation(string extension, string progId, string fileTypeDescription, string applicationFilePath)
{
bool madeChanges = false;
madeChanges |= SetKeyDefaultValue(#"Software\Classes\" + extension, progId);
madeChanges |= SetKeyDefaultValue(#"Software\Classes\" + progId, fileTypeDescription);
madeChanges |= SetKeyDefaultValue($#"Software\Classes\{progId}\shell\open\command", "\"" + applicationFilePath + "\" \"%1\"");
return madeChanges;
}
private static bool SetKeyDefaultValue(string keyPath, string value)
{
using (var key = Registry.CurrentUser.CreateSubKey(keyPath))
{
if (key.GetValue(null) as string != value)
{
key.SetValue(null, value);
return true;
}
}
return false;
}
There may be specific reasons why you choose not to use an install package for your project but an install package is a great place to easily perform application configuration tasks such registering file extensions, adding desktop shortcuts, etc.
Here's how to create file extension association using the built-in Visual Studio Install tools:
Within your existing C# solution, add a new project and select project type as Other Project Types -> Setup and Deployment -> Setup Project (or try the Setup Wizard)
Configure your installer (plenty of existing docs for this if you need help)
Right-click the setup project in the Solution explorer, select View -> File Types, and then add the extension that you want to register along with the program to run it.
This method has the added benefit of cleaning up after itself if a user runs the uninstall for your application.
To be specific about the "Windows Registry" way:
I create keys under HKEY_CURRENT_USER\Software\Classes (like Ishmaeel said)
and follow the instruction answered by X-Cubed.
The sample code looks like:
private void Create_abc_FileAssociation()
{
/***********************************/
/**** Key1: Create ".abc" entry ****/
/***********************************/
Microsoft.Win32.RegistryKey key1 = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Software", true);
key1.CreateSubKey("Classes");
key1 = key1.OpenSubKey("Classes", true);
key1.CreateSubKey(".abc");
key1 = key1.OpenSubKey(".abc", true);
key1.SetValue("", "DemoKeyValue"); // Set default key value
key1.Close();
/*******************************************************/
/**** Key2: Create "DemoKeyValue\DefaultIcon" entry ****/
/*******************************************************/
Microsoft.Win32.RegistryKey key2 = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Software", true);
key2.CreateSubKey("Classes");
key2 = key2.OpenSubKey("Classes", true);
key2.CreateSubKey("DemoKeyValue");
key2 = key2.OpenSubKey("DemoKeyValue", true);
key2.CreateSubKey("DefaultIcon");
key2 = key2.OpenSubKey("DefaultIcon", true);
key2.SetValue("", "\"" + "(The icon path you desire)" + "\""); // Set default key value
key2.Close();
/**************************************************************/
/**** Key3: Create "DemoKeyValue\shell\open\command" entry ****/
/**************************************************************/
Microsoft.Win32.RegistryKey key3 = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Software", true);
key3.CreateSubKey("Classes");
key3 = key3.OpenSubKey("Classes", true);
key3.CreateSubKey("DemoKeyValue");
key3 = key3.OpenSubKey("DemoKeyValue", true);
key3.CreateSubKey("shell");
key3 = key3.OpenSubKey("shell", true);
key3.CreateSubKey("open");
key3 = key3.OpenSubKey("open", true);
key3.CreateSubKey("command");
key3 = key3.OpenSubKey("command", true);
key3.SetValue("", "\"" + "(The application path you desire)" + "\"" + " \"%1\""); // Set default key value
key3.Close();
}
Just show you guys a quick demo, very easy to understand. You could modify those key values and everything is good to go.
The code below is a function the should work, it adds the required values in the windows registry. Usually i run SelfCreateAssociation(".abc") in my executable. (form constructor or onload or onshown) It will update the registy entry for the current user, everytime the executable is executed. (good for debugging, if you have some changes).
If you need detailed information about the registry keys involved check out this MSDN link.
https://msdn.microsoft.com/en-us/library/windows/desktop/dd758090(v=vs.85).aspx
To get more information about the general ClassesRoot registry key. See this MSDN article.
https://msdn.microsoft.com/en-us/library/windows/desktop/ms724475(v=vs.85).aspx
public enum KeyHiveSmall
{
ClassesRoot,
CurrentUser,
LocalMachine,
}
/// <summary>
/// Create an associaten for a file extension in the windows registry
/// CreateAssociation(#"vendor.application",".tmf","Tool file",#"C:\Windows\SYSWOW64\notepad.exe",#"%SystemRoot%\SYSWOW64\notepad.exe,0");
/// </summary>
/// <param name="ProgID">e.g. vendor.application</param>
/// <param name="extension">e.g. .tmf</param>
/// <param name="description">e.g. Tool file</param>
/// <param name="application">e.g. #"C:\Windows\SYSWOW64\notepad.exe"</param>
/// <param name="icon">#"%SystemRoot%\SYSWOW64\notepad.exe,0"</param>
/// <param name="hive">e.g. The user-specific settings have priority over the computer settings. KeyHive.LocalMachine need admin rights</param>
public static void CreateAssociation(string ProgID, string extension, string description, string application, string icon, KeyHiveSmall hive = KeyHiveSmall.CurrentUser)
{
RegistryKey selectedKey = null;
switch (hive)
{
case KeyHiveSmall.ClassesRoot:
Microsoft.Win32.Registry.ClassesRoot.CreateSubKey(extension).SetValue("", ProgID);
selectedKey = Microsoft.Win32.Registry.ClassesRoot.CreateSubKey(ProgID);
break;
case KeyHiveSmall.CurrentUser:
Microsoft.Win32.Registry.CurrentUser.CreateSubKey(#"Software\Classes\" + extension).SetValue("", ProgID);
selectedKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(#"Software\Classes\" + ProgID);
break;
case KeyHiveSmall.LocalMachine:
Microsoft.Win32.Registry.LocalMachine.CreateSubKey(#"Software\Classes\" + extension).SetValue("", ProgID);
selectedKey = Microsoft.Win32.Registry.LocalMachine.CreateSubKey(#"Software\Classes\" + ProgID);
break;
}
if (selectedKey != null)
{
if (description != null)
{
selectedKey.SetValue("", description);
}
if (icon != null)
{
selectedKey.CreateSubKey("DefaultIcon").SetValue("", icon, RegistryValueKind.ExpandString);
selectedKey.CreateSubKey(#"Shell\Open").SetValue("icon", icon, RegistryValueKind.ExpandString);
}
if (application != null)
{
selectedKey.CreateSubKey(#"Shell\Open\command").SetValue("", "\"" + application + "\"" + " \"%1\"", RegistryValueKind.ExpandString);
}
}
selectedKey.Flush();
selectedKey.Close();
}
/// <summary>
/// Creates a association for current running executable
/// </summary>
/// <param name="extension">e.g. .tmf</param>
/// <param name="hive">e.g. KeyHive.LocalMachine need admin rights</param>
/// <param name="description">e.g. Tool file. Displayed in explorer</param>
public static void SelfCreateAssociation(string extension, KeyHiveSmall hive = KeyHiveSmall.CurrentUser, string description = "")
{
string ProgID = System.Reflection.Assembly.GetExecutingAssembly().EntryPoint.DeclaringType.FullName;
string FileLocation = System.Reflection.Assembly.GetExecutingAssembly().Location;
CreateAssociation(ProgID, extension, description, FileLocation, FileLocation + ",0", hive);
}
The file associations are defined in the registry under HKEY_CLASSES_ROOT.
There's a VB.NET example here that I'm you can port easily to C#.
There are two cmd tools that have been around since Windows 7 which make it very easy to create simple file associations. They are assoc and ftype. Here's a basic explanation of each command.
Assoc - associates a file extension (like '.txt') with a "file type."
FType - defines an executable to run when the user opens a given "file type."
Note that these are cmd tools and not executable files (exe). This means that they can only be run in a cmd window, or by using ShellExecute with "cmd /c assoc." You can learn more about them at the links or by typing "assoc /?" and "ftype /?" at a cmd prompt.
So to associate an application with a .bob extension, you could open a cmd window (WindowKey+R, type cmd, press enter) and run the following:
assoc .bob=BobFile
ftype BobFile=c:\temp\BobView.exe "%1"
This is much simpler than messing with the registry and it is more likely to work in future windows version.
Wrapping it up, here is a C# function to create a file association:
public static int setFileAssociation(string[] extensions, string fileType, string openCommandString) {
int v = execute("cmd", "/c ftype " + fileType + "=" + openCommandString);
foreach (string ext in extensions) {
v = execute("cmd", "/c assoc " + ext + "=" + fileType);
if (v != 0) return v;
}
return v;
}
public static int execute(string exeFilename, string arguments) {
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.CreateNoWindow = false;
startInfo.UseShellExecute = true;
startInfo.FileName = exeFilename;
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.Arguments = arguments;
try {
using (Process exeProcess = Process.Start(startInfo)) {
exeProcess.WaitForExit();
return exeProcess.ExitCode;
}
} catch {
return 1;
}
}

Categories

Resources