65.9K
CodeProject 正在变化。 阅读更多。
Home

Ved 启动管理器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2017 年 2 月 4 日

CPOL

4分钟阅读

viewsIcon

8634

downloadIcon

339

本文使用VC++编写,作为管理Windows启动应用程序的实用工具。

引言

启动程序是在Windows启动时自动启动或开始的条目。有些程序需要在Windows启动时自动启动,但启动列表中的大多数应用程序会占用系统内存和CPU。这还会增加系统的启动时间。其中一些程序会在后台运行,从而降低系统性能。因此,有必要根据您的需求管理启动应用程序。

背景

启动项是Windows操作系统的重要组成部分。通过Windows,我们可以使用“Windows Management Instrumentation”查看启动程序。要通过此过程查看启动程序,我们需要遵循以下步骤:
    1) 打开命令提示符并键入命令“WMIC”,然后按“Enter”键。
    2) 之后,键入“STARTUP”,然后按“Enter”键。
我们也可以通过“MSCONFIG”或“TASKMANAGER”查看启动程序。

关于启动项

在Windows操作系统上,我们可以通过两种方法使任何应用程序作为启动项运行。要么我们将执行命令(包含任何命令行参数的可执行路径)放入系统的注册表中,要么我们将应用程序的链接放入Windows操作系统的特殊文件夹中。

注册表

注册表中启动应用程序的位置如下:

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunServices
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce

RUN

在“RUN”子键中找到的命令会在用户每次登录计算机时启动。关于“RUN”子键下的命令的有趣点是:

  • 此子键下的命令将在每次窗口登录后运行,而不是在启动时运行。
  • 这些命令没有特定的执行顺序。
  • 此键位于“HKEY_LOCAL_MACHINE”和“HKEY_CURRENT_USER”注册表配置单元下。
  • 如果位于“HKEY_LOCAL_MACHINE”下,则命令会影响所有用户。

RUNONCE

此类别下的命令只运行一次。Windows在执行后会从“RunOnce”子键路径中删除这些命令。对于其他情况,“RUNONCE”子键与“RUN”类似。

要了解有关这些条目和其他启动项的更多信息,请参阅以下链接:

http://www.tenouk.com/ModuleP1.html

https://msdn.microsoft.com/en-us/library/windows/desktop/aa376977(v=vs.85).aspx

https://msdn.microsoft.com/en-us/windows/hardware/drivers/install/runonce-registry-key

https://support.microsoft.com/en-us/help/310593/description-of-the-runonceex-registry-key

https://msdn.microsoft.com/en-us/library/windows/desktop/ms724072(v=vs.85).aspx

注意

  • 如果您的操作系统是64位,则32位应用程序的启动项将存储在:
    HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Run
    HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\RunOnce
    HKEY_CURRENT_USER\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Run
    HKEY_CURRENT_USER\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\RunOnce
  • Wow6432Node
    要在64位计算机上管理32位应用程序,将使用“Wow6432Node”键。

启动文件夹

启动目录或文件夹包含在Windows登录后立即启动的快捷方式或可执行文件的列表。在Windows 8/10上,我们可以在以下路径找到它们:

所有用户

%PROGRAMDATA%\Microsoft\Windows\Start Menu\Programs\StartUp
访问此路径的快捷方式:打开“运行”,键入“shell:common startup”并按 Enter。

用户配置文件/当前用户

%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup
访问此路径的快捷方式:打开“运行”,键入“shell:startup”并按 Enter。

使用的类

在详细阐述本文这一部分之前,我想感谢“Sven Wiegand”的文章《检索文件版本信息》。我在此应用程序中使用了“CFileVersionInfo”类来检索文件信息。您可以在此处找到它:

https://codeproject.org.cn/Articles/1502/CFileVersionInfo-Retrieving-a-File-s-full-Version

此实用程序的源代码包含以下类:

  1. CStartupDlg: 这是应用程序的主要类,负责用户界面。

  1. CCommonUtilities: 此类包含项目中使用的通用实用函数。

  1. CRegistryUtility: 此类包含项目所需的注册表操作函数。

  1. CAddNewStartupEntry: 这是对话框类,用于通过用户界面将新的启动项添加到注册表中。

 

使用代码

注册表实用函数

以下两个函数用于从注册表中访问启动项:

32BIT

