Ignoring pre-Vista OS's, assuming you have TCB privs on your token (are running as System, basically), you can use CreateProcessAsUser to do this.
Example to be run as System (e.g.: an NT Service or with psexec -s) which will start notepad in the console session winlogon desktop:
#define WIN32_LEAN_AND_MEAN
#pragma comment(lib, "Userenv.lib")
#include <Windows.h>
#include <UserEnv.h>
#include <iostream>
#include <string>
HANDLE GetTokenForStart();
LPVOID GetEnvBlockForUser(HANDLE hToken);
void StartTheProcess(HANDLE hToken, LPVOID pEnvironment);
int main(int argc, wchar_t* argv[])
{
    //while (!IsDebuggerPresent()) Sleep(500);
    try 
    {
        HANDLE hUserToken = GetTokenForStart();
        LPVOID env = GetEnvBlockForUser(hUserToken);
        StartTheProcess(hUserToken, env);
    }
    catch (std::wstring err)
    {
        auto gle = GetLastError();
        std::wcerr << L"Error: " << err << L" GLE: " << gle << L"\r\n";
        return -1;
    }
}
HANDLE GetTokenForStart()
{
    HANDLE hToken = 0;
    {
        HANDLE processToken = 0;
        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_EXECUTE, &processToken))
        {
            throw std::wstring(L"Could not open current process token");
        }
        if (!DuplicateTokenEx(processToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &hToken))
        {
            throw std::wstring(L"Could not duplicate process token");
        }
    }
    DWORD consoleSessionId = WTSGetActiveConsoleSessionId();
    if (!SetTokenInformation(hToken, TokenSessionId, &consoleSessionId, sizeof(consoleSessionId)))
    {
        throw std::wstring(L"Could not set session ID");
    }
    return hToken;
}
LPVOID GetEnvBlockForUser(HANDLE hToken)
{
    LPVOID pEnvironment = NULL;
    if (!CreateEnvironmentBlock(&pEnvironment, hToken, FALSE))
    {
        throw std::wstring(L"Could not create env block");
    }
    return pEnvironment;
}
void StartTheProcess(HANDLE hToken, LPVOID pEnvironment)
{
    STARTUPINFO si = { 0 };
    si.cb = sizeof(si);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_SHOW;
    si.lpDesktop = (LPWSTR)L"winsta0\\winlogon";
    wchar_t path[MAX_PATH] = L"notepad.exe";
    PROCESS_INFORMATION pi = { 0 };
    if (!CreateProcessAsUser(hToken, NULL, path, NULL, NULL, FALSE,
        CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, pEnvironment, NULL, &si, &pi))
    {
        throw std::wstring(L"Could not start process");
    }
    if (!CloseHandle(pi.hThread))
    {
        throw std::wstring(L"Could not close thread handle");
    }
}
Or, if you prefer C#:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
namespace StartWinlogonManaged
{
    class Program
    {
        static void Main(string[] args)
        {
            var hUserToken = GetTokenForStart();
            var env = GetEnvBlockForUser(hUserToken);
            StartTheProcess(hUserToken, env);
        }
        const string 
            Advapi32 = "advapi32.dll",
            Userenv = "userenv.dll",
            Kernel32 = "kernel32.dll";
        [DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
        public static extern IntPtr GetCurrentProcess();
        [DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
        public static extern bool OpenProcessToken(IntPtr ProcessToken, int DesiredAccess, out IntPtr TokenHandle);
        [DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
        public static extern bool DuplicateTokenEx(IntPtr ExistingToken, int DesiredAccess,
            IntPtr TokenAttributes, int ImpersonationLevel, int TokenType, out IntPtr NewToken);
        [DllImport("kernel32.dll", ExactSpelling = true)]
        static extern int WTSGetActiveConsoleSessionId();
        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SetTokenInformation(IntPtr hToken,
            int tokenInfoClass, ref int pTokenInfo, int tokenInfoLength);
        static IntPtr GetTokenForStart()
        {
            IntPtr hToken = IntPtr.Zero;
            {
                IntPtr processToken = IntPtr.Zero;
                if (!OpenProcessToken(GetCurrentProcess(), 0x2001f /* TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_EXECUTE */, out processToken))
                {
                    throw new Win32Exception("Could not open current process token");
                }
                if (!DuplicateTokenEx(processToken, 0x02000000 /* MAXIMUM_ALLOWED */, IntPtr.Zero, 2 /* SecurityImpersonation */, 1 /* TokenPrimary */, out hToken))
                {
                    throw new Win32Exception("Could not duplicate process token");
                }
            }
            int consoleSessionId = WTSGetActiveConsoleSessionId();
            if (!SetTokenInformation(hToken, 12 /* TokenSessionId */, ref consoleSessionId, 4 /* sizeof(int) */))
            {
                throw new Win32Exception("Could not set session ID");
            }
            return hToken;
        }
        [DllImport(Userenv, CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
        static IntPtr GetEnvBlockForUser(IntPtr hToken)
        {
            IntPtr pEnvironment = IntPtr.Zero;
            if (!CreateEnvironmentBlock(out pEnvironment, hToken, true))
            {
                throw new Win32Exception("Could not create env block");
            }
            return pEnvironment;
        }
        [DllImport(Advapi32, CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool CreateProcessAsUser(IntPtr hToken,
            StringBuilder appExeName, StringBuilder commandLine, IntPtr processAttributes,
            IntPtr threadAttributes, bool inheritHandles, uint dwCreationFlags,
            IntPtr environment, string currentDirectory, ref STARTUPINFO startupInfo,
            out PROCESS_INFORMATION startupInformation);
        [StructLayout(LayoutKind.Sequential)]
        internal struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public uint dwProcessId;
            public uint dwThreadId;
        }
        [StructLayout(LayoutKind.Sequential)]
        internal struct STARTUPINFO
        {
            public int cb;
            public IntPtr lpReserved;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpDesktop;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpTitle;
            public int dwX;
            public int dwY;
            public int dwXSize;
            public int dwYSize;
            public int dwXCountChars;
            public int dwYCountChars;
            public int dwFillAttribute;
            public int dwFlags;
            public short wShowWindow;
            public short cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }
        [DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
        public static extern bool CloseHandle(IntPtr handle);
        static void StartTheProcess(IntPtr hToken, IntPtr pEnvironment)
        {
            var si = new STARTUPINFO();
            si.cb = Marshal.SizeOf<STARTUPINFO>();
            si.dwFlags = 1 /* STARTF_USESHOWWINDOW */;
            si.wShowWindow = 5 /* SW_SHOW */;
            si.lpDesktop = "winsta0\\winlogon";
            var path = new StringBuilder("notepad.exe", 260);
            PROCESS_INFORMATION pi;
            if (!CreateProcessAsUser(hToken, null, path, IntPtr.Zero, IntPtr.Zero, false,
                0x410 /* CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT */, pEnvironment, null, ref si, out pi))
            {
                throw new Win32Exception("Could not start process");
            }
            if (!CloseHandle(pi.hThread))
            {
                throw new Win32Exception("Could not close thread handle");
            }
        }
    }
}
Note that this does require several privileges (TCB, AssignPrimaryToken, IncreaseQuota) enabled in your token.  This code also leaks handles, does not formulate a full command line, use name constants, etc..., and is only intended as an expository reference - not as a ready solution.