How to query all Windows Terminal Services (WTS) / RDP sessions?
I want to know who has a session on a Windows Machine, but don't want to use and parse the output of quser, qwinsta, query session or query user.
Info:
This was just a self-answered question.
You can use the following function.
The base was from the P/Invoke folk, but has been adapted and enhanced by me.
P/Invoke base: https://pinvoke.net/default.aspx/wtsapi32.WTSEnumerateSessions
Function
This code is a simple and high-level function.
Meanwhile I also created an enhanced version, published here:
https://gist.github.com/swbbl/205694b7e1bdf09e74f25800194d5bcd
function Get-ComputerSession {
[CmdletBinding()]
param(
# The computer name to get the current sessions from.
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[AllowEmptyString()]
[String]
$ComputerName
)
begin {
# this script is based on the PS code example for WTSEnumerateSessions from:
# https://pinvoke.net/default.aspx/wtsapi32.WTSEnumerateSessions
# but has many adaptions and enhancements.
# thanks for the input!
Add-Type -TypeDefinition #'
using System;
using System.Runtime.InteropServices;
using System.ComponentModel;
namespace WTS
{
public class API {
// https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsopenserverexw
[DllImport("wtsapi32.dll", SetLastError=true, CharSet = CharSet.Unicode)]
public static extern IntPtr WTSOpenServerEx(string pServerName);
// https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtscloseserver
[DllImport("wtsapi32.dll", SetLastError=true, CharSet = CharSet.Unicode)]
public static extern void WTSCloseServer(System.IntPtr hServer);
// https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsenumeratesessionsexw
[DllImport("wtsapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern int WTSEnumerateSessionsEx(
System.IntPtr hServer,
ref int pLevel,
int Filter,
ref System.IntPtr ppSessionInfo,
ref int pCount);
[DllImport("wtsapi32.dll", SetLastError=true)]
public static extern int WTSQuerySessionInformationW(
System.IntPtr hServer,
int SessionId,
int WTSInfoClass ,
ref System.IntPtr ppSessionInfo,
ref int pBytesReturned );
// https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsfreememoryexw
[DllImport("wtsapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool WTSFreeMemoryEx(
WTS_TYPE_CLASS WTSTypeClass,
IntPtr pMemory,
UInt32 NumberOfEntries);
}
// https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/ns-wtsapi32-wts_session_info_1w
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WTS_SESSION_INFO_1
{
public int ExecEnvId;
public WTS_CONNECTSTATE_CLASS State;
public UInt32 SessionId;
public String SessionName;
public String HostName;
public String UserName;
public String DomainName;
public String FarmName;
}
public enum WTS_TYPE_CLASS
{
TypeProcessInfoLevel0,
TypeProcessInfoLevel1,
TypeSessionInfoLevel1
}
// https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/ne-wtsapi32-wts_connectstate_class
public enum WTS_CONNECTSTATE_CLASS
{
[Description("A user is logged on to the WinStation. This state occurs when a user is signed in and actively connected to the device.")]
Active,
[Description("The WinStation is connected to the client.")]
Connected,
[Description("The WinStation is in the process of connecting to the client.")]
ConnectQuery,
[Description("The WinStation is shadowing another WinStation.")]
Shadow,
[Description("The WinStation is active but the client is disconnected. This state occurs when a user is signed in but not actively connected to the device, such as when the user has chosen to exit to the lock screen.")]
Disconnected,
[Description("The WinStation is waiting for a client to connect.")]
Idle,
[Description("The WinStation is listening for a connection. A listener session waits for requests for new client connections. No user is logged on a listener session. A listener session cannot be reset, shadowed, or changed to a regular client session.")]
Listen,
[Description("The WinStation is being reset.")]
Reset,
[Description("The WinStation is down due to an error.")]
Down,
[Description("The WinStation is initializing.")]
Init
}
}
'#
$wtsSessionDataSize = [System.Runtime.InteropServices.Marshal]::SizeOf([System.Type][WTS.WTS_SESSION_INFO_1])
}
process {
[UInt32] $pLevel = 1 # for a reserved parameter. must be always 1
[UInt32] $pCount = 0
[IntPtr] $ppSessionInfo = 0
try {
[IntPtr] $wtsServerHandle = [WTS.API]::WTSOpenServerEx($ComputerName)
if (-not $wtsServerHandle) {
throw [System.ComponentModel.Win32Exception]::new([Runtime.InteropServices.Marshal]::GetLastWin32Error())
}
[bool] $wtsSessionsCheck = [WTS.API]::WTSEnumerateSessionsEx($wtsServerHandle, [ref] $pLevel, [UInt32] 0, [ref] $ppSessionInfo, [ref] $pCount)
if (-not $wtsSessionsCheck) {
throw [System.ComponentModel.Win32Exception]::new([Runtime.InteropServices.Marshal]::GetLastWin32Error())
}
for ($i = 0; $i -lt $pCount; $i++) {
$wtsSessionInfoOffset = $wtsSessionDataSize * $i
[System.Runtime.InteropServices.Marshal]::PtrToStructure([IntPtr]::Add($ppSessionInfo, $wtsSessionInfoOffset), [type][WTS.WTS_SESSION_INFO_1])
}
} catch {
Write-Error -ErrorRecord $_
} finally {
try {
$wtsSessionInfoFreeMemCheck = [WTS.API]::WTSFreeMemoryEx([WTS.WTS_TYPE_CLASS]::TypeSessionInfoLevel1, $ppSessionInfo, $pCount)
if (-not $wtsSessionInfoFreeMemCheck) {
throw [System.ComponentModel.Win32Exception]::new([Runtime.InteropServices.Marshal]::GetLastWin32Error())
}
} finally {
$ppSessionInfo = [IntPtr]::Zero
[WTS.API]::WTSCloseServer($wtsServerHandle)
}
}
}
}
How to use
Using the function without the parameter ComputerName will ask your for a name.
Just hit [ENTER] if you want to get the sessions of the local computer/server or provide an empty string for the parameter ComputerName
Example
For a local query.
PS> Get-ComputerSession -ComputerName ''
ExecEnvId : 0
State : Disconnected
SessionId : 0
SessionName : Services
HostName :
UserName :
DomainName :
FarmName :
ExecEnvId : 1
State : Active
SessionId : 1
SessionName : Console
HostName :
UserName : svenw
DomainName : SVEN-PC
FarmName :
ExecEnvId : 2
State : Disconnected
SessionId : 2
SessionName :
HostName :
UserName : Test
DomainName : Sven-PC
FarmName :
Each property is (more or less) explained here:
https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/ns-wtsapi32-wts_session_info_1w
Enhanced version
I've also created and uploaded a more enhanced version on gist:
https://gist.github.com/swbbl/205694b7e1bdf09e74f25800194d5bcd
Related
so I have an issue now with Windows Certificates. I have a list of certificates (identified by SerialNumber/Thumbprint) for which I have to set EnhancedKeyUsage
So what I would like to happen is for a certificate:
From code to "Enable only for following purposes" and only "Client Authentication" to be selected, basically I would like to set a specific certificate as client auth certificate.
So the end-result should be :
Now from what I've read only ( information is very limited) I found that I should use CertSetCertificateContextProperty from Crypt32.dll.
My code looks like this:
public async Task<bool> SetAuthKeyUsageExtension(string certThumbprint)
{
//open certificate store for Read/Write
using X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);
//find certificate
var certificate = FindCertificate(certThumbprint, store);
if (certificate != null)
{
//set EKU for Client Auth
SetKeyExtangeUsage(certificate);
return true;
}
return false;
}
private X509Certificate2 FindCertificate(string thumbPrint, X509Store store)
{
X509Certificate2Collection foundX509Certificates = store.Certificates.Find(X509FindType.FindByThumbprint, thumbPrint, false);
if (foundX509Certificates != null || foundX509Certificates.Count > 0)
{
return foundX509Certificates.FirstOrDefault();
}
return null;
}
My native class looks like this:
[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern Boolean CertSetCertificateContextProperty(
[In] IntPtr pCertContext,
[In] UInt32 dwPropId,
[In] UInt32 dwFlags,
[In] IntPtr pvData);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct CRYPT_OBJID_BLOB
{
public uint cbData;
public IntPtr pbData;
}
private const string OID_PKIX_KP_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2";
private const int CERT_ENHKEY_USAGE_PROP_ID = 9;
public static bool SetKeyExtangeUsage(X509Certificate2 cert)
{
//Create a new Oid collection
OidCollection oids = new OidCollection();
//add to collection
oids.Add(new Oid
{
Value = OID_PKIX_KP_CLIENT_AUTH
});
X509EnhancedKeyUsageExtension eku = new X509EnhancedKeyUsageExtension(oids, true);
//pbData
var pbData = Marshal.AllocHGlobal(eku.RawData.Length);
CRYPT_OBJID_BLOB objID = new CRYPT_OBJID_BLOB();
IntPtr pvData = Marshal.AllocHGlobal(Marshal.SizeOf(objID));
objID.pbData = pbData;
objID.cbData = (uint)eku.RawData.Length;
Marshal.StructureToPtr(objID, pvData, false);
// var result = CertSetCertificateContextProperty(cert.Handle, CERT_ENHKEY_USAGE_PROP_ID, 0, objID.pbData);
var result = CertSetCertificateContextProperty(cert.Handle, CERT_ENHKEY_USAGE_PROP_ID, 0, pvData);
Marshal.FreeHGlobal(objID.pbData);
Marshal.FreeHGlobal(pvData);
return true;
}
This code "works" in terms that it does not break or throw any errors, but when I check the certificate using the UI, no Extended Key Usage are changed, basically it looks like I did nothing. I am sure I am missing something, but I have very few experience with X509Certificate2 and also Interop so I am guessing that somewhere in SetKeyExtangeUsage I am missing something. 5
I used this How to set certificate purposes? as reference, but there is no working code there, only steps, which I think I followed.
Any ideas, what I am missing?
NEDIT: Now running the same code I get this error:
"Attempted to read or write protected memory. This is often an indication that other memory is corrupt."
EDIT2: Changed pbData to pvData as mentioned.
EDIT3: Changed function
public static bool SetClientAuthEKU(X509Certificate2 cert)
{
OidCollection oids = new OidCollection();
oids.Add(new Oid
{
Value = OID_PKIX_KP_CLIENT_AUTH
});
X509EnhancedKeyUsageExtension eku = new X509EnhancedKeyUsageExtension(oids, true);
CRYPT_OBJID_BLOB objID = new CRYPT_OBJID_BLOB();
//allocate space in memory
IntPtr pbData = Marshal.AllocHGlobal(eku.RawData.Length);
IntPtr pvData = Marshal.AllocHGlobal(Marshal.SizeOf(objID));
//copy eku raw data into pbData
Marshal.Copy(eku.RawData, 0, pbData, eku.RawData.Length);
//set CRYPT_OBJECT value with ekuRaw data and Length
objID.pbData = pbData;
objID.cbData = (uint)eku.RawData.Length;
//copy CRYPT OBJECT into IntPtr
Marshal.StructureToPtr(objID, pvData, false);
var result = CertSetCertificateContextProperty(cert.Handle, CERT_ENHKEY_USAGE_PROP_ID, 0, pvData);
Marshal.FreeHGlobal(objID.pbData);
Marshal.FreeHGlobal(pvData);
return true;
}
EDIT4:
The problem is that you pass actual raw data (without length indicator) to CertSetCertificateContextProperty function, while it must be a pointer to CRYPTOAPI_BLOB. That is, last parameter of CertSetCertificateContextProperty function must be pvData.
The mistake can be easily detected by finding usages of pvData in your code. You write structure into pvData pointer, but the pointer is not used anywhere in the code.
Update:
I just noticed that you don't put anything in pbData. You allocate the buffer, but don't write anything there. You have to copy extension's raw data into pbData buffer.
I love that Windows 10 now has support for virtual desktops built in, but I have some features that I'd like to add/modify (e.g., force a window to appear on all desktops, launch the task view with a hotkey, have per-monitor desktops, etc.)
I have searched for applications and developer references to help me customize my desktops, but I have had no luck.
Where should I start? I am looking for Windows API functions (ideally, that are callable from a C# application) that will give me programmatic access to manipulate virtual desktops and the windows therein.
The Windows SDK Support Team Blog posted a C# demo to switch Desktops via IVirtualDesktopManager:
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("a5cd92ff-29be-454c-8d04-d82879fb3f1b")]
[System.Security.SuppressUnmanagedCodeSecurity]
public interface IVirtualDesktopManager
{
[PreserveSig]
int IsWindowOnCurrentVirtualDesktop(
[In] IntPtr TopLevelWindow,
[Out] out int OnCurrentDesktop
);
[PreserveSig]
int GetWindowDesktopId(
[In] IntPtr TopLevelWindow,
[Out] out Guid CurrentDesktop
);
[PreserveSig]
int MoveWindowToDesktop(
[In] IntPtr TopLevelWindow,
[MarshalAs(UnmanagedType.LPStruct)]
[In]Guid CurrentDesktop
);
}
[ComImport, Guid("aa509086-5ca9-4c25-8f95-589d3c07b48a")]
public class CVirtualDesktopManager
{
}
public class VirtualDesktopManager
{
public VirtualDesktopManager()
{
cmanager = new CVirtualDesktopManager();
manager = (IVirtualDesktopManager)cmanager;
}
~VirtualDesktopManager()
{
manager = null;
cmanager = null;
}
private CVirtualDesktopManager cmanager = null;
private IVirtualDesktopManager manager;
public bool IsWindowOnCurrentVirtualDesktop(IntPtr TopLevelWindow)
{
int result;
int hr;
if ((hr = manager.IsWindowOnCurrentVirtualDesktop(TopLevelWindow, out result)) != 0)
{
Marshal.ThrowExceptionForHR(hr);
}
return result != 0;
}
public Guid GetWindowDesktopId(IntPtr TopLevelWindow)
{
Guid result;
int hr;
if ((hr = manager.GetWindowDesktopId(TopLevelWindow, out result)) != 0)
{
Marshal.ThrowExceptionForHR(hr);
}
return result;
}
public void MoveWindowToDesktop(IntPtr TopLevelWindow, Guid CurrentDesktop)
{
int hr;
if ((hr = manager.MoveWindowToDesktop(TopLevelWindow, CurrentDesktop)) != 0)
{
Marshal.ThrowExceptionForHR(hr);
}
}
}
it includes the API to detect on which desktop the Window is shown and it can switch and move a Windows the a Desktop.
Programmatic access to the virtual desktop feature is very limited, as Microsoft has only exposed the IVirtualDesktopManager COM interface. It does provide two key functions:
IVirtualDesktopManager::GetWindowDesktopId allows you to retrieve the ID of a virtual desktop, based on a window that is already assigned to that desktop.
IVirtualDesktopManager::MoveWindowToDesktop allows you to move a window to a specific virtual desktop.
Unfortunately, this is not nearly enough to accomplish anything useful. I've written some C# code based on the reverse-engineering work done by NickoTin. I can't read much of the Russian in his blog post, but his C++ code was pretty accurate.
I do need to emphasize that this code is not something you want to commit to in a product. Microsoft always feels free to change undocumented APIs whenever they feel like it. And there is a runtime risk as well: this code does not necessarily interact well when the user is tinkering with the virtual desktops. Always keep in mind that a virtual desktop can appear and disappear at any time, completely out of sync with your code.
To use the code, create a new C# class library project. I'll first post ComInterop.cs, it contains the COM interface declarations that match NickoTin's C++ declarations:
using System;
using System.Runtime.InteropServices;
namespace Windows10Interop {
internal static class Guids {
public static readonly Guid CLSID_ImmersiveShell =
new Guid(0xC2F03A33, 0x21F5, 0x47FA, 0xB4, 0xBB, 0x15, 0x63, 0x62, 0xA2, 0xF2, 0x39);
public static readonly Guid CLSID_VirtualDesktopManagerInternal =
new Guid(0xC5E0CDCA, 0x7B6E, 0x41B2, 0x9F, 0xC4, 0xD9, 0x39, 0x75, 0xCC, 0x46, 0x7B);
public static readonly Guid CLSID_VirtualDesktopManager =
new Guid("AA509086-5CA9-4C25-8F95-589D3C07B48A");
public static readonly Guid IID_IVirtualDesktopManagerInternal =
new Guid("AF8DA486-95BB-4460-B3B7-6E7A6B2962B5");
public static readonly Guid IID_IVirtualDesktop =
new Guid("FF72FFDD-BE7E-43FC-9C03-AD81681E88E4");
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("FF72FFDD-BE7E-43FC-9C03-AD81681E88E4")]
internal interface IVirtualDesktop {
void notimpl1(); // void IsViewVisible(IApplicationView view, out int visible);
Guid GetId();
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("AF8DA486-95BB-4460-B3B7-6E7A6B2962B5")]
internal interface IVirtualDesktopManagerInternal {
int GetCount();
void notimpl1(); // void MoveViewToDesktop(IApplicationView view, IVirtualDesktop desktop);
void notimpl2(); // void CanViewMoveDesktops(IApplicationView view, out int itcan);
IVirtualDesktop GetCurrentDesktop();
void GetDesktops(out IObjectArray desktops);
[PreserveSig]
int GetAdjacentDesktop(IVirtualDesktop from, int direction, out IVirtualDesktop desktop);
void SwitchDesktop(IVirtualDesktop desktop);
IVirtualDesktop CreateDesktop();
void RemoveDesktop(IVirtualDesktop desktop, IVirtualDesktop fallback);
IVirtualDesktop FindDesktop(ref Guid desktopid);
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("a5cd92ff-29be-454c-8d04-d82879fb3f1b")]
internal interface IVirtualDesktopManager {
int IsWindowOnCurrentVirtualDesktop(IntPtr topLevelWindow);
Guid GetWindowDesktopId(IntPtr topLevelWindow);
void MoveWindowToDesktop(IntPtr topLevelWindow, ref Guid desktopId);
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("92CA9DCD-5622-4bba-A805-5E9F541BD8C9")]
internal interface IObjectArray {
void GetCount(out int count);
void GetAt(int index, ref Guid iid, [MarshalAs(UnmanagedType.Interface)]out object obj);
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("6D5140C1-7436-11CE-8034-00AA006009FA")]
internal interface IServiceProvider10 {
[return: MarshalAs(UnmanagedType.IUnknown)]
object QueryService(ref Guid service, ref Guid riid);
}
}
Next is Desktop.cs. It contains the friendly C# classes that you can use in your code:
using System;
using System.Runtime.InteropServices;
namespace Windows10Interop
{
public class Desktop {
public static int Count {
// Returns the number of desktops
get { return DesktopManager.Manager.GetCount(); }
}
public static Desktop Current {
// Returns current desktop
get { return new Desktop(DesktopManager.Manager.GetCurrentDesktop()); }
}
public static Desktop FromIndex(int index) {
// Create desktop object from index 0..Count-1
return new Desktop(DesktopManager.GetDesktop(index));
}
public static Desktop FromWindow(IntPtr hWnd) {
// Creates desktop object on which window <hWnd> is displayed
Guid id = DesktopManager.WManager.GetWindowDesktopId(hWnd);
return new Desktop(DesktopManager.Manager.FindDesktop(ref id));
}
public static Desktop Create() {
// Create a new desktop
return new Desktop(DesktopManager.Manager.CreateDesktop());
}
public void Remove(Desktop fallback = null) {
// Destroy desktop and switch to <fallback>
var back = fallback == null ? DesktopManager.GetDesktop(0) : fallback.itf;
DesktopManager.Manager.RemoveDesktop(itf, back);
}
public bool IsVisible {
// Returns <true> if this desktop is the current displayed one
get { return object.ReferenceEquals(itf, DesktopManager.Manager.GetCurrentDesktop()); }
}
public void MakeVisible() {
// Make this desktop visible
DesktopManager.Manager.SwitchDesktop(itf);
}
public Desktop Left {
// Returns desktop at the left of this one, null if none
get {
IVirtualDesktop desktop;
int hr = DesktopManager.Manager.GetAdjacentDesktop(itf, 3, out desktop);
if (hr == 0) return new Desktop(desktop);
else return null;
}
}
public Desktop Right {
// Returns desktop at the right of this one, null if none
get {
IVirtualDesktop desktop;
int hr = DesktopManager.Manager.GetAdjacentDesktop(itf, 4, out desktop);
if (hr == 0) return new Desktop(desktop);
else return null;
}
}
public void MoveWindow(IntPtr handle) {
// Move window <handle> to this desktop
DesktopManager.WManager.MoveWindowToDesktop(handle, itf.GetId());
}
public bool HasWindow(IntPtr handle) {
// Returns true if window <handle> is on this desktop
return itf.GetId() == DesktopManager.WManager.GetWindowDesktopId(handle);
}
public override int GetHashCode() {
return itf.GetHashCode();
}
public override bool Equals(object obj) {
var desk = obj as Desktop;
return desk != null && object.ReferenceEquals(this.itf, desk.itf);
}
private IVirtualDesktop itf;
private Desktop(IVirtualDesktop itf) { this.itf = itf; }
}
internal static class DesktopManager {
static DesktopManager() {
var shell = (IServiceProvider10)Activator.CreateInstance(Type.GetTypeFromCLSID(Guids.CLSID_ImmersiveShell));
Manager = (IVirtualDesktopManagerInternal)shell.QueryService(Guids.CLSID_VirtualDesktopManagerInternal, Guids.IID_IVirtualDesktopManagerInternal);
WManager = (IVirtualDesktopManager)Activator.CreateInstance(Type.GetTypeFromCLSID(Guids.CLSID_VirtualDesktopManager));
}
internal static IVirtualDesktop GetDesktop(int index) {
int count = Manager.GetCount();
if (index < 0 || index >= count) throw new ArgumentOutOfRangeException("index");
IObjectArray desktops;
Manager.GetDesktops(out desktops);
object objdesk;
desktops.GetAt(index, Guids.IID_IVirtualDesktop, out objdesk);
Marshal.ReleaseComObject(desktops);
return (IVirtualDesktop)objdesk;
}
internal static IVirtualDesktopManagerInternal Manager;
internal static IVirtualDesktopManager WManager;
}
}
And finally a little test WinForms project that I used to test the code. Just drop 4 buttons on a form and name them buttonLeft/Right/Create/Destroy:
using Windows10Interop;
using System.Diagnostics;
...
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void buttonRight_Click(object sender, EventArgs e) {
var curr = Desktop.FromWindow(this.Handle);
Debug.Assert(curr.Equals(Desktop.Current));
var right = curr.Right;
if (right == null) right = Desktop.FromIndex(0);
if (right != null) {
right.MoveWindow(this.Handle);
right.MakeVisible();
this.BringToFront();
Debug.Assert(right.IsVisible);
}
}
private void buttonLeft_Click(object sender, EventArgs e) {
var curr = Desktop.FromWindow(this.Handle);
Debug.Assert(curr.Equals(Desktop.Current));
var left = curr.Left;
if (left == null) left = Desktop.FromIndex(Desktop.Count - 1);
if (left != null) {
left.MoveWindow(this.Handle);
left.MakeVisible();
this.BringToFront();
Debug.Assert(left.IsVisible);
}
}
private void buttonCreate_Click(object sender, EventArgs e) {
var desk = Desktop.Create();
desk.MoveWindow(this.Handle);
desk.MakeVisible();
Debug.Assert(desk.IsVisible);
Debug.Assert(desk.Equals(Desktop.Current));
}
private void buttonDestroy_Click(object sender, EventArgs e) {
var curr = Desktop.FromWindow(this.Handle);
var next = curr.Left;
if (next == null) next = curr.Right;
if (next != null && next != curr) {
next.MoveWindow(this.Handle);
curr.Remove(next);
Debug.Assert(next.IsVisible);
}
}
}
The only real quirk I noticed while testing this is that moving a window from one desktop to another can move it to the bottom of the Z-order when you first switch the desktop, then move the window. No such problem if you do it the other way around.
There is this guy that made a application to map keyboard shorcut to move a window between virtual desktop.
https://github.com/Grabacr07/SylphyHorn
(I use it every day )
He has a blog where he explain what he did
http://grabacr.net/archives/5701 ( you can use google translate it is in japanese)
He in fact used the same api mantionned in the Alberto Tostado response.
http://www.cyberforum.ru/blogs/105416/blog3671.html
and the api can be found on his github https://github.com/Grabacr07/VirtualDesktop
The api is really simple to use BUT it seems impossible to move a window from another process.
public static bool MoveToDesktop(IntPtr hWnd, VirtualDesktop virtualDesktop)
{
ThrowIfNotSupported();
int processId;
NativeMethods.GetWindowThreadProcessId(hWnd, out processId);
if (Process.GetCurrentProcess().Id == processId) // THAT LINE
{
var guid = virtualDesktop.Id;
VirtualDesktop.ComManager.MoveWindowToDesktop(hWnd, ref guid);
return true;
}
return false;
}
To workaround this problem they made another implementation that they use alongside the one in the russian blog
if (VirtualDesktopHelper.MoveToDesktop(hWnd, right) //<- the one in the russian blog
|| this.helper.MoveWindowToDesktop(hWnd, right.Id)) <- the second implementation
The second implementation can be found here: https://github.com/tmyt/VDMHelper
This one can move a window from another process to another desktop. BUT it is buggy right now. For exemple when i try to move some window like google chrome it crash.
So this is the result of my research. I m rigth now trying to make a StickyWindow feature with these api.
I fear that all about "Virtual desktops" in Windows 10 is undocumented, but in a Russian page I've seen documented the interfaces. I don't speak Russian but seems that they have used reversed engineering. Anyway, the code is very clear (Thanks to them!).
Keep an eye here:
http://www.cyberforum.ru/blogs/105416/blog3671.html
I've been trying to see if the old API's CreateDesktop, OpenDesktop, etc... is linked to the new Virtual-Desktops, but no way...
The interfaces work with the final production release of Windows 10 (2015-05-08), but you shouldn't use them in a real wide distributed application until Microsoft documents them. Too much risk.
Regards.
Is there any way to access all WiFi access points and their respective RSSI values using .NET? It would be really nice if I could do it without using unmanaged code or even better if it worked in mono as well as .NET.
If it is possible i would appriciate a code sample.
Thanks
Here are a few similiar stackoverflow questions i found:
-Get SSID of the wireless network I am connected to with C# .Net on Windows Vista
-Managing wireless network connection in C#
-Get BSSID (MAC address) of wireless access point from C#
It is a wrapper project with managed code in c# at http://www.codeplex.com/managedwifi
It supports Windows Vista and XP SP2 (or later version).
sample code:
using NativeWifi;
using System;
using System.Text;
namespace WifiExample
{
class Program
{
/// <summary>
/// Converts a 802.11 SSID to a string.
/// </summary>
static string GetStringForSSID(Wlan.Dot11Ssid ssid)
{
return Encoding.ASCII.GetString( ssid.SSID, 0, (int) ssid.SSIDLength );
}
static void Main( string[] args )
{
WlanClient client = new WlanClient();
foreach ( WlanClient.WlanInterface wlanIface in client.Interfaces )
{
// Lists all networks with WEP security
Wlan.WlanAvailableNetwork[] networks = wlanIface.GetAvailableNetworkList( 0 );
foreach ( Wlan.WlanAvailableNetwork network in networks )
{
if ( network.dot11DefaultCipherAlgorithm == Wlan.Dot11CipherAlgorithm.WEP )
{
Console.WriteLine( "Found WEP network with SSID {0}.", GetStringForSSID(network.dot11Ssid));
}
}
// Retrieves XML configurations of existing profiles.
// This can assist you in constructing your own XML configuration
// (that is, it will give you an example to follow).
foreach ( Wlan.WlanProfileInfo profileInfo in wlanIface.GetProfiles() )
{
string name = profileInfo.profileName; // this is typically the network's SSID
string xml = wlanIface.GetProfileXml( profileInfo.profileName );
}
// Connects to a known network with WEP security
string profileName = "Cheesecake"; // this is also the SSID
string mac = "52544131303235572D454137443638";
string key = "hello";
string profileXml = string.Format("<?xml version=\"1.0\"?><WLANProfile xmlns=\"http://www.microsoft.com/networking/WLAN/profile/v1\"><name>{0}</name><SSIDConfig><SSID><hex>{1}</hex><name>{0}</name></SSID></SSIDConfig><connectionType>ESS</connectionType><MSM><security><authEncryption><authentication>open</authentication><encryption>WEP</encryption><useOneX>false</useOneX></authEncryption><sharedKey><keyType>networkKey</keyType><protected>false</protected><keyMaterial>{2}</keyMaterial></sharedKey><keyIndex>0</keyIndex></security></MSM></WLANProfile>", profileName, mac, key);
wlanIface.SetProfile( Wlan.WlanProfileFlags.AllUser, profileXml, true );
wlanIface.Connect( Wlan.WlanConnectionMode.Profile, Wlan.Dot11BssType.Any, profileName );
}
}
}
}
If the platform is Windows10, you can use Microsoft.Windows.SDK.Contracts package to access all available wifis.
First, install Microsoft.Windows.SDK.Contracts package from nuget.
Then, you can use next code to get ssid and signal strength.
var adapters = await WiFiAdapter.FindAllAdaptersAsync();
foreach (var adapter in adapters)
{
foreach (var network in adapter.NetworkReport.AvailableNetworks)
{
Console.WriteLine($"ssid: {network.Ssid}");
Console.WriteLine($"signal strength: {network.SignalBars}");
}
}
Use Native Wifi APIs, present on all Vista and XP SP3 systems. XP SP2 has a different API with which you can do the same thing.
How to enumerate networks
How to get signal strength
I am doing it running a command netsh wlan show networks mode=bssid from C# code.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace ConsoleApp2
{
class AccessPoint
{
public string SSID { get; set; }
public string BSSID { get; set; }
public byte Signal { get; set; }
}
class Program
{
private static async Task Main(string[] args)
{
var apList = await GetSignalOfNetworks();
foreach (var ap in apList)
{
WriteLine($"{ap.BSSID} - {ap.Signal} - {ap.SSID}");
}
Console.ReadKey();
}
private static async Task<AccessPoint[]> GetSignalOfNetworks()
{
string result = await ExecuteProcessAsync(#"C:\Windows\System32\netsh.exe", "wlan show networks mode=bssid");
return Regex.Split(result, #"[^B]SSID \d+").Skip(1).SelectMany(network => GetAccessPointFromNetwork(network)).ToArray();
}
private static AccessPoint[] GetAccessPointFromNetwork(string network)
{
string withoutLineBreaks = Regex.Replace(network, #"[\r\n]+", " ").Trim();
string ssid = Regex.Replace(withoutLineBreaks, #"^:\s+(\S+).*$", "$1").Trim();
return Regex.Split(withoutLineBreaks, #"\s{4}BSSID \d+").Skip(1).Select(ap => GetAccessPoint(ssid, ap)).ToArray();
}
private static AccessPoint GetAccessPoint(string ssid, string ap)
{
string withoutLineBreaks = Regex.Replace(ap, #"[\r\n]+", " ").Trim();
string bssid = Regex.Replace(withoutLineBreaks, #"^:\s+([a-f0-9]{2}(:[a-f0-9]{2}){5}).*$", "$1").Trim();
byte signal = byte.Parse(Regex.Replace(withoutLineBreaks, #"^.*(Signal|Sinal)\s+:\s+(\d+)%.*$", "$2").Trim());
return new AccessPoint
{
SSID = ssid,
BSSID = bssid,
Signal = signal,
};
}
private static async Task<string> ExecuteProcessAsync(string cmd, string args = null)
{
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = cmd,
Arguments = args,
RedirectStandardInput = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8,
}
};
process.Start();
string result = await process.StandardOutput.ReadToEndAsync();
process.WaitForExit();
#if DEBUG
if (result.Trim().Contains("The Wireless AutoConfig Service (wlansvc) is not running."))
{
return await GetFakeData();
}
#endif
return result;
}
private static async Task<string> GetFakeData()
{
var assembly = Assembly.GetExecutingAssembly();
var resourceName = "ConsoleApp2.FakeData.txt";
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
using (StreamReader reader = new StreamReader(stream))
{
return await reader.ReadToEndAsync();
}
}
private static void WriteLine(string str)
{
Console.WriteLine(str);
}
}
}
You might be able to achieve it using WMI queries. Take a look at this thread.
If you are using vista wmi does not work with all network adapters, another alternative for vista is to use the netsh command. Have a look at this codeproject article.
I found the other answers very useful and wanted to see how they compared with each other. Here's some LINQ code which gets the data from each and joins them. Here's a LINQPad script which contains all the nuget dependencies and usings: http://share.linqpad.net/7pxls3.linq
var adapters = await WiFiAdapter.FindAllAdaptersAsync();
var wifiAdapterResults = adapters
.SelectMany(a => a.NetworkReport.AvailableNetworks.Select(a => new
{
a.Ssid,
a.Bssid,
Ghz = Math.Round(a.ChannelCenterFrequencyInKilohertz / 1000000.0, 1),
a.NetworkRssiInDecibelMilliwatts,
a.SignalBars,
a.PhyKind,
}))
.Where(n => n.Ssid != null)
.OrderByDescending(n => n.NetworkRssiInDecibelMilliwatts);
WlanClient client = new WlanClient();
var wlanResults = client.Interfaces.SelectMany(i => i.GetNetworkBssList().Select(n => new
{
SSID = new string(Encoding.ASCII.GetChars(n.dot11Ssid.SSID, 0, (int)n.dot11Ssid.SSIDLength)),
n.linkQuality,
n.rssi,
MAC = BitConverter.ToString(n.dot11Bssid).Replace('-', ':').ToLowerInvariant(),
}))
.OrderByDescending(n => n.rssi);
wifiAdapterResults.Join(wlanResults, r => r.Bssid, r => r.MAC, (r1, r2) => Util.Merge(r1, r2)).Dump();
The Util.Merge function is a LINQPad concept, if you want to run outside of LINQPad you'll have to create the merged object yourself.
The built-in WinRT WifiAdapter is easier to call, but doesn't have a nice link quality value, only the very granular SignalBars and the rssi. The ManagedWifi library on the other hand has a 1-100 linkQuality value which seems more useful.
Hope someone else finds this useful!
I found another way to do it, although it does cost some money.
There is a .NET lib available at rawether.net that lets you get at the ethernet drivers.
I would like to track changes to a registry key, for instance addition/removal of a subkey, addition/removal/edition of a value. How could I create an IObservable sequence that exposes these changes?
One way is to p/invoke the RegNotifyChangeKeyValue, a Win32 function which notifies the caller about changes to the attributes or contents of a specified registry key. This function sets an event whenever it detects a change. Note that it it must be called on a persistent thread, otherwise it will signal whenever the thread exits (even though no change happened). See below for a possible implementation of this with Rx.Net.
using System;
using System.ComponentModel;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Win32;
public class RegistryMonitoringOperations
{
[Flags]
public enum RegChangeNotifyFilter
{
/// <summary>Notify the caller if a subkey is added or deleted.</summary>
Key = 1,
/// <summary>Notify the caller of changes to the attributes of the key,
/// such as the security descriptor information.</summary>
Attribute = 2,
/// <summary>Notify the caller of changes to a value of the key. This can
/// include adding or deleting a value, or changing an existing value.</summary>
Value = 4,
/// <summary>Notify the caller of changes to the security descriptor
/// of the key.</summary>
Security = 8
}
private const int KeyQueryValue = 0x0001;
private const int KeyNotify = 0x0010;
private const int StandardRightsRead = 0x00020000;
public static IObservable<Unit> CreateKeyValuesChangedObservable(
RegistryHive hive,
string subKey,
RegChangeNotifyFilter filter,
IScheduler registrationScheduler)
{
return Observable.Create<Unit>(
obs =>
{
try
{
var key = OpenKey(hive, subKey);
return new CompositeDisposable(
CreateKeyValuesChangedObservable(key, filter).SubscribeOn(registrationScheduler).Subscribe(obs),
Disposable.Create(() => RegCloseKey(key)));
}
catch (Win32Exception e)
{
obs.OnError(e);
return Disposable.Empty;
}
});
}
private static IDisposable SetCallbackWhenSignalled(WaitHandle waitObject, Action action)
{
var registeredWait = ThreadPool.RegisterWaitForSingleObject(waitObject, (s, t) => action(), null, -1, true);
return Disposable.Create(() => registeredWait.Unregister(null));
}
private static IObservable<Unit> CreateKeyValuesChangedObservable(IntPtr key, RegChangeNotifyFilter filter)
{
return Observable.Create<Unit>(
obs =>
{
var eventNotify = new AutoResetEvent(false);
var result = RegNotifyChangeKeyValue(key, true, filter, eventNotify.SafeWaitHandle.DangerousGetHandle(), true);
if (result != 0)
{
obs.OnError(new Win32Exception(Marshal.GetLastWin32Error()));
}
return new CompositeDisposable(
eventNotify,
SetCallbackWhenSignalled(
eventNotify,
() =>
{
obs.OnNext(Unit.Default);
obs.OnCompleted();
}));
}).Repeat();
}
private static IntPtr OpenKey(RegistryHive hive, string subKey)
{
IntPtr registryKey;
var result = RegOpenKeyEx((int)hive, subKey, 0, StandardRightsRead | KeyQueryValue | KeyNotify, out registryKey);
if (result != 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
return registryKey;
}
Here's a typical usage of this function:
RegistryMonitoringOperations.CreateKeyValuesChangedObservable(
RegistryHive.LocalMachine,
"somepath",
RegistryMonitoringOperations.RegChangeNotifyFilter.Value,
DispatcherScheduler.Instance)
As you can see above, one way to avoid dedicating a thread for calling this function is to use the UI thread which is persistent (so in rx terms, using the dispatcher scheduler). RegNotifyChangeKeyValue returns immediatly when in asynchronous mode so it won't block the UI.
I am trying to get the IP address of networks like Wi-Fi,Data Network. I use the following class to find the IP.
public class MyIPAddress
{
Action<IPAddress> FoundCallback;
UdpAnySourceMulticastClient MulticastSocket;
const int PortNumber = 50000; // pick a number, any number
string MulticastMessage = "FIND-MY-IP-PLEASE" + new Random().Next().ToString();
public void Find(Action<IPAddress> callback)
{
FoundCallback = callback;
MulticastSocket = new UdpAnySourceMulticastClient(IPAddress.Parse("239.255.255.250"), PortNumber);
MulticastSocket.BeginJoinGroup((result) =>
{
try
{
MulticastSocket.EndJoinGroup(result);
GroupJoined(result);
}
catch (Exception ex)
{
// Debug.WriteLine("EndjoinGroup exception {0}", ex.Message);
// This can happen eg when wifi is off
FoundCallback(null);
}
},
null);
}
void callback_send(IAsyncResult result)
{
}
byte[] MulticastData;
bool keepsearching;
void GroupJoined(IAsyncResult result)
{
MulticastData = Encoding.UTF8.GetBytes(MulticastMessage);
keepsearching = true;
MulticastSocket.BeginSendToGroup(MulticastData, 0, MulticastData.Length, callback_send, null);
while (keepsearching)
{
try
{
byte[] buffer = new byte[MulticastData.Length];
MulticastSocket.BeginReceiveFromGroup(buffer, 0, buffer.Length, DoneReceiveFromGroup, buffer);
}
catch (Exception ex)
{
// Debug.WriteLine("Stopped Group read due to " + ex.Message);
keepsearching = false;
}
}
}
void DoneReceiveFromGroup(IAsyncResult result)
{
string str = "";
IPEndPoint where;
int responselength = MulticastSocket.EndReceiveFromGroup(result, out where);
byte[] buffer = result.AsyncState as byte[];
if (responselength == MulticastData.Length && buffer.SequenceEqual(MulticastData))
{
str = where.Address.ToString();
keepsearching = false;
FoundCallback(where.Address);
}
Console.WriteLine(str);
}
}
I was successful to find out the IP address of connected Wi-Fi. I turn off Wi-Fi and turn on the Data Connection. I am not able to get the IP address of connected network. I got the error ** a socket operation encountered a dead network**. I have also refer this question A socket operation encountered a dead network. How can I solve this problem ?
Question is a bit old, but answer may be useful for someone:
You get this error, because your MyIPAddress class can only find a local IP (the address inside your internal WiFi network, behind router). To get an external IP address you should call an external server that will tell you your IP (eg. whatismyip.com).