You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#更新器替换user_pc.exe时路径访问被拒问题求助

C#更新器替换user_pc.exe时权限拒绝错误的原因与解决方法

我正在开发一款C#更新器应用,通过下载并解压ZIP文件替换旧应用程序文件,但尝试替换user_pc.exe时抛出权限拒绝错误,错误日志如下:

Starting updater...
Local version: 1.0.0.0
Server version: 1.0.0.1
Update found. Starting update process...
Downloading update...
Extracting update...
Replacing old files...
Error: Access to the path 'C:\Users\mnbvc\AppData\Roaming\y7_s\user_pc.exe' is denied.
Press any key to exit...

使用的代码如下:

namespace updater
{
    class Program
    {
        static string serverUrl = "http://199.218.1.105:8080/update/"; 
        static string versionFile = "version.js";
        static string zipFile = "userapp.zip";
        static string tempDir = Path.Combine(Path.GetTempPath(), "userapp_update");
        static string updaterDir = AppDomain.CurrentDomain.BaseDirectory; 
        static string appDir = Path.GetFullPath(Path.Combine(updaterDir, ".."));
        static string userAppExe = "user_pc.exe"; // Name of user application

        static void Main()
        {
            try
            {
                Console.WriteLine("Starting updater...");
                string userAppPath = Path.Combine(appDir, userAppExe);
                string localVersion = GetAssemblyVersion(userAppPath);

                Console.WriteLine($"Local version: {localVersion}");

                // Step 1: Fetch server version
                string versionJson = new WebClient().DownloadString(serverUrl + versionFile);
                var serverVersion = JsonSerializer.Deserialize<VersionInfo>(versionJson);

                Console.WriteLine($"Server version: {serverVersion.Version}");

                // Step 2: Compare versions
                if (serverVersion.Version == localVersion)
                {
                    Console.WriteLine("No updates found. Closing updater...");
                    Thread.Sleep(2000);
                    return; // Exit if no update is needed
                }

                Console.WriteLine("Update found. Starting update process...");

                // Step 3: Close userapp.exe if it's running
                KillProcess(userAppExe);

                // Step 4: Download the new ZIP file
                Directory.CreateDirectory(tempDir);
                string tempZipPath = Path.Combine(tempDir, zipFile);

                Console.WriteLine("Downloading update...");
                new WebClient().DownloadFile(serverUrl + zipFile, tempZipPath);

                // Step 5: Extract files manually to the temporary folder
                Console.WriteLine("Extracting update...");
                ExtractWithOverwrite(tempZipPath, tempDir);

                // Step 6: Replace old files
                Console.WriteLine("Replacing old files...");
                foreach (string file in Directory.GetFiles(tempDir, "*", SearchOption.AllDirectories))
                {
                    string relativePath = file.Substring(tempDir.Length + 1);
                    string destPath = Path.Combine(appDir, relativePath);

                    // Ensure the destination directory exists
                    Directory.CreateDirectory(Path.GetDirectoryName(destPath));

                    // Delete old file if it exists
                    if (File.Exists(destPath))
                        File.Delete(destPath);

                    File.Copy(file, destPath, overwrite: true);
                }

                // Step 7: Cleanup temporary files
                Console.WriteLine("Cleaning up...");
                File.Delete(tempZipPath);
                Directory.Delete(tempDir, recursive: true);

                // Step 8: Restart the application
                Console.WriteLine("Restarting application...");
                Process.Start(Path.Combine(appDir, userAppExe));

                Console.WriteLine("Update complete!");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
            }
            finally
            {
                // Keep the console window open to read errors or messages
                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }
        }