void CRegistryUtility::Query32BitAppValues(const HKEY hRoot, const CString strSubKey, vector<pair<CString, CString>> &vecValueData)
    /* ===================================================================================
    NAME OF FUNCTION:   Query32BitAppValues
    CREDIT:             Satish Jagtap
    PURPOSE:            Retrieve data from registry.
    PARAMETERS:         1) [IN] const HKEY hRoot: Root key or hive
                        2) [IN] const CString strSubKey: Key path to retrieve data from
                        3) [OUT] vector<pair<CString, CString>> &vecValueData: List of
                                    retrieved data entries
    RETURN VALUE:       None
    CALLS TO: -         QueryRegistryKeyForValAndData
    CALLED FROM: -      None
    Added date: -       1 Jan, 2016
   ===================================================================================*/
{
    HKEY hKey = NULL;

    if(RegOpenKeyEx(hRoot, strSubKey, 0, KEY_ALL_ACCESS|KEY_WOW64_32KEY, &hKey) != ERROR_SUCCESS) return;

    QueryRegistryKeyForValAndData(hKey, vecValueData); 
    RegCloseKey(hKey);
}

64BIT

void CRegistryUtility::Query64BitAppValues(const HKEY hRoot, const CString strSubKey, vector<pair<CString, CString>> &vecValueData)
    /* ===================================================================================
    NAME OF FUNCTION:   Query64BitAppValues
    CREDIT:             Satish Jagtap
    PURPOSE:            Retrieve data from registry.
    PARAMETERS:         1) [IN] const HKEY hRoot: Root key or hive
                        2) [IN] const CString strSubKey: Key path to retrieve data from
                        3) [OUT] vector<pair<CString, CString>> &vecValueData: List of
                                    retrieved data entries
    RETURN VALUE:       None
    CALLS TO: -         QueryRegistryKeyForValAndData
    CALLED FROM: -      None
    Added date: -       1 Jan, 2016
   ===================================================================================*/
{
    HKEY hKey = NULL;

    if(RegOpenKeyEx(hRoot, strSubKey, 0, KEY_ALL_ACCESS|KEY_WOW64_64KEY, &hKey) != ERROR_SUCCESS) return;

    QueryRegistryKeyForValAndData(hKey, vecValueData); 
    RegCloseKey(hKey);
}

主要功能

  1. 从注册表的“RUN”和“RUNONCE”子项中检索所有启动项。
