`robot.Rws.File` gives access to the file system of the controller: browse it, download and upload files, and create, rename, copy or delete files and directories. Nothing has to be shared or mounted on the PC.

Paths are the ones used on the controller. The environment variables of the controller are accepted where a path is expected, `$home` and `$temp` for example, and they behave as directories. `robot.Rws.Controller.GetEnvironmentVariable` gives the real path behind such a name.

## Browse the file system

`ListDirectory` returns a `DirectoryListing` with the files, the subdirectories and, at the root, the storage devices of the controller. The complete content is always returned, however many entries the directory holds.

**C# : FileList**
```csharp
using UnderAutomation.ABB;
using UnderAutomation.ABB.Rws.Data;

public class FileList
{
    static void Main()
    {
        AbbController robot = new AbbController();
        robot.Connect("192.168.0.1");

        /**/
        // The root lists the storage devices of the controller
        DirectoryListing root = robot.Rws.File.ListDirectory("/");

        foreach (DeviceItem device in root.Devices)
        {
            Console.WriteLine($"{device.Name} ({device.DeviceType}) {device.FreeSpace}/{device.TotalSpace} bytes");
        }

        // A directory lists its files and its subdirectories
        DirectoryListing listing = robot.Rws.File.ListDirectory("$home");
        Console.WriteLine($"{listing.DirectoryCount} directories, {listing.FileCount} files");

        foreach (DirectoryItem directory in listing.Directories)
        {
            Console.WriteLine($"[DIR] {directory.Name}");
        }

        foreach (FileItem file in listing.Files)
        {
            Console.WriteLine($"{file.Name} {file.Size} bytes, modified {file.ModificationDate}");
        }
        /**/

        robot.Disconnect();
    }
}
```

**Python : FileList**
```python
from underautomation.abb.abb_controller import AbbController

robot = AbbController()
robot.connect("192.168.0.1")

##
# The root lists the storage devices of the controller
root = robot.rws.file.list_directory("/")

for device in root.devices:
    print(f"{device.name} ({device.device_type}) {device.free_space}/{device.total_space} bytes")

# A directory lists its files and its subdirectories
listing = robot.rws.file.list_directory("$home")
print(f"{listing.directory_count} directories, {listing.file_count} files")

for directory in listing.directories:
    print(f"[DIR] {directory.name}")

for file in listing.files:
    print(f"{file.name} {file.size} bytes, modified {file.modification_date}")
##

robot.disconnect()
```

Pass `"/"` or null to list the root. The root is where the storage devices are, with their free and total space. A `DeviceItem` has a `DeviceType`: `Fixed` for an internal disk, `Removable` for a USB key, `RamDisk`, `Remote` for a network storage, `Unknown` otherwise.

`FileCount`, `DirectoryCount`, `DeviceCount` and `TotalCount` are computed by the SDK, they save a null check on the three arrays.

## One class per kind of item

`FileItem`, `DirectoryItem` and `DeviceItem` all derive from `FileSystemItem`, which carries the name and the dates. There is no flag saying what an item is, the type itself says it.

**C# : FileSystemItems**
```csharp
using UnderAutomation.ABB;
using UnderAutomation.ABB.Rws.Data;

public class FileSystemItems
{
    static void Main()
    {
        AbbController robot = new AbbController();
        robot.Connect("192.168.0.1");

        DirectoryListing listing = robot.Rws.File.ListDirectory("/");

        /**/
        // The three arrays hold subclasses of the same base class
        List<FileSystemItem> items = new List<FileSystemItem>();
        items.AddRange(listing.Directories);
        items.AddRange(listing.Files);
        items.AddRange(listing.Devices);

        foreach (FileSystemItem item in items)
        {
            // Name, CreationDate and ModificationDate come from the base class
            Console.Write($"{item.Name} {item.ModificationDate} ");

            // What the item really is, is found with a type test, not with a flag
            if (item is FileItem file)
            {
                Console.WriteLine($"file of {file.Size} bytes");
            }
            else if (item is DirectoryItem directory)
            {
                Console.WriteLine($"directory, read only: {directory.IsReadOnly}");
            }
            else if (item is DeviceItem device)
            {
                Console.WriteLine($"device of type {device.DeviceType}, {device.FreeSpace} bytes free");
            }
        }
        /**/

        robot.Disconnect();
    }
}
```

