如何检测已安装的MS-Office版本?

发布于 2024-09-09 12:11:29 字数 99 浏览 2 评论 0原文

有谁知道检测安装的 Office 版本的最佳方法是什么?另外,如果安装了多个版本的 Office,我想知道它们是什么版本。如果我可以检测到已安装的 Excel 的特定版本,那就太好了。

Does anyone know what would be the best way to detect which version of Office is installed? Plus, if there are multiple versions of Office installed, I'd like to know what versions they are. A bonus would be if I can detect the specific version(s) of Excel that is(/are) installed.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

心舞飞扬 2024-09-16 12:11:32

试试这个(对所有 Office 版本都有效):

            ManagementObjectSearcher LicenseSearcher =
                       new ManagementObjectSearcher("root\\CIMV2",
                       "SELECT name, LicenseStatus,Description,PartialProductKey  FROM SoftwareLicensingProduct where name like '%office%'");

            foreach (ManagementObject LSObj in LicenseSearcher.Get())
            {
                String prod_name = LSObj["Name"].ToString();
                String prod_description = LSObj["Description"].ToString();

                String prod_key = "";
                try
                {
                    prod_key = LSObj["PartialProductKey"].ToString();
                }
                catch
                {
                    prod_key = "";
                }
 
                
                String prod_lic_sta= LSObj["LicenseStatus"].ToString();

        
                switch (prod_lic_sta)
                {
                    case "0": 
                        prod_lic_sta = "Unlicensed";
                        break;
                    case "1": 
                        prod_lic_sta = "Licensed";
                        break;
                    case "2": 
                        prod_lic_sta = "OOBGrace";
                        break;
                    case "3":
                        prod_lic_sta = "OOTGrace";
                        break;
                    case "4":
                        prod_lic_sta = "NonGenuineGrace";
                        break;
                    case "5":
                        prod_lic_sta = "Notification";
                        break;
                    case "6":
                        prod_lic_sta = "ExtendedGrace";
                        break;
                }

                
            }
        

输出:

Office 19

Office19HomeBusiness2019R_Retail 版

密钥:XXXXX

状态:已许可

Try this (valid with all Office Versions):

            ManagementObjectSearcher LicenseSearcher =
                       new ManagementObjectSearcher("root\\CIMV2",
                       "SELECT name, LicenseStatus,Description,PartialProductKey  FROM SoftwareLicensingProduct where name like '%office%'");

            foreach (ManagementObject LSObj in LicenseSearcher.Get())
            {
                String prod_name = LSObj["Name"].ToString();
                String prod_description = LSObj["Description"].ToString();

                String prod_key = "";
                try
                {
                    prod_key = LSObj["PartialProductKey"].ToString();
                }
                catch
                {
                    prod_key = "";
                }
 
                
                String prod_lic_sta= LSObj["LicenseStatus"].ToString();

        
                switch (prod_lic_sta)
                {
                    case "0": 
                        prod_lic_sta = "Unlicensed";
                        break;
                    case "1": 
                        prod_lic_sta = "Licensed";
                        break;
                    case "2": 
                        prod_lic_sta = "OOBGrace";
                        break;
                    case "3":
                        prod_lic_sta = "OOTGrace";
                        break;
                    case "4":
                        prod_lic_sta = "NonGenuineGrace";
                        break;
                    case "5":
                        prod_lic_sta = "Notification";
                        break;
                    case "6":
                        prod_lic_sta = "ExtendedGrace";
                        break;
                }

                
            }
        

Output:

Office 19

Office19HomeBusiness2019R_Retail edition

Key: XXXXX

Status: Licensed

别理我 2024-09-16 12:11:31
namespace Software_Info_v1._0
{
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Office.Interop;

public class MS_Office
{
    public string GetOfficeVersion()
    {
        string sVersion = string.Empty;
        Microsoft.Office.Interop.Word.Application appVersion = new Microsoft.Office.Interop.Word.Application();
        appVersion.Visible = false;
        switch (appVersion.Version.ToString())
        {
            case "7.0":
                sVersion = "95";
                break;
            case "8.0":
                sVersion = "97";
                break;
            case "9.0":
                sVersion = "2000";
                break;
            case "10.0":
                sVersion = "2002";
                break;
            case "11.0":
                sVersion = "2003";
                break;
            case "12.0":
                sVersion = "2007";
                break;
            case "14.0":
                sVersion = "2010";
                break;
            default:
                sVersion = "Too Old!";
                break;
        }
        Console.WriteLine("MS office version: " + sVersion);
        return null;
    }



}
}
namespace Software_Info_v1._0
{
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Office.Interop;

public class MS_Office
{
    public string GetOfficeVersion()
    {
        string sVersion = string.Empty;
        Microsoft.Office.Interop.Word.Application appVersion = new Microsoft.Office.Interop.Word.Application();
        appVersion.Visible = false;
        switch (appVersion.Version.ToString())
        {
            case "7.0":
                sVersion = "95";
                break;
            case "8.0":
                sVersion = "97";
                break;
            case "9.0":
                sVersion = "2000";
                break;
            case "10.0":
                sVersion = "2002";
                break;
            case "11.0":
                sVersion = "2003";
                break;
            case "12.0":
                sVersion = "2007";
                break;
            case "14.0":
                sVersion = "2010";
                break;
            default:
                sVersion = "Too Old!";
                break;
        }
        Console.WriteLine("MS office version: " + sVersion);
        return null;
    }



}
}
萧瑟寒风 2024-09-16 12:11:31

