ok

alphons <alphons@heijden.com> 15 Dec 2025, 15:00
556b60dc31ebe0e43a4c8c43fca80f0980e555c1
3 files changed
  • BackupMongoDb/BackupMongoDb.csproj
  • BackupMongoDb/Helper.cs
  • BackupMongoDb/Program.cs
diff --git a/BackupMongoDb/BackupMongoDb.csproj b/BackupMongoDb/BackupMongoDb.csproj
index b407cfd..08a87a7 100644
--- a/BackupMongoDb/BackupMongoDb.csproj
+++ b/BackupMongoDb/BackupMongoDb.csproj
@@ -8,8 +8,6 @@
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PlatformTarget>x64</PlatformTarget>
<PublishSingleFile>true</PublishSingleFile>
- <IncludeNativeLibrariesForSelfExtract>false</IncludeNativeLibrariesForSelfExtract>
- <IncludeAllContentForSelfExtract>false</IncludeAllContentForSelfExtract>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -21,9 +19,13 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="AlphaVSS" Version="2.0.3" />
<PackageReference Include="MongoDB.Driver" Version="3.5.2" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="10.0.1" />
+ <PackageReference Include="System.Management" Version="10.0.0" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Folder Include="Properties\PublishProfiles\" />
</ItemGroup>
</Project>
diff --git a/BackupMongoDb/Helper.cs b/BackupMongoDb/Helper.cs
index 32da36a..d0039e0 100644
--- a/BackupMongoDb/Helper.cs
+++ b/BackupMongoDb/Helper.cs
@@ -1,8 +1,9 @@
-using Alphaleonis.Win32.Vss;
+
using MongoDB.Bson;
using MongoDB.Driver;
using System.Diagnostics;
using System.IO.Compression;
+using System.Management;
using System.Security.AccessControl;
using System.Security.Principal;
using System.ServiceProcess;
@@ -29,118 +30,138 @@ public class Helper
private static async Task<bool> ZipFromSnapshotAsync(string sourceDirectory, string zipPath)
{
- IVssFactory? vssFactory = null;
- IVssBackupComponents? backup = null;
- try
+ if (!Directory.Exists(sourceDirectory))
{
- // Valideer source directory
- if (!Directory.Exists(sourceDirectory))
- {
- Console.WriteLine($"Source directory '{sourceDirectory}' does not exist.");
- return false;
- }
+ Console.WriteLine($"Source directory '{sourceDirectory}' does not exist.");
+ return false;
+ }
- // Zorg ervoor dat de doeldirectory bestaat
- string? targetDir = Path.GetDirectoryName(zipPath);
- if (!string.IsNullOrEmpty(targetDir) && !Directory.Exists(targetDir))
- {
- Directory.CreateDirectory(targetDir);
- }
+ string? targetDir = Path.GetDirectoryName(zipPath);
+ if (!string.IsNullOrEmpty(targetDir) && !Directory.Exists(targetDir))
+ {
+ Directory.CreateDirectory(targetDir);
+ }
- // Verwijder bestaand ZIP-bestand met foutafhandeling
- if (File.Exists(zipPath))
+ if (File.Exists(zipPath))
+ {
+ try
{
- try
- {
- File.Delete(zipPath);
- }
- catch (IOException ex)
- {
- Console.WriteLine($"Failed to delete existing ZIP file '{zipPath}': {ex.Message}");
- return false;
- }
+ File.Delete(zipPath);
}
-
- // Initialiseer VSS
- vssFactory = VssFactoryProvider.Default.GetVssFactory();
- backup = vssFactory.CreateVssBackupComponents();
- backup.InitializeForBackup(null);
- backup.GatherWriterMetadata();
- backup.SetBackupState(false, true, VssBackupType.Full, false);
- backup.SetContext(VssSnapshotContext.Backup);
-
- // Bepaal het volume (bijv. C:\)
- string volumePath = Path.GetPathRoot(sourceDirectory)?.TrimEnd('\\') ?? throw new ArgumentException("Could not determine volume for source directory.");
- if (!Directory.Exists(volumePath))
+ catch (IOException ex)
{
- Console.WriteLine($"Volume '{volumePath}' does not exist.");
+ Console.WriteLine($"Failed to delete existing ZIP file '{zipPath}': {ex.Message}");
return false;
}
+ }
- volumePath += '\\';
+ ManagementScope scope = new(@"\\.\root\cimv2");
+ scope.Connect();
- // Maak een VSS-snapshot van het volume
- Console.WriteLine($"Creating VSS snapshot for volume '{volumePath}' to back up directory '{sourceDirectory}'...");
- Guid setId = backup.StartSnapshotSet();
- Guid snapshotId = backup.AddToSnapshotSet(volumePath);
- backup.PrepareForBackup();
+ string volume = Path.GetPathRoot(sourceDirectory) ?? throw new InvalidOperationException("Invalid path root.");
- await Task.Run(() => backup.DoSnapshotSet()); // Asynchroon in een Task
+ // Create snapshot
+ ObjectGetOptions options = new();
+ ManagementClass shadowCopyClass = new(scope, new ManagementPath("Win32_ShadowCopy"), options);
+ ManagementBaseObject inParams = shadowCopyClass.GetMethodParameters("Create");
+ inParams["Context"] = "ClientAccessible";
+ inParams["Volume"] = volume;
- var snapshotProperties = backup.GetSnapshotProperties(snapshotId);
- var snapshotDevicePath = snapshotProperties.SnapshotDeviceObject;
- var relativePath = sourceDirectory[volumePath.Length..].TrimStart('\\');
- var snapshotSourcePath = Path.Combine(snapshotDevicePath, relativePath);
+ ManagementBaseObject outParams = shadowCopyClass.InvokeMethod("Create", inParams, null);
+ uint result = (uint)outParams["ReturnValue"];
+ if (result != 0)
+ {
+ Console.WriteLine($"Failed to create shadow copy. Error code: {result}");
+ return false;
+ }
- Console.WriteLine($"VSS snapshot created successfully");
- // Controleer of het snapshot-pad toegankelijk is
- if (!Directory.Exists(snapshotSourcePath))
+ string shadowId = outParams["ShadowID"].ToString() ?? string.Empty;
+ string deviceObject = string.Empty;
+
+ // Find device path
+ using (ManagementObjectSearcher searcher = new(scope, new SelectQuery("SELECT * FROM Win32_ShadowCopy WHERE ID='" + shadowId + "'")))
+ {
+ foreach (ManagementObject obj in searcher.Get().Cast<ManagementObject>())
{
- Console.WriteLine($"Snapshot source path '{snapshotSourcePath}' is not accessible.");
- return false;
+ deviceObject = obj["DeviceObject"].ToString() ?? string.Empty;
+ break;
}
+ }
- // Maak ZIP-bestand van alleen de bestanden in de hoofddirectory van de snapshot
- Console.WriteLine($"Zipping files to '{zipPath}'...");
+ if (string.IsNullOrEmpty(deviceObject))
+ {
+ Console.WriteLine("Failed to retrieve shadow device path.");
+ return false;
+ }
+
+ // Ensure trailing backslash
+ if (!deviceObject.EndsWith('\\'))
+ {
+ deviceObject += "\\";
+ }
+
+ string relativePath = sourceDirectory[volume.Length..].TrimStart('\\');
+ string snapshotSourcePath = Path.Combine(deviceObject, relativePath);
+
+ Console.WriteLine($"Zipping from snapshot '{snapshotSourcePath}' to '{zipPath}'...");
+
+ try
+ {
using (var zip = ZipFile.Open(zipPath, ZipArchiveMode.Create))
{
foreach (string file in Directory.GetFiles(snapshotSourcePath, "*", SearchOption.TopDirectoryOnly))
{
if (Path.GetExtension(file) == ".lock")
+ {
continue;
- string entryName = Path.GetFileName(file); // Alleen bestandsnaam, geen directorystructuur
+ }
+
+ string entryName = Path.GetFileName(file);
zip.CreateEntryFromFile(file, entryName, CompressionLevel.Optimal);
}
}
- Console.WriteLine($"ZIP file created successfully");
-
- backup.DeleteSnapshotSet(setId, false);
- Console.WriteLine($"DeleteSnapshotSet successfully");
+ Console.WriteLine("ZIP file created successfully from snapshot");
return true;
}
- catch (VssException ex)
- {
- Console.WriteLine($"VSS error: {ex.Message}");
- return false;
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Error zipping '{sourceDirectory}' to '{zipPath}': {ex.Message}");
- return false;
- }
finally
{
- // Ruim VSS op
- try
+ // Always delete snapshot
+ if (!string.IsNullOrEmpty(shadowId))
{
- backup?.BackupComplete();
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Error completing VSS backup: {ex.Message}");
+ try
+ {
+ await Task.Delay(3000); // Longer wait for file system release
+
+ using ManagementObjectSearcher searcher = new(
+ scope,
+ new SelectQuery($"SELECT * FROM Win32_ShadowCopy WHERE ID='{shadowId}'"));
+
+ ManagementObjectCollection collection = searcher.Get();
+
+ if (collection.Count == 0)
+ {
+ Console.WriteLine("Shadow copy already removed or not found.");
+ }
+ else
+ {
+ foreach (ManagementObject shadow in collection.Cast<ManagementObject>())
+ {
+ shadow.Delete();
+ Console.WriteLine("Shadow copy deleted successfully.");
+ break;
+ }
+ }
+ }
+ catch (ManagementException mex) when (mex.ErrorCode == ManagementStatus.NotFound)
+ {
+ Console.WriteLine("Shadow copy not found during cleanup.");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Failed to delete shadow copy: {ex.Message}");
+ }
}
- backup?.Dispose();
}
}
@@ -380,4 +401,65 @@ public class Helper
}
}
}
+
+ public static void ListAllShadowCopies(bool deleteCopies = false)
+ {
+ try
+ {
+ ManagementScope scope = new (@"\\.\root\cimv2");
+ scope.Connect();
+
+ using ManagementObjectSearcher searcher = new (scope, new SelectQuery("SELECT * FROM Win32_ShadowCopy"));
+ using ManagementObjectCollection collection = searcher.Get();
+
+ if (collection.Count == 0)
+ {
+ Console.WriteLine("No lingering shadow copies found.");
+ return;
+ }
+
+ Console.WriteLine($"Found {collection.Count} shadow copy(ies):");
+
+ foreach (ManagementObject shadow in collection.Cast<ManagementObject>())
+ {
+ try
+ {
+ string id = shadow["ID"]?.ToString() ?? "Unknown";
+
+ if (deleteCopies)
+ {
+ shadow.Delete();
+ Console.WriteLine($"Deleted shadow copy ID: {id}");
+ }
+ else
+ {
+ string deviceObject = shadow["DeviceObject"]?.ToString() ?? "Unknown";
+ string volume = shadow["VolumeName"]?.ToString() ?? "Unknown";
+ string installDate = ManagementDateTimeConverter.ToDateTime(shadow["InstallDate"]?.ToString() ?? string.Empty)
+ .ToString("yyyy-MM-dd HH:mm:ss");
+
+ Console.WriteLine($"ID: {id}");
+ Console.WriteLine($" DeviceObject: {deviceObject}");
+ Console.WriteLine($" Volume: {volume}");
+ Console.WriteLine($" InstallDate: {installDate}");
+ Console.WriteLine("---");
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Failed on shadow copy: {ex.Message}");
+ }
+ }
+
+ if (deleteCopies)
+ {
+ Console.WriteLine("Cleanup completed.");
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error querying shadow copies: {ex.Message}");
+ }
+ }
+
}
diff --git a/BackupMongoDb/Program.cs b/BackupMongoDb/Program.cs
index b8a00e0..a628c66 100644
--- a/BackupMongoDb/Program.cs
+++ b/BackupMongoDb/Program.cs
@@ -3,23 +3,40 @@ using BackupMongoDb;
string connection = args.Length > 2 ? args[2] : "mongodb://localhost:27017";
-if (args.Length < 2)
+if (args.Length < 1)
{
Console.WriteLine("Usage:");
- Console.WriteLine($"\tBackupMongoDb -backup <backupdir> [{connection}]");
- Console.WriteLine($"\tBackupMongoDb -restore <backupfile.zip> [{connection}]");
+ Console.WriteLine($"\tBackupMongoDb --backup <backupdir> [{connection}]");
+ Console.WriteLine($"\tBackupMongoDb --restore <backupfile.zip> [{connection}]");
+ Console.WriteLine();
+ Console.WriteLine($"\tBackupMongoDb --listshadowcopies");
+ Console.WriteLine($"\tBackupMongoDb --deleteshadowcopies");
Environment.Exit(0);
}
-if (args[0] == "-backup")
+switch(args[0])
{
- await Helper.BackupAsync(args[1], connection);
+ case "--backup":
+ if (args.Length >= 2)
+ await Helper.BackupAsync(args[1], connection);
+ else
+ Console.WriteLine($"\tBackupMongoDb -backup <backupdir> [{connection}]");
+ break;
+ case "--restore":
+ if (args.Length >= 2)
+ await Helper.RestoreAsync(args[1], connection);
+ else
+ Console.WriteLine($"\tBackupMongoDb -restore <backupfile.zip> [{connection}]");
+ break;
+ case "--listshadowcopies":
+ Helper.ListAllShadowCopies(deleteCopies: false);
+ break;
+ case "--deleteshadowcopies":
+ Helper.ListAllShadowCopies(deleteCopies: true);
+ break;
+ default:
+ Console.WriteLine($"Unknown command: {args[0]}");
+ Environment.Exit(1);
+ break;
}
-
-if (args[0] == "-restore")
-{
- await Helper.RestoreAsync(args[1], connection);
-}
-
-