CopyFiles using IFileOperation (C++) - c++

I want to copy multiple files using IFileOperation.
Copy a single file is not a problem like this example:
http://msdn.microsoft.com/en-us/library/windows/desktop/bb775761%28v=vs.85%29.aspx
My Problem is that I don't find a way to copy multiple files like *.txt.
I have tried using SHCreateShellItemArrayFromIDLists, like this snippet:
IShellItem *psiTo = NULL;
HRESULT hr = SHCreateItemFromParsingName( csTarget, NULL, IID_PPV_ARGS(&psiTo) );
LPCITEMIDLIST pidlFiles = ILCreateFromPath(csSource);
UINT count = sizeof(pidlFiles);
IShellItemArray* psiaFiles = NULL;
hr = SHCreateShellItemArrayFromIDLists(count, &pidlFiles, &psiaFiles);
hr = pfo->CopyItems(psiaFiles, psiTo);
hr = pfo->PerformOperations();
A other way is to use SHCreateShellItemArray, like this:
LPCITEMIDLIST pidlParent = ILCreateFromPath(_T("C:\\"));
LPCITEMIDLIST pidlChild = ILCreateFromPath(_T("C:\\Temp\\*.txt"));
HRESULT hr = SHCreateShellItemArray(pidlParent, NULL, 1, &pidlChild, &psiaFiles);
hr = pfo->CopyItems(psiaFiles, psiTo);
hr = pfo->PerformOperations();
I tried different way but almost I get E_INVALIDIDARG or ERROR_PATH_NOT_FOUND.
Whats wrong?

ILCreateFromPath doesn't take wildcards. Couldn't, since it returns a single PIDLIST_ABSOLUTE.
The easiest solution is to enumerate all files manually FindFirstFile(*.txt), call CopyItem on each result, and then call PerformOperations once at the end.
As usual, the older Windows functions are better. SHFileOperation is ten times simpler and more capable to boot: it does support wildcards directly.

Related

How can i create a dir in Documents folder? [C++]

Im trying to create a directory , or a subdirectory in the Documents folder.
PWSTR ppszPath; // variable to receive the path memory block pointer.
HRESULT hr = SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &ppszPath);
std::wstring myPath;
if (SUCCEEDED(hr)) {
myPath = ppszPath; // make a local copy of the path
}
const wchar_t* str = myPath.c_str();
_bstr_t b(str);
int status = _mkdir(b+"\\New");
As you can see , I'm trying to create a new folder named "New" in Documents Folder.
The path to the documents is correct but the dir is not created.
This is using _bstr_t to avoid using Unicode, the new path is converted to ANSI and will be invalid unless the original path was ANSI, (or ASCII to be guaranteed)
Just pad L"\\New" and wide string functions to solve the problem.
You also have to free ppszPath as stated in documentation
std::wstring myPath;
wchar_t *ppszPath;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &ppszPath);
if (SUCCEEDED(hr))
{
myPath = ppszPath;
CoTaskMemFree(ppszPath);
}//error checking?
myPath += L"\\New";
std::filesystem::create_directory(myPath)
//or _wmkdir(myPath.c_str());
The std::filesystem::path class understands Unicode just fine, so you don’t need to mess with any helpers there. Also, you need to check both function results to determine success or failure:
bool success = false;
PWSTR documents_path = nullptr;
if (SUCCEEDED( SHGetKnownFolderPath( FOLDERID_Documents, 0, NULL, &documents_path ) ))
{
using namespace std::filesystem;
success = create_directory( path{ documents_path } / "new_folder" );
CoTaskMemFree( documents_path );
documents_path = nullptr;
}
The result of the operation is indicated in the variable success.
I would personally separate the functions of obtaining the user’s Documents folder and the directory creation into two separate functions, but the above will do just fine.

c++ MFC SDI DLL call method of OLE Server using IDispatch