为什么不检查 HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\[office.exe],其中 [office.exe] 代表特定的办公产品exe文件名,例如winword.exeexcel.exe等。
在那里您可以获得可执行文件的路径并检查该文件的版本。

如何检查文件的版本: 在 C++ 中 / 在 C#< /a>

对这种做法有什么批评吗?

Why not check HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\[office.exe], where [office.exe] stands for particular office product exe-filename, e.g. winword.exe, excel.exe etc.
There you get path to executable and check version of that file.

How to check version of the file: in C++ / in C#

Any criticism towards such approach?

梦过后 2024-09-16 12:11:31

我找到了一种优雅的方法来检测“Microsoft Office 版本”,当您想检测是否安装了“Office 2016”或“Office 2019”时,该方法可能也有效。

我刚刚检测到“Microsoft Office”的安装路径,然后从“Office”应用程序(在我的示例中为“Word”)的exe文件中声明了System.Diagnostics.FileVersionInfo,并从此FileVersionInfo中获取了我需要的所有内容。

以下是一个非常小的控制台应用程序的完整代码作为示例:

using System;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Sandbox_Console
{
    public class Program
    {
        static void Main(string[] args)
        {
            string installpath = GetOfficeInstallPath();
            OfficeVersionInfo info = new OfficeVersionInfo(installpath);
            Console.WriteLine("Full Office Version Number: " + info.FullVersionNumber);
            Console.WriteLine("Full Office Name: " + info.FullOfficeVersionName);
            Console.Write("Press any key to end this program...");
            Console.ReadKey();
        }

        public class OfficeVersionInfo
        {
            private string _FullVersionNumber = "";
            private string _FullOfficeVersionName = "";

            public string FullVersionNumber { get { return _FullVersionNumber; } }
            public string FullOfficeVersionName { get { return _FullOfficeVersionName; } }

            public OfficeVersionInfo(string installPath)
            {
                string filepath = installPath + "\\winword.exe"; //For Excel, PowerPoint or others use the exe-files for them.
                if (File.Exists(filepath))
                {
                    FileVersionInfo info = FileVersionInfo.GetVersionInfo(filepath);
                    _FullVersionNumber = info.ProductVersion;
                    _FullOfficeVersionName = info.ProductName;
                }
            }
        }

        public static string GetOfficeInstallPath()
        {
            string result = "";
            try
            {
                Type appType = Type.GetTypeFromProgID("Word.Application"); //Also works with Excel, PowerPoint or others.
                dynamic appInstance = Activator.CreateInstance(appType);
                result = appInstance.Path;
                appInstance.quit();
                Marshal.ReleaseComObject(appInstance);
            } catch (Exception exc)
            {
                Debug.Print(exc.Message);
            }
            return result;
        }  
    }
}

代码示例中“OfficeVersionInfo”中存储的信息是右键单击“winword.exe”然后单击“属性”时获得的信息。看这个:
winword.exe 详细信息

I figured out an elegant way to detect the "Microsoft Office Version" that perhaps also works, when you want to detect if you have "Office 2016" or "Office 2019" installed.

I just detected the installation path of "Microsoft Office" then declared a System.Diagnostics.FileVersionInfo from an exe-file of an "Office"-Application (in my example "Word") and got everything i need from this FileVersionInfo.

Here is the full code of a very small console application as an example:

using System;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Sandbox_Console
{
    public class Program
    {
        static void Main(string[] args)
        {
            string installpath = GetOfficeInstallPath();
            OfficeVersionInfo info = new OfficeVersionInfo(installpath);
            Console.WriteLine("Full Office Version Number: " + info.FullVersionNumber);
            Console.WriteLine("Full Office Name: " + info.FullOfficeVersionName);
            Console.Write("Press any key to end this program...");
            Console.ReadKey();
        }

        public class OfficeVersionInfo
        {
            private string _FullVersionNumber = "";
            private string _FullOfficeVersionName = "";

            public string FullVersionNumber { get { return _FullVersionNumber; } }
            public string FullOfficeVersionName { get { return _FullOfficeVersionName; } }

            public OfficeVersionInfo(string installPath)
            {
                string filepath = installPath + "\\winword.exe"; //For Excel, PowerPoint or others use the exe-files for them.
                if (File.Exists(filepath))
                {
                    FileVersionInfo info = FileVersionInfo.GetVersionInfo(filepath);
                    _FullVersionNumber = info.ProductVersion;
                    _FullOfficeVersionName = info.ProductName;
                }
            }
        }

        public static string GetOfficeInstallPath()
        {
            string result = "";
            try
            {
                Type appType = Type.GetTypeFromProgID("Word.Application"); //Also works with Excel, PowerPoint or others.
                dynamic appInstance = Activator.CreateInstance(appType);
                result = appInstance.Path;
                appInstance.quit();
                Marshal.ReleaseComObject(appInstance);
            } catch (Exception exc)
            {
                Debug.Print(exc.Message);
            }
            return result;
        }  
    }
}

