Am 16.06.2026 um 09:39 schrieb Lawrence D?Oliveiro:
On Mon, 15 Jun 2026 11:10:46 +0200, Bonita Montero wrote:
I've written a tool called "bg" to combine Windows' runas with
start.
Sort of a Windows answer to systemd? Somebody is already working on that <https://github.com/Jamesits/SvcGuest>.
No, my tool has nothing to do with making arbitrary executables
runnable as services. It's a combination of Windows' runas, start
and Unix' time.
Here are the commandline options of my tool:
static const char usage[] =
"--console create new console\n"
"--detach start detached / hidden\n"
"--idle idle priority\n"
"--below below normal priority\n"
"--above above normal priority\n"
"--high high priority\n"
"--realtime realtime priority\n"
"--affintiy f CPU-affinity as hex (0x*), binary (b*) or list (e.g. 1,5-9)\n"
"--group g primary processor group\n"
"--wait wait for program to terminate\n"
"--logon u d p create process under 1. username, 2. domain and 3. password\n"
"--no-profile don't load registry for --logon, just network-credentials\n"
"--dir d set startup directory\n"
"--times show process times (includes --wait)";
Here's the main source:
#include <Windows.h>
#include <iostream>
#include <iomanip>
#include <vector>
#include <string>
#include <cwchar>
#include <cstddef>
#include <system_error>
#include <cassert>
#include <sstream>
#include <functional>
#include <array>
#include <type_traits>
#include <cstdio>
#include <bit>
#include <span>
#include <regex>
#include <expected>
#include "defer.hpp"
#pragma warning(disable: 4554) // operator prececence
#pragma warning(disable: 26812) // prefer scoped enums
using namespace std;
struct params
{
wstring userName, domain, password;
bool noProfile = false;
wstring startupDir;
DWORD
dwPrioFlag = NORMAL_PRIORITY_CLASS,
dwConsoleFlag = 0;
DWORD_PTR dwpAffinity = -1;
WORD wGroup = -1;
bool wait = false;
wstring cmdLine;
bool times = false;
bool parse( int argc, const wchar_t *const *argv );
private:
struct state
{
using check_fn = bool (state::*)( params & );
static const check_fn g_checkFns[9];
span<const wchar_t *const> m_todo;
vector<wstring> m_errs;
bool checkNoProfile( params &pms );
bool checkLogon( params &pms );
bool checkDir( params &pms );
bool checkConsole( params &pms );
bool checkPriority( params &pms );
bool checkAffinity( params &pms );
bool checkGroup( params &pms );
bool checkWait( params &pms );
bool checkTimes( params &pms );
bool parse( params &pms, int argc, const wchar_t *const *argv );
};
};
static expected<wstring, DWORD> inputPassword();
static string formatTime( uint64_t t );
static string formatDotted( uintmax_t value );
int wmain( int argc, const wchar_t *const *argv )
{
try
{
params pms;
if( !pms.parse( argc, argv ) )
return EXIT_FAILURE;
const wchar_t *curDir = pms.startupDir.length() ? pms.startupDir.c_str() : nullptr;
if( pms.userName.length() && !pms.password.length() )
{
cout << "password: ";
expected<wstring, DWORD> xptPass = inputPassword();
if( !xptPass )
throw exception( "error while password input" );
pms.password = *xptPass;
cout << endl;
}
BOOL cpRet;
bool suspended = pms.dwpAffinity != -1 || pms.wGroup != (WORD)-1;
DWORD dwCreationFlags = pms.dwPrioFlag | pms.dwConsoleFlag | (!suspended ? 0 : CREATE_SUSPENDED);
STARTUPINFOW si { sizeof si };
PROCESS_INFORMATION pi;
if( !pms.userName.length() )
cpRet = CreateProcessW(
nullptr, (LPWSTR)pms.cmdLine.c_str(), nullptr, nullptr,
FALSE, dwCreationFlags, nullptr, curDir, &si, &pi );
else
cpRet = CreateProcessWithLogonW(
pms.userName.c_str(),
pms.domain.c_str(),
pms.password.c_str(),
pms.noProfile ? LOGON_NETCREDENTIALS_ONLY : LOGON_WITH_PROFILE,
nullptr, (LPWSTR)pms.cmdLine.c_str(),
dwCreationFlags, nullptr, curDir, &si, &pi );
if( !cpRet )
throw exception( "creating process failed" );
defer closeHandles( [&] { CloseHandle( pi.hProcess ); CloseHandle(
pi.hThread ); } );
defer terminate( [&] { TerminateProcess( pi.hProcess, EXIT_FAILURE ); } );
if( suspended )
{
bool threadAssigned = false;
if( pms.wGroup != (WORD)-1 )
{
GROUP_AFFINITY aff {};
DWORD dwNCPUs = GetActiveProcessorCount( pms.wGroup );
if( !dwNCPUs )
throw exception( "can't determine the number of processors in the
destination group" );
if( dwNCPUs < sizeof(KAFFINITY) * 8 )
aff.Mask = ((KAFFINITY)1 << dwNCPUs) - 1;
else
aff.Mask = -1;
aff.Group = pms.wGroup;
if( !SetThreadGroupAffinity( GetCurrentThread(), &aff, nullptr ) )
throw exception( "can't set primary thread's group affinity" );
threadAssigned = true;
}
if( pms.dwpAffinity != -1 )
if( !SetProcessAffinityMask( pi.hProcess, pms.dwpAffinity ) )
throw exception( "can't set process affinity mask" );
else if( !threadAssigned && !SetThreadAffinityMask( pi.hThread,
pms.dwpAffinity ) )
throw exception( "can't set primary thread's affinity mask" );
if( ResumeThread( pi.hThread ) == -1 )
throw exception( "can't resume main thread of process" );
}
terminate.disable();
if( !pms.wait )
return EXIT_SUCCESS;
if( WaitForSingleObject( pi.hProcess, INFINITE ) != WAIT_OBJECT_0 )
throw exception( "can't wait for end of program" );
DWORD dwExitCode;
if( !GetExitCodeProcess( pi.hProcess, &dwExitCode ) )
throw exception( "can't get process exit code" );
if( !pms.times )
return dwExitCode;
FILETIME creationTime, exitTime, userFTime, sysFTime;
if( !GetProcessTimes( pi.hProcess, &creationTime, &exitTime, &sysFTime, &userFTime ) )
throw exception( "can't get process times" );
ULONG64 processCycles;
if( !QueryProcessCycleTime( pi.hProcess, &processCycles ) )
throw exception( "can't get process cycles" );
auto ft2ts = []( const FILETIME &ft ) static -> uint64_t { return
(uint64_t)ft.dwHighDateTime << 32 | ft.dwLowDateTime; };
uint64_t
realTime = ft2ts( exitTime ) - ft2ts( creationTime ),
userTime = ft2ts( userFTime ),
sysTime = ft2ts( sysFTime );
cerr
<< "real " << formatTime( realTime ) << endl
<< "user " << formatTime( userTime ) << endl
<< "sys " << formatTime( sysTime ) << endl
<< "cylces " << formatDotted( processCycles ) << endl;
return dwExitCode;
}
catch( const exception &exc )
{
cerr << exc.what() << endl;
}
}
static const char usage[] =
"--console create new console\n"
"--detach start detached / hidden\n"
"--idle idle priority\n"
"--below below normal priority\n"
"--above above normal priority\n"
"--high high priority\n"
"--realtime realtime priority\n"
"--affintiy f CPU-affinity as hex (0x*), binary (b*) or list (e.g. 1,5-9)\n"
"--group g primary processor group\n"
"--wait wait for program to terminate\n"
"--logon u d p create process under 1. username, 2. domain and 3. password\n"
"--no-profile don't load registry for --logon, just network-credentials\n"
"--dir d set startup directory\n"
"--times show process times (includes --wait)";
const params::state::check_fn params::state::g_checkFns[9] =
{
&checkNoProfile,
&checkLogon,
&checkDir,
&checkConsole,
&checkPriority,
&checkAffinity,
&checkGroup,
&checkWait,
&checkTimes
};
bool params::parse( int argc, const wchar_t *const *argv )
{
state st;
return st.parse( *this, argc, argv );
}
bool params::state::checkNoProfile( params &pms )
{
if( _wcsicmp( m_todo.front(), L"--no-profile" ) != 0 )
return false;
pms.noProfile = true;
m_todo = m_todo.subspan( 1 );
return true;
}
bool params::state::checkLogon( params &pms )
{
if( _wcsicmp( m_todo.front(), L"--logon" ) != 0 )
return false;
if( m_todo.size() < 4 )
{
m_todo = {};
m_errs.emplace_back( L"please specify username, domain and password" );
return true;
};
pms.userName = m_todo[1];
pms.domain = m_todo[2];
pms.password = m_todo[3];
m_todo = m_todo.subspan( 4 );
return true;
}
bool params::state::checkDir( params &pms )
{
if( _wcsicmp( m_todo.front(), L"--dir" ) != 0 )
return false;
if( m_todo.size() < 2 )
{
m_todo = {};
m_errs.emplace_back( L"please supply startup direcotry" );
return true;
};
pms.startupDir = m_todo[1];
m_todo = m_todo.subspan( 2 );
return true;
}
bool params::state::checkConsole( params &pms )
{
if( _wcsicmp( m_todo.front(), L"--console" ) == 0 )
pms.dwConsoleFlag = CREATE_NEW_CONSOLE;
else if( _wcsicmp( m_todo.front(), L"--detach" ) == 0 )
pms.dwConsoleFlag = DETACHED_PROCESS;
else
return false;
m_todo = m_todo.subspan( 1 );
return true;
}
bool params::state::checkPriority( params &pms )
{
using opt_t = pair<const wchar_t *, DWORD> ;
static const opt_t opts[] =
{
{ L"--idle", IDLE_PRIORITY_CLASS },
{ L"--below", BELOW_NORMAL_PRIORITY_CLASS },
{ L"--above", ABOVE_NORMAL_PRIORITY_CLASS },
{ L"--high", HIGH_PRIORITY_CLASS },
{ L"--realtime", REALTIME_PRIORITY_CLASS }
};
DWORD dwPrio = 0;
for( const opt_t &opt : opts )
if( _wcsicmp( m_todo.front(), opt.first ) == 0 )
{
dwPrio = opt.second;
break;
}
if( !dwPrio )
return false;
m_todo = m_todo.subspan( 1 );
pms.dwPrioFlag = dwPrio;
return true;
}
bool params::state::checkAffinity( params &pms )
{
if( _wcsicmp( m_todo.front(), L"--affinity" ) != 0 )
return false;
if( m_todo.size() < 2 )
{
m_todo = {};
m_errs.emplace_back( L"please specify affinity" );
cerr << "" << endl;
return false;
};
DWORD_PTR dwpAff = 0;
match_results<wstring_view::iterator> match;
defer chop2( [&] { m_todo = m_todo.subspan( 2 ); } );
wstring_view svPattern( m_todo[1] );
constexpr const wchar_t
*RxHex = L"^0x([0-9a-f]+)$",
*RxBin = sizeof( DWORD_PTR ) == 8 ? L"^b((?:[01]+){1,64})$" : L"^b((?:[01]+){1,32})$",
*RxRange = L"^([0-9]+)(?:-([0-9]+))?(,)?";
if( wregex rxHex( RxHex, regex_constants::icase ); regex_match( svPattern.begin(), svPattern.end(), match, rxHex ) )
wistringstream( wstring( svPattern ) ) >> hex >> dwpAff;
else if( wregex rxBinary( RxBin, regex_constants::icase ); regex_match(
svPattern.begin(), svPattern.end(), match, rxBinary ) )
for( wchar_t c : wstring_view( match[1].first, match[1].second ) )
dwpAff = dwpAff << 1 | (c - '0');
else
{
defer affZero( [&] { dwpAff = 0; } );
auto it = svPattern.begin();
wregex rx( RxRange );
match_results<wstring_view::iterator> match;
for( ; regex_search( it, svPattern.end(), match, rx ); it = match[0].second )
{
if( match[3].matched && match[3].second == svPattern.end() )
break;
unsigned from, to;
wistringstream( wstring( match[1].first, match[1].second ) ) >> from;
if( match[2].matched )
{
wistringstream( wstring( match[2].first, match[2].second ) ) >> to;
++to;
}
else
to = from + 1;
constexpr unsigned BITS = sizeof(ULONG_PTR) * 8;
if( from >= to || from >= BITS || to > BITS )
break;
constexpr DWORD_PTR ALL = -1;
DWORD_PTR dwpBits = ALL << from;
if( to < BITS )
dwpBits &= ~(ALL << to);
dwpAff |= dwpBits;
}
if( it == svPattern.end() )
affZero.disable();
}
if( !dwpAff )
{
wostringstream woss;
woss << "invalid affinity pattern: " << svPattern;
m_errs.emplace_back( woss.str() );
return true;
}
DWORD_PTR dwpProc, dwpSys;
GetProcessAffinityMask( GetCurrentProcess(), &dwpProc, &dwpSys );
if( (dwpAff &= dwpProc) )
pms.dwpAffinity = dwpAff;
return true;
}
bool params::state::checkGroup( params &pms )
{
if( _wcsicmp( m_todo.front(), L"--group" ) != 0 )
return false;
if( m_todo.size() < 2 )
{
m_todo = {};
m_errs.emplace_back( L"please specify processor group" );
return false;
};
defer finish( [&] { m_todo = m_todo.subspan( 2 ); } );
WORD wGroups = GetActiveProcessorGroupCount();
if( !wGroups )
{
m_errs.emplace_back( L"unable to determine the active processor groups" );
return true;
}
defer invalid( [&] { m_errs.emplace_back( L"please specify valid processor group" ); } );
wstring_view svGroup( m_todo[1] );
match_results<wstring_view::iterator> match;
wregex rxGroup( L"^[0-9]+$" );
if( !regex_match( svGroup.begin(), svGroup.end(), match, rxGroup ) )
return false;
WORD group;
wistringstream( wstring( m_todo[1] ) ) >> group;
if( group >= wGroups )
return false;
pms.wGroup = group;
invalid.disable();
return true;
}
bool params::state::checkWait( params &pms )
{
if( _wcsicmp( m_todo.front(), L"--wait" ) != 0 )
return false;
pms.wait = true;
m_todo = m_todo.subspan( 1 );
return true;
}
bool params::state::checkTimes( params &pms )
{
if( _wcsicmp( m_todo.front(), L"--times" ) != 0 )
return false;
pms.wait = true;
pms.times = true;
m_todo = m_todo.subspan( 1 );
return true;
}
// might throw bad_alloc
static optional<WORD> setConsoleCols( WORD wAttrs );
static wstring quot( wstring_view str );
bool params::state::parse( params &pms, int argc, const wchar_t *const
*argv )
{
m_todo = span<const wchar_t *const>( argv + 1, argc - 1 );
while( m_todo.size() )
{
bool checked = false;
for( check_fn check : g_checkFns )
if( !m_todo.size() || (checked = (this->*check)( pms )) )
break;
if( !checked )
break;
}
if( m_errs.size() )
{
optional<WORD> wOldAtrs( setConsoleCols( FOREGROUND_RED ) );
defer oldCols( [&] { if( wOldAtrs ) setConsoleCols( *wOldAtrs ); } );
for( const wstring &err : m_errs )
wcout << err << endl;
return false;
}
if( !m_todo.size() )
{
cerr << endl << ::usage << endl;
return false;
}
for( const wchar_t *const &part : m_todo )
{
if( &part != &m_todo.front() )
pms.cmdLine += L" ";
pms.cmdLine += L"\"";
pms.cmdLine += part;
pms.cmdLine += L"\"";
}
return true;
}
static optional<WORD> setConsoleCols( WORD wAttrs )
{
constexpr WORD FG_CLR_MASK = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY;
HANDLE hStdErr = GetStdHandle( STD_ERROR_HANDLE );
if( !hStdErr || hStdErr == INVALID_HANDLE_VALUE )
return nullopt;
if( GetFileType( hStdErr ) != FILE_TYPE_CHAR )
return nullopt;
CONSOLE_SCREEN_BUFFER_INFO csbi {};
if( !GetConsoleScreenBufferInfo( hStdErr, &csbi ) )
return nullopt;
if( wAttrs == (WORD)-1 )
return csbi.wAttributes;
wAttrs = (csbi.wAttributes & ~FG_CLR_MASK) | (wAttrs & FG_CLR_MASK);
if( !SetConsoleTextAttribute( hStdErr, wAttrs ) )
return nullopt;
return csbi.wAttributes;
}
static expected<wstring, DWORD> inputPassword()
{
cout.flush(), wcout.flush();
constexpr auto *err = +[] static { return unexpected( GetLastError() ); };
HANDLE hStdIn, hStdOut;
if( !(hStdIn = GetStdHandle( STD_INPUT_HANDLE )) )
return err();
if( !(hStdOut = GetStdHandle( STD_OUTPUT_HANDLE )) )
return err();
DWORD dwOldConsoleMode;
if( !GetConsoleMode( hStdIn, &dwOldConsoleMode ) )
return err();
if( !SetConsoleMode( hStdIn, dwOldConsoleMode & ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT) ) )
return err();
defer revert( [&] { SetConsoleMode( hStdIn, dwOldConsoleMode ); } );
vector<COORD> coords;
wstring pass;
for( ; ; )
{
wchar_t ch;
if( DWORD dwRead; !ReadConsoleW( hStdIn, &ch, 1, &dwRead, nullptr ) ||
!dwRead )
return err();
if( ch == L'\r' )
return pass;
auto writeConsole = [&]( wchar_t ch )
{
CONSOLE_SCREEN_BUFFER_INFO csbi;
if( !GetConsoleScreenBufferInfo( hStdOut, &csbi ) )
return false;
coords.emplace_back( csbi.dwCursorPosition );
DWORD dwWritten;
return WriteConsoleW( hStdOut, &ch, 1, &dwWritten, nullptr ) &&
dwWritten == 1;
};
if( ch != L'\b' )
{
pass += ch;
if( !writeConsole( L'*' ) )
return err();
continue;
}
if( !pass.length() )
continue;
pass.pop_back();
auto back = [&]()
{
if( !SetConsoleCursorPosition( hStdOut, coords.back() ) )
return false;
coords.pop_back();
return true;
};
if( !back() || !writeConsole( L' ' ) || !back() )
return err();
}
return pass;
}
static string formatTime( uint64_t t )
{
constexpr auto MS = 10'000u;
t = (t + MS / 2) / MS;
uint16_t ms = t % 1'000; t /= 1'000;
uint8_t secs = t % 60; t /= 60;
uint8_t mins = t % 60; t /= 60;
uint32_t hours = (uint32_t)t;
ostringstream oss;
if( hours )
oss << hours << "h" << ":";
if( hours || mins )
oss << mins << "m" << ":";
oss << (int)secs;
if( ms )
oss << "." << setw( 3 ) << setfill( '0' ) << ms;
oss << "s";
return oss.str();
}
static string formatDotted( uintmax_t value )
{
ostringstream oss;
oss << value;
string_view svDigits( oss.view() );
auto pDigits = svDigits.begin();
size_t
digLen = svDigits.length(),
dots = (digLen - 1) / 3,
head = digLen - 3 * dots;
string dotted( digLen + dots, 0 );
auto pDotted = dotted.begin();
pDotted = copy( pDigits, pDigits + head, pDotted );
for( ; pDotted != dotted.end(); pDigits += 3 )
{
*pDotted++ = L'.';
pDotted = copy( pDigits, pDigits + 3, pDotted );
}
return dotted;
}
--- PyGate Linux v1.5.17
* Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)