How do I determine the (local-) path for the "Program Files" directory on a remote computer? There does not appear to any version of SHGetFolderPath (or related function) that takes the name of a remote computer as a parameter.
I guess I could try to query HKLM\Software\Microsoft\Windows\CurrentVersion\ProgramFilesDir using remote-registry, but I was hoping there would be "documented" way of doing it.
Many of the standard paths require a user to be logged in, especially the SH* functions as those are provided by the "shell", that is, Explorer. I suspect the only way you're going to get the right path is through the registry like you already mentioned.
This is what I ended up doing: (pszComputer must be on the form "\\name". nPath is size of pszPath (in TCHARs))
DWORD GetProgramFilesDir(PCTSTR pszComputer, PTSTR pszPath, DWORD& nPath)
{
DWORD n;
HKEY hHKLM;
if ((n = RegConnectRegistry(pszComputer, HKEY_LOCAL_MACHINE, &hHKLM)) == ERROR_SUCCESS)
{
HKEY hWin;
if ((n = RegOpenKeyEx(hHKLM, _T("Software\\Microsoft\\Windows\\CurrentVersion"), 0, KEY_READ, &hWin)) == ERROR_SUCCESS)
{
DWORD nType, cbPath = nPath * sizeof(TCHAR);
n = RegQueryValueEx(hWin, _T("ProgramFilesDir"), NULL, &nType, reinterpret_cast<PBYTE>(pszPath), &cbPath);
nPath = cbPath / sizeof(TCHAR);
RegCloseKey(hWin);
}
RegCloseKey(hHKLM);
}
return n;
}
Related
I am aware that GetFinalPathNameByHandle can be used to obtain the target of a symbolic link or a reparse point, but there are situations where its use is not desirable:
If the target is not available, doesn't exist or cannot be opened, CreateFile on a symlink fails, and thus the path cannot be obtained.
If I point a symlink "a" to file "b" and create a symlink "b" to file "c", the function follows the whole chain, returning "c".
The function is not useful much when I already have the handle to the actual symlink at hand.
It seems that DeviceIoControl can be used together with FSCTL_GET_REPARSE_POINT to obtain the actual reparse data of the file, but that gets me the REPARSE_DATA_BUFFER, and I would have to parse that.
I don't know how the system actually processes reparse points, but I think that the target location is a piece of information that should be available at some point. The dir command, for example, can display the target path correctly for any reparse point... well I have already seen it handle just symlinks and mount points (junctions).
how the system actually processes reparse points
this is done inside file system and file system filter drivers. result depend from are FILE_FLAG_OPEN_REPARSE_POINT option used in call CreateFile (or FILE_OPEN_REPARSE_POINT in NT calls).
when FILE_FLAG_OPEN_REPARSE_POINT is specified - file system bypass normal reparse point processing for the file and attempts to directly open the reparse point file as is.
If the FILE_OPEN_REPARSE_POINT flag is not specified - file-system attempts to open a file to which reparse point is point (if fs understand format of reparse point - primary only Microsoft reparse points)
the data format saved in reparse point is REPARSE_DATA_BUFFER (Microsoft reparse point format) or REPARSE_GUID_DATA_BUFFER - need look for ReparseTag at begin.
to determine whether a reparse point tag corresponds to a tag owned by Microsoft we use IsReparseTagMicrosoft macro.
code for test/print reparse point data:
volatile UCHAR guz;
ULONG TestReparsePoint(PCWSTR FileName)
{
HANDLE hFile = CreateFile(FileName, 0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, 0);
if (hFile == INVALID_HANDLE_VALUE)
{
return GetLastError();
}
union {
PVOID pv;
PULONG ReparseTag;
PREPARSE_DATA_BUFFER prdb;
PREPARSE_GUID_DATA_BUFFER prgdb;
};
PVOID stack = alloca(guz);
ULONG cb = 0, rcb = sizeof(REPARSE_DATA_BUFFER) + 0x100, BytesReturned;
ULONG dwError;
do
{
if (cb < rcb) cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
if (DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, 0, 0, pv, cb, &BytesReturned, 0))
{
dwError = NOERROR;
if (IsReparseTagMicrosoft(*ReparseTag))
{
char cc[16];
LPCSTR name;
switch (*ReparseTag)
{
case IO_REPARSE_TAG_SYMLINK:
name = " SYMLINK";
stack = prdb->SymbolicLinkReparseBuffer.PathBuffer;
break;
case IO_REPARSE_TAG_MOUNT_POINT:
name = " MOUNT_POINT";
stack = prdb->MountPointReparseBuffer.PathBuffer;
break;
default:
sprintf(cc, " %08x", prdb->ReparseTag);
name = cc;
}
DbgPrint(" %s->%.*S <%.*S>\n", name,
prdb->MountPointReparseBuffer.SubstituteNameLength >> 1,
RtlOffsetToPointer(stack, prdb->MountPointReparseBuffer.SubstituteNameOffset),
prdb->MountPointReparseBuffer.PrintNameLength >> 1,
RtlOffsetToPointer(stack, prdb->MountPointReparseBuffer.PrintNameOffset)
);
}
else
{
PGUID g = &prgdb->ReparseGuid;
DbgPrint(" tag=%x {%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x} size=%x\n", *ReparseTag,
g->Data1, g->Data2, g->Data3,
g->Data4[0],g->Data4[1],g->Data4[2],g->Data4[3],g->Data4[4],g->Data4[5],g->Data4[6],g->Data4[7],
prgdb->ReparseDataLength);
}
break;
}
rcb = IsReparseTagMicrosoft(*ReparseTag)
? REPARSE_DATA_BUFFER_HEADER_SIZE + prdb->ReparseDataLength
: REPARSE_GUID_DATA_BUFFER_HEADER_SIZE + prgdb->ReparseDataLength;
} while((dwError = GetLastError()) == ERROR_MORE_DATA);
CloseHandle(hFile);
return dwError;
}
Microsoft reparse points can be read with REPARSE_DATA_BUFFER instead. The MS open protocol specification might also be useful.
Parsing other GUID based tags can only be done if you know the format.
I'm trying to enumerate 32bit process modules names from 64bit application using the following code:
if (EnumProcessModulesEx(hProcess, hMods, sizeof(hMods), &cbNeeded, LIST_MODULES_ALL))
{
for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++)
{
TCHAR szModName[MAX_PATH] = { 0 };
if (GetModuleFileNameEx(hProcess, hMods[i], szModName,
sizeof(szModName) / sizeof(TCHAR)))
{
printf("module name is: %S", szModName);
}
}
}
The code works as expected in Windows 7, as part of the results are:
...
C:\Windows\**SysWOW64**\ntdll.dll
...
In Windows 10 the above code returns the full path but with System32 instead of SysWOW64. e.g,
...
C:\Windows\**System32**\ntdll.dll
...
Looking deeper for the cause, I notice that GetModuleFileNameEx reads the remote process PEB and LDR_TABLE_ENTRY, and starting from Windows 10 the LDR_TABLE_ENTRY contains the full path with System32 and not SysWOW64 - also for 32bit applications.
I also tried to use GetMappedFileName but it isn't straight forward and efficient to translate the path from dos path (\device\harddiskvolume) to standard (c:\) path.
I wonder if there are any other easy way to extract the full syswow64 path.
for get valid win32 file path from file nt-path - simplest way - add L"\\\\?\\globalroot" (\\?\globalroot) prefix. this is because CreateFileW looked from \??\ directory and globalroot is symbolic link in \??\ which let as to jump to root of nt namespace.
for example - \Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll is nt absolute path. and \\?\globalroot\Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll is valid win32 path for CreateFileW - this api convert well known prefix \\?\ to nt prefix \??\ and pass name \??\globalroot\Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll to kernel. when parsing this name - after process symbolic link globalroot which point to root of namespace - we again got \Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll - correct nt path.
so if we need valid win32 path for use in CreateFileW - simply append this prefix to nt path. however some shell32 api not accept this form path. also it not nice looked in UI. if we want got DOS drive letter form path (this is subset of valid win32 paths) - we can use IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH which convert device name to drive letter. this ioctl take as input MOUNTDEV_NAME (declared in mountmgr.h) and output buffer is MOUNTMGR_VOLUME_PATHS. in MOUNTDEV_NAME buffer must be exactly device name, without file path. so we need break returned nt path to 2 components. for example in \Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll :
\Device\HarddiskVolume9 - device path
\Windows\SysWOW64\ntdll.dll - file system path
correct way here first open file and call GetFileInformationByHandleEx with FileNameInfo - we got file system path in output. with this we can use wcsstr for separate device path. also if we open file handle - we can use it in call GetFinalPathNameByHandleW with VOLUME_NAME_DOS. this api do exactly which we will be do - query file path, separate device path and call IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH. + open/close mount manager.
but usual nt file path begin from \Device\HarddiskVolumeX. this allow first try fast way - avoid open file and query it path.
so first we need open mount manager:
#include <mountmgr.h>
HANDLE hMountManager = CreateFile(MOUNTMGR_DOS_DEVICE_NAME,
0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);
then we can run next code:
void dumpModules(HANDLE hMountManager, HANDLE hProcess)
{
ULONG cb = 0, cbNeeded = 16;
volatile static UCHAR guz;
PVOID stack = alloca(guz);
HMODULE *hMods, hmod;
__continue:
// cumulative allocate memory in stack, not need free it
cb = RtlPointerToOffset(hMods = (HMODULE*)alloca(cbNeeded - cb), stack);
if (EnumProcessModulesEx(hProcess, hMods, cb, &cbNeeded, LIST_MODULES_32BIT))
{
if (cb < cbNeeded)
{
goto __continue;
}
if (cbNeeded /= sizeof(HMODULE))
{
//i use hard coded size buffers, for reduce code and show main idea
#define FILE_NAME_INFO_buffer_size FIELD_OFFSET(FILE_NAME_INFO, FileName[MAX_PATH])
#define MOUNTDEV_NAME_buffer_size FIELD_OFFSET(MOUNTDEV_NAME, Name[MAX_PATH])
#define MOUNTMGR_VOLUME_PATHS_buffer_size FIELD_OFFSET(MOUNTMGR_VOLUME_PATHS, MultiSz[64])
// + space for 0 at the end
PFILE_NAME_INFO pfni = (PFILE_NAME_INFO)alloca(FILE_NAME_INFO_buffer_size + sizeof(WCHAR));
PMOUNTMGR_VOLUME_PATHS pmvp = (PMOUNTMGR_VOLUME_PATHS)alloca(MOUNTMGR_VOLUME_PATHS_buffer_size);
PMOUNTDEV_NAME pmdn = (PMOUNTDEV_NAME)alloca(MOUNTDEV_NAME_buffer_size);
static WCHAR globalroot[] = L"\\\\.\\globalroot";
alloca(sizeof(globalroot));
PWSTR win32Path = pmdn->Name - RTL_NUMBER_OF(globalroot) + 1;
memcpy(win32Path, globalroot, sizeof(globalroot));
USHORT NameLength = pmdn->NameLength;
do
{
hmod = *hMods++;
if (GetMappedFileNameW(hProcess, hmod, pmdn->Name, MAX_PATH))
{
DbgPrint("%p %S\n",hmod, pmdn->Name);
PWSTR c = 0;
static const WCHAR HarddiskVolume[] = L"\\Device\\HarddiskVolume";
// fast way
if (!memcmp(pmdn->Name, HarddiskVolume, sizeof(HarddiskVolume) - sizeof(WCHAR)))
{
c = wcschr(pmdn->Name + RTL_NUMBER_OF(HarddiskVolume) - 1, '\\');
}
// else - for demo
{
pmdn->NameLength = NameLength;
HANDLE hFile = CreateFile(win32Path, 0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
//++ just for demo
WCHAR DosPath[MAX_PATH];
if (GetFinalPathNameByHandleW(hFile, DosPath, RTL_NUMBER_OF(DosPath), VOLUME_NAME_DOS))
{
DbgPrint("%S\n", DosPath);
}
RtlGetLastNtStatus();
//-- just for demo
BOOL fOk = GetFileInformationByHandleEx(hFile, FileNameInfo, pfni, FILE_NAME_INFO_buffer_size);
CloseHandle(hFile);
if (fOk)
{
// FileName not 0 terminated
pfni->FileName[pfni->FileNameLength/sizeof(WCHAR)] = 0;
c = wcsstr(pmdn->Name, pfni->FileName);
}
}
}
if (c)
{
pmdn->NameLength = (USHORT)RtlPointerToOffset(pmdn->Name, c);
if (DeviceIoControl(hMountManager, IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH,
pmdn, MOUNTDEV_NAME_buffer_size,
pmvp, MOUNTMGR_VOLUME_PATHS_buffer_size, &cb, NULL))
{
DbgPrint("%S%S\n", pmvp->MultiSz, c);
}
}
}
} while (--cbNeeded);
}
}
}
and demo output for notepad:
0000000000170000 \Device\HarddiskVolume9\Windows\SysWOW64\notepad.exe
\\?\C:\Windows\SysWOW64\notepad.exe
C:\Windows\SysWOW64\notepad.exe
0000000077A90000 \Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll
\\?\C:\Windows\SysWOW64\ntdll.dll
0000000075460000 \Device\HarddiskVolume9\Windows\SysWOW64\kernel32.dll
\\?\C:\Windows\SysWOW64\kernel32.dll
C:\Windows\SysWOW64\kernel32.dll
0000000074A30000 \Device\HarddiskVolume9\Windows\SysWOW64\KernelBase.dll
\\?\C:\Windows\SysWOW64\KernelBase.dll
C:\Windows\SysWOW64\KernelBase.dll
00000000749B0000 \Device\HarddiskVolume9\Windows\SysWOW64\advapi32.dll
\\?\C:\Windows\SysWOW64\advapi32.dll
I writing small application in pure C++. But now I encourage strange problem. I wanted to add my application to autostart but it not working. I use this code to access to Registry:
BOOL SetKeyData(HKEY hRootKey, WCHAR *subKey, DWORD dwType, WCHAR *value, LPBYTE data, DWORD cbData)
{
HKEY hKey;
if(RegCreateKeyW(hRootKey, subKey, &hKey) != ERROR_SUCCESS)
return FALSE;
LSTATUS status = RegSetValueExW(hKey, value, 0, dwType, data, cbData);
if(status != ERROR_SUCCESS)
{
RegCloseKey(hKey);
return FALSE;
}
RegCloseKey(hKey);
return TRUE;
}
At first I thought that problem is in data that I serve, so i converted WCHAR with path to LPBYTE like this and execute this function in this way:
size_t i;
char *pMBBuffer = (char *)malloc( MAX_PATH );
wcstombs_s(&i, pMBBuffer, MAX_PATH, my_program, MAX_PATH-1 );
SetKeyData(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", REG_SZ, L"zwApplication", (LPBYTE)pMBBuffer, i))
I get status code ERROR_ACCESS_DENIED. Maybe problem is policy in Windows 7, but I thought that I have full access to everything in HKEY_LOCAL_MACHINE. How to solve this problem?
Writing to HKEY_LOCAL_MACHINE requires that your app runs with elevated privileges. Which means your app would require to set this in its manifest file.
Without this, you can only write to HKEY_CURRENT_USER, or read from HKEY_LOCAL_MACHINE - but for your autostart requirement, that would work just as fine.
In win32 c++; is there a way to determine if a folder/file is accessible? You know how if you try to access a certain folder in the C:/Windows directory & you will get a popup saying "This folder is not accessible".
Maybe there is a file attribute constant that signifies that the file is private? Maybe something like FILE_ATTRIBUTE_PRIVATE?
WIN32_FIND_DATA dirData;
while (FindNextFile( dir, &dirData ) != 0 )
{
// I made the following constant up
if ( !(fileData.dwFileAttributes & FILE_ATTRIBUTE_PRIVATE) )
{
// file is accessible so store filepath
files.push_back( fileData.cFileName );
}
else // file is not accessible so dont store
}
Or is the only way to know by going:
dir = FindFirstFileEx( (LPCTSTR)directory.c_str(), FindExInfoStandard, &dirData, FindExSearchNameMatch, NULL, 0 );
if ( dir == ??? ) { the file is inaccessible } [/code]
Best thing to do is just try to access it.
You can calculate the access granted by the access control list for a particular user account, but this is quite complicated, and the permission could change after you do the access check. So just open the file and handle access denied errors.
It wouldn't be a flag on the file itself because different accounts may have access to different files/directories. Instead, windows uses ACL's (access control lists), which are data structures that determine who has access to what.
ACLs in windows can be used with just about anything that is referred to by a handle (files, directories, processes, mutexes, named pipes...). You can view file ACLs by going to properties of a file and view "Security" tab.
So in your app you don't really want to check for a flag, but to compare file's ACL against the user account under which your app is running. Check out AccessCheck Win32 function. I think it's exactly what you are looking for.
Personally, I've never used that function, but if you are looking for Win32 solution and you want a function call, that's probably your best bet. However, as others have pointed out, it might be too complicated. I've always used _access (or _waccess) which is part of CRT, uber easy to use, and you don't take a performance hit of acquiring a file handle only to close it (depending on how tight your loop is, those calls can actually add up).
int _access(
const char *path,
int mode
);
Simple to use:
http://msdn.microsoft.com/en-us/library/1w06ktdy%28v=vs.80%29.aspx
Yes Aaron Ballman you are a boss! Oh man oh man! Which I could use some useful expletives to expressives my joy right now.
https://blog.aaronballman.com/2011/08/how-to-check-access-rights/ is a link to the example of checking accesss rights in win32 on a fpath. And the explanation behind the poorly documented AccessCheck win32 function. Below is the code.
bool canAccessPath( LPCTSTR folderName, DWORD genericAccessRights )
{
bool bRet = 0;
DWORD length = 0;
if (!::GetFileSecurity( folderName, OWNER_SECURITY_INFORMATION|GROUP_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION, NULL, 0, &length ) &&
ERROR_INSUFFICIENT_BUFFER == ::GetLastError())
{
PSECURITY_DESCRIPTOR security = static_cast< PSECURITY_DESCRIPTOR >( ::malloc( length ) );
if (security && ::GetFileSecurity( folderName, OWNER_SECURITY_INFORMATION|GROUP_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION, security, length, &length ))
{
HANDLE hToken = NULL;
if (::OpenProcessToken( ::GetCurrentProcess(), TOKEN_IMPERSONATE|TOKEN_QUERY|TOKEN_DUPLICATE|STANDARD_RIGHTS_READ, &hToken ))
{
HANDLE hImpersonatedToken = NULL;
if (::DuplicateToken( hToken, SecurityImpersonation, &hImpersonatedToken ))
{
GENERIC_MAPPING mapping = { 0xFFFFFFFF };
PRIVILEGE_SET privileges = { 0 };
DWORD grantedAccess = 0, privilegesLength = sizeof( privileges );
BOOL result = FALSE;
mapping.GenericRead = FILE_GENERIC_READ;
mapping.GenericWrite = FILE_GENERIC_WRITE;
mapping.GenericExecute = FILE_GENERIC_EXECUTE;
mapping.GenericAll = FILE_ALL_ACCESS;
::MapGenericMask( &genericAccessRights, &mapping );
if (::AccessCheck( security, hImpersonatedToken, genericAccessRights,
&mapping, &privileges, &privilegesLength, &grantedAccess, &result ))
{
bRet = result == TRUE;
}
::CloseHandle( hImpersonatedToken );
}
::CloseHandle( hToken );
}
::free( security );
}
}
return bRet;
}
Don't be shy to pop into his website and check out his work. I am sure he has more interesting stuff.
I have a windows network (peer-2-peer) as well as Active Directory and I need to log the name of users who send any kind of print to the server.
I want to write a program to log their username and/or their respective IP and I'm familiar with c#.net and c++ but I haven't found any clue regarding how to solve my problem.
is there any sorts of way to catch their name by the help of WMI or should dirty my hand with APIs(but which API I don't have any idea)?
regards.
Those features are exposed under the Spooler API.
EnumJobs will enumerate all the current jobs for a given printer. It will return a JOB_INFO_1 struct, which includes the username associated with a given print job:
typedef struct _JOB_INFO_1 {
DWORD JobId;
LPTSTR pPrinterName;
LPTSTR pMachineName;
LPTSTR pUserName;
LPTSTR pDocument;
LPTSTR pDatatype;
LPTSTR pStatus;
DWORD Status;
DWORD Priority;
DWORD Position;
DWORD TotalPages;
DWORD PagesPrinted;
SYSTEMTIME Submitted;
}JOB_INFO_1, *PJOB_INFO_1;
If you'd prefer WMI, you can use wmic.exe with the /node switch (or your preferred variation) and the Win32_PrintJob class. Roughly:
c:\> wmic /node 10.0.0.1
wmic> SELECT * FROM Win32_PrintJob
...will return a struct with all the print job information for the selected server. You can filter as you wish with the WHERE clause.
I would go with using WMI. That gives you the ability to query printer batches of printers associated with your system as well as pull all supporting properties. It's as simple as...
System.Management.ObjectQuery oq = new System.Management.ObjectQuery("SELECT * FROM Win32_PrintJob");
...creating an WMI object searcher and enumerating through the results.
Here's an example:
WMI query printers
I've used this in the past and if it doesn't have all of what you need, it should at least take care of monitoring the print queues.
http://www.merrioncomputing.com
http://www.merrioncomputing.com/Download/PrintQueueWatch/PrinterQueueWatchLicensing.htm
Source code link (from the OP's comment):
http://www.codeproject.com/KB/printing/printwatchvbnet.aspx
Find out which user has sent print job using C++ in Windows.
#include <WinSpool.h>
wstring GetUserNameFromPrintJob(wstring m_strFriendlyName)
{
wstring strDocName = L"";
wstring strMachineName = L"";
wstring strUserName = L"";
HANDLE hPrinter ;
if ( OpenPrinter(const_cast<LPWSTR>(m_strFriendlyName.c_str()), &hPrinter, NULL) == 0 )
{
/*OpenPrinter call failed*/
}
DWORD dwBufsize = 0;
PRINTER_INFO_2* pinfo = 0;
GetPrinter(hPrinter, 2,(LPBYTE)pinfo, dwBufsize, &dwBufsize); //Get dwBufsize
PRINTER_INFO_2* pinfo2 = (PRINTER_INFO_2*)malloc(dwBufsize); //Allocate with dwBufsize
GetPrinter(hPrinter, 2,(LPBYTE)pinfo2, dwBufsize, &dwBufsize);
DWORD numJobs = pinfo2->cJobs;
free(pinfo2);
JOB_INFO_1 *pJobInfo = 0;
DWORD bytesNeeded = 0, jobsReturned = 0;
//Get info about jobs in queue.
EnumJobs(hPrinter, 0, numJobs, 1, (LPBYTE)pJobInfo, 0,&bytesNeeded,&jobsReturned);
pJobInfo = (JOB_INFO_1*) malloc(bytesNeeded);
EnumJobs(hPrinter, 0, numJobs, 1, (LPBYTE)pJobInfo, bytesNeeded, &bytesNeeded, &jobsReturned);
JOB_INFO_1 *pJobInfoInitial = pJobInfo;
for(unsigned short count = 0; count < jobsReturned; count++)
{
if (pJobInfo != NULL)
{
strUserName = pJobInfo->pUserName //username
strMachineName = pJobInfo->pMachineName; //machine name
strDocName = pJobInfo->pDocument; // Document name
DWORD dw = pJobInfo->Status;
}
pJobInfo++;
}
free(pJobInfoInitial);
ClosePrinter( hPrinter );
return strUserName ;
}