Got language as 0 when calling VerQueryValue - winapi

My code below is similar to the example in the VerQueryValue() documentation:
// Structure used to store enumerated languages and code pages.
HRESULT hr;
struct LANGANDCODEPAGE {
WORD wLanguage;
WORD wCodePage;
} *lpTranslate;
// Read the list of languages and code pages.
VerQueryValue(pBlock,
TEXT("\\VarFileInfo\\Translation"),
(LPVOID*)&lpTranslate,
&cbTranslate);
// Read the file description for each language and code page.
for( i=0; i < (cbTranslate/sizeof(struct LANGANDCODEPAGE)); i++ )
{
hr = StringCchPrintf(SubBlock, 50,
TEXT("\\StringFileInfo\\%04x%04x\\FileDescription"),
lpTranslate[i].wLanguage,
lpTranslate[i].wCodePage);
if (FAILED(hr))
{
// TODO: write error handler.
}
// Retrieve file description for language and code page "i".
VerQueryValue(pBlock,
SubBlock,
&lpBuffer,
&dwBytes);
}
The code can retrieve string informations for other exe files, but only for 1 particular file, it can not retrieve string infos (I noticed the language is zero for this case).
By using file explorer, I can see the language is 'Language Neutral'. But, the code shows language part is 0 and codepage part is 1252.
Is it normal to get language as 0?
Furthermore, I'm not able to get CompanyName and other string, but those are available from windows file explorer.
Here's the screenshots in VC++ for that exe:
Any ideas?

Related

Updating display of directory/folder path in MFC File-Open dialog [duplicate]

I'm trying to make a 'Save As' dialog with an event that would change the default path based on the type of file we choose from the filters combo box. The problem is, all the examples I've seen execute the code on result IDOK or IDCANCEL while I'd need the code to be executed while the dialog is still opened.
Also, is there any way to differentiate between what filter has been chosen if the filters have the same type? The GetFileExt() method just returns the extension but I have no way of telling if it was the first .my filter or the template .my filter.
I've seen something like LPOFNHOOKPROC but there was no example of how would I even use it and I'm not sure whether it would even solve my problem or not.
void CMyClass::OnFileOpen()
{
CString pathNam;
CString fileName;
TCHAR szFilters[]= _T("MyType Files (*.my)|*.my|Template MyType (*.my)|*.my||");
CFileDialog fileDlg(TRUE, _T("my"), _T("*.my"),
OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, szFilters);
if(fileDlg.DoModal() == IDOK)
{
pathName = fileDlg.GetPathName();
fileName = fileDlg.GetFileTitle();
}
}
EDIT:
I am now able to get the specific filter that's been chosen by getting the OFN and checking the nFilterIndex value. So the remaining problem is whether I can update the path based on the chosen file format?
EDIT2:
I've found the OnChangeType method and overloaded it in the subclass and it indeed executes the method and the code within, but when I try to update the file path I get an access violation:
void TFileDialogExt::OnTypeChange()
{
LPWSTR buff = L"C:\\TEST\\template.my";
if(m_pOFN->nFilterIndex == 2)
m_ofn.lpstrFile = buff;
}
Basically you have to subclass CFileDialog and handle its CFileDialog::OnTypeChange method.
But, as suggested by Microsoft: you'd better use a new Common Item Dialog instead.
I did some research about this and found some useful questions:
Programmatically pre-select using IFileDialog in C++
How to use SHCreateItemFromParsingName with names from the shell namespace?
Also, have a look at: SHCreateItemFromParsingName.
Here is a sample OnTypeChange handler:
void CMyFileDialog::OnTypeChange()
{
{
IFileOpenDialog* pfod = NULL;
HRESULT hr = (static_cast<IFileDialog*>(m_pIFileDialog))->QueryInterface(IID_PPV_ARGS(&pfod));
if (SUCCEEDED(hr))
{
IShellItem* psiInitialDir;
CString strFolder = L"d:\\";
hr = SHCreateItemFromParsingName(strFolder.GetString(), NULL, IID_PPV_ARGS(&psiInitialDir));
if(SUCCEEDED(hr))
{
pfod->SetFolder(psiInitialDir);
}
}
}
CFileDialog::OnTypeChange();
}
My code uses a hard coded path for testing purposes, but you should now be able to complete your code:
Determine which path you want to use based on the currently selected filter index.
Use similar logic as here to navigate to that folder.

