自动维护

平台

客户端 - Windows 8
服务器 - Windows Server 2012

说明

Windows 依赖于执行收件箱和第三方维护活动来增加大部分价值,包括Windows 更新、自动磁盘碎片整理以及防病毒更新和扫描。 此外,企业经常使用网络访问保护 (NAP) 扫描等维护活动来帮助在所有企业工作站上强制实施安全标准。

Windows 中的维护活动设计为在后台运行,用户交互有限,对性能和能源效率的影响最小。 但是,在 Windows 7 及更早版本中,由于 Windows 中多个维护活动的计划不确定且差异很大,性能和能源效率仍然受到影响。 当用户主动使用计算机时,当维护活动运行时,对用户的响应能力会降低。 应用还经常要求用户更新其软件并运行后台维护,并将用户定向到多个体验,包括操作中心、控制面板、Windows 更新、任务计划程序 MMC 管理单元和第三方控件。

自动维护的目标是合并 Windows 中的所有后台维护活动,并帮助第三方开发人员将其维护活动添加到 Windows,而不会对性能和能源效率产生负面影响。 此外,自动维护使用户和企业能够控制维护活动的计划和配置。

关键问题

自动维护旨在解决 Windows 中维护活动的以下问题:

  • 截止时间计划
  • 资源利用率冲突
  • 节能
  • 用户的透明度

功能

自动维护可提高空闲效率,并允许所有活动及时按优先级运行。 它还有助于实现对维护活动的统一可见性和控制,并允许第三方开发人员将其维护活动添加到 Windows,而不会对性能和能源效率产生负面影响。 为此,它提供完全自动模式、用户启动模式、自动停止、截止时间和通知以及企业控制。 下面分别介绍了这些内容。

完全自动模式

此默认模式支持在电脑空闲时间和计划时间进行智能计划 -- 无需任何用户干预即可执行和自动暂停维护活动。 用户可以设置每周或每日计划。 所有维护活动都是非交互式的,并且以无提示方式执行。

当系统不太可能处于使用状态时,计算机会自动从睡眠状态恢复,并遵循电源管理策略(在笔记本电脑的情况下,该策略默认仅在使用交流电源时允许唤醒)。 大功率的完整系统资源用于尽快完成维护活动。 如果系统已从睡眠状态恢复为自动维护,则请求它返回到睡眠状态。

与活动(例如配置)相关的任何必需的用户交互都在自动维护执行之外执行。

用户发起的模式

如果用户需要为旅行做好准备,希望长时间使用电池电量,或者希望优化性能和响应能力,可以选择按需启动自动维护。 用户可以配置自动维护属性,包括自动运行计划。 他们可以查看自动维护执行的当前状态,并可以根据需要停止自动维护。

自动停止

如果用户开始与计算机交互,自动维护会自动停止当前运行的维护活动。 当系统恢复为空闲状态时,维护活动将恢复。

注意

自动维护中的所有活动都必须支持在 2 秒或更短时间内停止。 应通知用户活动已停止。

 

截止时间和通知

关键维护活动必须在预定义的时间范围内执行。 如果关键任务无法在指定时间内运行,则自动维护将在下一个可用的系统空闲机会时自动开始执行。 但是,如果任务状态仍然落后于截止时间,则自动维护将通知用户有关活动的信息,并提供手动运行自动维护的选项。 计划维护的所有任务都将运行,但最饥饿的任务优先。 此活动可能会影响系统响应能力和性能;因此,自动维护将通知用户关键维护活动正在执行。

企业控制

企业 IT 专业人员应能够确定自动维护何时在其 Windows 系统上执行,通过标准化管理界面强制实施该计划,并检索有关自动维护执行尝试状态的事件数据。 此外,IT 专业人员应能够通过标准管理界面远程调用特定的自动维护活动。 每次执行自动维护时,都会运行状态报告,包括由于用户手动暂停活动而无法执行自动维护时的通知。 IT 专业人员应考虑将登录脚本移动到自动维护,以帮助加快用户的登录体验。

创建自动维护任务

本部分详细介绍开发人员如何使用 XML 或 C 语言的任务定义创建任务。 请记住,维护活动不应启动任何需要用户交互的用户界面,因为自动维护是完全无提示的,并在用户不在时运行。 事实上,如果用户在自动维护期间与计算机交互,则进程中的任何任务都将结束,直到下一个空闲期。

使用 XML

任务计划程序包括一个内置的命令行工具(schtasks.exe),该工具可以导入 XML 格式的任务定义。 任务定义的架构记录在 中 https://msdn.microsoft.com/library/aa383609(v=VS.85).aspx。 下面是 XML 中定义的自动维护任务的示例。

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Date>2011-07-01T11:34:31</Date>
    <Author>IT Deptartment</Author>
  </RegistrationInfo>
  <Principals>
    <Principal id="Author">
      <RunLevel>LeastPrivilege</RunLevel>
      <GroupId>NT AUTHORITY\SYSTEM</GroupId>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
    <AllowHardTerminate>true</AllowHardTerminate>
    <StartWhenAvailable>false</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
    <MaintenanceSettings>
      <Period>P2D</Period>
      <Deadline>P14D</Deadline>
    </MaintenanceSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>
    <UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>
    <WakeToRun>false</WakeToRun>
    <ExecutionTimeLimit>P3D</ExecutionTimeLimit>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>cmd</Command>
      <Arguments>/c timeout -t 60</Arguments>
    </Exec>
  </Actions>