**Python : FileSystemItems**
```python
from underautomation.abb.abb_controller import AbbController
from underautomation.abb.rws.data.device_item import DeviceItem
from underautomation.abb.rws.data.directory_item import DirectoryItem
from underautomation.abb.rws.data.file_item import FileItem

robot = AbbController()
robot.connect("192.168.0.1")

listing = robot.rws.file.list_directory("/")

##
# The three lists hold subclasses of the same base class
items = list(listing.directories) + list(listing.files) + list(listing.devices)

for item in items:
    # name, creation_date and modification_date come from the base class
    line = f"{item.name} {item.modification_date} "

    # What the item really is, is found with a type test, not with a flag
    if isinstance(item, FileItem):
        line += f"file of {item.size} bytes"
    elif isinstance(item, DirectoryItem):
        line += f"directory, read only: {item.is_read_only}"
    elif isinstance(item, DeviceItem):
        line += f"device of type {item.device_type}, {item.free_space} bytes free"

    print(line)
##

robot.disconnect()
```

`CreationDate` and `ModificationDate` are nullable, the controller does not always report them. `Size` is in bytes and only exists on a file.

## Download a file

Four methods read a file, they differ only by what you get back.

| Method                                  | Returns                                   |
| --------------------------------------- | ----------------------------------------- |
| `GetFileAsText(path, encoding)`         | `string`, UTF-8 when no encoding is given |
| `GetFileAsBytes(path)`                  | `byte[]`                                  |
| `GetFileToDestination(path, localPath)` | Nothing, the file is written on the PC    |
| `GetFileAsReadonlyStream(path)`         | A readable `Stream`                       |

**C# : FileDownload**
```csharp
using System.Text;
using UnderAutomation.ABB;

public class FileDownload
{
    static void Main()
    {
        AbbController robot = new AbbController();
        robot.Connect("192.168.0.1");

        /**/
        // A text file, UTF-8 by default
        string text = robot.Rws.File.GetFileAsText("$home/notes.txt");

        // Another encoding when the file is not UTF-8
        string latin = robot.Rws.File.GetFileAsText("$home/notes.txt", Encoding.GetEncoding("iso-8859-1"));

        // A binary file
        byte[] bytes = robot.Rws.File.GetFileAsBytes("$home/backup.zip");

        // Straight to a file of the PC
        robot.Rws.File.GetFileToDestination("$home/backup.zip", @"C:\temp\backup.zip");
        /**/

        /**/
        // As a stream, to copy the content without keeping a second copy of it
        using (Stream source = robot.Rws.File.GetFileAsReadonlyStream("$home/backup.zip"))
        using (FileStream destination = System.IO.File.Create(@"C:\temp\backup.zip"))
        {
            source.CopyTo(destination);
        }
        /**/

        robot.Disconnect();
    }
}
```

**Python : FileDownload**
```python
from underautomation.abb.abb_controller import AbbController

robot = AbbController()
robot.connect("192.168.0.1")

##
# A binary file, as a list of bytes
data = robot.rws.file.get_file_as_bytes("$home/backup.zip")

# A text file, decoded by your code. UTF-8 is the usual encoding.
text = bytes(robot.rws.file.get_file_as_bytes("$home/notes.txt")).decode("utf-8")

# Another encoding when the file is not UTF-8
latin = bytes(robot.rws.file.get_file_as_bytes("$home/notes.txt")).decode("iso-8859-1")

# Straight to a file of the PC, without keeping the content in memory
robot.rws.file.get_file_to_destination("$home/backup.zip", r"C:\temp\backup.zip")
##

robot.disconnect()
```

Use `GetFileAsText` for a RAPID module, a configuration file or a log. Use the bytes or the stream for a backup archive or any binary content. The synchronous `GetFileAsReadonlyStream` buffers the whole content in memory first, for compatibility with .NET Framework 3.5 and 4.0. `GetFileAsReadonlyStreamAsync` reads from the network as you read the stream, which is the one to use for a large file.

## Upload a file

The four upload methods mirror the downloads. An existing file is replaced.

**C# : FileUpload**
```csharp
using UnderAutomation.ABB;

public class FileUpload
{
    static void Main()
    {
        AbbController robot = new AbbController();
        robot.Connect("192.168.0.1");

        /**/
        // From a string, UTF-8 by default
        robot.Rws.File.UploadFileFromText("$home/notes.txt", "Written by the SDK");

        // From raw bytes
        robot.Rws.File.UploadFileFromBytes("$home/data.bin", new byte[] { 1, 2, 3, 4 });

        // From a file of the PC
        robot.Rws.File.UploadFileFromPath("$home/MyModule.mod", @"C:\temp\MyModule.mod");

        // From a stream, for a large file
        using (FileStream source = System.IO.File.OpenRead(@"C:\temp\MyModule.mod"))
        {
            robot.Rws.File.UploadFileFromStream("$home/MyModule.mod", source);
        }
        /**/

        robot.Disconnect();
    }
}
```

