How to convert full process path to only process name ?

Hi all!
I have used IoQueryFileDosDeviceName() to get full path of a process. Now my question is how to convert this full path (c:\myfolder\ApacheOpenOfficeEng.exe) to process name (ApacheOpenOfficeEng.exe)

I already try psGetProcessImageFileName() but only 14 characters (ApacheOpenOffi) is printed because this fonction is limited to the first 16 (ASCII) characters of the image file name.
Sorry for my english.
Thank advance.

#define OBJ_NAME_PATH_SEPARATOR ((WCHAR) L'\\')

/// <summary>
/// Gets the last segment of the given string.
/// </summary>
/// <param name="Delimiter">The delimiter.</param>
/// <param name="Buffer">The buffer.</param>
/// <param name="Length">The length.</param>
wchar_t* StringUtils::GetLastSegment(wchar_t Delimiter, wchar_t* Buffer, SIZE_T Length)
{
	if (Buffer == NULL || Length == 0)
	{
		return NULL;
	}

	for (SIZE_T I = Length - 1; I > 0; I--)
	{
		if (Buffer[I] == Delimiter)
		{
			return &Buffer[I + 1];
		}
	}

	return Buffer;
}

/// <summary>
/// Gets the image file path of the given process.
/// </summary>
/// <param name="ProcessId">The process.</param>
/// <param name="OutProcessName">The process image file path.</param>
NTSTATUS ProcessUtils::GetProcessImageFilePath(CONST PEPROCESS Process, OUT WCHAR OutProcessName[MAXIMUM_FILENAME_LENGTH])
{
	NTSTATUS Status = { };

	// 
	// Verify the passed arguments.
	// 

	if (Process == NULL)
	{
		return STATUS_INVALID_PARAMETER_1;
	}

	if (OutProcessName == NULL)
	{
		return STATUS_INVALID_PARAMETER_2;
	}

	// 
	// If it's PID 0, handle it separately.
	// 

	if (PsGetProcessId(Process) == 0)
	{
		constexpr WCHAR IdleProcessName[] = L"System Idle Process";
		RtlCopyMemory(&OutProcessName[0], IdleProcessName, sizeof(IdleProcessName));
		return STATUS_SUCCESS;
	}

	// 
	// If it's PID 4 / System Process, handle it separately.
	// 

	if (Process == PsInitialSystemProcess)
	{
		constexpr WCHAR SystemProcessName[] = L"System Process";
		RtlCopyMemory(&OutProcessName[0], SystemProcessName, sizeof(SystemProcessName));
		return STATUS_SUCCESS;
	}

	// 
	// Open a handle to the process.
	// 

	auto* ProcessHandle = ZwCurrentProcess();

	if (PsGetCurrentProcess() != Process)
	{
		// 
		// Open a handle to the process.
		// 

		if (!NT_SUCCESS(Status = ObOpenObjectByPointer(Process, OBJ_KERNEL_HANDLE, NULL, GENERIC_ALL, *PsProcessType, KernelMode, &ProcessHandle)))
		{
			return Status;
		}
	}

	// 
	// Retrieve the name of the process with the given PID.
	// 

	ULONG ReturnedLength = 0;
	BYTE Buffer[MAXIMUM_FILENAME_LENGTH + sizeof(UNICODE_STRING)] = { };
	UNICODE_STRING* ProcessName = (UNICODE_STRING*) Buffer;

	Status = ZwQueryInformationProcess(ProcessHandle, ProcessImageFileName, &Buffer, sizeof(Buffer), &ReturnedLength);

	// 
	// Close the handle to the process.
	// 

	if (ProcessHandle != ZwCurrentProcess())
	{
		ZwClose(ProcessHandle);
	}

	// 
	// If the query was successful, copy the image filename to the caller provided buffer.
	// 

	if (NT_SUCCESS(Status))
	{
		RtlZeroMemory(&OutProcessName[0], max(sizeof(OutProcessName), ProcessName->Length + sizeof(WCHAR)));
		RtlCopyMemory(&OutProcessName[0], ProcessName->Buffer, ProcessName->Length);
	}
	
	return Status;
}