The infos stored in "OfficeVersionInfo" in the code example are the infos you get when you rightclick on the "winword.exe" and then click on "Properties". See this:
Details of winword.exe

浅忆流年 2024-09-16 12:11:31

如果我能够检测到已安装的 Excel 的特定版本,那就太好了。

我知道这个问题很久以前就已经被提出和回答了,但是同样的问题让我一直很忙,直到我做出以下观察:

要获取内部版本号(例如 15.0.4569.1506),请探测 HKLM\SOFTWARE\Microsoft\Office\[VER]\Common\ProductVersion::LastProduct,其中 [VER] 是主版本号(Office 2007 为 12.0,Office 2010 为 14.0) ,Office 2013 为 15.0)。

在 64 位 Windows 上,您需要在 SOFTWAREMicrosoft 面包屑之间插入 Wow6432Node,无论 Office 安装的位数如何。

在我的机器上,这给出了最初安装的版本的版本信息。例如,对于 Office 2010,这些数字与此处列出的数字相匹配,并且它们与 File > 中报告的版本不同帮助,反映了修补程序应用的补丁。

A bonus would be if I can detect the specific version(s) of Excel that is(/are) installed.

I know the question has been asked and answered a long time ago, but this same question has kept me busy until I made this observation:

To get the build number (e.g. 15.0.4569.1506), probe HKLM\SOFTWARE\Microsoft\Office\[VER]\Common\ProductVersion::LastProduct, where [VER] is the major version number (12.0 for Office 2007, 14.0 for Office 2010, 15.0 for Office 2013).

On a 64-bit Windows, you need to insert Wow6432Node between the SOFTWARE and Microsoft crumbs, irrespective of the bitness of the Office installation.

On my machines, this gives the version information of the originally installed version. For Office 2010 for instance, the numbers match the ones listed here, and they differ from the version reported in File > Help, which reflects patches applied by hotfixes.

☆獨立☆ 2024-09-16 12:11:31
        public string WinWordVersion
        {
            get
            {
                string _version = string.Empty;
                Word.Application WinWord = new Word.Application();   

                switch (WinWord.Version.ToString())
                {
                    case "7.0":  _version = "95";
                        break;
                    case "8.0": _version = "97";
                        break;
                    case "9.0": _version = "2000";
                        break;
                    case "10.0": _version = "2002";
                        break;
                    case "11.0":  _version = "2003";
                        break;
                    case "12.0": _version = "2007";
                        break;
                    case "14.0": _version = "2010";
                        break;
                    case "15.0":  _version = "2013";
                        break;
                    case "16.0": _version = "2016";
                        break;
                    default:                            
                        break;
                }

                return WinWord.Caption + " " + _version;
            }
        }
        public string WinWordVersion
        {
            get
            {
                string _version = string.Empty;
                Word.Application WinWord = new Word.Application();   

                switch (WinWord.Version.ToString())
                {
                    case "7.0":  _version = "95";
                        break;
                    case "8.0": _version = "97";
                        break;
                    case "9.0": _version = "2000";
                        break;
                    case "10.0": _version = "2002";
                        break;
                    case "11.0":  _version = "2003";
                        break;
                    case "12.0": _version = "2007";
                        break;
                    case "14.0": _version = "2010";
                        break;
                    case "15.0":  _version = "2013";
                        break;
                    case "16.0": _version = "2016";
                        break;
                    default:                            
                        break;
                }

                return WinWord.Caption + " " + _version;
            }
        }
薄荷梦 2024-09-16 12:11:31

对于任何可能关心的人,这是我检查 Office 95-2019 和 Office 95-2019 的版本。 O365,基于 MSI 和 ClickAndRun 在 32 位和 64 位系统上均受支持(未安装 64 位版本时回落到 32 位)。

用 Python 3.5 编写,但当然您始终可以使用该逻辑以另一种语言编写自己的代码:

from winreg import *
from typing import Tuple, Optional, List

# Let's make sure the dictionnary goes from most recent to oldest
KNOWN_VERSIONS = {
    '16.0': '2016/2019/O365',
    '15.0': '2013',
    '14.0': '2010',
    '12.0': '2007',
    '11.0': '2003',
    '10.0': '2002',
    '9.0': '2000',
    '8.0': '97',
    '7.0': '95',
}


