diff --git a/build/updates.sql b/build/updates.sql index 152122e..d453bd5 100644 --- a/build/updates.sql +++ b/build/updates.sql @@ -22,6 +22,17 @@ update kb set NetworkRequired = "No" where NetworkRequired = 0; update kb set ExclusiveInstall = "No" where ExclusiveInstall = 0; update kb set MSRCSeverity = null where MSRCSeverity = "n/a"; +update kb set Architecture = null where Architecture = "n/a"; +update kb set Architecture = "x64" where Architecture LIKE '%x64%'; +update kb set Architecture = "x64" where Architecture LIKE '%AMD64%'; +update kb set Architecture = "x64" where Architecture LIKE '%64%bit%'; +update kb set Architecture = "ARM64" where Architecture LIKE '%ARM64%'; +update kb set Architecture = "ARM32" where Architecture LIKE '%ARM%based%'; +update kb set Architecture = "ARM32" where Architecture LIKE '%ARM32%'; +update kb set Architecture = "x86" where Architecture LIKE '%x86%'; +update kb set Architecture = "x86" where Architecture LIKE '%32%bit%'; +update kb set Architecture = null where architecture NOT IN ('x64','x86','IA64','ARM64','ARM','ARM32'); + DELETE FROM Link WHERE EXISTS ( SELECT 1 FROM Link p2 diff --git a/kbupdate.psm1 b/kbupdate.psm1 index 5f9dd64..5b1278f 100644 --- a/kbupdate.psm1 +++ b/kbupdate.psm1 @@ -67,7 +67,7 @@ if (-not $IsLinux -and -not $IsMacOs) { } # Register autocompleters -Register-PSFTeppScriptblock -Name Architecture -ScriptBlock { "x64", "x86", "ia64", "ARM" } +Register-PSFTeppScriptblock -Name Architecture -ScriptBlock { "x64","x86","IA64","ARM64","ARM","ARM32" } Register-PSFTeppScriptblock -Name OperatingSystem -ScriptBlock { "Windows Server 2022", "Windows Server 2019", "Windows Server 2016", "Windows 10", "Windows 8.1", "Windows Server 2012 R2", "Windows 8", "Windows Server 2012", "Windows Server 2012 Hyper-V", "Windows 7", "Windows Server 2008 R2", "Windows Vista", "Windows Server 2008", "Windows Small Business Server (SBS) 2008", "Windows Server 2003", "Windows Small Business Server (SBS) 2003", "Windows XP", "Windows XP Media Center Edition (MCE)", "Windows XP Tablet PC Edition", "Windows 2000", "Small Business Server (SBS) 2000", "Windows NT 4.0", "Windows Millennium Edition (ME)", "Windows 98 Second Edition (SE)", "Windows 98", "Windows 95", "Microsoft Windows Update", "Windows Embedded Compact 2013", "Windows Embedded Compact 7", "Windows Embedded CE 6.0", "Windows CE 5.0", "Windows CE .NET 4.2", "Windows CE .NET 4.1" } Register-PSFTeppScriptblock -Name Product -ScriptBlock { "Exchange Server 2019", "Exchange Server 2016", "Exchange Server 2013", "Exchange Server 2010", "Exchange Server 2007", "Exchange Server 2003", "Exchange Server 2000", "Exchange Server 5.5", "Exchange Server 5.0", "Exchange Server 4.0", "Microsoft Office 365", "Outlook 2019", "Excel 2019", "Word 2019", "Access 2019", "Outlook 2016", "Excel 2016", "Word 2016", "Access 2016", "Outlook 2013", "Excel 2013", "Word 2013", "Access 2013", "Outlook 2010", "Excel 2010", "Word 2010", "Access 2010", "Outlook 2007", "Excel 2007", "Word 2007", "Access 2007", "PowerPoint 2007", "Visio 2007", "Publisher 2007", "Project 2007", "OneNote 2007", "InfoPath 2007", "Microsoft Office Groove 2007", "Outlook 2003", "Excel 2003", "Word 2003", "Access 2003", "PowerPoint 2003", "FrontPage 2003", "Visio 2003", "Publisher 2003", "Project 2003", "OneNote 2003", "InfoPath 2003", "Outlook 2002 (Outlook XP)", "Excel 2002 (Excel XP)", "Word 2002 (Word XP)", "Access 2002 (Access XP)", "PowerPoint 2002 (PowerPoint XP)", "FrontPage 2002 (FrontPage XP)", "Visio 2002 (Visio XP)", "Publisher 2002 (Publisher XP)", "Project 2002 (Project XP)", "Outlook 2000", "Excel 2000", "Word 2000", "Access 2000", "PowerPoint 2000", "FrontPage 2000", "Visio 2000", "Publisher 2000", "Project 2000", "Microsoft Office Live Meeting 2005", "Microsoft Works Suite 2003", "Microsoft Works Suite 2002", "Microsoft Works Suite 2001", "Microsoft Works 2000", "SharePoint Server 2019", "SharePoint Server 2016", "SharePoint Server 2013", "SharePoint Server 2010", "SharePoint Server 2007", "SharePoint Portal Server 2003", "SharePoint Portal Server 2001", "BizTalk Server 2006", "BizTalk Server 2004", "BizTalk Server 2002", "BizTalk Server 2000", "Internet Security and Acceleration (ISA) Server 2006", "Internet Security and Acceleration (ISA) Server 2004", "Internet Security and Acceleration (ISA) Server 2000", "System Center Essentials (SCE) 2010", "System Center Essentials (SCE) 2007", "System Center Operations Manager (SCOM) 2012", "System Center Virtual Machine Manager (SCVMM) 2012", "System Center Orchestrator (SCO) 2012", "System Center Service Manager (SCSM) 2012", "System Center Configuration Manager (SCCM) 2012", "System Center Configuration Manager (SCCM) 2007", "Systems Management Server (SMS) 2003", "Systems Management Server (SMS) 2.0", "Systems Management Server (SMS) 1.2", "Systems Management Server (SMS) 1.1", "Systems Management Server (SMS) 1.0", "SNA Server 4.0", "SNA Server 3.0", "System Center Operations Manager (SCOM) 2007", "Operations Manager (MOM) 2005", "Operations Manager (MOM) 2000", "Host Integration Server (HIS) 2004", "Host Integration Server (HIS) 2000", "Commerce Server 2007", "Commerce Server 2002", "Commerce Server 2000", "Dynamics CRM 3.0", "Zune", "Xbox 360", "Internet Explorer 11", "Internet Explorer 10", "Internet Explorer 9", "Internet Explorer 8", "Internet Explorer 7", "Internet Explorer 6", "Internet Explorer 5.5", "Internet Explorer 5.0", "SQL Server 2017", "SQL Server 2016", "SQL Server 2014", "SQL Server 2012", "SQL Server 2008 R2", "SQL Server 2008", "SQL Server 2005", "SQL Server 2000", "SQL Server 7.0", "Microsoft Data Access Components (MDAC) 2.8", "Microsoft Data Access Components (MDAC) 2.7", "Microsoft Data Access Components (MDAC) 2.6", "Microsoft Data Access Components (MDAC) 2.5", "Microsoft Data Access Components (MDAC) 2.1", "Visual FoxPro 9.0", "Visual FoxPro 8.0", "Visual FoxPro 7.0", "Visual FoxPro 6.0", ".NET Framework 4.7", ".NET Framework 4.6", ".NET Framework 4.5", ".NET Framework 4", ".NET Framework 3.5", ".NET Framework 3.0", ".NET Framework 2.0", ".NET Framework 1.1", ".NET Framework 1.0", "ASP.NET 2.0", "ASP.NET 1.1", "ASP.NET 1.0", "Visual Studio 2008", "Visual Studio 2005", "Visual C++ 2005", "Visual C# 2005", "Visual Basic 2005", "Visual Studio .NET 2003", "Visual C++ .NET 2003", "Visual C# .NET 2003", "Visual Basic .NET 2003", "Visual Studio .NET 2002", "Visual C++ .NET 2002", "Visual C# .NET 2002", "Visual Basic .NET 2002", "Visual Studio 6.0", "Visual C++ 6.0", "Visual Basic 6.0", "Windows Media Player 11", "Windows Media Player 10", "Windows Media Player 9", "Internet Information Services (IIS) 7.0", "Internet Information Services (IIS) 6.0", "Internet Information Services (IIS) 5.1", "Internet Information Services (IIS) 5.0", "Office Accounting 2007", "Small Business Accounting 2006", "Money 2007", "Money 2006", "Money 2005", "Money 2004", "Money 2003", "Money 2002", "Money 2001", "Visual SourceSafe 6.0", "Microsoft Encarta Encyclopedia 2000", "Age of Empires III (AoE3)", "Age of Empires II (AoE2)", "Age of Mythology", "Zoo Tycoon 2", "Zoo Tycoon", "Microsoft Mail for Appletalk Networks 3.1", "Microsoft Mail for Appletalk Networks 3.0" } # Languge is a tough one diff --git a/private/Update-KbDatabase.ps1 b/private/Update-KbDatabase.ps1 index 928a9ca..e836bf8 100644 --- a/private/Update-KbDatabase.ps1 +++ b/private/Update-KbDatabase.ps1 @@ -162,6 +162,22 @@ function Update-KbDatabase { $arch = "x86" } + if ($arch -eq "n/a") { + $arch = $null + } + if ($link -match "x64" -or $link -match "AMD64") { + $arch = "x64" + } + if ($link -match "x86") { + $arch = "x86" + } + if ($link -match "ARM64") { + $arch = "ARM64" + } + if ($link -match "ARM-based") { + $arch = "ARM32" + } + $detaildialog = Invoke-TlsWebRequest -Uri "https://www.catalog.update.microsoft.com/ScopedViewInline.aspx?updateid=$updateid" $description = Get-Info -Text $detaildialog -Pattern '' $lastmodified = Get-Info -Text $detaildialog -Pattern '' @@ -431,7 +447,7 @@ function Update-KbDatabase { $null = Install-Module kbupdate-library -ErrorAction Stop -Scope CurrentUser $null = Import-Module kbupdate-library -ErrorAction Stop } - $modpath = Split-Path (Get-Module kbupdate-library).Path + $modpath = Split-Path (Get-Module kbupdate-library).Path | Select-Object -Last 1 $kblib = Join-PSFPath -Path $modpath -Child library $db = Join-PSFPath -Path $kblib -Child kb.sqlite diff --git a/public/Get-KbNeededUpdate.ps1 b/public/Get-KbNeededUpdate.ps1 index c520a84..60eb3f7 100644 --- a/public/Get-KbNeededUpdate.ps1 +++ b/public/Get-KbNeededUpdate.ps1 @@ -103,11 +103,15 @@ function Get-KbNeededUpdate { } $searcher = $wua.CreateUpdateSearcher() Write-Verbose -Message "Searching for needed updates" - $wsuskbs = $searcher.Search("IsAssigned=1 and IsHidden=0 and IsInstalled=0") + $wsuskbs = $searcher.Search("Type='Software' and IsHidden=0") Write-Verbose -Message "Found $($wsuskbs.Count) updates" foreach ($wsu in $wsuskbs) { foreach ($wsuskb in $wsu.Updates) { + #isinstalled didnt work as expected for me in the searcher + if ($wsuskb.IsInstalled) { + continue + } # iterate the updates in searchresult # it must be force iterated like this $links = @() @@ -118,7 +122,6 @@ function Get-KbNeededUpdate { } } } - [pscustomobject]@{ ComputerName = $Computer Title = $wsuskb.Title @@ -141,7 +144,7 @@ function Get-KbNeededUpdate { Supersedes = $null #TODO SupersededBy = $null #TODO Link = $links - InputObject = $wsu + InputObject = $wsuskb } } } diff --git a/public/Get-KbUpdate.ps1 b/public/Get-KbUpdate.ps1 index 14b16b4..cf11c51 100644 --- a/public/Get-KbUpdate.ps1 +++ b/public/Get-KbUpdate.ps1 @@ -40,7 +40,7 @@ function Get-KbUpdate { Filters out any patches that have been superseded by other patches in the batch .PARAMETER Force - When using Latest, the Web is required to get the freshest data unless Force is used. + When using Latest, the Web is required to get the freshest data unless Force is used. Also when Force is used, the cache is ignored. .PARAMETER Exclude Exclude matches for pattern @@ -284,8 +284,12 @@ function Get-KbUpdate { $script:allresults += $guid $hashkey = "$guid-$Simple" if ($script:kbcollection.ContainsKey($hashkey)) { - $script:kbcollection[$hashkey] - continue + if ($Force) { + $script:kbcollection.Remove($hashkey) + } else { + $script:kbcollection[$hashkey] + continue + } } $severity = $wsuskb.MsrcSeverity | Select-Object -First 1 $alert = $wsuskb.SecurityBulletins | Select-Object -First 1 @@ -505,9 +509,13 @@ function Get-KbUpdate { $itemtitle = $item.Title $hashkey = "$guid-$Simple" if ($script:kbcollection.ContainsKey($hashkey)) { - $guids = $guids | Where-Object Guid -notin $guid - $script:kbcollection[$hashkey] - continue + if ($Force) { + $script:kbcollection.Remove($hashkey) + } else { + $guids = $guids | Where-Object Guid -notin $guid + $script:kbcollection[$hashkey] + continue + } } } diff --git a/public/Install-KbUpdate.ps1 b/public/Install-KbUpdate.ps1 index dc0e830..8d86706 100644 --- a/public/Install-KbUpdate.ps1 +++ b/public/Install-KbUpdate.ps1 @@ -35,7 +35,7 @@ function Install-KbUpdate { If the file is an exe and no Title is specified, we will have to get it from Get-KbUpdate .PARAMETER Method - Not yet implemented but will be used to specify DSC or Windows Update + Used to specify DSC or WindowsUpdate. By default, WindowsUpdate is used on localhost and DSC is used on remote servers. .PARAMETER ArgumentList This is an advanced parameter for those of you who need special argumentlists for your platform-specific update. @@ -51,7 +51,7 @@ function Install-KbUpdate { Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch. .NOTES - Author: Jess Pomfret (@jpomfret), Chrissy LeMaire (@cl) + Author: Chrissy LeMaire (@cl), Jess Pomfret (@jpomfret) Copyright: (c) licensed under MIT License: MIT https://opensource.org/licenses/MIT @@ -142,6 +142,8 @@ function Install-KbUpdate { foreach ($item in $ComputerName) { $computer = $item.ComputerName + $completed++ + if ($item.IsLocalHost -and -not (Test-ElevationRequirement -ComputerName $computer)) { Stop-PSFFunction -EnableException:$EnableException -Message "You must be an administrator to run this command on the local host" -Continue } @@ -149,30 +151,47 @@ function Install-KbUpdate { if (-not $item.IsLocalHost -and $Method -eq "WindowsUpdate") { Stop-PSFFunction -EnableException:$EnableException -Message "The Windows Update method is only supported on localhost due to Windows security restrictions" -Continue } + + if ((Get-Service wuauserv | Where-Object StartType -ne Disabled) -and $Method -eq "WindowsUpdate") { + Stop-PSFFunction -EnableException:$EnableException -Message "The Windows Update method cannot be used when the Windows Update service is stopped on $computer" -Continue + } + # null out a couple things to be safe $remotefileexists = $programhome = $remotesession = $null Write-PSFMessage -Level Verbose -Message "Processing $computer" - - - if ($Method -eq "WindowsUpdate") { - $sessiontype = [type]::GetTypeFromProgID("Microsoft.Update.Session") - $session = [activator]::CreateInstance($sessiontype) - $session.ClientApplicationID = "kbupdate installer" - $downloadat = $false - $updateinstall = New-Object -ComObject 'Microsoft.Update.UpdateColl' - - if ($InputObject.InputObject) { - Write-PSFMessage -Level Verbose -Message "Got an input object" - $searchresult = $InputObject.InputObject + if ($item.IsLocalhost -and $Method -ne "DSC") { + if ((Get-Service wuauserv | Where-Object StartType -ne Disabled)) { + $dowin = $true } else { - Write-PSFMessage -Level Verbose -Message "Build needed updates" - $searchresult = $session.CreateUpdateSearcher().Search("Type='Software'") + $dowin = $false + } + } + + if ($dowin -or $Method -eq "WindowsUpdate") { + try { + Write-PSFMessage -Level Verbose -Message "Using the Windows Update method" + $sessiontype = [type]::GetTypeFromProgID("Microsoft.Update.Session") + $session = [activator]::CreateInstance($sessiontype) + $session.ClientApplicationID = "kbupdate installer" + + if ($InputObject.UpdateId) { + Write-PSFMessage -Level Verbose -Message "Got an UpdateId" + $searchresult = $session.CreateUpdateSearcher().Search("UpdateId = '$($InputObject.UpdateId)'") + } else { + Write-PSFMessage -Level Verbose -Message "Build needed updates" + $searchresult = $session.CreateUpdateSearcher().Search("Type='Software' and IsInstalled=0 and IsHidden=0") + } + } catch { + Stop-PSFFunction -EnableException:$EnableException -Message "Failed to create update searcher" -ErrorRecord $_ -Continue } # iterate the updates in searchresult # it must be force iterated like this if ($searchresult.Updates) { + Write-PSFMessage -Level Verbose -Message "Processing $($searchresult.Updates.Count) updates" foreach ($update in $searchresult.Updates) { + $updateinstall = New-Object -ComObject 'Microsoft.Update.UpdateColl' + Write-PSFMessage -Level Verbose -Message "Accepting EULA for $($update.Title)" $null = $update.AcceptEula() foreach ($bundle in $update.BundledUpdates) { $files = New-Object -ComObject "Microsoft.Update.StringColl.1" @@ -186,48 +205,161 @@ function Install-KbUpdate { } } } - if (-not $update.IsDownloaded) { $downloadat = $true } + Write-PSFMessage -Level Verbose -Message "Checking to see if IsDownloaded ($($update.IsDownloaded)) is true" + if ($update.IsDownloaded) { + Write-PSFMessage -Level Verbose -Message "Updates for $($update.Title) do not need to be downloaded" + } else { + Write-PSFMessage -Level Verbose -Message "Update for $($update.Title) needs to be downloaded" + try { + Write-PSFMessage -Level Verbose -Message "Creating update downloader" + $downloader = $session.CreateUpdateDownloader() + Write-PSFMessage -Level Verbose -Message "Adding Updates" + $downloader.Updates = $searchresult.Updates + Write-PSFMessage -Level Verbose -Message "Executing download" + $null = $downloader.Download() + } catch { + Stop-PSFFunction -EnableException:$EnableException -Message "Failure on $env:ComputerName" -ErrorRecord $PSItem -Continue + } + } $updateinstall.Add($update) | Out-Null - } - if ($downloadat) { try { - $downloader = $session.CreateUpdateDownloader() - $downloader.Updates = $searchresult.Updates - $null = $downloader.Download() + Write-PSFMessage -Level Verbose -Message "Creating installer object for $($update.Title)" + $installer = $session.CreateUpdateInstaller() + if ($updateinstall) { + Write-PSFMessage -Level Verbose -Message "Adding updates via updateinstall" + $installer.Updates = $updateinstall + } else { + Write-PSFMessage -Level Verbose -Message "Adding updates via .Updates" + $installer.Updates = $searchresult.Updates + } + + Write-PSFMessage -Level Verbose -Message "Installing updates" + + Write-ProgressHelper -Activity "Installing updates on $computer" -Message "Installing $($update.Title)" -ExcludePercent + $results = $installer.Install() + + if ($results.RebootRequired -and $HResult -eq 0) { + $status = "Success - Reboot required" + } else { + switch ($results.ResultCode) { + 1 { + $status = "In Progress" + } + 2 { + $status = "Succeeded" + } + 3 { + $status = "Succeeded with errors" + } + 4 { + $status = "Failed" + } + 5 { + $status = "Aborted" + } + default { + $status = "Failure" + } + } + } + if ($update.BundledUpdates.DownloadContents.DownloadUrl) { + $filename = Split-Path -Path $update.BundledUpdates.DownloadContents.DownloadUrl -Leaf + } else { + $filename = $null + } + + [pscustomobject]@{ + ComputerName = $computer + Title = $update.Title + ID = $update.Identity.UpdateID + Status = $status + HotFixId = ($update.KBArticleIDs | Select-Object -First 1) + Update = $update + } | Select-DefaultView -Property ComputerName, Title, HotFixId, Id, Status } catch { Stop-PSFFunction -EnableException:$EnableException -Message "Failure on $env:ComputerName" -ErrorRecord $PSItem -Continue } } } else { $files = New-Object -ComObject "Microsoft.Update.StringColl.1" + $updateinstall = New-Object -ComObject 'Microsoft.Update.UpdateColl' + Write-PSFMessage -Level Verbose -Message "Link" foreach ($link in $searchresult.Link) { if ($link -and $RepositoryPath) { $filename = Split-Path -Path $link -Leaf $fullpath = Join-Path -Path $RepositoryPath -ChildPath $filename + Write-PSFMessage -Level Verbose -Message "Adding $fullpath" $null = $files.Add($fullpath) } } # load into Windows Update API try { + Write-PSFMessage -Level Verbose -Message "Copying files to cache" $bundle.CopyToCache($files) } catch { Stop-PSFFunction -EnableException:$EnableException -Message "Failure on $env:ComputerName" -ErrorRecord $PSItem -Continue } - } - try { - $installer = $session.CreateUpdateInstaller() - if ($updateinstall) { - $installer.Updates = $updateinstall - } else { - $installer.Updates = $searchresult.Updates + try { + Write-PSFMessage -Level Verbose -Message "Creating installer object" + $installer = $session.CreateUpdateInstaller() + if ($updateinstall) { + Write-PSFMessage -Level Verbose -Message "Adding updates via updateinstall" + $installer.Updates = $updateinstall + } else { + Write-PSFMessage -Level Verbose -Message "Adding updates via .Updates" + $installer.Updates = $searchresult.Updates + } + + Write-PSFMessage -Level Verbose -Message "Installing updates" + + Write-ProgressHelper -Activity "Installing updates on $computer" -Message "Installing $($update.Title)" -ExcludePercent + $results = $installer.Install() + + if ($results.RebootRequired -and $HResult -eq 0) { + $status = "Success - Reboot required" + } else { + switch ($results.ResultCode) { + 1 { + $status = "In Progress" + } + 2 { + $status = "Succeeded" + } + 3 { + $status = "Succeeded with errors" + } + 4 { + $status = "Failed" + } + 5 { + $status = "Aborted" + } + default { + $status = "Failure" + } + } + if ($update.BundledUpdates.DownloadContents.DownloadUrl) { + $filename = Split-Path -Path $update.BundledUpdates.DownloadContents.DownloadUrl -Leaf + } else { + $filename = $null + } + + [pscustomobject]@{ + ComputerName = $computer + Title = $update.Title + ID = $update.Identity.UpdateID + Status = $status + HotFixId = ($update.KBArticleIDs | Select-Object -First 1) + Update = $update + } | Select-DefaultView -Property ComputerName, Title, HotFixId, Id, Status + } + } catch { + Stop-PSFFunction -EnableException:$EnableException -Message "Failure on $env:ComputerName" -ErrorRecord $PSItem -Continue } - $installer.Install() - } catch { - Stop-PSFFunction -EnableException:$EnableException -Message "Failure on $env:ComputerName" -ErrorRecord $PSItem -Continue } } else { # Method is DSC