/// <summary>
/// Gets the image filename of the given process.
/// </summary>
/// <param name="ProcessId">The process.</param>
/// <param name="OutProcessName">The process image filename.</param>
NTSTATUS ProcessUtils::GetProcessImageFileName(CONST PEPROCESS Process, OUT WCHAR OutProcessName[MAXIMUM_FILENAME_LENGTH])
{
	NTSTATUS Status = { };

	// 
	// Verify the passed arguments.
	// 

	if (Process == NULL)
	{
		return STATUS_INVALID_PARAMETER_1;
	}

	if (OutProcessName == NULL)
	{
		return STATUS_INVALID_PARAMETER_2;
	}

	// 
	// Retrieve the process's full image file path.
	// 

	WCHAR FullProcessFilePath[MAXIMUM_FILENAME_LENGTH];

	if (!NT_SUCCESS(Status = GetProcessImageFilePath(Process, &FullProcessFilePath[0])))
	{
		return Status;
	}

	// 
	// Format the path to only get the actual filename with its extension.
	// 
	
	SIZE_T FilePathLength = StringUtils::Length(&FullProcessFilePath[0]);
	WCHAR* FileNameWithoutExtension = StringUtils::GetLastSegment(OBJ_NAME_PATH_SEPARATOR, &FullProcessFilePath[0], FilePathLength);
	ULONG  FileNameOffset = FileNameWithoutExtension != NULL ? (((ULONG_PTR) FileNameWithoutExtension - (ULONG_PTR) &FullProcessFilePath[0]) / 2) : 0;

	if (FileNameWithoutExtension != NULL)
	{
		StringUtils::Copy(&OutProcessName[0], MAXIMUM_FILENAME_LENGTH, FileNameWithoutExtension);
		OutProcessName[FilePathLength + FileNameOffset] = NULL;
	}
	else
	{
		RtlCopyMemory(&OutProcessName[0], &FullProcessFilePath[0], FilePathLength * sizeof(WCHAR));
	}

	return STATUS_SUCCESS;
}

Many thank to you ThatsBerkan.
I will try you solution.

Did you REALLY need to ask this question? Do you honestly mean to say that, given a full path string, you do not know how to find the file name part? If that’s true, then I don’t want your code anywhere near the kernel.

I’m astounded by this question.

@Tim_Roberts said:
Did you REALLY need to ask this question? Do you honestly mean to say that, given a full path string, you do not know how to find the file name part? If that’s true, then I don’t want your code anywhere near the kernel.

I’m astounded by this question.

Hello Sir Tim_Roberts.
Sorry if my question has astounded you. But the normal way i always used to get only process name like (explorer.exe) is to call psGetProcessImageFileName() and this work fine. But the problem i’m now facing is that the process name lenght is up than 23 characters (ApacheOpenOfficeEng.exe). So when i use psGetProcessImageFileName(), on part of process name is printed like (ApacheOpenOffi).
Below is a part of my code.

status = PsReferenceProcessFilePointer(PsGetCurrentProcess(), &pFilePoint);
if (!NT_SUCCESS(status))
{
return OB_PREOP_SUCCESS;
}
status = IoQueryFileDosDeviceName(pFilePoint, &pObjectNameInfo); //Full path of process is ok
if (!NT_SUCCESS(status))
{
ObDereferenceObject(pFilePoint);
return OB_PREOP_SUCCESS;
}

/*

Many lines of code has been skipped

*/

status = PsLookupProcessByProcessId(TargetPid, &eProcess);

          if (!NT_SUCCESS(status))
          {                
            ObDereferenceObject(eProcess);
            RtlFreeUnicodeString(&UpCaseProcNameUS); 
            RtlFreeUnicodeString(&UpCaseMainProcNameUS); 	
            return OB_PREOP_SUCCESS;
          }   
          
         CaptPsProcImageName = (char*)PsGetProcessImageFileName(eProcess); // **This function only print 14 characters**

If you have a string containing an executable’s full path name, then you search backwards from the end to find the last backslash. Everything after that point is the executable’s file name. You don’t need any APIs. How is that not painfully obvious?

@Tim_Roberts said:
If you have a string containing an executable’s full path name, then you search backwards from the end to find the last backslash. Everything after that point is the executable’s file name. You don’t need any APIs. How is that not painfully obvious?

Mr. Tim Roberts, thank you for giving me the idea to solve my problem. I will apply it.

My problem is ** successfully SOLVED**.

I thank Mr. Tim Roberts for giving me the idea. Also, many thank for Mr. ThatsBerkan for his source code.
I love osr community.

Certainly be careful of the path specifier characters that you use. I have never done anything like this in KM (hardly any need to do string parsing there), but CreateFile specifically documents that either slashes may be used.

https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea

The name of the file or device to be created or opened. You may use either forward slashes (/) or backslashes () in this name.

I do not know if someone will normalize the buffer that you are looking at before you see it, but adequately guarding against garbage input is a must.