def get_value(hive: int, key: str, value: Optional[str], arch: int = 0) -> str:
    """
    Returns a value from a given registry path

    :param hive: registry hive (windows.registry.HKEY_LOCAL_MACHINE...)
    :param key:  which registry key we're searching for
    :param value: which value we query, may be None if unnamed value is searched
    :param arch: which registry architecture we seek (0 = default, windows.registry.KEY_WOW64_64KEY, windows.registry.KEY_WOW64_32KEY)
                 Giving multiple arches here will return first result
    :return: value
    """

    def _get_value(hive: int, key: str, value: Optional[str], arch: int) -> str:
        try:
            open_reg = ConnectRegistry(None, hive)
            open_key = OpenKey(open_reg, key, 0, KEY_READ | arch)
            value, type = QueryValueEx(open_key, value)
            # Return the first match
            return value
        except (FileNotFoundError, TypeError, OSError) as exc:
            raise FileNotFoundError('Registry key [%s] with value [%s] not found. %s' % (key, value, exc))

    # 768 = 0 | KEY_WOW64_64KEY | KEY_WOW64_32KEY (where 0 = default)
    if arch == 768:
        for _arch in [KEY_WOW64_64KEY, KEY_WOW64_32KEY]:
            try:
                return _get_value(hive, key, value, _arch)
            except FileNotFoundError:
                pass
        raise FileNotFoundError
    else:
        return _get_value(hive, key, value, arch)


def get_keys(hive: int, key: str, arch: int = 0, open_reg: HKEYType = None, recursion_level: int = 1,
             filter_on_names: List[str] = None, combine: bool = False) -> dict:
    """
    :param hive: registry hive (windows.registry.HKEY_LOCAL_MACHINE...)
    :param key: which registry key we're searching for
    :param arch: which registry architecture we seek (0 = default, windows.registry.KEY_WOW64_64KEY, windows.registry.KEY_WOW64_32KEY)
    :param open_reg: (handle) handle to already open reg key (for recursive searches), do not give this in your function call
    :param recursion_level: recursivity level
    :param filter_on_names: list of strings we search, if none given, all value names are returned
    :param combine: shall we combine multiple arch results or return first match
    :return: list of strings
    """
    def _get_keys(hive: int, key: str, arch: int, open_reg: HKEYType, recursion_level: int, filter_on_names: List[str]):
        try:
            if not open_reg:
                open_reg = ConnectRegistry(None, hive)
            open_key = OpenKey(open_reg, key, 0, KEY_READ | arch)
            subkey_count, value_count, _ = QueryInfoKey(open_key)

            output = {}
            values = []
            for index in range(value_count):
                name, value, type = EnumValue(open_key, index)
                if isinstance(filter_on_names, list) and name not in filter_on_names:
                    pass
                else:
                    values.append({'name': name, 'value': value, 'type': type})
            if not values == []:
                output[''] = values

            if recursion_level > 0:
                for subkey_index in range(subkey_count):
                    try:
                        subkey_name = EnumKey(open_key, subkey_index)
                        sub_values = get_keys(hive=0, key=key + '\\' + subkey_name, arch=arch,
                                              open_reg=open_reg, recursion_level=recursion_level - 1,
                                              filter_on_names=filter_on_names)
                        output[subkey_name] = sub_values
                    except FileNotFoundError:
                        pass

            return output

        except (FileNotFoundError, TypeError, OSError) as exc:
            raise FileNotFoundError('Cannot query registry key [%s]. %s' % (key, exc))

    # 768 = 0 | KEY_WOW64_64KEY | KEY_WOW64_32KEY (where 0 = default)
    if arch == 768:
        result = {}
        for _arch in [KEY_WOW64_64KEY, KEY_WOW64_32KEY]:
            try:
                if combine:
                    result.update(_get_keys(hive, key, _arch, open_reg, recursion_level, filter_on_names))
                else:
                    return _get_keys(hive, key, _arch, open_reg, recursion_level, filter_on_names)
            except FileNotFoundError:
                pass
        return result
    else:
        return _get_keys(hive, key, arch, open_reg, recursion_level, filter_on_names)


def get_office_click_and_run_ident():
    # type: () -> Optional[str]
    """
    Try to find the office product via clickandrun productID
    """
    try:
        click_and_run_ident = get_value(HKEY_LOCAL_MACHINE,
                                                 r'Software\Microsoft\Office\ClickToRun\Configuration',
                                                 'ProductReleaseIds',
                                                 arch=KEY_WOW64_64KEY |KEY_WOW64_32KEY,)
    except FileNotFoundError:
        click_and_run_ident = None
    return click_and_run_ident


def _get_used_word_version():
    # type: () -> Optional[int]
    """
    Try do determine which version of Word is used (in case multiple versions are installed)
    """
    try:
        word_ver = get_value(HKEY_CLASSES_ROOT, r'Word.Application\CurVer', None)
    except FileNotFoundError:
        word_ver = None
    try:
        version = int(word_ver.split('.')[2])
    except (IndexError, ValueError, AttributeError):
        version = None
    return version