</Task> 

若要在 Windows 计算机上保存任务,请将上述 XML 保存为文本文件,并使用此命令行:

Schtasks.exe /create /tn <task name> /xml <text file name>

使用 C

也可以使用 C 代码创建自动维护任务。 下面是可用于配置任务的自动维护设置的代码示例:

/********************************************************************
This sample creates a maintenance task to start cmd window during maintenance opportunities with periodicity of 2 days and deadline 0f 14 days.
********************************************************************/

#define _WIN32_DCOM

#include <windows.h>
#include <iostream>
#include <stdio.h>
#include <comdef.h>
#include <wincred.h>
//  Include the task header file.
#include <taskschd.h>
//#pragma comment(lib, "taskschd.lib")
//#pragma comment(lib, "comsupp.lib")

int __cdecl 
MainteanceTask( )
{
    //  ------------------------------------------------------
    //  Initialize COM.
    HRESULT hr;

    //  ------------------------------------------------------
    //  Create a name for the task.
    LPCWSTR wszTaskName = L"MaintenanceTask";

    ITaskService *pService = NULL;
    ITaskFolder *pRootFolder = NULL;
    ITaskDefinition *pTask = NULL;
    ITaskSettings *pSettings = NULL;
    IRegistrationInfo *pRegInfo= NULL;
    IPrincipal *pPrincipal = NULL;
    ITaskSettings3 *pSettings3 = NULL;
    IMaintenanceSettings* pMaintenanceSettings = NULL;
    IActionCollection *pActionCollection = NULL;
    IAction *pAction = NULL;
    IExecAction *pExecAction = NULL;
    IRegisteredTask *pRegisteredTask = NULL;

    wprintf(L"\nCreate Maintenance Task %ws", wszTaskName );

    hr = CoInitializeEx( NULL, COINIT_MULTITHREADED);
    if( FAILED(hr) )
    {
        wprintf(L"\nCoInitializeEx failed: %x", hr );
        return 1;
    }

    //  Set general COM security levels.
    hr = CoInitializeSecurity( NULL,
        -1,
        NULL,
        NULL,
        RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
        RPC_C_IMP_LEVEL_IMPERSONATE,
        NULL,
        0,
        NULL);

    if( FAILED(hr) )
    {
        wprintf(L"\nCoInitializeSecurity failed: %x", hr );
        goto CleanUp;
    }

    //  ------------------------------------------------------
    //  Create an instance of the Task Service. 
    hr = CoCreateInstance( CLSID_TaskScheduler,
                           NULL,
                           CLSCTX_INPROC_SERVER,
                           IID_ITaskService,
                           (void**)&pService );  
    if (FAILED(hr))
    {
        wprintf(L"\nFailed to create an instance of ITaskService: %x", hr);
        goto CleanUp;
    }
        
    //  Connect to the task service.
    hr = pService->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());
    if( FAILED(hr) )
    {
        wprintf(L"\nITaskService::Connect failed: %x", hr );
        goto CleanUp;
    }

    //  ------------------------------------------------------
    //  Get the pointer to the root task folder.  This folder will hold the
    //  new task that is registered.
    hr = pService->GetFolder( _bstr_t( L"\\") , &pRootFolder );
    if( FAILED(hr) )
    {
        wprintf(L"\nCannot get Root folder pointer: %x", hr );
        goto CleanUp;
    }
    
    //  If the same task exists, remove it.
    ( void ) pRootFolder->DeleteTask( _bstr_t(wszTaskName), 0  );
    
    //  Create the task definition object to create the task.
    hr = pService->NewTask( 0, &pTask );
    if (FAILED(hr))
    {
        wprintf(L"\nFailed to CoCreate an instance of the TaskService class: %x", hr);
        goto CleanUp;
    }
        
    //  ------------------------------------------------------
    //  Get the registration info for setting the identification.
    hr = pTask->get_RegistrationInfo( &pRegInfo );
    if( FAILED(hr) )
    {
        wprintf(L"\nCannot get identification pointer: %x", hr );
        goto CleanUp;
    }
    
    hr = pRegInfo->put_Author( _bstr_t(L"Author Name") );    
    if( FAILED(hr) )
    {
        wprintf(L"\nCannot put identification info: %x", hr );
        goto CleanUp;
    }

    // The task needs to grant explicit FRFX to LOCAL SERVICE (A;;FRFX;;;LS)
    hr = pRegInfo->put_SecurityDescriptor( _variant_t(L"D:P(A;;FA;;;BA)(A;;FA;;;SY)(A;;FRFX;;;LS)") );
    if( FAILED(hr) )
    {
        wprintf(L"\nCannot put security descriptor: %x", hr );
        goto CleanUp;
    }

    //  ------------------------------------------------------
    //  Create the principal for the task - these credentials
    //  are overwritten with the credentials passed to RegisterTaskDefinition
    hr = pTask->get_Principal( &pPrincipal );
    if( FAILED(hr) )
    {
        wprintf(L"\nCannot get principal pointer: %x", hr );
        goto CleanUp;
    }
    
    //  Set up principal logon type to interactive logon
    hr = pPrincipal->put_LogonType( TASK_LOGON_INTERACTIVE_TOKEN );
    if( FAILED(hr) )
    {
        wprintf(L"\nCannot put principal info: %x", hr );
        goto CleanUp;
    }  

    //  ------------------------------------------------------
    //  Create the settings for the task
    hr = pTask->get_Settings( &pSettings );
    if( FAILED(hr) )
    {
        wprintf(L"\nCannot get settings pointer: %x", hr );
        goto CleanUp;
    }

    hr = pSettings->QueryInterface( __uuidof(ITaskSettings3), (void**) &pSettings3 );
    if( FAILED(hr) )
    {
        wprintf(L"\nCannot query ITaskSettings3 interface: %x", hr );
        goto CleanUp;
    }

    hr = pSettings3->put_UseUnifiedSchedulingEngine( VARIANT_TRUE );
    if( FAILED(hr) )
    {
        wprintf(L"\nCannot put_UseUnifiedSchedulingEngine: %x", hr );
        goto CleanUp;
    }

    hr = pSettings3->CreateMaintenanceSettings( &pMaintenanceSettings );
    if( FAILED(hr) )
    {
        wprintf(L"\nCannot CreateMaintenanceSettings: %x", hr );
        goto CleanUp;
    }

    hr = pMaintenanceSettings->put_Period ( _bstr_t(L"P2D") );
    if( FAILED(hr) )
    {
        wprintf(L"\nCannot put_Period: %x", hr );
        goto CleanUp;
    }

    hr = pMaintenanceSettings->put_Deadline ( _bstr_t(L"P14D") );
    if( FAILED(hr) )
    {
        wprintf(L"\nCannot put_Period: %x", hr );
        goto CleanUp;
    }

    //  ------------------------------------------------------
    //  Add an action to the task. This task will execute cmd.exe.     
    //  Get the task action collection pointer.
    hr = pTask->get_Actions( &pActionCollection );
    if( FAILED(hr) )
    {
        wprintf(L"\nCannot get Task collection pointer: %x", hr );
        goto CleanUp;
    }
    
    //  Create the action, specifying that it is an executable action.
    hr = pActionCollection->Create( TASK_ACTION_EXEC, &pAction );
    if( FAILED(hr) )
    {
        wprintf(L"\nCannot create the action: %x", hr );
        goto CleanUp;
    }

    //  QI for the executable task pointer.
    hr = pAction->QueryInterface( IID_IExecAction, (void**) &pExecAction );
    if( FAILED(hr) )
    {
        wprintf(L"\nQueryInterface call failed for IExecAction: %x", hr );
        goto CleanUp;
    }

    //  Set the path of the executable to cmd.exe.
    hr = pExecAction->put_Path( _bstr_t(L"cmd") );
    if( FAILED(hr) )
    {
        wprintf(L"\nCannot put action path: %x", hr );
        goto CleanUp;
    }  
    
    //  ------------------------------------------------------
    //  Save the task in the root folder.
    hr = pRootFolder->RegisterTaskDefinition(
            _bstr_t(wszTaskName),
            pTask,
            TASK_CREATE_OR_UPDATE, 
            _variant_t(), 
            _variant_t(), 
            TASK_LOGON_INTERACTIVE_TOKEN,
            _variant_t(L""),
            &pRegisteredTask);
    if( FAILED(hr) )
    {
        wprintf(L"\nError saving the Task : %x", hr );
        goto CleanUp;
    }
    
    wprintf(L"\nSuccess!\n----------------------------------" );

