I am using the following code to uninstall google chrome software from my remote machines, But this script is executed with no output. And when i check the control panel, still the google chrome program exists. Can someone check this code?
foreach($computer in (Get-Content \path\to\the\file))
{
$temp1 = Get-WmiObject -Class Win32_Product -ComputerName $computer | where { $_.name -eq "Google Chrome"}
$temp1.Uninstall()
}
You shouldn't use the Win32_Product WMI class, one of the side effects of enumeration operations is it checks the integrity of each installed program and performs a repair installation if the integrity check fails.
It is safer to query the registry for this information instead, which also happens to contain the uninstall string for removing the product with msiexec. The uninstall string here will be formatted like MsiExec.exe /X{PRODUCT_CODE_GUID}, with PRODUCT_CODE_GUID replaced with the actual product code for that software.
Note: This approach will only work for products installed with an MSI installer (or setup executables which extract and install MSIs). For pure executable installers which make no use of MSI installation, you'll need to consult the product documentation for how to uninstall that software, and find another method (such as a well-known installation location) of identifying whether that software is installed or not.
Note 2: I'm not sure when this changed but ChromeSetup.exe no longer wraps an MSI as it used to. I have modified the code below to handle the removal of both the MSI-installed and EXE-installed versions of Chrome.
# We need to check both 32 and 64 bit registry paths
$regPaths =
"HKLM:\SOFTWARE\Wow6432node\Microsoft\Windows\CurrentVersion\Uninstall",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
# Since technically you could have 32 and 64 bit versions of the same
# software, force $uninstallStrings to be an array to cover that case
# if this is reused elsewhere. Chrome usually should only have one or the
# other, however.
$productCodes = #( $regPaths | Foreach-Object {
Get-ItemProperty "${_}\*" | Where-Object {
$_.DisplayName -eq 'Google Chrome'
}
} ).PSPath
# Run the uninstall string (formatted like
$productCodes | ForEach-Object {
$keyName = ( Get-ItemProperty $_ ).PSChildName
# GUID check (if the previous key was not a product code we'll need a different removal strategy)
if ( $keyName -match '^{[a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}}$' ) {
# Use Start-Process with -Wait to wait for PowerShell to finish
# /qn suppresses the prompts for automation
$p = Start-Process -Wait msiexec -ArgumentList "/l*v ""$($pwd.FullName)/chromeuninst.log"" /X${keyName} /qn" -PassThru
# 0 means success, but 1638 means uninstallation is pending a reboot to complete
# This should still be considered a successful execution
$acceptableExitCodes = 0, 1638
}
else {
Write-Host "Stopping all running instances of chrome.exe, if any are running"
Get-Process chrome.exe -EA Ignore | Stop-Process -Force
# The registry key still has an uninstall string
# But cannot be silently removed
# So we will have to get creating and control the uninstall window with PowerShell
# We need to use the undocumented --force-uninstall parameter, added to below command
$uninstallString = "$(( Get-ItemProperty $_).UninstallString )) --force-uninstall"
# Break up the string into the executable and arguments so we can wait on it properly with Start-Process
$firstQuoteIdx = $uninstallString.IndexOf('"')
$secondQuoteIdx = $uninstallString.IndexOf('"', $firstQuoteIdx + 1)
$setupExe = $uninstallString[$firstQuoteIdx..$secondQuoteIdx] -join ''
$setupArgs = $uninstallString[( $secondQuoteIdx + 1 )..$uninstallString.Length] -join ''
Write-Host "Uninstallation command: ${setupExe} ${setupArgs}"
$p = Start-Process -Wait -FilePath $setupExe -ArgumentList $setupArgs -PassThru
# My testing shows this exits on exit code 19 for success. However, this is undocumented
# behavior so you may need to tweak the list of acceptable exit codes or remove this check
# entirely.
#
$acceptableExitCodes = 0, 19
}
if ( $p.ExitCode -notin $acceptableExitCodes ) {
Write-Error "Program exited with $($p.ExitCode)"
$p.Dispose()
exit $p.ExitCode
}
exit 0
}
Incidentally, if you already know the MSI ProductCode of a given program you don't have to find the uninstall string this way. You can simply execute msiexec /X{PRODUCT_CODE_GUID}.
If you have further problems which aren't caused by the syntax of the above this would be an operational issue and would be better troubleshot over at the https://superuser.com site.
Edit
As discovered via our chat conversation, you are installing per user and with the 79.0.3945.130 version. You can remove per user Chrome with the following command if installed per user (if the version is different you will need the correct version path):
&"C:\Users\username\AppData\Local\Google\Chrome\Application\79.0.3945.130\Installer\setup.exe" --uninstall --channel=stable --verbose-logging --force-uninstall
In the future, it is not recommended to use ChromeSetup.exe or ChromeStandaloneSetup64.exe to install and manage Chrome in an enterprise environment, you should instead use the Enterprise MSI and install system-wide so you can manage Chrome more efficiently. This is the supported way to deploy Chrome in an enterprise environment, and the script I provided will work to uninstall Chrome via msiexec and searching the registry for the {PRODUCT_CODE} as provided.
Assuming it's an msi install and remote powershell is enabled:
invoke-command -computername comp001 { uninstall-package 'google chrome' }
For the programs provider (all users), it's something like:
get-package *chrome* | % { $_.metadata['uninstallstring'] }
"C:\Program Files\Google\Chrome\Application\95.0.4638.54\Installer\setup.exe" --uninstall --channel=stable --system-level --verbose-logging
And then run that uninstallstring, but you'd have to figure out the silent uninstall option (--force-uninstall). It also runs in the background.
Related
I am writing a script in Power-shell that does basically the same thing that WSUS servers do,
Following the steps below:
WSUS server Downloads and installs the required patches on the client systems.
The PS script if patch is not successfully installed retries the installation on the client systems, by getting & executing the patch installation file (.cab,.msu,.exe) from the client systems.
The patches are selected by a independent process and send to a 3rd party location from where the PS script gets it.
The issue I am facing is the data in the 3rd party location for the patches contains UPDATE_ID as a patch identifier and I need a way to recognise whats is the UPDATE_ID for the existing patch installation file is.
NOTE: I can work through Hotfix_ID as the directory patches are stored in naming standards similar to "$Hotfix_ID". But single Hotfix_ID can have multiple patches.
The affected code is as follows:
param(
$KbFailed
)
foreach($KbNumber IN $KbFailed){
[array]$files_att=Get-ChildItem "C:\Windows\SoftwareDistribution\Download\*\*$KbNumber*"
Write-Host "Installing $KbNumber ..."
if ($null -eq $files_att){
Write-Error "Required file not found for $KbNumber"
}else{
foreach($file_att IN $files_att){
$file_extension=($file_att |Select-Object Extension).Extension
if ($file_extension -eq ".cab"){
dism.exe /online /add-package /PackagePath:$file_att /NoRestart
}
elseif($file_extension -eq ".exe"){
Start-Process "$file_att" -ArgumentList "/q" -Wait
}
elseif($file_extension -eq ".msu"){
Start-Process -FilePath "wusa.exe" -ArgumentList "$file_att /quiet /norestart" -Wait
}
else{
Write-Error "File type $file_extension not supported"
}
}
}
}
As mentioned I am managing it right now with HotFix_ID(Kb_number), but would like to replace it with UPDATE_ID
I wrote a ps1 script to automate some package installation but the strange part is when I run the command snippet for executing the .exe file for SEP (Symantec Endpoint Protection) , it is executing fine , but when I execute the entire script , it does run the command snippet.
Iam only running a simple .exe file , and even if I run it manually , it does not show any installer , rather it installs silently in the background.
So in the script, Iam only running the .exe file, thats it .
Should I be giving any wait time or any other inputs ?
Start-Process -Wait -FilePath "C:\Temp\Symantec-Windows\SEP 14.3.3384.1000 x64.exe" -passthru
$SymVersion = Get-WmiObject -Class Win32_Product -ComputerName $hostname | Where-Object -FilterScript {$_.Name -eq "symantec endpoint protection"} | Format-List -Property version, InstallState, name
echo $SymVersion
if($SymVersion)
{
echo 'Symantec is successfully installed' -ForegroundColor Green
}
else
{
echo 'Symantec is not successfully installed' -ForegroundColor Red
}
The symantec antivirus exe files are made for silent installations. If you want to proceed with GUI mode, better unzip the file and use MSI file with arguments. With your current script,Its better to check the process is exited with code 0. The following code is not tested.
$process = Start-Process -FilePath "C:\Temp\Symantec-Windows\SEP 14.3.3384.1000 x64.exe" -passthru -Wait
if($process.ExitCode -ne 0)
{
throw "Installation process returned error code: $($process.ExitCode)"
} else { Write-Host "Installation Successful"}
I have an update script for running the Dell Command Update tool. In short dcu-cli.exe. The thing now is than when i run the same script code on the computer local then everything runs OK but when i run the exact same code in a script with invoke-command(and yes i have full admin rights) than the exitcode is 2 meaning An unknown application error has occurred instead of 0 (everything OK)
It is a very large script so i created a new one to debug this. This is the shorted code:
Invoke-Command -ComputerName "MyComputer" -ScriptBlock {
$ExitCode = 0
#Declare path and arguments
$DcuCliPath = 'C:\Program Files (x86)\Dell\CommandUpdate\dcu-cli.exe'
$DellCommand = "/applyUpdates -autoSuspendBitLocker=enable -outputLog=C:\Dell_Update.log"
#Verify Dell Command | Update exists
If (Test-Path -Path $DcuCliPath) {
$objWMI = Get-WmiObject Win32_ComputerSystem
Write-Host ("Dell Model [{0}]" -f $objWMI.Model.Trim())
$serviceName = "DellClientManagementService"
Write-Host ("Service [{0}] is currently [{1}]" -f $serviceName, (Get-Service $serviceName).Status)
If ((Get-Service $serviceName).Status -eq 'Stopped') {
Start-Service $serviceName
Write-Host "Service [$serviceName] started"
}
#Update the system with the latest drivers
Write-Host "Starting Dell Command | Update tool with arguments [$DellCommand] dcu-cli found at [$DcuCliPath]"
$ExitCode = (Start-Process -FilePath ($DcuCliPath) -ArgumentList ($DellCommand) -PassThru -Wait).ExitCode
Write-Host ("Dell Command | Update tool finished with ExitCode: [$ExitCode] current Win32 ExitCode: [$LastExitCode] Check log for more information: C:\Dell_Update.log")
}
}
When i remove the Invoke-Command -ComputerName "MyComputer" -ScriptBlock { and then copy + run the script local on the PC then the exitcode = 0
What i also noticed than when i run the command via 'Invoke-Command' then there is also no log file created as i passed along in the arguments... So my best guess is something is going wrong with local an remote paths?
So what am i missing? I'm guessing it is something simple but i spend several hours to get this running without any luck...
Try running it this way. You should be able to see any output or error messages. I typically add to the path first rather than using & or start-process.
invoke-command mycomputer {
$env:path += ';C:\Program Files (x86)\Dell\CommandUpdate';
dcu-cli /applyUpdates -autoSuspendBitLocker=enable -outputLog=C:\Dell_Update.log }
Using start-process inside invoke-command seems pretty challenging. I can't even see the output of findstr unless I save it to a file. And if I didn't wait the output would be truncated. By default start-process runs in the background and in another window. There's a -nonewwindow option too but it doesn't help with invoke-command.
invoke-command localhost { # elevated
start-process 'findstr' '/i word c:\users\joe\file1' -wait -RedirectStandardOutput c:\users\joe\out }
#js2010, thanks for your additional help. Unfortunately this didn't helped either.
So i did some more debugging and it turns out it was a bug in the dcu-cli version running on my test machine, DOH...!!
On the test machine version 3.1.1 was running and on another machine version 4.0 was running and that worked fine via remote Powershell. So i looked for the release notes, which i found here: https://www.dell.com/support/kbdoc/000177325/dell-command-update
And as you can see in version 3.1.3 there was this fix:
A problem was solved where dcu-cli.exe was not executed in an external interactive session of PowerShell.
How to download latest Git for Windows using cmd or powershell ? (or other built-in windows software)
Currently, i have a script to check if Windows is x32 or x64 :
# eq is equal
# ne is not equal
if ((gwmi win32_operatingsystem | select osarchitecture).osarchitecture -ne "64-bit")
{
#32 bit logic here
Write "32-bit OS"
Read-Host -Prompt "Press Enter to continue"
}
else
{
#64 bit logic here
Write "64-bit OS"
Read-Host -Prompt "Press Enter to continue."
}
Thanks in advance!
The easiest approach would be to use chocolatey on the target machine. After chocolatey is installed a simple:
choco install git
downloads and installs git for windows. Check the package site first, if the latest version has been packaged. It might sometimes take a couple of days after the release of a new version before that happens.
If you are looking for a manual way to download the latest release from github, you can use the github api.
First figure out the name of the asset, that you want to download. They are listed here : https://github.com/git-for-windows/git/releases/latest. The assets you are interested in look like this:
Git-2.25.1-64-bit.exe
Git-<version>-<architecture>.exe
Now get the page you were just looking at as a json object. The github api does that for you: https://api.github.com/repos/git-for-windows/git/releases/latest
Finally find your asset in there and pass the download link to Invoke-WebRequest.
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$architecture = '64-bit'
$assetName = "Git-*-$architecture.exe"
$gitHubApi = 'https://api.github.com/repos/git-for-windows/git/releases/latest'
$response = Invoke-WebRequest -Uri $gitHubApi -UseBasicParsing
$json = $response.Content | ConvertFrom-Json
$release = $json.assets | Where-Object Name -like $assetName
Invoke-WebRequest $release.browser_download_url -OutFile ".\$($release.name)"
Note: Depending on the scale on which you want to use this, you should know that the github api allows only 60 calls per hour, if you are an unauthenticated user: https://developer.github.com/v3/#rate-limiting
So I currently have a Powershell script that installs various software silently
What I have at the moment are all of the commands which work perfectly:
Start-Process 'C:\Files\Install files\Firefox' -ArgumentList "/S" -Wait
Start-Process 'C:\Files\Install files\Office' -ArgumentList "/s" -Wait
Start-Process 'C:\Files\Install files\Zeb' -ArgumentList "/S" -Wait
What I now want to implement is 'checking if the installation is succeeded'.
I start the program by checking the install files:
Write-Host "Checking if install files are present..."
if(Test-Path 'C:\Files\Install files\')
{
Write-Host "Files located. Installation will begin"
Write-Host ""
} else
{
Write-Host "Files not located, please check the directory"
Write-Host ""
break
}
And since the code of above is working I thought that I could maybe to the same for installing my software? But using the software directory as a test-path function:
Write-Host "Installing Firefox"
Start-Process 'C:\Files\Install files\Firefox' -ArgumentList "/S" -Wait
if (Test-Path C:\Program Files(x86)\Firefox)
{
Write-Host "Firefox succesfully installed!"
}else
{
Write-Host "Error, FireFox hasn't been installed"
}
.... And continue like this for the other programs
I thought 'why not?' since all of the PC's are the same and I must admit that the code also works.
But how would someone would judge this method? And what are alternatives?
Thanks
Just a note, I think this is more a question for Server Fault or Super User.
There are some high end tools like Desired State Configuration that handle all this but it really depends on the environment. We build standalone servers in an offsite datacentre and it's so much more convenient and less setup to just have a script that installs and configures everything that can't be imaged. Sure it takes two hours to complete but it's unattended, and since the OS is installed from a template we guarantee they're all exactly the same.
Perhaps I wouldn't recommend this method if the hardware were drastically different or if not imaging the OS to run the script on, I used to have a batch file to deploy customer desktops (back in WinXP days) but eventually cut it down to just detect the model and install drivers because it was too much hassle to maintain. I would also not recommend this method if it will be maintained by more than one person, or if the person maintaining it will be maybe/definitely not long term staff.
Overall it's up to the company. DSC is certainly a more professional and complete approach and much more easily maintained by multiple people, so put it to them and if they won't devote resources for a deployment server and training then you don't have much choice either way.
I would recommend to have a look at something more professionell like "PowerShell App Deployment Toolkit". (http://psappdeploytoolkit.com/)
It will take some time for you to start, but it is not that complex.
It outputs logs and so on.