        // Function to manually extract ZIP files with overwrite functionality
        static void ExtractWithOverwrite(string sourceZipFile, string destinationDirectory)
        {
            using (ZipArchive archive = ZipFile.OpenRead(sourceZipFile))
            {
                foreach (ZipArchiveEntry entry in archive.Entries)
                {
                    string destinationPath = Path.Combine(destinationDirectory, entry.FullName);

                    if (string.IsNullOrEmpty(entry.Name)) // It's a folder
                    {
                        Directory.CreateDirectory(destinationPath);
                        continue;
                    }

                    Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));

                    // Delete any existing file to avoid conflicts
                    if (File.Exists(destinationPath))
                    {
                        File.SetAttributes(destinationPath, FileAttributes.Normal); // Reset attributes in case of 'read-only'
                        File.Delete(destinationPath);
                    }

                    entry.ExtractToFile(destinationPath, overwrite: true);
                }
            }
        }
        // Function to get assembly version
        static string GetAssemblyVersion(string exePath)
        {
            var assembly = Assembly.LoadFile(exePath);
            string version = assembly.GetName().Version.ToString();
            assembly = null; // Release the lock on the file
            return version;
        }

        // Function to kill a running process by name
        static void KillProcess(string processName)
        {
            foreach (var process in Process.GetProcessesByName(Path.GetFileNameWithoutExtension(processName)))
            {
                Console.WriteLine($"Closing {process.ProcessName}...");
                process.Kill();
                process.WaitForExit();
            }
        }

        // Class for deserializing version.js
        class VersionInfo
        {
            public string Version { get; set; }
        }
    }
}

我已尝试以下操作但问题仍存在:

  • 在替换文件前终止所有user_pc.exe运行进程,确保应用已关闭;
  • 以管理员身份运行应用;
  • 检查文件权限,确认用户对目标文件夹拥有完全访问权限。

错误原因分析

  1. 文件锁未释放:GetAssemblyVersion方法中使用Assembly.LoadFile加载了user_pc.exe,虽然设置了assembly = null,但CLR可能并未立即释放文件句柄,导致文件被锁定无法替换。
  2. 进程未完全终止:KillProcess调用process.Kill()后立即WaitForExit(),但部分进程可能需要额外时间释放资源,仍持有文件锁。
  3. 文件属性问题:目标文件可能被标记为只读或系统属性,即使删除操作前设置了FileAttributes.Normal,也可能因其他原因未生效。
  4. 防病毒软件拦截:部分杀毒软件会监控可执行文件的修改操作,自动锁定文件阻止替换。

可行的解决步骤

1. 修复版本读取逻辑,避免锁定文件

替换GetAssemblyVersion方法,使用FileVersionInfo读取版本号,不会锁定文件:

static string GetAssemblyVersion(string exePath)
{
    var versionInfo = FileVersionInfo.GetVersionInfo(exePath);
    return versionInfo.FileVersion;
}

2. 延长进程终止后的等待时间,确保资源释放

修改KillProcess方法,增加等待时间或循环检查进程是否完全退出:

static void KillProcess(string processName)
{
    string processNameWithoutExt = Path.GetFileNameWithoutExtension(processName);
    foreach (var process in Process.GetProcessesByName(processNameWithoutExt))
    {
        Console.WriteLine($"Closing {process.ProcessName}...");
        process.Kill();
        process.WaitForExit(5000); // 等待5秒,超时则强制结束
    }
    // 额外检查并等待进程完全退出
    while (Process.GetProcessesByName(processNameWithoutExt).Length > 0)
    {
        Thread.Sleep(1000);
        Console.WriteLine("Waiting for process to exit...");
    }
}

3. 强制重置目标文件属性并尝试替换

在替换文件前,先重置目标文件的属性,再尝试删除或替换:

// 在替换文件的循环中修改
if (File.Exists(destPath))
{
    // 强制重置属性为正常
    File.SetAttributes(destPath, FileAttributes.Normal);
    // 尝试删除,若失败则等待后重试
    int retryCount = 3;
    while (retryCount > 0)
    {
        try
        {
            File.Delete(destPath);
            break;
        }
        catch
        {
            retryCount--;
            Thread.Sleep(500);
        }
    }
}
File.Copy(file, destPath, overwrite: true);

4. 检查防病毒软件拦截

临时关闭防病毒软件测试更新流程,若问题解决,需将更新器和应用目录添加到杀毒软件的信任列表中。

5. 采用“先重命名再替换”的策略

如果直接删除失败,先将旧文件重命名为临时文件,再复制新文件,最后删除旧文件:

if (File.Exists(destPath))
{
    string tempOldPath = destPath + ".old";
    // 先重命名旧文件
    File.Move(destPath, tempOldPath);
    // 复制新文件
    File.Copy(file, destPath, overwrite: true);
    // 尝试删除旧文件,失败则忽略(后续可清理)
    try
    {
        File.Delete(tempOldPath);
    }
    catch
    {
        // 记录日志或后续处理
    }
}
else
{
    File.Copy(file, destPath, overwrite: true);
}

内容的提问来源于stack exchange,提问作者Y7_s

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 11:22:08