CleanUp:

    if ( pService != NULL ) pService->Release();
    if ( pRootFolder != NULL ) pRootFolder->Release();
    if ( pTask != NULL ) pTask->Release();
    if ( pSettings != NULL ) pSettings->Release();
    if ( pRegInfo != NULL ) pRegInfo->Release();
    if ( pPrincipal != NULL ) pPrincipal->Release();
    if ( pSettings3 != NULL ) pSettings3->Release();
    if ( pMaintenanceSettings != NULL ) pMaintenanceSettings->Release();
    if ( pActionCollection != NULL ) pActionCollection->Release();
    if ( pAction != NULL ) pAction->Release();
    if ( pExecAction != NULL ) pExecAction->Release();
    if ( pRegisteredTask != NULL ) pRegisteredTask->Release();

    CoUninitialize();
    return SUCCEEDED ( hr ) ? 0 : 1;
}

验证任务

验证任务是否已成功创建并作为维护的一部分运行。

验证任务创建

使用此命令行将任务定义导出到文件,并确保任务定义符合预期:

Schtasks.exe /Query /tn<task name> /xml <text file name>

验证任务执行

运行此命令行以启动任务,并验证任务计划程序 UI (taskschd.msc) 是否显示任务已运行:

Schtasks.exe /Run /tn<task name>

资源