Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running a process at the Windows 7 Welcome Screen

So here's the scoop:

I wrote a tiny C# app a while back that displays the hostname, ip address, imaged date, thaw status (we use DeepFreeze), current domain, and the current date/time, to display on the welcome screen of our Windows 7 lab machines. This was to replace our previous information block, which was set statically at startup and actually embedded text into the background, with something a little more dynamic and functional. The app uses a Timer to update the ip address, deepfreeze status, and clock every second, and it checks to see if a user has logged in and kills itself when it detects such a condition.

If we just run it, via our startup script (set via group policy), it holds the script open and the machine never makes it to the login prompt. If we use something like the start or cmd commands to start it off under a separate shell/process, it runs until the startup script finishes, at which point Windows seems to clean up any and all child processes of the script. We're currently able to bypass that using psexec -s -d -i -x to fire it off, which lets it persist after the startup script is completed, but can be incredibly slow, adding anywhere between 5 seconds and over a minute to our startup time.

We have experimented with using another C# app to start the process, via the Process class, using WMI Calls (Win32_Process and Win32_ProcessStartup) with various startup flags, etc, but all end with the same result of the script finishing and the info block process getting killed. I tinkered with rewriting the app as a service, but services were never designed to interact with the desktop, let alone the login window, and getting things operating in the right context never really seemed to work out.

So for the question: Does anybody have a good way to accomplish this? Launch a task so that it would be independent of the startup script and run on top of the welcome screen?

like image 338
peelman Avatar asked Jun 18 '10 13:06

peelman


People also ask

How do I fix please wait on Windows 7?

Go to the Admin Tools "Services" section. Locate the following services and disable them: Windows Error Reporting, Network Location Service, Network List service. Power down the PC gracefully (Start, Shutdown, etc.) Restart the PC, wait a minute or two (no more)

Why is Windows 7 stuck on starting Windows screen?

To fix Windows 7 stuck at starting windows screen problem, you can try to change your computer memory or reinstall the computer memory. And then, restart the computer in Safe Mode. In Safe Mode, press Win + R keys to run MSCONFIG and press OK.


1 Answers

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.

like image 125
Mitch Avatar answered Sep 20 '22 05:09

Mitch