Windows 2000 and later versions provides set of API that supports creating, opening
and switching desktops. Quick look at this link will provide you those functions
and details about them:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms687107(v=vs.85).aspx
Few days back, I developed one kiosk application which was supposed to run on Windows
8.1 in full screen mode at public kiosk machines. One of the requirement was
to load the application in it’s desktop that will not show any taskbar or side
bar and access to any other OS’s functions. Quick search on Bing landed me to
this CodeProject article which gave me managed wrapper over this unmanaged APIs to access
various functions easily.
I borrowed some code from this article and made my simple DesktopAPI class as following:
using System;
using System.Runtime.InteropServices;
namespace TestLauncher
{
public class DesktopAPI
{
#region Constants
private const uint INFINITE = 0xFFFFFFFF;
private const int NORMAL_PRIORITY_CLASS = 0x00000020;
#endregion
#region Enums
private enum DESKTOP_ACCESS : uint
{
DESKTOP_NONE = 0,
DESKTOP_READOBJECTS = 0x0001,
DESKTOP_CREATEWINDOW = 0x0002,
DESKTOP_CREATEMENU = 0x0004,
DESKTOP_HOOKCONTROL = 0x0008,
DESKTOP_JOURNALRECORD = 0x0010,
DESKTOP_JOURNALPLAYBACK = 0x0020,
DESKTOP_ENUMERATE = 0x0040,
DESKTOP_WRITEOBJECTS = 0x0080,
DESKTOP_SWITCHDESKTOP = 0x0100,
GENERIC_ALL =
DESKTOP_READOBJECTS | DESKTOP_CREATEWINDOW | DESKTOP_CREATEMENU |
DESKTOP_HOOKCONTROL
| DESKTOP_JOURNALRECORD | DESKTOP_JOURNALPLAYBACK | DESKTOP_ENUMERATE
| DESKTOP_WRITEOBJECTS
| DESKTOP_SWITCHDESKTOP,
}
#endregion
#region Public Methods and Operators
public static void ShowSecureDesktop()
{
IntPtr hOldDesktop = GetThreadDesktop(GetCurrentThreadId());
IntPtr hNewDesktop = CreateDesktop(
"TestAppDesktop",
IntPtr.Zero,
IntPtr.Zero,
0,
(uint)DESKTOP_ACCESS.GENERIC_ALL,
IntPtr.Zero);
SwitchDesktop(hNewDesktop);
SetThreadDesktop(hNewDesktop);
var si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "TestAppDesktop";
var pi = new PROCESS_INFORMATION();
CreateProcess(
null,
@"TestApp.exe",
IntPtr.Zero,
IntPtr.Zero,
true,
NORMAL_PRIORITY_CLASS,
IntPtr.Zero,
null,
ref si,
ref pi);
WaitForSingleObject(pi.hProcess, INFINITE);
SwitchDesktop(hOldDesktop);
CloseDesktop(hNewDesktop);
}
#endregion
#region Methods
[DllImport("user32.dll")]
private static extern bool CloseDesktop(IntPtr handle);
[DllImport("user32.dll")]
private static extern IntPtr CreateDesktop(
string lpszDesktop,
IntPtr lpszDevice,
IntPtr pDevmode,
int dwFlags,
uint dwDesiredAccess,
IntPtr lpsa);
[DllImport("kernel32.dll")]
private static extern bool CreateProcess(
string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
int dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
ref PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll")]
private static extern int GetCurrentThreadId();
[DllImport("user32.dll")]
private static extern IntPtr GetThreadDesktop(int dwThreadId);
[DllImport("user32.dll")]
private static extern bool SetThreadDesktop(IntPtr hDesktop);
[DllImport("user32.dll")]
private static extern bool SwitchDesktop(IntPtr hDesktop);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
#endregion
#region Structure
[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_INFORMATION
{
public readonly IntPtr hProcess;
public readonly IntPtr hThread;
public readonly int dwProcessId;
public readonly int dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
private struct STARTUPINFO
{
public int cb;
public readonly string lpReserved;
public string lpDesktop;
public readonly string lpTitle;
public readonly int dwX;
public readonly int dwY;
public readonly int dwXSize;
public readonly int dwYSize;
public readonly int dwXCountChars;
public readonly int dwYCountChars;
public readonly int dwFillAttribute;
public readonly int dwFlags;
public readonly short wShowWindow;
public readonly short cbReserved2;
public readonly IntPtr lpReserved2;
public readonly IntPtr hStdInput;
public readonly IntPtr hStdOutput;
public readonly IntPtr hStdError;
}
#endregion
}
}
Listing 1.0 Desktop API wrapper

Figure 1.0 Test Launcher app
In the supplied sample code, I have put the class mentioned in Listing 1.0 in to
TestLauncher project. The method to put attention to is ShowSecureDesktop.
public static void ShowSecureDesktop()
{
IntPtr hOldDesktop = GetThreadDesktop(GetCurrentThreadId());
IntPtr hNewDesktop = CreateDesktop(
"TestAppDesktop",
IntPtr.Zero,
IntPtr.Zero,
0,
(uint)DESKTOP_ACCESS.GENERIC_ALL,
IntPtr.Zero);
SwitchDesktop(hNewDesktop);
SetThreadDesktop(hNewDesktop);
var si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "TestAppDesktop";
var pi = new PROCESS_INFORMATION();
CreateProcess(
null,
@"TestApp.exe",
IntPtr.Zero,
IntPtr.Zero,
true,
NORMAL_PRIORITY_CLASS,
IntPtr.Zero,
null,
ref si,
ref pi);
WaitForSingleObject(pi.hProcess, INFINITE);
SwitchDesktop(hOldDesktop);
CloseDesktop(hNewDesktop);
}
Listing 1.1 ShowSecureDesktop method

Figure 1.1 Test app loaded in secure desktop
This method first gets reference to current desktop. This is required since once
your newly created desktop is closed, you need to revert back to your original
desktop. We then create new desktop by calling CreateDesktop. We will then use
well known CreateProcess method to launch our test WPF app “TestApp.exe”. With
this, we are also telling process to load this app into desktop named “TestAppDesktop”.
This is the desktop which we just created. So as soon as the code gets executed
till this point, the new desktop will be created and your application will be
launched in that.
Now the question is when to close this desktop.
In this case, we will switch to this desktop as soon as the TestApp gets closed.
For that we can use the WaitForSingleObject. This will ensure that the calling
application thread will wait till the process we launched finish execution and
terminated. When you run the sample code, you will see that the TestLauncher
application will launch TestApp and the TestApp will have End Test button.
Clicking
on the button will close the TestApp and will trigger switching to original desktop. When
the new desktop is created and set, you will see that any sidebar or taskbar
navigation are not present. This will also help preventing any other application
to gain access to keystrokes or mouse interaction happening as they are in different
desktop. The only way you can see running applications is by pressing Alt + Ctrl
+ Del combination and bringing up Task Manager. You can still disable this as
well! I will leave this up to you if you want to try with the sample code provided. Just
Bing it!
Source code is here