Yet Another Windows Update Automation

#目的
Windows Updateを完全自動化します。

#環境
OS        : Windows Vista 以降
PowerShell: Version 2 以降

#使用方法
ソース中に記載（~~英語版のみです。悪しからず。~~）

#備考
- Windows10のCreaters Updateなどの大型アップデートについては未検証です。
- Windows Updateが「更新プログラムを自動的にインストールする」設定になっている場合、競合が発生してエラーとなる場合があります。
- コンソールウィンドウの幅に合わせて更新のタイトルを切り詰めます。ウィンドウ幅をなるべく広げて実行してください。タイトルの切り詰めを無効にするにはFormatTitle関数の1行目のコメントをはずしてください。

#コード
PowerShellスクリプトですが、Windows バッチファイルとしても実行できる特殊な構造になっています。
(http://earthdiver.net/download/WUA.zip)


```PowerShell:WUA.bat
<# : Batch Commands (PowerShell Comments) Start
@echo off & setlocal
rem
rem  Windows Update Automation
rem
rem
rem   SYNOPSIS 1: Execute the following command from a console started as admin.
rem
rem       WUA.bat  [<password>] [<service_type>] [<feature_update>] [<important_updates_only>]
rem                [-ExcludeList/Exclude/EL <exclude_list *1>] 
rem                [-KBArticleIDs/KBs/IDs   <include_list *1>]
rem                [-NonInteractive/NI *2]
rem                                              *1 comma-delimited list of KBArticle IDs
rem                                              *2 switch for non-interactive mode
rem
rem                <service_type>  0:*default, 1:WSUS, 2:Windows Update, 3:Microsoft Update
rem              <feature_update>  0:*exclude, 1:include (service pack is also included.)
rem      <important_updates_only>  0:include preferred updates
rem                                1:*important updates only
rem
rem                                              * default for non-interactive mode
rem
rem                If no command arguments are specified, you will be prompted for
rem                <password> and <service_type>.
rem
rem       Examples (non-interactive mode)
rem                1.  WUA.bat -NI  <password>  3
rem                2.  WUA.bat -NI  @@@@@       3   (in case of credential information
rem                                                  being stored on disk)
rem
rem
rem
rem   SYNOPSIS 2: Right click the WUA.bat icon and select "Run as Administrator".
rem               You will be prompted for password, service_type, etc.
rem
rem
rem
rem   NOTES: -Administrator rights are required for execution of this script.
rem          -Your computer will be automatically restarted as needed.
rem          -A log file will be generated in the same folder as this script.
rem           The log file generation is suppressed if $LogDir is blank.
rem          -'SavedCredential.dat' will be generated in the same folder as this script
rem           in order to store the credential information for later use.
rem          -The first command argument (password) is required in non-interactive mode
rem           (unless a file containing the credential information has been generated).
rem           To specify the second argument (service_id) without explicitly specifying
rem           password, '@@@@@' may be used in place of the password.
rem          -If the same IDs are specified in both ExcludeList and KBArticleIDs,
rem           the corresponding updates are excluded.
rem          -KBArticleIDs may be input from pipe(stdin).
rem          -Files with the following names will be executed at the end if exists.
rem           '<basename of this script>_PP.XXX'
rem           where XXX is either one of BAT, PS1, or VBS.
rem
rem    Created by earthdiver1 (inspired by https://gist.github.com/ijprest/d55c3754650cd81df3a5)
rem    Version 1.17
rem    Licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
rem 
rem -------------------------------------------------------------------------------
rem The following is a preamble for converting a PowerShell script into polyglot
rem one that also runs as a batch script. Change the file extension to .ps1 and
rem run it from PowerShell console when debugging.
set BATCH_ARGS=%*
if defined BATCH_ARGS set BATCH_ARGS=%BATCH_ARGS:"=\"%
set P_CMD=$PSCP='%~f0';$input^|^&([ScriptBlock]::Create((${%~f0}^|Out-String)))
endlocal & Powershell -NoProfile -Command "%P_CMD%" %BATCH_ARGS%
exit/b
rem -------------------------------------------------------------------------------
: Batch Commands (PowerShell Comments) End #>
param (
    [Parameter(Position=0)]
    [String]$Password = "@@@@@",

    [Parameter(Position=1)]
    [Int]$Server      = -1,                      # the default value may be altered

    [Parameter(Position=2)]
    [Int]$SPack       = -1,                      # the default value may be altered

    [Parameter(Position=3)]
    [Int]$AutoSelect  = -1,                      # the default value may be altered

    [Alias("EL","Exclude")]
    [ValidateNotNullOrEmpty()]
    [ValidateLength(8,9)]
    [ValidatePattern("KB[0-9]+")]
    [String[]]$ExcludeList,                      # the default value may be specified

    [Alias("KBs","IDs")]
    [ValidateNotNullOrEmpty()]
    [ValidateLength(8,9)]
    [ValidatePattern("KB[0-9]+")]
    [String[]]$KBArticleIDs = @($input),

    [Alias("NI")]
    [Switch]$NonInteractive,

    [Switch]$Resumed,                            # for internal control
    [Alias("NL")]
    [Int]$NotificationLevel                      # for internal control
)

$script:PSCommandPath = $PSCP

$ThisScript = $MyInvocation.MyCommand.Path       # the path to this script (when invoked as .ps1 file)
if (-not $ThisScript) {
    $ThisScript = $script:PSCommandPath          # the path to this script (when invoked as scriptblock) 
}
$script:PSScriptRoot = Split-Path $ThisScript -Parent  # folder path in which this script is located

# uncomment the following line to prevent line breaks.
#$host.UI.RawUI.BufferSize = New-Object System.Management.Automation.Host.Size(512, $host.UI.RawUI.BufferSize.Height)

############### EDIT HERE ################

$InteractiveMode = 1                             # interactive mode flag  0:OFF  1:ON

#$LogDir = ""                                    # suppress log file generation
#$LogDir = $env:TEMP                             # %TEMP%
$LogDir = $script:PSScriptRoot                   # same folder as this script

#$LogFilename = "WUA_$((Get-Date).ToString('yyyyMMddHHmm')).log"  # log file name (with date-time) 
$LogFilename = "WUA.log"                         # log file name (fixed) 

$MaxUpdatesToInstall = 100                       # maximum number of updates to be processed at a time

$CreateRestorePoint = $False                     # switch for restore point creation
                                                 # (It seems like that a restore point is created automatically
                                                 #  when updates exist. So maybe this is for paranoia only.)
$MinimumRestorePointInterval = 60                # minimum interval for restore point creation [min]

##########################################

Function Main() {
    StartTranscript
    try {

        if ($KBArticleIDs)   { $KBArticleIDs = $KBArticleIDs | Foreach-Object { $_.ToUpper() } }
        if ($ExcludeList)    { $ExcludeList  = $ExcludeList  | Foreach-Object { $_.ToUpper() } }
        if ($NonInteractive) { $InteractiveMode = 0 }
        $RegAuditModeKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\State"
        if ((Get-ItemProperty -Path $RegAuditModeKey).ImageState -ne "IMAGE_STATE_COMPLETE") {
            Write-Host "Windows is in audit mode." -Fore Green
            $AuditMode = 1
        } else {
            $AuditMode = 0
        }

        if ($InteractiveMode) {
            Write-Host "Starting in interactive mode." -Fore Green
        } else {
            Write-Host "Starting in non-interactive mode."  -Fore Green
            Write-Host ""
            Write-Host "Command Arguments" -Fore Green
            Write-Host "  Password      : ``$Password``"   -Fore Green
            Write-Host "  Server        : $Server"         -Fore Green
            Write-Host "  SPack         : $SPack"          -Fore Green
            Write-Host "  AutoSelect    : $AutoSelect"     -Fore Green
            Write-Host "  ExcludeList   : $ExcludeList"    -Fore Green
            Write-Host "  KBArticleIDs  : $KBArticleIDs"   -Fore Green
            Write-Host "  NonInteractive: $NonInteractive" -Fore Green
            Write-Host ""
        }

        if (-not([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]"Administrator")) {
            if ($InteractiveMode) {
                Write-Host "Administrator privileges required to continue." -Fore Red
                Write-Host "Restarting by 'Run as Administrator' in a new console window." -Fore Red
                $RemainingArgs = ""
                if ($ExcludeList) {
                    $RemainingArgs += " -ExcludeList " + ($ExcludeList -Join ",")
                }
                if ($KBArticleIDs) {
                    $RemainingArgs += " -KBArticleIDs " + ($KBArticleIDs -Join ",")
                }
                $CmdArgs = "-NoProfile -Command `$PSCP='$ThisScript';&([ScriptBlock]::Create((Get-Content -LiteralPath `$PSCP|Out-String)))" `
                         + " '$Password' $Server $SPack $AutoSelect $RemainingArgs"
                Start-Process "powershell.exe" -Verb Runas -ArgumentList $CmdArgs
                $InteractiveMode = $False
            } else {
                Write-Host "Administrator privileges required. Exiting ..." -Fore Red
            }
            exit
        }

        [Int]$Answer = -1

        if ($InteractiveMode -and -not $Resumed) {
            Write-Host "Your computer may be restarted during the update session." -Fore Yellow
            Write-Host "Please save your work." -Fore Yellow
            Write-Host "Do you want to exit now?  0:Yes, 1:No [0]:" -NoNewline -Fore Yellow
            try { $Answer = Read-Host } catch [System.Exception] { $Answer = -1 }
            switch ($Answer) {
                0       { Write-Host "Exiting ..." -Fore Green ; exit }
                1       { } #Continuing the update session
                default { Write-Host "Invalid input detected. Exiting ..." -Fore Red ; exit }
            }
            Write-Host ""
            Write-Host "(Press Ctrl+C to exit." -Fore Green
            Write-Host " Press Ctrl+Break to abort." -NoNewline -Fore Green
            Write-Host "(deprecated)" -NoNewline -Fore Yellow
            Write-Host ") " -Fore Green
            Write-Host ""
        }

        $UserName = "$((Get-ChildItem Env:USERDOMAIN).Value)\$((Get-ChildItem Env:USERNAME).Value)"
        $CredentialFileOK = $False
        if (-not $AuditMode) {
            if ($Password -eq "@@@@@") {
                $SavedCredential = "$script:PSScriptRoot\SavedCredential.dat"
                if (Test-Path $SavedCredential) {
                    Write-Host "Validating the stored credential information." -Fore Green
                    try {
                        $SecurePassword = Get-Content $SavedCredential | ConvertTo-SecureString -ErrorAction SilentlyContinue
                        $Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $UserName,$SecurePassword
                        $Password = $Credential.GetNetworkCredential().Password
                    } catch [System.Exception] {}
                    if (CheckCredential) {
                        $CredentialFileOK = $True
                    } else {
                        Write-Host "Incorrect password in the stored credential information." -Fore Yellow
                        Write-Host "Deleting $($SavedCredential)." -Fore Yellow
                        try {
                            Remove-Item $SavedCredential -Force -ErrorAction SilentlyContinue
                        } catch [System.Exception] {
                            Write-Host "Failed to delete the file." -Fore Yellow
                            Write-Host $Error[0].Exception
                            Write-Host "Continuing the update session." -Fore Yellow
                        }
                    }
                }
                if (-not $CredentialFileOK) {
                    if (-not $InteractiveMode) {
                        Write-Host "Password not specified. Exiting ..." -Fore Red
                        Write-Host @"

  WUA.bat  [<password>] [<service_type>] [<feature_update>] [<important_updates_only>]
           [-ExcludeList/Exclude/EL <exclude_list>] 
           [-KBArticleIDs/KBs/IDs   <include_list>]
           [-NonInteractive/NI]

           The first argument (<password>) is mandatry in non-interactive mode
           unless the credential information has been stored.

"@
                        exit
                    }
                    $Credential = $Host.UI.PromptForCredential("Credential Request","Please enter your password.",$UserName,$Null)
                    if (-not $Credential) {
                        Write-Host "Password input cancelled. Exiting ..." -Fore Red
                        exit
                    }
                    $Password = $Credential.GetNetworkCredential().Password
                    if (-not (CheckCredential)) {
                        Write-Host "Password incorrect. Exiting ..." -Fore Red
                        exit
                    }
                    if ($Password -ne "") {
                        try {
                            ConvertFrom-SecureString $Credential.Password | Set-Content $SavedCredential
                            $CredentialFileOK = $True
                            Write-Host "Credential has been saved in $SavedCredential." -Fore Green
                        } catch [System.Exception] {
                            Write-Host "Failed to save credential in $SavedCredential." -Fore Yellow
                            Write-Host "Continuing the update session." -Fore Yellow
                        }
                    }
                }
            } else {
                if (-not (CheckCredential)) {
                    Write-Host "Password incorrect. Exiting ..." -Fore Red
                    exit
                }
            }
        }

        if ($Server -lt 0) {
            if (-not $InteractiveMode) {
                Write-Host "Service_type not specified. '0:default' will be used." -Fore Yellow
                $Server = 0
            } else {
                Write-Host "Please enter service_type." -Fore Green
                Write-Host " (0:default, 1:WSUS, 2:Windows Update, 3:Microsoft Update) [0]:" -NoNewline -Fore Green
                try { $Answer = Read-Host } catch [System.Exception] { $Answer = -1 }
                $Server = $Answer
            }
        }
        switch ($Server) {
            0 {}
            1 {}
            2 {}
            3 { $ServiceID="7971f918-a847-4430-9279-4a52d1efe18d" }
            default { Write-Host "Invalid value (service_type). Exiting ..." -Fore Red ; exit }
        }

        if ($SPack -lt 0) {
            if (-not $InteractiveMode) {
                Write-Host "feature_update switch not specified. '0:default(OFF)' will be used." -Fore Yellow
                $SPack = 0
            } else {
                Write-Host "Include Feature Update, Service Pack? 0:No, 1:Yes [0]:" -NoNewline -Fore Green
                try { $Answer = Read-Host } catch [System.Exception] { $Answer = -1 }
                $SPack = $Answer
            }
        }
        switch ($SPack) {
            0 {}
            1 {}
            default { Write-Host "Invalid value (feature_update). Exiting ..." -Fore Red ; exit }
        }

        if ($AutoSelect -lt 0) {
            if (-not $InteractiveMode) {
                Write-Host "Search only important updates? '1:ON(important updates only)' will be used." -Fore Yellow
                $AutoSelect = 1
            } else {
                Write-Host "Include preferred updates? (!= important updates) 0:No, 1:Yes [0]:" -NoNewline -Fore Green
                try { $Answer = Read-Host } catch [System.Exception] { $Answer = -1 }
                $AutoSelect = 1 - $Answer
            }
            switch ($AutoSelect) {
                0 { Write-Host "->All preferred updates will be included."  -Fore Green }
                1 { Write-Host "->Only important updates will be searched." -Fore Green }
            }
        }
        switch ($AutoSelect) {
            0       { $Criteria = "BrowseOnly=0 and IsInstalled=0 and IsHidden=0 and Type='Software'" }
            1       { $Criteria = "(IsAssigned=1 and IsInstalled=0 and IsHidden=0 and Type='Software') or " + `
                                  "(AutoSelectOnWebSites=1 and IsInstalled=0 and IsHidden=0 and Type='Software')" }
            default { Write-Host "Invalid value (important_updates_only). Exiting ..." -Fore Red ; exit }
        }

        if ($KBArticleIDs) { Write-Host "Include list:$($KBArticleIDs -Join ",")" -Fore Green }
        if ($ExcludeList)  { Write-Host "Exclude list:$($ExcludeList  -Join ",")" -Fore Green }

        if (-not $Resumed) {
            if ($CreateRestorePoint) { CreateRestorePoint }
            if ($Server -eq 3)       { EnableMicrosoftUpdate }
            $WUSettings = (New-Object -com "Microsoft.Update.AutoUpdate").Settings
            if ($WUSettings.NotificationLevel -gt 1) {
                try {
                    $tmp = $WUSettings.NotificationLevel
                    $WUSettings.NotificationLevel = 1
                    $WUSettings.Save()
                    Write-Host "Temporarily disabled OS standard Automatic Update." -Fore Yellow
                    $orgNotificationLevel = $tmp
                } catch [System.Exception] {
                    Write-Host "Unable to disable OS standard Automatic Update. Continuing ..." -Fore Yellow
                }
            }
            if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired") {
                Reboot_and_Rerun $SPack
            }
        } else {             # after reboot
            Write-Host ""
            DisableAutoLogon
            RemoveScheduledTask
            if ($NotificationLevel) { $orgNotificationLevel = $NotificationLevel }
            Write-Host "Resuming the previous Windows Update session ..." -Fore Green
            if ($InteractiveMode) { timeout 30 }
        }

        # Search for updates -> download -> apply
        $script:ULNotified = $False
        if ($SPack) {
            $Result = InstallUpdates "Feature Update, Service Packs"
        }
        $Result = InstallUpdates "Update Rollups, Quality Rollups"
        $Result = InstallUpdates "Other Updates"
        while ($Result) { 
            $Result = InstallUpdates "Remaining Updates"
        }
        Write-Host ""
        Write-Host "Terminating the update session." -Fore Green
        if ($script:Incomplete) {
            Write-Host "Unapplied updates exist." -Fore Red
            PostProcess "succeeded with errors"
        } else {
            PostProcess "succeeded"
        }
        Write-Host ""

        if ([System.Environment]::OSVersion.Version.Major -eq 10) { 
            try { usoclient.exe startscan } catch [System.Exception] {}
        }

    } catch [System.Exception] {

        Write-Host "System error occurred." -Fore Red
        Write-Host $Error[0].ToString() $Error[0].InvocationInfo.PositionMessage
        PostProcess "failed"

    } finally {

        if ($orgNotificationLevel) {
            Write-Host "Reverting the OS standard Automatic Update setting.`n" -Fore Yellow
            $WUSettings = (New-Object -com "Microsoft.Update.AutoUpdate").Settings
            $WUSettings.NotificationLevel = $orgNotificationLevel
            $WUSettings.Save()
        }
        StopTranscript
        if ($InteractiveMode) { Pause }
    }
}

Function StartTranscript() {
    if (-not $LogDir) { return }
    Write-Host "Creating a logfile in $LogDir." -Fore Green
    try {
        Start-TranScript "$LogDir\$LogFilename" -Append
    } catch [System.Exception] {
        Write-Host "Failed to open a log file." -Fore Red
        Write-Host $Error[0].Exception
        Write-Host "Destination folder changed to $env:TEMP." -Fore Yellow
        $LogDir = $env:TEMP
        try {
            Start-TranScript "$LogDir\$LogFilename" -Append
        } catch [System.Exception] {
            Write-Host "Failed to open a log file." -Fore Red
            Write-Host $Error[0].Exception
            Write-Host "Exiting ..." -Fore Red
            exit
        }
    }
}

Function CheckCredential() {
    $Tmp = $UserName.Split("\")
    $Domain = $Tmp[0]
    $User = $Tmp[1]
    Add-Type -AssemblyName System.DirectoryServices.AccountManagement 
    if ($Domain -ne $env:COMPUTERNAME) {  #domain user
        $CT = [System.DirectoryServices.AccountManagement.ContextType]::Domain
    } else {                              #local user
        $CT = [System.DirectoryServices.AccountManagement.ContextType]::Machine
    }
    $PC = New-Object System.DirectoryServices.AccountManagement.PrincipalContext $CT,$Domain
    return $PC.ValidateCredentials($User,$Password)
}

Function CreateRestorePoint() {
    $RegSPP = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SPP\Clients"
    if (-not ((Get-ItemProperty $RegSPP)."{09F7EDC5-294E-4180-AF6A-FB0E6A0E9513}")) { return }
    $LastCP = (@(Get-ComputerRestorePoint))[-1]
    if ($LastCP) { $LastCreationTime = $LastCP.ConvertToDateTime($LastCP.CreationTime) }
    if ((Get-Date) -lt ($LastCreationTime).AddMinutes($MinimumRestorePointInterval)) {
        Write-Host "Skipping a Restore Point creation because less than $($MinimumRestorePointInterval) minutes have passed since the last time." -Fore Green
    } else {
        Write-Host "Creating a Restore Point ..." -Fore Green
        CheckPoint-Computer -Description "Automatic Windows Update" -RestorePointType MODIFY_SETTINGS `
                            -ErrorAction SilentlyContinue
    }
}

Function EnableMicrosoftUpdate () {
    $MU = New-Object -ComObject Microsoft.Update.ServiceManager -Strict
    $ServiceExists = $False
    foreach ($Service in $MU.Services) {
       if ($Service.ServiceID -eq $ServiceID) { $ServiceExists = $True }
    }
    if (-not $ServiceExists) {
        Write-Host "Opting in Microsoft Update service." -Fore Green
        try {
            $Result = $MU.AddService2("7971f918-a847-4430-9279-4a52d1efe18d",2,"")
        } catch [System.Exception] {
            Write-Host "Failed to opt in Microsoft Update service." -Fore Red
            Write-Host "Exiting ..." -Fore Red
            exit
        }
    }
}

Function Reboot_and_Rerun($SP) {
    Write-Host "Reboot required. Your computer will now be restarted." -Fore Red
    if ($InteractiveMode) {
        Write-Host "Waiting 30 seconds. Press any key to continue." -Fore Green 
        timeout 30 | Out-Null
    }
    EnableAutoLogon
    AddScheduledTask $SP
    Start-Sleep -s 3
    Restart-Computer -Force
    Stop-Process $PID
}

Function EnableAutoLogon() {
    if ($AuditMode) { return }
    $RegLogonKey = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" 
    if ((Get-ItemProperty -Path $RegLogonKey).AutoAdminLogon -eq 1) { return }
    Write-Host "Enabling automatic logon." -Fore Yellow
    $Result = New-ItemProperty -Path $RegLogonKey -name AutoAdminLogon  -value 1         -Force
    $Result = New-ItemProperty -Path $RegLogonKey -name ForceAutoLogon  -value 1         -Force # workaround for Win10's "Sign-in" button
    $Result = New-ItemProperty -Path $RegLogonKey -name DefaultUserName -value $UserName -Force
    $Result = New-ItemProperty -Path $RegLogonKey -name DefaultPassword -value $Password -Force
}

Function AddScheduledTask($SP) {
    if (CheckTask) { return }
    Write-Host "Enabling automatic restart of the update session." -Fore Yellow
    $RemainingArgs = "-R"
    if ($ExcludeList) {
        $RemainingArgs += " -EL "  + ($ExcludeList -Join ",")
    }
    if ($KBArticleIDs) {
        $RemainingArgs += " -KBs " + ($KBArticleIDs -Join ",")
    }
    if (-not $InteractiveMode) {
        $RemainingArgs += " -NI"
    }
    if ($CredentialFileOK) { $Password = "@@@@@" }
    if ($orgNotificationLevel) {
        $RemainingArgs += " -NL $orgNotificationLevel"
    }
    $Command = "PowerShell.exe -NoP -C `$PSCP='''$ThisScript''';&([ScriptBlock]::Create((gc -Li `$PSCP|Out-String)))" `
             + " '''$Password''' $Server $SPack $AutoSelect $RemainingArgs"
    $Result = schtasks /create /tn Windows-Update-Script /ru $UserName /sc ONLOGON /tr $Command /it /f /rl HIGHEST 2>&1
    if (-not $?) { throw $Result }
}

Function CheckTask() {
    schtasks /query /tn Windows-Update-Script 2>&1 | Out-Null
    return $?
}

Function DisableAutoLogon() {
    if ($AuditMode) { return }
    $RegLogonKey = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" 
    if (-not ((Get-ItemProperty -Path $RegLogonKey).AutoAdminLogon)) { return }
    Write-Host "Disabling automatic logon." -Fore Yellow
    Remove-ItemProperty -Path $RegLogonKey -name AutoAdminLogon  -ErrorAction SilentlyContinue
    Remove-ItemProperty -Path $RegLogonKey -name ForceAutoLogon  -ErrorAction SilentlyContinue
    Remove-ItemProperty -Path $RegLogonKey -name DefaultUserName -ErrorAction SilentlyContinue
    Remove-ItemProperty -Path $RegLogonKey -name DefaultPassword -ErrorAction SilentlyContinue
}

Function RemoveScheduledTask() {
    if (-not (CheckTask)) { return }
    Write-Host "Disabling automatic restart of the update session." -Fore Yellow
    $Result = schtasks /delete /tn Windows-Update-Script /f 2>&1
    if (-not $?) { throw $Result }
}

Function InstallUpdates($Type) {
    $Msg = @( "not started",         `
              "in progress",         `
              "succeeded",           `
              "succeededwitherrors", `
              "failed",              `
              "aborted"              `
            )
    $script:Incomplete = $False
    # Search for updates
    Write-Host ""
    Write-Host "Searching for updates. : $Type" -Fore Green
    $UpdateSession = New-Object -ComObject Microsoft.Update.Session
    if (Get-Variable SearchResult -Scope script -ErrorAction SilentlyContinue) {
        $SearchResult = $script:SearchResult
    } else {
        $UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
        $UpdateSearcher.ServerSelection = $Server
        if ($Server -eq 3) { $UpdateSearcher.ServiceID = $ServiceID }
        $SearchResult = $UpdateSearcher.Search($Criteria)
        $script:SearchResult = $SearchResult
    }
    if ($Type -Match "Feature Update") {
        [array]$Updates = $SearchResult.Updates | Where-Object {
                         
                              $_.Title -Match "Feature Update"     -or `
                              $_.Title -Match "機能更新プログラム" -or `
                              $_.Categories -Contains "68c5b0a3-d1a6-4553-ae49-01d3a7827828"
                          }
    } elseif ($Type -Match "Rollups") {
        [array]$Updates = $SearchResult.Updates | Where-Object {
                              $_.Title -Match "Quality Rollup"     -or `
                              $_.Title -Match "品質ロールアップ"   -or `
                              $_.Categories -Contains "28bc880e-0592-4cbf-8f95-c79b17911d5f"
                          }
    } else {
        [array]$Updates = $SearchResult.Updates | Where-Object {
                              $_.Title -NotMatch "Feature Update"     -and `
                              $_.Title -NotMatch "機能更新プログラム" -and `
                              $_.Categories -NotContains "68c5b0a3-d1a6-4553-ae49-01d3a7827828"
                          }
    }
    if ($KBArticleIDs) {
        $Updates = $Updates | Where-Object { $KBArticleIDs -Contains "KB$($_.KBArticleIDs)" }
    }
    if (-not $Updates) { $Updates = @() } 
    Write-Host "  $($Updates.Count) update(s) found." -Fore Green
    $NumDownload = 0
    $NumInstall = 0
    if ($Updates.Count) {
        # Download updates
        foreach ($Update in $Updates) {
            if ($ExcludeList -Contains "KB$($Update.KBArticleIDs)") {
                Write-Host "  - "                          -NoNewline -Fore Green
                Write-Host $(FormatTitle $Update.Title 23) -NoNewline
                Write-Host " has been excluded."                      -Fore Green
                Continue
            }
            if ($Update.IsDownloaded) { 
                Write-Host "  - "                          -NoNewline -Fore Green
                Write-Host $(FormatTitle $Update.Title 25) -NoNewline
                Write-Host " has been downloaded."                    -Fore Green
                $NumDownload++
            } else {
                Write-Host "  - Downloading "              -NoNewline -Fore Green
                Write-Host $(FormatTitle $Update.Title 17) -NoNewline
                Write-Host "."                                        -Fore Green
                $UpdatesToDownload = New-Object -ComObject Microsoft.Update.UpdateColl
                $Result = $UpdatesToDownload.Add($Update)
                $Downloader = $UpdateSession.CreateUpdateDownloader()
                $Downloader.Updates = $UpdatesToDownload
                $Result = $Downloader.Download()
                if ($Result.ResultCode -eq 2) {
                    $NumDownload++
                } else {
                    $Result = $Downloader.Download()
                    if ($Result.ResultCode -eq 2) {
                        $NumDownload++
                    } else {
                        Write-Host "    Failed to download "       -NoNewline -Fore Red
                        Write-Host $(FormatTitle $Update.Title 24) -NoNewline
                        Write-Host "."                                        -Fore Red
                        $script:Incomplete = $True
                    }
                }
            }
            if ($NumDownload -ge $MaxUpdatesToInstall) { 
                if (-not $script:ULNotified -and $Updates.Count -gt $MaxUpdatesToInstall) {
                    $script:ULNotified = $True
                    Write-Host "  Maximum number of updates to be processed at a time is $($MaxUpdatesToInstall)." -Fore Yellow
                    Write-Host "  The limit has been reached. NOTE:This message will not be shown again." -Fore Yellow
                }
                exit 
            }
        }
        # Apply updates
        if ($NumDownload) {
            Write-Host "  ------------------------------------------------" -Fore Green
            Write-Host "  Applying $NumDownload update(s) ..." -Fore Green
            $UpdatesToInstall = New-Object -Com Microsoft.Update.UpdateColl
            foreach ($Update in $Updates) {
                if ($Update.IsDownloaded) {
                    if ($Update.InstallationBehavior.CanRequestUserInput) {
                        Write-Host "  - "                                     -NoNewline -Fore Red
                        Write-Host $(FormatTitle $Update.Title 44)            -NoNewline
                        Write-Host " *Skipping because user input required.*"            -Fore Red
                        $script:Incomplete = $True
                    } else {
                        if ($Update.EulaAccepted) {
                            Write-Host "  - "                                 -NoNewline -Fore Green
                            Write-Host $(FormatTitle $Update.Title 4)
                        } else {
                            Write-Host "  - "                                 -NoNewline -Fore Yellow
                            Write-Host $(FormatTitle $Update.Title 22)        -NoNewline
                            Write-Host " *Accepting EULA.*"                              -Fore Yellow
                            $Update.AcceptEULA()
                        }
                        $Result = $UpdatesToInstall.Add($Update)
                    }
                }
            }
            if ($UpdatesToInstall.Count) {
                Write-Host "  ------------------------------------------------" -Fore Green
                $Installer = $UpdateSession.CreateUpdateInstaller()
                $Installer.Updates = $UpdatesToInstall
                $Result = $Installer.Install()
                Write-Host "  Application result(s) of $($UpdatesToInstall.Count) update(s)" -Fore Green
                for ($i = 0; $i -lt $UpdatesToInstall.Count; $i++) {
                    Write-Host "  - Result of " -NoNewline -Fore Green
                    Write-Host $(FormatTitle $UpdatesToInstall.Item($i).Title 28) -NoNewline
                    Write-Host ": "             -NoNewline -Fore Green
                    if ($Result.GetUpdateResult($i).ResultCode -eq 2) {
                        Write-Host ("$($Result.GetUpdateResult($i).ResultCode):" + `
                                    $Msg[$Result.GetUpdateResult($i).ResultCode]) -Fore Blue
                        $NumInstall++ 
                    } else {
                        Write-Host ("$($Result.GetUpdateResult($i).ResultCode):" + `
                                    $Msg[$Result.GetUpdateResult($i).ResultCode] + `
                                    " (0x$('{0:x}' -f $Result.GetUpdateResult($i).HResult))") -Fore Red
                    }
                }
                Write-Host "  ------------------------------------------------" -Fore Green
                if ($Result.ResultCode -ne 2) {
                    Write-Host "  More than 1 update could not be applied." -Fore Red
                    $script:Incomplete = $True
                }
                if ($Result.rebootRequired) {
                    Reboot_and_Rerun 0
                }
            } else {
                Write-Host "  No applicable updates available." -Fore Yellow
            }
        } else {
            Write-Host "  No applicable updates available." -Fore Yellow
        }
    } else {
#       Write-Host "  No applicable updates available." -Fore Green
    }
    if ($NumInstall) { Remove-Variable SearchResult -Scope script}
    return $NumInstall
}

Function StopTranscript() {
    if (-not $LogDir) { return }
    try { Stop-Transcript | Out-Null } catch [System.Exception] {}
}

Function Pause() {
    Write-Host "Press any key to continue."
    $Host.UI.RawUI.FlushInputBuffer()
    $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyUp") | Out-Null
}

Add-Type -AssemblyName "Microsoft.VisualBasic"

Function FormatTitle([String]$Title, $Margin) {
#   return $Title # uncomment this line if you don't want truncation of update titles.
    $MaxWidth = $host.UI.RawUI.WindowSize.Width - 1 - $Margin
    try {
        $Wide = [Microsoft.VisualBasic.Strings]::StrConv($Title,[Microsoft.VisualBasic.VbStrConv]::Wide)
    } catch [System.ArgumentException] {
        return ($Title -Replace "^(.{$($MaxWidth-3)}).{4,}$",'$1...')
    }
    $Width = 0
    for ($Idx=0; $Idx -lt $Title.Length; $Idx++) {
        $Width++
        if ($Title[$Idx] -eq $Wide[$Idx]) { $Width++ }
    }
    if ($Width -le $MaxWidth) { return $Title }
    $SB = New-Object System.Text.StringBuilder
    $Width = 0
    for ($Idx=0; $Idx -lt $Title.Length; $Idx++) {
        $Width++
        if ($Title[$Idx] -eq $Wide[$Idx]) { $Width++ }
        if ($Width -gt $MaxWidth - 3) { Break }
        [void]$SB.Append($Title[$Idx])
    }
    [void]$SB.Append("...")
    return $SB.ToString()
}

Function PostProcess() {
    $filter = ((Split-Path $ThisScript -Leaf) -Replace "\.[^.]+$","") + "_PP.???"
    $files = Get-ChildItem -LiteralPath $script:PSScriptRoot -Filter $filter
    :LOOP foreach ($file in $files) {
        Switch ($file.Extension) {
            ".ps1" { $Cmd = { PowerShell.exe -NoProfile -ExecutionPolicy Bypass -Command "&'$($file.FullName)'" $Args } }
            ".bat" { $Cmd = $file.FullName }
            ".vbs" { $Cmd = $file.FullName }
           default { continue LOOP }
        }
        Write-Host "Executing an external command $($file.FullName) ..." -Fore Green
        try { 
            & $Cmd $Args
        } catch [System.Exception] {
            Write-Host "System Error occorred while executing the external command." -Fore Yellow
            Write-Host $Error[0].Exception
            Write-Host "Continuing..." -Fore Yellow
        }
        break
    }
}

Main
```