3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Yet Another Windows Update Automation

Last updated at Posted at 2017-09-23

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

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

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

#備考

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

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

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
3
3
17

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?