Tanium Packages This chapter contains a collection of Tanium Packages used for deploying and executing tasks across managed endpoints. Each package includes detailed instructions, command-line parameters, and associated scripts—primarily in PowerShell—to automate administrative actions, remediation tasks, and system maintenance. Ideal for managing package logic, reusability, and rapid deployment in enterprise environments. Creating a Tanium Package with a PowerShell Script Prepare Your PowerShell Script Create and save your script, e.g., Name.ps1 . Access the Tanium Console Go to: Administration > Content > Packages Create a New Package Click “Create Package” to open the package configuration window. Configure the Package Command : cmd.exe /d /c powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -NonInteractive -NoProfile -File .\Name.ps1 Upload Script : Attach the Name.ps1 script file to the package. Save the Package Click Save to create your new Tanium package. Remove Folder via URL-Encoded Path This PowerShell script is designed to remove a folder when its path is provided in a URL-encoded format. It can be especially useful in automation pipelines or tools where folder paths are passed as encoded parameters. Features Accepts a folder path as a parameter ( $FolderPath ) Automatically unescapes URL-encoded characters (e.g., %20 becomes a space) Removes the specified folder and all its contents, if it exists 📜 Script param ( [string]$FolderPath = "" ) # Unescape the URL-encoded path $unescapedFolderPath = [Uri]::UnescapeDataString($FolderPath) if (Test-Path -Path $unescapedFolderPath) { Remove-Item -Path $unescapedFolderPath -Recurse -Force } This script permanently deletes the folder and its contents. Ensure the path provided is correct and safe to delete. Ideal for use in cleanup tasks, temp directory deletion, or post-deployment teardown processes. Start-Cleanup Function This PowerShell function automates disk cleanup tasks to help reclaim space on the C: drive, targeting common sources of junk like Windows temporary files, the SoftwareDistribution folder, IIS logs, user temp folders, and more. A detailed log is created in $env:TEMP , and cleanup only targets files older than a specified age (default is 7 days). ⚙️ Features Deletes: Windows temp files C:\Windows\SoftwareDistribution User profile temp folders IIS logs (if installed) Recycle Bin contents Automatically logs deletions to a timestamped file in $env:TEMP Supports -WhatIf for safe testing Configurable: $DaysToDelete $LogFile $VerbosePreference $ErrorActionPreference 📜 Script Function Start-Cleanup { <# .SYNOPSIS Automate cleaning up a C:\ drive with low disk space .DESCRIPTION Cleans the C: drive's Window Temperary files, Windows SoftwareDistribution folder, the local users Temperary folder, IIS logs(if applicable) and empties the recycling bin. All deleted files will go into a log transcript in $env:TEMP. By default this script leaves files that are newer than 7 days old however this variable can be edited. .EXAMPLE PS C:\> .\Start-Cleanup.ps1 Save the file to your hard drive with a .PS1 extention and run the file from an elavated PowerShell prompt. .NOTES This script will typically clean up anywhere from 1GB up to 15GB of space from a C: drive. .FUNCTIONALITY PowerShell v3+ #> ## Allows the use of -WhatIf [CmdletBinding(SupportsShouldProcess=$True)] param( ## Delete data older then $daystodelete [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true,Position=0)] $DaysToDelete = 7, ## LogFile path for the transcript to be written to [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true,Position=1)] $LogFile = ("$env:TEMP\" + (get-date -format "MM-d-yy-HH-mm") + '.log'), ## All verbose outputs will get logged in the transcript($logFile) [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true,Position=2)] $VerbosePreference = "Continue", ## All errors should be withheld from the console [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true,Position=3)] $ErrorActionPreference = "SilentlyContinue" ) ## Begin the timer $Starters = (Get-Date) ## Check $VerbosePreference variable, and turns -Verbose on Function global:Write-Verbose ( [string]$Message ) { if ( $VerbosePreference -ne 'SilentlyContinue' ) { Write-Host "$Message" -ForegroundColor 'Green' } } ## Tests if the log file already exists and renames the old file if it does exist if(Test-Path $LogFile){ ## Renames the log to be .old Rename-Item $LogFile $LogFile.old -Verbose -Force } else { ## Starts a transcript in C:\temp so you can see which files were deleted Write-Host (Start-Transcript -Path $LogFile) -ForegroundColor Green } ## Writes a verbose output to the screen for user information Write-Host "Retriving current disk percent free for comparison once the script has completed. " -NoNewline -ForegroundColor Green Write-Host "[DONE]" -ForegroundColor Green -BackgroundColor Black ## Gathers the amount of disk space used before running the script $Before = Get-WmiObject Win32_LogicalDisk | Where-Object { $_.DriveType -eq "3" } | Select-Object SystemName, @{ Name = "Drive" ; Expression = { ( $_.DeviceID ) } }, @{ Name = "Size (GB)" ; Expression = {"{0:N1}" -f ( $_.Size / 1gb)}}, @{ Name = "FreeSpace (GB)" ; Expression = {"{0:N1}" -f ( $_.Freespace / 1gb ) } }, @{ Name = "PercentFree" ; Expression = {"{0:P1}" -f ( $_.FreeSpace / $_.Size ) } } | Format-Table -AutoSize | Out-String ## Stops the windows update service so that c:\windows\softwaredistribution can be cleaned up Get-Service -Name wuauserv | Stop-Service -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Verbose # Sets the SCCM cache size to 1 GB if it exists. if ((Get-WmiObject -namespace root\ccm\SoftMgmtAgent -class CacheConfig) -ne "$null"){ # if data is returned and sccm cache is configured it will shrink the size to 1024MB. $cache = Get-WmiObject -namespace root\ccm\SoftMgmtAgent -class CacheConfig $Cache.size = 1024 | Out-Null $Cache.Put() | Out-Null Restart-Service ccmexec -ErrorAction SilentlyContinue -WarningAction SilentlyContinue } ## Deletes the contents of windows software distribution. Get-ChildItem "C:\Windows\SoftwareDistribution\*" -Recurse -Force -ErrorAction SilentlyContinue | Remove-Item -recurse -ErrorAction SilentlyContinue -Verbose Write-Host "The Contents of Windows SoftwareDistribution have been removed successfully! " -NoNewline -ForegroundColor Green Write-Host "[DONE]" -ForegroundColor Green -BackgroundColor Black ## Deletes the contents of the Windows Temp folder. Get-ChildItem "C:\Windows\Temp\*" -Recurse -Force -Verbose -ErrorAction SilentlyContinue | Where-Object { ($_.CreationTime -lt $(Get-Date).AddDays( - $DaysToDelete)) } | Remove-Item -force -recurse -ErrorAction SilentlyContinue -Verbose Write-host "The Contents of Windows Temp have been removed successfully! " -NoNewline -ForegroundColor Green Write-Host "[DONE]" -ForegroundColor Green -BackgroundColor Black ## Deletes all files and folders in user's Temp folder older then $DaysToDelete Get-ChildItem "C:\users\*\AppData\Local\Temp\*" -Recurse -Force -ErrorAction SilentlyContinue | Where-Object { ($_.CreationTime -lt $(Get-Date).AddDays( - $DaysToDelete))} | Remove-Item -force -recurse -ErrorAction SilentlyContinue -Verbose Write-Host "The contents of `$env:TEMP have been removed successfully! " -NoNewline -ForegroundColor Green Write-Host "[DONE]" -ForegroundColor Green -BackgroundColor Black ## Removes all files and folders in user's Temporary Internet Files older then $DaysToDelete Get-ChildItem "C:\users\*\AppData\Local\Microsoft\Windows\Temporary Internet Files\*" ` -Recurse -Force -Verbose -ErrorAction SilentlyContinue | Where-Object {($_.CreationTime -lt $(Get-Date).AddDays( - $DaysToDelete))} | Remove-Item -Force -Recurse -ErrorAction SilentlyContinue -Verbose Write-Host "All Temporary Internet Files have been removed successfully! " -NoNewline -ForegroundColor Green Write-Host "[DONE]" -ForegroundColor Green -BackgroundColor Black ## Removes *.log from C:\windows\CBS if(Test-Path C:\Windows\logs\CBS\){ Get-ChildItem "C:\Windows\logs\CBS\*.log" -Recurse -Force -ErrorAction SilentlyContinue | remove-item -force -recurse -ErrorAction SilentlyContinue -Verbose Write-Host "All CBS logs have been removed successfully! " -NoNewline -ForegroundColor Green Write-Host "[DONE]" -ForegroundColor Green -BackgroundColor Black } else { Write-Host "C:\inetpub\logs\LogFiles\ does not exist, there is nothing to cleanup. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } ## Cleans IIS Logs older then $DaysToDelete if (Test-Path C:\inetpub\logs\LogFiles\) { Get-ChildItem "C:\inetpub\logs\LogFiles\*" -Recurse -Force -ErrorAction SilentlyContinue | Where-Object { ($_.CreationTime -lt $(Get-Date).AddDays(-60)) } | Remove-Item -Force -Verbose -Recurse -ErrorAction SilentlyContinue Write-Host "All IIS Logfiles over $DaysToDelete days old have been removed Successfully! " -NoNewline -ForegroundColor Green Write-Host "[DONE]" -ForegroundColor Green -BackgroundColor Black } else { Write-Host "C:\Windows\logs\CBS\ does not exist, there is nothing to cleanup. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } ## Removes C:\Config.Msi if (test-path C:\Config.Msi){ remove-item -Path C:\Config.Msi -force -recurse -Verbose -ErrorAction SilentlyContinue } else { Write-Host "C:\Config.Msi does not exist, there is nothing to cleanup. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } ## Removes c:\Intel if (test-path c:\Intel){ remove-item -Path c:\Intel -force -recurse -Verbose -ErrorAction SilentlyContinue } else { Write-Host "c:\Intel does not exist, there is nothing to cleanup. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } ## Removes c:\PerfLogs if (test-path c:\PerfLogs){ remove-item -Path c:\PerfLogs -force -recurse -Verbose -ErrorAction SilentlyContinue } else { Write-Host "c:\PerfLogs does not exist, there is nothing to cleanup. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } ## Removes $env:windir\memory.dmp if (test-path $env:windir\memory.dmp){ remove-item $env:windir\memory.dmp -force -Verbose -ErrorAction SilentlyContinue } else { Write-Host "C:\Windows\memory.dmp does not exist, there is nothing to cleanup. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } ## Removes rouge folders Write-host "Deleting Rouge folders " -NoNewline -ForegroundColor Green Write-Host "[DONE]" -ForegroundColor Green -BackgroundColor Black ## Removes Windows Error Reporting files if (test-path C:\ProgramData\Microsoft\Windows\WER){ Get-ChildItem -Path C:\ProgramData\Microsoft\Windows\WER -Recurse | Remove-Item -force -recurse -Verbose -ErrorAction SilentlyContinue Write-host "Deleting Windows Error Reporting files " -NoNewline -ForegroundColor Green Write-Host "[DONE]" -ForegroundColor Green -BackgroundColor Black } else { Write-Host "C:\ProgramData\Microsoft\Windows\WER does not exist, there is nothing to cleanup. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } ## Removes System and User Temp Files - lots of access denied will occur. ## Cleans up c:\windows\temp if (Test-Path $env:windir\Temp\) { Remove-Item -Path "$env:windir\Temp\*" -Force -Recurse -Verbose -ErrorAction SilentlyContinue } else { Write-Host "C:\Windows\Temp does not exist, there is nothing to cleanup. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } ## Cleans up minidump if (Test-Path $env:windir\minidump\) { Remove-Item -Path "$env:windir\minidump\*" -Force -Recurse -Verbose -ErrorAction SilentlyContinue } else { Write-Host "$env:windir\minidump\ does not exist, there is nothing to cleanup. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } ## Cleans up prefetch if (Test-Path $env:windir\Prefetch\) { Remove-Item -Path "$env:windir\Prefetch\*" -Force -Recurse -Verbose -ErrorAction SilentlyContinue } else { Write-Host "$env:windir\Prefetch\ does not exist, there is nothing to cleanup. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } ## Cleans up each users temp folder if (Test-Path "C:\Users\*\AppData\Local\Temp\") { Remove-Item -Path "C:\Users\*\AppData\Local\Temp\*" -Force -Recurse -Verbose -ErrorAction SilentlyContinue } else { Write-Host "C:\Users\*\AppData\Local\Temp\ does not exist, there is nothing to cleanup. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } ## Cleans up all users windows error reporting if (Test-Path "C:\Users\*\AppData\Local\Microsoft\Windows\WER\") { Remove-Item -Path "C:\Users\*\AppData\Local\Microsoft\Windows\WER\*" -Force -Recurse -Verbose -ErrorAction SilentlyContinue } else { Write-Host "C:\ProgramData\Microsoft\Windows\WER does not exist, there is nothing to cleanup. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } ## Cleans up users temporary internet files if (Test-Path "C:\Users\*\AppData\Local\Microsoft\Windows\Temporary Internet Files\") { Remove-Item -Path "C:\Users\*\AppData\Local\Microsoft\Windows\Temporary Internet Files\*" -Force -Recurse -Verbose -ErrorAction SilentlyContinue } else { Write-Host "C:\Users\*\AppData\Local\Microsoft\Windows\Temporary Internet Files\ does not exist. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } ## Cleans up Internet Explorer cache if (Test-Path "C:\Users\*\AppData\Local\Microsoft\Windows\IECompatCache\") { Remove-Item -Path "C:\Users\*\AppData\Local\Microsoft\Windows\IECompatCache\*" -Force -Recurse -Verbose -ErrorAction SilentlyContinue } else { Write-Host "C:\Users\*\AppData\Local\Microsoft\Windows\IECompatCache\ does not exist. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } ## Cleans up Internet Explorer cache if (Test-Path "C:\Users\*\AppData\Local\Microsoft\Windows\IECompatUaCache\") { Remove-Item -Path "C:\Users\*\AppData\Local\Microsoft\Windows\IECompatUaCache\*" -Force -Recurse -Verbose -ErrorAction SilentlyContinue } else { Write-Host "C:\Users\*\AppData\Local\Microsoft\Windows\IECompatUaCache\ does not exist. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } ## Cleans up Internet Explorer download history if (Test-Path "C:\Users\*\AppData\Local\Microsoft\Windows\IEDownloadHistory\") { Remove-Item -Path "C:\Users\*\AppData\Local\Microsoft\Windows\IEDownloadHistory\*" -Force -Recurse -Verbose -ErrorAction SilentlyContinue } else { Write-Host "C:\Users\*\AppData\Local\Microsoft\Windows\IEDownloadHistory\ does not exist. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } ## Cleans up Internet Cache if (Test-Path "C:\Users\*\AppData\Local\Microsoft\Windows\INetCache\") { Remove-Item -Path "C:\Users\*\AppData\Local\Microsoft\Windows\INetCache\*" -Force -Recurse -Verbose -ErrorAction SilentlyContinue } else { Write-Host "C:\Users\*\AppData\Local\Microsoft\Windows\INetCache\ does not exist. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } ## Cleans up Internet Cookies if (Test-Path "C:\Users\*\AppData\Local\Microsoft\Windows\INetCookies\") { Remove-Item -Path "C:\Users\*\AppData\Local\Microsoft\Windows\INetCookies\*" -Force -Recurse -Verbose -ErrorAction SilentlyContinue } else { Write-Host "C:\Users\*\AppData\Local\Microsoft\Windows\INetCookies\ does not exist. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } ## Cleans up terminal server cache if (Test-Path "C:\Users\*\AppData\Local\Microsoft\Terminal Server Client\Cache\") { Remove-Item -Path "C:\Users\*\AppData\Local\Microsoft\Terminal Server Client\Cache\*" -Force -Recurse -Verbose -ErrorAction SilentlyContinue } else { Write-Host "C:\Users\*\AppData\Local\Microsoft\Terminal Server Client\Cache\ does not exist. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } Write-host "Removing System and User Temp Files " -NoNewline -ForegroundColor Green Write-Host "[DONE]" -ForegroundColor Green -BackgroundColor Black ## Removes the hidden recycling bin. if (Test-path 'C:\$Recycle.Bin'){ Remove-Item 'C:\$Recycle.Bin' -Recurse -Force -Verbose -ErrorAction SilentlyContinue } else { Write-Host "C:\`$Recycle.Bin does not exist, there is nothing to cleanup. " -NoNewline -ForegroundColor DarkGray Write-Host "[WARNING]" -ForegroundColor DarkYellow -BackgroundColor Black } ## Turns errors back on $ErrorActionPreference = "Continue" ## Checks the version of PowerShell ## If PowerShell version 4 or below is installed the following will process if ($PSVersionTable.PSVersion.Major -le 4) { ## Empties the recycling bin, the desktop recyling bin $Recycler = (New-Object -ComObject Shell.Application).NameSpace(0xa) $Recycler.items() | ForEach-Object { ## If PowerShell version 4 or bewlow is installed the following will process Remove-Item -Include $_.path -Force -Recurse -Verbose Write-Host "The recycling bin has been cleaned up successfully! " -NoNewline -ForegroundColor Green Write-Host "[DONE]" -ForegroundColor Green -BackgroundColor Black } } elseif ($PSVersionTable.PSVersion.Major -ge 5) { ## If PowerShell version 5 is running on the machine the following will process Clear-RecycleBin -DriveLetter C:\ -Force -Verbose Write-Host "The recycling bin has been cleaned up successfully! " -NoNewline -ForegroundColor Green Write-Host "[DONE]" -ForegroundColor Green -BackgroundColor Black } ## Starts cleanmgr.exe Function Start-CleanMGR { Try{ Write-Host "Windows Disk Cleanup is running. " -NoNewline -ForegroundColor Green Start-Process -FilePath Cleanmgr -ArgumentList '/sagerun:1' -Wait -Verbose Write-Host "[DONE]" -ForegroundColor Green -BackgroundColor Black } Catch [System.Exception]{ Write-host "cleanmgr is not installed! To use this portion of the script you must install the following windows features:" -ForegroundColor Red -NoNewline Write-host "[ERROR]" -ForegroundColor Red -BackgroundColor black } } Start-CleanMGR ## gathers disk usage after running the cleanup cmdlets. $After = Get-WmiObject Win32_LogicalDisk | Where-Object { $_.DriveType -eq "3" } | Select-Object SystemName, @{ Name = "Drive" ; Expression = { ( $_.DeviceID ) } }, @{ Name = "Size (GB)" ; Expression = {"{0:N1}" -f ( $_.Size / 1gb)}}, @{ Name = "FreeSpace (GB)" ; Expression = {"{0:N1}" -f ( $_.Freespace / 1gb ) } }, @{ Name = "PercentFree" ; Expression = {"{0:P1}" -f ( $_.FreeSpace / $_.Size ) } } | Format-Table -AutoSize | Out-String ## Restarts wuauserv Get-Service -Name wuauserv | Start-Service -ErrorAction SilentlyContinue ## Stop timer $Enders = (Get-Date) ## Calculate amount of seconds your code takes to complete. Write-Verbose "Elapsed Time: $(($Enders - $Starters).totalseconds) seconds " ## Sends hostname to the console for ticketing purposes. Write-Host (Hostname) -ForegroundColor Green ## Sends the date and time to the console for ticketing purposes. Write-Host (Get-Date | Select-Object -ExpandProperty DateTime) -ForegroundColor Green ## Sends the disk usage before running the cleanup script to the console for ticketing purposes. Write-Verbose " Before: $Before" ## Sends the disk usage after running the cleanup script to the console for ticketing purposes. Write-Verbose "After: $After" ## Prompt to scan for large ISO, VHD, VHDX files. Function PromptforScan { param( $ScanPath, $title = (Write-Host "Search for large files" -ForegroundColor Green), $message = (Write-Host "Would you like to scan $ScanPath for ISOs or VHD(X) files?" -ForegroundColor Green) ) $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Scans $ScanPath for large files." $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Skips scanning $ScanPath for large files." $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no) $prompt = $host.ui.PromptForChoice($title, $message, $options, 0) switch ($prompt) { 0 { Write-Host "Scanning $ScanPath for any large .ISO and or .VHD\.VHDX files per the Administrators request." -ForegroundColor Green Write-Verbose ( Get-ChildItem -Path $ScanPath -Include *.iso, *.vhd, *.vhdx -Recurse -ErrorAction SilentlyContinue | Sort-Object Length -Descending | Select-Object Name, Directory, @{Name = "Size (GB)"; Expression = { "{0:N2}" -f ($_.Length / 1GB) }} | Format-Table | Out-String -verbose ) } 1 { Write-Host "The Administrator chose to not scan $ScanPath for large files." -ForegroundColor DarkYellow -Verbose } } } PromptforScan -ScanPath C:\ # end of function ## Completed Successfully! Write-Host (Stop-Transcript) -ForegroundColor Green Write-host " Script finished " -NoNewline -ForegroundColor Green Write-Host "[DONE]" -ForegroundColor Green -BackgroundColor Black } Start-Cleanup ⚠️ Caution Run from an elevated PowerShell prompt Ensure no critical data exists in cleanup paths Always test with -WhatIf in production environments Run DISM and SFC for System Repair This PowerShell script performs a two-step process to repair system integrity issues on Windows. It first runs the DISM tool to restore the system image and then executes SFC to scan and repair system files, only if DISM completes successfully. ⚙️ Features Automates the standard DISM + SFC repair sequence Color-coded console messages for success or failure Check DISM completion before running SFC to avoid redundant operations 💻 Script # Run DISM to repair the system image Write-Host "Running DISM repair..." -ForegroundColor Green dism /online /cleanup-image /restorehealth # Check if DISM was successful before running SFC if ($?) { Write-Host "DISM completed successfully. Running SFC..." -ForegroundColor Green # Run SFC to scan and repair system files sfc /scannow } else { Write-Host "DISM failed. Please check the DISM logs for errors." -ForegroundColor Red } 📝 Notes Requires Administrator privileges Best used when: Windows features aren't working correctly Updates fail repeatedly System file corruption is suspected Log files: DISM: %windir%\Logs\DISM\dism.log SFC: %windir%\Logs\CBS\CBS.log Suitable for ad‑hoc remediation or targeted deployments.  Recommended for systems with suspected component store corruption, repeated update failures, or unexplained instability.​ Because these operations can be lengthy and resource‑intensive, schedule deployments during maintenance windows where possible and monitor Tanium results for completion and error codes.​ Alternative Method Create a Tanium package and enter the following in the command field. This method does not require uploading a script. cmd /c ..\..\Tools\StdUtils\TPowershell.exe -ExecutionPolicy Bypass -Command "DISM /ONLINE /CLEANUP-IMAGE /SCANHEALTH;DISM /ONLINE /CLEANUP-IMAGE /CHECKHEALTH;DISM.exe /Online /Cleanup-image /Restorehealth;dism.exe /Online /Cleanup-Image /StartComponentCleanup;sfc /scannow" TPowershell.exe runs a PowerShell context that sequentially executes DISM /ScanHealth, /CheckHealth, /RestoreHealth, /StartComponentCleanup, and then sfc /scannow, in a single remediation action.   Scan for Component Store Corruption (DISM) This PowerShell script uses DISM /ScanHealth to check for corruption in the Windows Component Store. It captures the output and searches the logs for any corruption-related entries, optionally saving details to a text file for review. 🔍 Features Runs DISM /ScanHealth to check system health Logs output to a temporary file Searches for corruption-related entries Saves details about corrupted files (if found) to a text file Useful for early detection of system integrity issues 💻 Script # Run DISM to check for component store corruption $logFile = "$env:TEMP\DISM.log" $corruptFileList = "$env:TEMP\CorruptFiles.txt" # Run the DISM command and capture the output $DISMOutput = dism /Online /Cleanup-Image /ScanHealth 2>&1 | Tee-Object -FilePath $logFile # Look for corruption indicators in the DISM log $corruptEntries = $DISMOutput | Select-String "corrupt" # Extract corrupted file paths (if any) if ($corruptEntries) { Write-Output "Corrupt files detected. Extracting details..." # Read DISM logs to locate potential corrupt files $dismLogPath = "C:\Windows\Logs\DISM\dism.log" $foundFiles = Select-String -Path $dismLogPath -Pattern "(.+corrupt:.+)" -AllMatches | ForEach-Object { $_.Matches.Groups[1].Value } if ($foundFiles) { $foundFiles | Out-File -FilePath $corruptFileList Write-Output "Corrupt files list saved to: $corruptFileList" Get-Content $corruptFileList } else { Write-Output "Corruption detected, but no specific files were listed in the logs." } } else { Write-Output "No corruption found." } 📝 Notes Admin rights are required to run this script. Output files: DISM Output: $env:TEMP\DISM.log Corrupt File List: $env:TEMP\CorruptFiles.txt Log parsing may not identify every type of corruption. Always review C:\Windows\Logs\DISM\dism.log manually for critical diagnostics. Use this script proactively to detect and investigate potential system health problems before they escalate. Microsoft Edge Cleanup and Reinstall Script This page documents a robust PowerShell script used to completely remove an application (like Microsoft Edge) and reinstall it cleanly. It's especially useful when traditional uninstallation or update processes fail, leaving broken traces behind. 📄 About This script helps remove: Registry entries Scheduled tasks Background services Running processes Residual folders and files 🔧 Configuration Tips Finding Configurations: Search online for: Uninstall GUIDs File paths Registry keys Define: $regNameToMatch $knownUninstallers $knownPathsToDelete , etc. Repeat if needed: leftover services or background tasks can require multiple runs. Get-ItemProperty "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -eq "Microsoft Edge" } | Select-Object PSChildName, DisplayName, DisplayVersion, SystemComponent, UninstallString, WindowsInstaller | Format-List powershell.exe -NoProfile -EncodedCommand JABrACAAPQAgAEcAZQB0AC0ASQB0AGUAbQBQAHIAbwBwAGUAcgB0AHkAIAAnAEgASwBMAE0AOgBcAFMATwBGAFQAVwBBAFIARQBcAFcATwBXADYANAAzADIATgBvAGQAZQBcAE0AaQBjAHIAbwBzAG8AZgB0AFwAVwBpAG4AZABvAHcAcwBcAEMAdQByAHIAZQBuAHQAVgBlAHIAcwBpAG8AbgBcAFUAbgBpAG4AcwB0AGEAbABsAFwATQBpAGMAcgBvAHMAbwBmAHQAIABFAGQAZwBlACcAIAAtAEUAcgByAG8AcgBBAGMAdABpAG8AbgAgAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUACgBpAGYAIAAoACQAawApACAAewAKACAAIAAgACAAJABlAHgAZQBQAGEAdABoACAAPQAgACQAawAuAFUAbgBpAG4AcwB0AGEAbABsAFMAdAByAGkAbgBnACAALQByAGUAcABsAGEAYwBlACAAJwBeACIAKABbAF4AIgBdACsAKQAiAC4AKgAnACwAJwAkADEAJwAKACAAIAAgACAAIgBTAHkAcwB0AGUAbQBDAG8AbQBwAG8AbgBlAG4AdAA9ACQAKAAkAGsALgBTAHkAcwB0AGUAbQBDAG8AbQBwAG8AbgBlAG4AdAApACAAfAAgAEUAeABlAEUAeABpAHMAdABzAD0AJAAoAFQAZQBzAHQALQBQAGEAdABoACAAJABlAHgAZQBQAGEAdABoACkAIAB8ACAARQB4AGUAUABhAHQAaAA9ACQAZQB4AGUAUABhAHQAaAAiAAoAfQAgAGUAbABzAGUAIAB7AAoAIAAgACAAIAAnAEsAZQB5ACAAbgBvAHQAIABwAHIAZQBzAGUAbgB0ACcACgB9AA== Remove duplicate key powershell.exe -NoProfile -EncodedCommand JABwACAAPQAgACcASABLAEwATQA6AFwAUwBPAEYAVABXAEEAUgBFAFwAVwBPAFcANgA0ADMAMgBOAG8AZABlAFwATQBpAGMAcgBvAHMAbwBmAHQAXABXAGkAbgBkAG8AdwBzAFwAQwB1AHIAcgBlAG4AdABWAGUAcgBzAGkAbwBuAFwAVQBuAGkAbgBzAHQAYQBsAGwAXABNAGkAYwByAG8AcwBvAGYAdAAgAEUAZABnAGUAJwAKAGkAZgAgACgAVABlAHMAdAAtAFAAYQB0AGgAIAAkAHAAKQAgAHsACgAgACAAIAAgAFIAZQBtAG8AdgBlAC0ASQB0AGUAbQAgAC0AUABhAHQAaAAgACQAcAAgAC0ARgBvAHIAYwBlAAoAIAAgACAAIAAiAFIAZQBtAG8AdgBlAGQAIABkAHUAcABsAGkAYwBhAHQAZQAgAGsAZQB5ADoAIAAkAHAAIgAKAH0AIABlAGwAcwBlACAAewAKACAAIAAgACAAIgBLAGUAeQAgAG4AbwB0ACAAcAByAGUAcwBlAG4AdAAgAC0AIABuAG8AdABoAGkAbgBnACAAdABvACAAcgBlAG0AbwB2AGUAIgAKAH0A   Remove-Item "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge" -Force 🛠️ Script ### ### About ### # Application Cleanup and Reinstall # What is this? # This PowerShell script helps remove most traces of an installed application # so you can reinstall it cleanly. It targets leftover files, folders, and registry # entries that can block updates or fresh installs. It was originally built to # remove problematic versions of Microsoft Edge that failed to update. # How to find configurations? # 1. Search online for uninstall GUIDs, file paths, and registry keys related to # your application. Official docs or forums often list these details. # 2. Use RegShot (on GitHub) to capture "before" and "after" system snapshots. # Compare them to see which files and registry entries the installer added. # 3. In the script, specify top-level folders, file patterns, and registry keys you # want removed. It will also scan the usual uninstall locations by name. # 4. If the first run doesn’t catch everything, repeat the process. Look for # background processes, services, or custom registry keys not covered initially. # Having issues? # Please email me with detailed information: # • RegShot comparison report # • Script output or action log # • List of running processes # • Your script configurations # The more data you provide, the faster we can diagnose and fix the problem. ###################################################################################################################### ###################################################################################################################### ### ### Configurations ### ###################################################################################################################### $regNameToMatch = "Microsoft Edge" # This setting tells the script what application name to look for when scanning standard uninstall registry # locations. It uses a "match" operation against the Name, DisplayName, and ProductName properties in those # registry keys. Use a clear, unique identifier for your app so you don’t accidentally remove other software. # Example usage # $regNameToMatch = "Microsoft Edge" # This will match any registry entries whose Name, DisplayName, or ProductName contains "Microsoft Edge" ###################################################################################################################### $knownUninstallers = @('"C:\Program Files (x86)\Microsoft\Edge\Application\*\Installer\setup.exe" --uninstall --system-level --force-uninstall') # Define known uninstaller paths Add any known uninstaller executable locations to this array. You can use PowerShell # wildcards (\*) to match varying folder names. If the path includes spaces, wrap it in single quotes on the outside # and double quotes for the executable path. # Example usage # $knownUninstallers = @( # '"C:\Program Files (x86)\Microsoft\Edge\Application\*\Installer\setup.exe" --uninstall --system-level --force-uninstall' # ) ###################################################################################################################### $scheduledTasksNameToRemove = "Edge" # Configure scheduled task removalThis array specifies a name pattern to search for in Scheduled Tasks.The script # uses a "like" operator, adding wildcards (*) before and afterthe name you provide to match any part of the task name. # Example usage # $scheduledTasksNameToRemove = "Edge" # This will match tasks containing "Edge" anywhere in their names. ###################################################################################################################### $processNamesToStop = @("msedge","msedgewebview2","widget","msiexec","MicrosoftEdgeUpdate") # Define processes to stop List any running process names that should be terminated at the start of cleanup. # You can include wildcards (*) for partial matches. # Example usage # $processNamesToStop = @("msedge","msedgewebview2","widget","msiexec","MicrosoftEdgeUpdate") # This will stop any processes matching those names before uninstall steps begin. ###################################################################################################################### $knownPathsToDelete = @( "HKLM:\SOFTWARE\Microsoft\EdgeUpdate", "HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate", "HKLM:\SOFTWARE\Microsoft\Edge", "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Edge", "C:\Program Files (x86)\Microsoft\Edge", "C:\ProgramData\Microsoft\EdgeUpdate" ) # Define known paths to delete. Specify any registry keys or file paths to remove during cleanup. # Use PowerShell paths for registry (e.g., HKLM:...) and standard file paths. Wildcards (*) can be used. # Example usage # $knownPathsToDelete = @( # "HKLM:\SOFTWARE\Microsoft\EdgeUpdate", # "HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate", # "C:\Program Files (x86)\Microsoft\Edge" # ) ###################################################################################################################### $knownsServicesToDelete = @() # Define services to delete List any service names to remove during cleanup. The script uses sc.exe to delete # matching services. Wildcards (*) are supported. # Example usage # $knownServicesToDelete = @("*Soup*") # This will delete any service whose name contains "Soup" ###################################################################################################################### $installerStrings = @("msiexec.exe /i MicrosoftEdgeEnterpriseX64.msi /qn") # Define install commands for reinstallation If you plan to reinstall the application, list the full install # commands here. Include the executable and all arguments. Installer files must reside in the script’s working # directory, so you can call them by name without a full path. # Example usage # $installerStrings = @("msiexec.exe /i MicrosoftEdgeEnterpriseX64.msi /qn") ###################################################################################################################### $executableTimeoutSeconds = 300 # Configure executable timeout This setting defines the maximum time (in seconds) the script will wait for any # executable to finish. Keep this value as low as possible to avoid hanging on installers that launch interactive GUIs. # Example usage # $executableTimeoutSeconds = 300 # This sets a 5-minute timeout for each executable call. ###################################################################################################################### ###################################################################################################################### ### ### Main function can be modified to change the order of operation or additional rounds ### ###################################################################################################################### function Main { SetLog("Starting Cleanup") Stop-AppProcessesByName($processNamesToStop) Remove-ScheduledActions($scheduledTasksNameToRemove) Remove-KnownServices($knownsServicesToDelete) Invoke-KnownUninstallStrings($knownUninstallers) $cleanupPaths = Find-AllRegistryLocations($regNameToMatch) $GUIDs = Get-GUIDFromRegPath($cleanupPaths) $cleanupPaths = $cleanupPaths + (Expand-GUIDPaths($GUIDs)) Invoke-MSIUninstall($GUIDs) Invoke-UninstallStrings($cleanupPaths) Remove-CleanupItems($knownPathsToDelete) Remove-CleanupItems($cleanupPaths) Invoke-InstallStrings($installerStrings) SetLog("Cleanup complete") } ###################################################################################################################### ###################################################################################################################### ### ### Working function below this point. If you find modifications are needed, please ### reach out for assistance or share your changes. This will allow others to avoid ### and benefit from the issues you encountered. jeff@chuco.com ### ###################################################################################################################### function SetLog($logline){ Write-Host ("[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date) + $logline) } function Remove-KnownServices($services){ if($services.count -eq 0){ return } SetLog("Removing Services") foreach($service in $services){ $svcs = Get-Service $service SetLog("Found $($svcs.count) service(s) using $service") #looping through if multiple services are found foreach($svc in $svcs){ $thisName = $svc.Name if($svc.status -eq "Running"){ SetLog("Service $thisName is running. Attempting to stop before removal.") $svc | Stop-Service -ErrorAction SilentlyContinue } SetLog("Attempting to remove: $thisName") sc.exe delete ($thisName) if((Get-Service $thisName).count -eq 0){ SetLog("Service removed") } else { SetLog("Failed to remove service") } } } } function Invoke-KnownUninstallStrings($strings){ Invoke-ExecutableStrArr -arr $strings -msg "Starting known uninstall strings" } function Invoke-ExecutableStrArr{ param( [array]$arr, [string]$msg ) if($arr.count -eq 0){ return } SetLog($msg) foreach($string in $arr){ $stringParts = Expand-ExecutableString($string) Invoke-Executable @($stringParts.path,$stringParts.args) } } function Invoke-InstallStrings($installers){ Invoke-ExecutableStrArr -arr $installers -msg "Starting installer(s)" } function Stop-AppProcessesByName($procNames){ SetLog("Stopping Application Processes") $procNames = ConvertTo-Array $procNames foreach($procName in $procNames){ SetLog("Looking for processes with name $procName") $attempt = 0 do{ if($attempt -gt 0 ){ SetLog("Attempt number $($attempt+1) to stop processes for $procName") } $procs = Get-Process | Where-Object {$_.Name -match $procName} foreach($proc in $procs){ try{ SetLog("Stopping process $($proc.processname) PID $($proc.id)") Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue }catch{ SetLog("ERROR stopping process: $($Error[0].Exception.Message)") SetLog("ERROR NAME: $($error[0].Exception.GetType().FullName)") } } Start-Sleep 1 $attempt++ }while(Get-Process | Where-Object {$_.Name -match $procName}) } } function Invoke-MSIUninstall($GUIDs){ if($GUIDs.count -eq 0){ return } SetLog("Uninstall by GUID") foreach($GUID in $GUIDs){ Invoke-Executable @("msiexec.exe","/x $GUID /q") } } function Invoke-UninstallStrings($cleanupObj){ foreach($path in $cleanupObj){ $targetStrings = New-Object System.Collections.ArrayList foreach($props in (Get-ItemProperty $path)){ $propKeys = $props.PSObject.Properties.Name #Locate any well know uninstall string locations if($propKeys -contains "UninstallString"){ $targetStrings.add($props.UninstallString) | Out-Null } if($propKeys -contains "QuietUninstallString"){ $targetStrings.add($props.QuietUninstallString) | Out-Null } if($targetStrings.count -eq 0){ SetLog("Unhandled potential props: $($propKeys -join ", ")") } } foreach($targetString in $targetStrings){ if($targetString -match "MsiExec"){ if($targetString -match ".*({.*}).*"){ Invoke-MSIUninstall @($Matches[1]) }else{ Invoke-Executable @("msiexec.exe","/X $($targetString) /q") } } else { #Regex to extract the executable and arguments $exeStrings = Expand-ExecutableString($targetString) if($exeStrings){ Invoke-Executable @($exeStrings['Path'],$exeStrings['Args']) } else { SetLog("Unable to process uninstall string: $($targetString)") } } } } } function Expand-ExecutableString($str){ $pattern = '(?:"(?[A-Za-z]:\\[^"\\]+(?:\\[^"\\]+)*)"|(?[A-Za-z]:\\\S+(?:\\\S+)*)|(?[\w\.-]+\.exe))(?:\s+(?.*))?' if($str -match $pattern){ return [PSCustomObject]@{ Path = $Matches['Path'] Args = $Matches['Args'] } } return $false } function Expand-GUIDPaths($GUIDs){ $results = New-Object System.Collections.ArrayList foreach($GUID in $GUIDs){ $productCode = Convert-GUIDToMsiProductCode($GUID) $productCodeRegistryPath = (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\$productCode").PSPath $results.add(($productCodeRegistryPath))|Out-Null $results + (Find-AllRegistryLocations($GUID)) + (Find-AllRegistryLocations($productCode)) } return $results } function Get-GUIDFromRegPath($pathsObj){ $results = New-Object System.Collections.ArrayList foreach($path in $pathsObj){ if($path -match ".*({.*}).*"){ $results.add($Matches[1])|Out-Null } } if($results.Count -gt 0){ return $results } else { return $null } } Function Find-AllRegistryLocations($appName){ SetLog("Locating all application registry values for $appName") $results = New-Object System.Collections.ArrayList $paths = @( "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall", "HKLM:\SOFTWARE\Classes\Installer\Products", "HKLM:\SOFTWARE\Classes\Installer\UpgradeCodes", "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\Folders" ) #To work with the asterisk path needed for HKU an additional loop is required foreach($path in @("Registry::HKEY_USERS\*\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall","Registry::HKEY_USERS\*\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall")){ Get-ChildItem $path | ForEach-Object {$paths += $_.PSPath} } $valuesOfInterest = @("Name","DisplayName","ProductName") foreach ($path in $paths){ if (Test-Path $path){ $regHive = Get-ChildItem -Path $path foreach($regKey in $regHive){ $thisPSPath = $regKey.PSPath if($thisPSPath -match $appName){ SetLog("FOUND: $thisPSPath") $results.Add($thisPSPath)|Out-Null continue } foreach($value in $valuesOfInterest){ try{ $foundValue = Get-ItemPropertyValue -Path $thisPSPath -Name $value if($foundValue -match $appName){ $results.Add($thisPSPath)|Out-Null } }catch [System.Management.Automation.PSArgumentException]{ #Nothing to do - This reg did not have one of the values }catch{ #Log it for later review SetLog("ERROR: $($Error[0].Exception.Message)") SetLog("ERROR NAME: $($error[0].Exception.GetType().FullName)") } } } } } return $results | Select-Object -Unique } function Convert-GUIDToMsiProductCode { param([string]$Guid) $ProductIDChars = [regex]::replace($GUID, "[^a-zA-Z0-9]", "") $RearrangedCharIndex = 7,6,5,4,3,2,1,0,11,10,9,8,15,14,13,12,17,16,19,18,21,20,23,22,25,24,27,26,29,28,31,30 Return -join ($RearrangedCharIndex | ForEach-Object{$ProductIDChars[$_]}) } function ConvertTo-Array($in){ if(-not($in -is [array])){ return @($in) } return $in } function Invoke-Executable($un){ SetLog("Expanding: $un") try{ $thisPaths = Get-Item $un[0] -ErrorAction SilentlyContinue }catch{ #nothing to do. catch to silence errors } if($thisPaths.count -eq 0 -and -not($un[0] -match "\*")){ $thisPaths = @{} $thisPaths.FullName = $un[0] } if($thisPaths.count -eq 0){ SetLog("No executable found") return } #Looping through to handle variable paths returning multiple foreach($thisPath in $thisPaths){ $exePath = $thisPath.fullname if($null -eq $exePath -or $exePath -eq ""){ continue } SetLog("Starting command: $exePath $($un[1])") if($un[1]){ $thisPoc = Start-Process $exePath -ArgumentList ($un[1]) -PassThru -ErrorAction SilentlyContinue } else { $thisPoc = Start-Process $exePath -PassThru -ErrorAction SilentlyContinue } if ($thisPoc){ try{ Wait-Process -Id $thisPoc.Id -Timeout $executableTimeoutSeconds }catch{ SetLog("ERROR: executable exceeded timed out (max: $executableTimeoutSeconds)") Stop-Process -Id $thisPoc.Id -Force } } else { SetLog("Failed to start executable process") } SetLog("ExitCode: $($thisPoc.ExitCode)") } } function Remove-ScheduledActions($name){ SetLog("Checking for scheduled tasks with task name like: $name") $tasks = Get-ScheduledTask | Where-Object { $_.TaskName -like "*$name*" } if ($tasks) { foreach ($task in $tasks) { SetLog("Removing scheduled task: $($task.TaskName)") Unregister-ScheduledTask -TaskName $task.TaskName -Confirm:$false -ErrorAction SilentlyContinue } } else { SetLog("No scheduled tasks found with task name like: $name") } } function Remove-CleanupItems($paths){ if($paths.count -eq 0){ return } SetLog("Removing items by path") $paths = ConvertTo-Array $paths foreach($path in $paths){ if(Test-Path $path){ SetLog("Found and removing item: $path") Remove-Item -Path $path -Recurse -Force -Erroraction SilentlyContinue SetLog("Item removed: $(-not(Test-Path $path))") } else { SetLog("Item found during scan no longer exists: $path") } } } Main <# .SYNOPSIS Converts a self-updating (setup.exe-based) Microsoft Edge system-level install to the MSI Enterprise build, so Tanium has authoritative control over the version. .DESCRIPTION Targets endpoints where Edge was installed via the Chromium standalone/self-update installer (registers under HKLM Uninstall as "Microsoft Edge" with a setup.exe UninstallString, no MSI ProductCode). It: 1. Detects that install type (skips machines already on MSI - nothing to do). 2. Stops/disables the EdgeUpdate service + scheduled tasks so they can't re-trigger an install or fight the uninstall mid-flight. 3. Force-uninstalls the existing Edge using its own registered uninstall string. 4. Installs the Enterprise MSI (must be staged in the same Tanium package). 5. Optionally re-enables EdgeUpdate afterwards (see $ReenableEdgeUpdate below) - decide this based on whether Tanium alone should own patching, or whether you want EdgeUpdate as a safety net between Tanium cycles. .NOTES Package this script + the Enterprise MSI (Stable x64) in the same Tanium package. Download the MSI from https://www.microsoft.com/edge/business/download Run as SYSTEM (standard Tanium package context). #> [CmdletBinding()] param( # Name of the MSI file staged alongside this script in the Tanium package. # Tanium extracts package files into the working directory the action runs from, # so a relative name is normally sufficient - adjust if your package structure differs. [string]$MsiName = "MicrosoftEdgeEnterpriseX64.msi", # Set to $true if you want EdgeUpdate re-enabled after the MSI install # (lets Edge self-patch between Tanium cycles). $false leaves Tanium as the # sole patch authority - recommended if "silent drift" is what caused this # problem in the first place. [bool]$ReenableEdgeUpdate = $false, [string]$LogPath = "$env:ProgramData\_TaniumLogs\Convert-Edge-to-MSI.log" ) $ErrorActionPreference = "Stop" New-Item -ItemType Directory -Path (Split-Path $LogPath) -Force | Out-Null Start-Transcript -Path $LogPath -Append | Out-Null function Write-Log { param($msg) Write-Host "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $msg" } # --------------------------------------------------------------------------- # 1. Detect current install type # --------------------------------------------------------------------------- $uninstallKeyPaths = @( "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge", "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge" ) $edgeKey = $null foreach ($p in $uninstallKeyPaths) { if (Test-Path $p) { $edgeKey = Get-ItemProperty -Path $p; break } } if (-not $edgeKey) { Write-Log "No self-update Edge install found under expected uninstall keys. Nothing to convert. Exiting 0." Stop-Transcript | Out-Null exit 0 } $uninstallString = $edgeKey.UninstallString if (-not $uninstallString -or $uninstallString -notmatch "setup\.exe") { Write-Log "Edge is registered, but UninstallString doesn't look like the self-update setup.exe form ($uninstallString). Likely already MSI-based. Exiting 0." Stop-Transcript | Out-Null exit 0 } Write-Log "Self-update Edge detected. UninstallString: $uninstallString" # --------------------------------------------------------------------------- # 2. Disable EdgeUpdate service + scheduled tasks so nothing re-triggers # a background install/update mid-conversion # --------------------------------------------------------------------------- Write-Log "Stopping EdgeUpdate service and scheduled tasks..." Stop-Service -Name "edgeupdate" -Force -ErrorAction SilentlyContinue Set-Service -Name "edgeupdate" -StartupType Disabled -ErrorAction SilentlyContinue Stop-Service -Name "edgeupdatem" -Force -ErrorAction SilentlyContinue Set-Service -Name "edgeupdatem" -StartupType Disabled -ErrorAction SilentlyContinue Get-ScheduledTask -TaskName "MicrosoftEdgeUpdateTaskMachineCore*" -ErrorAction SilentlyContinue | Disable-ScheduledTask -ErrorAction SilentlyContinue | Out-Null Get-ScheduledTask -TaskName "MicrosoftEdgeUpdateTaskMachineUA*" -ErrorAction SilentlyContinue | Disable-ScheduledTask -ErrorAction SilentlyContinue | Out-Null # --------------------------------------------------------------------------- # 3. Force-uninstall existing Edge # --------------------------------------------------------------------------- # Parse "path arg1 arg2..." out of the UninstallString (it's quoted exe + args) if ($uninstallString -match '^"([^"]+)"\s*(.*)$') { $exePath = $matches[1] $exeArgs = $matches[2] } else { $split = $uninstallString.Split(" ", 2) $exePath = $split[0] $exeArgs = $split[1] } if (-not (Test-Path $exePath)) { Write-Log "ERROR: setup.exe not found at $exePath. Cannot uninstall. Exiting 1603." Stop-Transcript | Out-Null exit 1603 } # --force-uninstall is required, otherwise Edge's own setup.exe blocks removal # when it's the last/default browser or when EdgeUpdate is still fighting it. $fullArgs = "$exeArgs --force-uninstall" Write-Log "Running: `"$exePath`" $fullArgs" $proc = Start-Process -FilePath $exePath -ArgumentList $fullArgs -Wait -PassThru -WindowStyle Hidden Write-Log "Uninstall exit code: $($proc.ExitCode)" if ($proc.ExitCode -ne 0) { Write-Log "WARNING: Non-zero uninstall exit code. Continuing to MSI install anyway (setup.exe often returns nonzero on partial cleanup even when removal succeeded) - will validate at the end." } Start-Sleep -Seconds 5 # --------------------------------------------------------------------------- # 4. Install the Enterprise MSI # --------------------------------------------------------------------------- $msiPath = Join-Path $PSScriptRoot $MsiName if (-not (Test-Path $msiPath)) { Write-Log "ERROR: MSI not found at $msiPath. Verify it's staged in the Tanium package alongside this script. Exiting 1603." Stop-Transcript | Out-Null exit 1603 } $msiLog = "$env:ProgramData\_TaniumLogs\EdgeMSI_Install.log" $msiArgs = "/i `"$msiPath`" /qn /norestart DONOTCREATEDESKTOPSHORTCUT=TRUE /log `"$msiLog`"" Write-Log "Running: msiexec.exe $msiArgs" $msiProc = Start-Process -FilePath "msiexec.exe" -ArgumentList $msiArgs -Wait -PassThru Write-Log "MSI install exit code: $($msiProc.ExitCode)" if ($msiProc.ExitCode -notin @(0, 3010)) { Write-Log "ERROR: MSI install failed. See $msiLog for details." Stop-Transcript | Out-Null exit $msiProc.ExitCode } # --------------------------------------------------------------------------- # 5. Optionally re-enable EdgeUpdate; otherwise leave it disabled so Tanium # stays the single source of truth for the version on this endpoint # --------------------------------------------------------------------------- if ($ReenableEdgeUpdate) { Write-Log "Re-enabling EdgeUpdate service/tasks per `$ReenableEdgeUpdate." Set-Service -Name "edgeupdate" -StartupType Automatic -ErrorAction SilentlyContinue Start-Service -Name "edgeupdate" -ErrorAction SilentlyContinue Get-ScheduledTask -TaskName "MicrosoftEdgeUpdateTaskMachineCore*" -ErrorAction SilentlyContinue | Enable-ScheduledTask -ErrorAction SilentlyContinue | Out-Null Get-ScheduledTask -TaskName "MicrosoftEdgeUpdateTaskMachineUA*" -ErrorAction SilentlyContinue | Enable-ScheduledTask -ErrorAction SilentlyContinue | Out-Null } else { Write-Log "Leaving EdgeUpdate disabled - Tanium/MSI is now the sole patch path for Edge on this endpoint." # Belt-and-suspenders: also block EdgeUpdate from re-registering itself # for the Stable channel via policy, in case anything re-enables the service later. $edgeUpdatePolicyPath = "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" New-Item -Path $edgeUpdatePolicyPath -Force | Out-Null New-ItemProperty -Path $edgeUpdatePolicyPath -Name "Update{56EB18F8-B008-4CBD-B6D2-8C97FE7E9062}" -PropertyType DWord -Value 0 -Force | Out-Null } # --------------------------------------------------------------------------- # Validate: confirm MSI ProductCode is now registered (Tanium sensor target) # --------------------------------------------------------------------------- $msiRegistered = Get-ItemProperty "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -eq "Microsoft Edge" -and $_.WindowsInstaller -eq 1 } if ($msiRegistered) { Write-Log "SUCCESS: MSI-based Edge confirmed - version $($msiRegistered.DisplayVersion), ProductCode $($msiRegistered.PSChildName)." Stop-Transcript | Out-Null exit 0 } else { Write-Log "ERROR: MSI product not found in registry after install - conversion did not complete cleanly." Stop-Transcript | Out-Null exit 1603 } <# .SYNOPSIS Converts a self-updating (setup.exe-based) Microsoft Edge system-level install to the MSI Enterprise build, so Tanium has authoritative control over the version. .DESCRIPTION Targets endpoints where Edge was installed via the Chromium standalone/self-update installer (registers under HKLM Uninstall as "Microsoft Edge" with a setup.exe UninstallString, no MSI ProductCode). It: 1. Detects that install type (skips machines already on MSI - nothing to do). 2. Stops/disables the EdgeUpdate service + scheduled tasks so they can't re-trigger an install or fight the uninstall mid-flight. 3. Force-uninstalls the existing Edge using its own registered uninstall string. 4. Installs the Enterprise MSI (must be staged in the same Tanium package). 5. Optionally re-enables EdgeUpdate afterwards (see $ReenableEdgeUpdate below) - decide this based on whether Tanium alone should own patching, or whether you want EdgeUpdate as a safety net between Tanium cycles. .NOTES Package this script + the Enterprise MSI (Stable x64) in the same Tanium package. Download the MSI from https://www.microsoft.com/edge/business/download Run as SYSTEM (standard Tanium package context). #> [CmdletBinding()] param( # Name of the MSI file staged alongside this script in the Tanium package. # Tanium extracts package files into the working directory the action runs from, # so a relative name is normally sufficient - adjust if your package structure differs. [string]$MsiName = "MicrosoftEdgeEnterpriseX64.msi", # Set to $true if you want EdgeUpdate re-enabled after the MSI install # (lets Edge self-patch between Tanium cycles). $false leaves Tanium as the # sole patch authority - recommended if "silent drift" is what caused this # problem in the first place. [bool]$ReenableEdgeUpdate = $false, [string]$LogPath = "$env:ProgramData\_TaniumLogs\Convert-Edge-to-MSI.log" ) $ErrorActionPreference = "Stop" New-Item -ItemType Directory -Path (Split-Path $LogPath) -Force | Out-Null Start-Transcript -Path $LogPath -Append | Out-Null function Write-Log { param($msg) Write-Host "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $msg" } # --------------------------------------------------------------------------- # 1. Detect current install state. # IMPORTANT: "not found" is NOT the same as "already converted, skip." # A prior cleanup pass (or a failed earlier attempt) can leave a machine # with NO Edge registration at all - that machine still needs the MSI # installed, it just doesn't need the uninstall step first. # --------------------------------------------------------------------------- $uninstallKeyPaths = @( "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge", "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge" ) $edgeKey = $null foreach ($p in $uninstallKeyPaths) { if (Test-Path $p) { $edgeKey = Get-ItemProperty -Path $p; break } } $alreadyMsi = Get-ItemProperty "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -eq "Microsoft Edge" -and $_.WindowsInstaller -eq 1 } if ($alreadyMsi) { Write-Log "MSI-based Edge already installed (version $($alreadyMsi.DisplayVersion)). Nothing to do. Exiting 0." Stop-Transcript | Out-Null exit 0 } $needsUninstall = $false if ($edgeKey -and $edgeKey.UninstallString -match "setup\.exe") { $needsUninstall = $true $uninstallString = $edgeKey.UninstallString Write-Log "Self-update Edge detected. UninstallString: $uninstallString" } else { Write-Log "No self-update Edge registration found (likely already removed by a prior cleanup pass). Skipping uninstall step and proceeding straight to MSI install." } # --------------------------------------------------------------------------- # 2. Disable EdgeUpdate service + scheduled tasks so nothing re-triggers # a background install/update mid-conversion # --------------------------------------------------------------------------- Write-Log "Stopping EdgeUpdate service and scheduled tasks..." Stop-Service -Name "edgeupdate" -Force -ErrorAction SilentlyContinue Set-Service -Name "edgeupdate" -StartupType Disabled -ErrorAction SilentlyContinue Stop-Service -Name "edgeupdatem" -Force -ErrorAction SilentlyContinue Set-Service -Name "edgeupdatem" -StartupType Disabled -ErrorAction SilentlyContinue Get-ScheduledTask -TaskName "MicrosoftEdgeUpdateTaskMachineCore*" -ErrorAction SilentlyContinue | Disable-ScheduledTask -ErrorAction SilentlyContinue | Out-Null Get-ScheduledTask -TaskName "MicrosoftEdgeUpdateTaskMachineUA*" -ErrorAction SilentlyContinue | Disable-ScheduledTask -ErrorAction SilentlyContinue | Out-Null # --------------------------------------------------------------------------- # 3. Force-uninstall existing Edge (only if one was actually detected) # --------------------------------------------------------------------------- if ($needsUninstall) { # Parse "path arg1 arg2..." out of the UninstallString (it's quoted exe + args) if ($uninstallString -match '^"([^"]+)"\s*(.*)$') { $exePath = $matches[1] $exeArgs = $matches[2] } else { $split = $uninstallString.Split(" ", 2) $exePath = $split[0] $exeArgs = $split[1] } if (-not (Test-Path $exePath)) { Write-Log "setup.exe not found at $exePath (likely already removed). Skipping uninstall step, proceeding to install." } else { # --force-uninstall is required, otherwise Edge's own setup.exe blocks removal # when it's the last/default browser or when EdgeUpdate is still fighting it. $fullArgs = "$exeArgs --force-uninstall" Write-Log "Running: `"$exePath`" $fullArgs" $proc = Start-Process -FilePath $exePath -ArgumentList $fullArgs -Wait -PassThru -WindowStyle Hidden Write-Log "Uninstall exit code: $($proc.ExitCode)" if ($proc.ExitCode -ne 0) { Write-Log "WARNING: Non-zero uninstall exit code. Continuing to MSI install anyway (setup.exe often returns nonzero on partial cleanup even when removal succeeded) - will validate at the end." } Start-Sleep -Seconds 5 } } else { Write-Log "No uninstall needed - proceeding directly to MSI install." } # --------------------------------------------------------------------------- # 4. Install the Enterprise MSI # --------------------------------------------------------------------------- $msiPath = Join-Path $PSScriptRoot $MsiName if (-not (Test-Path $msiPath)) { Write-Log "ERROR: MSI not found at $msiPath. Verify it's staged in the Tanium package alongside this script. Exiting 1603." Stop-Transcript | Out-Null exit 1603 } $msiLog = "$env:ProgramData\_TaniumLogs\EdgeMSI_Install.log" $msiArgs = "/i `"$msiPath`" /qn /norestart DONOTCREATEDESKTOPSHORTCUT=TRUE /log `"$msiLog`"" Write-Log "Running: msiexec.exe $msiArgs" $msiProc = Start-Process -FilePath "msiexec.exe" -ArgumentList $msiArgs -Wait -PassThru Write-Log "MSI install exit code: $($msiProc.ExitCode)" if ($msiProc.ExitCode -notin @(0, 3010)) { Write-Log "ERROR: MSI install failed. See $msiLog for details." Stop-Transcript | Out-Null exit $msiProc.ExitCode } # --------------------------------------------------------------------------- # 5. Optionally re-enable EdgeUpdate; otherwise leave it disabled so Tanium # stays the single source of truth for the version on this endpoint # --------------------------------------------------------------------------- if ($ReenableEdgeUpdate) { Write-Log "Re-enabling EdgeUpdate service/tasks per `$ReenableEdgeUpdate." Set-Service -Name "edgeupdate" -StartupType Automatic -ErrorAction SilentlyContinue Start-Service -Name "edgeupdate" -ErrorAction SilentlyContinue Get-ScheduledTask -TaskName "MicrosoftEdgeUpdateTaskMachineCore*" -ErrorAction SilentlyContinue | Enable-ScheduledTask -ErrorAction SilentlyContinue | Out-Null Get-ScheduledTask -TaskName "MicrosoftEdgeUpdateTaskMachineUA*" -ErrorAction SilentlyContinue | Enable-ScheduledTask -ErrorAction SilentlyContinue | Out-Null } else { Write-Log "Leaving EdgeUpdate disabled - Tanium/MSI is now the sole patch path for Edge on this endpoint." # Belt-and-suspenders: also block EdgeUpdate from re-registering itself # for the Stable channel via policy, in case anything re-enables the service later. $edgeUpdatePolicyPath = "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" New-Item -Path $edgeUpdatePolicyPath -Force | Out-Null New-ItemProperty -Path $edgeUpdatePolicyPath -Name "Update{56EB18F8-B008-4CBD-B6D2-8C97FE7E9062}" -PropertyType DWord -Value 0 -Force | Out-Null } # --------------------------------------------------------------------------- # Validate: confirm MSI ProductCode is now registered (Tanium sensor target) # --------------------------------------------------------------------------- $msiRegistered = Get-ItemProperty "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -eq "Microsoft Edge" -and $_.WindowsInstaller -eq 1 } if ($msiRegistered) { Write-Log "SUCCESS: MSI-based Edge confirmed - version $($msiRegistered.DisplayVersion), ProductCode $($msiRegistered.PSChildName)." Stop-Transcript | Out-Null exit 0 } else { Write-Log "ERROR: MSI product not found in registry after install - conversion did not complete cleanly." Stop-Transcript | Out-Null exit 1603 } <# .SYNOPSIS Converts a self-updating (setup.exe-based) Microsoft Edge system-level install to the MSI Enterprise build, so Tanium has authoritative control over the version. .DESCRIPTION Targets endpoints where Edge was installed via the Chromium standalone/self-update installer (registers under HKLM Uninstall as "Microsoft Edge" with a setup.exe UninstallString, no MSI ProductCode). It: 1. Detects that install type (skips machines already on MSI - nothing to do). 2. Stops/disables the EdgeUpdate service + scheduled tasks so they can't re-trigger an install or fight the uninstall mid-flight. 3. Force-uninstalls the existing Edge using its own registered uninstall string. 4. Installs the Enterprise MSI (must be staged in the same Tanium package). 5. Optionally re-enables EdgeUpdate afterwards (see $ReenableEdgeUpdate below) - decide this based on whether Tanium alone should own patching, or whether you want EdgeUpdate as a safety net between Tanium cycles. .NOTES Package this script + the Enterprise MSI (Stable x64) in the same Tanium package. Download the MSI from https://www.microsoft.com/edge/business/download Run as SYSTEM (standard Tanium package context). #> [CmdletBinding()] param( # Name of the MSI file staged alongside this script in the Tanium package. # Tanium extracts package files into the working directory the action runs from, # so a relative name is normally sufficient - adjust if your package structure differs. [string]$MsiName = "MicrosoftEdgeEnterpriseX64.msi", # Set to $true if you want EdgeUpdate re-enabled after the MSI install # (lets Edge self-patch between Tanium cycles). $false leaves Tanium as the # sole patch authority - recommended if "silent drift" is what caused this # problem in the first place. [bool]$ReenableEdgeUpdate = $false, [string]$LogPath = "$env:ProgramData\_TaniumLogs\Convert-Edge-to-MSI.log" ) $ErrorActionPreference = "Stop" New-Item -ItemType Directory -Path (Split-Path $LogPath) -Force | Out-Null Start-Transcript -Path $LogPath -Append | Out-Null function Write-Log { param($msg) Write-Host "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $msg" } # --------------------------------------------------------------------------- # 1. Detect current install state. # IMPORTANT: "not found" is NOT the same as "already converted, skip." # A prior cleanup pass (or a failed earlier attempt) can leave a machine # with NO Edge registration at all - that machine still needs the MSI # installed, it just doesn't need the uninstall step first. # --------------------------------------------------------------------------- $uninstallKeyPaths = @( "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge", "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge" ) $edgeKey = $null foreach ($p in $uninstallKeyPaths) { if (Test-Path $p) { $edgeKey = Get-ItemProperty -Path $p; break } } $alreadyMsi = Get-ItemProperty "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -eq "Microsoft Edge" -and $_.WindowsInstaller -eq 1 } if ($alreadyMsi) { Write-Log "MSI-based Edge already installed (version $($alreadyMsi.DisplayVersion)). Nothing to do. Exiting 0." Stop-Transcript | Out-Null exit 0 } $needsUninstall = $false if ($edgeKey -and $edgeKey.UninstallString -match "setup\.exe") { $needsUninstall = $true $uninstallString = $edgeKey.UninstallString Write-Log "Self-update Edge detected. UninstallString: $uninstallString" } else { Write-Log "No self-update Edge registration found (likely already removed by a prior cleanup pass). Skipping uninstall step and proceeding straight to MSI install." } # --------------------------------------------------------------------------- # 2. Disable EdgeUpdate service + scheduled tasks so nothing re-triggers # a background install/update mid-conversion # --------------------------------------------------------------------------- Write-Log "Stopping EdgeUpdate service and scheduled tasks..." Stop-Service -Name "edgeupdate" -Force -ErrorAction SilentlyContinue Set-Service -Name "edgeupdate" -StartupType Disabled -ErrorAction SilentlyContinue Stop-Service -Name "edgeupdatem" -Force -ErrorAction SilentlyContinue Set-Service -Name "edgeupdatem" -StartupType Disabled -ErrorAction SilentlyContinue Get-ScheduledTask -TaskName "MicrosoftEdgeUpdateTaskMachineCore*" -ErrorAction SilentlyContinue | Disable-ScheduledTask -ErrorAction SilentlyContinue | Out-Null Get-ScheduledTask -TaskName "MicrosoftEdgeUpdateTaskMachineUA*" -ErrorAction SilentlyContinue | Disable-ScheduledTask -ErrorAction SilentlyContinue | Out-Null # --------------------------------------------------------------------------- # 3. Force-uninstall existing Edge (only if one was actually detected) # --------------------------------------------------------------------------- if ($needsUninstall) { # Parse "path arg1 arg2..." out of the UninstallString (it's quoted exe + args) if ($uninstallString -match '^"([^"]+)"\s*(.*)$') { $exePath = $matches[1] $exeArgs = $matches[2] } else { $split = $uninstallString.Split(" ", 2) $exePath = $split[0] $exeArgs = $split[1] } if (-not (Test-Path $exePath)) { Write-Log "setup.exe not found at $exePath (likely already removed). Skipping uninstall step, proceeding to install." } else { # --force-uninstall is required, otherwise Edge's own setup.exe blocks removal # when it's the last/default browser or when EdgeUpdate is still fighting it. $fullArgs = "$exeArgs --force-uninstall" Write-Log "Running: `"$exePath`" $fullArgs" $proc = Start-Process -FilePath $exePath -ArgumentList $fullArgs -Wait -PassThru -WindowStyle Hidden Write-Log "Uninstall exit code: $($proc.ExitCode)" if ($proc.ExitCode -ne 0) { Write-Log "WARNING: Non-zero uninstall exit code. Continuing to MSI install anyway (setup.exe often returns nonzero on partial cleanup even when removal succeeded) - will validate at the end." } Start-Sleep -Seconds 5 } } else { Write-Log "No uninstall needed - proceeding directly to MSI install." } # --------------------------------------------------------------------------- # 3.5. Clear known EdgeUpdate policy blocks before attempting the MSI install. # The MSI's "DoInstall" custom action honors HKLM\SOFTWARE\Policies\Microsoft\EdgeUpdate # just like the bootstrapper does. If Install{GUID}=0 or InstallDefault=0 is present # (commonly pushed by GPO/Intune baselines to prevent Edge reinstalls), the install # fails with "Error 1722 ... CustomAction DoInstall returned actual error code # -2147219438/-2147219674" - a documented Microsoft issue, not a package problem. # NOTE: if your org reasserts this via GPO/Intune on its next refresh cycle, clearing # it here is only a point-in-time fix for this deployment - flag it to whoever owns # that policy if you see it recur. # --------------------------------------------------------------------------- $edgeUpdatePolicyKey = "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" $stableInstallPolicyName = "Install{56EB18F8-B008-4CBD-B6D2-8C97FE7E9062}" if (Test-Path $edgeUpdatePolicyKey) { $props = Get-ItemProperty -Path $edgeUpdatePolicyKey -ErrorAction SilentlyContinue if ($props -and ($props.PSObject.Properties.Name -contains $stableInstallPolicyName) -and ($props.$stableInstallPolicyName -eq 0)) { Write-Log "Found blocking policy '$stableInstallPolicyName = 0' under $edgeUpdatePolicyKey - this blocks Edge installs by any method. Clearing it so the MSI can install." Remove-ItemProperty -Path $edgeUpdatePolicyKey -Name $stableInstallPolicyName -ErrorAction SilentlyContinue } if ($props -and ($props.PSObject.Properties.Name -contains "InstallDefault") -and ($props.InstallDefault -eq 0)) { Write-Log "Found blocking policy 'InstallDefault = 0' under $edgeUpdatePolicyKey. Clearing it so the MSI can install." Remove-ItemProperty -Path $edgeUpdatePolicyKey -Name "InstallDefault" -ErrorAction SilentlyContinue } } else { Write-Log "No EdgeUpdate policy key present at $edgeUpdatePolicyKey - nothing to clear." } # --------------------------------------------------------------------------- # 4. Install the Enterprise MSI # --------------------------------------------------------------------------- $msiPath = Join-Path $PSScriptRoot $MsiName if (-not (Test-Path $msiPath)) { Write-Log "ERROR: MSI not found at $msiPath. Verify it's staged in the Tanium package alongside this script. Exiting 1603." Stop-Transcript | Out-Null exit 1603 } $msiLog = "$env:ProgramData\_TaniumLogs\EdgeMSI_Install.log" $msiArgs = "/i `"$msiPath`" /qn /norestart DONOTCREATEDESKTOPSHORTCUT=TRUE /log `"$msiLog`"" Write-Log "Running: msiexec.exe $msiArgs" $msiProc = Start-Process -FilePath "msiexec.exe" -ArgumentList $msiArgs -Wait -PassThru Write-Log "MSI install exit code: $($msiProc.ExitCode)" if ($msiProc.ExitCode -notin @(0, 3010)) { Write-Log "ERROR: MSI install failed. See $msiLog for details." Stop-Transcript | Out-Null exit $msiProc.ExitCode } # --------------------------------------------------------------------------- # 5. Optionally re-enable EdgeUpdate; otherwise leave it disabled so Tanium # stays the single source of truth for the version on this endpoint # --------------------------------------------------------------------------- if ($ReenableEdgeUpdate) { Write-Log "Re-enabling EdgeUpdate service/tasks per `$ReenableEdgeUpdate." Set-Service -Name "edgeupdate" -StartupType Automatic -ErrorAction SilentlyContinue Start-Service -Name "edgeupdate" -ErrorAction SilentlyContinue Get-ScheduledTask -TaskName "MicrosoftEdgeUpdateTaskMachineCore*" -ErrorAction SilentlyContinue | Enable-ScheduledTask -ErrorAction SilentlyContinue | Out-Null Get-ScheduledTask -TaskName "MicrosoftEdgeUpdateTaskMachineUA*" -ErrorAction SilentlyContinue | Enable-ScheduledTask -ErrorAction SilentlyContinue | Out-Null } else { Write-Log "Leaving EdgeUpdate disabled - Tanium/MSI is now the sole patch path for Edge on this endpoint." # Belt-and-suspenders: also block EdgeUpdate from re-registering itself # for the Stable channel via policy, in case anything re-enables the service later. $edgeUpdatePolicyPath = "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" New-Item -Path $edgeUpdatePolicyPath -Force | Out-Null New-ItemProperty -Path $edgeUpdatePolicyPath -Name "Update{56EB18F8-B008-4CBD-B6D2-8C97FE7E9062}" -PropertyType DWord -Value 0 -Force | Out-Null } # --------------------------------------------------------------------------- # Validate: confirm MSI ProductCode is now registered (Tanium sensor target) # --------------------------------------------------------------------------- $msiRegistered = Get-ItemProperty "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -eq "Microsoft Edge" -and $_.WindowsInstaller -eq 1 } if ($msiRegistered) { Write-Log "SUCCESS: MSI-based Edge confirmed - version $($msiRegistered.DisplayVersion), ProductCode $($msiRegistered.PSChildName)." Stop-Transcript | Out-Null exit 0 } else { Write-Log "ERROR: MSI product not found in registry after install - conversion did not complete cleanly." Stop-Transcript | Out-Null exit 1603 } 📝 Notes Requires Admin privileges Designed to run in a clean-up and reinstall loop Highly configurable — edit $regNameToMatch and related variables per app Consider creating backups before aggressive cleanups Microsoft Teams - Full Removal Script This page documents a comprehensive PowerShell script to completely uninstall Microsoft Teams from all user profiles, including registry, file system remnants, and WMI entries. 📋 Purpose Stop all running Teams processes Uninstall Teams for all users (WMI + user installs) Remove leftover Teams folders and desktop/start menu shortcuts Clean registry keys (HKLM, HKU) Remove orphaned installer folders 🛠️ Script Write-Output "Stopping Teams processes" Get-Process "*teams*" | Stop-Process -ErrorAction SilentlyContinue Write-Host "Removing All Teams Installations via WMI" Get-WmiObject -Class Win32_Product | Where-Object{$_.Name -match "Teams"} | ForEach-Object {$_.Uninstall()} Write-Output "Checking all users for Teams installs" $TeamsUsers = Get-ChildItem -Path "$($ENV:SystemDrive)\Users" $TeamsUsers | ForEach-Object { Try { Write-Output ($_.Name) if (Test-Path "$($ENV:SystemDrive)\Users\$($_.Name)\AppData\Local\Microsoft\Teams") { Write-Output " Teams install found...Uninstalling" Start-Process -FilePath "$($ENV:SystemDrive)\Users\$($_.Name)\AppData\Local\Microsoft\Teams\Update.exe" -ArgumentList "-uninstall -s" -Wait } } Catch { Write-Output " Failed to uninstall from - $($ENV:SystemDrive)\Users\$($_.Name)\AppData\Local\Microsoft\Teams\Update.exe" } Try { if (Test-Path "$($ENV:SystemDrive)\Users\$($_.Name)\AppData\Local\Microsoft\Teams") { Write-Output " Cleaning up orphaned Teams install folder" Remove-Item –Path "$($ENV:SystemDrive)\Users\$($_.Name)\AppData\Local\Microsoft\Teams" -Recurse -Force -ErrorAction Ignore } } Catch { Write-Output " Failed to remove - $($ENV:SystemDrive)\Users\$($_.Name)\AppData\Local\Microsoft\Teams" } Write-Output " Removing orphaned desktop link" Remove-Item "$($ENV:SystemDrive)\Users\$($_.Name)\Desktop\Microsoft Teams.lnk" -ErrorAction SilentlyContinue Write-Output " Removing orphaned start menu link" Remove-Item "$($ENV:SystemDrive)\Users\$($_.Name)\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Microsoft Teams.lnk" -ErrorAction SilentlyContinue } Write-Output "" Write-Output "" Write-Output "Searching HKLM for Teams remnants" $prouctNameToPurge = "Teams" $RegPath = "HKLM:\Software\Classes\Installer\Products\" Write-Output(" Clean registry " + $RegPath) $appRegKey = Get-ChildItem -Path $RegPath -Recurse | Get-ItemProperty | Where-Object {$_.ProductName -match $prouctNameToPurge} If($appRegKey.PSChildName){ foreach($appKey in $appRegKey.PSChildName){ if($appKey -ne ""){ $appDirToDelete = $RegPath + $appKey SetLog(("App registry to delete:" + $appDirToDelete)) Remove-Item -Path $appDirToDelete -Force -Recurse -ErrorAction SilentlyContinue If(Test-Path $appDirToDelete){ Write-Output(" Failed to delete " + $appDirToDelete) }else{ Write-Output(" Successfully deleted " + $appDirToDelete) } } } }else{ Write-Output(" No "+ $prouctNameToPurge +" registry keys found in " + $RegPath) } $RegPaths = @("HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\","HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\") foreach($RegPath in $RegPaths){ Write-Output(" Clean registry " + $RegPath) $appRegKey = Get-ChildItem -Path $RegPath -Recurse | Get-ItemProperty | Where-Object {$_.DisplayName -match $prouctNameToPurge} If($appRegKey.PSChildName){ foreach($appKey in $appRegKey.PSChildName){ if($appKey -ne ""){ $appDirToDelete = $RegPath + $appKey Write-Output(" " + $prouctNameToPurge + " registry to delete:" + $appDirToDelete) Remove-Item -Path $appDirToDelete -Force -Recurse -ErrorAction SilentlyContinue If(Test-Path $appDirToDelete){ Write-Output(" Failed to delete " + $appDirToDelete) }else{ Write-Output(" Successfully deleted " + $appDirToDelete) } } } }else{ Write-output (" No "+ $prouctNameToPurge +" registry keys found in " + $RegPath) } } Write-Output "" Write-Output "" Write-Output "Searching HKU for Teams remnants" $RegPaths = @("\Software\Microsoft\Windows\CurrentVersion\Uninstall\","\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\") New-PSDrive -PSProvider Registry -Name HKU -Root HKEY_USERS -ErrorAction SilentlyContinue $allHKU = Get-ChildItem HKU: foreach($HKU in $allHKU){ foreach($RegPath in $RegPaths){ $appRegKey = Get-ChildItem -Path ('HKU:\' + ($HKU.PSChildName) + $RegPath ) -Recurse -ErrorAction SilentlyContinue| Get-ItemProperty | Where-Object {$_.DisplayName -match $prouctNameToPurge} if($appRegKey.QuietUninstallString){ $uninstSting = ($appRegKey.QuietUninstallString -split '"')[1] Start-Process $uninstSting -ArgumentList "--uninstall -s" -Wait -ErrorAction SilentlyContinue Remove-Item -Path ('HKU:\' + ($HKU.PSChildName) + $RegPath + ($appRegKey.PSChildname) ) -ErrorAction SilentlyContinue } } Remove-Item -Path ('HKU:\' + ($HKU.PSChildName) + '\SOFTWARE\Microsoft\Office\Teams') -ErrorAction SilentlyContinue } Write-Output "" Write-Output "" Write-Output "Cleaning Teams Installer Folder" Remove-Item "$($ENV:SystemDrive)\Program Files (x86)\Teams Installer" -Recurse -Force -ErrorAction Ignore 📎 Notes Run with Administrator rights. Designed to fully remove Teams from shared workstations or persistent VDI environments. Use caution when applying to multi-user systems. Logs or error handling can be added with Start-Transcript or logging functions. Force Patch Scan This page describes how the scan-now.txt marker file is used to trigger a patch scan and how to interpret its contents. The file is evaluated by the patching process to determine whether a new scan is required based on its last modified time.​ Behavior A scan is initiated when scan-now.txt exists and its modified time is later than the last recorded scan time on the system.​ The file does not need any specific contents; the logic relies on the file’s last modified timestamp to decide whether to run a new scan.​ For example: File modified date: 10/14/2025 9:42:56 PM. The presence of this file initiates a scan if the file's modified time is later than the last scan time. Script # Force Patch Scan cmd.exe /d /c echo Scan invoked on %DATE% %TIME% from package >> ..\..\Patch\scans\scan-now.txt /d prevents cmd.exe from running AutoRun commands so that only the echo statement executes. The echo line appends a one-line entry with the current date, time, and the text “from package” into ....\Patch\scans\scan-now.txt, creating the file if it does not already exist. Notes Requires Administrator privileges Best used when: Troubleshooting systems that intermittently miss scheduled scans.   The scan-now.txt file grows over time, providing a simple history of when the force-scan command ran from different packages or runs. Log file details Location: ....\Patch\scans\scan-now.txt relative to the script or package working directory. Start Windows Service If Stopped This page documents the Start-ServiceParameterized.ps1 script, which is designed to start a Windows service if it is currently stopped. The script supports both named and positional parameters and includes basic error handling. Overview This PowerShell script checks the status of a specified Windows service and starts it if it is not already running. It accepts the service name either as a named parameter or as a positional argument. Usage You can run the script using either method below: 1. Named Parameter   powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\Start-ServiceParameterized.ps1 -ServiceName wuauserv 2. Positional Argument powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\Start-ServiceParameterized.ps1 wuauserv 3. Example for Tanium Package (code field): cmd.exe /d /c powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -NonInteractive -NoProfile -File .\Start-ServiceParameterized.ps1 $1 Script Behavior The script performs these steps: Accepts the service name as either a named parameter ( -ServiceName ) or a positional argument. If no service name is provided, the script returns an error. Retrieves the Windows service using Get-Service . Evaluates the service status: If not running , attempts to start the service. If running , outputs that no action is required Script Code: #################################################################################################### # Start a Windows service if stopped # Service name can be passed as: # - Named parameter: -ServiceName wuauserv # - Positional/argument: wuauserv # code: powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\Start-ServiceParameterized.ps1 $1 ##################################################################################################### param( [Parameter(Mandatory = $false, Position = 0)] [string]$ServiceName ) # Fallback: if ServiceName wasn't bound but args were passed if (-not $ServiceName -and $args.Count -gt 0) { $ServiceName = $args[0] } if (-not $ServiceName) { Write-Output "ERROR: No service name specified. Please pass a service name." exit 1 } Write-Output "Checking service: $ServiceName" try { $svc = Get-Service -Name $ServiceName -ErrorAction Stop if ($svc.Status -ne "Running") { Write-Output "Starting service '$ServiceName'..." Start-Service -Name $ServiceName -ErrorAction Stop Write-Output "Service '$ServiceName' successfully started." } else { Write-Output "Service '$ServiceName' is already running." } } catch { Write-Output "Unable to access service '$ServiceName': $_" exit 2 } Notes Designed for automation platforms such as Tanium , SCCM , and Intune . Requires appropriate administrative privileges to start services.     Restart a Windows Service (Parameterized Tanium Package) OVERVIEW This package restarts a Windows service by stopping it and then restarting it. It uses a single required parameter (the service name) so the same package can be reused for different services across endpoints. PRIMARY COMMAND (Parameterized) cmd.exe /d /c net stop "$1" && net start "$1" WHAT THIS COMMAND DOES • cmd.exe /c runs the command and exits when done • /d disables AutoRun commands from the registry for consistent execution • net stop "$1" stops the service specified by the first package parameter • && ensures the start only runs if the stop succeeds • net start "$1" starts the same service REQUIRED PARAMETER Parameter #1 ($1) Name (suggested): ServiceName Type: String Required: Yes Description: Windows service “SERVICE_NAME” value (not the display name) IMPORTANT NOTE ABOUT SERVICE NAME VS DISPLAY NAME The parameter must be the service name (SERVICE_NAME), not the friendly Display Name. Examples: • Correct service name: Spooler • Correct service name: wuauserv • Incorrect display name: Print Spooler HOW TO FIND THE SERVICE NAME IN TANIUM (RECOMMENDED METHOD) Use Tanium Interact to collect service inventory from endpoints and then filter for the service you need. Step-by-step: Go to Interact. Ask the question: Get Service Details from all machines Filter the results for the expected service by searching for either: • Service Name (SERVICE_NAME), or • Display Name (friendly name) Confirm the exact Service Name value you will use as the package parameter. Tip: Always copy the Service Name exactly as shown in the results to avoid mismatch issues.