How to get the timestamp of when a disk is made offline from diskmgmt or other ways in windows?

I want to know the time when a disk is made offline by user. Is there a way to know this through WMI classes or other ways?
If you cannot find a way to do it through the Win32 API/WMI or other, I do know of an alternate way which you could look into as a last-resort.
What about using NtQueryVolumeInformationFile with the FileFsVolumeInformation class? You can do this to retrieve the data about the volume and then access the data through the FILE_FS_VOLUME_INFORMATION structure. This includes the creation time.
At the end of the post, I've left some resource links for you to read more on understanding this so you can finish it off the way you'd like to implement it; I do need to quickly address something important though, which is that the documentation will lead you to
an enum definition for the _FSINFOCLASS, but just by copy-pasting it from MSDN, it probably won't work. You need to set the first entry of the enum definition to 1 manually, otherwise it will mess up and NtQueryVolumeInformationFile will return an error status of STATUS_INVALID_INFO_CLASS (because the first entry will be identified as 0 and not 1 and then all the entries following it will be -1 to what they should be unless you manually set the = 1).
Here is the edited version which should work.
typedef enum _FSINFOCLASS {
FileFsVolumeInformation = 1,
FileFsLabelInformation,
FileFsSizeInformation,
FileFsDeviceInformation,
FileFsAttributeInformation,
FileFsControlInformation,
FileFsFullSizeInformation,
FileFsObjectIdInformation,
FileFsDriverPathInformation,
FileFsVolumeFlagsInformation,
FileFsSectorSizeInformation,
FileFsDataCopyInformation,
FileFsMetadataSizeInformation,
FileFsMaximumInformation
} FS_INFORMATION_CLASS, *PFS_INFORMATION_CLASS;
Once you've opened a handle to the disk, you can call NtQueryVolumeInformationFile like this:
NTSTATUS NtStatus = 0;
HANDLE FileHandle = NULL;
IO_STATUS_BLOCK IoStatusBlock = { 0 };
FILE_FS_VOLUME_INFORMATION FsVolumeInformation = { 0 };
...
Open the handle to the disk here, and then check that you have a valid handle.
...
NtStatus = NtQueryVolumeInformationFile(FileHandle,
&IoStatusBlock,
&FsVolumeInformation,
sizeof(FILE_FS_VOLUME_INFORMATION),
FileFsVolumeInformation);
...
If NtStatus represents an NTSTATUS error code for success (e.g. STATUS_SUCCESS) then you can access the VolumeCreationTime (LARGE_INTEGER) field of the FILE_FS_VOLUME_INFORMATION structure with the FsVolumeInformation variable.
Your final task at this point will be using the LARGE_INTEGER field named VolumeCreationTime to gather proper time/date information. There are two links included at the end of the post which are focused on that topic, they should help you sort it out.
See the following for more information.
https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/nf-ntifs-ntqueryvolumeinformationfile
https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ne-wdm-_fsinfoclass
https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntddk/ns-ntddk-_file_fs_volume_information
https://msdn.microsoft.com/en-us/library/windows/desktop/ms724280.aspx
https://blogs.msdn.microsoft.com/joshpoley/2007/12/19/datetime-formats-and-conversions/

8.3 format filenames received when files opened from context menu in Windows

I have edited the windows registry so that selected files can be opened with the program I made (from an option in context menu). Specifically, under specific file types I have added 'shell' key and under it a 'command' key with string containing "C:\MyProgram.exe %1". The file opens correctly, however my program receives the file name in old 8.3 format, and I need full file name for display. How should I fix this?
Side quest: How to open multiple files as multiple arguments in one program call instead of opening separate instances, each with only one argument(%1)?
The easiest way to get to the full path name is to call GetLongPathName. In C++ you would use something like the following:
std::wstring LongPathFromShortPath(const wchar_t* lpszShortPath) {
// Prevent truncation to MAX_PATH characters
std::wstring shortPath = L"\\\\?\\";
shortPath += lpszShortPath;
// Calculate required buffer size
std::vector<wchar_t> buffer;
DWORD requiredSize = ::GetLongPathNameW(shortPath.c_str(), buffer.data(), 0x0);
if (requiredSize == 0x0) {
throw std::runtime_error("GetLongPathNameW() failed.");
}
// Retrieve long path name
buffer.resize(static_cast<size_type>(requiredSize));
DWORD size = ::GetLongPathNameW(shortPath.c_str(), buffer.data(),
static_cast<DWORD>(buffer.size()));
if (size == 0x0) {
throw std::runtime_error("GetLongPathNameW() failed.");
}
// Construct final path name (not including the zero terminator)
return std::wstring(buffer.data(), buffer.size()-1);
}
For first part of question do that IInspectable suggested.
But if you want to do something more fancy, simple registry modification will not do the trick. You need Windows Shell Extension and implement Context Menu handler. Once I have made one, here are some useful links: here, here and here. And there are already similar questions like this