void CStartupDlg::RefreshStartupList_Enable()
    /* ===================================================================================
    NAME OF FUNCTION:   RefreshStartupList_Enable
    CREDIT:             Satish Jagtap
    PURPOSE:            Refresh list control with enabled startup list.
    PARAMETERS:         None
    RETURN VALUE:       None
    CALLS TO: -         1) CCommonUtilities::GetFullPathOfSelectedItem
                        2) CRegistryUtility::Query32BitAppValues
                        3) CRegistryUtility::Query64BitAppValues
                        4) FillStartUpList_Registry
                        5) ListStartupProgramsForSpecificUser
                        6) FillStartUpList_Folder
    CALLED FROM: -      1) OnTvnSelchangedTreeStartupEntries
                        2) OnBnClickedButtonAddnew
                        3) OnFileAddNewStartupEntry
                        4) OnBnClickedButtonRemove
                        5) OnEditRemoveEntry
                        6) OnEditEditEntry
                        7) DisableSelectedEntries
    Added date: -       1 Jan, 2016
   ===================================================================================*/
{
    HTREEITEM hitemSelected        = m_treectlStartupEntries.GetSelectedItem();
    CString strSelectedItemText = m_treectlStartupEntries.GetItemText(hitemSelected);
    CString strSelectedItemPath = m_objCommonUtilities.GetFullPathOfSelectedItem(m_treectlStartupEntries, hitemSelected);

    if(strSelectedItemPath.Find(_T("Enabled Entries")) != -1)
    {
        /***************************************************/
        //Enable/disable menu items
        bool bEnable = false;

        GetMenu()->EnableMenuItem(ID_EDIT_DISABLEENTRY, bEnable ? 0 : MF_ENABLED);  //Enable "Disable Entry" menu item
        GetMenu()->EnableMenuItem(ID_EDIT_ENABLEENTRY, bEnable ? 0 : MF_DISABLED | MF_GRAYED);  //Disable "Enable Entry" menu item
        GetMenu()->EnableMenuItem(ID_EDIT_REMOVEENTRY, bEnable ? 0 : MF_ENABLED);  //Enable "Remove Entry" menu item
        GetMenu()->EnableMenuItem(ID_EDIT_EDITENTRY, bEnable ? 0 : MF_ENABLED); //Enable "Edit Entry" menu item
        m_ctlRemoveButton.EnableWindow(true);  //Enable "Remove" button
        m_ctlAddNewButton.EnableWindow(true); //Enable "Add New" button
        /***************************************************/

        if(strSelectedItemPath.Find(_T("Registry")) != -1)
        {
            m_lstctrlStartupDetailsFolder.ShowWindow(SW_HIDE);
            m_lstctrlStartupDetailsRegistry.ShowWindow(SW_SHOW);

            if(strSelectedItemPath.Find(_T("CurrentUser")) != -1) //Retrieve Current User entries
            {
                if(strSelectedItemPath.Find(_T("Run\\")) != -1)
                {
                    if(strSelectedItemText == _T("32Bit"))
                    {
                        vector<pair<CString, CString>> vec32BitAppValueData;

                        m_objRegistryUtility.Query32BitAppValues(HKEY_CURRENT_USER, _T("SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run"), vec32BitAppValueData);
                        //Fill list control with start up entries details
                        FillStartUpList_Registry(vec32BitAppValueData, 
                                                    _T("HKEY_CURRENT_USER"),
                                                    _T("Run"),
                                                    _T("Enabled"),
                                                    _T("SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run"));
                    }
                    else if(strSelectedItemText == _T("64Bit"))
                    {
                        vector<pair<CString, CString>> vec64BitAppValueData;

                        m_objRegistryUtility.Query64BitAppValues(HKEY_CURRENT_USER, _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"), vec64BitAppValueData);
                        //Fill list control with start up entries details
                        FillStartUpList_Registry(vec64BitAppValueData, 
                                                    _T("HKEY_CURRENT_USER"),
                                                    _T("Run"),
                                                    _T("Enabled"),
                                                    _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"));
                    }
                }
                if(strSelectedItemPath.Find(_T("RunOnce\\")) != -1)
                {
                    if(strSelectedItemText == _T("32Bit"))
                    {
                        vector<pair<CString, CString>> vec32BitAppValueData;

                        m_objRegistryUtility.Query32BitAppValues(HKEY_CURRENT_USER, _T("SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce"), vec32BitAppValueData);
                        //Fill list control with start up entries details
                        FillStartUpList_Registry(vec32BitAppValueData, 
                                                    _T("HKEY_CURRENT_USER"),
                                                    _T("Run"),
                                                    _T("Enabled"),
                                                    _T("SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce"));
                    }
                    else if(strSelectedItemText == _T("64Bit"))
                    {
                        vector<pair<CString, CString>> vec64BitAppValueData;

                        m_objRegistryUtility.Query64BitAppValues(HKEY_CURRENT_USER, _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce"), vec64BitAppValueData);
                        //Fill list control with start up entries details
                        FillStartUpList_Registry(vec64BitAppValueData, 
                                                    _T("HKEY_CURRENT_USER"),
                                                    _T("Run"),
                                                    _T("Enabled"),
                                                    _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce"));
                    }
                }
            }
            else if(strSelectedItemPath.Find(_T("LocalUser")) != -1)  //Retrieve Local User entries
            {
                if(strSelectedItemPath.Find(_T("Run\\")) != -1)
                {
                    if(strSelectedItemText == _T("32Bit"))
                    {
                        vector<pair<CString, CString>> vec32BitAppValueData;

                        m_objRegistryUtility.Query32BitAppValues(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run"), vec32BitAppValueData);
                        //Fill list control with start up entries details
                        FillStartUpList_Registry(vec32BitAppValueData, 
                                                    _T("HKEY_LOCAL_MACHINE"),
                                                    _T("Run"),
                                                    _T("Enabled"),
                                                    _T("SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run"));
                    }
                    else if(strSelectedItemText == _T("64Bit"))
                    {
                        vector<pair<CString, CString>> vec64BitAppValueData;

                        m_objRegistryUtility.Query64BitAppValues(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"), vec64BitAppValueData);
                        //Fill list control with start up entries details
                        FillStartUpList_Registry(vec64BitAppValueData, 
                                                    _T("HKEY_LOCAL_MACHINE"),
                                                    _T("Run"),
                                                    _T("Enabled"),
                                                    _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"));
                    }
                }
                if(strSelectedItemPath.Find(_T("RunOnce\\")) != -1)
                {
                    if(strSelectedItemText == _T("32Bit"))
                    {
                        vector<pair<CString, CString>> vec32BitAppValueData;

                        m_objRegistryUtility.Query32BitAppValues(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce"), vec32BitAppValueData);
                        //Fill list control with start up entries details
                        FillStartUpList_Registry(vec32BitAppValueData, 
                            _T("HKEY_LOCAL_MACHINE"),
                            _T("Run"),
                            _T("Enabled"),
                            _T("SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce"));
                    }
                    else if(strSelectedItemText == _T("64Bit"))
                    {
                        vector<pair<CString, CString>> vec64BitAppValueData;

                        m_objRegistryUtility.Query64BitAppValues(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce"), vec64BitAppValueData);
                        //Fill list control with start up entries details
                        FillStartUpList_Registry(vec64BitAppValueData, 
                                                    _T("HKEY_LOCAL_MACHINE"),
                                                    _T("Run"),
                                                    _T("Enabled"),
                                                    _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce"));
                    }
                }
            }
        }
        else if(strSelectedItemPath.Find(_T("Startup Folders")) != -1)
        {
            m_lstctrlStartupDetailsFolder.ShowWindow(SW_SHOW);
            m_lstctrlStartupDetailsRegistry.ShowWindow(SW_HIDE);

            if(strSelectedItemText == _T("All Users"))
            {
                vector<CString> vecListOfStartupPrograms;

                ListStartupProgramsForSpecificUser(CSIDL_COMMON_APPDATA, vecListOfStartupPrograms); //Retrieve startup programs from all user's app data
                FillStartUpList_Folder(vecListOfStartupPrograms); //populate list control
            }
            else if(strSelectedItemText == _T("Current User"))
            {
                vector<CString> vecListOfStartupPrograms;

                ListStartupProgramsForSpecificUser(CSIDL_APPDATA, vecListOfStartupPrograms); //Retrieve startup programs from current user's app data
                FillStartUpList_Folder(vecListOfStartupPrograms); //populate list control
            }
        }
    }
}
  1. 禁用注册表中选定的启动项。
void CStartupDlg::DisableSelectedEntries()
    /* ===================================================================================
    NAME OF FUNCTION:   DisableSelectedEntries
    CREDIT:             Satish Jagtap
    PURPOSE:            Disable selected registry entries.
    PARAMETERS:         None
    RETURN VALUE:       None
    CALLS TO: -         1) CCommonUtilities::GetSelectedItemsFromList
                        2) CCommonUtilities::SplitString
                        3) CINIUtility::WriteToINIFile
                        4) RemoveSelectedItemsFromList_Registry
                        5) RefreshStartupList_Enable
    CALLED FROM: -      1) OnEditDisableEntry
    Added date: -       1 Jan, 2016
   ===================================================================================*/
{
    vector<CString> vecListSelectedItems;
    TCHAR szPath[MAX_PATH]        = {0};
    CString strDirectory        = _T("");
    CString strDisabledEntries    = _T("");

    if(SUCCEEDED(SHGetSpecialFolderPath(NULL, szPath, CSIDL_APPDATA, FALSE)))
    {
        strDirectory = szPath;

        strDirectory.Replace(_T("\\"), _T("/"));
        strDirectory += _T("/VedStartupManager/");

        if(!PathIsDirectory(strDirectory)) CreateDirectory(strDirectory, NULL);
    }

    if(m_lstctrlStartupDetailsRegistry.IsWindowVisible())
    {
        m_objCommonUtilities.GetSelectedItemsFromList(m_lstctrlStartupDetailsRegistry, vecListSelectedItems);

        if(vecListSelectedItems.size() > 0)
        {
            int nSelectedItems    = vecListSelectedItems.size();
            CString strFilePath = _T("");
            
            for(int nIndex = 0; nIndex < nSelectedItems; nIndex++)
            {
                CStringArray saTokens;
                CString strValueName = _T("");
                CString strData = _T("");
                CString strRoot = _T("");
                CString strSubKey = _T("");

                m_objCommonUtilities.SplitString(vecListSelectedItems[nIndex], _T(","), saTokens);

                CString strAppName            = _T("");
                CString strKeyName            = _T("");
                CString strStringToWrite    = _T("");
                CString strINIFile            = strDirectory + _T("DisabledEntries_Reg.ini");

                strAppName = saTokens[2].Trim() + _T("\\") + saTokens[5].Trim();
                strKeyName = saTokens[0].Trim();
                strStringToWrite = saTokens[1].Trim();

                m_objINIUtility.WriteToINIFile(strAppName, strKeyName, strStringToWrite, strINIFile);
            }

            RemoveSelectedItemsFromList_Registry(vecListSelectedItems, strDisabledEntries);
            RefreshStartupList_Enable();

            MessageBox(_T("Selected items are disabled file successfully."), _T("Startup Manager"), MB_OK);
        }
        else
        {
            MessageBox(_T("You have selected no entry/entries to disable."), _T("Startup Manager"), MB_OK);
        }
    }
}
  1. 启用选定的启动项。
void CStartupDlg::EnableSelectedEntries()
    /* ===================================================================================
    NAME OF FUNCTION:   EnableSelectedEntries
    CREDIT:             Satish Jagtap
    PURPOSE:            Enable selected disabled registry entries.
    PARAMETERS:         None
    RETURN VALUE:       None
    CALLS TO: -         1) CCommonUtilities::GetSelectedItemsFromList
                        2) CCommonUtilities::SplitString
                        3) CINIUtility::ReadWholeSectionDataFromINI
                        4) CRegistryUtility::SetApplicationOnStartup
                        5) RefreshStartupList_Disable
    CALLED FROM: -      1) OnEditEnableEntry
    Added date: -       1 Jan, 2016
   ===================================================================================*/
{
    vector<CString> vecListSelectedItems;
    TCHAR szPath[MAX_PATH];
    CString strDirectory = _T("");

    if(SUCCEEDED(SHGetSpecialFolderPath(NULL, szPath, CSIDL_APPDATA, FALSE)))
    {
        strDirectory = szPath;
        strDirectory.Replace(_T("\\"), _T("/"));
        strDirectory += _T("/VedStartupManager/");
    }

    if(m_lstctrlStartupDetailsRegistry.IsWindowVisible())
    {
        m_objCommonUtilities.GetSelectedItemsFromList(m_lstctrlStartupDetailsRegistry, vecListSelectedItems);

        if(vecListSelectedItems.size() > 0)
        {
            int nSelectedItems    = vecListSelectedItems.size();
            CString strFilePath = _T("");
            
            for(int nIndex = 0; nIndex < nSelectedItems; nIndex++)
            {
                CStringArray saTokens;
                CString strAppName            = _T("");
                CString strKeyName            = _T("");
                CString strStringToWrite    = _T("");
                CString strRoot                = _T("");
                CString strSubKey            = _T("");
                CString strINIFile            = strDirectory + _T("DisabledEntries_Reg.ini");

                m_objCommonUtilities.SplitString(vecListSelectedItems[nIndex], _T(","), saTokens);

                strRoot       = saTokens[2].Trim();
                strAppName = saTokens[2].Trim() + _T("\\") + saTokens[5].Trim();
                strKeyName = saTokens[0].Trim();
                strSubKey  = saTokens[5].Trim();

                vector<pair<CString, CString>> vecWholeSectionData;

                m_objINIUtility.ReadWholeSectionDataFromINI(strINIFile, strAppName, vecWholeSectionData);

                HKEY hRoot;
                CString strApplicationPath;

                if(strRoot.CompareNoCase(_T("HKEY_LOCAL_MACHINE")) == 0)
                    hRoot = HKEY_LOCAL_MACHINE;
                else if(strRoot.CompareNoCase(_T("HKEY_CURRENT_USER")) == 0) 
                    hRoot = HKEY_CURRENT_USER;

                m_objRegistryUtility.SetApplicationOnStartup(hRoot, strSubKey, saTokens[0], saTokens[1]);
                WritePrivateProfileString(strAppName, saTokens[0], NULL, strINIFile);

                RefreshStartupList_Disable();
            }
        }
        else
        {
            MessageBox(_T("You have selected no entry/entries to disable."), _T("Startup Manager"), MB_OK);
        }
    }
}

注意:要查看更多功能,请参阅本文随附的源代码。

使用应用程序

此应用程序可帮助您高效地管理Windows启动项。您可以轻松管理32位和64位启动项。您可以使用此实用程序对启动项执行以下操作:

  1.     添加新的启动项:将新的启动项添加到注册表。
  2.     启用条目:启用已禁用条目。
  3.     禁用条目:禁用注册表启动项。
  4.     删除条目:从注册表中删除启动项。
  5.     编辑条目:编辑现有启动项。
  6.     执行条目:执行启动项。
  7.     导出到文本文件:将启动项的详细信息导出到文本文件。
  8.     打开条目位置:打开启动项的位置。

关注点

在本文中,为了禁用注册表中的启动项,我使用INI文件作为中间源来存储禁用项。禁用项显示在树控件的不同分支下,以便于访问。在此应用程序中,我为32位和64位启动项创建了单独的分支。

备注

  1. 此实用程序仅限于“RUN”和“RUNONCE”子项。
  2. 在此实用程序中,我没有提供禁用“启动文件夹”中的启动项的功能。
© . All rights reserved.