**Python : FileUpload**
```python
from underautomation.abb.abb_controller import AbbController

robot = AbbController()
robot.connect("192.168.0.1")

##
# From a string, encoded by your code. UTF-8 is the usual encoding.
robot.rws.file.upload_file_from_bytes("$home/notes.txt", "Written by the SDK".encode("utf-8"))

# From raw bytes
robot.rws.file.upload_file_from_bytes("$home/data.bin", bytes([1, 2, 3, 4]))

# From a file of the PC, for a large file
robot.rws.file.upload_file_from_path("$home/MyModule.mod", r"C:\temp\MyModule.mod")
##

robot.disconnect()
```

`UploadFileFromText` writes UTF-8 by default, pass an `Encoding` for another one. `UploadFileFromStream` sends the content as it reads it, so a large file does not have to be loaded in memory.

Create the destination directory first with `CreateDirectory` when it does not exist yet.

## Create, rename, copy and delete

**C# : FileManage**
```csharp
using UnderAutomation.ABB;

public class FileManage
{
    static void Main()
    {
        AbbController robot = new AbbController();
        robot.Connect("192.168.0.1");

        /**/
        // Directories. The new name can be nested, the missing levels are created.
        robot.Rws.File.CreateDirectory("$home", "MyApp/Logs");
        robot.Rws.File.RenameDirectory("$home/MyApp/Logs", "Archive");
        robot.Rws.File.CopyDirectory("$home/MyApp/Archive", "Archive2", true);
        robot.Rws.File.DeleteDirectory("$home/MyApp/Archive2");
        /**/

        /**/
        // Files. The third argument of the copy overwrites an existing target.
        robot.Rws.File.RenameFile("$home/notes.txt", "notes-old.txt");
        robot.Rws.File.CopyFile("$home/notes-old.txt", "notes-backup.txt", true);
        robot.Rws.File.DeleteFile("$home/notes-old.txt");
        /**/

        robot.Disconnect();
    }
}
```

**Python : FileManage**
```python
from underautomation.abb.abb_controller import AbbController

robot = AbbController()
robot.connect("192.168.0.1")

##
# Directories. The new name can be nested, the missing levels are created.
robot.rws.file.create_directory("$home", "MyApp/Logs")
robot.rws.file.rename_directory("$home/MyApp/Logs", "Archive")
robot.rws.file.copy_directory("$home/MyApp/Archive", "Archive2", True)
robot.rws.file.delete_directory("$home/MyApp/Archive2")
##

##
# Files. The third argument of the copy overwrites an existing target.
robot.rws.file.rename_file("$home/notes.txt", "notes-old.txt")
robot.rws.file.copy_file("$home/notes-old.txt", "notes-backup.txt", True)
robot.rws.file.delete_file("$home/notes-old.txt")
##

robot.disconnect()
```

A few points to know:

- `CreateDirectory` takes the parent directory and the new name. The name can be nested, and the missing levels are created.
- The new name of a rename or a copy is relative to the directory the item is in. An absolute path on the controller is also accepted.
- The copy methods take an `overwrite` argument. With `false`, the controller refuses to replace an existing target.
- `DeleteDirectory` deletes the directory and its content. There is no recycle bin on the controller, a deleted file is gone.

The controller protects part of its file system. A write in a read only location fails with an `RwsException`, and `IsReadOnly` on the item tells you before you try. The user account also needs the matching UAS grant.

## Around the file system

Other services write files on the controller and leave them for this one to read:

- [Controller](/abb/documentation/rws-controller) writes a backup in a folder of the controller. Browse that folder and download its files, as shown in [Backup & restore a controller](/abb/documentation/backup-restore-controller).
- [Event log](/abb/documentation/rws-elog) writes the whole log to one file with `SaveInSystemDumpFormat`.
- [RAPID modules](/abb/documentation/rws-rapid-modules) loads a module from a path on the controller, so a module written from the PC is uploaded first and loaded afterwards.

Nothing here needs the [mastership](/abb/documentation/rws-mastership), the file system is not one of its domains. Loading into RAPID what you uploaded does need it.

## Try it in the demo application

Everything on this page can be tried without writing code, in the **File handling (RWS)** page of the [demo application](/abb/documentation/demo-app).

![File handling (RWS) page of the ABB SDK demo application](/abb/WinformsScreenshots/RwsFile.jpg)

Its C# source is [RwsFileControl.cs](https://github.com/underautomation/ABB.NET/blob/main/UnderAutomation.ABB.Showcase.Forms/Components/RwsFileControl.cs).

## API reference