I have written a c++ MFC DLL that brings up an SDI Application which is a very legacy OLE Server. (I have no choice about using this OLE Server so I have to make it work.)
I am accessing this c++ DLL from C#.
I have everything "working". I can call methods of the DLL, I have implemented C# delegates correctly, etc.
I can call methods of the OLE Server directly in C++ and export these so that my C# application call call them too. This is my "Hello World" for accessing the OLE Server functionality in its entirety from C#.
So far so good.
The next step is to make this C++ DLL as much of a "pass-through" as possible so that C# developers can write business logic around this OLE server. If changes or updates happen from the maker of the OLE Server, we do not want to have to update C++ code, we want to respond to the changes in C#.
So even though I can implement methods in C++ using the imported class from the OLEServer.tlb file and pass these through to C#, we do not want to ultimately do this. I am trying to call methods through the IDispatch interface instead and I am running into difficulties that I can't quite understand.
I am using Visual Studio 2010 for both unmanaged C++ and C#.
VARIANT LaserCat::LaserCatCommand(LPCTSTR* p_pMethodNameAndParamsInReverseOrder, UINT p_Count)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
VARIANT result;
VARIANT* pResult = NULL;
VariantInit(&result);
HRESULT hr = NULL;
DISPID dispid;
const IID IID_ITriad = {0x60EE772D,0xE076,0x4F58,{0xA8,0xB4,0x2F,0x7A,0x29,0xBB,0x02,0x50}};
COleException* pError = NULL;
BOOL HasDispatch = theApp.pTriadView->pTriadItem->Catalog.CreateDispatch(IID_ITriad, pError);
LPDISPATCH iDisp = theApp.pTriadView->pTriadItem->Catalog.m_lpDispatch;
LPOLESTR Names[1] = {(LPOLESTR)L"GetInterfaceVersion"};
hr = iDisp->GetIDsOfNames(IID_NULL, Names, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
if (hr != S_OK) return result;
DISPPARAMS* pParams = new DISPPARAMS();
short maj = 0;
short min = 0;
short* nMajor = &maj;
short* nMinor = &min;
VARIANTARG Args[2];
VariantInit(&Args[0]);
VariantInit(&Args[1]);
Args[0].piVal = nMinor;
Args[0].vt = VT_BYREF;
Args[1].piVal = nMajor;
Args[1].vt = VT_BYREF;
pParams->rgvarg = Args;
pParams->cNamedArgs = 0;
pParams->cArgs = 2;
pParams->rgdispidNamedArgs = NULL;
EXCEPINFO* pExcept = NULL;
UINT* pArgErrorIndex = NULL;
LPCTSTR Error = NULL;
hr = iDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, pParams, pResult, pExcept, pArgErrorIndex);
if (pExcept != NULL || pArgErrorIndex != NULL || hr != S_OK)
{
Error = _T("Error");
return result;
}
result = *pResult;
return result;
}
The following line from above gives me a "Bad variable type" error:
hr = iDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, pParams, pResult, pExcept, pArgErrorIndex);
BTW, I am sure that the code can be vastly improved, it has been years since I have written in C++ and this is just a "first kick" at getting this working so no real error handling etc.
I have toyed around with "Args[]" type and value with variations of "Type Mismatch" errors and "Null Reference Pointers"
The function that is imported by the .tlb file looks like this:
short GetInterfaceVersion(short * nMajor, short * nMinor)
{
short result;
static BYTE parms[] = VTS_PI2 VTS_PI2 ;
InvokeHelper(0x178, DISPATCH_METHOD, VT_I2, (void*)&result, parms, nMajor, nMinor);
return result;
}
Oh, although I am passing in the "pMethodNameAndParamsInReverseOrder" as a parameter, I am just hard-coding it to get this one simple method working. Once I have it working with this and a few other methods, I am planning on making this generic to handle any methods implemented by the COM interface via IDispatch.
Any help would be appreciated mostly in getting this working but I would also appreciate any pointers on improving the code, I have mouch to learn in C++
Thank-You!
BTW, If this helps clarify things, theApp.pTriadView->pTriadItem->Catalog is the COM class I am implementing
EDIT:
Thanks to #HansPassant (see first comment) I see what I was missing. Unfortunately I have hit a downstream result of fixing that. The VARIANT "pResult" is coming back empty. I will continue to hunt that down now but any thoughts would be welcome :)

How to detect if a given PIDL is actually a .zip file or similar?

