I am trying to read some XML code from a website, and am having a bit of trouble figuring out where my errors are. Using the code from this extremely useful post, I am trying to read a file that I have saved to my desktop ("H:\MyName\Desktop\secLendingXML.cfm.xml"). The code is below:
#include "stdafx.h"
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#import <msxml6.dll> rename_namespace(_T("MSXML"))
int main(/*int argc, char* argv[]*/)
{
HRESULT hr = CoInitialize(NULL);
if (SUCCEEDED(hr))
{
try
{
MSXML::IXMLDOMDocument2Ptr xmlDoc;
hr = xmlDoc.CreateInstance(__uuidof(MSXML::DOMDocument60), NULL, CLSCTX_INPROC_SERVER);
// TODO: if (FAILED(hr))...
if (xmlDoc->load(_T("H:\MyName\Desktop\secLendingXML.cfm.xml")) != VARIANT_TRUE)
{
printf("Unable to load input.xml\n");
}
else
{
printf("XML was successfully loaded\n");
xmlDoc->setProperty("SelectionLanguage", "XPath");
MSXML::IXMLDOMNodeListPtr wheels = xmlDoc->selectNodes("/Car/Wheels/*");
printf("Car has %u wheels\n", wheels->Getlength());
MSXML::IXMLDOMNodePtr node;
node = xmlDoc->createNode(MSXML::NODE_ELEMENT, _T("Engine"), _T(""));
node->text = _T("Engine 1.0");
xmlDoc->documentElement->appendChild(node);
hr = xmlDoc->save(_T("output.xml"));
if (SUCCEEDED(hr))
printf("output.xml successfully saved\n");
}
}
catch (_com_error &e)
{
printf("ERROR: %ws\n", e.ErrorMessage());
}
CoUninitialize();
}
system("PAUSE");
return 0;
}
The message "Unable to load input.xml" always shows, so I know I'm not having an error, but that the code is unable to load my XML file.
Do I need to save the XML file in a different location? Is the ".cfm" before the ".xml" screwing with the reading process?
To give an idea of my intended direction, I want to be able to load the XML file from the New York Fed website and read it into some sort of data file where I can automate the data retrieving process. That way, whenever the website is updated, I will automatically be notified, and it will be reflected by the change in the data file that I will have saved somewhere on my computer. If anyone also wants to help with how I go about that part - poll intervals, tracking website changes, etc. - that would also be appreciated.
Thank you for any and all help.
Do you need to escape the backslashes in your path string? e.g.
xmlDoc->load(_T("H:\\MyName\\Desktop\\secLendingXML.cfm.xml")
Related
I'm trying to get a list of all "File Explorer" instances currently running. It's fairly straight forward getting a list that includes all instances, but I find myself running into a brick wall filtering that list to only "File Explorer" instances.
The following piece of code retrieves all Explorer instances, meaning both "File Explorer" and "Internet Explorer":
#include <comdef.h>
#include <ExDisp.h>
#include <ShlGuid.h>
#include <Windows.h>
#include <cstdio>
using _com_util::CheckError;
using std::puts;
_COM_SMARTPTR_TYPEDEF(IShellWindows, __uuidof(IShellWindows));
int main()
{
CheckError(::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE));
// Acquire IShellWindows interface
IShellWindowsPtr spShellWindows{};
CheckError(spShellWindows.CreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_LOCAL_SERVER));
// Request iterator
IUnknownPtr spEnum{};
CheckError(spShellWindows->_NewEnum(&spEnum));
IEnumVARIANTPtr spEnumVariant{};
CheckError(spEnum.QueryInterface(__uuidof(spEnumVariant), &spEnumVariant));
// Iterate over shell windows ...
while (true) {
variant_t var{};
// ... one element at a time
HRESULT hr = spEnumVariant->Next(1, &var, nullptr);
CheckError(hr);
// Iterator depleted?
if (hr == S_FALSE) break;
// Did we get the expected `IDispatch` interface?
if (var.vt != VT_DISPATCH) continue;
IDispatchPtr spDisp{};
spDisp.Attach(var.pdispVal, true);
puts("Got suspect; need ID");
// That was easy; now on to some real challenges
}
}
The obvious attempt
My first take at the problem was to just get rid of everything that isn't "File Explorer". Asking for the IWebBrowser2 interface would certainly only get an affirmative response from objects that actually are web browsers. Adding the following to the code above:
_COM_SMARTPTR_TYPEDEF(IWebBrowser2, __uuidof(IWebBrowser2));
// ...
int main()
{
// ...
IWebBrowser2Ptr spWebBrowser{};
hr = spDisp.QueryInterface(__uuidof(spWebBrowser), &spWebBrowser);
if (SUCCEEDED(hr)) puts("Implements IWebBrowser2");
// ...
After making the changes and running the code while an "Internet Explorer" instance is running produces the desired output. However, running the code while a "File Explorer" instance is running produces the same output! That's a surprise and a disappointment, all at the same time.
More robust, less useful
Exluding objects that can be identified as "not File Explorer" didn't work out. Let's try to only include objects that can be identified as "File Explorer" instead. That sounds even more obvious, but as we've learned, "obvious" and "not" go hand in hand when it comes to the Windows Shell.
I haven't actually implemented this, but the IShellWindows interface provides an Item method that can return only objects that match a particular ShellWindowTypeConstants (e.g. SWC_EXPLORER or SWC_BROWSER). Or return an object at a particular index in the window collection. But not BOTH!
So, yes, (potentially) more robust, but also less useful as it doesn't meet my requirements as soon as more than one instances of "File Explorer" are running. Bummer.
Circumstantial evidence
While neither of the above led anywhere, I started over and went on a full-blown investigation looking for hints. Since "File Explorer" browses the Shell namespace, there may be something to that account. The following outlines the approach, based on an article by Raymond Chen titled A big little program: Monitoring Internet Explorer and Explorer windows, part 1: Enumeration:
Starting from the IDispatch interface above, ask for a service with ID SID_STopLevelBrowser to get an IShellBrowser interface.
Call IShellBrowser::QueryActiveShellView to get an IShellView interface.
Ask the IShellView whether it implements something Shell-namespace-y, e.g. IPersistIDList.
If it does, conclude that we're holding a reference to a "File Explorer" instance.
This appears to produce the desired result, though it's not clear to me future-proof this is or when it stops working. Leaving aside how overly convoluted this appears, I'm concerned about its reliability.
Question
What's the recommended/robust/reliable way to identify all "File Explorer" instances in an IShellWindows collection? I will favor solutions based on official documentation, though I understand that this is the Windows Shell and there's next to no documentation at all.
Here's a possibility ...
#include <comdef.h>
#include <ExDisp.h>
#include <ShlGuid.h>
#include <Windows.h>
#include <cstdio>
#include <atlbase.h>
#include <string>
using _com_util::CheckError;
using std::puts;
using std::string;
_COM_SMARTPTR_TYPEDEF(IShellWindows, __uuidof(IShellWindows));
//nicked from StackOverflow
std::string ProcessIdToName(DWORD_PTR processId)
{
std::string ret;
HANDLE handle = OpenProcess(
PROCESS_QUERY_LIMITED_INFORMATION,
FALSE,
processId /* This is the PID, you can find one from windows task manager */
);
if (handle)
{
DWORD buffSize = 1024;
CHAR buffer[1024];
if (QueryFullProcessImageNameA(handle, 0, buffer, &buffSize))
{
ret = buffer;
}
else
{
printf("Error GetModuleBaseNameA : %lu", GetLastError());
}
CloseHandle(handle);
}
else
{
printf("Error OpenProcess : %lu", GetLastError());
}
return ret;
}
int main()
{
CheckError(::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE));
// Acquire IShellWindows interface
IShellWindowsPtr spShellWindows{};
CheckError(spShellWindows.CreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_LOCAL_SERVER));
// Request iterator
IUnknownPtr spEnum{};
CheckError(spShellWindows->_NewEnum(&spEnum));
IEnumVARIANTPtr spEnumVariant{};
CheckError(spEnum.QueryInterface(__uuidof(spEnumVariant), &spEnumVariant));
// Iterate over shell windows ...
while (true) {
variant_t var{};
// ... one element at a time
HRESULT hr = spEnumVariant->Next(1, &var, nullptr);
CheckError(hr);
// Iterator depleted?
if (hr == S_FALSE) break;
// Did we get the expected `IDispatch` interface?
if (var.vt != VT_DISPATCH) continue;
IDispatchPtr spDisp{};
spDisp.Attach(var.pdispVal, true);
puts("Got suspect; need ID");
// That was easy; now on to some real challenges
CComPtr<IWebBrowser2> lpWB;
spDisp->QueryInterface(&lpWB);
SHANDLE_PTR hWnd{ 0 };
lpWB->get_HWND(&hWnd);
if (hWnd)
{
DWORD pid = 0;
GetWindowThreadProcessId((HWND)hWnd, &pid);
if (pid != 0)
{
puts("pid");
auto s = ProcessIdToName((DWORD_PTR)pid);
puts(s.c_str());
}
}
}
}
Has someone already figured out how to get BLE Beacons into a c++ desktop apps?
I have some code from the following websites to get it done in c#:
msdn sozial site
and
codefest.at post. Sorry, it's in german but the code is code
but that'for C# and not c++
I also have the example from MS (msdn.microsoft.com/en-us/library/hh973459.aspx) how to use the WinRL
For now I have the following code:
#include "stdafx.h"
#include <iostream>
#include <Windows.Foundation.h>
#include <wrl\wrappers\corewrappers.h>
#include <wrl\client.h>
#include <stdio.h>
#include <../winrt/windows.devices.bluetooth.h>
#include <../winrt/windows.devices.bluetooth.advertisement.h>
using namespace std;
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Devices::Bluetooth::Advertisement;
/* http://www.codefest.at/post/2015/09/07/Bluetooth-Beacons-Windows-10.aspx
private async void WatcherOnReceived(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs eventArgs)
{
_beaconManager.ReceivedAdvertisement(eventArgs);
}
var watcher = new BluetoothLEAdvertisementWatcher { ScanningMode = BluetoothLEScanningMode.Active };
watcher.Received += WatcherOnReceived;
watcher.Stopped += WatcherOnStopped;
watcher.Start();
*/
// Prints an error string for the provided source code line and HRESULT
// value and returns the HRESULT value as an int.
int PrintError(unsigned int line, HRESULT hr)
{
wprintf_s(L"ERROR: Line:%d HRESULT: 0x%X\n", line, hr);
return hr;
}
EventRegistrationToken watcherToken;
int main()
{
// Initialize the Windows Runtime.
RoInitializeWrapper initialize(RO_INIT_MULTITHREADED);
if (FAILED(initialize))
{
return PrintError(__LINE__, initialize);
}
// Get the activation factory for the IBluetoothLEAdvertisementWatcherFactory interface.
ComPtr<IBluetoothLEAdvertisementWatcherFactory> bleAdvWatcherFactory;
HRESULT hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_Devices_Bluetooth_Advertisement_BluetoothLEAdvertisementWatcher).Get(), &bleAdvWatcherFactory);
if (FAILED(hr))
{
return PrintError(__LINE__, hr);
}
IBluetoothLEAdvertisementWatcher* bleWatcher;
IBluetoothLEAdvertisementFilter* bleFilter;
hr = bleAdvWatcherFactory->Create(bleFilter, &bleWatcher);
if (bleWatcher == NULL)
{
cout << "bleWatcher is NULL, err is " << hex << hr;
}
else
{
bleWatcher->Start();
while (1)
Sleep(1000);
}
return 0;
}
my problem is that the Watcher Factory complains ( hr = E_INVALIDARG "One or more arguments are not valid" 0x80070057) that one of the variables is not valid (I suspect the filter because it has no valid content).
And even on the same level of severity, I have no idea how to register the event handler for the incoming beacons.
msdn.microsoft.com/en-us/library/windows/apps/windows.devices.bluetooth.advertisement.bluetoothleadvertisementwatcher.received?cs-save-lang=1&cs-lang=cpp
is telling me something about the Received event. But I don't have it in my autocomplete from VS and also not by manually looking into the header file.
The next best thing I have is the "add_Received" but I can't find any documentation about it how to use. And I don't get that much wiser from the header file.
Thanks in advance for tips and tricks or even a working solution.
Markus
You have few problems in your code. First of all All I* classes should be wrapped around ComPtr<I*>
ComPtr<IBluetoothLEAdvertisementWatcher> bleWatcher;
ComPtr<IBluetoothLEAdvertisementFilter> bleFilter;
In function bleAdvWatcherFactory->Create(bleFilter, &bleWatcher); parameter bleFilter is undefined and it will lead to undefined behaviour (In Debug version it might be initialized as nullptr).
You can create it by using:
Wrappers::HStringReference
Wrappers::HStringReference class_id_filter(RuntimeClass_Windows_Devices_Bluetooth_Advertisement_BluetoothLEAdvertisementFilter);
hr = RoActivateInstance(class_id_filter.Get(), reinterpret_cast<IInspectable**>(bleFilter.GetAddressOf()));
Then you are allowed to call:
hr = bleAdvWatcherFactory->Create(bleFilter.Get(), &bleWatcher.GetAddressOf());
First let me explain my goal. The goal I'm working towards is providing an input .wav file, sending it into some kind of Speech Recognition API, and returning a text file with the transcription. The application I have in mind is very simple. I do not require that it be parsed for grammar or punctuation. It can return a big, long sentence -- that's fine. I will treat each transcribed word as an observation in a text file (.tsv or .csv format)
However, the one tricky bit of data (tricky because 95% of all 3rd party audio transcription services I've reviewed don't provide this kind of data to the user) that I do need is the [0.00 - 1.00] confidence score of each word the SR takes its guess on. I would like to store that data in a new column of the text file that contains the transcribed text either in .tsv or .csv format.
That's it. That's my goal. It seems my goal is possible: here is a quote from an expert in a related post:
Convert Audio(Wav file) to Text using SAPI?
SAPI can certainly do what you want. Start with an in-proc recognizer,
connect up your audio as a file stream, set dictation mode, and off
you go.
and here is the relevant documentation for .wav transcription confidence scores:
https://msdn.microsoft.com/en-us/library/jj127911.aspx
https://msdn.microsoft.com/en-us/library/microsoft.speech.recognition.recognizedwordunit.confidence(v=office.14).aspx
Everyone makes it sound so simple, but now let me explain the problem; why I'm posting a question. The problem is that, for me, my goal is out of reach because I know next to nothing about c++ or COM. I thought that SAPI was part of the everday windows experience and had a dedicated, friendly user interface. So I grew increasingly alarmed the more I researched this procedure. However I still believe that in principle this is a very simple thing, so I'm optimistic.
I have knowledge in Python and a little JS. I know Python has code magic for other languages, so I'm sure Python can interface with SAPI this way, but since I don't know c++, I don't think that would make me any better off.
So just to reiterate, despite the skill mismatch, I'm still partial to SAPI because all the user friendly alternatives, like Dragon, Nuance, Chrome plug-ins, ect, don't provide the data granularity I need.
Now let me get to the heart of my question:
Can someone give me their assessment on the difficulty of my "goal" as described above? Could it be done in a single .bat file? Example code would be greatly appreciated.
It probably goes without saying, but I think you're going to find it difficult to work with SAPI's C interface if you don't have a strong handle on C as a language. I wrote a program that does almost exactly what you're talking about some time ago to test the concept. First a code dump:
#include "dirent.h"
#include <iostream>
#include <string>
#include <sapi.h>
#include <sphelper.h>
int main(int argc, char* argv[]){
DIR *dir;
struct dirent* entry;
struct stat* statbuf;
::CoInitialize(NULL);
if((dir = opendir(".")) != NULL){
while((entry = readdir(dir)) != NULL){
char extCheck[260];
strcpy(extCheck, entry->d_name);
if(strlen(extCheck) > 4 && !strcmp(strlwr(extCheck) + strlen(extCheck)-4, ".wav")){
//printf("%s\n",entry->d_name);
//1. Find the wav files
//2. Check the wavs to make sure they're the correct format
//3. Output any errors to the error log
//4. Produce the text files for the wavs
//5. Cleanup and exit
FILE* fp;
std::string fileName = std::string(entry->d_name,entry->d_name + strlen(entry->d_name)-4);
fileName += ".txt";
fp = fopen(fileName.c_str(), "w+");
HRESULT hr = S_OK;
CComPtr<ISpStream> cpInputStream;
CComPtr<ISpRecognizer> cpRecognizer;
CComPtr<ISpRecoContext> cpRecoContext;
CComPtr<ISpRecoGrammar> cpRecoGrammar;
CSpStreamFormat sInputFormat;
hr = cpRecognizer.CoCreateInstance(CLSID_SpInprocRecognizer);
hr = cpInputStream.CoCreateInstance(CLSID_SpStream);
hr = sInputFormat.AssignFormat(SPSF_16kHz16BitStereo);
std::string sInputFileName = entry->d_name;
std::wstring wInputFileName = std::wstring(sInputFileName.begin(), sInputFileName.end());
hr = cpInputStream->BindToFile(wInputFileName.c_str(), SPFM_OPEN_READONLY, &sInputFormat.FormatId(), sInputFormat.WaveFormatExPtr(), SPFEI_ALL_EVENTS);
hr = cpRecognizer->SetInput(cpInputStream, TRUE);
hr = cpRecognizer->CreateRecoContext(&cpRecoContext);
hr = cpRecoContext->CreateGrammar(NULL, &cpRecoGrammar);
hr = cpRecoGrammar->LoadDictation(NULL,SPLO_STATIC);
hr = cpRecoContext->SetNotifyWin32Event();
auto hEvent = cpRecoContext->GetNotifyEventHandle();
hr = cpRecoContext->SetInterest(SPFEI(SPEI_RECOGNITION) | SPFEI(SPEI_END_SR_STREAM), SPFEI(SPEI_RECOGNITION) | SPFEI(SPEI_END_SR_STREAM));
hr = cpRecoGrammar->SetDictationState(SPRS_ACTIVE);
BOOL fEndStreamReached = FALSE;
unsigned int timeOut = 0;
//WaitForSingleObject(hEvent, INFINITE);
while (!fEndStreamReached && S_OK == cpRecoContext->WaitForNotifyEvent(INFINITE)){
CSpEvent spEvent;
while (!fEndStreamReached && S_OK == spEvent.GetFrom(cpRecoContext)){
switch (spEvent.eEventId){
case SPEI_RECOGNITION:
{
auto pPhrase = spEvent.RecoResult();
SPPHRASE *phrase = nullptr;// new SPPHRASE();
LPWSTR* text = new LPWSTR(L"");
pPhrase->GetText(SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, TRUE, text, NULL);
pPhrase->GetPhrase(&phrase);
if(phrase != NULL && phrase->pElements != NULL) {
std::wstring wRuleName = L"";
if(nullptr != phrase && phrase->Rule.pszName != NULL) {
wRuleName = phrase->Rule.pszName;
}
std::wstring recognizedText = L"";
bool firstWord = true;
for(ULONG i = 0; i < (ULONG)phrase->Rule.ulCountOfElements; ++i) {
if(phrase->pElements[i].pszDisplayText != NULL) {
std::wstring outString = phrase->pElements[i].pszDisplayText;
std::string soutString = std::string(outString.begin(), outString.end());
if(!firstWord){
soutString = " " + soutString;
firstWord = false;
}
soutString = soutString + " ";
fputs(soutString.c_str(),fp);
/*if(recognizedText != L"") {
recognizedText += L" " + outString;
} else {
recognizedText += outString;
}*/
}
}
}
delete[] text;
break;
}
case SPEI_END_SR_STREAM:
{
fEndStreamReached = TRUE;
break;
}
}
// clear any event data/object references
spEvent.Clear();
}
}
hr = cpRecoGrammar->SetDictationState(SPRS_INACTIVE);
hr = cpRecoGrammar->UnloadDictation();
hr = cpInputStream->Close();
fclose(fp);
}
}
closedir(dir);
} else {
perror("Error opening directory");
}
::CoUninitialize();
std::printf("Press any key to continue...");
std::getchar();
return 0;
}
I haven't run this in a long time, but you'll have to get dirent.h for it work. I was playing around with that library for no other reason than to try it out.
With the code provided you could probably start looking at what confidence values are getting generated at the recognition step. You could also tweak this to run from a batch file if you wanted to.
The problems that I faced were the following:
Accuracy was a problem, and in order to improve it I would have to train the recognizer, which was going to require a lot more time than I had.
I found that direct translation into text wasn't what I really wanted after all. As it turns out the phoneme data is quite a bit more important. With that you can form your own confidence scheme, and develop your own alternatives specific to your application.
Window's Recognizer, while good, isn't going to recognize words that it doesn't know about. You'll have to figure out how to add your vocabulary to windows' speech recognizer lexicon.
With that said, it's not a trivial undertaking to use the stock windows desktop speech recognizer. I'd take a look at some existing APIs out there. If you're not limited to client side only applications, you'd do well to look into other APIs.
Quite honestly, this is fairly difficult given the approach you're describing in your question. The existing SAPI engines either don't do dictation at all (e.g., the "server" engine available via Microsoft.Speech.Recognition) or require training to learn the particulars of a given voice (e.g., the "desktop" engine available via System.Speech.Recognition).
The Windows Runtime recognizer (Windows.Media.SpeechRecognition) supports dictation and provides a confidence value, but does not support recognition from a stream.
With the approach you're describing, I would use the Bing Speech API, as it provides the confidence values you want via the REST API.
I am very new to this, sorry about the horrible code below. I am trying to get the default path for FOLDERID_Profile and then add "\test.exe" to the end of it. Then i need to use this as the path to copy a file to. I was able to use the pSHGetKnownFolderPath method to store the profiles directory in PWSTR user_dir . Problem is, this is not an acceptable string format for the copy function.
So i used the following code to very crudely attempt to convert it to something the copy function could use.
strcat((char *)user_dir,"\\test.exe");
test7 = (LPCWSTR)user_dir;
MessageBox(NULL,test7,L"WR test file",MB_OK);
i'm using a message box to check the path before using CopyFile(currentpath,test7,false); But this is giving me 㩃瑜獥硥 . I am currently using
CopyFileW(currentpath,L"C:\\Users\\Jenia\\test.exe",false);
as a workaround, but I really need this to work on other computers too...
I know I am messing up my ANSI vs Unicode formatting again, please tell me how to best achieve this goal. Let me know if you would like me to post the entire code block, but until i run that strcat method user_dir has the correct path just no file name for copy method.
more complete code below:
#include <windows.h>
#include <shlwapi.h>
#include <stdio.h>
#include <Shlobj.h>
LPCWSTR test7 = 0;
PWSTR user_dir = 0;
HMODULE hndl_shell32;
lpSHGetKnownFolderPath pSHGetKnownFolderPath;
hndl_shell32 = LoadLibrary(L"shell32");
if (NULL != hndl_shell32)
{
pSHGetKnownFolderPath = (lpSHGetKnownFolderPath)
GetProcAddress(hndl_shell32, "SHGetKnownFolderPath");
if(pSHGetKnownFolderPath != NULL)
{
if (SUCCEEDED(pSHGetKnownFolderPath(
FOLDERID_Profile,
0,
NULL,
&user_dir)))
{
//I think this is the problem here
strcat((char *)user_dir,"\\test.exe");
test7 = (LPCWSTR)user_dir;
MessageBox(NULL,test7,L"WR test file",MB_OK);
}
}
else
{
fprintf(stderr, "Failed to locate function: %d\n",
GetLastError());
}
}
else
{
fprintf(stderr, "Failed to load shell32.dll: %d\n", GetLastError());
}
Too many errors here. You cannot strcat on pointer filled by SHGetKnownFolderPath. Assuming that all variables are Unicode, this should work with project with any character set:
LPWSTR test7 = 0;
WCHAR user_dir[MAX_PATH];
...
SHGetKnownFolderPath(... &test7);
...
wcscpy(user_dir, test7);
wcscat(user_dir, L"\\test.exe");
MessageBoxW(NULL,test7,L"WR test file",MB_OK);
Don't forget to release the pointer test7 filled by SHGetKnownFolderPath.
This shows the basic way to complete your task; you'll need to adapt it for your needs.
#include <ShlObj.h>
#include <strsafe.h>
void ShowTestPath()
{
PWCHAR user_dir = NULL;
WCHAR test_file_path[MAX_PATH];
if (FAILED(SHGetKnownFolderPath(FOLDERID_Profile, 0, NULL, &user_dir)))
return;
if (FAILED(StringCchCopyW(test_file_path, MAX_PATH, user_dir)))
{
CoTaskMemFree(user_dir);
return;
}
CoTaskMemFree(user_dir);
if (FAILED(StringCchCatW(test_file_path, MAX_PATH, L"\\test.exe")))
return;
MessageBoxW(NULL, test_file_path, L"WR test file", MB_OK);
}
I'm trying to write a backup and recovery tool. I'm running my code on a WinPE CD (http://en.wikipedia.org/wiki/Windows_Preinstallation_Environment). I'm trying to read the entire C: partition and write it to the network. Just like the tar command, but windows specific. I have everything working except for setting the file owner. Windows seems to be really intolerant to files being owned by unknown SIDs. Since I'm running in WinPE, most of the users defined on C: aren't in the local user database.
Here are some of the functions I've tried:
SetFileSecurity (returns 1307)
SetSecurityInfo (returns 1307)
SetNamedSecurityInfo (returns 1307)
BackupWrite (returns 1307)
NtSetSecurityObject (returns 0xC000005A)
I know this can be done. SetACL (http://helgeklein.com/setacl/) is able to do it.
So, the question. How do I set the owner of a file to a non-existing user/SID? Any help is greatly appreciated!
Here's a sample of the code I've tried:
#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <sddl.h>
#include <aclapi.h>
#include <tchar.h>
INT _tmain(){
PSECURITY_DESCRIPTOR psdOwner = LocalAlloc(LPTR,SECURITY_DESCRIPTOR_MIN_LENGTH);
if (InitializeSecurityDescriptor(psdOwner,SECURITY_DESCRIPTOR_REVISION)){
PSID psOwner = (PSID)0;
if (ConvertStringSidToSid(TEXT("S-1-5-21-3626571138-2175758104-1447827851-1013"),&psOwner)){
if (SetSecurityDescriptorOwner(psdOwner,psOwner,FALSE)){
DWORD dwError = SetNamedSecurityInfo(TEXT("test.txt"),SE_FILE_OBJECT,OWNER_SECURITY_INFORMATION,psdOwner,NULL,NULL,NULL);
if (dwError == ERROR_SUCCESS){
_tprintf(TEXT("Success!\n"));
}else{
_tprintf(TEXT("Failed to set owner: %u\n"),dwError);
}
}else{
_tprintf(TEXT("Failed to set owner into SD: %u\n"),GetLastError());
}
}else{
_tprintf(TEXT("Failed to covnert Sid string to Sid: %u\n"),GetLastError());
}
if (psOwner) LocalFree(psOwner);
}else{
_tprintf(TEXT("Failed to initialize SD: %u\n"),GetLastError());
}
if (psdOwner) LocalFree(psdOwner);
return 0;
}
Turns out you need SE_RESTORE_NAME token privilege. You can adjust your process token with the following:
BOOL TakeSecurityPriv(LPCTSTR szPriv){
BOOL bReturn = FALSE;
HANDLE hProcToken = (HANDLE)0;
if (OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,&hProcToken)){
TOKEN_PRIVILEGES tpTokPriv;
if (LookupPrivilegeValue(NULL,szPriv,&tpTokPriv.Privileges[0].Luid)){
tpTokPriv.PrivilegeCount = 1;
tpTokPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (AdjustTokenPrivileges(hProcToken,FALSE,&tpTokPriv,0,NULL,0)){
bReturn = TRUE;
}
}
}
return bReturn;
}
Then you can add the following to the beginning of main in my example:
if (TakeSecurityPriv(SE_RESTORE_NAME)){
That gets rid of the 1307 error. Unfortunately there is another bug in my example because the owner isn't set to the correct SID. However, when I switch back to BackupRead/BackupWrite I won't have to create a SID or SECURITY_DESCRIPTOR. I've pondered over what could be the issue for a while now with no luck. Perhaps someone else could answer that part.