# Read and write files

> How-to guide: Access the file nodes of a server: read and append content, open files as a stream, check permissions, and handle errors.

> This content was machine translated and may contain errors. [Read in German](https://www.traeger.de/docs/development/opcua/client/file-access)

**Sample applications:**

- Server.CustomFile ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Scenarios/Server.CustomFile), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Scenarios/Server.CustomFile)): This use case represents in which way the IOpcFileInfo interface can be used to implement "custom file sources" for a OpcFileNode.

Types used: `OpcClient`, `OpcNodeId`, `OpcFile`, `OpcFileMode`, `OpcFileStream`, `OpcFileInfo`, `OpcFileMethods`, `SafeOpcFileHandle`

According to the OPC UA specification, nodes of the FileType type have defined properties (property nodes) and methods (method nodes). Through them, the client accesses a data stream the same way as a file in the file system. The node only provides information about the content of the logical or physical file. According to the specification, it does not provide a path to the file, even if one exists. The client accesses the file itself through the methods Open, Close, Read, Write, `GetPosition`, and `SetPosition`. These methods always process the data as binary. As on any other platform, in OPC UA you pass a mode when you call Open. The mode defines the kind of file access you intend. You can also request exclusive access to a file in OPC UA. After calling Open, the client receives a numeric key for further file access, the file handle. You always pass this handle to the methods Read, Write, `GetPosition`, and `SetPosition`. Close an open file with Close as soon as you no longer need it.

You can access nodes of the FileType type manually with the `ReadNode` and `CallMethod` methods of the `OpcClient` class. Alternatively, the OPC UA .NET SDK offers numerous other classes that implement access to such nodes, some of them modeled on .NET. The NodeId defines which file node the client accesses.

## Choose the right class

| Class | .NET counterpart | Suitable for |
|---|---|---|
| `OpcFile` | `System.IO.File` | reading or appending entire content in one call (`ReadAllText`, `ReadAllLines`, `ReadAllBytes`, `AppendAllText`, `AppendAllLines`) and opening streams (`Open`, `OpenRead`, `OpenWrite`, `OpenText`, `AppendText`) |
| `OpcFileInfo` (namespace `Opc.UaFx.Client`) | `System.IO.FileInfo` | querying the properties of a file (`Exists`, `Length`, `Name`, `MimeType`, `CanWrite`, `CanUserWrite`) and opening the file |
| `OpcFileStream` | `System.IO.FileStream` | reading, writing, and positioning through the `Stream` interface, also together with `StreamReader`, `StreamWriter`, or `CopyTo` |
| `OpcFileMethods` | none | calling the methods of the file node (`Open`, `Read`, `Write`, `GetPosition`, `SetPosition`, `Close`) one by one |

`OpcFileMode` defines the mode for opening: `Read`, `Write`, `ReadWrite`, `EraseExisting`, and `Truncate` (write after emptying the file). `OpcFile` offers no methods that overwrite an entire file in one call (like `File.WriteAllText`). To write, use a stream opened with `OpenWrite` or `Open`.

## Read and write entire files

**Read the entire content of a text file**:

```csharp
string reportText = OpcFile.ReadAllText(client, "ns=2;s=Machine/Report");

```

```vbnet
Dim reportText As String = OpcFile.ReadAllText(client, "ns=2;s=Machine/Report")

```

**Append more text to a text file**:

```csharp
OpcFile.AppendAllText(client, "ns=2;s=Machine/Report", "Lorem ipsum");

```

```vbnet
OpcFile.AppendAllText(client, "ns=2;s=Machine/Report", "Lorem ipsum")

```

**Open and read a file with `OpcFileStream`**:

```csharp
using (var stream = OpcFile.OpenRead(client, "ns=2;s=Machine/Report")) {
    var reader = new StreamReader(stream);
 
    while (!reader.EndOfStream)
        Console.WriteLine(reader.ReadLine());
}

```

```vbnet
Using stream = OpcFile.OpenRead(client, "ns=2;s=Machine/Report")
    Dim reader = New StreamReader(stream)

    While Not reader.EndOfStream
        Console.WriteLine(reader.ReadLine())
    End While
End Using
```

**Open and write a file with `OpcFileStream`**:

```csharp
using (var stream = OpcFile.OpenWrite(client, "ns=2;s=Machine/Report")) {
    var writer = new StreamWriter(stream);
 
    writer.WriteLine("Lorem ipsum");
    writer.WriteLine("dolor sit");
    // ...
}
```

```vbnet
Using stream = OpcFile.OpenWrite(client, "ns=2;s=Machine/Report")
    Dim writer = New StreamWriter(stream)

    writer.WriteLine("Lorem ipsum")
    writer.WriteLine("dolor sit")
    '...
End Using
```

**Read the entire content as lines or as bytes**:

