diff --git a/cleanup.ps1 b/cleanup.ps1 index 693bbe5..ee1145b 100644 --- a/cleanup.ps1 +++ b/cleanup.ps1 @@ -18,7 +18,7 @@ $LogDirectory = "C:\Quest" $LogDate = Get-Date -Format "yyyy-MM-dd" $RandomSuffix = Get-Random -Minimum 1000 -Maximum 9999 $LogFile = Join-Path $LogDirectory "cleanup-${LogDate}-${RandomSuffix}.log" -$WebhookUrl = "https://n8n.questcomputers.be/webhook/99077b7d-140f-477e-9241-2b9581ddaf34" +$WebhookUrl = "https://n8n.questcomputers.be/webhook-test/99077b7d-140f-477e-9241-2b9581ddaf34" $ScriptStartTime = Get-Date # Configuration - Timeout for winget (in seconds) @@ -32,7 +32,7 @@ if (-not (Test-Path $LogDirectory)) { # Initialize structured data object $ScriptResult = [ordered]@{ "script_name" = "System Cleanup Script" - "version" = "1.1" + "version" = "1.2" "execution_info" = @{ "started_at" = $ScriptStartTime.ToString("yyyy-MM-dd HH:mm:ss") "computer_name" = $env:COMPUTERNAME @@ -338,7 +338,6 @@ if ($ScriptResult.app_updates.winget_available) { # Create temporary files for output $WingetOutputFile = Join-Path $LogDirectory "winget_output.txt" - $WingetErrorFile = Join-Path $LogDirectory "winget_error.txt" # Start winget process with timeout $wingetProcess = Start-Process -FilePath "winget" -ArgumentList "upgrade --all --silent --accept-package-agreements --accept-source-agreements" -NoNewWindow -Wait -PassThru -ErrorAction Stop @@ -349,9 +348,13 @@ if ($ScriptResult.app_updates.winget_available) { Write-Log "Winget exit code: $WingetExitCode" -Level "INFO" Write-Log "Winget duration: $($WingetDuration.ToString('F2')) seconds" -Level "INFO" - # Read output if available - if (Test-Path $WingetOutputFile) { - $wingetOutput = Get-Content $WingetOutputFile -Raw + # Parse winget output from log file if available + $wingetOutput = Get-Content $LogFile | Select-String -Pattern "winget|Winget|Upgrading|Upgraded" -Context 2 | Out-String + $wingetOutput | Out-File -FilePath $LogFile -Append -Encoding UTF8 + + # Alternative: Try to capture output directly + try { + $wingetOutput = winget upgrade --all --silent 2>&1 | Out-String $wingetOutput | Out-File -FilePath $LogFile -Append -Encoding UTF8 # Parse winget output to extract updated apps @@ -374,16 +377,14 @@ if ($ScriptResult.app_updates.winget_available) { $ScriptResult.app_updates.apps_updated = $AppsUpdated $ScriptResult.app_updates.apps_checked = $AppsUpdated.Count - - # Clean up temporary files - Remove-Item $WingetOutputFile -ErrorAction SilentlyContinue - Remove-Item $WingetErrorFile -ErrorAction SilentlyContinue + } catch { + Write-Log "Could not capture winget output: $($_.Exception.Message)" -Level "WARNING" } if ($AppsUpdated.Count -gt 0) { Write-Log "Application updates completed. Updated $($AppsUpdated.Count) application(s): $($AppsUpdated -join ', ')" -Level "INFO" } else { - Write-Log "Application updates completed. All applications are up to date." -Level "INFO" + Write-Log "Application updates completed. All applications are up to date or winget output could not be parsed." -Level "INFO" } } catch { # Check if it's a timeout @@ -405,112 +406,133 @@ Write-Log "Application updates section completed." -Level "INFO" "" | Out-File -FilePath $LogFile -Append -Encoding UTF8 # -------------------------------------------------------------------------------- -# Updating Windows +# Updating Windows (using Windows Update API directly) # -------------------------------------------------------------------------------- Write-Log "================================================================================" -Level "INFO" Write-Log "SECTION: Windows Updates" -Level "INFO" Write-Log "================================================================================" -Level "INFO" -# Install NuGet provider -Write-Log "Installing NuGet package provider..." -Level "INFO" +# Load Windows Update COM object +Write-Log "Initializing Windows Update service..." -Level "INFO" try { - Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -ErrorAction Stop | Out-Null - $ScriptResult.windows_updates.nuget_installed = $true - Write-Log "NuGet package provider installed successfully." -Level "INFO" -} catch { - Write-Log "Failed to install NuGet: $($_.Exception.Message)" -Level "ERROR" -} - -# Install PSWindowsUpdate module -Write-Log "Installing PSWindowsUpdate module..." -Level "INFO" -try { - Install-Module PSWindowsUpdate -Force -AllowClobber -ErrorAction Stop | Out-Null - $ScriptResult.windows_updates.pswindowsupdate_installed = $true - Write-Log "PSWindowsUpdate module installed successfully." -Level "INFO" -} catch { - Write-Log "Failed to install PSWindowsUpdate: $($_.Exception.Message)" -Level "ERROR" -} - -# Import PSWindowsUpdate module -if ($ScriptResult.windows_updates.pswindowsupdate_installed) { - try { - Write-Log "Importing PSWindowsUpdate module..." -Level "INFO" - Import-Module PSWindowsUpdate -ErrorAction Stop - Write-Log "PSWindowsUpdate module imported successfully." -Level "INFO" - } catch { - Write-Log "Failed to import PSWindowsUpdate: $($_.Exception.Message)" -Level "ERROR" - } -} - -# Check for and install Windows updates -if ($ScriptResult.windows_updates.pswindowsupdate_installed) { - try { - Write-Log "Checking for and installing Windows updates..." -Level "INFO" + $UpdateSession = New-Object -ComObject "Microsoft.Update.Session" + $UpdateSearcher = $UpdateSession.CreateUpdateSearcher() + $UpdateDownloader = $UpdateSession.CreateUpdateDownloader() + $UpdateInstaller = $UpdateSession.CreateUpdateInstaller() + + Write-Log "Searching for available Windows updates..." -Level "INFO" + + # Search for updates + $SearchResult = $UpdateSearcher.Search("IsInstalled=0 and Type='Software'") + $AvailableUpdates = $SearchResult.Updates + + $ScriptResult.windows_updates.updates_available = $AvailableUpdates.Count + Write-Log "Found $($AvailableUpdates.Count) Windows update(s)." -Level "INFO" + + if ($AvailableUpdates.Count -gt 0) { + # Prepare download collection + $UpdatesToDownload = New-Object -ComObject "Microsoft.Update.UpdateColl" - # Run Windows update check and install in one command - $UpdateOutput = Get-WindowsUpdate -AcceptAll -Install -IgnoreUser -IgnoreReboot -ErrorAction Stop 2>&1 | Out-String - $UpdateOutput | Out-File -FilePath $LogFile -Append -Encoding UTF8 - - # Parse the output to extract KB numbers and titles - $UpdateLines = $UpdateOutput -split "`n" - $UpdatesFound = $false - $UpdatesInstalled = @() - - foreach ($line in $UpdateLines) { - # Look for KB pattern in the output - if ($line -match "KB\d+" -or $line -match "Update for|Security Update|Cumulative Update") { - $UpdatesFound = $true - - # Extract KB number - $kbMatch = [regex]::Match($line, 'KB\d+') - $kb = if ($kbMatch.Success) { $kbMatch.Value } else { "Unknown" } - - # Extract title (remove KB number and extra spaces) - $title = $line -replace 'KB\d+\s*' -replace '\s+' -replace '^\s*|\s*$' -trim - - if ($title -and $title.Length -gt 3) { - $UpdateInfo = @{ - "kb" = $kb - "title" = $title - "description" = "" - "size_mb" = 0 - "is_downloaded" = $true - "reboot_required" = $false - } - $UpdatesInstalled += $UpdateInfo - } + foreach ($Update in $AvailableUpdates) { + $UpdateInfo = @{ + "kb" = if ($Update.KBArticleIDs.Count -gt 0) { "KB$($Update.KBArticleIDs[0])" } else { "Unknown" } + "title" = $Update.Title + "description" = $Update.Description + "size_mb" = [math]::Round($Update.MaxDownloadSize / 1MB, 2) + "is_downloaded" = $false + "reboot_required" = ($Update.InstallationBehavior.RebootBehavior -ne "NeverRequiresReboot") } + $ScriptResult.windows_updates.updates_installed += $UpdateInfo + + # Add to download collection + $UpdatesToDownload.Add($Update) | Out-Null } - $ScriptResult.windows_updates.updates_installed = $UpdatesInstalled - $ScriptResult.windows_updates.updates_available = $UpdatesInstalled.Count + # Download updates + Write-Log "Downloading $($AvailableUpdates.Count) update(s)..." -Level "INFO" + $UpdateDownloader.Updates = $UpdatesToDownload + $DownloadResult = $UpdateDownloader.Download() - # Check if reboot is required from output - if ($UpdateOutput -match "reboot|Reboot|restart|restart your computer") { - $ScriptResult.windows_updates.reboot_required = $true + # Check download result + $DownloadResultCode = switch ($DownloadResult.ResultCode) { + 0 { "NotStarted" } + 1 { "InProgress" } + 2 { "Succeeded" } + 3 { "SucceededWithErrors" } + 4 { "Failed" } + 5 { "Aborted" } + default { "Unknown" } + } + + Write-Log "Download result: $DownloadResultCode" -Level "INFO" + + if ($DownloadResult.ResultCode -in @(2, 3)) { + # Prepare installation collection + $UpdatesToInstall = New-Object -ComObject "Microsoft.Update.UpdateColl" + + foreach ($Update in $AvailableUpdates) { + if ($Update.IsDownloaded) { + $UpdatesToInstall.Add($Update) | Out-Null + } + } + + # Install updates + Write-Log "Installing $($UpdatesToInstall.Count) update(s)..." -Level "INFO" + $UpdateInstaller.Updates = $UpdatesToInstall + $InstallResult = $UpdateInstaller.Install() + + # Check installation result + $InstallResultCode = switch ($InstallResult.ResultCode) { + 0 { "NotStarted" } + 1 { "InProgress" } + 2 { "Succeeded" } + 3 { "SucceededWithErrors" } + 4 { "Failed" } + 5 { "Aborted" } + default { "Unknown" } + } + + Write-Log "Installation result: $InstallResultCode" -Level "INFO" + + # Check if reboot is required + if ($InstallResult.RebootRequired) { + $ScriptResult.windows_updates.reboot_required = $true + } + + # Update status for downloaded updates + for ($i = 0; $i -lt $ScriptResult.windows_updates.updates_installed.Count; $i++) { + $ScriptResult.windows_updates.updates_installed[$i].is_downloaded = $true + } } Write-Log "Windows updates installation completed." -Level "INFO" - if ($UpdatesInstalled.Count -gt 0) { - Write-Log "Installed $($UpdatesInstalled.Count) update(s):" -Level "INFO" - foreach ($update in $UpdatesInstalled) { + if ($ScriptResult.windows_updates.updates_installed.Count -gt 0) { + Write-Log "Installed $($ScriptResult.windows_updates.updates_installed.Count) update(s):" -Level "INFO" + foreach ($update in $ScriptResult.windows_updates.updates_installed) { Write-Log " - $($update.kb): $($update.title)" -Level "INFO" } - } else { - Write-Log "No Windows updates were needed or found." -Level "INFO" } if ($ScriptResult.windows_updates.reboot_required) { Write-Log "A system reboot is required to complete the updates." -Level "WARNING" } - } catch { - $ErrorMsg = "Failed to install Windows updates: $($_.Exception.Message)" - $ScriptResult.windows_updates.errors += $ErrorMsg - Write-Log $ErrorMsg -Level "ERROR" + } else { + Write-Log "No Windows updates were needed." -Level "INFO" } -} else { - Write-Log "PSWindowsUpdate not installed. Skipping Windows updates." -Level "INFO" + + # Clean up COM objects + [System.Runtime.Interopservices.Marshal]::ReleaseComObject($UpdateInstaller) | Out-Null + [System.Runtime.Interopservices.Marshal]::ReleaseComObject($UpdateDownloader) | Out-Null + [System.Runtime.Interopservices.Marshal]::ReleaseComObject($UpdateSearcher) | Out-Null + [System.Runtime.Interopservices.Marshal]::ReleaseComObject($UpdateSession) | Out-Null + [System.GC]::Collect() + [System.GC]::WaitForPendingFinalizers() + +} catch { + $ErrorMsg = "Failed to install Windows updates: $($_.Exception.Message)" + $ScriptResult.windows_updates.errors += $ErrorMsg + Write-Log $ErrorMsg -Level "ERROR" } Write-Log "Windows updates section completed." -Level "INFO"