VS2010 introduced CMFCShellTreeCtrl which allows for a folder-browser tree ctrl to be dropped into our MFC apps.
However, there seems to be a serious lack of filtering capabilities in this class. i.e. it will build the list of container-objects (IShellFolder). But there doesn't seem to be a way to specify that .zip containers should not be displayed in the folder tree.
It does supply a virtual that could be used crudely for this purpose:
HRESULT CMFCShellTreeCtrl::EnumObjects(HTREEITEM hParentItem, LPSHELLFOLDER pParentFolder, LPITEMIDLIST pidlParent)
{
ASSERT_VALID(this);
ASSERT_VALID(afxShellManager);
LPENUMIDLIST pEnum = NULL;
HRESULT hr = pParentFolder->EnumObjects(NULL, m_dwFlags, &pEnum);
if (FAILED(hr) || pEnum == NULL)
{
return hr;
}
LPITEMIDLIST pidlTemp;
DWORD dwFetched = 1;
// Enumerate the item's PIDLs:
while (SUCCEEDED(pEnum->Next(1, &pidlTemp, &dwFetched)) && dwFetched)
{
TVITEM tvItem;
ZeroMemory(&tvItem, sizeof(tvItem));
// Fill in the TV_ITEM structure for this item:
tvItem.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_CHILDREN;
// AddRef the parent folder so it's pointer stays valid:
pParentFolder->AddRef();
// Put the private information in the lParam:
LPAFX_SHELLITEMINFO pItem = (LPAFX_SHELLITEMINFO)GlobalAlloc(GPTR, sizeof(AFX_SHELLITEMINFO));
ENSURE(pItem != NULL);
pItem->pidlRel = pidlTemp;
pItem->pidlFQ = afxShellManager->ConcatenateItem(pidlParent, pidlTemp);
pItem->pParentFolder = pParentFolder;
tvItem.lParam = (LPARAM)pItem;
CString strItem = OnGetItemText(pItem);
tvItem.pszText = strItem.GetBuffer(strItem.GetLength());
tvItem.iImage = OnGetItemIcon(pItem, FALSE);
tvItem.iSelectedImage = OnGetItemIcon(pItem, TRUE);
// Determine if the item has children:
DWORD dwAttribs = SFGAO_HASSUBFOLDER | SFGAO_FOLDER | SFGAO_DISPLAYATTRMASK | SFGAO_CANRENAME | SFGAO_FILESYSANCESTOR;
pParentFolder->GetAttributesOf(1, (LPCITEMIDLIST*) &pidlTemp, &dwAttribs);
tvItem.cChildren = (dwAttribs & (SFGAO_HASSUBFOLDER | SFGAO_FILESYSANCESTOR));
// Determine if the item is shared:
if (dwAttribs & SFGAO_SHARE)
{
tvItem.mask |= TVIF_STATE;
tvItem.stateMask |= TVIS_OVERLAYMASK;
tvItem.state |= INDEXTOOVERLAYMASK(1); //1 is the index for the shared overlay image
}
// Fill in the TV_INSERTSTRUCT structure for this item:
TVINSERTSTRUCT tvInsert;
tvInsert.item = tvItem;
tvInsert.hInsertAfter = TVI_LAST;
tvInsert.hParent = hParentItem;
InsertItem(&tvInsert);
dwFetched = 0;
}
pEnum->Release();
return S_OK;
}
What I am confused by is the lack of ability to distinguish what type of object this is that is being enumerated (or a better way to control the enumeration in the first place so as to filter out non-filesystem objects such as these).
It's possible to look at the text for the item being enumerated, and simply exclude it if it ends in ".zip". However, this seems wonky to me. After all, an arbitrary folder could be named XYZ.zip (which still being a folder, and not a zip archive). Similarly, there are likely other archive types than just .zip that may be yet supported in the future - or other container types which are nevertheless not really folders.
I don't want to eliminate things like "Network" and "Computer" from being valid nodes. Just ones that are problematic such as "Downloads\Foobar.zip"
I apologize for this question being rambling. Any help in improving my understanding and creative approaches to knowing what sorts of objects are part of Microsoft's shell namespace, and how that they can be gainfully used would be appreciated!
A zip file/folder would have SFGAO_STREAM/SFGAO_DROPTARGET along with SFGAO_FOLDER, so if you can read the shell item as stream, then it probably isn't a directory. Another way to tell is to use SHGetPathFromIDList+PathIsDirectory, however this only works for pidl that have file system paths.
There are other kind of browsable files too, like a saved search (and if you browse into the file it takes like forever to finish enumerating the items), so you probably want to think about how to deal with those files too.

Get record set from stored procedure using C++ ADODB CommandPtr::Execute()

I'm trying to get a recordset from a stored procedure using ADODB.
Stored procedures get executeed successfully (Doing everything written in the SP), but the recordset count is "-1".
Here is what I'm doing (stored procedure has no parameters):
hr = ptrCom.CreateInstance(__uuidof(Command));
ptrCom->ActiveConnection = _connection;
ptrCom->CommandType = adCmdStoredProc;
ptrCom->CommandText = _bstr_t(_T("get_all_users"));
_variant_t vtEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);
ADODB::_RecordsetPtr record_set;
HRESULT normal_hr = ptrCom->raw_Execute(&vtEmpty, &vtEmpty, adCmdStoredProc, &record_set);
int cnt = record_set->RecordCount; // PROBLEM: cnt == -1 :-(
Can anyone point out what am I doing wrong?
thanks!
Have you tried just calling Execute?
ptrCom->Execute(NULL, NULL, ADODB::adCmdStoredProc);
returns _RecordSetPtr.
You might also want to try:
after you set the connection.
ptrCom->ActiveConnection->PutCursorLocation(ADODB::adUseClient);

Word automation - SaveAs

