以编程方式获取锁定Excel工作簿的用户

5
我正在使用C#框架4.5,netoffice 1.6和sharpdevelop 4.4.1来操作一个位于网络共享中的Excel工作簿,并从Outlook内部进行操作。
在某些情况下,我需要像这样更改工作簿对象(ewb)的文件访问权限为读写:
ewb.ChangeFileAccess(Excel.Enums.XlFileAccess.xlReadWrite, System.Reflection.Missing.Value, true);

在改变文件访问权限之前,我会检查服务器上的文件是否被锁定。如果文件被锁定,我会通知用户稍后再尝试操作。
现在,我想在通知中包含锁定Excel文件的用户名。我已经搜索了 msdn、NetOffice 论坛等等,但都没有找到解决方案。 我知道,如果你以读写方式打开 Excel 文件,它会在 xlsx 文件中存储用户的名称。如何通过 C# 访问这个特定的信息呢?
编辑: 最终,我做了这个:
public string GetExcelFileOwner(string path, NetOffice.ExcelApi.Enums.XlFileFormat ffmt) {
        string tempmark = "~$";
        if(ffmt==NetOffice.ExcelApi.Enums.XlFileFormat.xlExcel8) {
            tempmark = "";
        }
        string uspath = Path.Combine(Path.GetDirectoryName(path), tempmark + Path.GetFileName(path));
        if (!File.Exists(uspath)) return "";
        var sharing = FileShare.ReadWrite | FileShare.Delete;
        using (var fs = new FileStream(uspath, FileMode.Open, FileAccess.Read, sharing))
        using (var br = new BinaryReader(fs, Encoding.Default)) {
            if(ffmt==NetOffice.ExcelApi.Enums.XlFileFormat.xlExcel8) {
                byte[] ByteBuffer = new byte[500];
                br.BaseStream.Seek(150, SeekOrigin.Begin);
                br.Read(ByteBuffer, 0, 500);
                return matchRegex(System.Text.Encoding.UTF8.GetString(ByteBuffer), @"(?=\w\w\w)([\w, ]+)").Trim();
            }
            else {
                return br.ReadString();
            }
        }
    }

    private static string matchRegex(string txt, string rgx) {
        Regex r;
        Match m;
        try {
            r = new Regex(rgx, RegexOptions.IgnoreCase);
            m = r.Match(txt);
            if (m.Success) {
                return m.Groups[1].Value.ToString();
            }
            else {
                return "";
            }
        }
        catch {
            return "";
        }
    }

我们使用的是Excel 2003和Excel 2007+文件格式(.xls和.xlsx)。 对于.xls,我必须查看.xls文件本身。 对于.xlsx,锁定用户存储在~$ temp文件中。 我知道,对于.xls文件,这是肮脏的代码,但我不知道.xls文件格式的结构。 因此,我只读取了一堆包括ASCII用户名的字节,并使用正则表达式提取该用户名。

你试过查看Win32_ConnectionShare和WMIC吗? - Mike Miller
2个回答

8
它将用户的姓名存储在xlsx文件中。 不是在.xlsx文件中,而是Excel创建另一个文件来存储用户名。它已打开隐藏文件属性,因此您通常无法在资源管理器中看到它。 通常具有与原始文件相同的名称,但前缀为~$。因此,对于名为test.xlsx的文件,您将获得名为~$test.xlsx的文件。它是一个二进制文件,包含用户姓名,编码方式为默认代码页和utf-16。以下是十六进制转储以显示其外观:
0000000000: 0C 48 61 6E 73 20 50 61 │ 73 73 61 6E 74 20 20 20  ♀Hans Passant
0000000010: 20 20 20 20 20 20 20 20 │ 20 20 20 20 20 20 20 20
0000000020: 20 20 20 20 20 20 20 20 │ 20 20 20 20 20 20 20 20
0000000030: 20 20 20 20 20 20 20 0C │ 00 48 00 61 00 6E 00 73         ♀ H a n s
0000000040: 00 20 00 50 00 61 00 73 │ 00 73 00 61 00 6E 00 74     P a s s a n t
0000000050: 00 20 00 20 00 20 00 20 │ 00 20 00 20 00 20 00 20
0000000060: 00 20 00 20 00 20 00 20 │ 00 20 00 20 00 20 00 20
0000000070: 00 20 00 20 00 20 00 20 │ 00 20 00 20 00 20 00 20
0000000080: 00 20 00 20 00 20 00 20 │ 00 20 00 20 00 20 00 20
0000000090: 00 20 00 20 00 20 00 20 │ 00 20 00 20 00 20 00 20
00000000A0: 00 20 00 20 00          │