def _get_installed_office_version():
    # type: () -> Optional[str, bool]
    """
    Try do determine which is the highest current version of Office installed
    """
    for possible_version, _ in KNOWN_VERSIONS.items():
        try:
            office_keys = get_keys(HKEY_LOCAL_MACHINE,
                                               r'SOFTWARE\Microsoft\Office\{}'.format(possible_version),
                                               recursion_level=2,
                                               arch=KEY_WOW64_64KEY |KEY_WOW64_32KEY,
                                               combine=True)

            try:
                is_click_and_run = True if office_keys['ClickToRunStore'] is not None else False
            except:
                is_click_and_run = False

            try:
                is_valid = True if office_keys['Word'] is not None else False
                if is_valid:
                    return possible_version, is_click_and_run
            except KeyError:
                pass
        except FileNotFoundError:
            pass
    return None, None


def get_office_version():
    # type: () -> Tuple[str, Optional[str]]
    """
    It's plain horrible to get the office version installed
    Let's use some tricks, ie detect current Word used
    """

    word_version = _get_used_word_version()
    office_version, is_click_and_run = _get_installed_office_version()

    # Prefer to get used word version instead of installed one
    if word_version is not None:
        office_version = word_version

    version = float(office_version)
    click_and_run_ident = get_office_click_and_run_ident()

    def _get_office_version():
        # type: () -> str
        if version:
            if version < 16:
                try:
                    return KNOWN_VERSIONS['{}.0'.format(version)]
                except KeyError:
                    pass
            # Special hack to determine which of 2016, 2019 or O365 it is
            if version == 16:
                if isinstance(click_and_run_ident, str):
                    if '2016' in click_and_run_ident:
                        return '2016'
                    if '2019' in click_and_run_ident:
                        return '2019'
                    if 'O365' in click_and_run_ident:
                        return 'O365'
                return '2016/2019/O365'
        # Let's return whatever we found out
        return 'Unknown: {}'.format(version, click_and_run_ident)

    if isinstance(click_and_run_ident, str) or is_click_and_run:
        click_and_run_suffix = 'ClickAndRun'
    else:
        click_and_run_suffix = None

    return _get_office_version(), click_and_run_suffix

您可以使用如下示例的代码:

office_version, click_and_run = get_office_version()
print('Office {} {}'.format(office_version, click_and_run))

备注

  • 未使用 Office 进行测试 < 2010 年,尽管
  • 注册表函数和 Office 函数之间的 Python 键入有所不同,因为我在发现 pypy / python2 不喜欢键入之前编写了注册表函数...在那些 python 解释器上,您可能只是完全删除键入
  • 任何改进都非常受欢迎

To whoever it might concern, here's my version that checks for Office 95-2019 & O365, both MSI based and ClickAndRun are supported, on both 32 and 64 bit systems (falls back to 32 bits when 64 bit version is not installed).

Written in Python 3.5 but of course you can always use that logic in order to write your own code in another language:

from winreg import *
from typing import Tuple, Optional, List

# Let's make sure the dictionnary goes from most recent to oldest
KNOWN_VERSIONS = {
    '16.0': '2016/2019/O365',
    '15.0': '2013',
    '14.0': '2010',
    '12.0': '2007',
    '11.0': '2003',
    '10.0': '2002',
    '9.0': '2000',
    '8.0': '97',
    '7.0': '95',
}


def get_value(hive: int, key: str, value: Optional[str], arch: int = 0) -> str:
    """
    Returns a value from a given registry path

    :param hive: registry hive (windows.registry.HKEY_LOCAL_MACHINE...)
    :param key:  which registry key we're searching for
    :param value: which value we query, may be None if unnamed value is searched
    :param arch: which registry architecture we seek (0 = default, windows.registry.KEY_WOW64_64KEY, windows.registry.KEY_WOW64_32KEY)
                 Giving multiple arches here will return first result
    :return: value
    """

    def _get_value(hive: int, key: str, value: Optional[str], arch: int) -> str:
        try:
            open_reg = ConnectRegistry(None, hive)
            open_key = OpenKey(open_reg, key, 0, KEY_READ | arch)
            value, type = QueryValueEx(open_key, value)
            # Return the first match
            return value
        except (FileNotFoundError, TypeError, OSError) as exc:
            raise FileNotFoundError('Registry key [%s] with value [%s] not found. %s' % (key, value, exc))

    # 768 = 0 | KEY_WOW64_64KEY | KEY_WOW64_32KEY (where 0 = default)
    if arch == 768:
        for _arch in [KEY_WOW64_64KEY, KEY_WOW64_32KEY]:
            try:
                return _get_value(hive, key, value, _arch)
            except FileNotFoundError:
                pass
        raise FileNotFoundError
    else:
        return _get_value(hive, key, value, arch)


