update
This commit is contained in:
8
Applications/AWS-CLI/Force-Update.ps1
Normal file
8
Applications/AWS-CLI/Force-Update.ps1
Normal file
@ -0,0 +1,8 @@
|
||||
# https://docs.aws.amazon.com/cli/latest/userguide/awscli-install-windows.html
|
||||
$dlurl = "https://s3.amazonaws.com/aws-cli/AWSCLI64PY3.msi"
|
||||
$installerPath = Join-Path $env:TEMP (Split-Path $dlurl -Leaf)
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
Invoke-WebRequest $dlurl -OutFile $installerPath
|
||||
Start-Process -FilePath msiexec -Args "/i $installerPath /passive" -Verb RunAs -Wait
|
||||
Remove-Item $installerPath
|
||||
$env:Path += ";C:\Program Files\Amazon\AWSCLI\bin"
|
28
Applications/Acrobat Reader DC/Force-Update.ps1
Normal file
28
Applications/Acrobat Reader DC/Force-Update.ps1
Normal file
@ -0,0 +1,28 @@
|
||||
# Silently install Adobe Reader DC with Microsoft Intune
|
||||
# In order to distribute Adobe Acrobat Reader DC software you need to have
|
||||
# a valid Adobe Acrobat Reader DC Distribution Agreement in place.
|
||||
# See http://www.adobe.com/products/acrobat/distribute.html?readstep for details.
|
||||
|
||||
# Check if Software is installed already in registry.
|
||||
$CheckADCReg = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | where {$_.DisplayName -like "Adobe Acrobat Reader DC*"}
|
||||
# If Adobe Reader is not installed continue with script. If it's istalled already script will exit.
|
||||
If ($CheckADCReg -eq $null) {
|
||||
|
||||
# Path for the temporary downloadfolder. Script will run as system so no issues here
|
||||
$Installdir = "c:\temp\install_adobe"
|
||||
New-Item -Path $Installdir -ItemType directory
|
||||
|
||||
# Download the installer from the Adobe website. Always check for new versions!!
|
||||
$source = "ftp://ftp.adobe.com/pub/adobe/reader/win/AcrobatDC/1800920044/AcroRdrDC1800920044_fr_FR.exe"
|
||||
$destination = "$Installdir\AcroRdrDC1800920044_fr_FR.exe"
|
||||
Invoke-WebRequest $source -OutFile $destination
|
||||
|
||||
# Start the installation when download is finished
|
||||
Start-Process -FilePath "$Installdir\AcroRdrDC1800920044_fr_FR.exe" -ArgumentList "/sAll /rs /rps /msi /norestart /quiet EULA_ACCEPT=YES"
|
||||
|
||||
# Wait for the installation to finish. Test the installation and time it yourself. I've set it to 240 seconds.
|
||||
Start-Sleep -s 240
|
||||
|
||||
# Finish by cleaning up the download. I choose to leave c:\temp\ for future installations.
|
||||
rm -Force $Installdir\AcroRdrDC*
|
||||
}
|
236
Applications/Edge/Force-Update.ps1
Normal file
236
Applications/Edge/Force-Update.ps1
Normal file
@ -0,0 +1,236 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get-EdgeEnterpriseMSI
|
||||
|
||||
.DESCRIPTION
|
||||
Imports all device configurations in a folder to a specified tenant
|
||||
|
||||
.PARAMETER Channel
|
||||
Channel to download, Valid Options are: Dev, Beta, Stable, EdgeUpdate, Policy.
|
||||
|
||||
.PARAMETER Platform
|
||||
Platform to download, Valid Options are: Windows or MacOS, if using channel "Policy" this should be set to "any"
|
||||
Defaults to Windows if not set.
|
||||
|
||||
.PARAMETER Architecture
|
||||
Architecture to download, Valid Options are: x86, x64, arm64, if using channel "Policy" this should be set to "any"
|
||||
Defaults to x64 if not set.
|
||||
|
||||
.PARAMETER Version
|
||||
If set the script will try and download a specific version. If not set it will download the latest.
|
||||
|
||||
.PARAMETER Folder
|
||||
Specifies the Download folder
|
||||
|
||||
.PARAMETER Force
|
||||
Overwrites the file without asking.
|
||||
|
||||
.NOTES
|
||||
Version: 1.2
|
||||
Author: Mattias Benninge
|
||||
Creation Date: 2020-07-01
|
||||
|
||||
Version history:
|
||||
|
||||
1.0 - Initial script development
|
||||
1.1 - Fixes and improvements by @KarlGrindon
|
||||
- Script now handles multiple files for e.g. MacOS Edge files
|
||||
- Better error handling and formating
|
||||
- URI Validation
|
||||
1.2 - Better compability on servers (force TLS and remove dependency to IE)
|
||||
|
||||
|
||||
https://docs.microsoft.com/en-us/mem/configmgr/apps/deploy-use/deploy-edge
|
||||
|
||||
.EXAMPLE
|
||||
|
||||
Download the latest version for the Beta channel and overwrite any existing file
|
||||
.\Get-EdgeEnterpriseMSI.ps1 -Channel Beta -Folder D:\SourceCode\PowerShell\Div -Force
|
||||
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $True, HelpMessage = 'Channel to download, Valid Options are: Dev, Beta, Stable, EdgeUpdate, Policy')]
|
||||
[ValidateSet('Dev', 'Beta', 'Stable', 'EdgeUpdate', 'Policy')]
|
||||
[string]$Channel = "Stable",
|
||||
|
||||
[Parameter(Mandatory = $True, HelpMessage = 'Folder where the file will be downloaded')]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$Folder = "C:\temp",
|
||||
|
||||
[Parameter(Mandatory = $false, HelpMessage = 'Platform to download, Valid Options are: Windows or MacOS')]
|
||||
[ValidateSet('Windows', 'MacOS', 'any')]
|
||||
[string]$Platform = "Windows",
|
||||
|
||||
[Parameter(Mandatory = $false, HelpMessage = "Architecture to download, Valid Options are: x86, x64, arm64, any")]
|
||||
[ValidateSet('x86', 'x64', 'arm64', 'any')]
|
||||
[string]$Architecture = "x64",
|
||||
|
||||
[parameter(Mandatory = $false, HelpMessage = "Specifies which version to download")]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$ProductVersion,
|
||||
|
||||
[parameter(Mandatory = $false, HelpMessage = "Overwrites the file without asking")]
|
||||
[Switch]$Force
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$edgeEnterpriseMSIUri = 'https://edgeupdates.microsoft.com/api/products?view=enterprise'
|
||||
|
||||
# Validating parameters to reduce user errors
|
||||
if ($Channel -eq "Policy" -and ($Architecture -ne "Any" -or $Platform -ne "Any")) {
|
||||
Write-Warning ("Channel 'Policy' requested, but either 'Architecture' and/or 'Platform' is not set to 'Any'.
|
||||
Setting Architecture and Platform to 'Any'")
|
||||
|
||||
$Architecture = "Any"
|
||||
$Platform = "Any"
|
||||
}
|
||||
elseif ($Channel -ne "Policy" -and ($Architecture -eq "Any" -or $Platform -eq "Any")) {
|
||||
throw "If Channel isn't set to policy, architecture and/or platform can't be set to 'Any'"
|
||||
}
|
||||
elseif ($Channel -eq "EdgeUpdate" -and ($Architecture -ne "x86" -or $Platform -eq "Windows")) {
|
||||
Write-Warning ("Channel 'EdgeUpdate' requested, but either 'Architecture' is not set to x86 and/or 'Platform'
|
||||
is not set to 'Windows'. Setting Architecture to 'x86' and Platform to 'Windows'")
|
||||
|
||||
$Architecture = "x86"
|
||||
$Platform = "Windows"
|
||||
}
|
||||
|
||||
Write-Host "Enabling connection over TLS for better compability on servers" -ForegroundColor Green
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12
|
||||
|
||||
# Test if HTTP status code 200 is returned from URI
|
||||
try {
|
||||
Invoke-WebRequest $edgeEnterpriseMSIUri -UseBasicParsing | Where-Object StatusCode -match 200 | Out-Null
|
||||
}
|
||||
catch {
|
||||
throw "Unable to get HTTP status code 200 from $edgeEnterpriseMSIUri. Does the URL still exist?"
|
||||
}
|
||||
|
||||
Write-Host "Getting available files from $edgeEnterpriseMSIUri" -ForegroundColor Green
|
||||
|
||||
# Try to get JSON data from Microsoft
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri $edgeEnterpriseMSIUri -Method Get -ContentType "application/json" -UseBasicParsing -ErrorVariable InvokeWebRequestError
|
||||
$jsonObj = ConvertFrom-Json $([String]::new($response.Content))
|
||||
Write-Host "Succefully retrived data" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
throw "Could not get MSI data: $InvokeWebRequestError"
|
||||
}
|
||||
|
||||
# Alternative is to use Invoke-RestMethod to get a Json object directly
|
||||
# $jsonObj = Invoke-RestMethod -Uri "https://edgeupdates.microsoft.com/api/products?view=enterprise" -UseBasicParsing
|
||||
|
||||
$selectedIndex = [array]::indexof($jsonObj.Product, "$Channel")
|
||||
|
||||
if (-not $ProductVersion) {
|
||||
try {
|
||||
Write-host "No version specified, getting the latest for $Channel" -ForegroundColor Green
|
||||
$selectedVersion = (([Version[]](($jsonObj[$selectedIndex].Releases |
|
||||
Where-Object { $_.Architecture -eq $Architecture -and $_.Platform -eq $Platform }).ProductVersion) |
|
||||
Sort-Object -Descending)[0]).ToString(4)
|
||||
|
||||
Write-Host "Latest Version for channel $Channel is $selectedVersion`n" -ForegroundColor Green
|
||||
$selectedObject = $jsonObj[$selectedIndex].Releases |
|
||||
Where-Object { $_.Architecture -eq $Architecture -and $_.Platform -eq $Platform -and $_.ProductVersion -eq $selectedVersion }
|
||||
}
|
||||
catch {
|
||||
throw "Unable to get object from Microsoft. Check your parameters and refer to script help."
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host "Matching $ProductVersion on channel $Channel" -ForegroundColor Green
|
||||
$selectedObject = ($jsonObj[$selectedIndex].Releases |
|
||||
Where-Object { $_.Architecture -eq $Architecture -and $_.Platform -eq $Platform -and $_.ProductVersion -eq $ProductVersion })
|
||||
|
||||
if (-not $selectedObject) {
|
||||
throw "No version matching $ProductVersion found in $channel channel for $Architecture architecture."
|
||||
}
|
||||
else {
|
||||
Write-Host "Found matching version`n" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (Test-Path $Folder) {
|
||||
foreach ($artifacts in $selectedObject.Artifacts) {
|
||||
# Not showing the progress bar in Invoke-WebRequest is quite a bit faster than default
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
Write-host "Starting download of: $($artifacts.Location)" -ForegroundColor Green
|
||||
# Work out file name
|
||||
$fileName = Split-Path $artifacts.Location -Leaf
|
||||
|
||||
if (Test-Path "$Folder\$fileName" -ErrorAction SilentlyContinue) {
|
||||
if ($Force) {
|
||||
Write-Host "Force specified. Will attempt to download and overwrite existing file." -ForegroundColor Green
|
||||
try {
|
||||
Invoke-WebRequest -Uri $artifacts.Location -OutFile "$Folder\$fileName" -UseBasicParsing
|
||||
}
|
||||
catch {
|
||||
throw "Attempted to download file, but failed: $error[0]"
|
||||
}
|
||||
}
|
||||
else {
|
||||
# CR-someday: There should be an evaluation of the file version, if possible. Currently the function only
|
||||
# checks if a file of the same name exists, not if the versions differ
|
||||
Write-Host "$Folder\$fileName already exists!" -ForegroundColor Yellow
|
||||
|
||||
do {
|
||||
$overWrite = Read-Host -Prompt "Press Y to overwrite or N to quit."
|
||||
}
|
||||
# -notmatch is case insensitive
|
||||
while ($overWrite -notmatch '^y$|^n$')
|
||||
|
||||
if ($overWrite -match '^y$') {
|
||||
Write-Host "Starting Download" -ForegroundColor Green
|
||||
try {
|
||||
Invoke-WebRequest -Uri $artifacts.Location -OutFile "$Folder\$fileName" -UseBasicParsing
|
||||
}
|
||||
catch {
|
||||
throw "Attempted to download file, but failed: $error[0]"
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host "File already exists and user chose not to overwrite, exiting script." -ForegroundColor Red
|
||||
exit
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host "Starting Download" -ForegroundColor Green
|
||||
try {
|
||||
Invoke-WebRequest -Uri $artifacts.Location -OutFile "$Folder\$fileName" -UseBasicParsing
|
||||
}
|
||||
catch {
|
||||
throw "Attempted to download file, but failed: $error[0]"
|
||||
}
|
||||
}
|
||||
if (((Get-FileHash -Algorithm $artifacts.HashAlgorithm -Path "$Folder\$fileName").Hash) -eq $artifacts.Hash) {
|
||||
Write-Host "Calculated checksum matches known checksum`n" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Warning "Checksum mismatch!"
|
||||
Write-Warning "Expected Hash: $($artifacts.Hash)"
|
||||
Write-Warning "Downloaded file Hash: $((Get-FileHash -Algorithm $($artifacts.HashAlgorithm) -Path "$Folder\$fileName").Hash)`n"
|
||||
}
|
||||
}
|
||||
}
|
||||
Else {
|
||||
throw "Folder $Folder does not exist"
|
||||
}
|
||||
Write-Host "-- Script Completed: File Downloaded -- " -ForegroundColor Green
|
||||
|
||||
Write-Host "Installing..." -ForegroundColor Green
|
||||
|
||||
cmd /c start /wait msiexec /i "$Folder\$fileName" /qn /norestart
|
||||
|
||||
Set-Service edgeupdate -StartupType Manual -ErrorAction SilentlyContinue
|
||||
Set-Service edgeupdatem -StartupType Manual -ErrorAction SilentlyContinue
|
||||
|
||||
Unregister-ScheduledTask -TaskName MicrosoftEdgeUpdateTaskMachineCore -Confirm:$false -ErrorAction SilentlyContinue
|
||||
Unregister-ScheduledTask -TaskName MicrosoftEdgeUpdateTaskMachineUA -Confirm:$false -ErrorAction SilentlyContinue
|
||||
|
||||
Write-Host "Installed Microsoft Edge" -ForegroundColor Green
|
2175
Applications/Firefox/Force-Update.ps1
Normal file
2175
Applications/Firefox/Force-Update.ps1
Normal file
File diff suppressed because it is too large
Load Diff
126
Applications/Flash Player/Force-Remove.ps1
Normal file
126
Applications/Flash Player/Force-Remove.ps1
Normal file
@ -0,0 +1,126 @@
|
||||
<#
|
||||
===========================================================================
|
||||
Created on: 0/01/2021 13:06
|
||||
Created by: Ben Whitmore
|
||||
Organization: -
|
||||
Filename: Install_Flash_Removal_KB4577586.ps1
|
||||
Target System: Windows 10 , Windows Server 2012/R2 | 2016 | 2019 | 1903 | 1909 | 2004
|
||||
===========================================================================
|
||||
|
||||
Version:
|
||||
1.2.1 - 22/01/2021
|
||||
Added support for Server OS - Thanks @Hoorge for the suggestion
|
||||
|
||||
1.2 - 04/01/2021
|
||||
Fixed 20H2 coding error - Credit @AndyUpperton
|
||||
|
||||
1.1 02/01/2021
|
||||
Basic Transcript Logging added
|
||||
|
||||
1.0 - 01/01/2021
|
||||
Release
|
||||
#>
|
||||
|
||||
#Set Current Directory
|
||||
$ScriptPath = $MyInvocation.MyCommand.Path
|
||||
$CurrentDir = Split-Path $ScriptPath
|
||||
|
||||
$Log = Join-Path $ENV:TEMP "Flash_Uninstall.log"
|
||||
Start-Transcript $Log
|
||||
|
||||
#Set WUSA.EXE Variable
|
||||
$WUSA = "$env:systemroot\System32\wusa.exe"
|
||||
|
||||
#Get OS Product Name
|
||||
$OS_ProductName = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion' ProductName).ProductName
|
||||
|
||||
#Build OS Version String
|
||||
Switch ($OS_ProductName) {
|
||||
{ $_.StartsWith("Windows 10") } { $OS_String = ($OS_ProductName -split "\s+" | Select-Object -First 2) -Join ' ' }
|
||||
{ $_.StartsWith("Windows Server 2012 R2") } { $OS_String = ($OS_ProductName -split "\s+" | Select-Object -First 4) -Join ' ' }
|
||||
{ ($_.StartsWith("Windows Server") -and (!($_.Contains("R2")))) } { $OS_String = ($OS_ProductName -split "\s+" | Select-Object -First 3) -Join ' ' }
|
||||
}
|
||||
|
||||
#Get OS Release ID for valid OS's
|
||||
If (!($OS_String -match "Server 2012")) {
|
||||
$OS_ReleaseID = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion' ReleaseId).ReleaseId
|
||||
}
|
||||
else {
|
||||
Write-Output "Skipping check of Release ID for $($OS_ProductName)"
|
||||
}
|
||||
|
||||
#Rename $OS_ReleaseID variable for "Windows 10 20H2" and "Windows Server, version 1909" because the same KB update is used for both 2004 and 2009
|
||||
If (($OS_ReleaseID -eq "2009" -and $OS_ProductName -match "Windows 10")) {
|
||||
$OS_ReleaseID = "2004"
|
||||
}
|
||||
|
||||
#Build OS Version Name variable
|
||||
Switch ($OS_String) {
|
||||
{ $_.Equals("Windows 10") } { $Version_String = $OS_String + " Version " + $OS_ReleaseID }
|
||||
{ $_.StartsWith("Windows Server 2") } { $Version_String = $OS_String }
|
||||
{ $_.StartsWith("Windows Server,") } { $Version_String = $OS_String + $OS_ReleaseID }
|
||||
}
|
||||
|
||||
#Get OS Architecture
|
||||
$OS_Architecture = Switch (Get-CIMInstance -Namespace "ROOT\CIMV2" -Class "Win32_Processor" | Select-Object -Unique -ExpandProperty Architecture) {
|
||||
9 { 'x64-based' }
|
||||
0 { 'x86-based' }
|
||||
5 { 'ARM64-based' }
|
||||
}
|
||||
|
||||
$PatchRequired = "Update for Removal of Adobe Flash Player for " + $Version_String + " for " + $OS_Architecture + " systems (KB4577586)"
|
||||
|
||||
#Get Patch Titles
|
||||
$PatchNames = Get-ChildItem $CurrentDir | Where-Object { $_.PSIsContainer } | Foreach-Object { $_.Name }
|
||||
|
||||
#Check if the patch has been downloaded for the current system
|
||||
$PatchFound = $False
|
||||
|
||||
#Check Installation
|
||||
$Patch = Get-Hotfix | Where-Object { $_.HotFixID -match "KB4577586" }
|
||||
If ($Patch) {
|
||||
Write-Host "Patch Already Installed"
|
||||
}
|
||||
else {
|
||||
Foreach ($Patch in $PatchNames) {
|
||||
If ($Patch -eq $PatchRequired) {
|
||||
$PatchFound = $True
|
||||
|
||||
#Get MSU from the correct Directory
|
||||
$MSU = Get-ChildItem (Join-Path $CurrentDir $Patch) -Recurse | Where-Object { $_.Extension -eq ".msu" }
|
||||
$MSUFullPath = Join-Path (Join-Path $CurrentDir $PatchRequired) $MSU.Name
|
||||
|
||||
#Set WUSA Args
|
||||
$Args = @(
|
||||
"""$MSUFullPath"""
|
||||
"/quiet"
|
||||
"/norestart"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#Patch detection determines outcome
|
||||
If ($PatchFound) {
|
||||
Write-Host "Patch found for this system"
|
||||
Write-Host "Patch Required: $($PatchRequired)"
|
||||
Write-Host "Patch Name: $($MSU.Name)"
|
||||
Write-Host "Installing Update..."
|
||||
|
||||
#Install Patch
|
||||
Start-Process -FilePath $WUSA -ArgumentList $Args -Wait
|
||||
|
||||
#Check Installation
|
||||
$Patch = Get-Hotfix | Where-Object { $_.HotFixID -match "KB4577586" }
|
||||
If ($Patch) {
|
||||
Write-Host "Patch Installed Successfully"
|
||||
}
|
||||
Else {
|
||||
Write-Warning "Patch Installation Failed"
|
||||
}
|
||||
}
|
||||
Else {
|
||||
Write-Host "Patch not found for this system"
|
||||
Write-Host "Patch Required: $($PatchRequired)"
|
||||
}
|
||||
}
|
||||
Stop-Transcript
|
2268
Applications/Java/Force-Update.ps1
Normal file
2268
Applications/Java/Force-Update.ps1
Normal file
File diff suppressed because it is too large
Load Diff
24
Applications/Notepad++/Force-Update.ps1
Normal file
24
Applications/Notepad++/Force-Update.ps1
Normal file
@ -0,0 +1,24 @@
|
||||
# Modern websites require TLS 1.2
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
|
||||
#requires -RunAsAdministrator
|
||||
|
||||
# Let's go directly to the website and see what it lists as the current version
|
||||
$BaseUri = "https://notepad-plus-plus.org"
|
||||
$BasePage = Invoke-WebRequest -Uri $BaseUri -UseBasicParsing
|
||||
$ChildPath = $BasePage.Links | Where-Object { $_.outerHTML -like '*Current Version*' } | Select-Object -ExpandProperty href
|
||||
# Now let's go to the latest version's page and find the installer
|
||||
$DownloadPageUri = $BaseUri + $ChildPath
|
||||
$DownloadPage = Invoke-WebRequest -Uri $DownloadPageUri -UseBasicParsing
|
||||
# Determine bit-ness of O/S and download accordingly
|
||||
if ( [System.Environment]::Is64BitOperatingSystem ) {
|
||||
$DownloadUrl = $DownloadPage.Links | Where-Object { $_.outerHTML -like '*npp.*.Installer.x64.exe"*' } | Select-Object -ExpandProperty href -Unique
|
||||
} else {
|
||||
$DownloadUrl = $DownloadPage.Links | Where-Object { $_.outerHTML -like '*npp.*.Installer.exe"*' } | Select-Object -ExpandProperty href -Unique
|
||||
}
|
||||
|
||||
Write-Host "Downloading the latest Notepad++ to the temp folder"
|
||||
Invoke-WebRequest -Uri $DownloadUrl -OutFile "$env:TEMP\$( Split-Path -Path $DownloadUrl -Leaf )" | Out-Null
|
||||
|
||||
Write-Host "Installing the latest Notepad++"
|
||||
Start-Process -FilePath "$env:TEMP\$( Split-Path -Path $DownloadUrl -Leaf )" -ArgumentList "/S" -Wait
|
437
Applications/Zoom/Force-Remove.ps1
Normal file
437
Applications/Zoom/Force-Remove.ps1
Normal file
@ -0,0 +1,437 @@
|
||||
<#
|
||||
tes
|
||||
.SYNOPSIS
|
||||
Uninstalls all Zoom applications registered with the Windows installer.
|
||||
Whether they are installed to the local computer or the users profile.
|
||||
|
||||
.DESCRIPTION
|
||||
Searches registry for applications registered with 'Zoom' as publisher.
|
||||
If any found, the uninstall string is retrieved and used to uninstall the application.
|
||||
If applications are found to be installed in the users profile, the users context is invoked and the application is uninstalled coming from SYSTEM context.
|
||||
|
||||
.NOTES
|
||||
Filename: Uninstall-EverythingZoom.ps1
|
||||
Version: 1.0
|
||||
Author: Martin Bengtsson
|
||||
Blog: www.imab.dk
|
||||
Twitter: @mwbengtsson
|
||||
|
||||
.LINK
|
||||
https://www.imab.dk/uninstall-all-zoom-applications-in-a-jiffy-using-configuration-manager-and-powershell/
|
||||
#>
|
||||
|
||||
function Execute-AsLoggedOnUser($Command,$Hidden=$true) {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Function that can execute powershell in the context of the logged-in user.
|
||||
.DESCRIPTION
|
||||
This function will use advanced API's to get the access token of the currently logged-in user, in order to execute a script in the users context.
|
||||
This is useful for scripts that are run in the local system users context.
|
||||
.REQUIREMENTS
|
||||
This script myst be run from the context of the SYSTEM account.
|
||||
Designes to be run by Intune or SCCM Agent.
|
||||
Absolute paths required.
|
||||
.EXAMPLE
|
||||
Running a powershell script visible to the user
|
||||
$userCommand = '-file c:\windows\temp\script.ps1'
|
||||
executeAsLoggedOnUser -Command $userCommand -Hidden $false
|
||||
.EXAMPLE
|
||||
Running a powershell command hidden from the user (hidden is default true)
|
||||
$userCommand = '-command &{remove-item c:\temp\secretfile.txt}'
|
||||
executeAsLoggedOnUser -Command $userCommand
|
||||
.COPYRIGHT
|
||||
MIT License, feel free to distribute and use as you like, please leave author information.
|
||||
.AUTHOR
|
||||
Michael Mardahl - @michael_mardahl on twitter - BLOG: https://www.iphase.dk
|
||||
C# borrowed from the awesome Justin Myrray (https://github.com/murrayju/CreateProcessAsUser)
|
||||
.DISCLAIMER
|
||||
This function is provided AS-IS, with no warranty - Use at own risk!
|
||||
#>
|
||||
|
||||
$csharpCode = @"
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace murrayju.ProcessExtensions
|
||||
{
|
||||
public static class ProcessExtensions
|
||||
{
|
||||
#region Win32 Constants
|
||||
|
||||
private const int CREATE_UNICODE_ENVIRONMENT = 0x00000400;
|
||||
private const int CREATE_NO_WINDOW = 0x08000000;
|
||||
|
||||
private const int CREATE_NEW_CONSOLE = 0x00000010;
|
||||
|
||||
private const uint INVALID_SESSION_ID = 0xFFFFFFFF;
|
||||
private static readonly IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;
|
||||
|
||||
#endregion
|
||||
|
||||
#region DllImports
|
||||
|
||||
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
|
||||
private static extern bool CreateProcessAsUser(
|
||||
IntPtr hToken,
|
||||
String lpApplicationName,
|
||||
String lpCommandLine,
|
||||
IntPtr lpProcessAttributes,
|
||||
IntPtr lpThreadAttributes,
|
||||
bool bInheritHandle,
|
||||
uint dwCreationFlags,
|
||||
IntPtr lpEnvironment,
|
||||
String lpCurrentDirectory,
|
||||
ref STARTUPINFO lpStartupInfo,
|
||||
out PROCESS_INFORMATION lpProcessInformation);
|
||||
|
||||
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
|
||||
private static extern bool DuplicateTokenEx(
|
||||
IntPtr ExistingTokenHandle,
|
||||
uint dwDesiredAccess,
|
||||
IntPtr lpThreadAttributes,
|
||||
int TokenType,
|
||||
int ImpersonationLevel,
|
||||
ref IntPtr DuplicateTokenHandle);
|
||||
|
||||
[DllImport("userenv.dll", SetLastError = true)]
|
||||
private static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
|
||||
|
||||
[DllImport("userenv.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool CloseHandle(IntPtr hSnapshot);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern uint WTSGetActiveConsoleSessionId();
|
||||
|
||||
[DllImport("Wtsapi32.dll")]
|
||||
private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken);
|
||||
|
||||
[DllImport("wtsapi32.dll", SetLastError = true)]
|
||||
private static extern int WTSEnumerateSessions(
|
||||
IntPtr hServer,
|
||||
int Reserved,
|
||||
int Version,
|
||||
ref IntPtr ppSessionInfo,
|
||||
ref int pCount);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Win32 Structs
|
||||
|
||||
private enum SW
|
||||
{
|
||||
SW_HIDE = 0,
|
||||
SW_SHOWNORMAL = 1,
|
||||
SW_NORMAL = 1,
|
||||
SW_SHOWMINIMIZED = 2,
|
||||
SW_SHOWMAXIMIZED = 3,
|
||||
SW_MAXIMIZE = 3,
|
||||
SW_SHOWNOACTIVATE = 4,
|
||||
SW_SHOW = 5,
|
||||
SW_MINIMIZE = 6,
|
||||
SW_SHOWMINNOACTIVE = 7,
|
||||
SW_SHOWNA = 8,
|
||||
SW_RESTORE = 9,
|
||||
SW_SHOWDEFAULT = 10,
|
||||
SW_MAX = 10
|
||||
}
|
||||
|
||||
private enum WTS_CONNECTSTATE_CLASS
|
||||
{
|
||||
WTSActive,
|
||||
WTSConnected,
|
||||
WTSConnectQuery,
|
||||
WTSShadow,
|
||||
WTSDisconnected,
|
||||
WTSIdle,
|
||||
WTSListen,
|
||||
WTSReset,
|
||||
WTSDown,
|
||||
WTSInit
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct PROCESS_INFORMATION
|
||||
{
|
||||
public IntPtr hProcess;
|
||||
public IntPtr hThread;
|
||||
public uint dwProcessId;
|
||||
public uint dwThreadId;
|
||||
}
|
||||
|
||||
private enum SECURITY_IMPERSONATION_LEVEL
|
||||
{
|
||||
SecurityAnonymous = 0,
|
||||
SecurityIdentification = 1,
|
||||
SecurityImpersonation = 2,
|
||||
SecurityDelegation = 3,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct STARTUPINFO
|
||||
{
|
||||
public int cb;
|
||||
public String lpReserved;
|
||||
public String lpDesktop;
|
||||
public String lpTitle;
|
||||
public uint dwX;
|
||||
public uint dwY;
|
||||
public uint dwXSize;
|
||||
public uint dwYSize;
|
||||
public uint dwXCountChars;
|
||||
public uint dwYCountChars;
|
||||
public uint dwFillAttribute;
|
||||
public uint dwFlags;
|
||||
public short wShowWindow;
|
||||
public short cbReserved2;
|
||||
public IntPtr lpReserved2;
|
||||
public IntPtr hStdInput;
|
||||
public IntPtr hStdOutput;
|
||||
public IntPtr hStdError;
|
||||
}
|
||||
|
||||
private enum TOKEN_TYPE
|
||||
{
|
||||
TokenPrimary = 1,
|
||||
TokenImpersonation = 2
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct WTS_SESSION_INFO
|
||||
{
|
||||
public readonly UInt32 SessionID;
|
||||
|
||||
[MarshalAs(UnmanagedType.LPStr)]
|
||||
public readonly String pWinStationName;
|
||||
|
||||
public readonly WTS_CONNECTSTATE_CLASS State;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Gets the user token from the currently active session
|
||||
private static bool GetSessionUserToken(ref IntPtr phUserToken)
|
||||
{
|
||||
var bResult = false;
|
||||
var hImpersonationToken = IntPtr.Zero;
|
||||
var activeSessionId = INVALID_SESSION_ID;
|
||||
var pSessionInfo = IntPtr.Zero;
|
||||
var sessionCount = 0;
|
||||
|
||||
// Get a handle to the user access token for the current active session.
|
||||
if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref sessionCount) != 0)
|
||||
{
|
||||
var arrayElementSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
|
||||
var current = pSessionInfo;
|
||||
|
||||
for (var i = 0; i < sessionCount; i++)
|
||||
{
|
||||
var si = (WTS_SESSION_INFO)Marshal.PtrToStructure((IntPtr)current, typeof(WTS_SESSION_INFO));
|
||||
current += arrayElementSize;
|
||||
|
||||
if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive)
|
||||
{
|
||||
activeSessionId = si.SessionID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If enumerating did not work, fall back to the old method
|
||||
if (activeSessionId == INVALID_SESSION_ID)
|
||||
{
|
||||
activeSessionId = WTSGetActiveConsoleSessionId();
|
||||
}
|
||||
|
||||
if (WTSQueryUserToken(activeSessionId, ref hImpersonationToken) != 0)
|
||||
{
|
||||
// Convert the impersonation token to a primary token
|
||||
bResult = DuplicateTokenEx(hImpersonationToken, 0, IntPtr.Zero,
|
||||
(int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary,
|
||||
ref phUserToken);
|
||||
|
||||
CloseHandle(hImpersonationToken);
|
||||
}
|
||||
|
||||
return bResult;
|
||||
}
|
||||
|
||||
public static bool StartProcessAsCurrentUser(string cmdLine, bool visible, string appPath = null, string workDir = null)
|
||||
{
|
||||
var hUserToken = IntPtr.Zero;
|
||||
var startInfo = new STARTUPINFO();
|
||||
var procInfo = new PROCESS_INFORMATION();
|
||||
var pEnv = IntPtr.Zero;
|
||||
int iResultOfCreateProcessAsUser;
|
||||
|
||||
startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO));
|
||||
|
||||
try
|
||||
{
|
||||
if (!GetSessionUserToken(ref hUserToken))
|
||||
{
|
||||
throw new Exception("StartProcessAsCurrentUser: GetSessionUserToken failed.");
|
||||
}
|
||||
|
||||
uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW);
|
||||
startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE);
|
||||
startInfo.lpDesktop = "winsta0\\default";
|
||||
|
||||
if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false))
|
||||
{
|
||||
throw new Exception("StartProcessAsCurrentUser: CreateEnvironmentBlock failed.");
|
||||
}
|
||||
|
||||
if (!CreateProcessAsUser(hUserToken,
|
||||
appPath, // Application Name
|
||||
cmdLine, // Command Line
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
false,
|
||||
dwCreationFlags,
|
||||
pEnv,
|
||||
workDir, // Working directory
|
||||
ref startInfo,
|
||||
out procInfo))
|
||||
{
|
||||
throw new Exception("StartProcessAsCurrentUser: CreateProcessAsUser failed.\n");
|
||||
}
|
||||
|
||||
iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseHandle(hUserToken);
|
||||
if (pEnv != IntPtr.Zero)
|
||||
{
|
||||
DestroyEnvironmentBlock(pEnv);
|
||||
}
|
||||
CloseHandle(procInfo.hThread);
|
||||
CloseHandle(procInfo.hProcess);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
"@
|
||||
# Compiling the source code as csharp
|
||||
$compilerParams = [System.CodeDom.Compiler.CompilerParameters]::new()
|
||||
$compilerParams.ReferencedAssemblies.AddRange(('System.Runtime.InteropServices.dll', 'System.dll'))
|
||||
$compilerParams.CompilerOptions = '/unsafe'
|
||||
$compilerParams.GenerateInMemory = $True
|
||||
Add-Type -TypeDefinition $csharpCode -Language CSharp -CompilerParameters $compilerParams
|
||||
# Adding powershell executeable to the command
|
||||
$Command = '{0}\System32\WindowsPowerShell\v1.0\powershell.exe -executionPolicy bypass {1}' -f $($env:windir),$Command
|
||||
# Adding double slashes to the command paths, as this is required.
|
||||
$Command = $Command.Replace("\","\\")
|
||||
# Execute a process as the currently logged on user.
|
||||
# Absolute paths required if running as SYSTEM!
|
||||
if($Hidden) { #running the command hidden
|
||||
$runCommand = [murrayju.ProcessExtensions.ProcessExtensions]::StartProcessAsCurrentUser($Command,$false)
|
||||
}else{ #running the command visible
|
||||
$runCommand = [murrayju.ProcessExtensions.ProcessExtensions]::StartProcessAsCurrentUser($Command,$true)
|
||||
}
|
||||
|
||||
if ($runCommand) {
|
||||
return "Executed `"$Command`" as loggedon user"
|
||||
} else {
|
||||
throw "Something went wrong when executing process as currently logged-on user"
|
||||
}
|
||||
}
|
||||
|
||||
function Uninstall-ZoomLocalMachine() {
|
||||
|
||||
Write-Verbose -Verbose -Message "Running Uninstall-ZoomLocalMachine function"
|
||||
$registryPath = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
|
||||
if (Test-Path -Path $registryPath) {
|
||||
$installedZoomApps = Get-ChildItem -Path $registryPath -Recurse | Get-ItemProperty | Where-Object {$_.Publisher -like "Zoom*" } | Select-Object Displayname,UninstallString
|
||||
if ($installedZoomApps) {
|
||||
Write-Verbose -Verbose -Message "Installed Zoom applications found in HKLM"
|
||||
foreach ($zoomApp in $installedZoomApps) {
|
||||
if ($zoomApp.UninstallString) {
|
||||
# Regular expression for format of MSI product code
|
||||
$msiRegEx = "\w{8}-\w{4}-\w{4}-\w{4}-\w{12}"
|
||||
# Formatting the productcode in a creative way.
|
||||
# Needed this separately, as the uninstall string retrieved from registry sometimes wasn't formatted properly
|
||||
$a = $zoomApp.Uninstallstring.Split("{")[1]
|
||||
$b = $a.Split("}")[0]
|
||||
# Only continuing if the uninstall string matches a regular MSI product code
|
||||
if ($b -match $msiRegEx) {
|
||||
$productCode = "{" + $b + "}"
|
||||
if ($productCode) {
|
||||
try {
|
||||
Write-Verbose -Verbose -Message "Uninstalling application: $($zoomApp.DisplayName)"
|
||||
Start-Process "C:\Windows\System32\msiexec.exe" -ArgumentList ("/x" + $productCode + " /passive") -Wait
|
||||
}
|
||||
|
||||
catch {
|
||||
Write-Error -Message "Failed to uninstall application: $($zoomApp.DisplayName)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Verbose -Message "No Zoom applications found in HKLM"
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Verbose -Message "Registry path not found"
|
||||
}
|
||||
}
|
||||
|
||||
function Uninstall-ZoomCurrentUser() {
|
||||
|
||||
Write-Verbose -Verbose -Message "Running Uninstall-ZoomCurrentUser function"
|
||||
# Getting all user profiles on the computer
|
||||
$userProfiles = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\*" | Where-Object {$_.PSChildName -match "S-1-5-21-(\d+-?){4}$"} | Select-Object @{Name="SID"; Expression={$_.PSChildName}}, @{Name="UserHive";Expression={"$($_.ProfileImagePath)\NTuser.dat"}}
|
||||
foreach ($userProfile in $userProfiles) {
|
||||
# Formatting the username in a separate variable
|
||||
$userName = $userProfile.UserHive.Split("\")[2]
|
||||
$registryPath = "Registry::HKEY_USERS\$($UserProfile.SID)\Software\Microsoft\Windows\CurrentVersion\Uninstall"
|
||||
if (Test-Path -Path $registryPath) {
|
||||
$installedZoomApps = Get-ChildItem -Path $registryPath -Recurse | Get-ItemProperty | Where-Object {$_.Publisher -like "Zoom*" } | Select-Object Displayname,UninstallString
|
||||
if ($installedZoomApps) {
|
||||
Write-Verbose -Verbose -Message "Installed Zoom applications found in HKCU for user: $userName"
|
||||
foreach ($zoomApp in $installedZoomApps) {
|
||||
if ($zoomApp.UninstallString) {
|
||||
$userCommand = '-command &{Start-Process "C:\Users\USERNAME\AppData\Roaming\Zoom\uninstall\Installer.exe" -ArgumentList "/uninstall" -Wait}'
|
||||
# Replacing the placeholder: USERNAME with the actual username retrieved from the userprofile
|
||||
# This can probably be done smarter, but I failed to find another method
|
||||
$userCommand = $userCommand -replace "USERNAME",$userName
|
||||
try {
|
||||
Write-Verbose -Verbose -Message "Uninstalling application: $($zoomApp.DisplayName) as the logged on user: $userName"
|
||||
Execute-AsLoggedOnUser -Command $userCommand
|
||||
}
|
||||
catch {
|
||||
Write-Error -Message "Failed to uninstall application: $($zoomApp.DisplayName) for user: $userName"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Verbose -Message "No Zoom applications found in HKCU for user: $userName"
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Verbose -Verbose -Message "Registry path not found for user: $userName"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Write-Verbose -Verbose -Message "Script is running"
|
||||
Uninstall-ZoomLocalMachine
|
||||
Uninstall-ZoomCurrentUser
|
||||
}
|
||||
|
||||
catch {
|
||||
Write-Verbose -Verbose -Message "Something went wrong during running of the script"
|
||||
}
|
||||
|
||||
finally {
|
||||
Write-Verbose -Verbose -Message "Script is done running"
|
||||
}
|
Reference in New Issue
Block a user