Search code examples
c#filemeffilestreamlocked

Manipulating files block them


I'm writing a WinForms program that uses MEF to load assemblies. Those assemblies are not located in the same folder than the executable. As I need to perform some file maintenance, I implemented some code in the file Program.cs, before loading the actual WinForm, so the files (even if assemblies) are not loaded (or shouldn't if they are) by the program.

I'm performing two operations: - Moving a folder from one location to an other one - Unzipping files from an archive and overwrite dll files from the folder moved (if file from the archive is newer than the one moved)

The problem is that after moving the folder, files in it are locked and cannot be overwritten. I also tried to move files one by one by disposing them when the move is finished.

Can someone explain me why the files are blocked and how I could avoid that

Thanks

private static void InitializePluginsFolder()
    {
        if (!Directory.Exists(Paths.PluginsPath))
        {
            Directory.CreateDirectory(Paths.PluginsPath);
        }

        // Find archive that contains plugins to deploy
        var assembly = Assembly.GetExecutingAssembly();
        if (assembly.Location == null)
        {
            throw new NullReferenceException("Executing assembly is null!");
        }

        var currentDirectory = new FileInfo(assembly.Location).DirectoryName;
        if (currentDirectory == null)
        {
            throw new NullReferenceException("Current folder is null!");
        }

        // Check if previous installation contains a "Plugins" folder
        var currentPluginsPath = Path.Combine(currentDirectory, "Plugins");
        if (Directory.Exists(currentPluginsPath))
        {
            foreach (FileInfo fi in new DirectoryInfo(currentPluginsPath).GetFiles())
            {
                using (FileStream sourceStream = new FileStream(fi.FullName, FileMode.Open))
                {
                    using (FileStream destStream = new FileStream(Path.Combine(Paths.PluginsPath, fi.Name), FileMode.Create))
                    {
                        destStream.Lock(0, sourceStream.Length);
                        sourceStream.CopyTo(destStream);
                    }
                }
            }

            Directory.Delete(currentPluginsPath, true);
        }

        // Then updates plugins with latest version of plugins (zipped)
        var pluginsZipFilePath = Path.Combine(currentDirectory, "Plugins.zip");

        // Extract content of plugins archive to a temporary folder
        var tempPath = string.Format("{0}_Temp", Paths.PluginsPath);

        if (Directory.Exists(tempPath))
        {
            Directory.Delete(tempPath, true);
        }

        ZipFile.ExtractToDirectory(pluginsZipFilePath, tempPath);

        // Moves all plugins to appropriate folder if version is greater
        // to the version in place
        foreach (var fi in new DirectoryInfo(tempPath).GetFiles())
        {
            if (fi.Extension.ToLower() != ".dll")
            {
                continue;
            }

            var targetFile = Path.Combine(Paths.PluginsPath, fi.Name);
            if (File.Exists(targetFile))
            {
                if (fi.GetAssemblyVersion() > new FileInfo(targetFile).GetAssemblyVersion())
                {
                    // If version to deploy is newer than current version
                    // Delete current version and copy the new one

                    // FAILS HERE

                    File.Copy(fi.FullName, targetFile, true);
                }
            }
            else
            {
                File.Move(fi.FullName, targetFile);
            }
        }

        // Delete temporary folder
        Directory.Delete(tempPath, true);
    }

Solution

  • Check the implementation of the GetAssemblyVersion() method used in this part of code:

    if (File.Exists(targetFile))
    {
      if (fi.GetAssemblyVersion() > new FileInfo(targetFile).GetAssemblyVersion())
      {
        // If version to deploy is newer than current version
        // Delete current version and copy the new one
    
        // FAILS HERE
    
        File.Copy(fi.FullName, targetFile, true);
      }
    }
    

    fi variable has type FileInfo, GetAssemblyVersion() looks like an extension method. You should check how assembly version is retrieved from the file. If this method loads an assembly it should also unload it to release the file.

    The separate AppDomain is helpful if you need to load the assembly, do the job and after that unload it. Here is the GetAssemblyVersion method implementation:

    public static Version GetAssemblyVersion(this FileInfo fi)
    {
      AppDomain checkFileDomain = AppDomain.CreateDomain("DomainToCheckFileVersion");
      Assembly assembly = checkFileDomain.Load(new AssemblyName {CodeBase = fi.FullName});
      Version fileVersion = assembly.GetName().Version;
      AppDomain.Unload(checkFileDomain);
      return fileVersion;
    }
    

    The following implementation of the GetAssemblyVersion() could retrieve the assembly version without loading assembly into your AppDomain. Thnx @usterdev for the hint. It also allows you to get the version without assembly references resolve:

    public static Version GetAssemblyVersion(this FileInfo fi)
    {
      return AssemblyName.GetAssemblyName(fi.FullName).Version;
    }