A cross-platform .NET library for SMB/CIFS file operations. Supports both Kerberos and username/password authentication. Works on Windows (native UNC) and Linux/macOS (via smbclient).
$ dotnet add package SmbSharpA cross-platform .NET library for SMB/CIFS file operations. Works seamlessly on Windows using native UNC paths (or smbclient via WSL), and on Linux/macOS using smbclient.
Install-Package SmbSharp
dotnet add package SmbSharp
<PackageReference Include="SmbSharp" Version="1.1.0" />
wsl apt-get install smbclient
smbclient to be installed:
# Debian/Ubuntu
sudo apt-get install smbclient
# RHEL/CentOS
sudo yum install samba-client
# Alpine Linux
apk add samba-client
smbclient to be installed:
brew install samba
Add smbclient to your Dockerfile:
Debian/Ubuntu-based images:
FROM mcr.microsoft.com/dotnet/aspnet:8.0
RUN apt-get update && apt-get install -y smbclient && rm -rf /var/lib/apt/lists/*
Alpine-based images:
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
RUN apk add --no-cache samba-client
RHEL/CentOS-based images:
FROM mcr.microsoft.com/dotnet/aspnet:8.0-rhel
RUN yum install -y samba-client && yum clean all
// Program.cs
builder.Services.AddSmbSharp();
// Usage in a controller/service
public class MyService
{
private readonly IFileHandler _fileHandler;
public MyService(IFileHandler fileHandler)
{
_fileHandler = fileHandler;
}
public async Task<IEnumerable<string>> GetFiles()
{
return await _fileHandler.EnumerateFilesAsync("//server/share/folder");
}
}
// Program.cs - Direct credentials
builder.Services.AddSmbSharp("username", "password", "DOMAIN");
// Or using configuration
builder.Services.AddSmbSharp(options =>
{
options.UseKerberos = false;
options.Username = "username";
options.Password = "password";
options.Domain = "DOMAIN";
});
// Or from appsettings.json
builder.Services.AddSmbSharp(options =>
{
builder.Configuration.GetSection("SmbSharp").Bind(options);
});
// Use smbclient through WSL instead of native UNC paths
builder.Services.AddSmbSharp(options =>
{
options.UseWsl = true; // Enable WSL smbclient on Windows
options.UseKerberos = false;
options.Username = "username";
options.Password = "password";
options.Domain = "DOMAIN";
});
using Microsoft.Extensions.Logging;
using SmbSharp.Business;
// Without logging
var handler = FileHandler.CreateWithKerberos();
var handler = FileHandler.CreateWithCredentials("username", "password", "DOMAIN");
// With console logging (for debugging)
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.SetMinimumLevel(LogLevel.Debug)
.AddConsole();
});
var handler = FileHandler.CreateWithKerberos(loggerFactory);
var handler = FileHandler.CreateWithCredentials("username", "password", "DOMAIN", loggerFactory);
// Using smbclient via WSL on Windows
var handler = FileHandler.CreateWithKerberos(useWsl: true);
var handler = FileHandler.CreateWithCredentials("username", "password", "DOMAIN", loggerFactory, useWsl: true);
// Usage
var files = await handler.EnumerateFilesAsync("//server/share/folder");
Note: To use console logging, you need to add the Microsoft.Extensions.Logging.Console package:
dotnet add package Microsoft.Extensions.Logging.Console
SmbSharp accepts SMB paths in multiple formats for flexibility:
// Forward slashes (recommended for cross-platform code)
await fileHandler.EnumerateFilesAsync("//server/share/folder");
// Backslashes (Windows UNC format)
await fileHandler.EnumerateFilesAsync("\\\\server\\share\\folder");
// Mixed (automatically normalized)
await fileHandler.EnumerateFilesAsync("//server/share\\folder");
Note: All path formats are automatically normalized internally. Forward slashes (/) are recommended for cross-platform compatibility, but backslashes (\) are fully supported for Windows-style UNC paths.
var files = await fileHandler.EnumerateFilesAsync("//server/share/folder");
foreach (var file in files)
{
Console.WriteLine(file);
}
await using var stream = await fileHandler.ReadFileAsync("//server/share/folder", "file.txt");
using var reader = new StreamReader(stream);
var content = await reader.ReadToEndAsync();
await fileHandler.WriteFileAsync("//server/share/folder/file.txt", "Hello, World!");
await using var fileStream = File.OpenRead("local-file.txt");
await fileHandler.WriteFileAsync("//server/share/folder/file.txt", fileStream);
// Overwrite existing file (default)
await fileHandler.WriteFileAsync("//server/share/file.txt", stream, FileWriteMode.Overwrite);
// Create only if doesn't exist (fails if exists)
await fileHandler.WriteFileAsync("//server/share/file.txt", stream, FileWriteMode.CreateNew);
// Append to existing file
await fileHandler.WriteFileAsync("//server/share/file.txt", stream, FileWriteMode.Append);
await fileHandler.DeleteFileAsync("//server/share/folder/file.txt");
await fileHandler.MoveFileAsync(
"//server/share/folder/old.txt",
"//server/share/folder/new.txt"
);
Note: On Linux/macOS (and Windows with WSL), move operations download and re-upload the file, which can be slow for large files. The operation is atomic with automatic retry logic - if the source deletion fails after copying, it retries once before rolling back the destination to maintain consistency.
await fileHandler.CreateDirectoryAsync("//server/share/newfolder");
bool canConnect = await fileHandler.CanConnectAsync("//server/share");
if (canConnect)
{
Console.WriteLine("Successfully connected!");
}
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
try
{
var files = await fileHandler.EnumerateFilesAsync(
"//server/share/folder",
cts.Token
);
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation timed out!");
}
On Linux, ensure you have a valid Kerberos ticket before using the library:
kinit username@DOMAIN.COM
Verify your ticket:
klist
Credentials are securely passed to smbclient via environment variables, not command-line arguments, preventing exposure in process listings.
SmbSharp includes built-in health check support for ASP.NET Core applications to monitor SMB share connectivity.
// Program.cs
builder.Services.AddSmbSharp();
builder.Services.AddHealthChecks()
.AddSmbShareCheck("//server/share/folder");
builder.Services.AddHealthChecks()
.AddSmbShareCheck(
directoryPath: "//server/share/folder",
name: "primary_smb_share",
failureStatus: HealthStatus.Degraded,
tags: new[] { "smb", "storage" },
timeout: TimeSpan.FromSeconds(10)
);
var shares = new Dictionary<string, string>
{
{ "primary", "//server1/share1" },
{ "backup", "//server2/share2" },
{ "archive", "//server3/share3" }
};
builder.Services.AddHealthChecks()
.AddSmbShareChecks(shares, tags: new[] { "smb" });
// Program.cs
var app = builder.Build();
app.MapHealthChecks("/health");
// Or with detailed response
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
Healthy:
{
"status": "Healthy",
"results": {
"smb_share": {
"status": "Healthy",
"description": "Successfully connected to SMB share: //server/share/folder"
}
}
}
Unhealthy:
{
"status": "Unhealthy",
"results": {
"smb_share": {
"status": "Unhealthy",
"description": "Unable to connect to SMB share: //server/share/folder"
}
}
}
When health checks fail, error logs are automatically generated to help troubleshoot connectivity issues:
// Enable logging in your appsettings.json
{
"Logging": {
"LogLevel": {
"SmbSharp.HealthChecks.SmbShareHealthCheck": "Error"
}
}
}
Example error log:
[Error] Health check failed: Unable to connect to SMB share: //server/share/folder
[Error] Health check failed for SMB share //server/share/folder: Access denied
| Method | Description |
|---|---|
EnumerateFilesAsync(directory, cancellationToken) | Lists all files in a directory |
ReadFileAsync(directory, fileName, cancellationToken) | Opens a file for reading as a stream |
WriteFileAsync(filePath, content, cancellationToken) | Writes a string to a file |
WriteFileAsync(filePath, stream, cancellationToken) | Writes a stream to a file |
WriteFileAsync(filePath, stream, writeMode, cancellationToken) | Writes a stream with specific write mode |
DeleteFileAsync(filePath, cancellationToken) | Deletes a file |
MoveFileAsync(sourcePath, destPath, cancellationToken) | Moves a file |
CreateDirectoryAsync(directoryPath, cancellationToken) | Creates a directory |
CanConnectAsync(directoryPath, cancellationToken) | Tests connectivity to a share |
| Value | Description |
|---|---|
Overwrite | Creates a new file or overwrites existing (default) |
CreateNew | Creates only if file doesn't exist (throws if exists) |
Append | Appends to existing file or creates new |
The library throws specific exceptions for different error scenarios:
try
{
await fileHandler.ReadFileAsync("//server/share", "file.txt");
}
catch (FileNotFoundException ex)
{
// File or path doesn't exist
}
catch (UnauthorizedAccessException ex)
{
// Access denied or authentication failed
}
catch (DirectoryNotFoundException ex)
{
// Network path not found
}
catch (IOException ex)
{
// Other SMB/network errors
}
catch (PlatformNotSupportedException ex)
{
// Running on unsupported platform (not Windows/Linux/macOS)
}
To troubleshoot authentication issues or see exactly what commands are being sent to smbclient, enable debug logging:
// In Program.cs or appsettings.json
builder.Logging.SetMinimumLevel(LogLevel.Debug);
// Or configure specific loggers
builder.Logging.AddFilter("SmbSharp.Infrastructure.ProcessWrapper", LogLevel.Debug);
Example debug output:
Executing process: smbclient //server/share -U "username" -c "ls folder/*"
Environment variables set: PASSWD
Process exited with code: 0
Note: Passwords are never logged. Only environment variable names (like PASSWD) are shown, not their values.
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.
Built with ❤️ for the .NET community.