I am trying to create a program that any normal user can run on windows and generate a process list of all processes, including the executable location. I have used CreateToolhelp32Snapshot() to get all process names, pid, ppid. But having issues getting the image path. Everything I do results in pretty much Access Denied.
I have tried ZwQueryInformationProcess, GetProcessImageFileName, etc. and also using OpenProcess to get the handle to each process. I can get the handle by using PROCESS_QUERY_LIMITED_INFORMATION, but any other option doesn't work. I am lost and have been at this for a few days. Can anyone point me in the right direction?
This is the code that works for non-admin user on Windows. Use the szExeFile member of PROCESSENTRY32 to get the path:
HANDLE hProcessSnap = NULL;
HANDLE hProcess = NULL;
PROCESSENTRY32 pe32;
DWORD dwPriorityClass = 0;
// Take a snapshot of all processes in the system.
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE)
{
return;
}
// Set the size of the structure before using it.
pe32.dwSize = sizeof(PROCESSENTRY32);
// Retrieve information about the first process,
// and exit if unsuccessful
if (!Process32First(hProcessSnap, &pe32))
{
CloseHandle(hProcessSnap); // clean the snapshot object
return;
}
// Now walk the snapshot of processes, and
// display information about each process in turn
do
{
// do something with the pe32 struct.
// pe32.szExeFile -> path of the file
} while (Process32Next(hProcessSnap, &pe32));
CloseHandle(hProcessSnap);
Related
Under Linux, my program would do something like this:
Process 1 opens a file (e.g. by mapping it into memory). Let's call this file#1
Process 2 unlinks the file, and creates a new file with the same name. Let's call this file#2.
Process 1 continues to work with file#1. When it is closed, it is deleted (since it has no links). Process 1 continues to work with the content in file#1, and does not see content from file#2.
When both processes have exited, file#2 remains on disk.
I want to achieve the same semantics in Windows. After reading this question, I think FILE_SHARE_DELETE does basically this. Is opening the file with FILE_SHARE_DELETE enough, or do I need to consider something more?
The above execution flow is just an example, I know there are other ways of solving that exact problem in Windows, but I want to understand how to make such code portable between Windows and Linux.
Edit: to clarify: The use cases would be to reuse a filename for different unrelated files, but let existing processes keep their data (think transactional update of a config file for example), and to make a file anonymous (unnamed), but continue to use it like an anonymous memory map. Again I know both are possible on Windows through other means, but I am trying to find a way that is portable across platforms.
You can achieve this by using a combination of CreateFile, CreateFileMapping and MapViewOfFile calls. MapViewOfFile will give you a memory-mapped buffer of the file backed by the file on disk.
Following code when executed from different processes, will write the process id of last closing process in the file at c:\temp\temp.txt
int main()
{
TCHAR szMsg[256];
HANDLE hMapFile;
LPCTSTR pBuf;
HANDLE hFile = CreateFileW(
L"C:\\Temp\\temp.txt",
GENERIC_WRITE|GENERIC_READ,
FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,NULL);
hMapFile = CreateFileMapping(
hFile, // Handle of file opened with rw access
NULL, // default security
PAGE_READWRITE, // read/write access
0, // maximum object size (high-order DWORD)
BUF_SIZE, // maximum object size (low-order DWORD)
szName); // name of mapping object
if (hMapFile == NULL)
{
printf( "Could not create file mapping object (%d).\n", GetLastError());
return 1;
}
pBuf = (LPTSTR) MapViewOfFile(hMapFile, // handle to map object
FILE_MAP_ALL_ACCESS, // read/write permission
0,
0,
BUF_SIZE);
if (pBuf == NULL)
{
printf("Could not map view of file (%d).\n", GetLastError());
CloseHandle(hMapFile);
return 1;
}
wsprintfW(szMsg, L"This is process id %d", GetCurrentProcessId());
CopyMemory((PVOID)pBuf, szMsg, (wcslen(szMsg) * sizeof(TCHAR)));
MessageBoxW(NULL, szMsg, L"Check", MB_OK);
UnmapViewOfFile(pBuf);
CloseHandle(hMapFile);
CloseHandle(hFile);
return 0;
}
Make sure you open the file with GENERIC_READ|GENERIC_WRITE access and allow FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE access to subsequent opens.
Also note the use of CREATE_ALWAYS in CreateFile which will delete the old file and open a new one every-time CreateFile is called. This is the 'unlink' effect you talk about.
Code inspired from Creating Named Shared Memory at msdn.
Can someone please tell me how to go about creating a maximum of N instances of an application in MFC?
Also, if N instances are running, and one instance gets closed, then one new instance can be created (but no more than N instances can run at any one time).
Thank you in advance.
a.
You can create a global semaphore that up to n process instances can enter. The n+1 th instance of your process will fail to enter the semaphore. Of course you should select a short timeout for the locking operation so you can present a meaningful feedback to the user.
For the semaphore stuff you can take a look at MSDN.
I'd use lock files. In your CMyApp::InitInstance() add:
CString Path;
// better get the path to the global app data or local user app data folder,
// depending on if you want to allow the three instances machine-wide or per user.
// Windows' file system virtualization will make GetModuleFileName() per user:
DWORD dw = GetModuleFileName(m_hInstance,
Path.GetBuffer(MAX_PATH), MAX_PATH);
Path.ReleaseBuffer();
// strip "exe" from filename and replace it with "lock"
Path = Path.Left(Path.GetLength()-3) + T("lock");
int i;
// better have the locking file in your class and do a clean Close on ExitInstance()!
CFile *pLockingFile = NULL;
for (i = 0; i < 3; i++) // restrict to three instances
{
CString Counter;
Counter.Format(T("%d"), i);
TRY
{
pLockingFile = new CFile(Path + Counter,
CFile::modeCreate|CFile::modeWrite|CFile::shareExclusive);
pLockingFile.Close();
break; // got an instance slot
}
CATCH( CFileException, e )
{
// maybe do something else here, if file open fails
}
END_CATCH
if (i >= 3)
return TRUE; // close instance, no slot available
}
Edit: To lock the software machine-wide, retrieve the common app folder using the following function instead of calling GetModuleFileName().
#pragma warning(disable: 4996) // no risk, no fun
BOOL GetCommonAppDataPath(char *path)
{
*path = '\0';
if (SHGetSpecialFolderPath(NULL, path, CSIDL_COMMON_APPDATA, TRUE))
{
strcat(path, T("\\MyApplication")); // usually found under C:\ProgramData\MyApplication
DWORD dwFileStat = GetFileAttributes(path);
if (dwFileStat == 0xffffffff) // no MyApplication directory yet?
CreateDirectory(path, NULL); // create it
dwFileStat = GetFileAttributes(path); // 2nd try, just to be sure
if (dwFileStat == 0xffffffff || !(dwFileStat & FILE_ATTRIBUTE_DIRECTORY))
return FALSE;
return TRUE;
}
return FALSE;
}
Note: This will only work from Vista on. If you have XP, writing to a global directory is an easy task, e.g. C:\Windows\Temp. I have put the function in a helper dll I only load if the OS is Vista or higher. Otherwise your software won't start because of unresolved references in system dlls.
I'm trying to write a windows batch file in order to resume a windows process that gets Suspended. I'm using pssuspend (from pstools) to resume the process. However, I'm trying to write windows batch file script that will continually get the status of a process (e.g. myExe.exe). If the script is not suspended, I would like for it to keep checking if it is suspended. If it is suspended, I would like it to run the pssuspend code. I'm unsure how to obtain the Suspend status. So far I have this:
if myExe.exe == "Suspend" (
pssuspend -r myExe.exe
suspend_fix.bat
) else (
suspend_fix.bat
)
Thanks for your help!
Windows services (that are created with the right attributes) can be suspended, but I am not sure how an executable can be suspended, or what exactly you mean by that.
If you mean that the program has been stopped, and when it does, you want to restart it, then here are a couple of code blocks that I have used to determine if a program is running:
1) by checking to see if the exe name exists, i.e., is running.
By the way, I recommend this one from my interpretation of your post:
BOOL ExeExists(char *exe)
{
HANDLE pss = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);
PROCESSENTRY32 pe = { 0 };
pe.dwSize = sizeof(pe);
if (Process32First(pss, &pe))
{
do
{
if (strstr(pe.szExeFile,exe))
{
CloseHandle(pss);
return TRUE;
}
}
while(Process32Next(pss, &pe));
}
CloseHandle(pss);
return FALSE;
}
2) by checking to see if the PID exists
BOOL PidExists(int pid)
{
HANDLE pss = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);
PROCESSENTRY32 pe = { 0 };
pe.dwSize = sizeof(pe);
if (Process32First(pss, &pe))
{
do
{
if (pe.th32ProcessID == pid)
{
CloseHandle(pss);
return TRUE;
}
}
while(Process32Next(pss, &pe));
}
CloseHandle(pss);
return FALSE;
}
By the way this is used to get the process ID (it is defined in winbase.h)
of the application making the call.
int GetProcessIdApp(void)
{
return GetProcessId(GetCurrentProcess());//defined in WinBase.h
}
Inside WinBase.h
WINBASEAPI
DWORD
WINAPI
GetProcessId(
__in HANDLE Process
);
In my scenario, An application broadcasts its PID at start up, such that
my monitoring program (the Windows service) can read it, then use it to make an ongoing determination of the application's status. If the app is discovered to be dead, and if other criteria indicate it should still be running, my service will start it back up.
I have read tons of SO questions on this matter, but I didn't find a real definitive guide for doing this the right way.
My goal is to enumerate [disconnected and active] user console sessions and start a process in each one of them. Every user session process requires at least these rights in its DACL :
Token access rights :
TOKEN_QUERY (for GetTokenInformation())
TOKEN_QUERY_SOURCE (for GetTokenInformation())
Process access rights :
PROCESS_QUERY_INFORMATION (for OpenProcessToken())
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ (for GetModuleFileNameEx())
PROCESS_VM_OPERATION (used with GetTokenInformation() to get other processes' username later with LookupAccountSid())
But as you can read here (at the bottom) : "Windows Vista introduces protected processes to enhance support for Digital Rights Management. The system restricts access to protected processes and the threads of protected processes."
So I thought maybe only with PROCESS_QUERY_LIMITED_INFORMATION I can get some information about other processes. I tried QueryFullProcessImageName() for elevated processes starting from Vista (see Giori's answer) but it doesn't work anymore as it seems.
Solution : CreateProcessAs_LOCAL_SYSTEM using a duplicated token of the Windows service.
Problem : The spawned processes should have the respective logged on user's environment variables set to be able to locate network printers and mapped drives among other things. But if I use the service's token I inherit its PEB and I can't even translate the mapped drives to their UNC paths.
So I started looking for ways to "elevate" the process and bypassing the UAC prompt, I tried :
Enabling some privileges like SE_DEBUG_PRIVILEGE in the token using AdjustTokenPrivileges() (does not work if the token does not have those privileges, verification can be done first using LookUpPrivilegeValue())
using the token from winlogon.exe. (does not work)
Changing the DACL (source code) (didn't work)
The steps I'm following are :
Enumerate sessions using WTSEnumerateSessions()
Get the token (two choices) :
SYSTEM token : OpenProcessToken(GetCurrentProcess(),TokenAccessLevels.MaximumAllowed, out hProcessToken)
User token : WTSQueryUserToken(sessionId, out hUserToken)
Duplicate the token using DuplicateTokenEx()
LookUpPrivilegeValue() / AdjustTokenPrivileges() (useless ?)
CreateEnvironmentBlock()
CreateProccessAsUser(), flags : NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, Startup info's desktop : "WinSta0\Default"
Change process DACL (see link above, useless ?)
Dispose/Clean : destroy PEB created, close opened handles and free memory.
My question : how to grant the process created using CreateProccessAsUser() from a Windows Service running under LOCAL_SYSTEM account enough privileges/rights to get information on other processes (from other sessions; of other users and different integrity levels) without losing the user's environment variables ?
You're confused about a number of things.
Every user session process requires at least these rights in its DACL
The process DACL controls access to that process, it does not determine what access that process has. The security token for the process determines access rights.
Windows Vista introduces protected processes to enhance support for Digital Rights Management.
It seems clear that you haven't gotten far enough to worry about protected processes yet. Get it to work for ordinary processes first!
The spawned processes should have the respective logged on user's environment variables set to be able to locate network printers and mapped drives among other things.
Network printers and mapped drives have nothing to do with environment variables. I think what you're trying to do is to put the new process into the user's logon session, that's what controls network drive mappings and the like.
how to grant the process created using CreateProccessAsUser() [...] enough privileges/rights to get information on other processes (from other sessions; of other users and different integrity levels) without losing the user's environment variables ?
Don't. This would violate the integrity of the security model.
Instead, enumerate and query processes from the system service, and pass only whatever information is necessary to the user session processes, using shared memory (look up "file mapping object" in MSDN) or another suitable IPC mechanism.
I know that this has been asked a while ago. Since I happened to have been doing the same, below is the working pseudo-code.
First, how to run a process in a user session from a service:
//IMPORTANT: All error checks are omitted for brevity!
// Each of the lines of code below MUST be
// checked for possible errors!!!
//INFO: The following pseudo-code is intended to run
// from the Windows local service.
DWORD dwSessionID; //Session ID to run your user process in
//Get token for the session ID
HANDLE hToken;
WTSQueryUserToken(dwSessionID, &hToken);
//Duplicate this token
HANDLE hToken2;
DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hToken2);
PSID gpSidMIL_High = NULL;
if(you_want_to_change_integrity_level_for_user_process)
{
if(!Windows_XP)
{
//For example, create "high" mandaroty integrity level SID
::ConvertStringSidToSid(L"S-1-16-12288", &gpSidMIL_High);
TOKEN_MANDATORY_LABEL tml = {0};
tml.Label.Attributes = SE_GROUP_INTEGRITY;
tml.Label.Sid = gpSidMIL_High;
SetTokenInformation(hToken2, TokenIntegrityLevel, &tml,
sizeof(TOKEN_MANDATORY_LABEL) + ::GetSidLengthRequired(1));
}
}
//Copy environment strings
LPVOID pEnvBlock = NULL;
CreateEnvironmentBlock(&pEnvBlock, hToken2, FALSE);
//Initialize the STARTUPINFO structure.
// Specify that the process runs in the interactive desktop.
STARTUPINFO si;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = _T("winsta0\\default");
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
//Create non-const buffer
TCHAR pBuffCmdLine[MAX_PATH];
pBuffCmdLine[0] = 0;
//Copy process path & parameters to the non-constant buffer
StringCchCopy(pBuffCmdLine, MAX_PATH, L"\"C:\\Program Files (x86)\\Company\\Brand\\process.exe\" -parameter");
//Impersonate the user
ImpersonateLoggedOnUser(hToken2);
//Launch the process in the user session.
bResult = CreateProcessAsUser(
hToken2, // client's access token
L"C:\\Program Files (x86)\\Company\\Brand\\process.exe", // file to execute
pBuffCmdLine[0] != 0 ? pBuffCmdLine : NULL, // command line
NULL, // pointer to process SECURITY_ATTRIBUTES
NULL, // pointer to thread SECURITY_ATTRIBUTES
FALSE, // handles are not inheritable
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, // creation flags
pEnvBlock, // pointer to new environment block
NULL, // name of current directory
&si, // pointer to STARTUPINFO structure
&pi // receives information about new process
);
//Get last error
nOSError = GetLastError();
//Revert to self
RevertToSelf();
//At this point you may want to wait for the user process to start, etc.
//using its handle in `pi.hProcess`
...
//Otherwise, close handles
if(pi.hProcess)
CloseHandle(pi.hProcess);
if(pi.hThread)
CloseHandle(pi.hThread);
//Clean-up
if(pEnvBlock)
DestroyEnvironmentBlock(pEnvBlock);
CloseHandle(hToken2);
CloseHandle(hToken);
if(gpSidMIL_High)
::LocalFree(gpSidMIL_High);
If you need to run your process in all sessions with a logged in interactive user, you can run the method I gave above for the sessions that you can obtain from the following enumeration:
//Enumerate all sessions
WTS_SESSION_INFO* pWSI = NULL;
DWORD nCntWSI = 0;
if(WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, NULL, 1, &pWSI, &nCntWSI))
{
//Go through all sessions
for(DWORD i = 0; i < nCntWSI; i++)
{
//To select logged in interactive user session,
//try to get its name. If you get something, then
//this session has a user logged in to...
LPTSTR pUserName = NULL;
DWORD dwcbSzUserName = 0;
if(WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE,
pWSI[i].SessionId,
WTSUserName, &pUserName, &dwcbSzUserName) &&
pUserName &&
dwcbSzUserName >= sizeof(TCHAR) &&
pUserName[0] != 0)
{
//Use my method above to run your user process
// in this session.
DWORD dwSessionID = pWSI[i].SessionId;
}
//Free mem
if(pUserName)
WTSFreeMemory(pUserName);
}
//Free mem
WTSFreeMemory(pWSI);
}
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.