```csharp
string[] lines = OpcFile.ReadAllLines(client, "ns=2;s=Machine/Recipe");
byte[] content = OpcFile.ReadAllBytes(client, "ns=2;s=Machine/Snapshot");
```

```vbnet
Dim lines As String() = OpcFile.ReadAllLines(client, "ns=2;s=Machine/Recipe")
Dim content As Byte() = OpcFile.ReadAllBytes(client, "ns=2;s=Machine/Snapshot")
```

## Get file information and open files

The `OpcFileInfo` class of the `Opc.UaFx.Client` namespace describes a single file. The `Opc.UaFx` namespace also contains a class named `OpcFileInfo`. For this reason, the following examples use the fully qualified type name:

**Create an instance of `OpcFileInfo`**:

```csharp
// The type name is qualified, because the Opc.UaFx namespace declares an OpcFileInfo too.
var file = new Opc.UaFx.Client.OpcFileInfo(client, "ns=2;s=Machine/Report");
```

```vbnet
'The type name is qualified, because the Opc.UaFx namespace declares an OpcFileInfo too.
Dim file = New Opc.UaFx.Client.OpcFileInfo(client, "ns=2;s=Machine/Report")
```

**Work with the instance of `OpcFileInfo`**:

```csharp
if (file.Exists) {
    Console.WriteLine("File Length: {0}", file.Length);
 
    if (file.CanUserWrite) {
        using (var stream = file.OpenWrite()) {
            // Your code to write via stream.
        }
    }
    else {
        using (var stream = file.OpenRead()) {
            // Your code to read via stream.
        }
    }
}
```

```vbnet
If file.Exists Then
    Console.WriteLine("File Length: {0}", file.Length)

    If file.CanUserWrite Then
        Using stream = file.OpenWrite()
            'Your code to write via stream.
        End Using
    Else
        Using stream = file.OpenRead()
            'Your code to read via stream.
        End Using
    End If
End If

```

**Query properties and write only if the user is allowed to**: `CanWrite` indicates whether the file is writable at all. `CanUserWrite` indicates whether the logged-on user is allowed to write it. An `OpcFileStream` opened for writing takes both into account. Its `CanWrite` property is `true` only if both apply:

```csharp
var file = new Opc.UaFx.Client.OpcFileInfo(client, "ns=2;s=Machine/Recipe");
 
Console.WriteLine(
        "{0}: {1} bytes, {2}",
        file.Name,
        file.Length,
        file.MimeType);
 
if (file.Exists && file.CanUserWrite) {
    using (var stream = file.OpenWrite())
    using (var writer = new StreamWriter(stream)) {
        writer.Write("Speed=1200");
    }
}

```

```vbnet
Dim file = New Opc.UaFx.Client.OpcFileInfo(client, "ns=2;s=Machine/Recipe")

Console.WriteLine( _
        "{0}: {1} bytes, {2}", _
        file.Name, _
        file.Length, _
        file.MimeType)

If file.Exists AndAlso file.CanUserWrite Then
    Using stream = file.OpenWrite()
        Using writer = New StreamWriter(stream)
            writer.Write("Speed=1200")
        End Using
    End Using
End If
```

## Process large files as a stream

**Copy large files**: Because `OpcFileStream` derives from `Stream`, you copy a file of the server to a local file with `CopyTo`. The stream reads the content in chunks:

```csharp
using (var source = OpcFile.OpenRead(client, "ns=2;s=Machine/Snapshot"))
using (var target = File.Create("snapshot.bin")) {
    source.CopyTo(target);
}

```

```vbnet
Using source = OpcFile.OpenRead(client, "ns=2;s=Machine/Snapshot")
    Using target = IO.File.Create("snapshot.bin")
        source.CopyTo(target)
    End Using
End Using
```

**Write at a specific position**: With `Seek`, you set the position within the file, for example to the end to append an entry:

```csharp
using (var stream = OpcFile.Open(client, "ns=2;s=Machine/Log", OpcFileMode.ReadWrite)) {
    stream.Seek(0, SeekOrigin.End);
 
    byte[] entry = Encoding.UTF8.GetBytes("Job started" + Environment.NewLine);
    stream.Write(entry, 0, entry.Length);
}
```

```vbnet
Using stream = OpcFile.Open(client, "ns=2;s=Machine/Log", OpcFileMode.ReadWrite)
    stream.Seek(0, SeekOrigin.End)

    Dim entry As Byte() = Encoding.UTF8.GetBytes("Job started" & Environment.NewLine)
    stream.Write(entry, 0, entry.Length)
End Using
```

## Call file methods directly

**Through the .NET concept `SafeHandle` (class `SafeOpcFileHandle`)**:

```csharp
using (var handle = OpcFileMethods.SecureOpen(client, "ns=2;s=Machine/Report", OpcFileMode.ReadWrite)) {
    byte[] data = OpcFileMethods.SecureRead(handle, 100);
 
    long position = OpcFileMethods.SecureGetPosition(handle);
    OpcFileMethods.SecureSetPosition(handle, position + data[data.Length - 1]);
 
    OpcFileMethods.SecureWrite(handle, new byte[] { 1, 2, 3 });
}

```

