refactor(VHDManager): 改进VHD挂载流程为两步操作并增加容错机制

将VHD挂载流程拆分为两步:先挂载VHD再分配盘符
增加对M盘已存在的检测和自动跳过功能
添加备用盘符分配方法以增强可靠性
删除过时的Windows 11故障排除文档
This commit is contained in:
Lannamokia
2025-10-20 03:56:55 +08:00
parent 358fb4ce14
commit 94e42e31ed
2 changed files with 227 additions and 116 deletions
+227 -24
View File
@@ -516,13 +516,14 @@ namespace VHDMounter
await UnmountDrive();
await UnmountVHD();
var diskpartScript = $@"select vdisk file=""{vhdPath}""
// 第一步:仅挂载VHD,不分配盘符
StatusChanged?.Invoke("步骤1: 挂载VHD文件...");
var attachScript = $@"select vdisk file=""{vhdPath}""
attach vdisk
assign letter=M
exit";
var tempScript = Path.GetTempFileName();
await File.WriteAllTextAsync(tempScript, diskpartScript);
await File.WriteAllTextAsync(tempScript, attachScript);
var process = new Process
{
@@ -547,39 +548,64 @@ exit";
if (completed == timeoutTask)
{
try { process.Kill(); } catch { }
StatusChanged?.Invoke("挂载超时(超过60秒),已中止diskpart进程");
File.Delete(tempScript);
StatusChanged?.Invoke("VHD挂载超时(超过60秒),已中止diskpart进程");
if (File.Exists(tempScript))
File.Delete(tempScript);
return false;
}
string output = await process.StandardOutput.ReadToEndAsync();
string error = await process.StandardError.ReadToEndAsync();
string attachOutput = await process.StandardOutput.ReadToEndAsync();
string attachError = await process.StandardError.ReadToEndAsync();
File.Delete(tempScript);
if (File.Exists(tempScript))
File.Delete(tempScript);
// 等待系统完成卷挂载
await Task.Delay(2000);
bool mounted = Directory.Exists(TARGET_DRIVE);
if (!mounted)
if (process.ExitCode != 0)
{
if (!string.IsNullOrWhiteSpace(error))
{
StatusChanged?.Invoke($"挂载失败:{error.Trim()}");
}
else
{
// 提供有限的DiskPart输出以辅助定位问题
var trimmedOutput = output?.Length > 400 ? output.Substring(output.Length - 400) : output;
StatusChanged?.Invoke($"挂载失败:未检测到M盘。DiskPart输出片段:{trimmedOutput}");
}
StatusChanged?.Invoke($"VHD挂载失败: {attachError.Trim()}");
Debug.WriteLine($"VHD挂载输出: {attachOutput}");
Debug.WriteLine($"VHD挂载错误: {attachError}");
return false;
}
return mounted;
StatusChanged?.Invoke("VHD挂载成功,等待系统识别分区...");
await Task.Delay(3000); // 等待系统识别分区
// 第二步:为第一个分区分配盘符
StatusChanged?.Invoke("步骤2: 为第一个分区分配盘符M...");
// 容错检查:如果M盘已经存在,可能是系统自动分配的
if (Directory.Exists(TARGET_DRIVE))
{
StatusChanged?.Invoke("检测到M盘已自动分配,跳过手动分配步骤");
StatusChanged?.Invoke("VHD挂载和盘符分配完成");
return true;
}
bool driveAssigned = await AssignDriveLetterToFirstPartition(vhdPath);
if (!driveAssigned)
{
StatusChanged?.Invoke("分配盘符失败,尝试备用方法...");
// 备用方法:直接尝试分配盘符
driveAssigned = await AssignDriveLetterDirect();
}
if (driveAssigned)
{
StatusChanged?.Invoke("VHD挂载和盘符分配完成");
return true;
}
else
{
StatusChanged?.Invoke("盘符分配失败,但VHD已挂载");
return false;
}
}
catch (Exception ex)
{
StatusChanged?.Invoke($"挂载失败: {ex.Message}");
Debug.WriteLine($"MountVHD异常: {ex}");
return false;
}
}
@@ -780,6 +806,183 @@ exit";
}
}
/// <summary>
/// 为VHD的第一个分区分配盘符M
/// </summary>
private async Task<bool> AssignDriveLetterToFirstPartition(string vhdPath)
{
try
{
// 容错机制:检查M盘是否已经存在
if (Directory.Exists(TARGET_DRIVE))
{
StatusChanged?.Invoke("检测到M盘已存在,跳过盘符分配");
Debug.WriteLine("M盘已存在,无需重复分配");
return true;
}
// 使用diskpart列出VHD的分区并为第一个分区分配盘符
var listScript = $@"select vdisk file=""{vhdPath}""
list partition
select partition 1
assign letter=M
exit";
var tempScript = Path.GetTempFileName();
await File.WriteAllTextAsync(tempScript, listScript);
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "diskpart",
Arguments = $"/s \"{tempScript}\"",
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};
process.Start();
var waitTask = process.WaitForExitAsync();
var timeoutTask = Task.Delay(30000);
var completed = await Task.WhenAny(waitTask, timeoutTask);
if (completed == timeoutTask)
{
try { process.Kill(); } catch { }
StatusChanged?.Invoke("分配盘符超时");
if (File.Exists(tempScript))
File.Delete(tempScript);
return false;
}
string output = await process.StandardOutput.ReadToEndAsync();
string error = await process.StandardError.ReadToEndAsync();
if (File.Exists(tempScript))
File.Delete(tempScript);
Debug.WriteLine($"分区盘符分配输出: {output}");
if (!string.IsNullOrWhiteSpace(error))
{
Debug.WriteLine($"分区盘符分配错误: {error}");
}
// 等待盘符分配完成
await Task.Delay(2000);
bool driveExists = Directory.Exists(TARGET_DRIVE);
if (driveExists)
{
StatusChanged?.Invoke("盘符M分配成功");
}
else
{
StatusChanged?.Invoke("盘符M分配失败,M盘不存在");
}
return driveExists;
}
catch (Exception ex)
{
StatusChanged?.Invoke($"分配盘符异常: {ex.Message}");
Debug.WriteLine($"AssignDriveLetterToFirstPartition异常: {ex}");
return false;
}
}
/// <summary>
/// 备用方法:直接尝试分配盘符(适用于单分区VHD)
/// </summary>
private async Task<bool> AssignDriveLetterDirect()
{
try
{
// 容错机制:检查M盘是否已经存在
if (Directory.Exists(TARGET_DRIVE))
{
StatusChanged?.Invoke("备用方法检测到M盘已存在,跳过盘符分配");
Debug.WriteLine("备用方法:M盘已存在,无需重复分配");
return true;
}
// 尝试为最后挂载的磁盘分配盘符
var assignScript = @"list disk
select disk 1
list partition
select partition 1
assign letter=M
exit";
var tempScript = Path.GetTempFileName();
await File.WriteAllTextAsync(tempScript, assignScript);
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "diskpart",
Arguments = $"/s \"{tempScript}\"",
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};
process.Start();
var waitTask = process.WaitForExitAsync();
var timeoutTask = Task.Delay(30000);
var completed = await Task.WhenAny(waitTask, timeoutTask);
if (completed == timeoutTask)
{
try { process.Kill(); } catch { }
StatusChanged?.Invoke("备用盘符分配超时");
if (File.Exists(tempScript))
File.Delete(tempScript);
return false;
}
string output = await process.StandardOutput.ReadToEndAsync();
string error = await process.StandardError.ReadToEndAsync();
if (File.Exists(tempScript))
File.Delete(tempScript);
Debug.WriteLine($"备用盘符分配输出: {output}");
if (!string.IsNullOrWhiteSpace(error))
{
Debug.WriteLine($"备用盘符分配错误: {error}");
}
// 等待盘符分配完成
await Task.Delay(2000);
bool driveExists = Directory.Exists(TARGET_DRIVE);
if (driveExists)
{
StatusChanged?.Invoke("备用方法盘符M分配成功");
}
else
{
StatusChanged?.Invoke("备用方法盘符M分配也失败");
}
return driveExists;
}
catch (Exception ex)
{
StatusChanged?.Invoke($"备用盘符分配异常: {ex.Message}");
Debug.WriteLine($"AssignDriveLetterDirect异常: {ex}");
return false;
}
}
public async Task<bool> UnmountVHD()
{
StatusChanged?.Invoke("正在解除VHD挂载...");
-92
View File
@@ -1,92 +0,0 @@
# Windows 11 VHD替换故障排除指南
## 问题描述
在Windows 11系统上,VHD文件替换可能会失败,这通常是由于Win11增强的安全策略和文件保护机制导致的。
## 常见错误原因
### 1. 权限问题
- **症状**: 提示"权限不足,无法替换文件"
- **原因**: Win11的UAC(用户账户控制)更加严格
- **解决方案**:
- 右键点击程序,选择"以管理员身份运行"
- 或者在程序属性中设置"始终以管理员身份运行"
### 2. 文件属性问题
- **症状**: 文件无法删除或替换
- **原因**: Win11可能自动设置VHD文件为只读、隐藏或系统文件
- **解决方案**: 程序现在会自动检测并移除这些属性
### 3. 文件被占用
- **症状**: 提示"文件被占用,无法删除"
- **原因**: VHD文件可能被Windows资源管理器或其他程序占用
- **解决方案**:
- 关闭所有文件资源管理器窗口
- 确保没有其他程序正在访问VHD文件
- 重启计算机后再尝试
### 4. Windows Defender实时保护
- **症状**: 文件操作被阻止或延迟
- **原因**: Windows Defender可能将VHD文件操作视为可疑行为
- **解决方案**:
- 临时禁用实时保护
- 或将VHD文件夹添加到排除列表
## 程序改进功能
### 新增的Win11兼容性检查
程序现在包含以下改进:
1. **自动检测Win11**: 程序会自动识别是否运行在Windows 11上
2. **权限检查**: 检查程序是否以管理员身份运行
3. **文件属性修复**: 自动移除只读、隐藏、系统等属性
4. **文件占用检测**: 检查文件是否被其他程序占用
5. **详细错误报告**: 提供更详细的错误信息和诊断数据
### 诊断信息
程序会在状态栏显示以下信息:
- 操作系统版本
- 管理员权限状态
- 文件属性详情
- 具体的错误原因
## 手动排除步骤
### 步骤1: 检查权限
1. 右键点击VHDMounter.exe
2. 选择"属性" → "兼容性"
3. 勾选"以管理员身份运行此程序"
4. 点击"确定"
### 步骤2: 检查文件属性
1. 右键点击VHD文件
2. 选择"属性"
3. 取消勾选"只读"
4. 在"高级"中取消勾选"隐藏"和"系统"
### 步骤3: 检查Windows Defender
1. 打开Windows安全中心
2. 转到"病毒和威胁防护"
3. 在"病毒和威胁防护设置"中点击"管理设置"
4. 临时关闭"实时保护"或添加排除项
### 步骤4: 检查磁盘空间
确保目标磁盘有足够的空间存储VHD文件
### 步骤5: 重启服务
如果问题持续,尝试重启以下服务:
- Windows资源管理器 (explorer.exe)
- 虚拟磁盘服务 (vds)
## 联系支持
如果以上步骤都无法解决问题,请提供以下信息:
1. 程序显示的具体错误消息
2. Windows 11版本号
3. VHD文件大小和位置
4. 是否以管理员身份运行程序
## 预防措施
1. 始终以管理员身份运行程序
2. 定期检查VHD文件的属性设置
3. 保持Windows 11系统更新
4. 避免在系统保护的文件夹中存储VHD文件