I've been trying to detect whether or not the Terminal Services are running. I tried to use the MSDN Way:
OSVERSIONINFOEX osVersionInfo;
DWORDLONG dwlConditionMask = 0;
memset( &osVersionInfo, 0, sizeof( osVersionInfo ) );
osVersionInfo.dwOSVersionInfoSize = sizeof( osVersionInfo );
osVersionInfo.wSuiteMask = VER_SUITE_TERMINAL;
VER_SET_CONDITION( dwlConditionMask, VER_SUITENAME, VER_AND );
return VerifyVersionInfo( &osVersionInfo, VER_SUITENAME, dwlConditionMask );
It works well, but on:
OS Name: Microsoft(R) Windows(R) Server 2003, Enterprise Edition
OS Version: 5.2.3790 Service Pack 2 Build 3790
OS Manufacturer: Microsoft Corporation
OS Configuration: Standalone Server
OS Build Type: Multiprocessor Free
without Terminal Services role, the call to VerifyVersionInfo returns a non-zero value which means "the currently running operating system satisfies the specified requirements" or the function fails.
GetLastError returns:
0x000000cb "The system could not find the environment option that was entered."
On Windows XP SP3, a call to VerifyVersionInfo returns a zero value, so there are no typing errors, I suppose.
How do I handle this behavior? Is it good to treat this as if there are no Terminal Services? Are there any better techniques?
Is it a bug in the OS?
Update:
On Windows Server 2008 R1 behavior is similar (fails). May be on many other systems too.
Terminal Services is an NT-service like any other, internally named TermServices, and if you have the permission.privilege to access the service manager, you could poll there to see if it's running:
OpenSCManager
OpenService
QueryServiceStatus
Well, I found almost working solution (fails only on Win2k server).
OSVERSIONINFOEX osVersionInfo;
DWORDLONG dwlConditionMask = 0;
memset( &osVersionInfo, 0, sizeof( osVersionInfo ) );
osVersionInfo.dwOSVersionInfoSize = sizeof( osVersionInfo );
osVersionInfo.wSuiteMask = VER_SUITE_SINGLEUSERTS;
VER_SET_CONDITION( dwlConditionMask, VER_SUITENAME, VER_AND );
return !VerifyVersionInfo( &osVersionInfo, VER_SUITENAME, dwlConditionMask );
Interesting fact - call to VerifyVersionInfo do not modify last error code. Error code 0x000000cb that GetLastError returns to me is just a garbage.
A late reply but better late then never, the code below is Delphi but it wouldn't need much changes for c/c++
function AreWeRunningTerminalServices: Boolean;
var VersionInfo: TOSVersionInfoEx;
dwlConditionMask: Int64;
begin
// Zero Memory and set structure size
ZeroMemory(#VersionInfo, SizeOf(VersionInfo));
VersionInfo.dwOSVersionInfoSize := SizeOf(VersionInfo);
// We are either Terminal Server or Personal Terminal Server
VersionInfo.wSuiteMask := VER_SUITE_TERMINAL or VER_SUITE_SINGLEUSERTS;
dwlConditionMask := VerSetConditionMask(0, VER_SUITENAME, VER_OR);
// Test it
Result := VerifyVersionInfo(VersionInfo, VER_SUITENAME, dwlConditionMask);
end;
or if you want to check it the Terminal Server service is really running:
function IsTerminalServiceRunning: boolean;
var hSCM: HANDLE;
hService: HANDLE;
ServiceStatus: SERVICE_STATUS;
begin
Result := False;
// Open handle to Service Control Manager
hSCM := OpenSCManager(nil, SERVICES_ACTIVE_DATABASE, GENERIC_READ);
if hSCM > 0 then
begin
// Open handle to Terminal Server Service
hService := OpenService(hSCM, 'TermService', GENERIC_READ);
if hService > 0 then
begin
// Check if the service is running
QueryServiceStatus(hService, ServiceStatus);
Result := ServiceStatus.dwCurrentState = SERVICE_RUNNING;
// Close the handle
CloseServiceHandle(hService);
end;
// Close the handle
CloseServiceHandle(hSCM);
end;
end;
Related
I'm trying to create a Medium or Low integrity process from a elevated process. I know there are other questions like this but they mostly focus on the workarounds like using Explorer or the Task Scheduler and I want to stick with CreateRestrictedToken()+CreateProcessAsUser().
I assume it must be possible to do this somehow since I believe UAC does it when you log in but I have not been able to get everything in the token to look like the normal UAC Medium IL token.
You can get 80% there by creating the token with CreateRestrictedToken(hThisProcessToken, LUA_TOKEN, ...) and then setting TokenOwner, TokenDefaultDacl and TokenIntegrityLevel before calling CreateProcessAsUser().
The remaining issues are TokenVirtualizationAllowed, TokenVirtualizationEnabled, TokenElevation, TokenElevationType and TokenMandatoryPolicy where SetTokenInformation() fails with ERROR_PRIVILEGE_NOT_HELD or ERROR_INVALID_PARAMETER.
If I run as SYSTEM # SECURITY_MANDATORY_SYSTEM_RID with all privileges enabled as opposed to an Administrator # SECURITY_MANDATORY_HIGH_RID then I'm able to set TokenMandatoryPolicy and TokenVirtualization* but setting TokenElevation* still fails! (Only tested on Windows 8 so far)
Not having the correct TokenElevation* values in the token is a big issue because Internet Explorer fails to start in Protected Mode because it thinks the token is elevated.
The documentation for SetTokenInformation() does not say which TOKEN_INFORMATION_CLASS items it is possible to set and which privileges, if any, are required and I don't understand why you would not be allowed to set these to lower security values that match the actual integrity level (TokenIntegrityLevel) of the token.
Using the Safer API to create a SAFER_LEVELID_NORMALUSER token does not fix any of these issues and also creates a token that is more restricted than the normal Medium IL token.
I found somebody with a similar issue from the early Vista/Longhorn days, has nothing changed since then?
function CreateLowProcess(szProcessName: WideString; const IntegritySid: UnicodeString=''): Boolean;
var
hToken: THandle;
hNewToken: THandle;
szIntegritySid: WideString;
pIntegritySid: PSID;
TIL: TOKEN_MANDATORY_LABEL;
ProcInfo: PROCESS_INFORMATION;
startupInfo: TStartupInfo;
const
SE_GROUP_INTEGRITY = $00000020;
TokenIntegrityLevel = 25;
SLowIntegritySid: UnicodeString = 'S-1-16-4096';
SMediumIntegritySid: UnicodeString = 'S-1-16-8192';
SHighIntegritySid: UnicodeString = 'S-1-16-12288';
SSystemIntegritySid: UnicodeString = 'S-1-16-16384';
begin
{
Designing Applications to Run at a Low Integrity Level
http://msdn.microsoft.com/en-us/library/bb625960.aspx
To start a low-integrity process:
- Duplicate the handle of the current process, which is at medium integrity level.
- Use SetTokenInformation to set the integrity level in the access token to Low.
- Use CreateProcessAsUser to create a new process using the handle to the low integrity access token.
CreateProcessAsUser updates the security descriptor in the new child process and the security descriptor
for the access token to match the integrity level of the low-integrity access token.
}
// Low integrity SID
if IntegritySid <> '' then
szIntegritySid := IntegritySid
else
szIntegritySid := SLowIntegritySid;
// szIntegritySid := 'S-1-5-32-545'; //BUILTIN\USERS
ZeroMemory(#startupInfo, sizeof(startupInfo));
if (not OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE or TOKEN_ADJUST_DEFAULT or TOKEN_QUERY or TOKEN_ASSIGN_PRIMARY,
{var}hToken)) then
RaiseLastOSError;
try
if (not DuplicateTokenEx(hToken, 0, nil, SecurityImpersonation, TokenPrimary, {var}hNewToken)) then
RaiseLastOSError;
try
pIntegritySid := StringToSid(szIntegritySid); //free with LocalFree
try
TIL._Label.Attributes := SE_GROUP_INTEGRITY;
TIL._Label.Sid := pIntegritySid;
// Set the process integrity level
if (not SetTokenInformation(hNewToken, TTokenInformationClass(TokenIntegrityLevel), #TIL,
sizeof(TOKEN_MANDATORY_LABEL) + GetLengthSid(pIntegritySid))) then
RaiseLastOSError;
//Create the new process at Low integrity
Result := CreateProcessAsUserW(
hNewToken,
nil,
PWideChar(szProcessName),
nil, //ProcessAttributes
nil, //ThreadAttributes
False, //bInheritHandles
0, //dwCreationFlags
nil, //lpEnvironment
nil, //lpCurrentDirectory
startupInfo,
ProcInfo);
finally
LocalFree(HLOCAL(pIntegritySid));
end;
finally
CloseHandle(hNewToken);
end;
finally
CloseHandle(hToken);
end;
end;
The code below for file with no-extension brings up a dialog shown below listing applications that can be used to open the file. This behavior is seen only from Windows-8. And the applications listed in the dialog are taken from HKEY_LOCAL_MACHINE\SOFTWARE\Classes*\OpenWithList. Is there anyway to suppress this dialog and get a behavior similar to old platforms?
-Karthik
SHELLEXECUTEINFO shinfo;
unsigned long mask = SEE_MASK_FLAG_NO_UI;
memset(&shinfo,0,sizeof(shinfo));
shinfo.cbSize = sizeof(shinfo);
shinfo.fMask = SEE_MASK_FLAG_DDEWAIT | mask;
shinfo.hwnd = NULL;
shinfo.lpVerb = "open";
shinfo.lpFile = prog;
shinfo.lpParameters = NULL;
shinfo.lpDirectory = 0;
shinfo.fMask = SEE_MASK_FLAG_NO_UI;
shinfo.nShow = SW_SHOWDEFAULT;
rc = ShellExecuteEx(&shinfo);
I'd suspect there is no default action associated with open on Windows 8 for files without an extension. I confirmed this using your code (a Delphi version of it, anyway) on Windows 7.
Running the code with shinfo.lpverb set to 'open' caused ShellExecuteEx to return FALSE, and GetLastError did indeed return ERROR_NO_ASSOCIATION. However, changing lpVerb to NULL (nil in Delphi) instead displayed the standard Win7 Open With dialog, just like your code does on Windows 8.
Here's a modified version of your code for testing:
SHELLEXECUTEINFO shinfo;
memset(&shinfo, 0, sizeof(shinfo));
shinfo.cbSize = sizeof(shinfo);
shinfo.fMask = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI;
shinfo.lpVerb = NULL;
shinfo.lpFile = prog;
shinfo.hwnd = NULL;
shinfo.lpParameters = NULL;
shinfo.lpDirectory = 0;
shinfo.nShow = SW_SHOWDEFAULT;
rc = ShellExecuteEx(&shinfo);
Here's my test Delphi code for comparison (a quite literal translation of your C++ code):
var
shInfo: TShellExecuteInfo;
FillChar(shInfo, SizeOf(shInfo), 0); // Same result as memset()
shInfo.cbSize := SizeOf(shInfo);
shInfo.fMask := SEE_MASK_FLAG_NO_UI or SEE_MASK_FLAG_DDEWAIT;
shInfo.Wnd := 0;
shInfo.lpVerb := nil; // Also tested with 'open'
shInfo.lpDirectory := 'D:\TempFiles'; // Path to my no-extension file
shInfo.lpFile := 'datafile'; // My test file with no ext
shInfo.nShow := SW_SHOWDEFAULT;
if not ShellExecuteEx(#shInfo) then
ShowMessage(SysErrorMessage(GetLastError)); // Readable error message
A quick change to your code to replace "open" with NULL as the lpVerb should confirm.
You can also confirm my suspicion fairly easily by right-clicking a file with no extension in Win8's Explorer, and checking the bold default action at the top of the context menu. If there is no bold option, or if it's anything but open, my suspicions are correct.
While trying to solve a previously asked SO question of mine, I've find that even without my threads, the problem occurs.
what I have now , is a really simple single-threaded code , that calls - NetServerEnum()
. when returned, it calls NetApiBufferFree() and return from main, which supposed to end the process.
at that point, my thread truly ends, but the process won't exit , as there are 4 threads opened (not by me):
1 * ntdll.dll!TplsTimerSet+0x7c0 (stack is at ntdll.dll!WaitForMultipleObjects)
(This one opened upon the call to NetServerEnum())
3 * ndll.dll!RtValidateHeap+0x170 (stack is at ntdll.dll!ZwWaitWorkViaWorkerFactory+0xa)
(These are open when my code returns)
UPDATE:
If I kill the thread running ntdll.dll!TplsTimerSet+0x7c0 externally (using process explorer) , before return of main(), the program exit gracefully.
I thought it might be useful to know.
UPDATE2: (some more tech info)
I'm using:
MS Visual Studio 2010 Ultimate x64 (SP1Rel) on Win7 Enterprise SP1
Code is C (but compile as c++ switch is on)
Subsystem: WINDOWS
Compiler: cl.exe (using IDE)
all other parameters are default.
I'm Using a self modified entry point (/ENTRY:"entry") , and it is the only function In my program):
int entry(void)
{
SERVER_INFO_101* si;
DWORD a,b;
NET_API_STATUS c;
c = NetServerEnum ( NULL , 101 , (LPBYTE*) &si , MAX_PREFERRED_LENGTH , &b , &a , SV_TYPE_WORKSTATION, NULL , 0 );
c = NetApiBufferFree (si);
Sleep(1000);
return 0;
}
all the tested mentioned before where preformed inside a windows domain network of about 100 units.
UPDATE 3:
This problem does not occur when tested on a (non-virtual) WinXP 32bit. (same binary, though for the Win7 x64 two binary were tested - 32bit over WOW , and native x64)
When you use a custom entry point, you're bypassing the runtime library, which means you're responsible for exiting the process. The process will exit implicitly if there are no more threads running, but as you've discovered, the operating system may create threads on your behalf that you don't have control over.
In your case, all you need to do is to call ExitProcess explicitly at the end of the entry() function.
int entry(void)
{
SERVER_INFO_101* si;
DWORD a,b;
NET_API_STATUS c;
c = NetServerEnum ( NULL , 101 , (LPBYTE*) &si , MAX_PREFERRED_LENGTH , &b , &a , SV_TYPE_WORKSTATION, NULL , 0 );
c = NetApiBufferFree (si);
Sleep(1000);
ExitProcess(0);
}
In the absence of a call to ExitProcess and with a custom entry point, the behaviour you're seeing is as expected.
I am writing a master installer with the following ShellExecuteEx() function that call a few Advanced Installer created installers (installing multiple products) one by one through a loop construct.
// Shell Execute
bool CFileHelper::ShellExecute(CString strCommandPath, CString strOptions)
{
CString strQCommandPath = CString(_T("\"")) + strCommandPath + CString(_T("\"")); //place the command in the quote to handle path with space
LPWSTR szInstallerPath = strQCommandPath.GetBuffer();
LPWSTR szOptions = strOptions.GetBuffer(MAX_PATH);
SHELLEXECUTEINFO ShellInfo; // Name structure
memset(&ShellInfo, 0, sizeof(ShellInfo)); // Set up memory block
ShellInfo.cbSize = sizeof(ShellInfo); // Set up structure size
ShellInfo.hwnd = 0; // Calling window handle
ShellInfo.lpVerb = _T("open");
ShellInfo.lpFile = szInstallerPath;
ShellInfo.fMask = SEE_MASK_NOCLOSEPROCESS; //| SEE_MASK_NOASYNC | SEE_MASK_WAITFORINPUTIDLE;
ShellInfo.lpParameters = szOptions;
bool res = ShellExecuteEx(&ShellInfo); // Call to function
if (!res)
{
//printf( "CreateProcess failed (%d).\n", GetLastError() );
CString strMsg = CString(_T("Failed to execute command ")) + strCommandPath + CString(_T("!"));
AfxMessageBox(strMsg);
return false;
}
WaitForSingleObject(ShellInfo.hProcess, INFINITE); // wait forever for process to finish
//WaitForInputIdle(ShellInfo.hProcess, INFINITE);
CloseHandle( ShellInfo.hProcess);
strQCommandPath.ReleaseBuffer();
strOptions.ReleaseBuffer();
return true;
}
The function work every well when I have this master installer and other individual product installers on hard drive.
However, if I move all of them to either USB drive or CD, the ShellExecuteEx() didn't wait for the previous product installer to complete its task. So all product installers get lunched at once; giving me the error message "Another installation is in progress. You must complete that installation before continuing this one.".
One thing puzzle me is why it works on hard drive but not on USB drive and CD drive. I need to distribute the products on CD.
Putting Sleep(500) before WaitForSingleObject(ShellInfo.hProcess, INFINITE) didn't help as well.
Work from the assumption that this is real. The installer might have noticed it was started from a removable drive and copied itself to the hard disk. Launched that copy and quit. This avoids trouble when the user pops out the media, that produces a very low-level paging fault that the process itself cannot catch. The Windows dialog isn't great and may well run counter to the installer's request to insert the next disk.
Verify this guess by comparing the process ID of the process you started vs the one you see running in Taskmgr.exe. Reliably fixing this ought to be quite a headache.
I have an application that aids people with disabilities. In order to work, it tracks the what window is currently in the foreground. Normally, I use this function to get process executable.
bool GetWindowProcessExe2(HWND hwnd, wxString& process_exe)
//LPTSTR buf, DWORD size)
{
DWORD result = 0;
DWORD pid = 0;
GetWindowThreadProcessId(hwnd, &pid);
if (HANDLE process =
OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid))
{
char buff[512];
LPTSTR pbuff = buff;
result = GetModuleFileNameEx(process, 0, pbuff, 512);
if(result == 0)
{
//failed.
wxLogError("GetModuleFileNameEx failed with error code %d", GetLastError());
}
CloseHandle(process);
process_exe = fromCString(pbuff);
}
return result > 0 ? true : false;
}
Unfortunately, if the foreground window is the Vista file manager window (the window that opens when you click Start->Computer), GetModuleFileNameEx() fails with error code 299 which says I don't have privileges for this action. My code works for any regular application but not for the windows built in window (the file explorer). I need to know when this window is forefront. Is there another way to do it? I tried reading the window title but that just returns the current directory being shown. Any ideas?
I'm not sure why this isn't working for explorer, but error 299 is ERROR_PARTIAL_COPY, meaning that attempting to read the module name out of explorer is failing.
On Vista, prefer QueryProcessImageFileName and only open the process with PROCESS_QUERY_LIMITED_INFORMATION - your code will work in more cases.
WCHAR exeName[512];
DWORD cchExeName = 512;
HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid);
QueryFullProcessImageName(process, 0, exeName, &cchExeName);
EDIT: I also got ERROR_PARTIAL_COPY with your code running on 64-bit, but only when the querying process was 32-bit. 64-bit/64-bit worked fine.
Looks like a 32 bit process can call GetModuleFileNameEx only on othere 32 bit processes. If you try to call it on 64 bit processes it fails with ERROR_PARTIAL_COPY.On a 64 bit platform make the calling process 64 bit and you should be able to call GetModuleFileNameEx on both 64 and 32 bit processes.