Find localized Windows strings

I need to find some strings that the current version of Windows is using. For example, when I create a new folder, it is initially named "New Folder" on English Vista. I need to programmatically find what that folder would be named on any language and version of Windows that I might be running on.
Anyone have any ideas how to do that?
Thanks Morinar -- I just stumbled upon that article too. Unfortunately, the stringID doesn't appear to be constant -- it's 30396 on my Vista, which isn't the same as what they show for XP. So it would appear MS didn't keep it stable.
EDIT: Looks like this isn't possible...? This apps runs on computers in Germany, The Netherlands, France, Spain, Brazil, Mexico, Vietnam, Taiwan, China, Japan, South Korea, India, Israel, Hungary ... You get the idea. It will take a very long time to install all the different language packs and find out what 'New Folder' is in every language.
Perhaps the best option is to default to "New Folder" and make the user change that value if they want to. I just prefer to have the software figure out as much as it can and spare the user from configuring _yet_another_setting_.
Shamelessly cribbed from http://blogs.msdn.com/oldnewthing/archive/2004/01/30/65013.aspx. This is mostly correct but if there is a resource string of "New Folder something else" it will match that:
LPCWSTR FindStringResourceEx(HINSTANCE hinst,
UINT uId, UINT langId)
{
// Convert the string ID into a bundle number
LPCWSTR pwsz = NULL;
HRSRC hrsrc = FindResourceEx(hinst, RT_STRING,
MAKEINTRESOURCE(uId / 16 + 1),
langId);
if (hrsrc) {
HGLOBAL hglob = LoadResource(hinst, hrsrc);
if (hglob) {
pwsz = reinterpret_cast<LPCWSTR>
(LockResource(hglob));
if (pwsz) {
// okay now walk the string table
for (int i = 0; i < (uId & 15); i++) {
pwsz += 1 + (UINT)*pwsz;
}
pwsz+= 1;
}
}
}
return pwsz;
}
UINT FindResourceStringId(HMODULE resource_handle, LPCWSTR string, UINT langId)
{
UINT resource_id= -1;
for (int i= 0; i<65536; ++i)
{
LPCWSTR resource_string= FindStringResourceEx(resource_handle, i, langId);
if (resource_string && wcsncmp(resource_string, string, wcslen(string))==0)
{
resource_id= i;
}
}
return resource_id;
}
int main()
{
HMODULE shell_handle= LoadLibraryW(L"shell32.dll");
UINT new_folder_id= FindResourceStringId(shell_handle, L"New Folder", 0x409); // look for US English "New Folder" resource id.
}
This is not easy. These strings are private data for Windows Explorer, and as such they can (and probably do) change between releases. You can hack something up where you do a lot of version checking and read the appropriate resource string, but that seems like a losing battle. What are you trying to accomplish?
Unsure if there is a more elegant way or not (I couldn't seem to find one), but those strings are stored in %windir%\System32\Shell32.dll. Theoretically, you could merely read that file in and extract the appropriate strings.
Seems a bit hacky-ish, but should get the job done. Here's a link to an article that discusses where they live in said file:
http://www.askvg.com/customize-new-folder-and-new-shortcut-text-in-windows-xp/
Seems like there could or even should be an interface to them via the Windows API, but trolling through the documentation I couldn't find one. Perhaps you'll have more luck.
If you want to handle 80% of the cases, you can start with "New Folder".
As I guess you're in an enterprise environment, you can get the folder names back into a storage, then after a week (or any time) you get through the names; update your app; release the new version which will please the users. (then later publish the results here)
You can preemptively test your app on a range of platform you're suspecting the users to use. to get a first series of folders names.
This will avoid the problem of dealing with code specific with each of the platform you're looking at.
EDIT
Well I get a second thought about that, I guess you might want to warn the user about that "New Folder" if it wasn't rename after some time (say a minute)? then I guess you would need to add a list and a timer ...

Where can I find a good example of ReportEvent function usage?

As with most "legacy" MSDN pages, the page for ReportEvent has too little information for me to make much sense of it. I've tried searching, but can't find a good, clean, simple example of the function's usage. Could anyone suggest one?
I ended up using this:
HANDLE eventLog;
WORD type;
const char* msg;
// ... snip ...
ReportEvent(eventLog, type, 0, 1, NULL, 1, 0, &LPCTSTR(msg), NULL);
Seems to work well enough.
Well this seems to be a very old thread, landed here looking for a good example of Report Event... but figured out you have not received any replies...and would have probably already found the solution.
The reason you see "Event Id not found" is because the EventViewer is not able to load/lookup the text resource to be displayed for the event Id. Sorry if last line sounded geeky.. but this is what i understand of EventLog:
-EventLogging has two aspects
Registering with EventLog (or in other terms creating EventSource)
Logging or Writing into Event Log
Viewing or Reading from log
When you register in event log, you simply specify a eventSource (any name that identifies that log) + EventMessageFile, Category File and SupportedEventTypes. Here EventMessageFile points to a DLL/EXE that contains your message descriptions/resources.
When you log an event, you simply log it with some data like EventID, Category ID and EventData. But when you view it using any EventViewer (or Windows eventVwr.exe) the viewer reads your events, looks for a DLL/EXE associated with your eventSource(pointed by EventMessageFile), and renders the decription from the resource section of that DLL/EXE.
This DLL is nothing but a simple resource file that was compiled using MessageCompiler, and contains a "MessageTable". This is done to provide culture specific event logging
This is the reason, When you export the log into XML/TXT etc from your EventViewer, It asks you if you want to Save it "with Display informaion" or "without display information", so that you can view it on computers that do not have EventMessageFile.
JFYI the reg entry is located at:
HKLM\CurrentControlSet\System\Services\EventLog\Application
one catch: If you're wondering how .Net does it..., it simply does it by providing a default EventMessageFile called EventLogMessage.dll (found under %SYSTEMROOT%\Microsoft.Net\Framework\vXXXX\)
As I recall it is a pain to set up correctly - you need to add messages to you application using the Message Compiler - if you skip this you won't see useful messages only error codes. Take a look at Creating a Windows NT Service by Using ATL for an example
The sample Windows Service C++, is a windows service reporting to event log, you can get the code from https://code.msdn.microsoft.com/windowsapps/CppWindowsService-cacf4948
in particular, the following function (quoted from ServiceBase.cpp) does it
//
// FUNCTION: CServiceBase::WriteEventLogEntry(PWSTR, WORD)
//
// PURPOSE: Log a message to the Application event log.
//
// PARAMETERS:
// * pszMessage - string message to be logged.
// * wType - the type of event to be logged. The parameter can be one of
// the following values.
//
// EVENTLOG_SUCCESS
// EVENTLOG_AUDIT_FAILURE
// EVENTLOG_AUDIT_SUCCESS
// EVENTLOG_ERROR_TYPE
// EVENTLOG_INFORMATION_TYPE
// EVENTLOG_WARNING_TYPE
//
void CServiceBase::WriteEventLogEntry(PWSTR pszMessage, WORD wType)
{
HANDLE hEventSource = NULL;
LPCWSTR lpszStrings[2] = { NULL, NULL };
hEventSource = RegisterEventSource(NULL, m_name);
if (hEventSource)
{
lpszStrings[0] = m_name;
lpszStrings[1] = pszMessage;
ReportEvent(hEventSource, // Event log handle
wType, // Event type
0, // Event category
0, // Event identifier
NULL, // No security identifier
2, // Size of lpszStrings array
0, // No binary data
lpszStrings, // Array of strings
NULL // No binary data
);
DeregisterEventSource(hEventSource);
}
}

Resources