I try to write a simple MFC - Word Automation to save for every 1 minute.
I follow this article : http://www.codeproject.com/KB/office/MSOfficeAuto.aspx
And this is what Im trying to implement , I'm new to COM so I think there's problem here:
my VBA is generated by Word 2010:
ActiveDocument.SaveAs2 FileName:="1.docx", FileFormat:=wdFormatXMLDocument _
, LockComments:=False, Password:="", AddToRecentFiles:=True, _
WritePassword:="", ReadOnlyRecommended:=False, EmbedTrueTypeFonts:=False, _
SaveNativePictureFormat:=False, SaveFormsData:=False, SaveAsAOCELetter:= _
False, CompatibilityMode:=14
And my code to implement VBA code above :
{
COleVariant varName(L"b.docx");
COleVariant varFormat(L"wdFormatXMLDocument");
COleVariant varLockCmt((BYTE)0);
COleVariant varPass(L"");
COleVariant varReadOnly((BYTE)0);
COleVariant varEmbedFont((BYTE)0);
COleVariant varSaveNativePicFormat((BYTE)0);
COleVariant varForms((BYTE)0);
COleVariant varAOCE((BYTE)0);
VARIANT x;
x.vt = VT_I4;
x.lVal = 14;
COleVariant varCompability(&x);;
VARIANT result;
VariantInit(&result);
_hr=OLEMethod( DISPATCH_METHOD, &result, pDocApp, L"SaveAs2",10,
varName.Detach(),varFormat.Detach(),varLockCmt.Detach(),varPass.Detach(),varReadOnly.Detach(),
varEmbedFont.Detach(),varSaveNativePicFormat.Detach(),varForms.Detach(),varAOCE.Detach(),varCompability.Detach()
);
}
I get no error from this one, but it doesn't work.
The VBA syntax uses named parameters where the order and count of the parameters do not matter. However, when calling from C++ you need to pass the required number of parameters in the right order.
SaveAs2 is defined as:
void SaveAs2(
ref Object FileName,
ref Object FileFormat,
ref Object LockComments,
ref Object Password,
ref Object AddToRecentFiles,
ref Object WritePassword,
ref Object ReadOnlyRecommended,
ref Object EmbedTrueTypeFonts,
ref Object SaveNativePictureFormat,
ref Object SaveFormsData,
ref Object SaveAsAOCELetter,
ref Object Encoding,
ref Object InsertLineBreaks,
ref Object AllowSubstitutions,
ref Object LineEnding,
ref Object AddBiDiMarks,
ref Object CompatibilityMode
)
So, it has 17 parameters which means you should specify them all if you are going to pass CompatibilityMode, which was specified as 10th parameter in your example (that corressponds to SaveFormsData).
If you do not need all the parameters, or just for testing, you can try a simpler code:
_hr = OLEMethod(DISPATCH_METHOD, &result, pDocApp, L"SaveAs2", 2, varName.Detach(), varFormat.Detach());
If you need the rest of the parameters, you need to pass all the parameters up to the parameter you need set. In that case, you can pass
COleVariant vtOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
for the parameters you do not like to set.
Edit - Test Code
This works for me:
CoInitialize(NULL);
CLSID clsid;
IDispatch *pWApp;
HRESULT hr = CLSIDFromProgID(L"Word.Application", &clsid);
hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, IID_IDispatch, (void **)&pWApp);
hr = OLEMethod(DISPATCH_PROPERTYPUT, NULL, pWApp, L"Visible", 1, COleVariant((long)1));
VARIANT result;
VariantInit(&result);
hr = OLEMethod(DISPATCH_PROPERTYGET, &result, pWApp, L"Documents", 0);
IDispatch *pDocs = result.pdispVal;
VARIANT result2;
VariantInit(&result2);
hr = OLEMethod(DISPATCH_METHOD, &result2, pDocs, L"Open", 1, COleVariant(L"D:\\Archive\\t1.docx"));
IDispatch *pDoc = result2.pdispVal;
VARIANT result3;
VariantInit(&result3);
hr = OLEMethod(DISPATCH_METHOD, &result3, pDoc, L"SaveAs2", 1, COleVariant(L"D:\\Archive\\t2.docx"));
CoUninitialize();
Change your variable varFormat from wdFormatXMLDocument to an integer of 12 (probably like you've done the varCompability variable). Also, what's the 10 for after "SaveAs2"?
Guess I'll start over since you opened the bounty.
Change your variable varFormat from wdFormatXMLDocument to an integer of 12 (probably like you've done the varCompability variable). wdFormatXMLDocument is an enum of WdSaveFormat and was introduced in Word 2003. There is no need to send in an L"name" - just send in the integer of 12.
If these are previously saved documents (i.e. not new ones), perform a conversion first to get it to the right format (like ActiveDocument.Convert)