**Methods of FileService**
```csharp
// Copies a directory (synchronous)
void CopyDirectory(string path, string newName, bool overwrite);

// Copies a file (synchronous)
void CopyFile(string path, string newName, bool overwrite);

// Creates a new directory (synchronous) The newName parameter can contain nested directory structure (e.g. "parentdir/subdir") which will create both directories if they don't exist.
void CreateDirectory(string path, string newName);

// Deletes a directory and all its subdirectories and files (synchronous)
void DeleteDirectory(string path);

// Deletes a file (synchronous)
void DeleteFile(string path);

// Gets file content as raw bytes (synchronous)
byte[] GetFileAsBytes(string path);

// Gets file content as a read-only stream (synchronous) For sync: Returns a read-only MemoryStream (buffered for .NET 3.5/4.0 compatibility) For true HTTP streaming with large files, use GetFileAsReadonlyStreamAsync() instead
Stream GetFileAsReadonlyStream(string path);

// Gets file content as text (synchronous)
string GetFileAsText(string path, Encoding encoding = null);

// Downloads a file to a local path (synchronous)
void GetFileToDestination(string path, string localPath);

// Lists contents of a directory resource (synchronous) Environment variables (e.g. $home, $temp) and devices are treated as directories. When listing the root path ("/", null, or "\\"), the response includes available devices in DirectoryListing.Devices. The complete content is always returned, however many entries the directory holds.
DirectoryListing ListDirectory(string path);

// Renames a directory (synchronous)
void RenameDirectory(string path, string newName);

// Renames a file (synchronous)
void RenameFile(string path, string newName);

// Uploads a file from raw bytes (synchronous)
void UploadFileFromBytes(string path, byte[] content, string contentType = null);

// Uploads a local file to the controller (synchronous)
void UploadFileFromPath(string path, string localPath, string contentType = null);

// Uploads a file from a stream (synchronous)
void UploadFileFromStream(string path, Stream contentStream, string contentType = null);

// Uploads a file from text (synchronous)
void UploadFileFromText(string path, string content, Encoding encoding = null);
```

Every method also exists in an asynchronous version, with the same name followed by `Async` and an optional `CancellationToken`.

**Members of Rws.Data.DirectoryListing**
```csharp
public class DirectoryListing {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.DirectoryListing" data-throw-if-not-resolved="false"></xref> class
    public DirectoryListing(string path)

    // Number of devices in this listing
    public int DeviceCount { get; }

    // Devices available in this listing (typically only present at root "/")
    public DeviceItem[] Devices { get; set; }

    // Subdirectories contained in this directory
    public DirectoryItem[] Directories { get; set; }

    // Number of subdirectories in this listing
    public int DirectoryCount { get; }

    // Number of files in this listing
    public int FileCount { get; }

    // Files contained in this directory
    public FileItem[] Files { get; set; }

    // Path that was listed
    public string Path { get; }

    // Returns a string representation of this directory listing
    public override string ToString()

    // Total number of items (files + directories + devices)
    public int TotalCount { get; }
}
```

**Members of Rws.Data.FileSystemItem**
```csharp
public abstract class FileSystemItem {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.FileSystemItem" data-throw-if-not-resolved="false"></xref> class
    protected FileSystemItem()

    // Creation date of the resource, if available
    public DateTime? CreationDate { get; set; }

    // Last modification date of the resource, if available
    public DateTime? ModificationDate { get; set; }

    // Name of the item (file name, directory name, or device name such as "C:")
    public string Name { get; set; }

    // Returns the name of the item
    public override string ToString()
}
```

**Members of Rws.Data.FileItem**
```csharp
public class FileItem : FileSystemItem {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.FileItem" data-throw-if-not-resolved="false"></xref> class
    public FileItem()

    // Indicates if the file is read-only
    public bool IsReadOnly { get; set; }

    // File size in bytes
    public long Size { get; set; }

    // Returns a string representation of this file entry
    public override string ToString()
}
```

**Members of Rws.Data.DirectoryItem**
```csharp
public class DirectoryItem : FileSystemItem {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.DirectoryItem" data-throw-if-not-resolved="false"></xref> class
    public DirectoryItem()

    // Indicates if the directory is read-only
    public bool IsReadOnly { get; set; }

    // Returns a string representation of this directory entry
    public override string ToString()
}
```

**Members of Rws.Data.DeviceItem**
```csharp
public class DeviceItem : FileSystemItem {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.DeviceItem" data-throw-if-not-resolved="false"></xref> class
    public DeviceItem()

    // Type of device (Fixed, Removable, RamDisk, Remote)
    public DeviceType DeviceType { get; set; }

    // Free storage space in bytes
    public long FreeSpace { get; set; }

    // Indicates if the device is enabled
    public bool IsEnabled { get; set; }

    // Indicates if the device is read-only
    public bool IsReadOnly { get; set; }

    // Returns a string representation of this device entry
    public override string ToString()

    // Total storage space in bytes
    public long TotalSpace { get; set; }
}
```

**Members of Rws.Data.DeviceType**
```csharp
public enum DeviceType {
    // Fixed storage device (hard drive)
    Fixed = 0

    // RAM disk
    RamDisk = 2

    // Remote or network storage
    Remote = 3

    // Removable storage device (USB, SD card, etc.)
    Removable = 1

    // Unknown device type
    Unknown = 4
}
```