def get_keys(hive: int, key: str, arch: int = 0, open_reg: HKEYType = None, recursion_level: int = 1,
             filter_on_names: List[str] = None, combine: bool = False) -> dict:
    """
    :param hive: registry hive (windows.registry.HKEY_LOCAL_MACHINE...)
    :param key: which registry key we're searching for
    :param arch: which registry architecture we seek (0 = default, windows.registry.KEY_WOW64_64KEY, windows.registry.KEY_WOW64_32KEY)
    :param open_reg: (handle) handle to already open reg key (for recursive searches), do not give this in your function call
    :param recursion_level: recursivity level
    :param filter_on_names: list of strings we search, if none given, all value names are returned
    :param combine: shall we combine multiple arch results or return first match
    :return: list of strings
    """
    def _get_keys(hive: int, key: str, arch: int, open_reg: HKEYType, recursion_level: int, filter_on_names: List[str]):
        try:
            if not open_reg:
                open_reg = ConnectRegistry(None, hive)
            open_key = OpenKey(open_reg, key, 0, KEY_READ | arch)
            subkey_count, value_count, _ = QueryInfoKey(open_key)

            output = {}
            values = []
            for index in range(value_count):
                name, value, type = EnumValue(open_key, index)
                if isinstance(filter_on_names, list) and name not in filter_on_names:
                    pass
                else:
                    values.append({'name': name, 'value': value, 'type': type})
            if not values == []:
                output[''] = values

            if recursion_level > 0:
                for subkey_index in range(subkey_count):
                    try:
                        subkey_name = EnumKey(open_key, subkey_index)
                        sub_values = get_keys(hive=0, key=key + '\\' + subkey_name, arch=arch,
                                              open_reg=open_reg, recursion_level=recursion_level - 1,
                                              filter_on_names=filter_on_names)
                        output[subkey_name] = sub_values
                    except FileNotFoundError:
                        pass

            return output

        except (FileNotFoundError, TypeError, OSError) as exc:
            raise FileNotFoundError('Cannot query registry key [%s]. %s' % (key, exc))

    # 768 = 0 | KEY_WOW64_64KEY | KEY_WOW64_32KEY (where 0 = default)
    if arch == 768:
        result = {}
        for _arch in [KEY_WOW64_64KEY, KEY_WOW64_32KEY]:
            try:
                if combine:
                    result.update(_get_keys(hive, key, _arch, open_reg, recursion_level, filter_on_names))
                else:
                    return _get_keys(hive, key, _arch, open_reg, recursion_level, filter_on_names)
            except FileNotFoundError:
                pass
        return result
    else:
        return _get_keys(hive, key, arch, open_reg, recursion_level, filter_on_names)


def get_office_click_and_run_ident():
    # type: () -> Optional[str]
    """
    Try to find the office product via clickandrun productID
    """
    try:
        click_and_run_ident = get_value(HKEY_LOCAL_MACHINE,
                                                 r'Software\Microsoft\Office\ClickToRun\Configuration',
                                                 'ProductReleaseIds',
                                                 arch=KEY_WOW64_64KEY |KEY_WOW64_32KEY,)
    except FileNotFoundError:
        click_and_run_ident = None
    return click_and_run_ident


def _get_used_word_version():
    # type: () -> Optional[int]
    """
    Try do determine which version of Word is used (in case multiple versions are installed)
    """
    try:
        word_ver = get_value(HKEY_CLASSES_ROOT, r'Word.Application\CurVer', None)
    except FileNotFoundError:
        word_ver = None
    try:
        version = int(word_ver.split('.')[2])
    except (IndexError, ValueError, AttributeError):
        version = None
    return version


def _get_installed_office_version():
    # type: () -> Optional[str, bool]
    """
    Try do determine which is the highest current version of Office installed
    """
    for possible_version, _ in KNOWN_VERSIONS.items():
        try:
            office_keys = get_keys(HKEY_LOCAL_MACHINE,
                                               r'SOFTWARE\Microsoft\Office\{}'.format(possible_version),
                                               recursion_level=2,
                                               arch=KEY_WOW64_64KEY |KEY_WOW64_32KEY,
                                               combine=True)

            try:
                is_click_and_run = True if office_keys['ClickToRunStore'] is not None else False
            except:
                is_click_and_run = False

            try:
                is_valid = True if office_keys['Word'] is not None else False
                if is_valid:
                    return possible_version, is_click_and_run
            except KeyError:
                pass
        except FileNotFoundError:
            pass
    return None, None


def get_office_version():
    # type: () -> Tuple[str, Optional[str]]
    """
    It's plain horrible to get the office version installed
    Let's use some tricks, ie detect current Word used
    """

    word_version = _get_used_word_version()
    office_version, is_click_and_run = _get_installed_office_version()

    # Prefer to get used word version instead of installed one
    if word_version is not None:
        office_version = word_version

    version = float(office_version)
    click_and_run_ident = get_office_click_and_run_ident()

    def _get_office_version():
        # type: () -> str
        if version:
            if version < 16:
                try:
                    return KNOWN_VERSIONS['{}.0'.format(version)]
                except KeyError:
                    pass
            # Special hack to determine which of 2016, 2019 or O365 it is
            if version == 16:
                if isinstance(click_and_run_ident, str):
                    if '2016' in click_and_run_ident:
                        return '2016'
                    if '2019' in click_and_run_ident:
                        return '2019'
                    if 'O365' in click_and_run_ident:
                        return 'O365'
                return '2016/2019/O365'
        # Let's return whatever we found out
        return 'Unknown: {}'.format(version, click_and_run_ident)

    if isinstance(click_and_run_ident, str) or is_click_and_run:
        click_and_run_suffix = 'ClickAndRun'
    else:
        click_and_run_suffix = None

    return _get_office_version(), click_and_run_suffix

