Copying directory structure with SHFileOperation failed if FOF_NOERRORUI is requested - winapi

I have a working code utilizing SHFileOperation for copying one directory into another. In this case this is Pascal code, but I also have been using the same function in C++, and the problem seems related to Windows core, not a specific programming language.
According to MSDN, I want to specify the following combination of flags:
FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI
That is, I do not need a progress bar, I suppress all possible questions about files and directories by implied 'yes' answers, and I don't want any error message in GUI (dialog boxes).
With this combination of flags, the function returns error 0x4C7 (cancelled by user, which is not true). If I remove the FOF_NOERRORUI it works ok on the same input parameters and filesystem state.
Unfortunately, I need to suppress error messages as well, and FOF_NOERRORUI flag is required.
Does someone know how this combination of flags (and may be other prerequisites) should be adjusted to meet my needs?
Here is the source code for those who may think some bugs are there:
function CopyDirectory(WindowHandle: HWND; FilenameFrom: string; FilenameTo: string): Boolean;
var
SH: TSHFILEOPSTRUCT;
begin
FillChar(SH, SizeOf(SH), 0);
with SH do
begin
Wnd := WindowHandle;
wFunc := FO_COPY;
pFrom := PChar(FilenameFrom + #0);
pTo := PChar(FilenameTo + #0);
fFlags := FOF_NOCONFIRMMKDIR or FOF_NOCONFIRMATION or FOF_SILENT or FOF_NOERRORUI;
end;
Result := SHFileOperation(SH) = 0;
Result := Result and (not SH.fAnyOperationsAborted);
end;

0x4C7 is actually:
"The operation was canceled by the user, or silently canceled if the appropriate flags were supplied to SHFileOperation."
If you turn off all the flags and let the operation run, what sort of questions are you asked? My guess is that one of those questions is being answered as "No" because the safe option is to do that.
Update
Have you thought of using the CopyFile() API function? No UI suppression necessary. The documentation is here.

Related

Inno Setup: Overwrite existing installation or show dir prompt

It would be nice if I have this in my setup:
If there is no previous installation, then an edit field for the destination directory should be shown.
If there is a previous installation, than the user should should be asked if he wants to overwrite the existing installation (no directory
prompt should be visible) or if he wants to install this version in a
different directory as a separate installation (two entries in the uninstall list). If this option is chosen, than the edit field
for the destination directory should be shown.
Is it possible to use existing Inno Setup options to achieve this? Or do I have to build a custom dialog page?
At the beginning (InitializeSetup event function), check if the application is installed already (see GetUninstallString in the code below). If it is, ask user, what to do (see MsgBox use in the code and the first screenshot). If user chooses to update the existing installation, proceed normally. Inno Setup by default does not allow changing installation path of an existing installation (see DisableDirPage).
If uses chooses to install another copy, set AppId to a new unique value (GetAppId function in the code). This will make Inno Setup treat the installation as new, so it will prompt for the installation path. Update also UninstallDisplayName, so that the user can distinguish the installations when choosing which copy to uninstall (see GetAppIdentification and the third screenshot). Also update DefaultDirName to a new unique path (see GetAppIdentification and the third screenshot).
#define AppName "My Program"
#define AppVersion "1.5"
[Setup]
AppId={code:GetAppId}
AppName={#AppName}
AppVersion={#AppVersion}
UninstallDisplayName={#AppName} {#AppVersion}{code:GetAppIdentification}
UsePreviousLanguage=no # Needed when AppId is dynamic
DefaultDirName={autopf}\My Program{code:GetAppIdentification}
[Code]
var
Instance: string;
function GetAppId(Param: string): string;
begin
Result := '{#AppName}' + Instance;
end;
function GetAppIdentification(Param: string): string;
begin
if Instance <> '' then Result := ' (' + Instance + ')';
end;
function GetUninstallString(): string;
var
UninstallKey: string;
begin
UninstallKey :=
'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + GetAppId('') + '_is1';
RegQueryStringValue(HKA, UninstallKey, 'UninstallString', Result);
Log(Result)
end;
function InitializeSetup(): Boolean;
var
Message: string;
Answer: Integer;
begin
Result := True;
if GetUninstallString() = '' then
begin
Log('Application is not installed yed, installing the first copy');
end
else
begin
Log('Application is installed already, asking what to do');
Message :=
'This program is installed already, ' +
'do you want to update the existing installation? ' +
'Press No to install another copy of the program';
Answer := MsgBox(Message, mbConfirmation, MB_YESNOCANCEL);
if Answer = IDYES then
begin
Log('User chose to update the installation');
end
else
if Answer = IDNO then
begin
Log('User chose to install another copy');
Instance := '2';
end
else
begin
Log('User chose to abort the installation');
Result := False;
end;
end;
end;
Now the question is what to do if there are already two installations. To make a third (or more), it's easy, just loop, increasing the value in Instance, until GetUninstallString returns an empty string. But had you wanted the user to be able to choose what copy to update, it would be more difficult. That's too much for one question.
What you want to do is quite complicated. If you want to keep the flexibility, I think that the easiest solution is to treat every new version as a separate software. In addition, when starting the installation, as a courtesy to those who want to keep the latest version only, offer to uninstall the previous (latest) installation automatically. If the user already has multiple installations, do nothing specific (or just inform the user).
I solved this problem by putting the responsibility on the user (the one installing the application) to explicitly specify that they want to install a separate instance of the application by specifying a /instancename parameter on the installer's command line (the AppId directive uses a scripted constant).

Is there a way to print to output console? (twincat3)

Is there a way to print to output to console like debug.print() in VB.NET using structured text? (twincat3)
You can send messages through ADS commands from TwinCAT code. The function is called ADSLOGSTR. There also also own functions for DINT and REAL, but the STRING function of course can be used with anything.
The function has three inputs:
msgCtrlMask
Mask that describes the message type
Types can be found here
For example, to show warning message and save it to Windows log: msgCtrlMask := ADSLOG_MSGTYPE_WARN OR ADSLOG_MSGTYPE_LOG
To show just a Windows MessageBox: msgCtrlMask := ADSLOG_MSGTYPE_MSGBOX
msgFmtStr
The message to be shown as STRING
A %s can be used to add parameter without CONCAT functions. See the last parameter.
strArg
A STRING that is replaces the %s in previous string.
Here is an example the probably is what you need:
IF test THEN
ADSLOGSTR(
msgCtrlMask := ADSLOG_MSGTYPE_HINT,
msgFmtStr := 'Test message. Parameter is %s',
strArg := 'ABC'
);
test := false;
END_IF
When you set the test true, and call the function, you will see this on your Visual Studio error list. Note that it is not written to console.
I often use error messages (ADSLOG_MSGTYPE_ERROR) because I hide notes and warnings quite often and the I wouldn't notice my own entries. Other good way is to add the entry to the Windows log, if you want to log something to be seen later.

Read/Write to file on old IBM PS/2 in turbo pascal 5.5

The Question: I recently acquired a 1989 IBM PS2 and I am trying move large files from my newer UNIX-based machine to this IBM via floppy. I have a bash script that splits my files into ~2MB chunks, now I am trying to write a pascal program to reconstruct these files after they have been transferred.
I am unable to find the correct read/write to file methods on this computer. I have tried various pascal tutorial sites, but they are all for newer versions (the site I followed with File Handling In Pascal). I am able to create an empty file (as described below), but I am unable to write to it. Does anyone know the correct pascal read and write methods for this type of computer?
I know this is an obscure question, so thank you in advance for any help you can give me!
The Details:
The current test code that creates a file correctly is this:
program testingFiles;
uses Crt, Win;
const FILE_NAME = 'testFile.txt';
var outFile : File;
begin
writeln('creating file ...');
Assign(outFile, FILE_NAME);
rewrite(outFile);
end.
This is some test code that does not work, the method's append() and close() could not be found:
program testingFiles;
uses Crt, Win;
const FILE_NAME = 'testFile.txt';
var outFile : File;
begin
writeln('creating file ...');
Assign(outFile, FILE_NAME);
append(outFile);
writeln('this should be in the file');
close(outFile);
end.
This is an alternative that also did not work, the writeln() method only ever prints to the terminal. But otherwise this does compile.
program testingFiles;
uses Crt, Win;
const FILE_NAME = 'testFile.txt';
var outFile : File;
begin
writeln('creating file ...');
Assign(outFile, FILE_NAME);
rewrite(outFile);
writeln('this should be in the file');
close(outFile);
end.
The system: As was previously mentioned, this is a 1989 IBM PS2.
It has Windows 3.0 installed and can also run DOS and MS-DOS terminals.
It has Microsoft SMARTDrive Disk Cache version 3.06
It has Turbo Pascal 5.5 installed and I am using turbo as my command line pascal editor. (the readme was last updated in 1989)
It has Turbo debugger 1.5 installed.
Again, I know this is an obscure question, so thank you in advance for any help you can give me!
My Pascal memory is VERY rusty... but as other have pointed out, here is what you should consider:
program testingFiles;
uses Crt, System;
//No need of importin Win Win is for Windows enviorment, however I'm not sure if you need to use System, Sysutils or was there a Dos class???
const FILE_NAME = 'testFile.txt';
var outFile,inFile : File;
begin
writeln('creating file ...');
Assign(outFile, FILE_NAME);
rewrite(outFile);
//Now Open the first chunk of the file you want to concatenate
AssignFile(inFile, "fisrt_chunk.dat");
reset(inFile);
while not eof(inFile) do
begin
readln(inFile, s);
writeln(outFile,s);
end;
close(inFile);
end.
I don't have Turbo/Borland Pascal installed any longer so I couldn't compile it myself, no promise that it will work it is more like an idea:
Key thing to remember, readln and writeln will ALWAYS add a return at the end of the string/line, read and write on the other hand will leave the cursor wherever it is without jumping to a new line.
Here's some old Delphi code that should be at least close to syntax-compatible that will give you the gist of copying a file (with limited error checking and resource handling in case of error - I'll leave that as an exercise for you). It works to copy both binary and text content.
program Project2;
uses
SysUtils;
var
NumRead, NumWritten: LongInt;
pBuff : PChar;
SrcFile, DstFile: File;
const
BuffSize = 2048; // 2K buffer. Remember not much RAM available
InFileName = 'somefile.txt';
OutFileName = 'newfile.txt';
begin
NumRead := 0;
NumWritten := 0;
AssignFile(SrcFile, InFileName);
AssignFile(DstFile, OutFileName);
// Allocate memory for the buffer
GetMem(pBuff, BuffSize);
FileMode := 0; // Make input read-only
Reset( SrcFile, 1 );
FileMode := 2; // Output file read/write
Rewrite( DstFile, 1 );
repeat
// Read a buffer full from input
BlockRead(SrcFile, pBuff^, BuffSize, NumRead);
// Write it to output
BlockWrite(DstFile, pBuff^, NumRead, NumWritten);
until (NumRead = 0) or (NumWritten <> NumRead);
// Cleanup stuff. Should be protected in a try..finally,
// of course.
CloseFile(SrcFile);
CloseFile(DstFile);
FreeMem(pBuff);
end.
The above code compiles under Delphi 2007 currently (the oldest version I have installed). (See the note below.)
As a side note, this was from an archived version of some code I had that compiled both for 16-bit Delphi 1 and was extended to also compile under 32-bit Delphi 2 back in the mid-to-late 90s. It's still hanging around in my source repositories in an old tagged branch. I think I need to do some pruning. :-) I cleaned it up to remove some other functionality and removed a lot of {$IFDEF WIN32} ... {$ELSE} ... {$ENDIF} stuff before posting.)

Open file in OS X

In Delphi, I would like to open a file in OS X. My approach is as follows:
const
Filename = 'test.bmp';
procedure SaveAndOpen;
begin
Chart.SaveToBitmapFile(Filename);
{$IFDEF MSWINDOWS}
ShellExecute(0, 'open', Filename, '', '', SW_Normal);
{$ELSE}
_System(Filename);
{$ENDIF}
end;
But nothing happens. What am I doing wrong?
This article from Embarcadero's Malcolm Groves covers this topic: Opening files and URLs in default applications in OS X.
In summary, all you need is this:
uses
Macapi.Appkit, // for NSWorkspace
Macapi.Foundation; // for NSSTR
....
var
Workspace: NSWorkspace; // interface, no need for explicit destruction
....
Workspace := TNSWorkspace.Create;
Workspace.openFile(NSSTR(FileName));
For sake of completeness, should you wish to open a URL rather than a file, then you call openURL instead:
Workspace.openURL(NSSTR(URL));
Regarding your Windows code, I would recommend not using ShellExecute. That function does not have reasonable error reporting. Use ShellExecuteEx in its place.
And finally, you should probably abstract this functionality away so that it can be re-used by other parts of your program. You want to write that IFDEF as few times as possible.
You must add the open verb like so
_System(PAnsiChar('open ' + Filename));

Do Windows shortcuts support very long argument lengths?

I am trying to create a shortcut (on the Desktop) that contains a long argument string (> MAX_PATH).
The MSDN documentation clearly states that for Unicode string the string can be longer than MAX_PATH.
The resulting shortcut is cut exactly after MAX_PATH characters (that is the Path + the Arguments).
Is there something wrong with my implementation or is this some Windows limitation?
procedure CreateShortcut(APath: WideString;
AWorkingDirectory: WideString; AArguments: WideString; ADescription: WideString;
ALinkFileName: WideString);
var
IObject : IUnknown;
ISLink : IShellLinkW;
IPFile : IPersistFile;
begin
IObject := CreateComObject(CLSID_ShellLink);
ISLink := IObject as IShellLinkW;
ISLink.SetPath( PWideChar(APath));
ISLink.SetWorkingDirectory(PWideChar(AWorkingDirectory));
ISLink.SetArguments( PWideChar(AArguments));
ISLink.SetDescription( PWideChar(ADescription));
IPFile := IObject as IPersistFile;
IPFile.Save(PWideChar(ALinkFileName), False);
end;
PS: OS is Windows XP (and above).
It turns out that this issue is in fact solely a limitation in the Explorer shell dialog. The generated shortcut file does not have a 260 character limitation. It's simply that the dialog refuse to display a Target with more characters than that. Presumably it calls GetPath with a fixed length buffer.
procedure TForm11.Button1Click(Sender: TObject);
var
sl: IShellLinkW;
pf: IPersistFile;
begin
CoCreateInstance(CLSID_ShellLink, nil,
CLSCTX_INPROC_SERVER, IID_IShellLinkW, sl);
sl.SetPath('c:\desktop\test.bat');
sl.SetWorkingDirectory('c:\desktop\');
sl.SetArguments(PChar(StringOfChar('x', 300)+'_the_end'));
pf := sl as IPersistFile;
pf.Save('c:\desktop\test.lnk', False);
end;
My test.bat looks like this:
echo %1> test.out
The resulting test.out goes right the way to _the_end!
Thanks all who contributed to this thread - it helped me immensely.
However, if I may, I would like to add the below information I discovered in crafting my solution:
On Windows 7 Enterprise ~SP1, it would seem that using VBS to create the shortcut there is still a limit on maximum characters in (at least) the arguments field. I tested up to 1023 chars before it got trunicated. I presume the same limit would apply to the Delphi method likewise.
On Windows XP Professional ~SP3, while the VBS method will create a shortcut longer than 260 characters (lnk file contains the data), it seems to trunicate it at about this number when executing it.

Resources