```vbnet
Using handle = OpcFileMethods.SecureOpen(client, "ns=2;s=Machine/Report", OpcFileMode.ReadWrite)
    Dim data As Byte() = OpcFileMethods.SecureRead(handle, 100)

    Dim position As Long = OpcFileMethods.SecureGetPosition(handle)
    OpcFileMethods.SecureSetPosition(handle, position + data(data.Length - 1))

    OpcFileMethods.SecureWrite(handle, New Byte() {1, 2, 3})
End Using

```

**Through a numeric file handle**:

```csharp
uint handle = OpcFileMethods.Open(client, "ns=2;s=Machine/Report", OpcFileMode.ReadWrite);
 
try {
    byte[] data = OpcFileMethods.Read(client, "ns=2;s=Machine/Report", handle, 100);
 
    ulong position = OpcFileMethods.GetPosition(client, "ns=2;s=Machine/Report", handle);
    OpcFileMethods.SetPosition(client, "ns=2;s=Machine/Report", handle, position + data[data.Length - 1]);
 
    OpcFileMethods.Write(client, "ns=2;s=Machine/Report", handle, new byte[] { 1, 2, 3 });
}
finally {
    OpcFileMethods.Close(client, "ns=2;s=Machine/Report", handle);
}

```

```vbnet
Dim handle As UInteger = OpcFileMethods.Open(client, "ns=2;s=Machine/Report", OpcFileMode.ReadWrite)

Try
    Dim data As Byte() = OpcFileMethods.Read(client, "ns=2;s=Machine/Report", handle, 100)

    Dim position As ULong = OpcFileMethods.GetPosition(client, "ns=2;s=Machine/Report", handle)
    OpcFileMethods.SetPosition(client, "ns=2;s=Machine/Report", handle, position + data(data.Length - 1))

    OpcFileMethods.Write(client, "ns=2;s=Machine/Report", handle, New Byte() {1, 2, 3})
Finally
    OpcFileMethods.Close(client, "ns=2;s=Machine/Report", handle)
End Try

```

Only file access through `OpcFile`, `OpcFileInfo`, `OpcFileStream`, and `SafeOpcFileHandle` guarantees that an open file is released even if the Close method is never called. In that case, the `OpcClient` closes all open files automatically, at the latest when the connection to the server closes. This does not apply if you use the methods of the `OpcFileMethods` class without the `Secure` prefix.

The OPC UA specification defines no way to recognize a node as a node of the FileType type and thus as a file node. For this reason, the SDK can identify a file node by its node structure:

```csharp
if (OpcFileMethods.IsFileNode(client, "ns=2;s=Machine/Report")) {
    // Your code to operate on the file node.
}

```

```vbnet
If OpcFileMethods.IsFileNode(client, "ns=2;s=Machine/Report") Then
    'Your code to operate on the file node.
End If

```

## Handle errors

If the specified node does not exist or is not a file node, the methods of `OpcFile`, `OpcFileInfo`, and `OpcFileMethods` throw an `InvalidOperationException`. If the server rejects a method call, the method throws an `OpcException` with the status of the server. This is the case, for example, if the user is not allowed to write the file or the file is already open for exclusive access. For operations that the mode of the stream does not allow (for example, writing to a stream opened for reading), `OpcFileStream` throws a `NotSupportedException`:

```csharp
try {
    string recipe = OpcFile.ReadAllText(client, "ns=2;s=Machine/Recipe");
}
catch (InvalidOperationException) {
    // The node does not exist or is not a file node.
}
catch (OpcException ex) {
    // The server rejected a file method, e.g. with BadUserAccessDenied.
    Console.WriteLine("{0}: {1}", ex.Code, ex.Message);
}
```

```vbnet
Try
    Dim recipe As String = OpcFile.ReadAllText(client, "ns=2;s=Machine/Recipe")
Catch ex As InvalidOperationException
    'The node does not exist or is not a file node.
Catch ex As OpcException
    'The server rejected a file method, e.g. with BadUserAccessDenied.
    Console.WriteLine("{0}: {1}", ex.Code, ex.Message)
End Try

```

## Security

Treat content you read from a server like external input. For example, do not use file names or paths from a file you read for access to the local file system without checking them. Limit the size (`Length` property) before you read a file into memory completely. Check the `CanUserWrite` property before you write.

## Scope

- The `OpcNodeStream` class reads large binary values that a server provides not as a file but as the value of a variable (see [Large binary values as a stream](https://www.traeger.de/en/docs/development/opcua/client/node-types#large-binary-values-as-a-stream)).
- The page [Node types](https://www.traeger.de/en/docs/development/opcua/server/node-types) in the server chapter describes how a server provides file nodes itself.