You can than use the code like the following example:

office_version, click_and_run = get_office_version()
print('Office {} {}'.format(office_version, click_and_run))

Remarks

  • Didn't test with office < 2010 though
  • Python typing is different between the registry functions and the office functions since I wrote the registry ones before finding out that pypy / python2 does not like typing... On those python interpreters you might just remove typing completly
  • Any improvements are highly welcome
征棹 2024-09-16 12:11:30

检查已安装的 Office 版本的一种方法是检查感兴趣的 Office 应用程序的 InstallRoot 注册表项。

例如,如果您想检查是否安装了 Word 2007,则应检查是否存在以下注册表项:

HKLM\Software\Microsoft\Office\12.0\Word\InstallRoot::Path

该条目包含可执行文件的路径。

将 12.0(对于 Office 2007)替换为相应的版本号:

Office 97   -  7.0
Office 98   -  8.0
Office 2000 -  9.0
Office XP   - 10.0
Office 2003 - 11.0
Office 2007 - 12.0
Office 2010 - 14.0 (sic!)
Office 2013 - 15.0
Office 2016 - 16.0
Office 2019 - 16.0 (sic!)

其他应用程序具有类似的密钥:

HKLM\Software\Microsoft\Office\12.0\Excel\InstallRoot::Path
HKLM\Software\Microsoft\Office\12.0\PowerPoint\InstallRoot::Path

或者您可以检查所有应用程序的公共根路径:

HKLM\Software\Microsoft\Office\12.0\Common\InstallRoot::Path

另一种不使用特定注册表项的选项是使用 < code>MSIENumProducts API,如此处所述。

顺便说一句,Microsoft 并不正式支持不同 Office 版本的并行安装。它们确实起到了一定的作用,但您可能会得到不需要的效果和不一致的结果。

更新:Office 2019 和 Office 365

从 Office 2019 开始,基于 MSI 的安装不再可用,即点即用是现在部署 Office 的唯一方式。除了定期更新 Office 365 的这一变化之外,Office 的主要/次要版本号也不再更新(至少暂时如此)。这意味着,即使对于 Office 2019,注册表项中使用的值和 Application.Version(例如在 Word 中)返回的值仍然是 16.0

目前,还没有记录的方法来区分 Office 2016 和 Office 2019。线索可能是 winword.exe 的文件版本;但是,对于已修补的 Office 2016 版本,此版本也会增加(请参阅下面 @antonio 的评论)。

如果您需要以某种方式区分 Office 版本,例如,为了确保存在某种功能或安装了最低版本的 Office,可能是查看主要 Office 之一的文件版本的最佳方法应用程序:

// Using the file path to winword.exe
// Retrieve the path e.g. from the InstallRoot Registry key
var fileVersionInfo = FileVersionInfo.GetVersionInfo(@"C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE");
var version = new Version(fileVersionInfo.FileVersion);

// On a running instance using the `Process` class
var process = Process.GetProcessesByName("winword").First();
string fileVersionInfo = process.MainModule.FileVersionInfo.FileVersion;
var version = Version(fileVersionInfo);

Office 2019 的文件版本为 16.0.10730.20102,因此,如果您看到任何大于该版本的内容,则说明您正在处理 Office 2019 或当前的 Office 365 版本。

One way to check for the installed Office version would be to check the InstallRoot registry keys for the Office applications of interest.

For example, if you would like to check whether Word 2007 is installed you should check for the presence of the following Registry key:

HKLM\Software\Microsoft\Office\12.0\Word\InstallRoot::Path

This entry contains the path to the executable.

Replace 12.0 (for Office 2007) with the corresponding version number:

Office 97   -  7.0
Office 98   -  8.0
Office 2000 -  9.0
Office XP   - 10.0
Office 2003 - 11.0
Office 2007 - 12.0
Office 2010 - 14.0 (sic!)
Office 2013 - 15.0
Office 2016 - 16.0
Office 2019 - 16.0 (sic!)

The other applications have similar keys:

HKLM\Software\Microsoft\Office\12.0\Excel\InstallRoot::Path
HKLM\Software\Microsoft\Office\12.0\PowerPoint\InstallRoot::Path

Or you can check the common root path of all applications:

HKLM\Software\Microsoft\Office\12.0\Common\InstallRoot::Path

Another option, without using specific Registry keys would be to query the MSI database using the MSIEnumProducts API as described here.

As an aside, parallel installations of different Office versions are not officially supported by Microsoft. They do somewhat work, but you might get undesired effects and inconsistencies.

Update: Office 2019 and Office 365