文件中的0x0C单词是字符串长度(以字符而非字节为单位),后面跟着54个字符来存储用户名,用空格填充。最简单的方法是使用BinaryReader.ReadString()来读取它:

public static string GetExcelFileOwner(string path) {
    string uspath = Path.Combine(Path.GetDirectoryName(path), "~$" + Path.GetFileName(path));
    if (!File.Exists(uspath)) return "";
    var sharing = FileShare.ReadWrite | FileShare.Delete;
    using (var fs = new FileStream(uspath, FileMode.Open, FileAccess.Read, sharing))
    using (var br = new BinaryReader(fs, Encoding.Default)) {
        return br.ReadString();
    }
}

但不一定是最正确的方法,如果8位编码在您的语言环境中运行效果不佳,您可能希望改进代码并尝试定位utf-16字符串(而不是使用ReadString)。首先Seek()到偏移量0x37。请确保正确使用该方法,它具有隐式竞争条件,因此请确保只在操作失败之后使用它,并且无论如何都期望返回空字符串。我无法保证此方法将在所有Excel版本(包括未来版本)上正常工作,我只在工作站类计算机上测试了Office 2013的情况。


嗨,汉斯。这似乎仅适用于本地文件,而不适用于位于网络上的Excel文件。当我在硬盘驱动器上打开和修改Excel文件时,我会看到~$文件。但是从网络打开时,我看不到该文件。我在网络上以读/写方式打开xlsx文件时检查了它。打开它的用户名称确实存储在xlsx中。因此,我仍然认为我应该从xlsx文件中读取设置。 - nire
嗯,不太可能仅为了写入用户名而修改.xlsx文件。我能想到的唯一问题是用户没有对共享文件夹的写访问权限。但是让我们不要猜测,使用SysInternals的Process Monitor来查看发生了什么,你会看到Excel.exe正在处理文件。 - Hans Passant
Hans,你说得对。Procmon显示Excel.exe正在访问$......xlsx,并且结果为SUCCESS。哦天啊,现在我确实在服务器上看到了$....xlsx文件。我还尝试了一个xls文件,但并没有创建~$文件。用户的名称存储在实际的.xls文件中。我将尝试您的代码并回报结果。 - nire
Hans,你的解决方案对于 .xlsx 文件非常有效!赏金很快就会到账。 - nire

0
你遇到了哪个部分的问题?
不了解xslx,我只能猜测:您想打开文件并指定FileAccess.Read和FileShare.ReadWrite,如此处所示:How to read open excel file at C#之后,您使用某种库将XSLX转换为DataTable,并提取所需的特定行。

我正在使用netoffice.NET包装组件来操作Office文件和应用程序。问题是,我无法提取阻止Excel文件的用户的用户名。 - nire
听起来你需要知道谁打开了特定的文件以及它对该文件的标志。PSFile实用程序可以通过命令行完成此操作。"psfile \yourremoteshare C:\test.xlsx"。如果您有兴趣以编程方式执行此操作,可以下载APIMonitor并查看psfile如何执行此操作。我个人怀疑您是否可以访问“xlsx文件中的用户名”,这听起来像是一个内部事务。 - Erti-Chris Eelmaa
尽管我的建议解决方案需要管理员权限,这是不允许的。但您可以使用API监视器来检查Excel从远程文件接收用户名的方式。(例如,通过哪些API调用,以及它是否是内部的) - Erti-Chris Eelmaa
我不知道如何实现那个。我从 Rohitab 下载了 API 监控器,并从 Excel 进程中获取了一份跟踪记录。我应该如何捕获所需的信息呢? - nire

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接