Jump to content
chkaufmann

Update hidden file

Recommended Posts

When I use TStringList.SaveToFile() on a hidden text file, I get a create error. Even if I do this as administrator.

 

So the only solution I see is to read the attributes with GetFileAttributes, then save the file and then use SetFileAttributes to hide the file again. Or do I miss something?

 

Christian

Share this post


Link to post
23 minutes ago, chkaufmann said:

So the only solution I see is to read the attributes with GetFileAttributes, then save the file and then use SetFileAttributes to hide the file again. Or do I miss something?

Yes you miss something.

Get attribs, set attribs without readonly/hidden flag, save file, restore original attribs.

Share this post


Link to post

You're right.  A hidden file can be replaced only with a hidden file. Unfortunately you cannot create a hidden file as in FileCreate the attribute is hard coded to be FILE_ATTRIBUTE_NORMAL.

Share this post


Link to post
47 minutes ago, Lajos Juhász said:

FILE_ATTRIBUTE_NORMAL

Hmm... that I need to try.

I would thought that it had ARCHIVE flag set, NORMAL would be no attribute.

 

To be continued...

Share this post


Link to post
6 hours ago, KodeZwerg said:

Get attribs, set attribs without readonly/hidden flag, save file, restore original attribs.

From the CreateFile documentation:

Quote

If CREATE_ALWAYS and FILE_ATTRIBUTE_NORMAL are specified, CreateFile fails and sets the last error to ERROR_ACCESS_DENIED if the file exists and has the FILE_ATTRIBUTE_HIDDEN or FILE_ATTRIBUTE_SYSTEM attribute. To avoid the error, specify the same attributes as the existing file.

So, you don't have to actually remove the attributes, just match the existing attributes.  Which FileCreate() can't do.  But you could call CreateFile() directly, and then wrap the HANDLE in a THandleStream if needed.

  • Like 1

Share this post


Link to post
Posted (edited)

Thank you @Remy Lebeaueven if I dont understand why telling me.

TStringList.SaveToFile() uses internal TFileStream.Create() with fmCreate flag  where normal users have no access to.

 

If you say my answer is wrong  please explain to learn from.

 

To make myself clear, my steps:

0. "Var VariableX: DWORD"

1. Get attributes

    "VariableX := GetFileAttributes(PChar(FileName))"

2. Clear attributes to have write access

    "SetFileAttributes(PChar(FileName), FILE_ATTRIBUTE_NORMAL)"

3. Invoke "TSringList.SaveToFile(FileName)"

4. Restore 

    SetFileAttributes(PChar(FileName), VariableX)"

 

If I typed correct this is how it work, no?

 

Missing things like checking for existance and that it is a file.

Edited by KodeZwerg
Spelling

Share this post


Link to post

Thanks Remy I missed the part that you can give a Handle to the TFileStream constructor. You can replace the file using this code. Of course you've to make sure first the file exists:
 

procedure TForm1.FormCreate(Sender: TObject);
var lStrList: TStringList;
    lfStream: TFileStream;
    lFileName: PChar;
begin
  lFileName:='D:\temp\0104.txt';
  lStrList:=TStringList.Create;
  lfStream:=TFileStream.Create(Createfile(lFileName, GENERIC_WRITE, FILE_SHARE_WRITE, nil, CREATE_ALWAYS, GetFileAttributes(lFileName), 0));
  try
    lStrList.SaveToStream(lfStream);
  finally
    lfStream.Free;
  end;
end;

 

  • Like 2

Share this post


Link to post

I got a flashing bulb over my head 💡

 

Now I do understand what you both wrote, I was not aware that you had a TStringList.SaveToFile() replacement in mind.

 

By reading CreateFileW() doc  I wonder what happen with created Handle.

Wouldnt it be better to split it up and call CloseHandle() afterwards?

Share this post


Link to post
4 minutes ago, KodeZwerg said:

By reading CreateFileW() doc  I wonder what happen with created Handle.

Wouldnt it be better to split it up and call CloseHandle() afterwards?

The answer is here, the TFileStream will take over the ownership and close it in the destructor (FileClose will call CloseHandle):
 

destructor TFileStream.Destroy;
begin
  if FHandle <> INVALID_HANDLE_VALUE then
    FileClose(FHandle);
  inherited Destroy;
end;

 

  • Thanks 1

Share this post


Link to post
1 hour ago, Lajos Juhász said:

Thanks Remy I missed the part that you can give a Handle to the TFileStream constructor.

I said to use THandleStream, not TFileStream.  TFileStream does not have a constructor that accepts an external THandle as input.  Unless that is a recent addition that has not been documented yet.

1 hour ago, Lajos Juhász said:

Of course you've to make sure first the file exists

Sure, eg:

procedure TForm1.FormCreate(Sender: TObject);
var
  lFile: THandle;
  lStrList: TStringList;
  lStream: THandleStream;
  lFileName: PChar;
  lAttrs: DWORD;
begin
  lStrList := TStringList.Create;
  try
    ...
    lFileName := 'D:\temp\0104.txt';
    lAttrs := GetFileAttributes(PChar(lFileName));
    if lAttrs = INVALID_FILE_ATTRIBUTES then
    begin
      if GetLastError() <> ERROR_FILE_NOT_FOUND then RaiseLastOSError;
      lAttrs = FILE_ATTRIBUTE_NORMAL;
    end;
    lFile := CreateFile(lFileName, GENERIC_WRITE, FILE_SHARE_WRITE, nil, CREATE_ALWAYS, lAttrs, 0);
    if lFile = INVALID_HANDLE_VALUE then RaiseLastOSError;
    try
      lStream := THandleStream.Create(lFile);
      try
        lStrList.SaveToStream(lStream);
      finally
        lStream.Free;
      end;
    finally
      CloseHandle(lFile);
    end;
  finally
    lStrList.Free;
  end;
end;

 

  • Thanks 1

Share this post


Link to post
2 hours ago, KodeZwerg said:

If you say my answer is wrong  please explain to learn from.

I didn't say it was wrong, just that it does not follow what Microsoft says to do.

Share this post


Link to post
3 minutes ago, Remy Lebeau said:

I said to use THandleStream, not TFileStream.  TFileStream does not have a constructor that accepts an external THandle as input.

 

 

TFileStream is derived from THandleStream and unlike the THandleStream it will close the file in the destructor as I showed in my earlier post, so it's just a try ... finally less to write.

 

Share this post


Link to post
Posted (edited)
4 hours ago, Lajos Juhász said:

TFileStream is derived from THandleStream and unlike the THandleStream it will close the file in the destructor

I'm aware of that.  But I have never seen an example of anyone ever passing an existing file handle to a TFileStream constructor, only to a THandleStream constructor.

Edited by Remy Lebeau

Share this post


Link to post
Posted (edited)

Strange. Passing handle to TFIleStream.Create does not work in Delphi 5, but works in Delphi XE. It also does not compile in FPC 3.2.0. And I do not understand, why it compiles with Delphi XE.

After all, TComponent.Create; does not compile....

Edited by Virgo
fixing missing not

Share this post


Link to post
16 hours ago, Remy Lebeau said:

 


Attrs := GetFileAttributes(PChar(lFileName));

 

Sorry for another beginner question.

Why you call it that way? Isnt lFileName not already a PChar at that moment?

Share this post


Link to post
Quote

A method can be redeclared using the overload directive. In this case, if the redeclared method has a different parameter signature from its ancestor, it overloads the inherited method without hiding it. Calling the method in a descendent class activates whichever implementation matches the parameters in the call.

So it appears, that calling TFileStream.Create with handle parameter works because, TFIleStream has constructors with overload;

That is something, that I newer knew.

 

Works in all Delphi ja FPC versions. Just in FPC TFileStream constructors are not with overload;

 

Share this post


Link to post
1 hour ago, Virgo said:

So it appears, that calling TFileStream.Create with handle parameter works because, TFIleStream has constructors with overload

I checked, and overload was added to TFileStream in Delphi 6, when the 3-parameter constructor for passing in Rights was introduced.

Share this post


Link to post
3 hours ago, KodeZwerg said:

Why you call it that way? Isnt lFileName not already a PChar at that moment?

Yes, it is. But I didn't notice that initially, I thought it was a String instead (which it should be).

Share this post


Link to post
12 minutes ago, Remy Lebeau said:

I checked, and overload was added to TFileStream in Delphi 6, when the 3-parameter constructor for passing in Rights was introduced.

Right. And fpc allows in objfpc mode multiple constructors without overload, which explains why  TFileStream in fpc has two constructors (2 parameters and 3 parameters), but does not allow creating TFileStream with THandleStream constructor.

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×