As of Office 2019, MSI-based setup are no longer available, Click-To-Run is the only way to deploy Office now. Together with this change towards the regularly updated Office 365, also the major/minor version numbers of Office are no longer updated (at least for the time being). That means that – even for Office 2019 – the value used in Registry keys and the value returned by Application.Version (e.g. in Word) still is 16.0.

For the time being, there is no documented way to distinguish the Office 2016 from Office 2019. A clue might be the file version of the winword.exe; however, this version is also incremented for patched Office 2016 versions (see the comment by @antonio below).

If you need to distinguish somehow between Office versions, e.g. to make sure that a certain feature is present or that a minimum version of Office is installed, probably the best way it to look at the file version of one of the main Office applications:

// Using the file path to winword.exe
// Retrieve the path e.g. from the InstallRoot Registry key
var fileVersionInfo = FileVersionInfo.GetVersionInfo(@"C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE");
var version = new Version(fileVersionInfo.FileVersion);

// On a running instance using the `Process` class
var process = Process.GetProcessesByName("winword").First();
string fileVersionInfo = process.MainModule.FileVersionInfo.FileVersion;
var version = Version(fileVersionInfo);

The file version of Office 2019 is 16.0.10730.20102, so if you see anything greater than that you are dealing with Office 2019 or a current Office 365 version.

烟沫凡尘 2024-09-16 12:11:30

HKEY_CLASSES_ROOT\Word.Application\CurVer 怎么样?

How about HKEY_CLASSES_ROOT\Word.Application\CurVer?

给我一枪 2024-09-16 12:11:30

如果您在 64 位计算机上安装了 32 位 Office,则可能需要检查是否存在“SOFTWARE\Wow6432Node\Microsoft\Office\12.0\”,并将 12.0 替换为适当的版本。对于安装在 64 位 Windows 7 上的 Office 2007 来说,情况确实如此。

请注意,Office 2010 (== 14.0) 是第一个存在 64 位版本的 Office。

If you've installed 32-bit Office on a 64-bit machine, you may need to check for the presence of "SOFTWARE\Wow6432Node\Microsoft\Office\12.0\", substituting the 12.0 with the appropriate version. This is certainly the case for Office 2007 installed on 64-bit Windows 7.

Note that Office 2010 (== 14.0) is the first Office for which a 64-bit version exists.

℡Ms空城旧梦 2024-09-16 12:11:30

尽管这个问题很久以前就已经得到了回答,但我发现了一些与上述答案相关的有趣事实需要补充。

正如 Dirk 提到的,从 Office 365 / 2019 开始,MS 似乎有一种奇怪的版本控制方式。你无法再通过查看可执行路径来区分这三个版本(2016、2019、O365)。正如他自己所宣称的那样,通过查看可执行文件的构建来判断哪个是什么,也不是很有效。

经过一番研究,我找到了一个可行的解决方案。解决方案位于注册表子项 Computer\HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Common\Licensing\LicensingNext 下。

所以,我的逻辑如下:

情况1:如果计算机安装了MSOffice 2016,则Licensing下没有子项。

情况2:如果计算机安装了MSOffice 2019,则有值的名称(它是Office产品ID之一)。 (例如Standard2019Volume

情况3:如果计算机安装了Office365,则有一个名为o365bussinessretail的值(也是产品ID)与一些其他值。

提供了可能的productId

为了区分这三者,我只是打开钥匙看看是否失败。如果打开失败,则为Office 2016。然后,我枚举 LicensingNext 并尝试查看是否有任何名称具有前缀 o365,如果找到,则为 O365。如果没有,则为 Office 2019

坦白说,我没有足够的时间来测试不同环境下的逻辑。所以请注意这一点。

希望这对感兴趣的人有所帮助。

Despite the fact that this question has been answered long time ago, I found some interesting facts to add that are related to the answers above.

As Dirk mentioned, there seems to be a weird fashion of version control from MS, starting from Office 365 / 2019. You cannot distinguish among the three(2016, 2019, O365), by seeing at the executable paths anymore. And just like he reputed himself, looking at the builds of the executable, as a mean of telling which is what, isn't quite effective either.

After some researching, I found a feasible solution. The solution lies under the registry subkey Computer\HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Common\Licensing\LicensingNext.

So, my logic follows below:

Case 1: If the computer has the MSOffice 2016 installed, there is no subkeys under Licensing.

Case 2: if the computer has MSOffice 2019 installed, there is the name of the value (which is one of the Office Product ID). (e.g. Standard2019Volume)

Case 3: if the computer has Office365 installed, there is a value called o365bussinessretail(which is also a product ID) along with some other values.

The possible productIds are provided here.

To distinguish the three, I just opened the key and see if fails. If the open fails, its Office 2016. Then I enumerate LicensingNext and try to see if any name has a prefix o365, if it finds it then its O365. If it does not, then its Office 2019.

Frankly speaking, I did not have enough time to test the logic under varying environment. So please, note that.

Hope this will help whoever's interest.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文